]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - misc-utils/wipefs.c
Merge branch 'PR/libmount-exec-errors' of github.com:karelzak/util-linux-work
[thirdparty/util-linux.git] / misc-utils / wipefs.c
index 2c1ea59473c863cdb88740dd9e634b3de21c5cd5..148907f27b780e0ee9e5595bdd27a1a300c50959 100644 (file)
 #include <getopt.h>
 #include <string.h>
 #include <limits.h>
+#include <libgen.h>
 
 #include <blkid.h>
+#include <libsmartcols.h>
 
 #include "nls.h"
 #include "xalloc.h"
@@ -54,168 +56,299 @@ struct wipe_desc {
 
        struct wipe_desc        *next;
 
-       unsigned int    zap : 1,
-                       on_disk : 1,
+       unsigned int    on_disk : 1,
                        is_parttable : 1;
 
 };
 
-enum {
-       WP_MODE_PRETTY,         /* default */
-       WP_MODE_PARSABLE
+struct wipe_control {
+       char            *devname;
+       const char      *type_pattern;          /* -t <pattern> */
+       const char      *lockmode;
+       const char      *backup;                /* location of backups */
+
+       struct libscols_table *outtab;
+       struct wipe_desc *offsets;              /* -o <offset> -o <offset> ... */
+
+       size_t          ndevs;                  /* number of devices to probe */
+
+       char            **reread;               /* devices to BLKRRPART */
+       size_t          nrereads;               /* size of reread */
+
+       unsigned int    noact : 1,
+                       all : 1,
+                       quiet : 1,
+                       force : 1,
+                       json : 1,
+                       no_headings : 1,
+                       parsable : 1;
 };
 
+
+/* column IDs */
 enum {
-       WP_FL_NOACT     = (1 << 1),
-       WP_FL_ALL       = (1 << 2),
-       WP_FL_QUIET     = (1 << 3),
-       WP_FL_BACKUP    = (1 << 4),
-       WP_FL_FORCE     = (1 << 5)
+       COL_UUID = 0,
+       COL_LABEL,
+       COL_LEN,
+       COL_TYPE,
+       COL_OFFSET,
+       COL_USAGE,
+       COL_DEVICE
 };
 
-static const char *type_pattern;
+/* column names */
+struct colinfo {
+       const char *name;       /* header */
+       double whint;           /* width hint (N < 1 is in percent of termwidth) */
+       int flags;              /* SCOLS_FL_* */
+       const char *help;
+};
 
-static void
-print_pretty(struct wipe_desc *wp, int line)
+/* columns descriptions */
+static const struct colinfo infos[] = {
+       [COL_UUID]    = {"UUID",     4, 0, N_("partition/filesystem UUID")},
+       [COL_LABEL]   = {"LABEL",    5, 0, N_("filesystem LABEL")},
+       [COL_LEN]     = {"LENGTH",   6, 0, N_("magic string length")},
+       [COL_TYPE]    = {"TYPE",     4, 0, N_("superblock type")},
+       [COL_OFFSET]  = {"OFFSET",   5, 0, N_("magic string offset")},
+       [COL_USAGE]   = {"USAGE",    5, 0, N_("type description")},
+       [COL_DEVICE]  = {"DEVICE",   5, 0, N_("block device name")}
+};
+
+static int columns[ARRAY_SIZE(infos) * 2];
+static size_t ncolumns;
+
+static int column_name_to_id(const char *name, size_t namesz)
 {
-       if (!line) {
-               printf("offset               type\n");
-               printf("----------------------------------------------------------------\n");
+       size_t i;
+
+       assert(name);
+
+       for (i = 0; i < ARRAY_SIZE(infos); i++) {
+               const char *cn = infos[i].name;
+               if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+                       return i;
        }
+       warnx(_("unknown column: %s"), name);
+       return -1;
+}
 
-       printf("0x%-17jx  %s   [%s]", wp->offset, wp->type, _(wp->usage));
+static int get_column_id(size_t num)
+{
+       assert(num < ncolumns);
+       assert(columns[num] < (int)ARRAY_SIZE(infos));
+       return columns[num];
+}
 
-       if (wp->label && *wp->label)
-               printf("\n%27s %s", "LABEL:", wp->label);
-       if (wp->uuid)
-               printf("\n%27s %s", "UUID: ", wp->uuid);
-       puts("\n");
+static const struct colinfo *get_column_info(int num)
+{
+       return &infos[get_column_id(num)];
 }
 
-static void
-print_parsable(struct wipe_desc *wp, int line)
+
+static void init_output(struct wipe_control *ctl)
 {
-       char enc[256];
+       struct libscols_table *tb;
+       size_t i;
 
-       if (!line)
-               printf("# offset,uuid,label,type\n");
+       scols_init_debug(0);
+       tb = scols_new_table();
+       if (!tb)
+               err(EXIT_FAILURE, _("failed to allocate output table"));
 
-       printf("0x%jx,", wp->offset);
+       if (ctl->json) {
+               scols_table_enable_json(tb, 1);
+               scols_table_set_name(tb, "signatures");
+       }
+       scols_table_enable_noheadings(tb, ctl->no_headings);
 
-       if (wp->uuid) {
-               blkid_encode_string(wp->uuid, enc, sizeof(enc));
-               printf("%s,", enc);
-       } else
-               fputc(',', stdout);
+       if (ctl->parsable) {
+               scols_table_enable_raw(tb, 1);
+               scols_table_set_column_separator(tb, ",");
+       }
 
-       if (wp->label) {
-               blkid_encode_string(wp->label, enc, sizeof(enc));
-               printf("%s,", enc);
-       } else
-               fputc(',', stdout);
+       for (i = 0; i < ncolumns; i++) {
+               const struct colinfo *col = get_column_info(i);
+               struct libscols_column *cl;
 
-       blkid_encode_string(wp->type, enc, sizeof(enc));
-       printf("%s\n", enc);
+               cl = scols_table_new_column(tb, col->name, col->whint,
+                                           col->flags);
+               if (!cl)
+                       err(EXIT_FAILURE,
+                           _("failed to initialize output column"));
+               if (ctl->json) {
+                       int id = get_column_id(i);
+
+                       if (id == COL_LEN)
+                               scols_column_set_json_type(cl, SCOLS_JSON_NUMBER);
+               }
+       }
+       ctl->outtab = tb;
 }
 
-static void
-print_all(struct wipe_desc *wp, int mode)
+static void finalize_output(struct wipe_control *ctl)
 {
-       int n = 0;
+       scols_print_table(ctl->outtab);
+       scols_unref_table(ctl->outtab);
+}
 
-       while (wp) {
-               switch (mode) {
-               case WP_MODE_PRETTY:
-                       print_pretty(wp, n++);
+static void fill_table_row(struct wipe_control *ctl, struct wipe_desc *wp)
+{
+       static struct libscols_line *ln;
+       size_t i;
+
+       ln = scols_table_new_line(ctl->outtab, NULL);
+       if (!ln)
+               errx(EXIT_FAILURE, _("failed to allocate output line"));
+
+       for (i = 0; i < ncolumns; i++) {
+               char *str = NULL;
+
+               switch (get_column_id(i)) {
+               case COL_UUID:
+                       if (wp->uuid)
+                               str = xstrdup(wp->uuid);
+                       break;
+               case COL_LABEL:
+                       if (wp->label)
+                               str = xstrdup(wp->label);
+                       break;
+               case COL_OFFSET:
+                       xasprintf(&str, "0x%jx", (intmax_t)wp->offset);
+                       break;
+               case COL_LEN:
+                       xasprintf(&str, "%zu", wp->len);
+                       break;
+               case COL_USAGE:
+                       if (wp->usage)
+                               str = xstrdup(wp->usage);
+                       break;
+               case COL_TYPE:
+                       if (wp->type)
+                               str = xstrdup(wp->type);
                        break;
-               case WP_MODE_PARSABLE:
-                       print_parsable(wp, n++);
+               case COL_DEVICE:
+                       if (ctl->devname) {
+                               char *dev = xstrdup(ctl->devname);
+                               str = xstrdup(basename(dev));
+                               free(dev);
+                       }
                        break;
                default:
                        abort();
                }
-               wp = wp->next;
+
+               if (str && scols_line_refer_data(ln, i, str))
+                       errx(EXIT_FAILURE, _("failed to add output data"));
        }
 }
 
-static struct wipe_desc *
-add_offset(struct wipe_desc *wp0, loff_t offset, int zap)
+static void add_to_output(struct wipe_control *ctl, struct wipe_desc *wp)
 {
-       struct wipe_desc *wp = wp0;
+       for (/*nothing*/; wp; wp = wp->next)
+               fill_table_row(ctl, wp);
+}
 
-       while (wp) {
-               if (wp->offset == offset)
-                       return wp;
-               wp = wp->next;
+/* Allocates a new wipe_desc and add to the wp0 if not NULL */
+static struct wipe_desc *add_offset(struct wipe_desc **wp0, loff_t offset)
+{
+       struct wipe_desc *wp, *last = NULL;
+
+       if (wp0) {
+               /* check if already exists */
+               for (wp = *wp0; wp; wp = wp->next) {
+                       if (wp->offset == offset)
+                               return wp;
+                       last = wp;
+               }
        }
 
        wp = xcalloc(1, sizeof(struct wipe_desc));
        wp->offset = offset;
-       wp->next = wp0;
-       wp->zap = zap ? 1 : 0;
-       return wp;
-}
-
-static struct wipe_desc *
-clone_offset(struct wipe_desc *wp0)
-{
-       struct wipe_desc *wp = NULL;
-
-       while(wp0) {
-               wp = add_offset(wp, wp0->offset, wp0->zap);
-               wp0 = wp0->next;
-       }
+       wp->next = NULL;
 
+       if (last)
+               last->next = wp;
+       if (wp0 && !*wp0)
+               *wp0 = wp;
        return wp;
 }
 
-static struct wipe_desc *
-get_desc_for_probe(struct wipe_desc *wp, blkid_probe pr)
+/* Read data from libblkid and if detected type pass -t and -o filters than:
+ * - allocates a new wipe_desc
+ * - add the new wipe_desc to wp0 list (if not NULL)
+ *
+ * The function always returns offset and len if libblkid detected something.
+ */
+static struct wipe_desc *get_desc_for_probe(struct wipe_control *ctl,
+                                           struct wipe_desc **wp0,
+                                           blkid_probe pr,
+                                           loff_t *offset,
+                                           size_t *len)
 {
-       const char *off, *type, *mag, *p, *usage = NULL;
-       size_t len;
-       loff_t offset;
+       const char *off, *type, *mag, *p, *use = NULL;
+       struct wipe_desc *wp;
        int rc, ispt = 0;
 
+       *len = 0;
+
        /* superblocks */
        if (blkid_probe_lookup_value(pr, "TYPE", &type, NULL) == 0) {
                rc = blkid_probe_lookup_value(pr, "SBMAGIC_OFFSET", &off, NULL);
                if (!rc)
-                       rc = blkid_probe_lookup_value(pr, "SBMAGIC", &mag, &len);
+                       rc = blkid_probe_lookup_value(pr, "SBMAGIC", &mag, len);
                if (rc)
-                       return wp;
+                       return NULL;
 
        /* partitions */
        } else if (blkid_probe_lookup_value(pr, "PTTYPE", &type, NULL) == 0) {
                rc = blkid_probe_lookup_value(pr, "PTMAGIC_OFFSET", &off, NULL);
                if (!rc)
-                       rc = blkid_probe_lookup_value(pr, "PTMAGIC", &mag, &len);
+                       rc = blkid_probe_lookup_value(pr, "PTMAGIC", &mag, len);
                if (rc)
-                       return wp;
-               usage = N_("partition table");
+                       return NULL;
+               use = N_("partition-table");
                ispt = 1;
        } else
-               return wp;
+               return NULL;
 
-       if (type_pattern && !match_fstype(type, type_pattern))
-               return wp;
+       errno = 0;
+       *offset = strtoll(off, NULL, 10);
+       if (errno)
+               return NULL;
+
+       /* Filter out by -t <type> */
+       if (ctl->type_pattern && !match_fstype(type, ctl->type_pattern))
+               return NULL;
+
+       /* Filter out by -o <offset> */
+       if (ctl->offsets) {
+               struct wipe_desc *w = NULL;
+
+               for (w = ctl->offsets; w; w = w->next) {
+                       if (w->offset == *offset)
+                               break;
+               }
+               if (!w)
+                       return NULL;
 
-       offset = strtoll(off, NULL, 10);
+               w->on_disk = 1; /* mark as "found" */
+       }
 
-       wp = add_offset(wp, offset, 0);
+       wp = add_offset(wp0, *offset);
        if (!wp)
                return NULL;
 
-       if (usage || blkid_probe_lookup_value(pr, "USAGE", &usage, NULL) == 0)
-               wp->usage = xstrdup(usage);
+       if (use || blkid_probe_lookup_value(pr, "USAGE", &use, NULL) == 0)
+               wp->usage = xstrdup(use);
 
        wp->type = xstrdup(type);
        wp->on_disk = 1;
        wp->is_parttable = ispt ? 1 : 0;
 
-       wp->magic = xmalloc(len);
-       memcpy(wp->magic, mag, len);
-       wp->len = len;
+       wp->magic = xmalloc(*len);
+       memcpy(wp->magic, mag, *len);
+       wp->len = *len;
 
        if (blkid_probe_lookup_value(pr, "LABEL", &p, NULL) == 0)
                wp->label = xstrdup(p);
@@ -235,12 +368,12 @@ new_probe(const char *devname, int mode)
                return NULL;
 
        if (mode) {
-               int fd = open(devname, mode);
+               int fd = open(devname, mode | O_NONBLOCK);
                if (fd < 0)
                        goto error;
 
                pr = blkid_new_probe();
-               if (pr && blkid_probe_set_device(pr, fd, 0, 0)) {
+               if (!pr || blkid_probe_set_device(pr, fd, 0, 0) != 0) {
                        close(fd);
                        goto error;
                }
@@ -260,35 +393,41 @@ new_probe(const char *devname, int mode)
                        BLKID_SUBLKS_BADCSUM);  /* accept bad checksums */
 
        blkid_probe_enable_partitions(pr, 1);
-       blkid_probe_set_partitions_flags(pr, BLKID_PARTS_MAGIC);
-
+       blkid_probe_set_partitions_flags(pr, BLKID_PARTS_MAGIC |
+                                            BLKID_PARTS_FORCE_GPT);
        return pr;
 error:
        blkid_free_probe(pr);
        err(EXIT_FAILURE, _("error: %s: probing initialization failed"), devname);
-       return NULL;
 }
 
-static struct wipe_desc *
-read_offsets(struct wipe_desc *wp, const char *devname)
+static struct wipe_desc *read_offsets(struct wipe_control *ctl)
 {
-       blkid_probe pr = new_probe(devname, 0);
+       blkid_probe pr = new_probe(ctl->devname, 0);
+       struct wipe_desc *wp0 = NULL;
 
        if (!pr)
                return NULL;
 
        while (blkid_do_probe(pr) == 0) {
-               wp = get_desc_for_probe(wp, pr);
-               if (!wp)
-                       break;
+               size_t len = 0;
+               loff_t offset = 0;
+
+               /* add a new offset to wp0 */
+               get_desc_for_probe(ctl, &wp0, pr, &offset, &len);
+
+               /* hide last detected signature and scan again */
+               if (len) {
+                       blkid_probe_hide_range(pr, offset, len);
+                       blkid_probe_step_back(pr);
+               }
        }
 
        blkid_free_probe(pr);
-       return wp;
+       return wp0;
 }
 
-static void
-free_wipe(struct wipe_desc *wp)
+static void free_wipe(struct wipe_desc *wp)
 {
        while (wp) {
                struct wipe_desc *next = wp->next;
@@ -304,22 +443,22 @@ free_wipe(struct wipe_desc *wp)
        }
 }
 
-static void do_wipe_real(blkid_probe pr, const char *devname,
-                       struct wipe_desc *w, int flags)
+static void do_wipe_real(struct wipe_control *ctl, blkid_probe pr,
+                       struct wipe_desc *w)
 {
        size_t i;
 
-       if (blkid_do_wipe(pr, (flags & WP_FL_NOACT) != 0))
-               warn(_("%s: failed to erase %s magic string at offset 0x%08jx"),
-                    devname, w->type, w->offset);
+       if (blkid_do_wipe(pr, ctl->noact) != 0)
+               err(EXIT_FAILURE, _("%s: failed to erase %s magic string at offset 0x%08jx"),
+                    ctl->devname, w->type, (intmax_t)w->offset);
 
-       if (flags & WP_FL_QUIET)
+       if (ctl->quiet)
                return;
 
        printf(P_("%s: %zd byte was erased at offset 0x%08jx (%s): ",
                  "%s: %zd bytes were erased at offset 0x%08jx (%s): ",
                  w->len),
-              devname, w->len, w->offset, w->type);
+              ctl->devname, w->len, (intmax_t)w->offset, w->type);
 
        for (i = 0; i < w->len; i++) {
                printf("%02x", w->magic[i]);
@@ -334,7 +473,7 @@ static void do_backup(struct wipe_desc *wp, const char *base)
        char *fname = NULL;
        int fd;
 
-       xasprintf(&fname, "%s0x%08jx.bak", base, wp->offset);
+       xasprintf(&fname, "%s0x%08jx.bak", base, (intmax_t)wp->offset);
 
        fd = open(fname, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
        if (fd < 0)
@@ -348,151 +487,210 @@ err:
        err(EXIT_FAILURE, _("%s: failed to create a signature backup"), fname);
 }
 
+#ifdef BLKRRPART
 static void rereadpt(int fd, const char *devname)
 {
-#ifdef BLKRRPART
        struct stat st;
+       int try = 0;
 
        if (fstat(fd, &st) || !S_ISBLK(st.st_mode))
                return;
 
-       errno = 0;
-       ioctl(fd, BLKRRPART);
-       printf(_("%s: calling ioclt to re-read partition table: %m\n"), devname);
-#endif
+       do {
+               /*
+                * Unfortunately, it's pretty common that the first re-read
+                * without delay is uncuccesful. The reason is probably kernel
+                * and/or udevd.  Let's wait a moment and try more attempts.
+                */
+               xusleep(250000);
+
+               errno = 0;
+               ioctl(fd, BLKRRPART);
+               if (errno != EBUSY)
+                       break;
+       } while (try++ < 4);
+
+       printf(_("%s: calling ioctl to re-read partition table: %m\n"), devname);
 }
+#endif
 
-static struct wipe_desc *
-do_wipe(struct wipe_desc *wp, const char *devname, int flags)
+static int do_wipe(struct wipe_control *ctl)
 {
        int mode = O_RDWR, reread = 0, need_force = 0;
        blkid_probe pr;
-       struct wipe_desc *w, *wp0;
-       int zap = (flags & WP_FL_ALL) ? 1 : wp->zap;
        char *backup = NULL;
+       struct wipe_desc *w;
 
-       if (!(flags & WP_FL_FORCE))
+       if (!ctl->force)
                mode |= O_EXCL;
-       pr = new_probe(devname, mode);
+
+       pr = new_probe(ctl->devname, mode);
        if (!pr)
-               return NULL;
+               return -errno;
 
-       if (zap && (flags & WP_FL_BACKUP)) {
-               const char *home = getenv ("HOME");
-               if (!home)
-                       errx(EXIT_FAILURE, _("failed to create a signature backup, $HOME undefined"));
-               xasprintf (&backup, "%s/wipefs-%s-", home, basename(devname));
+       if (blkdev_lock(blkid_probe_get_fd(pr),
+                       ctl->devname, ctl->lockmode) != 0) {
+               blkid_free_probe(pr);
+               return -1;
        }
 
-       wp0 = clone_offset(wp);
+       if (ctl->backup) {
+               char *tmp = xstrdup(ctl->devname);
 
-       while (blkid_do_probe(pr) == 0) {
-               wp = get_desc_for_probe(wp, pr);
-               if (!wp)
-                       break;
-
-               /* Check if offset is in provided list */
-               w = wp0;
-               while(w && w->offset != wp->offset)
-                       w = w->next;
-               if (wp0 && !w)
-                       continue;
+               xasprintf(&backup, "%s/wipefs-%s-", ctl->backup, basename(tmp));
+               free(tmp);
+       }
 
-               /* Mark done if found in provided list */
-               if (w)
-                       w->on_disk = wp->on_disk;
+       while (blkid_do_probe(pr) == 0) {
+               int wiped = 0;
+               size_t len = 0;
+               loff_t offset = 0;
+               struct wipe_desc *wp;
 
-               if (!wp->on_disk)
-                       continue;
+               wp = get_desc_for_probe(ctl, NULL, pr, &offset, &len);
+               if (!wp)
+                       goto done;
 
-               if (!(flags & WP_FL_FORCE)
+               if (!ctl->force
                    && wp->is_parttable
                    && !blkid_probe_is_wholedisk(pr)) {
-                       warnx(_("%s: ignore nested \"%s\" partition "
-                               "table on non-whole disk device."), devname, wp->type);
+                       warnx(_("%s: ignoring nested \"%s\" partition table "
+                               "on non-whole disk device"), ctl->devname, wp->type);
                        need_force = 1;
-                       continue;
+                       goto done;
                }
 
-               if (zap) {
-                       if (backup)
-                               do_backup(wp, backup);
-                       do_wipe_real(pr, devname, wp, flags);
-                       if (wp->is_parttable)
-                               reread = 1;
+               if (backup)
+                       do_backup(wp, backup);
+               do_wipe_real(ctl, pr, wp);
+               if (wp->is_parttable)
+                       reread = 1;
+               wiped = 1;
+       done:
+               if (!wiped && len) {
+                       /* if the offset has not been wiped (probably because
+                        * filtered out by -t or -o) we need to hide it for
+                        * libblkid to try another magic string for the same
+                        * superblock, otherwise libblkid will continue with
+                        * another superblock. Don't forget that the same
+                        * superblock could be detected by more magic strings
+                        * */
+                       blkid_probe_hide_range(pr, offset, len);
+                       blkid_probe_step_back(pr);
                }
+               free_wipe(wp);
        }
 
-       for (w = wp0; w != NULL; w = w->next) {
-               if (!w->on_disk && !(flags & WP_FL_QUIET))
-                       warnx(_("%s: offset 0x%jx not found"), devname, w->offset);
+       for (w = ctl->offsets; w; w = w->next) {
+               if (!w->on_disk && !ctl->quiet)
+                       warnx(_("%s: offset 0x%jx not found"),
+                                       ctl->devname, (uintmax_t)w->offset);
        }
 
        if (need_force)
                warnx(_("Use the --force option to force erase."));
 
-       fsync(blkid_probe_get_fd(pr));
+       if (fsync(blkid_probe_get_fd(pr)) != 0)
+               err(EXIT_FAILURE, _("%s: cannot flush modified buffers"),
+                               ctl->devname);
+
+#ifdef BLKRRPART
+       if (reread && (mode & O_EXCL)) {
+               if (ctl->ndevs > 1) {
+                       /*
+                        * We're going to probe more device, let's postpone
+                        * re-read PT ioctl until all is erased to avoid
+                        * situation we erase PT on /dev/sda before /dev/sdaN
+                        * devices are processed.
+                        */
+                       if (!ctl->reread)
+                               ctl->reread = xcalloc(ctl->ndevs, sizeof(char *));
+
+                       ctl->reread[ctl->nrereads++] = ctl->devname;
+               } else
+                       rereadpt(blkid_probe_get_fd(pr), ctl->devname);
+       }
+#endif
 
-       if (reread && (mode & O_EXCL))
-               rereadpt(blkid_probe_get_fd(pr), devname);
+       if (close(blkid_probe_get_fd(pr)) != 0)
+               err(EXIT_FAILURE, _("%s: close device failed"), ctl->devname);
 
-       close(blkid_probe_get_fd(pr));
        blkid_free_probe(pr);
-       free_wipe(wp0);
        free(backup);
-
-       return wp;
+       return 0;
 }
 
 
 static void __attribute__((__noreturn__))
-usage(FILE *out)
+usage(void)
 {
-       fputs(_("\nUsage:\n"), out);
-       fprintf(out,
-             _(" %s [options] <device>\n"), program_invocation_short_name);
-
-       fputs(_("\nOptions:\n"), out);
-       fputs(_(" -a, --all           wipe all magic strings (BE CAREFUL!)\n"
-               " -b, --backup        create a signature backup in $HOME\n"
-               " -f, --force         force erasure\n"
-               " -h, --help          show this help text\n"
-               " -n, --no-act        do everything except the actual write() call\n"
-               " -o, --offset <num>  offset to erase, in bytes\n"
-               " -p, --parsable      print out in parsable instead of printable format\n"
-               " -q, --quiet         suppress output messages\n"
-               " -t, --types <list>  limit the set of filesystem, RAIDs or partition tables\n"
-               " -V, --version       output version information and exit\n"), out);
-
-       fprintf(out, _("\nFor more information see wipefs(8).\n"));
-
-       exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+       size_t i;
+
+       fputs(USAGE_HEADER, stdout);
+       fprintf(stdout, _(" %s [options] <device>\n"), program_invocation_short_name);
+
+       fputs(USAGE_SEPARATOR, stdout);
+       fputsln(_("Wipe signatures from a device."), stdout);
+
+       fputs(USAGE_OPTIONS, stdout);
+       fputsln(_(" -a, --all            wipe all magic strings (BE CAREFUL!)"), stdout);
+       fputsln(_(" -b, --backup[=<dir>] create a signature backup in <dir> or $HOME"), stdout);
+       fputsln(_(" -f, --force          force erasure"), stdout);
+       fputsln(_(" -i, --noheadings     don't print headings"), stdout);
+       fputsln(_(" -J, --json           use JSON output format"), stdout);
+       fputsln(_(" -n, --no-act         do everything except the actual write() call"), stdout);
+       fputsln(_(" -o, --offset <num>   offset to erase, in bytes"), stdout);
+       fputsln(_(" -O, --output <list>  COLUMNS to display (see below)"), stdout);
+       fputsln(_(" -p, --parsable       print out in parsable instead of printable format"), stdout);
+       fputsln(_(" -q, --quiet          suppress output messages"), stdout);
+       fputsln(_(" -t, --types <list>   limit the set of filesystem, RAIDs or partition tables"), stdout);
+       fprintf(stdout,
+            _("     --lock[=<mode>] use exclusive device lock (%s, %s or %s)\n"), "yes", "no", "nonblock");
+
+       fprintf(stdout, USAGE_HELP_OPTIONS(22));
+
+       fputs(USAGE_ARGUMENTS, stdout);
+       fprintf(stdout, USAGE_ARG_SIZE(_("<num>")));
+
+       fputs(USAGE_COLUMNS, stdout);
+       for (i = 0; i < ARRAY_SIZE(infos); i++)
+               fprintf(stdout, " %8s  %s\n", infos[i].name, _(infos[i].help));
+
+       fprintf(stdout, USAGE_MAN_TAIL("wipefs(8)"));
+       exit(EXIT_SUCCESS);
 }
 
 
 int
 main(int argc, char **argv)
 {
-       struct wipe_desc *wp0 = NULL, *wp;
-       int c, has_offset = 0, flags = 0;
-       int mode = WP_MODE_PRETTY;
-
+       struct wipe_control ctl = { .devname = NULL };
+       int c;
+       size_t i;
+       char *outarg = NULL;
+       enum {
+               OPT_LOCK = CHAR_MAX + 1,
+       };
        static const struct option longopts[] = {
-           { "all",       0, 0, 'a' },
-           { "backup",    0, 0, 'b' },
-           { "force",     0, 0, 'f' },
-           { "help",      0, 0, 'h' },
-           { "no-act",    0, 0, 'n' },
-           { "offset",    1, 0, 'o' },
-           { "parsable",  0, 0, 'p' },
-           { "quiet",     0, 0, 'q' },
-           { "types",     1, 0, 't' },
-           { "version",   0, 0, 'V' },
-           { NULL,        0, 0, 0 }
+           { "all",       no_argument,       NULL, 'a' },
+           { "backup",    optional_argument, NULL, 'b' },
+           { "force",     no_argument,       NULL, 'f' },
+           { "help",      no_argument,       NULL, 'h' },
+           { "lock",      optional_argument, NULL, OPT_LOCK },
+           { "no-act",    no_argument,       NULL, 'n' },
+           { "offset",    required_argument, NULL, 'o' },
+           { "parsable",  no_argument,       NULL, 'p' },
+           { "quiet",     no_argument,       NULL, 'q' },
+           { "types",     required_argument, NULL, 't' },
+           { "version",   no_argument,       NULL, 'V' },
+           { "json",      no_argument,       NULL, 'J'},
+           { "noheadings",no_argument,       NULL, 'i'},
+           { "output",    required_argument, NULL, 'O'},
+           { NULL,        0, NULL, 0 }
        };
 
-       static const ul_excl_t excl[] = {       /* rows and cols in in ASCII order */
-               { 'a','o' },
+       static const ul_excl_t excl[] = {       /* rows and cols in ASCII order */
+               { 'O','a','o' },
                { 0 }
        };
        int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
@@ -500,77 +698,144 @@ main(int argc, char **argv)
        setlocale(LC_ALL, "");
        bindtextdomain(PACKAGE, LOCALEDIR);
        textdomain(PACKAGE);
-       atexit(close_stdout);
+       close_stdout_atexit();
 
-       while ((c = getopt_long(argc, argv, "afhno:pqt:V", longopts, NULL)) != -1) {
+       while ((c = getopt_long(argc, argv, "ab::fhiJnO:o:pqt:V", longopts, NULL)) != -1) {
 
                err_exclusive_options(c, longopts, excl, excl_st);
 
                switch(c) {
                case 'a':
-                       flags |= WP_FL_ALL;
+                       ctl.all = 1;
                        break;
                case 'b':
-                       flags |= WP_FL_BACKUP;
+                       if (optarg) {
+                               ctl.backup = optarg;
+                       } else {
+                               ctl.backup = getenv("HOME");
+                               if (!ctl.backup)
+                                       errx(EXIT_FAILURE,
+                                            _("failed to create a signature backup, $HOME undefined"));
+                       }
                        break;
                case 'f':
-                       flags |= WP_FL_FORCE;
+                       ctl.force = 1;
                        break;
-               case 'h':
-                       usage(stdout);
+               case 'J':
+                       ctl.json = 1;
+                       break;
+               case 'i':
+                       ctl.no_headings = 1;
+                       break;
+               case 'O':
+                       outarg = optarg;
                        break;
                case 'n':
-                       flags |= WP_FL_NOACT;
+                       ctl.noact = 1;
                        break;
                case 'o':
-                       wp0 = add_offset(wp0, strtosize_or_err(optarg,
-                                        _("invalid offset argument")), 1);
-                       has_offset++;
+                       add_offset(&ctl.offsets, strtosize_or_err(optarg,
+                                        _("invalid offset argument")));
                        break;
                case 'p':
-                       mode = WP_MODE_PARSABLE;
+                       ctl.parsable = 1;
+                       ctl.no_headings = 1;
                        break;
                case 'q':
-                       flags |= WP_FL_QUIET;
+                       ctl.quiet = 1;
                        break;
                case 't':
-                       type_pattern = optarg;
+                       ctl.type_pattern = optarg;
+                       break;
+               case OPT_LOCK:
+                       ctl.lockmode = "1";
+                       if (optarg) {
+                               if (*optarg == '=')
+                                       optarg++;
+                               ctl.lockmode = optarg;
+                       }
                        break;
+               case 'h':
+                       usage();
                case 'V':
-                       printf(UTIL_LINUX_VERSION);
-                       return EXIT_SUCCESS;
+                       print_version(EXIT_SUCCESS);
                default:
-                       usage(stderr);
-                       break;
+                       errtryhelp(EXIT_FAILURE);
                }
        }
 
-       if (optind == argc)
-               usage(stderr);
+       if (optind == argc) {
+               warnx(_("no device specified"));
+               errtryhelp(EXIT_FAILURE);
+
+       }
 
-       if ((flags & WP_FL_BACKUP) && !((flags & WP_FL_ALL) || has_offset))
+       if (ctl.backup && !(ctl.all || ctl.offsets))
                warnx(_("The --backup option is meaningless in this context"));
 
-       if (!(flags & WP_FL_ALL) && !has_offset) {
+       if (!ctl.all && !ctl.offsets) {
                /*
                 * Print only
                 */
+               if (ctl.parsable) {
+                       /* keep it backward compatible */
+                       columns[ncolumns++] = COL_OFFSET;
+                       columns[ncolumns++] = COL_UUID;
+                       columns[ncolumns++] = COL_LABEL;
+                       columns[ncolumns++] = COL_TYPE;
+               } else {
+                       /* default, may be modified by -O <list> */
+                       columns[ncolumns++] = COL_DEVICE;
+                       columns[ncolumns++] = COL_OFFSET;
+                       columns[ncolumns++] = COL_TYPE;
+                       columns[ncolumns++] = COL_UUID;
+                       columns[ncolumns++] = COL_LABEL;
+               }
+
+               if (outarg
+                   && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
+                                            &ncolumns, column_name_to_id) < 0)
+                       return EXIT_FAILURE;
+
+               init_output(&ctl);
+
                while (optind < argc) {
-                       wp0 = read_offsets(NULL, argv[optind++]);
-                       if (wp0)
-                               print_all(wp0, mode);
-                       free_wipe(wp0);
+                       struct wipe_desc *wp;
+
+                       ctl.devname = argv[optind++];
+                       wp = read_offsets(&ctl);
+                       if (wp)
+                               add_to_output(&ctl, wp);
+                       free_wipe(wp);
                }
+               finalize_output(&ctl);
        } else {
                /*
                 * Erase
                 */
+               ctl.ndevs = argc - optind;
+
                while (optind < argc) {
-                       wp = clone_offset(wp0);
-                       wp = do_wipe(wp, argv[optind++], flags);
-                       free_wipe(wp);
+                       ctl.devname = argv[optind++];
+                       do_wipe(&ctl);
+                       ctl.ndevs--;
                }
-       }
 
+#ifdef BLKRRPART
+               /* Re-read partition tables on whole-disk devices. This is
+                * postponed until all is done to avoid conflicts.
+                */
+               for (i = 0; i < ctl.nrereads; i++) {
+                       char *devname = ctl.reread[i];
+                       int fd = open(devname, O_RDONLY);
+
+                       if (fd >= 0) {
+                               rereadpt(fd, devname);
+                               close(fd);
+                       }
+               }
+               free(ctl.reread);
+#endif
+       }
        return EXIT_SUCCESS;
 }