]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - misc-utils/blkid.c
libblkid: check status for the current CDROM slot
[thirdparty/util-linux.git] / misc-utils / blkid.c
index 6a7991f8724497633fe4fbecdd39a0e9884b304a..88a90578b030b1658860ed5fedadeefd3a3c11da 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_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 LOWPROBE_TOPOLOGY      (1 << 1)
-#define LOWPROBE_SUPERBLOCKS   (1 << 2)
+#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"
 
-#define STRTOXX_EXIT_CODE      4               /* strtoxx_or_err() */
 #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"
 
-const char *progname = "blkid";
-
-int raw_chars;
-
-static void print_version(FILE *out)
-{
-       fprintf(out, "%s from %s (libblkid %s, %s)\n",
-               progname, PACKAGE_STRING, LIBBLKID_VERSION, LIBBLKID_DATE);
-}
-
-static void usage(int error)
+#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,
+               no_part_details:1,
+               raw_chars:1;
+};
+
+static void __attribute__((__noreturn__)) usage(void)
 {
-       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>] [<dev> ...]\n\n"
-               " %1$s -p [-s <tag>] [-O <offset>] [-S <size>] \n"
-               "       [-o <format>] <dev> ...\n\n"
-               " %1$s -i [-s <tag>] [-o <format>] <dev> ...\n\n"
-               "Options:\n"
-               " -c <file>   read from <file> instead of reading from the default\n"
-               "               cache file (-c /dev/null means no cache)\n"
-               " -d          don't encode non-printing characters\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, export or full; (default: full)\n"
-               " -k          list all known filesystems/RAIDs and exit\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          look up only first device with token 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"
-               " <dev>       specify device(s) to probe (default: all devices)\n\n"
-               "Low-level probing options:\n"
-               " -p          low-level superblocks probing (bypass cache)\n"
-               " -i          gather information about I/O limits\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);
-
-       exit(error);
+       FILE *out = stdout;
+
+       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(_(        " -D, --no-part-details      don't print info from partition table\n"), out);
+
+       fputs(USAGE_SEPARATOR, out);
+       printf(USAGE_HELP_OPTIONS(28));
+       printf(USAGE_MAN_TAIL("blkid(8)"));
+       exit(EXIT_SUCCESS);
 }
 
 /*
  * 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;
 
@@ -110,7 +119,7 @@ static void safe_print(const char *cp, int len)
 
        while (len--) {
                ch = *cp++;
-               if (!raw_chars) {
+               if (!ctl->raw_chars) {
                        if (ch >= 128) {
                                fputs("M-", stdout);
                                ch -= 128;
@@ -118,36 +127,14 @@ static void safe_print(const char *cp, int len)
                        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)
 {
@@ -160,9 +147,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;
 }
 
@@ -175,9 +162,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;
@@ -212,7 +199,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;
@@ -238,13 +225,18 @@ static void pretty_print_dev(blkid_dev dev)
        mtpt[0] = 0;
        retval = check_mount_point(devname, &mount_flags, mtpt, sizeof(mtpt));
        if (retval == 0) {
+               const char *msg = NULL;
+
                if (mount_flags & MF_MOUNTED) {
                        if (!mtpt[0])
-                               strcpy(mtpt, "(mounted, mtpt unknown)");
+                               msg = _("(mounted, mtpt unknown)");
                } else if (mount_flags & MF_BUSY)
-                       strcpy(mtpt, "(in use)");
+                       msg = _("(in use)");
                else
-                       strcpy(mtpt, "(not mounted)");
+                       msg = _("(not mounted)");
+
+               if (msg)
+                       xstrncpy(mtpt, msg, sizeof(mtpt));
        }
 
        pretty_print_line(devname, fs_type, label, mtpt, uuid);
@@ -262,7 +254,7 @@ static void print_udev_format(const char *name, const char *value)
                printf("ID_FS_%s=%s\n", name, enc);
 
        } else if (!strcmp(name, "UUID") ||
-                !strcmp(name, "LABEL") ||
+                !strncmp(name, "LABEL", 5) ||
                 !strcmp(name, "UUID_SUB")) {
 
                blkid_safe_string(value, safe, sizeof(safe));
@@ -271,6 +263,9 @@ static void print_udev_format(const char *name, const char *value)
                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);
 
@@ -292,45 +287,47 @@ static void print_udev_format(const char *name, const char *value)
                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) {
+       } else if (ctl->output & OUTPUT_UDEV_LIST) {
                print_udev_format(name, value);
 
-       } else if (output & OUTPUT_EXPORT_LIST) {
+       } else if (ctl->output & OUTPUT_EXPORT_LIST) {
                if (num == 1 && devname)
                        printf("DEVNAME=%s\n", devname);
                fputs(name, stdout);
                fputs("=", stdout);
-               safe_print(value, valsz);
+               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;
@@ -340,34 +337,34 @@ static void print_tags(blkid_dev dev, char *show[], int output)
        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;
 
                if (num == 1 && !first &&
-                   (output & (OUTPUT_UDEV_LIST | OUTPUT_EXPORT_LIST)))
+                   (ctl->output & (OUTPUT_UDEV_LIST | OUTPUT_EXPORT_LIST)))
                        /* add extra line between output from more devices */
                        fputc('\n', stdout);
 
-               print_value(output, num++, devname, value, type, strlen(value));
+               print_value(ctl, num++, devname, value, type, strlen(value));
        }
        blkid_tag_iterate_end(iter);
 
        if (num > 1) {
-               if (!(output & (OUTPUT_VALUE_ONLY | OUTPUT_UDEV_LIST |
+               if (!(ctl->output & (OUTPUT_VALUE_ONLY | OUTPUT_UDEV_LIST |
                                                OUTPUT_EXPORT_LIST)))
                        printf("\n");
                first = 0;
@@ -385,11 +382,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)
-               return -1;
-
-       *res = str;
+       *res = str = xrealloc(str, len + 1);
        str += *sz;
 
        if (a) {
@@ -415,17 +408,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;
 
@@ -443,7 +436,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:
@@ -451,7 +444,7 @@ done:
        return rc;
 }
 
-static int lowprobe_superblocks(blkid_probe pr)
+static int lowprobe_superblocks(blkid_probe pr, struct blkid_control *ctl)
 {
        struct stat st;
        int rc, fd = blkid_probe_get_fd(pr);
@@ -471,13 +464,14 @@ static int lowprobe_superblocks(blkid_probe pr)
 
                rc = blkid_do_fullprobe(pr);
                if (rc < 0)
-                       return rc;      /* -1 = error, 1 = nothing, 0 = succes */
+                       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);
+       if (!ctl->no_part_details)
+               blkid_probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS);
        blkid_probe_enable_superblocks(pr, 1);
 
        return blkid_do_safeprobe(pr);
@@ -495,8 +489,7 @@ static int lowprobe_topology(blkid_probe pr)
 }
 
 static int lowprobe_device(blkid_probe pr, const char *devname,
-                       int chain, char *show[], int output,
-                       blkid_loff_t offset, blkid_loff_t size)
+                          struct blkid_control *ctl)
 {
        const char *data;
        const char *name;
@@ -506,44 +499,33 @@ static int lowprobe_device(blkid_probe pr, const char *devname,
        int rc = 0;
        static int first = 1;
 
-       fd = open(devname, O_RDONLY);
+       fd = open(devname, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
        if (fd < 0) {
-               fprintf(stderr, "error: %s: %m\n", devname);
-               return 2;
+               warn(_("error: %s"), devname);
+               return BLKID_EXIT_NOTFOUND;
        }
-       if (blkid_probe_set_device(pr, fd, offset, size))
+       errno = 0;
+       if (blkid_probe_set_device(pr, fd, ctl->offset, ctl->size)) {
+               if (errno)
+                       warn(_("error: %s"), devname);
                goto done;
+       }
 
-       if (chain & LOWPROBE_TOPOLOGY)
+       if (ctl->lowprobe_topology)
                rc = lowprobe_topology(pr);
-       if (rc >= 0 && (chain & LOWPROBE_SUPERBLOCKS))
-               rc = lowprobe_superblocks(pr);
+       if (rc >= 0 && ctl->lowprobe_superblocks)
+               rc = lowprobe_superblocks(pr, ctl);
        if (rc < 0)
                goto done;
 
        if (!rc)
                nvals = blkid_probe_numof_values(pr);
 
-       if (nvals &&
-           !(chain & LOWPROBE_TOPOLOGY) &&
-           !(output & OUTPUT_UDEV_LIST) &&
-           !blkid_probe_has_value(pr, "TYPE") &&
-           !blkid_probe_has_value(pr, "PTTYPE"))
-               /*
-                * Ignore probing result if there is not any filesystem or
-                * partition table on the device and udev output is not
-                * requested.
-                *
-                * The udev db stores information about partitions, so
-                * PART_ENTRY_* values are alway important.
-                */
-               nvals = 0;
-
-       if (nvals && !first && output & (OUTPUT_UDEV_LIST | OUTPUT_EXPORT_LIST))
+       if (nvals && !first && ctl->output & (OUTPUT_UDEV_LIST | OUTPUT_EXPORT_LIST))
                /* add extra line between output from devices */
                fputc('\n', stdout);
 
-       if (nvals && (output & OUTPUT_DEVICE_ONLY)) {
+       if (nvals && (ctl->output & OUTPUT_DEVICE_ONLY)) {
                printf("%s\n", devname);
                goto done;
        }
@@ -551,34 +533,34 @@ static int lowprobe_device(blkid_probe pr, const char *devname,
        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);
+               len = strnlen(data, len);
+               print_value(ctl, num++, devname, data, name, len);
        }
 
        if (first)
                first = 0;
-       if (nvals >= 1 && !(output & (OUTPUT_VALUE_ONLY |
+
+       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);
 
        if (rc == -2)
-               return 8;       /* ambivalent probing result */
+               return BLKID_EXIT_AMBIVAL;      /* ambivalent probing result */
        if (!nvals)
-               return 2;       /* nothing detected */
+               return BLKID_EXIT_NOTFOUND;     /* nothing detected */
 
        return 0;               /* success */
 }
@@ -614,9 +596,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[] */
@@ -631,34 +613,28 @@ static char **list_to_types(const char *list, int *flag)
                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[])
@@ -674,69 +650,94 @@ 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 = NULL;
-       char *show[128] = { NULL, };
        char *search_type = NULL, *search_value = NULL;
        char *read = 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;
-       atexit(close_stdout);
+       static const struct option longopts[] = {
+               { "cache-file",       required_argument, NULL, 'c' },
+               { "no-encoding",      no_argument,       NULL, 'd' },
+               { "no-part-details",  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);
+       close_stdout_atexit();
+
+       strutils_set_exitcode(BLKID_EXIT_OTHER);
+
+       while ((c = getopt_long (argc, argv,
+                           "c:DdghilL: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:df:ghilL:n:ko:O:ps:S:t:u:U:w:v")) != EOF)
                switch (c) {
                case 'c':
-                       if (optarg && !*optarg)
-                               read = NULL;
-                       else
-                               read = optarg;
+                       read = optarg;
                        break;
                case 'd':
-                       raw_chars = 1;
+                       ctl.raw_chars = 1;
+                       break;
+               case 'D':
+                       ctl.no_part_details = 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':
-                       lowprobe |= LOWPROBE_TOPOLOGY;
+                       ctl.lowprobe_topology = 1;
                        break;
                case 'l':
-                       lookup++;
+                       ctl.lookup = 1;
                        break;
                case 'g':
-                       gc = 1;
+                       ctl.gc = 1;
                        break;
                case 'k':
                {
@@ -745,157 +746,148 @@ int main(int argc, char **argv)
 
                        while (blkid_superblocks_get_name(idx++, &name, NULL) == 0)
                                printf("%s\n", name);
-                       exit(0);
+                       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;     /* deprecated */
+                               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"))
-                               output_format = OUTPUT_EXPORT_LIST;
+                               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':
-                       offset = strtosize_or_err(optarg, "failed to parse offset");
+                       ctl.offset = strtosize_or_err(optarg, _("invalid offset argument"));
                        break;
                case 'p':
-                       lowprobe |= LOWPROBE_SUPERBLOCKS;
+                       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"));
+                               errtryhelp(err);
                        }
-                       show[numtag++] = optarg;
-                       show[numtag] = NULL;
+                       ctl.show[numtag++] = optarg;
                        break;
                case 'S':
-                       size = strtosize_or_err(optarg, "failed to parse size");
+                       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"));
+                               errtryhelp(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"));
+                               errtryhelp(err);
                        }
                        break;
+               case 'V':
                case 'v':
-                       version = 1;
-                       break;
+                       fprintf(stdout, _("%s from %s  (libblkid %s, %s)\n"),
+                               program_invocation_short_name, PACKAGE_STRING,
+                               LIBBLKID_VERSION, LIBBLKID_DATE);
+                       err = 0;
+                       goto exit;
                case 'w':
                        /* ignore - backward compatibility */
                        break;
                case 'h':
-                       err = 0;
-                       /* fallthrough */
+                       usage();
+                       break;
                default:
-                       usage(err);
+                       errtryhelp(EXIT_FAILURE);
                }
+       }
 
+       if (ctl.lowprobe_topology || ctl.lowprobe_superblocks)
+               ctl.lowprobe = 1;
 
        /* The rest of the args are device names */
        if (optind < argc) {
-               devices = calloc(argc - optind, sizeof(char *));
-               if (!devices) {
-                       fprintf(stderr, "Failed to allocate device name array\n");
-                       goto exit;
-               }
-
+               devices = xcalloc(argc - optind, sizeof(char *));
                while (optind < argc)
                        devices[numdev++] = argv[optind++];
        }
 
-       if (version) {
-               print_version(stdout);
-               goto exit;
-       }
-
        /* 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 (!output_format  && (lowprobe & LOWPROBE_TOPOLOGY))
-                       output_format = OUTPUT_EXPORT_LIST;
+               if (!ctl.output  && ctl.lowprobe_topology)
+                       ctl.output = OUTPUT_EXPORT_LIST;
 
                pr = blkid_new_probe();
                if (!pr)
                        goto exit;
 
-               if (lowprobe & LOWPROBE_SUPERBLOCKS) {
+               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))
+
+                       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))
+                       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], lowprobe, show,
-                                       output_format,
-                                       (blkid_loff_t) offset,
-                                       (blkid_loff_t) size);
+               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
                 */
@@ -904,24 +896,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 */
@@ -937,7 +928,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);
@@ -951,7 +942,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;
                }
        }
@@ -960,7 +951,7 @@ 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;