]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lsblk: add --sort <column>
authorKarel Zak <kzak@redhat.com>
Tue, 20 May 2014 13:08:08 +0000 (15:08 +0200)
committerKarel Zak <kzak@redhat.com>
Tue, 20 May 2014 13:12:51 +0000 (15:12 +0200)
This implementation allow to sort output by arbitrary output column.

Note that output is always converted to human readable strings, but
sort functions in many cases work with original data (stored as
private data to cells by scols_cell_set_userdata()).

Signed-off-by: Karel Zak <kzak@redhat.com>
misc-utils/lsblk.8
misc-utils/lsblk.c

index e87689b46a3220b549db9026fca147ba4defb2b0..664db0e91249902acd06e150b8cd68a272dd56df 100644 (file)
@@ -110,6 +110,9 @@ This option is equivalent to
 .TP
 .BR \-V , " \-\-version"
 Display version information and exit.
+.TP
+.BR \-x , " \-\-sort " \fIcolumn\fP
+Sort output lines by output \fIcolumn\fP.
 .SH NOTES
 For partitions, some information (e.g. queue attributes) is inherited from the
 parent device.
index 24853dcf8e2a25bd19a729c9483ac9190979580d..e87b5f968524e208f56c2ea3442ae06148034eb8 100644 (file)
@@ -117,12 +117,19 @@ enum {
        LSBLK_TREE =            (1 << 4),
 };
 
+enum {
+       SORT_STRING     = 0,    /* default is to use scols_cell_get_data() */
+       SORT_U64        = 1     /* use private pointer from scols_cell_get_userdata() */
+};
+
 /* 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;
+
+       int     sort_type;              /* SORT_* */
 };
 
 /* columns descriptions */
@@ -130,7 +137,7 @@ static struct colinfo infos[] = {
        [COL_NAME]   = { "NAME",    0.25, SCOLS_FL_TREE | SCOLS_FL_NOEXTREMES, N_("device name") },
        [COL_KNAME]  = { "KNAME",   0.3, 0, N_("internal kernel device name") },
        [COL_PKNAME] = { "PKNAME",   0.3, 0, N_("internal parent kernel device name") },
-       [COL_MAJMIN] = { "MAJ:MIN", 6, 0, N_("major:minor device number") },
+       [COL_MAJMIN] = { "MAJ:MIN", 6, 0, N_("major:minor device number"), SORT_U64 },
        [COL_FSTYPE] = { "FSTYPE",  0.1, SCOLS_FL_TRUNC, N_("filesystem type") },
        [COL_TARGET] = { "MOUNTPOINT", 0.10, SCOLS_FL_TRUNC, N_("where the device is mounted") },
        [COL_LABEL]  = { "LABEL",   0.1, 0, N_("filesystem LABEL") },
@@ -141,31 +148,31 @@ static struct colinfo infos[] = {
        [COL_PARTUUID]  = { "PARTUUID",  36,  0, N_("partition UUID") },
        [COL_PARTFLAGS] = { "PARTFLAGS",  36,  0, N_("partition flags") },
 
-       [COL_RA]     = { "RA",      3, SCOLS_FL_RIGHT, N_("read-ahead of the device") },
+       [COL_RA]     = { "RA",      3, SCOLS_FL_RIGHT, N_("read-ahead of the device"), SORT_U64 },
        [COL_RO]     = { "RO",      1, SCOLS_FL_RIGHT, N_("read-only device") },
        [COL_RM]     = { "RM",      1, SCOLS_FL_RIGHT, N_("removable device") },
        [COL_ROTA]   = { "ROTA",    1, SCOLS_FL_RIGHT, N_("rotational device") },
        [COL_RAND]   = { "RAND",    1, SCOLS_FL_RIGHT, N_("adds randomness") },
        [COL_MODEL]  = { "MODEL",   0.1, SCOLS_FL_TRUNC, N_("device identifier") },
        [COL_SERIAL] = { "SERIAL",  0.1, SCOLS_FL_TRUNC, N_("disk serial number") },
-       [COL_SIZE]   = { "SIZE",    5, SCOLS_FL_RIGHT, N_("size of the device") },
+       [COL_SIZE]   = { "SIZE",    5, SCOLS_FL_RIGHT, N_("size of the device"), SORT_U64 },
        [COL_STATE]  = { "STATE",   7, SCOLS_FL_TRUNC, N_("state of the device") },
        [COL_OWNER]  = { "OWNER",   0.1, SCOLS_FL_TRUNC, N_("user name"), },
        [COL_GROUP]  = { "GROUP",   0.1, SCOLS_FL_TRUNC, N_("group name") },
        [COL_MODE]   = { "MODE",    10,   0, N_("device node permissions") },
-       [COL_ALIOFF] = { "ALIGNMENT", 6, SCOLS_FL_RIGHT, N_("alignment offset") },
-       [COL_MINIO]  = { "MIN-IO",  6, SCOLS_FL_RIGHT, N_("minimum I/O size") },
-       [COL_OPTIO]  = { "OPT-IO",  6, SCOLS_FL_RIGHT, N_("optimal I/O size") },
-       [COL_PHYSEC] = { "PHY-SEC", 7, SCOLS_FL_RIGHT, N_("physical sector size") },
-       [COL_LOGSEC] = { "LOG-SEC", 7, SCOLS_FL_RIGHT, N_("logical sector size") },
+       [COL_ALIOFF] = { "ALIGNMENT", 6, SCOLS_FL_RIGHT, N_("alignment offset"), SORT_U64 },
+       [COL_MINIO]  = { "MIN-IO",  6, SCOLS_FL_RIGHT, N_("minimum I/O size"), SORT_U64 },
+       [COL_OPTIO]  = { "OPT-IO",  6, SCOLS_FL_RIGHT, N_("optimal I/O size"), SORT_U64 },
+       [COL_PHYSEC] = { "PHY-SEC", 7, SCOLS_FL_RIGHT, N_("physical sector size"), SORT_U64 },
+       [COL_LOGSEC] = { "LOG-SEC", 7, SCOLS_FL_RIGHT, N_("logical sector size"), SORT_U64 },
        [COL_SCHED]  = { "SCHED",   0.1, 0, N_("I/O scheduler name") },
-       [COL_RQ_SIZE]= { "RQ-SIZE", 5, SCOLS_FL_RIGHT, N_("request queue size") },
+       [COL_RQ_SIZE]= { "RQ-SIZE", 5, SCOLS_FL_RIGHT, N_("request queue size"), SORT_U64 },
        [COL_TYPE]   = { "TYPE",    4, 0, N_("device type") },
-       [COL_DALIGN] = { "DISC-ALN", 6, SCOLS_FL_RIGHT, N_("discard alignment offset") },
-       [COL_DGRAN]  = { "DISC-GRAN", 6, SCOLS_FL_RIGHT, N_("discard granularity") },
-       [COL_DMAX]   = { "DISC-MAX", 6, SCOLS_FL_RIGHT, N_("discard max bytes") },
+       [COL_DALIGN] = { "DISC-ALN", 6, SCOLS_FL_RIGHT, N_("discard alignment offset"), SORT_U64 },
+       [COL_DGRAN]  = { "DISC-GRAN", 6, SCOLS_FL_RIGHT, N_("discard granularity"), SORT_U64 },
+       [COL_DMAX]   = { "DISC-MAX", 6, SCOLS_FL_RIGHT, N_("discard max bytes"), SORT_U64 },
        [COL_DZERO]  = { "DISC-ZERO", 1, SCOLS_FL_RIGHT, N_("discard zeroes data") },
-       [COL_WSAME]  = { "WSAME",   6, SCOLS_FL_RIGHT, N_("write same max bytes") },
+       [COL_WSAME]  = { "WSAME",   6, SCOLS_FL_RIGHT, N_("write same max bytes"), SORT_U64 },
        [COL_WWN]    = { "WWN",     18, 0, N_("unique storage identifier") },
        [COL_HCTL]   = { "HCTL", 10, 0, N_("Host:Channel:Target:Lun for SCSI") },
        [COL_TRANSPORT] = { "TRAN", 6, 0, N_("device transport type") },
@@ -175,6 +182,9 @@ static struct colinfo infos[] = {
 
 struct lsblk {
        struct libscols_table *table;   /* output table */
+       struct libscols_column *sort_col;/* sort output by this colum */
+       int sort_id;
+
        unsigned int all_devices:1;     /* print all devices, including empty */
        unsigned int bytes:1;           /* print SIZE in bytes */
        unsigned int inverse:1;         /* print inverse dependencies */
@@ -296,6 +306,16 @@ static int column_name_to_id(const char *name, size_t namesz)
        return -1;
 }
 
+static int column_id_to_number(int id)
+{
+       size_t i;
+
+       for (i = 0; i < (size_t) ncolumns; i++)
+               if (columns[i] == id)
+                       return i;
+       return -1;
+}
+
 static void reset_blkdev_cxt(struct blkdev_cxt *cxt)
 {
        if (!cxt)
@@ -699,15 +719,63 @@ static char *mk_dm_name(const char *name)
        return p;
 }
 
+/* stores data to scols cell userdata (invisible and independent on output)
+ * to make the original values accessible for sort functions
+ */
+static void set_sortdata_u64(struct libscols_line *ln, int col, uint64_t x)
+{
+       struct libscols_cell *ce = scols_line_get_cell(ln, col);
+       uint64_t *data;
+
+       if (!ce)
+               return;
+       data = xmalloc(sizeof(uint64_t));
+       *data = x;
+       scols_cell_set_userdata(ce, data);
+}
+
+static void set_sortdata_u64_from_string(struct libscols_line *ln, int col, const char *str)
+{
+       uint64_t x;
+
+       if (!str || sscanf(str, "%"SCNu64, &x) != 1)
+               return;
+
+       set_sortdata_u64(ln, col, x);
+}
+
+static void unref_sortdata(struct libscols_table *tb)
+{
+       struct libscols_iter *itr;
+       struct libscols_line *ln;
+
+       if (!tb || !lsblk->sort_col)
+               return;
+       itr = scols_new_iter(SCOLS_ITER_FORWARD);
+       if (!itr)
+               return;
+       while (scols_table_next_line(tb, itr, &ln) == 0) {
+               struct libscols_cell *ce = scols_line_get_column_cell(ln,
+                                                               lsblk->sort_col);
+               void *data = scols_cell_get_userdata(ce);
+               free(data);
+       }
+
+       scols_free_iter(itr);
+}
+
 static void set_scols_data(struct blkdev_cxt *cxt, int col, int id, struct libscols_line *ln)
 {
-       int st_rc = 0;
+       int sort = 0, st_rc = 0;
        char *str = NULL;
 
        if (!cxt->st.st_rdev && (id == COL_OWNER || id == COL_GROUP ||
                                 id == COL_MODE))
                st_rc = stat(cxt->filename, &cxt->st);
 
+       if (lsblk->sort_id == id)
+               sort = 1;
+
        switch(id) {
        case COL_NAME:
                str = cxt->dm_name ? mk_dm_name(cxt->dm_name) : mk_name(cxt->name);
@@ -748,6 +816,8 @@ static void set_scols_data(struct blkdev_cxt *cxt, int col, int id, struct libsc
                        xasprintf(&str, "%u:%u", cxt->maj, cxt->min);
                else
                        xasprintf(&str, "%3u:%-3u", cxt->maj, cxt->min);
+               if (sort)
+                       set_sortdata_u64(ln, col, makedev(cxt->maj, cxt->min));
                break;
        case COL_FSTYPE:
                probe_device(cxt);
@@ -795,6 +865,8 @@ static void set_scols_data(struct blkdev_cxt *cxt, int col, int id, struct libsc
                break;
        case COL_RA:
                str = sysfs_strdup(&cxt->sysfs, "queue/read_ahead_kb");
+               if (sort)
+                       set_sortdata_u64_from_string(ln, col, str);
                break;
        case COL_RO:
                str = xstrdup(is_readonly_device(cxt) ? "1" : "0");
@@ -836,6 +908,8 @@ static void set_scols_data(struct blkdev_cxt *cxt, int col, int id, struct libsc
                        xasprintf(&str, "%jd", cxt->size);
                else
                        str = size_to_human_string(SIZE_SUFFIX_1LETTER, cxt->size);
+               if (sort)
+                       set_sortdata_u64(ln, col, cxt->size);
                break;
        case COL_STATE:
                if (!cxt->partition && !cxt->dm_name)
@@ -848,24 +922,36 @@ static void set_scols_data(struct blkdev_cxt *cxt, int col, int id, struct libsc
                break;
        case COL_ALIOFF:
                str = sysfs_strdup(&cxt->sysfs, "alignment_offset");
+               if (sort)
+                       set_sortdata_u64_from_string(ln, col, str);
                break;
        case COL_MINIO:
                str = sysfs_strdup(&cxt->sysfs, "queue/minimum_io_size");
+               if (sort)
+                       set_sortdata_u64_from_string(ln, col, str);
                break;
        case COL_OPTIO:
                str = sysfs_strdup(&cxt->sysfs, "queue/optimal_io_size");
+               if (sort)
+                       set_sortdata_u64_from_string(ln, col, str);
                break;
        case COL_PHYSEC:
                str = sysfs_strdup(&cxt->sysfs, "queue/physical_block_size");
+               if (sort)
+                       set_sortdata_u64_from_string(ln, col, str);
                break;
        case COL_LOGSEC:
                str = sysfs_strdup(&cxt->sysfs, "queue/logical_block_size");
+               if (sort)
+                       set_sortdata_u64_from_string(ln, col, str);
                break;
        case COL_SCHED:
                str = get_scheduler(cxt);
                break;
        case COL_RQ_SIZE:
                str = sysfs_strdup(&cxt->sysfs, "queue/nr_requests");
+               if (sort)
+                       set_sortdata_u64_from_string(ln, col, str);
                break;
        case COL_TYPE:
                str = get_type(cxt);
@@ -885,25 +971,37 @@ static void set_scols_data(struct blkdev_cxt *cxt, int col, int id, struct libsc
                        str = sysfs_strdup(&cxt->sysfs, "discard_alignment");
                if (!str)
                        str = xstrdup("0");
+               if (sort)
+                       set_sortdata_u64_from_string(ln, col, str);
                break;
        case COL_DGRAN:
-               if (lsblk->bytes)
+               if (lsblk->bytes) {
                        str = sysfs_strdup(&cxt->sysfs, "queue/discard_granularity");
-               else {
+                       if (sort)
+                               set_sortdata_u64_from_string(ln, col, str);
+               } else {
                        uint64_t x;
                        if (sysfs_read_u64(&cxt->sysfs,
-                                          "queue/discard_granularity", &x) == 0)
+                                          "queue/discard_granularity", &x) == 0) {
                                str = size_to_human_string(SIZE_SUFFIX_1LETTER, x);
+                               if (sort)
+                                       set_sortdata_u64(ln, col, x);
+                       }
                }
                break;
        case COL_DMAX:
-               if (lsblk->bytes)
+               if (lsblk->bytes) {
                        str = sysfs_strdup(&cxt->sysfs, "queue/discard_max_bytes");
-               else {
+                       if (sort)
+                               set_sortdata_u64_from_string(ln, col, str);
+               } else {
                        uint64_t x;
                        if (sysfs_read_u64(&cxt->sysfs,
-                                          "queue/discard_max_bytes", &x) == 0)
+                                          "queue/discard_max_bytes", &x) == 0) {
                                str = size_to_human_string(SIZE_SUFFIX_1LETTER, x);
+                               if (sort)
+                                       set_sortdata_u64(ln, col, x);
+                       }
                }
                break;
        case COL_DZERO:
@@ -913,14 +1011,19 @@ static void set_scols_data(struct blkdev_cxt *cxt, int col, int id, struct libsc
                        str = xstrdup("0");
                break;
        case COL_WSAME:
-               if (lsblk->bytes)
+               if (lsblk->bytes) {
                        str = sysfs_strdup(&cxt->sysfs, "queue/write_same_max_bytes");
-               else {
+                       if (sort)
+                               set_sortdata_u64_from_string(ln, col, str);
+               } else {
                        uint64_t x;
 
                        if (sysfs_read_u64(&cxt->sysfs,
-                                          "queue/write_same_max_bytes", &x) == 0)
+                                          "queue/write_same_max_bytes", &x) == 0) {
                                str = size_to_human_string(SIZE_SUFFIX_1LETTER, x);
+                               if (sort)
+                                       set_sortdata_u64(ln, col, x);
+                       }
                }
                if (!str)
                        str = xstrdup("0");
@@ -931,7 +1034,7 @@ static void set_scols_data(struct blkdev_cxt *cxt, int col, int id, struct libsc
                scols_line_refer_data(ln, col, str);
 }
 
-static void print_device(struct blkdev_cxt *cxt, struct libscols_line *scols_parent)
+static void fill_table_line(struct blkdev_cxt *cxt, struct libscols_line *scols_parent)
 {
        int i;
 
@@ -1064,7 +1167,7 @@ static int list_partitions(struct blkdev_cxt *wholedisk_cxt, struct blkdev_cxt *
                                goto next;
 
                        wholedisk_cxt->parent = &part_cxt;
-                       print_device(&part_cxt, parent_cxt ? parent_cxt->scols_line : NULL);
+                       fill_table_line(&part_cxt, parent_cxt ? parent_cxt->scols_line : NULL);
                        if (!lsblk->nodeps)
                                process_blkdev(wholedisk_cxt, &part_cxt, 0, NULL);
                } else {
@@ -1078,7 +1181,7 @@ static int list_partitions(struct blkdev_cxt *wholedisk_cxt, struct blkdev_cxt *
 
                        /* Print whole disk only once */
                        if (r)
-                               print_device(wholedisk_cxt, parent_cxt ? parent_cxt->scols_line : NULL);
+                               fill_table_line(wholedisk_cxt, parent_cxt ? parent_cxt->scols_line : NULL);
                        if (ps == 0 && !lsblk->nodeps)
                                process_blkdev(&part_cxt, wholedisk_cxt, 0, NULL);
                }
@@ -1167,7 +1270,7 @@ static int process_blkdev(struct blkdev_cxt *cxt, struct blkdev_cxt *parent,
        if (do_partitions && cxt->npartitions)
                return list_partitions(cxt, parent, part_name);
 
-       print_device(cxt, parent ? parent->scols_line : NULL);
+       fill_table_line(cxt, parent ? parent->scols_line : NULL);
        return list_deps(cxt);
 }
 
@@ -1339,6 +1442,25 @@ static void parse_includes(const char *str0)
        }
 }
 
+/*
+ * see set_sortdata_u64() and columns initialization in main()
+ */
+static int cmp_u64_cells(struct libscols_cell *a,
+                        struct libscols_cell *b,
+                        __attribute__((__unused__)) void *data)
+{
+       uint64_t *adata = (uint64_t *) scols_cell_get_userdata(a),
+                *bdata = (uint64_t *) scols_cell_get_userdata(b);
+
+       if (adata == NULL && bdata == NULL)
+               return 0;
+       if (adata == NULL)
+               return -1;
+       if (bdata == NULL)
+               return 1;
+       return *adata == *bdata ? 0 : *adata >= *bdata ? 1 : -1;
+}
+
 static void __attribute__((__noreturn__)) help(FILE *out)
 {
        size_t i;
@@ -1365,6 +1487,7 @@ static void __attribute__((__noreturn__)) help(FILE *out)
        fputs(_(" -s, --inverse        inverse dependencies\n"), out);
        fputs(_(" -S, --scsi           output info about SCSI devices\n"), out);
        fputs(_(" -t, --topology       output info about topology\n"), out);
+       fputs(_(" -x, --sort <column>  sort output by <colum>\n"), out);
        fputs(USAGE_SEPARATOR, out);
        fputs(USAGE_HELP, out);
        fputs(USAGE_VERSION, out);
@@ -1388,7 +1511,7 @@ static void check_sysdevblock(void)
 
 int main(int argc, char *argv[])
 {
-       struct lsblk _ls;
+       struct lsblk _ls = { .sort_id = -1 };
        int scols_flags = LSBLK_TREE;
        int i, c, status = EXIT_FAILURE;
        char *outarg = NULL;
@@ -1414,6 +1537,7 @@ int main(int argc, char *argv[])
                { "paths",      0, 0, 'p' },
                { "pairs",      0, 0, 'P' },
                { "scsi",       0, 0, 'S' },
+               { "sort",       1, 0, 'x' },
                { "version",    0, 0, 'V' },
                { NULL, 0, 0, 0 },
        };
@@ -1439,7 +1563,7 @@ int main(int argc, char *argv[])
        memset(lsblk, 0, sizeof(*lsblk));
 
        while((c = getopt_long(argc, argv,
-                              "abdDe:fhlnmo:OpPiI:rstVS", longopts, NULL)) != -1) {
+                              "abdDe:fhlnmo:OpPiI:rstVSx:", longopts, NULL)) != -1) {
 
                err_exclusive_options(c, longopts, excl, excl_st);
 
@@ -1540,6 +1664,12 @@ int main(int argc, char *argv[])
                case 'V':
                        printf(UTIL_LINUX_VERSION);
                        return EXIT_SUCCESS;
+               case 'x':
+                       scols_flags &= ~LSBLK_TREE; /* disable the default */
+                       lsblk->sort_id = column_name_to_id(optarg, strlen(optarg));
+                       if (lsblk->sort_id >= 0)
+                               break;
+                       /* fallthrough */
                default:
                        help(stderr);
                }
@@ -1564,6 +1694,9 @@ int main(int argc, char *argv[])
        if (nexcludes == 0 && nincludes == 0)
                excludes[nexcludes++] = 1;      /* default: ignore RAM disks */
 
+       if (lsblk->sort_id >= 0 && column_id_to_number(lsblk->sort_id) < 0)
+               errx(EXIT_FAILURE, _("the sort column has to be between output columns."));
+
        mnt_init_debug(0);
 
        /*
@@ -1578,15 +1711,23 @@ int main(int argc, char *argv[])
 
        for (i = 0; i < ncolumns; i++) {
                struct colinfo *ci = get_column_info(i);
-               int fl = ci->flags;
+               struct libscols_column *cl;
+               int id = get_column_id(i), fl = ci->flags;
 
-               if (!(scols_flags & LSBLK_TREE) && get_column_id(i) == COL_NAME)
+               if (!(scols_flags & LSBLK_TREE) && id == COL_NAME)
                        fl &= ~SCOLS_FL_TREE;
 
-               if (!scols_table_new_column(lsblk->table, ci->name, ci->whint, fl)) {
+               cl = scols_table_new_column(lsblk->table, ci->name, ci->whint, fl);
+               if (!cl) {
                        warn(_("failed to initialize output column"));
                        goto leave;
                }
+               if (!lsblk->sort_col && lsblk->sort_id == id) {
+                       lsblk->sort_col = cl;
+                       scols_column_set_cmpfunc(cl,
+                               ci->sort_type == SORT_STRING ?
+                               scols_cmpstr_cells : cmp_u64_cells, NULL);
+               }
        }
 
        if (optind == argc)
@@ -1594,9 +1735,15 @@ int main(int argc, char *argv[])
        else while (optind < argc)
                status = process_one_device(argv[optind++]);
 
+       if (lsblk->sort_col)
+               scols_sort_table(lsblk->table, lsblk->sort_col);
+
        scols_print_table(lsblk->table);
 
 leave:
+       if (lsblk->sort_col)
+               unref_sortdata(lsblk->table);
+
        scols_unref_table(lsblk->table);
 
        mnt_unref_table(mtab);