]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - misc-utils/blkid.c
misc: fix xalloc.h related exit codes
[thirdparty/util-linux.git] / misc-utils / blkid.c
index 651deb3d83a3967790653f7f788fdd56a9826426..3e6f1ce0622481d6f4d11cfe00129d73698a75a3 100644 (file)
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
-#include <termios.h>
 #include <errno.h>
-#ifdef HAVE_SYS_IOCTL_H
-#include <sys/ioctl.h>
-#endif
-#ifdef HAVE_GETOPT_H
 #include <getopt.h>
-#else
-extern int getopt(int argc, char * const argv[], const char *optstring);
-extern char *optarg;
-extern int optind;
-#endif
 
-#define OUTPUT_VALUE_ONLY      0x0001
-#define OUTPUT_DEVICE_ONLY     0x0002
-#define OUTPUT_PRETTY_LIST     0x0004
-#define OUTPUT_UDEV_LIST       0x0008
+#define OUTPUT_FULL            (1 << 0)
+#define OUTPUT_VALUE_ONLY      (1 << 1)
+#define OUTPUT_DEVICE_ONLY     (1 << 2)
+#define OUTPUT_PRETTY_LIST     (1 << 3)                /* deprecated */
+#define OUTPUT_UDEV_LIST       (1 << 4)                /* deprecated */
+#define OUTPUT_EXPORT_LIST     (1 << 5)
+
+#define BLKID_EXIT_NOTFOUND    2       /* token or device not found */
+#define BLKID_EXIT_OTHER       4       /* bad usage or other error */
+#define BLKID_EXIT_AMBIVAL     8       /* ambivalent low-level probing detected */
 
 #include <blkid.h>
 
 #include "ismounted.h"
-#include "strtosize.h"
 
-const char *progname = "blkid";
+#include "strutils.h"
+#define OPTUTILS_EXIT_CODE     BLKID_EXIT_OTHER        /* exclusive_option() */
+#include "optutils.h"
+#define CLOSE_EXIT_CODE                BLKID_EXIT_OTHER        /* close_stdout() */
+#include "closestream.h"
+
+#include "nls.h"
+#include "ttyutils.h"
+
+#define XALLOC_EXIT_CODE    BLKID_EXIT_OTHER    /* x.*alloc(), xstrndup() */
+#include "xalloc.h"
+
+struct blkid_control {
+       int output;
+       uintmax_t offset;
+       uintmax_t size;
+       char *show[128];
+       unsigned int
+               eval:1,
+               gc:1,
+               lookup:1,
+               lowprobe:1,
+               lowprobe_superblocks:1,
+               lowprobe_topology:1,
+               raw_chars:1;
+};
 
 static void print_version(FILE *out)
 {
-       fprintf(out, "%s from %s (libblkid %s, %s)\n",
-               progname, PACKAGE_STRING, LIBBLKID_VERSION, LIBBLKID_DATE);
+       fprintf(out, _("%s from %s  (libblkid %s, %s)\n"),
+               program_invocation_short_name, PACKAGE_STRING,
+               LIBBLKID_VERSION, LIBBLKID_DATE);
 }
 
 static void usage(int error)
 {
        FILE *out = error ? stderr : stdout;
 
-       print_version(out);
-       fprintf(out,
-               "Usage:\n"
-               "  %1$s -L <label> | -U <uuid>\n\n"
-               "  %1$s [-c <file>] [-ghlLv] [-o format] [-s <tag>] \n"
-               "        [-t <token>] [-w <file>] [dev ...]\n\n"
-               "  %1$s -p [-O <offset>] [-S <size>] [-o format] <dev> [dev ...]\n\n"
-               "Options:\n"
-               "  -c <file>   cache file (default: /etc/blkid.tab, /dev/null = none)\n"
-               "  -h          print this usage message and exit\n"
-               "  -g          garbage collect the blkid cache\n"
-               "  -o <format> output format; can be one of:\n"
-               "              value, device, list, udev or full; (default: full)\n"
-               "  -s <tag>    show specified tag(s) (default show all tags)\n"
-               "  -t <token>  find device with a specific token (NAME=value pair)\n"
-               "  -l          lookup the the first device with arguments specified by -t\n"
-               "  -L <label>  convert LABEL to device name\n"
-               "  -U <uuid>   convert UUID to device name\n"
-               "  -v          print version and exit\n"
-               "  -w <file>   write cache to different file (/dev/null = no write)\n"
-               "  <dev>       specify device(s) to probe (default: all devices)\n\n"
-               "Low-level probing options:\n"
-               "  -p          switch to low-level mode (bypass cache)\n"
-               "  -S <size>   overwrite device size\n"
-               "  -O <offset> probe at the given offset\n"
-               "  -u <list>   filter by \"usage\" (e.g. -u filesystem,raid)\n"
-               "  -n <list>   filter by filesystem type (e.g. -n vfat,ext3)\n"
-               "\n",
-                               progname);
-
+       fputs(USAGE_HEADER, out);
+       fprintf(out, _( " %s --label <label> | --uuid <uuid>\n\n"), program_invocation_short_name);
+       fprintf(out, _( " %s [--cache-file <file>] [-ghlLv] [--output <format>] [--match-tag <tag>] \n"
+                       "       [--match-token <token>] [<dev> ...]\n\n"), program_invocation_short_name);
+       fprintf(out, _( " %s -p [--match-tag <tag>] [--offset <offset>] [--size <size>] \n"
+                       "       [--output <format>] <dev> ...\n\n"), program_invocation_short_name);
+       fprintf(out, _( " %s -i [--match-tag <tag>] [--output <format>] <dev> ...\n"), program_invocation_short_name);
+       fputs(USAGE_OPTIONS, out);
+       fputs(_(        " -c, --cache-file <file>    read from <file> instead of reading from the default\n"
+                       "                              cache file (-c /dev/null means no cache)\n"), out);
+       fputs(_(        " -d, --no-encoding          don't encode non-printing characters\n"), out);
+       fputs(_(        " -g, --garbage-collect      garbage collect the blkid cache\n"), out);
+       fputs(_(        " -o, --output <format>      output format; can be one of:\n"
+                       "                              value, device, export or full; (default: full)\n"), out);
+       fputs(_(        " -k, --list-filesystems     list all known filesystems/RAIDs and exit\n"), out);
+       fputs(_(        " -s, --match-tag <tag>      show specified tag(s) (default show all tags)\n"), out);
+       fputs(_(        " -t, --match-token <token>  find device with a specific token (NAME=value pair)\n"), out);
+       fputs(_(        " -l, --list-one             look up only first device with token specified by -t\n"), out);
+       fputs(_(        " -L, --label <label>        convert LABEL to device name\n"), out);
+       fputs(_(        " -U, --uuid <uuid>          convert UUID to device name\n"), out);
+       fputs(_(        " <dev>                      specify device(s) to probe (default: all devices)\n"), out);
+       fputs(          "\n", out);
+       fputs(_(        "Low-level probing options:\n"), out);
+       fputs(_(        " -p, --probe                low-level superblocks probing (bypass cache)\n"), out);
+       fputs(_(        " -i, --info                 gather information about I/O limits\n"), out);
+       fputs(_(        " -S, --size <size>          overwrite device size\n"), out);
+       fputs(_(        " -O, --offset <offset>      probe at the given offset\n"), out);
+       fputs(_(        " -u, --usages <list>        filter by \"usage\" (e.g. -u filesystem,raid)\n"), out);
+       fputs(_(        " -n, --match-types <list>   filter by filesystem type (e.g. -n vfat,ext3)\n"), out);
+
+       fputs(USAGE_SEPARATOR, out);
+       fputs(USAGE_HELP, out);
+       fputs(USAGE_VERSION, out);
+       fprintf(out, USAGE_MAN_TAIL("blkid(8)"));
        exit(error);
 }
 
 /*
  * This function does "safe" printing.  It will convert non-printable
  * ASCII characters using '^' and M- notation.
+ *
+ * If 'esc' is defined then escape all chars from esc by \.
  */
-static void safe_print(const char *cp, int len)
+static void safe_print(const struct blkid_control *ctl, const char *cp, int len,
+                      const char *esc)
 {
        unsigned char   ch;
 
@@ -97,42 +125,22 @@ static void safe_print(const char *cp, int len)
 
        while (len--) {
                ch = *cp++;
-               if (ch > 128) {
-                       fputs("M-", stdout);
-                       ch -= 128;
-               }
-               if ((ch < 32) || (ch == 0x7f)) {
-                       fputc('^', stdout);
-                       ch ^= 0x40; /* ^@, ^A, ^B; ^? for DEL */
+               if (!ctl->raw_chars) {
+                       if (ch >= 128) {
+                               fputs("M-", stdout);
+                               ch -= 128;
+                       }
+                       if ((ch < 32) || (ch == 0x7f)) {
+                               fputc('^', stdout);
+                               ch ^= 0x40; /* ^@, ^A, ^B; ^? for DEL */
+
+                       } else if (esc && strchr(esc, ch))
+                               fputc('\\', stdout);
                }
                fputc(ch, stdout);
        }
 }
 
-static int get_terminal_width(void)
-{
-#ifdef TIOCGSIZE
-       struct ttysize  t_win;
-#endif
-#ifdef TIOCGWINSZ
-       struct winsize  w_win;
-#endif
-        const char     *cp;
-
-#ifdef TIOCGSIZE
-       if (ioctl (0, TIOCGSIZE, &t_win) == 0)
-               return (t_win.ts_cols);
-#endif
-#ifdef TIOCGWINSZ
-       if (ioctl (0, TIOCGWINSZ, &w_win) == 0)
-               return (w_win.ws_col);
-#endif
-        cp = getenv("COLUMNS");
-       if (cp)
-               return strtol(cp, NULL, 10);
-       return 80;
-}
-
 static int pretty_print_word(const char *str, int max_len,
                             int left_len, int overflow_nl)
 {
@@ -145,9 +153,9 @@ static int pretty_print_word(const char *str, int max_len,
                len = 0;
        } else if (len > max_len)
                ret = len - max_len;
-       do
+       do {
                fputc(' ', stdout);
-       while (len++ < max_len);
+       while (len++ < max_len);
        return ret;
 }
 
@@ -160,9 +168,9 @@ static void pretty_print_line(const char *device, const char *fs_type,
        static int term_width = -1;
        int len, w;
 
-       if (term_width < 0)
-               term_width = get_terminal_width();
-
+       if (term_width < 0) {
+               term_width = get_terminal_width(80);
+       }
        if (term_width > 80) {
                term_width -= 80;
                w = term_width / 10;
@@ -179,7 +187,8 @@ static void pretty_print_line(const char *device, const char *fs_type,
        len = pretty_print_word(device, device_len, 0, 1);
        len = pretty_print_word(fs_type, fs_type_len, len, 0);
        len = pretty_print_word(label, label_len, len, 0);
-       len = pretty_print_word(mtpt, mtpt_len, len, 0);
+       pretty_print_word(mtpt, mtpt_len, len, 0);
+
        fputs(uuid, stdout);
        fputc('\n', stdout);
 }
@@ -196,7 +205,7 @@ static void pretty_print_dev(blkid_dev dev)
        if (dev == NULL) {
                pretty_print_line("device", "fs_type", "label",
                                  "mount point", "UUID");
-               for (len=get_terminal_width()-1; len > 0; len--)
+               for (len=get_terminal_width(0)-1; len > 0; len--)
                        fputc('-', stdout);
                fputc('\n', stdout);
                return;
@@ -224,19 +233,20 @@ static void pretty_print_dev(blkid_dev dev)
        if (retval == 0) {
                if (mount_flags & MF_MOUNTED) {
                        if (!mtpt[0])
-                               strcpy(mtpt, "(mounted, mtpt unknown)");
+                               strcpy(mtpt, _("(mounted, mtpt unknown)"));
                } else if (mount_flags & MF_BUSY)
-                       strcpy(mtpt, "(in use)");
+                       strcpy(mtpt, _("(in use)"));
                else
-                       strcpy(mtpt, "(not mounted)");
+                       strcpy(mtpt, _("(not mounted)"));
        }
 
        pretty_print_line(devname, fs_type, label, mtpt, uuid);
 }
 
-static void print_udev_format(const char *name, const char *value, size_t sz)
+static void print_udev_format(const char *name, const char *value)
 {
        char enc[265], safe[256];
+       size_t namelen = strlen(name);
 
        *safe = *enc = '\0';
 
@@ -254,86 +264,112 @@ static void print_udev_format(const char *name, const char *value, size_t sz)
                blkid_encode_string(value, enc, sizeof(enc));
                printf("ID_FS_%s_ENC=%s\n", name, enc);
 
+       } else if (!strcmp(name, "PTUUID")) {
+               printf("ID_PART_TABLE_UUID=%s\n", value);
+
        } else if (!strcmp(name, "PTTYPE")) {
                printf("ID_PART_TABLE_TYPE=%s\n", value);
 
        } else if (!strcmp(name, "PART_ENTRY_NAME") ||
                  !strcmp(name, "PART_ENTRY_TYPE")) {
 
-               blkid_safe_string(value, safe, sizeof(safe));
-               printf("ID_%s=%s\n", name, safe);
-
                blkid_encode_string(value, enc, sizeof(enc));
-               printf("ID_%s_ENC=%s\n", name, enc);
+               printf("ID_%s=%s\n", name, enc);
 
        } else if (!strncmp(name, "PART_ENTRY_", 11))
                printf("ID_%s=%s\n", name, value);
 
+       else if (namelen >= 15 && (
+                  !strcmp(name + (namelen - 12), "_SECTOR_SIZE") ||
+                  !strcmp(name + (namelen - 8), "_IO_SIZE") ||
+                  !strcmp(name, "ALIGNMENT_OFFSET")))
+                       printf("ID_IOLIMIT_%s=%s\n", name, value);
        else
                printf("ID_FS_%s=%s\n", name, value);
 }
 
-static int has_item(char *ary[], const char *item)
+static int has_item(const struct blkid_control *ctl, const char *item)
 {
-       char **p;
+       char * const *p;
 
-       for (p = ary; *p != NULL; p++)
+       for (p = ctl->show; *p != NULL; p++)
                if (!strcmp(item, *p))
                        return 1;
        return 0;
 }
 
-static void print_value(int output, int num, const char *devname,
-                       const char *value, const char *name, size_t valsz)
+static void print_value(const struct blkid_control *ctl, int num,
+                       const char *devname, const char *value,
+                       const char *name, size_t valsz)
 {
-       if (output & OUTPUT_VALUE_ONLY) {
+       if (ctl->output & OUTPUT_VALUE_ONLY) {
                fputs(value, stdout);
                fputc('\n', stdout);
 
-       } else if (output & OUTPUT_UDEV_LIST) {
-               print_udev_format(name, value, valsz);
+       } else if (ctl->output & OUTPUT_UDEV_LIST) {
+               print_udev_format(name, value);
+
+       } else if (ctl->output & OUTPUT_EXPORT_LIST) {
+               if (num == 1 && devname)
+                       printf("DEVNAME=%s\n", devname);
+               fputs(name, stdout);
+               fputs("=", stdout);
+               safe_print(ctl, value, valsz, " \\\"'$`<>");
+               fputs("\n", stdout);
 
        } else {
                if (num == 1 && devname)
-                       printf("%s: ", devname);
+                       printf("%s:", devname);
+               fputs(" ", stdout);
                fputs(name, stdout);
                fputs("=\"", stdout);
-               safe_print(value, valsz);
-               fputs("\" ", stdout);
+               safe_print(ctl, value, valsz, "\"\\");
+               fputs("\"", stdout);
        }
 }
 
-static void print_tags(blkid_dev dev, char *show[], int output)
+static void print_tags(const struct blkid_control *ctl, blkid_dev dev)
 {
        blkid_tag_iterate       iter;
        const char              *type, *value, *devname;
        int                     num = 1;
+       static int              first = 1;
 
        if (!dev)
                return;
 
-       if (output & OUTPUT_PRETTY_LIST) {
+       if (ctl->output & OUTPUT_PRETTY_LIST) {
                pretty_print_dev(dev);
                return;
        }
 
        devname = blkid_dev_devname(dev);
 
-       if (output & OUTPUT_DEVICE_ONLY) {
+       if (ctl->output & OUTPUT_DEVICE_ONLY) {
                printf("%s\n", devname);
                return;
        }
 
        iter = blkid_tag_iterate_begin(dev);
        while (blkid_tag_next(iter, &type, &value) == 0) {
-               if (show[0] && !has_item(show, type))
+               if (ctl->show[0] && !has_item(ctl, type))
                        continue;
-               print_value(output, num++, devname, value, type, strlen(value));
+
+               if (num == 1 && !first &&
+                   (ctl->output & (OUTPUT_UDEV_LIST | OUTPUT_EXPORT_LIST)))
+                       /* add extra line between output from more devices */
+                       fputc('\n', stdout);
+
+               print_value(ctl, num++, devname, value, type, strlen(value));
        }
        blkid_tag_iterate_end(iter);
 
-       if (num > 1 && !(output & (OUTPUT_VALUE_ONLY | OUTPUT_UDEV_LIST)))
-               printf("\n");
+       if (num > 1) {
+               if (!(ctl->output & (OUTPUT_VALUE_ONLY | OUTPUT_UDEV_LIST |
+                                               OUTPUT_EXPORT_LIST)))
+                       printf("\n");
+               first = 0;
+       }
 }
 
 
@@ -347,12 +383,7 @@ static int append_str(char **res, size_t *sz, const char *a, const char *b)
        if (!len)
                return -1;
 
-       str = realloc(str, len + 1);
-       if (!str) {
-               free(*res);
-               return -1;
-       }
-       *res = str;
+       *res = str = xrealloc(str, len + 1);
        str += *sz;
 
        if (a) {
@@ -378,17 +409,17 @@ static int print_udev_ambivalent(blkid_probe pr)
        int count = 0, rc = -1;
 
        while (!blkid_do_probe(pr)) {
-               const char *usage = NULL, *type = NULL, *version = NULL;
+               const char *usage_txt = NULL, *type = NULL, *version = NULL;
                char enc[256];
 
-               blkid_probe_lookup_value(pr, "USAGE", &usage, NULL);
+               blkid_probe_lookup_value(pr, "USAGE", &usage_txt, NULL);
                blkid_probe_lookup_value(pr, "TYPE", &type, NULL);
                blkid_probe_lookup_value(pr, "VERSION", &version, NULL);
 
-               if (!usage || !type)
+               if (!usage_txt || !type)
                        continue;
 
-               blkid_encode_string(usage, enc, sizeof(enc));
+               blkid_encode_string(usage_txt, enc, sizeof(enc));
                if (append_str(&val, &valsz, enc, ":"))
                        goto done;
 
@@ -406,7 +437,7 @@ static int print_udev_ambivalent(blkid_probe pr)
 
        if (count > 1) {
                *(val + valsz - 1) = '\0';              /* rem tailing whitespace */
-               printf("ID_FS_AMBIVALEN=%s\n", val);
+               printf("ID_FS_AMBIVALENT=%s\n", val);
                rc = 0;
        }
 done:
@@ -414,86 +445,83 @@ done:
        return rc;
 }
 
-static int lowprobe_device(blkid_probe pr, const char *devname,        char *show[],
-                       int output, blkid_loff_t offset, blkid_loff_t size)
+static int lowprobe_superblocks(blkid_probe pr)
+{
+       struct stat st;
+       int rc, fd = blkid_probe_get_fd(pr);
+
+       if (fd < 0 || fstat(fd, &st))
+               return -1;
+
+       blkid_probe_enable_partitions(pr, 1);
+
+       if (!S_ISCHR(st.st_mode) && blkid_probe_get_size(pr) <= 1024 * 1440 &&
+           blkid_probe_is_wholedisk(pr)) {
+               /*
+                * check if the small disk is partitioned, if yes then
+                * don't probe for filesystems.
+                */
+               blkid_probe_enable_superblocks(pr, 0);
+
+               rc = blkid_do_fullprobe(pr);
+               if (rc < 0)
+                       return rc;      /* -1 = error, 1 = nothing, 0 = success */
+
+               if (blkid_probe_lookup_value(pr, "PTTYPE", NULL, NULL) == 0)
+                       return 0;       /* partition table detected */
+       }
+
+       blkid_probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS);
+       blkid_probe_enable_superblocks(pr, 1);
+
+       return blkid_do_safeprobe(pr);
+}
+
+static int lowprobe_topology(blkid_probe pr)
+{
+       /* enable topology probing only */
+       blkid_probe_enable_topology(pr, 1);
+
+       blkid_probe_enable_superblocks(pr, 0);
+       blkid_probe_enable_partitions(pr, 0);
+
+       return blkid_do_fullprobe(pr);
+}
+
+static int lowprobe_device(blkid_probe pr, const char *devname,
+                          struct blkid_control *ctl)
 {
        const char *data;
        const char *name;
-       int nvals = 0, n, num = 1, has_pt = 0;
+       int nvals = 0, n, num = 1;
        size_t len;
        int fd;
        int rc = 0;
-       struct stat st;
+       static int first = 1;
 
-       fd = open(devname, O_RDONLY);
+       fd = open(devname, O_RDONLY|O_CLOEXEC);
        if (fd < 0) {
-               fprintf(stderr, "error: %s: %s\n", devname, strerror(errno));
-               return 2;
+               warn(_("error: %s"), devname);
+               return BLKID_EXIT_NOTFOUND;
        }
-       if (blkid_probe_set_device(pr, fd, offset, size))
-               goto done;
-
-       if (fstat(fd, &st))
+       if (blkid_probe_set_device(pr, fd, ctl->offset, ctl->size))
                goto done;
-       /*
-        * partitions probing
-        */
-       blkid_probe_enable_superblocks(pr, 0);  /* enabled by default ;-( */
-
-       blkid_probe_enable_partitions(pr, 1);
-
-       /* This is for PART_ENTRY_{UUID,NAME,TYPE,...} values and it will
-        * open() whole-disk device.
-        */
-       blkid_probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS);
-
-       rc = blkid_do_fullprobe(pr);
-       blkid_probe_enable_partitions(pr, 0);
 
+       if (ctl->lowprobe_topology)
+               rc = lowprobe_topology(pr);
+       if (rc >= 0 && ctl->lowprobe_superblocks)
+               rc = lowprobe_superblocks(pr);
        if (rc < 0)
-               goto done;      /* -1 = error, 1 = nothing, 0 = succes */
-
-       if (blkid_probe_lookup_value(pr, "PTTYPE", NULL, NULL) == 0)
-               /* partition table detected */
-               has_pt = 1;
-
-       /*
-        * filesystems/raids probing
-        *
-        * Q: Why do we try to probe for FS/RAID on device with partiton table?
-        *
-        * A: For some devices the kernel creates partitions because it is too
-        *    dumb to find out, that the device contains a RAID signature or
-        *    other metadata that signifies, that this device should not be
-        *    handled.
-        *
-        *    Many RAID signatures are at the end of the device, or at least
-        *    still allow that the volume contains a partition table at the
-        *    beginning of the device, which the kernel will find. Udev needs
-        *    to look at the disk, if there are such RAID signatures, and then
-        *    it needs to handle it, and in many cases just kill all the
-        *    wrongly kernel-created partitions.
-        *
-        * -- Kay Sievers
-        *    http://thread.gmane.org/gmane.linux.utilities.util-linux-ng/2888/focus=2890
-        *
-        * This all is unncecessary for very small devices (<= 1.44MiB).
-        */
-       if (has_pt == 0 || S_ISCHR(st.st_mode) ||
-           blkid_probe_get_size(pr) > 1024 * 1440) {
-               /*
-                * filesystems/RAIDs probing
-                */
-               blkid_probe_enable_superblocks(pr, 1);
+               goto done;
 
-               rc = blkid_do_safeprobe(pr);
-               if (rc < 0)
-                       goto done;
-       }
+       if (!rc)
+               nvals = blkid_probe_numof_values(pr);
 
-       nvals = blkid_probe_numof_values(pr);
+       if (nvals && !first && ctl->output & (OUTPUT_UDEV_LIST | OUTPUT_EXPORT_LIST))
+               /* add extra line between output from devices */
+               fputc('\n', stdout);
 
-       if (output & OUTPUT_DEVICE_ONLY) {
+       if (nvals && (ctl->output & OUTPUT_DEVICE_ONLY)) {
                printf("%s\n", devname);
                goto done;
        }
@@ -501,27 +529,36 @@ static int lowprobe_device(blkid_probe pr, const char *devname,   char *show[],
        for (n = 0; n < nvals; n++) {
                if (blkid_probe_get_value(pr, n, &name, &data, &len))
                        continue;
-               if (show[0] && !has_item(show, name))
+               if (ctl->show[0] && !has_item(ctl, name))
                        continue;
                len = strnlen((char *) data, len);
-               print_value(output, num++, devname, (char *) data, name, len);
+               print_value(ctl, num++, devname, (char *) data, name, len);
        }
 
-       if (nvals >= 1 && !(output & (OUTPUT_VALUE_ONLY | OUTPUT_UDEV_LIST)))
+       if (first)
+               first = 0;
+
+       if (nvals >= 1 && !(ctl->output & (OUTPUT_VALUE_ONLY |
+                                       OUTPUT_UDEV_LIST | OUTPUT_EXPORT_LIST)))
                printf("\n");
 done:
        if (rc == -2) {
-               if (output & OUTPUT_UDEV_LIST)
+               if (ctl->output & OUTPUT_UDEV_LIST)
                        print_udev_ambivalent(pr);
                else
-                       fprintf(stderr,
-                               "%s: ambivalent result (probably more "
+                       warnx(_("%s: ambivalent result (probably more "
                                "filesystems on the device, use wipefs(8) "
-                               "to see more details)\n",
+                               "to see more details)"),
                                devname);
        }
        close(fd);
-       return !nvals ? 2 : 0;
+
+       if (rc == -2)
+               return BLKID_EXIT_AMBIVAL;      /* ambivalent probing result */
+       if (!nvals)
+               return BLKID_EXIT_NOTFOUND;     /* nothing detected */
+
+       return 0;               /* success */
 }
 
 /* converts comma separated list to BLKID_USAGE_* mask */
@@ -555,9 +592,9 @@ static int list_to_usage(const char *list, int *flag)
        return mask;
 err:
        *flag = 0;
-       fprintf(stderr, "unknown kerword in -u <list> argument: '%s'\n",
+       warnx(_("unknown keyword in -u <list> argument: '%s'"),
                        word ? word : list);
-       exit(4);
+       exit(BLKID_EXIT_OTHER);
 }
 
 /* converts comma separated list to types[] */
@@ -565,41 +602,35 @@ static char **list_to_types(const char *list, int *flag)
 {
        int i;
        const char *p = list;
-       char **res;
+       char **res = NULL;
 
        if (p && strncmp(p, "no", 2) == 0) {
                *flag = BLKID_FLTR_NOTIN;
                p += 2;
        }
        if (!p || !*p) {
-               fprintf(stderr, "error: -u <list> argument is empty\n");
+               warnx(_("error: -u <list> argument is empty"));
                goto err;
        }
        for (i = 1; p && (p = strchr(p, ',')); i++, p++);
 
-       res = calloc(i + 1, sizeof(char *));
-       if (!res)
-               goto err_mem;
+       res = xcalloc(i + 1, sizeof(char *));
        p = *flag & BLKID_FLTR_NOTIN ? list + 2 : list;
        i = 0;
 
        while(p) {
                const char *word = p;
                p = strchr(p, ',');
-               res[i] = p ? strndup(word, p - word) : strdup(word);
-               if (!res[i++])
-                       goto err_mem;
+               res[i++] = p ? xstrndup(word, p - word) : xstrdup(word);
                if (p)
                        p++;
        }
        res[i] = NULL;
        return res;
-err_mem:
-       fprintf(stderr, "out of memory\n");
 err:
        *flag = 0;
        free(res);
-       exit(4);
+       exit(BLKID_EXIT_OTHER);
 }
 
 static void free_types_list(char *list[])
@@ -615,203 +646,238 @@ static void free_types_list(char *list[])
 
 int main(int argc, char **argv)
 {
+       struct blkid_control ctl = { .output = OUTPUT_FULL, 0 };
        blkid_cache cache = NULL;
-       char *devices[128] = { NULL, };
-       char *show[128] = { NULL, };
+       char **devices = NULL;
        char *search_type = NULL, *search_value = NULL;
        char *read = NULL;
-       char *write = NULL;
        int fltr_usage = 0;
        char **fltr_type = NULL;
        int fltr_flag = BLKID_FLTR_ONLYIN;
        unsigned int numdev = 0, numtag = 0;
-       int version = 0;
-       int err = 4;
+       int err = BLKID_EXIT_OTHER;
        unsigned int i;
-       int output_format = 0;
-       int lookup = 0, gc = 0, lowprobe = 0, eval = 0;
        int c;
-       uintmax_t offset = 0, size = 0;
 
-       show[0] = NULL;
+       static const struct option longopts[] = {
+               { "cache-file",       required_argument, NULL, 'c' },
+               { "no-encoding",      no_argument,       NULL, 'd' },
+               { "garbage-collect",  no_argument,       NULL, 'g' },
+               { "output",           required_argument, NULL, 'o' },
+               { "list-filesystems", no_argument,       NULL, 'k' },
+               { "match-tag",        required_argument, NULL, 's' },
+               { "match-token",      required_argument, NULL, 't' },
+               { "list-one",         no_argument,       NULL, 'l' },
+               { "label",            required_argument, NULL, 'L' },
+               { "uuid",             required_argument, NULL, 'U' },
+               { "probe",            no_argument,       NULL, 'p' },
+               { "info",             no_argument,       NULL, 'i' },
+               { "size",             required_argument, NULL, 'S' },
+               { "offset",           required_argument, NULL, 'O' },
+               { "usages",           required_argument, NULL, 'u' },
+               { "match-types",      required_argument, NULL, 'n' },
+               { "version",          no_argument,       NULL, 'V' },
+               { "help",             no_argument,       NULL, 'h' },
+               { NULL, 0, NULL, 0 }
+       };
+
+       static const ul_excl_t excl[] = {       /* rows and cols in ASCII order */
+               { 'n','u' },
+               { 0 }
+       };
+       int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+       setlocale(LC_ALL, "");
+       bindtextdomain(PACKAGE, LOCALEDIR);
+       textdomain(PACKAGE);
+       atexit(close_stdout);
+
+       strutils_set_exitcode(BLKID_EXIT_OTHER);
+
+       while ((c = getopt_long (argc, argv,
+                           "c:dghilL:n:ko:O:ps:S:t:u:U:w:Vv", longopts, NULL)) != -1) {
+
+               err_exclusive_options(c, NULL, excl, excl_st);
 
-       while ((c = getopt (argc, argv, "c:f:ghlL:n:o:O:ps:S:t:u:U:w:v")) != EOF)
                switch (c) {
                case 'c':
-                       if (optarg && !*optarg)
-                               read = NULL;
-                       else
-                               read = optarg;
-                       if (!write)
-                               write = read;
+                       read = optarg;
+                       break;
+               case 'd':
+                       ctl.raw_chars = 1;
                        break;
                case 'L':
-                       eval++;
-                       search_value = strdup(optarg);
-                       search_type = strdup("LABEL");
+                       ctl.eval = 1;
+                       search_value = xstrdup(optarg);
+                       search_type = xstrdup("LABEL");
                        break;
                case 'n':
-                       if (fltr_usage) {
-                               fprintf(stderr, "error: -u and -n options are mutually exclusive\n");
-                               exit(4);
-                       }
                        fltr_type = list_to_types(optarg, &fltr_flag);
                        break;
                case 'u':
-                       if (fltr_type) {
-                               fprintf(stderr, "error: -u and -n options are mutually exclusive\n");
-                               exit(4);
-                       }
                        fltr_usage = list_to_usage(optarg, &fltr_flag);
                        break;
                case 'U':
-                       eval++;
-                       search_value = strdup(optarg);
-                       search_type = strdup("UUID");
+                       ctl.eval = 1;
+                       search_value = xstrdup(optarg);
+                       search_type = xstrdup("UUID");
+                       break;
+               case 'i':
+                       ctl.lowprobe_topology = 1;
                        break;
                case 'l':
-                       lookup++;
+                       ctl.lookup = 1;
                        break;
                case 'g':
-                       gc = 1;
+                       ctl.gc = 1;
                        break;
+               case 'k':
+               {
+                       size_t idx = 0;
+                       const char *name = NULL;
+
+                       while (blkid_superblocks_get_name(idx++, &name, NULL) == 0)
+                               printf("%s\n", name);
+                       exit(EXIT_SUCCESS);
+               }
                case 'o':
                        if (!strcmp(optarg, "value"))
-                               output_format = OUTPUT_VALUE_ONLY;
+                               ctl.output = OUTPUT_VALUE_ONLY;
                        else if (!strcmp(optarg, "device"))
-                               output_format = OUTPUT_DEVICE_ONLY;
+                               ctl.output = OUTPUT_DEVICE_ONLY;
                        else if (!strcmp(optarg, "list"))
-                               output_format = OUTPUT_PRETTY_LIST;
+                               ctl.output = OUTPUT_PRETTY_LIST;        /* deprecated */
                        else if (!strcmp(optarg, "udev"))
-                               output_format = OUTPUT_UDEV_LIST;
+                               ctl.output = OUTPUT_UDEV_LIST;
+                       else if (!strcmp(optarg, "export"))
+                               ctl.output = OUTPUT_EXPORT_LIST;
                        else if (!strcmp(optarg, "full"))
-                               output_format = 0;
-                       else {
-                               fprintf(stderr, "Invalid output format %s. "
-                                       "Choose from value,\n\t"
-                                       "device, list, udev or full\n", optarg);
-                               exit(4);
-                       }
+                               ctl.output = 0;
+                       else
+                               errx(BLKID_EXIT_OTHER, _("unsupported output format %s"), optarg);
                        break;
                case 'O':
-                       if (strtosize(optarg, &offset))
-                               fprintf(stderr,
-                                       "Invalid offset '%s' specified\n",
-                                       optarg);
+                       ctl.offset = strtosize_or_err(optarg, _("invalid offset argument"));
                        break;
                case 'p':
-                       lowprobe++;
+                       ctl.lowprobe_superblocks = 1;
                        break;
                case 's':
-                       if (numtag + 1 >= sizeof(show) / sizeof(*show)) {
-                               fprintf(stderr, "Too many tags specified\n");
-                               usage(err);
+                       if (numtag + 1 >= sizeof(ctl.show) / sizeof(*ctl.show)) {
+                               warnx(_("Too many tags specified"));
+                               errtryh(err);
                        }
-                       show[numtag++] = optarg;
-                       show[numtag] = NULL;
+                       ctl.show[numtag++] = optarg;
                        break;
                case 'S':
-                       if (strtosize(optarg, &size))
-                               fprintf(stderr,
-                                       "Invalid size '%s' specified\n",
-                                       optarg);
+                       ctl.size = strtosize_or_err(optarg, _("invalid size argument"));
                        break;
                case 't':
                        if (search_type) {
-                               fprintf(stderr, "Can only search for "
-                                               "one NAME=value pair\n");
-                               usage(err);
+                               warnx(_("Can only search for "
+                                       "one NAME=value pair"));
+                               errtryh(err);
                        }
                        if (blkid_parse_tag_string(optarg,
                                                   &search_type,
                                                   &search_value)) {
-                               fprintf(stderr, "-t needs NAME=value pair\n");
-                               usage(err);
+                               warnx(_("-t needs NAME=value pair"));
+                               errtryh(err);
                        }
                        break;
+               case 'V':
                case 'v':
-                       version = 1;
-                       break;
+                       print_version(stdout);
+                       err = 0;
+                       goto exit;
                case 'w':
-                       if (optarg && !*optarg)
-                               write = NULL;
-                       else
-                               write = optarg;
+                       /* ignore - backward compatibility */
                        break;
                case 'h':
-                       err = 0;
+                       usage(0);
+                       break;
                default:
-                       usage(err);
+                       errtryh(EXIT_FAILURE);
                }
+       }
 
-       while (optind < argc)
-               devices[numdev++] = argv[optind++];
+       if (ctl.lowprobe_topology || ctl.lowprobe_superblocks)
+               ctl.lowprobe = 1;
 
-       if (version) {
-               print_version(stdout);
-               goto exit;
+       /* The rest of the args are device names */
+       if (optind < argc) {
+               devices = xcalloc(argc - optind, sizeof(char *));
+               while (optind < argc)
+                       devices[numdev++] = argv[optind++];
        }
 
        /* convert LABEL/UUID lookup to evaluate request */
-       if (lookup && output_format == OUTPUT_DEVICE_ONLY && search_type &&
+       if (ctl.lookup && ctl.output == OUTPUT_DEVICE_ONLY && search_type &&
            (!strcmp(search_type, "LABEL") || !strcmp(search_type, "UUID"))) {
-               eval++;
-               lookup = 0;
+               ctl.eval = 1;
+               ctl.lookup = 0;
        }
 
-       if (!lowprobe && !eval && blkid_get_cache(&cache, read) < 0)
+       if (!ctl.lowprobe && !ctl.eval && blkid_get_cache(&cache, read) < 0)
                goto exit;
 
-       if (gc) {
+       if (ctl.gc) {
                blkid_gc_cache(cache);
                err = 0;
                goto exit;
        }
-       err = 2;
+       err = BLKID_EXIT_NOTFOUND;
 
-       if (eval == 0 && output_format & OUTPUT_PRETTY_LIST) {
-               if (lowprobe) {
-                       fprintf(stderr, "The low-level probing mode does not "
-                                       "support 'list' output format\n");
-                       exit(4);
-               }
+       if (ctl.eval == 0 && (ctl.output & OUTPUT_PRETTY_LIST)) {
+               if (ctl.lowprobe)
+                       errx(BLKID_EXIT_OTHER,
+                            _("The low-level probing mode does not "
+                              "support 'list' output format"));
                pretty_print_dev(NULL);
        }
 
-       if (lowprobe) {
+       if (ctl.lowprobe) {
                /*
                 * Low-level API
                 */
                blkid_probe pr;
 
-               if (!numdev) {
-                       fprintf(stderr, "The low-level probing mode "
-                                       "requires a device\n");
-                       exit(4);
-               }
+               if (!numdev)
+                       errx(BLKID_EXIT_OTHER,
+                            _("The low-level probing mode "
+                              "requires a device"));
+
+               /* automatically enable 'export' format for I/O Limits */
+               if (!ctl.output  && ctl.lowprobe_topology)
+                       ctl.output = OUTPUT_EXPORT_LIST;
+
                pr = blkid_new_probe();
                if (!pr)
                        goto exit;
 
-               blkid_probe_set_superblocks_flags(pr,
+               if (ctl.lowprobe_superblocks) {
+                       blkid_probe_set_superblocks_flags(pr,
                                BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID |
                                BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE |
                                BLKID_SUBLKS_USAGE | BLKID_SUBLKS_VERSION);
 
-               if (fltr_usage &&
-                   blkid_probe_filter_superblocks_usage(pr, fltr_flag, fltr_usage))
-                       goto exit;
-               else if (fltr_type &&
-                   blkid_probe_filter_superblocks_type(pr, fltr_flag, fltr_type))
-                       goto exit;
 
-               for (i = 0; i < numdev; i++)
-                       err = lowprobe_device(pr, devices[i], show,
-                                       output_format,
-                                       (blkid_loff_t) offset,
-                                       (blkid_loff_t) size);
+                       if (fltr_usage &&
+                           blkid_probe_filter_superblocks_usage(pr, fltr_flag, fltr_usage))
+                               goto exit;
+
+                       else if (fltr_type &&
+                                blkid_probe_filter_superblocks_type(pr, fltr_flag, fltr_type))
+                               goto exit;
+               }
+
+               for (i = 0; i < numdev; i++) {
+                       err = lowprobe_device(pr, devices[i], &ctl);
+                       if (err)
+                               break;
+               }
                blkid_free_probe(pr);
-       } else if (eval) {
+       } else if (ctl.eval) {
                /*
                 * Evaluate API
                 */
@@ -820,24 +886,23 @@ int main(int argc, char **argv)
                        err = 0;
                        printf("%s\n", res);
                }
-       } else if (lookup) {
+       } else if (ctl.lookup) {
                /*
                 * Classic (cache based) API
                 */
                blkid_dev dev;
 
-               if (!search_type) {
-                       fprintf(stderr, "The lookup option requires a "
-                               "search type specified using -t\n");
-                       exit(4);
-               }
+               if (!search_type)
+                       errx(BLKID_EXIT_OTHER,
+                            _("The lookup option requires a "
+                              "search type specified using -t"));
                /* Load any additional devices not in the cache */
                for (i = 0; i < numdev; i++)
                        blkid_get_dev(cache, devices[i], BLKID_DEV_NORMAL);
 
                if ((dev = blkid_find_dev_with_tag(cache, search_type,
                                                   search_value))) {
-                       print_tags(dev, show, output_format);
+                       print_tags(&ctl, dev);
                        err = 0;
                }
        /* If we didn't specify a single device, show all available devices */
@@ -853,7 +918,7 @@ int main(int argc, char **argv)
                        dev = blkid_verify(cache, dev);
                        if (!dev)
                                continue;
-                       print_tags(dev, show, output_format);
+                       print_tags(&ctl, dev);
                        err = 0;
                }
                blkid_dev_iterate_end(iter);
@@ -867,7 +932,7 @@ int main(int argc, char **argv)
                            !blkid_dev_has_tag(dev, search_type,
                                               search_value))
                                continue;
-                       print_tags(dev, show, output_format);
+                       print_tags(&ctl, dev);
                        err = 0;
                }
        }
@@ -876,7 +941,8 @@ exit:
        free(search_type);
        free(search_value);
        free_types_list(fltr_type);
-       if (!lowprobe && !eval)
+       if (!ctl.lowprobe && !ctl.eval)
                blkid_put_cache(cache);
+       free(devices);
        return err;
 }