diff options
47 files changed, 4837 insertions, 2291 deletions
diff --git a/Documentation/TODO b/Documentation/TODO index 86ee25af9..03e0a75cb 100644 --- a/Documentation/TODO +++ b/Documentation/TODO @@ -34,12 +34,6 @@ lscpu lsblk ----- - - re-write the way how lsblk internally holds info about devices. Now we - compose only output (by libsmartcols). It would be better to have in memory - complete tree of the devices and generate the output from this tree. It will - make code more readable and allow to generate output in more ways -- for - example for RAIDs (https://github.com/karelzak/util-linux/issues/616) - - currently it does not show mountpoint for all devices in btrfs RAID. It's because /proc/#/mountinfo contains reference to the one device only. Maybe we can add some btrfs specific code to provide a better output for FS based stacks. Not sure. diff --git a/configure.ac b/configure.ac index 6ed505de5..4c8de521f 100644 --- a/configure.ac +++ b/configure.ac @@ -544,18 +544,24 @@ AC_CHECK_FUNCS([timer_create], [AC_CHECK_LIB([rt], [timer_create], [ have_timer="yes" REALTIME_LIBS="-lrt" + AC_DEFINE_UNQUOTED([HAVE_TIMER_CREATE], [1], [Define if timer_create exist in -lrt]) ],[ AC_SEARCH_LIBS([timer_create], [rt], [ AC_MSG_RESULT(yes) have_timer="yes" REALTIME_LIBS="-lrt -lpthread" + AC_DEFINE_UNQUOTED([HAVE_TIMER_CREATE], [1], [Define if timer_create exist in -lrt -lpthread]) ],[], [-lpthread] ) - ])] + ])] ) AC_SUBST([REALTIME_LIBS]) +AS_IF([test x"$have_timer" = xno], [ + AC_CHECK_FUNCS([setitimer], [have_timer="yes"], [have_timer="no"]) +]) + AC_CHECK_LIB([rtas], [rtas_get_sysparm], [ RTAS_LIBS="-lrtas" @@ -594,10 +600,7 @@ AS_IF([test x"$have_dirfd" = xno], [ #include <dirent.h>]) ]) -AS_CASE([$have_dirfd:$have_ddfd], - [no:no], - [AC_MSG_ERROR([cannot find a method to get filedescriptor of directory])] -) +AM_CONDITIONAL([HAVE_DIRFD], [test "x$have_dirfd" = xyes -o "x$have_ddfd" = xyes]) AC_MSG_CHECKING([whether program_invocation_short_name is defined]) @@ -784,6 +787,7 @@ AC_CHECK_DECLS([CPU_ALLOC], [], [], [[ # on Solaris, you can't mix and match standards, since we use c99 # aparently at this stage, XOPEN_SOURCE will conflict. As workaround, # check for crypt.h and use that without XOPEN_SOURCE. +have_crypt=no AC_CHECK_HEADERS([crypt.h]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #ifdef HAVE_CRYPT_H @@ -808,8 +812,9 @@ char *c = crypt("abc","pw"); ]])],[ AC_DEFINE([HAVE_LIBCRYPT], [1], [Do we need -lcrypt?]) have_libcrypt=yes + have_crypt=yes ],[ - AC_MSG_ERROR([crypt() is not available]) + AC_MSG_WARN([crypt() is not available]) ]) ]) AM_CONDITIONAL([HAVE_LIBCRYPT], [test "x$have_libcrypt" = xyes]) @@ -1065,6 +1070,7 @@ AC_ARG_ENABLE([libblkid], ) UL_BUILD_INIT([libblkid]) UL_REQUIRES_HAVE([libblkid], [openat], [openat functions]) +UL_REQUIRES_HAVE([libblkid], [dirfd,ddfd], [dirfd or ddfd function]) AC_SUBST([LIBBLKID_DATE]) AC_SUBST([LIBBLKID_VERSION]) AC_SUBST([LIBBLKID_VERSION_INFO]) @@ -1086,6 +1092,7 @@ AC_ARG_ENABLE([libmount], UL_BUILD_INIT([libmount]) UL_REQUIRES_BUILD([libmount], [libblkid]) UL_REQUIRES_HAVE([libmount], [scanf_alloc_modifier], [scanf string alloc modifier]) +UL_REQUIRES_HAVE([libmount], [dirfd,ddfd], [dirfd or ddfd function]) AM_CONDITIONAL([BUILD_LIBMOUNT], [test "x$build_libmount" = xyes]) AM_CONDITIONAL([BUILD_LIBMOUNT_TESTS], [test "x$build_libmount" = xyes -a "x$enable_static" = xyes]) AS_IF([test "x$build_libmount" = xyes], [ @@ -1162,6 +1169,7 @@ enable_cfdisk=$enable_fdisks UL_BUILD_INIT([fdisk]) UL_REQUIRES_HAVE([fdisk], [openat], [openat functions]) +UL_REQUIRES_HAVE([fdisk], [dirfd,ddfd], [dirfd or ddfd function]) UL_REQUIRES_BUILD([fdisk], [libfdisk]) UL_REQUIRES_BUILD([fdisk], [libsmartcols]) AM_CONDITIONAL([BUILD_FDISK], [test "x$build_fdisk" = xyes]) @@ -1169,6 +1177,7 @@ AM_CONDITIONAL([BUILD_FDISK], [test "x$build_fdisk" = xyes]) UL_BUILD_INIT([sfdisk]) UL_REQUIRES_HAVE([sfdisk], [openat], [openat functions]) +UL_REQUIRES_HAVE([sfdisk], [dirfd,ddfd], [dirfd or ddfd function]) UL_REQUIRES_BUILD([sfdisk], [libfdisk]) UL_REQUIRES_BUILD([sfdisk], [libsmartcols]) AM_CONDITIONAL([BUILD_SFDISK], [test "x$build_sfdisk" = xyes]) @@ -1636,6 +1645,7 @@ AC_ARG_ENABLE([switch_root], UL_BUILD_INIT([switch_root]) UL_REQUIRES_LINUX([switch_root]) UL_REQUIRES_HAVE([switch_root], [openat], [openat function]) +UL_REQUIRES_HAVE([switch_root], [dirfd,ddfd], [dirfd or ddfd function]) AM_CONDITIONAL([BUILD_SWITCH_ROOT], [test "x$build_switch_root" = xyes]) @@ -1650,7 +1660,7 @@ AM_CONDITIONAL([BUILD_PIVOT_ROOT], [test "x$build_pivot_root" = xyes]) UL_BUILD_INIT([flock], [check]) -UL_REQUIRES_HAVE([flock], [timer], [timer_create function]) +UL_REQUIRES_HAVE([flock], [timer], [timer_create/setitimer function]) AM_CONDITIONAL([BUILD_FLOCK], [test "x$build_flock" = xyes]) @@ -1882,6 +1892,7 @@ AC_ARG_ENABLE([newgrp], [], [UL_DEFAULT_ENABLE([newgrp], [no])] ) UL_BUILD_INIT([newgrp]) +UL_REQUIRES_HAVE([newgrp], [crypt], [crypt function]) AM_CONDITIONAL([BUILD_NEWGRP], [test "x$build_newgrp" = xyes]) @@ -1978,6 +1989,7 @@ AC_ARG_ENABLE([sulogin], [], [UL_DEFAULT_ENABLE([sulogin], [check])] ) UL_BUILD_INIT([sulogin]) +UL_REQUIRES_HAVE([sulogin], [crypt], [crypt function]) UL_REQUIRES_HAVE([sulogin], [shadow_h], [shadow.h header]) AM_CONDITIONAL([BUILD_SULOGIN], [test "x$build_sulogin" = xyes]) diff --git a/include/c.h b/include/c.h index 059fadf0b..fb6835253 100644 --- a/include/c.h +++ b/include/c.h @@ -147,8 +147,8 @@ */ #ifndef container_of #define container_of(ptr, type, member) __extension__ ({ \ - void *__mptr = (void *)(ptr); \ - ((type *)(__mptr - offsetof(type, member))); }) + const __typeof__( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) #endif #ifndef HAVE_PROGRAM_INVOCATION_SHORT_NAME diff --git a/include/list.h b/include/list.h index f3ffc7985..96c84e572 100644 --- a/include/list.h +++ b/include/list.h @@ -137,6 +137,16 @@ _INLINE_ int list_entry_is_last(struct list_head *entry, struct list_head *head) } /** + * list_entry_is_first - tests whether is entry first in the list + * @entry: the entry to test. + * @head: the list to test. + */ +_INLINE_ int list_entry_is_first(struct list_head *entry, struct list_head *head) +{ + return head->next == entry; +} + +/** * list_splice - join two lists * @list: the new list to add. * @head: the place to add it in the first list. @@ -198,6 +208,17 @@ _INLINE_ void list_splice(struct list_head *list, struct list_head *head) for (pos = (head)->next, pnext = pos->next; pos != (head); \ pos = pnext, pnext = pos->next) +_INLINE_ size_t list_count_entries(struct list_head *head) +{ + struct list_head *pos; + size_t ct = 0; + + list_for_each(pos, head) + ct++; + + return ct; +} + #define MAX_LIST_LENGTH_BITS 20 /* diff --git a/include/path.h b/include/path.h index 7ab9779a5..b34aa366e 100644 --- a/include/path.h +++ b/include/path.h @@ -40,6 +40,8 @@ void *ul_path_get_dialect(struct path_cxt *pc); int ul_path_set_enoent_redirect(struct path_cxt *pc, int (*func)(struct path_cxt *, const char *, int *)); int ul_path_get_dirfd(struct path_cxt *pc); +void ul_path_close_dirfd(struct path_cxt *pc); +int ul_path_isopen_dirfd(struct path_cxt *pc); char *ul_path_get_abspath(struct path_cxt *pc, char *buf, size_t bufsz, const char *path, ...) __attribute__ ((__format__ (__printf__, 4, 5))); diff --git a/include/sysfs.h b/include/sysfs.h index 74f69fb9c..edda8ca99 100644 --- a/include/sysfs.h +++ b/include/sysfs.h @@ -97,6 +97,7 @@ dev_t sysfs_devname_to_devno(const char *name); dev_t __sysfs_devname_to_devno(const char *prefix, const char *name, const char *parent); char *sysfs_devno_to_devpath(dev_t devno, char *buf, size_t bufsiz); char *sysfs_devno_to_devname(dev_t devno, char *buf, size_t bufsiz); +int sysfs_devno_count_partitions(dev_t devno); int sysfs_blkdev_scsi_get_hctl(struct path_cxt *pc, int *h, int *c, int *t, int *l); char *sysfs_blkdev_scsi_host_strdup_attribute(struct path_cxt *pc, diff --git a/include/timer.h b/include/timer.h index aa9f9c018..70da1ba9e 100644 --- a/include/timer.h +++ b/include/timer.h @@ -4,8 +4,19 @@ #include <signal.h> #include <sys/time.h> -extern int setup_timer(timer_t * t_id, struct itimerval *timeout, +#ifdef HAVE_TIMER_CREATE +struct ul_timer { + timer_t t_id; +}; +#else +struct ul_timer { + struct itimerval old_timer; + struct sigaction old_sa; +}; +#endif + +extern int setup_timer(struct ul_timer *timer, struct itimerval *timeout, void (*timeout_handler)(int, siginfo_t *, void *)); -extern void cancel_timer(timer_t * t_id); +extern void cancel_timer(struct ul_timer *timer); #endif /* UTIL_LINUX_TIMER_H */ diff --git a/lib/Makemodule.am b/lib/Makemodule.am index 13b19f151..862a06c17 100644 --- a/lib/Makemodule.am +++ b/lib/Makemodule.am @@ -48,10 +48,12 @@ libcommon_la_SOURCES += lib/cpuset.c endif if HAVE_OPENAT +if HAVE_DIRFD libcommon_la_SOURCES += lib/path.c libcommon_la_SOURCES += lib/procutils.c libcommon_la_SOURCES += lib/sysfs.c endif +endif noinst_LTLIBRARIES += libtcolors.la libtcolors_la_CFLAGS = $(AM_CFLAGS) @@ -96,9 +98,11 @@ check_PROGRAMS += \ endif if HAVE_OPENAT +if HAVE_DIRFD check_PROGRAMS += test_procutils check_PROGRAMS += test_path endif +endif test_ttyutils_SOURCES = lib/ttyutils.c test_ttyutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_TTYUTILS @@ -126,11 +130,10 @@ test_randutils_SOURCES = lib/randutils.c test_randutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_RANDUTILS if HAVE_OPENAT +if HAVE_DIRFD test_procutils_SOURCES = lib/procutils.c test_procutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PROCUTILS -endif -if HAVE_OPENAT test_path_SOURCES = lib/path.c lib/fileutils.c if HAVE_CPU_SET_T test_path_SOURCES += lib/cpuset.c @@ -138,6 +141,7 @@ endif test_path_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PATH test_path_LDADD = $(LDADD) endif +endif if LINUX test_cpuset_SOURCES = lib/cpuset.c diff --git a/lib/path.c b/lib/path.c index 9cc2e3e2e..d36fe41e8 100644 --- a/lib/path.c +++ b/lib/path.c @@ -94,8 +94,7 @@ void ul_unref_path(struct path_cxt *pc) DBG(CXT, ul_debugobj(pc, "dealloc")); if (pc->dialect) pc->free_dialect(pc); - if (pc->dir_fd >= 0) - close(pc->dir_fd); + ul_path_close_dirfd(pc); free(pc->dir_path); free(pc->prefix); free(pc); @@ -212,6 +211,23 @@ int ul_path_get_dirfd(struct path_cxt *pc) return pc->dir_fd; } +/* Note that next ul_path_get_dirfd() will reopen the directory */ +void ul_path_close_dirfd(struct path_cxt *pc) +{ + assert(pc); + + if (pc->dir_fd >= 0) { + DBG(CXT, ul_debugobj(pc, "closing dir: '%s'", pc->dir_path)); + close(pc->dir_fd); + pc->dir_fd = -1; + } +} + +int ul_path_isopen_dirfd(struct path_cxt *pc) +{ + return pc && pc->dir_fd >= 0; +} + static const char *ul_path_mkpath(struct path_cxt *pc, const char *path, va_list ap) { int rc = vsnprintf(pc->path_buffer, sizeof(pc->path_buffer), path, ap); @@ -301,7 +317,7 @@ int ul_path_open(struct path_cxt *pc, int flags, const char *path) if (!pc) { fd = open(path, flags); - DBG(CXT, ul_debug("opening '%s'", path)); + DBG(CXT, ul_debug("opening '%s' [no context]", path)); } else { int fdx; int dir = ul_path_get_dirfd(pc); diff --git a/lib/sysfs.c b/lib/sysfs.c index 626451bdd..6916c2584 100644 --- a/lib/sysfs.c +++ b/lib/sysfs.c @@ -999,6 +999,20 @@ char *sysfs_devno_to_devname(dev_t devno, char *buf, size_t bufsiz) return res; } +int sysfs_devno_count_partitions(dev_t devno) +{ + struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL); + int n = 0; + + if (pc) { + char buf[PATH_MAX + 1]; + char *name = sysfs_blkdev_get_name(pc, buf, sizeof(buf)); + + n = sysfs_blkdev_count_partitions(pc, name); + ul_unref_path(pc); + } + return n; +} #ifdef TEST_PROGRAM_SYSFS diff --git a/lib/timer.c b/lib/timer.c index 210c726cb..c1ea54eb8 100644 --- a/lib/timer.c +++ b/lib/timer.c @@ -27,7 +27,9 @@ * The applications need to ensure that they can tolerate multiple signal * deliveries. */ -int setup_timer(timer_t * t_id, struct itimerval *timeout, +#ifdef HAVE_TIMER_CREATE +int setup_timer(struct ul_timer *timer, + struct itimerval *timeout, void (*timeout_handler)(int, siginfo_t *, void *)) { time_t sec = timeout->it_value.tv_sec; @@ -52,14 +54,42 @@ int setup_timer(timer_t * t_id, struct itimerval *timeout, if (sigaction(SIGALRM, &sig_a, NULL)) return 1; - if (timer_create(CLOCK_MONOTONIC, &sig_e, t_id)) + if (timer_create(CLOCK_MONOTONIC, &sig_e, &timer->t_id)) return 1; - if (timer_settime(*t_id, 0, &val, NULL)) + if (timer_settime(timer->t_id, 0, &val, NULL)) return 1; return 0; } +void cancel_timer(struct ul_timer *timer) +{ + timer_delete(timer->t_id); +} + +#else /* !HAVE_TIMER_CREATE */ + +int setup_timer(struct ul_timer *timer, + struct itimerval *timeout, + void (*timeout_handler)(int, siginfo_t *, void *)) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof sa); + memset(timer, 0, sizeof(*timer)); -void cancel_timer(timer_t *t_id) + sa.sa_flags = SA_SIGINFO | SA_RESETHAND; + sa.sa_sigaction = timeout_handler; + + if (sigaction(SIGALRM, &sa, &timer->old_sa)) + return 1; + if (setitimer(ITIMER_REAL, timeout, &timer->old_timer) != 0) + return 1; + return 0; +} + +void cancel_timer(struct ul_timer *timer) { - timer_delete(*t_id); + setitimer(ITIMER_REAL, &timer->old_timer, NULL); + sigaction(SIGALRM, &timer->old_sa, NULL); + } +#endif /* !HAVE_TIMER_CREATE */ diff --git a/libmount/docs/libmount-sections.txt b/libmount/docs/libmount-sections.txt index 811726bb2..a046fb387 100644 --- a/libmount/docs/libmount-sections.txt +++ b/libmount/docs/libmount-sections.txt @@ -398,6 +398,7 @@ mnt_get_fstab_path mnt_get_mountpoint mnt_get_mtab_path mnt_get_swaps_path +mnt_guess_system_root mnt_has_regular_mtab mnt_mangle mnt_match_fstype diff --git a/libmount/src/libmount.h.in b/libmount/src/libmount.h.in index 63ba3225f..1ce9995ec 100644 --- a/libmount/src/libmount.h.in +++ b/libmount/src/libmount.h.in @@ -326,6 +326,8 @@ extern const char *mnt_get_mtab_path(void); extern int mnt_has_regular_mtab(const char **mtab, int *writable); extern char *mnt_get_mountpoint(const char *path) __ul_attribute__((warn_unused_result)); +extern int mnt_guess_system_root(dev_t devno, struct libmnt_cache *cache, char **path) + __ul_attribute__((nonnull(3))); /* cache.c */ extern struct libmnt_cache *mnt_new_cache(void) diff --git a/libmount/src/libmount.sym b/libmount/src/libmount.sym index ad7bd4786..5145c876d 100644 --- a/libmount/src/libmount.sym +++ b/libmount/src/libmount.sym @@ -341,3 +341,7 @@ MOUNT_2.33 { mnt_context_switch_origin_ns; mnt_context_switch_target_ns; } MOUNT_2.30; + +MOUNT_2.34 { + mnt_guess_system_root; +} MOUNT_2.33; diff --git a/libmount/src/mountP.h b/libmount/src/mountP.h index 8f4fba7af..795ea69dc 100644 --- a/libmount/src/mountP.h +++ b/libmount/src/mountP.h @@ -120,7 +120,6 @@ extern int mnt_get_filesystems(char ***filesystems, const char *pattern); extern void mnt_free_filesystems(char **filesystems); extern char *mnt_get_kernel_cmdline_option(const char *name); -extern int mnt_guess_system_root(dev_t devno, struct libmnt_cache *cache, char **path); extern int mnt_stat_mountpoint(const char *target, struct stat *st); /* tab.c */ diff --git a/libmount/src/utils.c b/libmount/src/utils.c index 034d3436f..4f4d10bf9 100644 --- a/libmount/src/utils.c +++ b/libmount/src/utils.c @@ -1037,7 +1037,12 @@ char *mnt_get_kernel_cmdline_option(const char *name) return res; } -/* +/** + * mnt_guess_system_root: + * @devno: device number or zero + * @cache: paths cache or NULL + * @path: returns allocated path + * * Converts @devno to the real device name if devno major number is greater * than zero, otherwise use root= kernel cmdline option to get device name. * diff --git a/libsmartcols/docs/libsmartcols-sections.txt b/libsmartcols/docs/libsmartcols-sections.txt index 79786b544..72c4ab866 100644 --- a/libsmartcols/docs/libsmartcols-sections.txt +++ b/libsmartcols/docs/libsmartcols-sections.txt @@ -88,6 +88,12 @@ scols_unref_line </SECTION> <SECTION> +<FILE>grouping</FILE> +scols_line_link_group +scols_table_group_lines +</SECTION> + +<SECTION> <FILE>symbols</FILE> libscols_symbols scols_copy_symbols @@ -98,6 +104,13 @@ scols_symbols_set_right scols_symbols_set_vertical scols_symbols_set_title_padding scols_symbols_set_cell_padding +scols_symbols_set_group_vertical +scols_symbols_set_group_horizontal +scols_symbols_set_group_first_member +scols_symbols_set_group_last_member +scols_symbols_set_group_middle_member +scols_symbols_set_group_last_child +scols_symbols_set_group_middle_child scols_unref_symbols </SECTION> diff --git a/libsmartcols/samples/Makemodule.am b/libsmartcols/samples/Makemodule.am index 644ac129e..d6ab25c13 100644 --- a/libsmartcols/samples/Makemodule.am +++ b/libsmartcols/samples/Makemodule.am @@ -4,6 +4,8 @@ check_PROGRAMS += \ sample-scols-wrap \ sample-scols-continuous \ sample-scols-fromfile \ + sample-scols-grouping-simple \ + sample-scols-grouping-overlay \ sample-scols-maxout sample_scols_cflags = $(AM_CFLAGS) $(NO_UNUSED_WARN_CFLAGS) \ @@ -37,3 +39,10 @@ sample_scols_fromfile_SOURCES = libsmartcols/samples/fromfile.c sample_scols_fromfile_LDADD = $(sample_scols_ldadd) libcommon.la sample_scols_fromfile_CFLAGS = $(sample_scols_cflags) +sample_scols_grouping_simple_SOURCES = libsmartcols/samples/grouping-simple.c +sample_scols_grouping_simple_LDADD = $(sample_scols_ldadd) libcommon.la +sample_scols_grouping_simple_CFLAGS = $(sample_scols_cflags) + +sample_scols_grouping_overlay_SOURCES = libsmartcols/samples/grouping-overlay.c +sample_scols_grouping_overlay_LDADD = $(sample_scols_ldadd) libcommon.la +sample_scols_grouping_overlay_CFLAGS = $(sample_scols_cflags) diff --git a/libsmartcols/samples/grouping-overlay.c b/libsmartcols/samples/grouping-overlay.c new file mode 100644 index 000000000..ef125a660 --- /dev/null +++ b/libsmartcols/samples/grouping-overlay.c @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2018 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <getopt.h> + +#include "c.h" +#include "nls.h" +#include "strutils.h" +#include "xalloc.h" + +#include "libsmartcols.h" + + +enum { COL_NAME, COL_DATA }; + +/* add columns to the @tb */ +static void setup_columns(struct libscols_table *tb) +{ + if (!scols_table_new_column(tb, "NAME", 0, SCOLS_FL_TREE)) + goto fail; + if (!scols_table_new_column(tb, "DATA", 0, 0)) + goto fail; + return; +fail: + scols_unref_table(tb); + err(EXIT_FAILURE, "failed to create output columns"); +} + +static struct libscols_line *add_line(struct libscols_table *tb, struct libscols_line *parent, const char *name, const char *data) +{ + struct libscols_line *ln = scols_table_new_line(tb, parent); + if (!ln) + err(EXIT_FAILURE, "failed to create output line"); + + if (scols_line_set_data(ln, COL_NAME, name)) + goto fail; + if (scols_line_set_data(ln, COL_DATA, data)) + goto fail; + return ln; +fail: + scols_unref_table(tb); + err(EXIT_FAILURE, "failed to create output line"); +} + +int main(int argc, char *argv[]) +{ + struct libscols_table *tb; + struct libscols_line *ln; /* any line */ + struct libscols_line *g1, *g2, *g3; /* groups */ + struct libscols_line *p1; /* parents */ + int c; + + static const struct option longopts[] = { + { "maxout", 0, NULL, 'm' }, + { "width", 1, NULL, 'w' }, + { "help", 1, NULL, 'h' }, + + { NULL, 0, NULL, 0 }, + }; + + setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */ + + scols_init_debug(0); + + tb = scols_new_table(); + if (!tb) + err(EXIT_FAILURE, "failed to create output table"); + + while((c = getopt_long(argc, argv, "hmw:", longopts, NULL)) != -1) { + switch(c) { + case 'h': + printf("%s [--help | --maxout | --width <num>]\n", program_invocation_short_name); + break; + case 'm': + scols_table_enable_maxout(tb, TRUE); + break; + case 'w': + scols_table_set_termforce(tb, SCOLS_TERMFORCE_ALWAYS); + scols_table_set_termwidth(tb, strtou32_or_err(optarg, "failed to parse terminal width")); + break; + } + } + + scols_table_enable_colors(tb, isatty(STDOUT_FILENO)); + setup_columns(tb); + + add_line(tb, NULL, "Alone", "bla bla bla"); + + p1 = add_line(tb, NULL, "A", "bla bla bla"); + add_line(tb, p1, "A:B", "bla bla bla"); + add_line(tb, p1, "A:C", "bla bla bla"); + + g1 = add_line(tb, NULL, "B", "bla bla bla"); + + g2 = add_line(tb, NULL, "C", "bla bla bla"); + ln = add_line(tb, NULL, "D", "bla bla bla"); + scols_table_group_lines(tb, g2, ln, 0); + + ln = add_line(tb, NULL, "G2:A", "alb alb alb"); + scols_line_link_group(ln, g2, 0); + + ln = add_line(tb, NULL, "E", "bla bla bla"); + scols_table_group_lines(tb, g1, ln, 0); + + + ln = add_line(tb, NULL, "G1:A", "alb alb alb"); + scols_line_link_group(ln, g1, 0); + + add_line(tb, NULL, "G", "bla bla bla"); + + g3 = ln = add_line(tb, NULL, "G1:B", "alb alb alb"); + scols_line_link_group(ln, g1, 0); + + ln = add_line(tb, NULL, "F", "bla bla bla"); + scols_table_group_lines(tb, g3, ln, 0); + + ln = add_line(tb, NULL, "G3:A", "alb alb alb"); + scols_line_link_group(ln, g3, 0); + + add_line(tb, NULL, "foo", "bla bla bla"); + add_line(tb, NULL, "bar", "bla bla bla"); + + scols_print_table(tb); + + scols_unref_table(tb); + return EXIT_SUCCESS; +} diff --git a/libsmartcols/samples/grouping-simple.c b/libsmartcols/samples/grouping-simple.c new file mode 100644 index 000000000..8f363feea --- /dev/null +++ b/libsmartcols/samples/grouping-simple.c @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <getopt.h> + +#include "c.h" +#include "nls.h" +#include "strutils.h" +#include "xalloc.h" + +#include "libsmartcols.h" + + +enum { COL_NAME, COL_DATA }; + +/* add columns to the @tb */ +static void setup_columns(struct libscols_table *tb) +{ + if (!scols_table_new_column(tb, "NAME", 0, SCOLS_FL_TREE)) + goto fail; + if (!scols_table_new_column(tb, "DATA", 0, 0)) + goto fail; + return; +fail: + scols_unref_table(tb); + err(EXIT_FAILURE, "failed to create output columns"); +} + +static struct libscols_line *add_line(struct libscols_table *tb, struct libscols_line *parent, const char *name, const char *data) +{ + struct libscols_line *ln = scols_table_new_line(tb, parent); + if (!ln) + err(EXIT_FAILURE, "failed to create output line"); + + if (scols_line_set_data(ln, COL_NAME, name)) + goto fail; + if (scols_line_set_data(ln, COL_DATA, data)) + goto fail; + return ln; +fail: + scols_unref_table(tb); + err(EXIT_FAILURE, "failed to create output line"); +} + +int main(int argc, char *argv[]) +{ + struct libscols_table *tb; + struct libscols_line *ln; /* any line */ + struct libscols_line *g1; /* groups */ + struct libscols_line *p1, *p2; /* parents */ + int c; + + static const struct option longopts[] = { + { "maxout", 0, NULL, 'm' }, + { "width", 1, NULL, 'w' }, + { "help", 1, NULL, 'h' }, + + { NULL, 0, NULL, 0 }, + }; + + setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */ + + scols_init_debug(0); + + tb = scols_new_table(); + if (!tb) + err(EXIT_FAILURE, "failed to create output table"); + + while((c = getopt_long(argc, argv, "hmw:", longopts, NULL)) != -1) { + switch(c) { + case 'h': + printf("%s [--help | --maxout | --width <num>]\n", program_invocation_short_name); + break; + case 'm': + scols_table_enable_maxout(tb, TRUE); + break; + case 'w': + scols_table_set_termforce(tb, SCOLS_TERMFORCE_ALWAYS); + scols_table_set_termwidth(tb, strtou32_or_err(optarg, "failed to parse terminal width")); + break; + } + } + + scols_table_enable_colors(tb, isatty(STDOUT_FILENO)); + setup_columns(tb); + + add_line(tb, NULL, "Alone", "bla bla bla"); + + p1 = add_line(tb, NULL, "A", "bla bla bla"); + add_line(tb, p1, "A:B", "bla bla bla"); + add_line(tb, p1, "A:C", "bla bla bla"); + + g1 = add_line(tb, NULL, "B", "bla bla bla"); + add_line(tb, NULL, "C", "bla bla bla"); + p1 = add_line(tb, NULL, "D", "bla bla bla"); + + p2 = add_line(tb, p1, "D:A", "bla bla bla"); + + ln = add_line(tb, p2, "D:A:A", "bla bla bla"); + scols_table_group_lines(tb, g1, ln, 0); + + add_line(tb, p1, "D:B", "bla bla bla"); + + ln = add_line(tb, NULL, "E", "bla bla bla"); + scols_table_group_lines(tb, g1, ln, 0); + + p1 = ln; + add_line(tb, p1, "E:A", "bla bla bla"); + add_line(tb, p1, "E:B", "bla bla bla"); + add_line(tb, p1, "E:C", "bla bla bla"); + + add_line(tb, NULL, "F", "bla bla bla"); + + ln = add_line(tb, NULL, "G1:A", "alb alb alb"); + scols_line_link_group(ln, g1, 0); + + p1 = ln; + add_line(tb, p1, "G1:A:A", "bla bla bla"); + add_line(tb, p1, "G1:A:B", "bla bla bla"); + add_line(tb, p1, "G1:A:C", "bla bla bla"); + + add_line(tb, NULL, "G", "bla bla bla"); + + ln = add_line(tb, NULL, "G1:B", "alb alb alb"); + scols_line_link_group(ln, g1, 0); + + add_line(tb, NULL, "foo", "bla bla bla"); + add_line(tb, NULL, "bar", "bla bla bla"); + + scols_print_table(tb); + + scols_unref_table(tb); + return EXIT_SUCCESS; +} diff --git a/libsmartcols/src/Makemodule.am b/libsmartcols/src/Makemodule.am index 665b2aa7f..c458715f5 100644 --- a/libsmartcols/src/Makemodule.am +++ b/libsmartcols/src/Makemodule.am @@ -15,8 +15,13 @@ libsmartcols_la_SOURCES= \ libsmartcols/src/column.c \ libsmartcols/src/line.c \ libsmartcols/src/table.c \ - libsmartcols/src/table_print.c \ + libsmartcols/src/print.c \ + libsmartcols/src/fput.c \ + libsmartcols/src/print-api.c \ libsmartcols/src/version.c \ + libsmartcols/src/buffer.c \ + libsmartcols/src/calculate.c \ + libsmartcols/src/grouping.c \ libsmartcols/src/init.c libsmartcols_la_LIBADD = $(LDADD) libcommon.la diff --git a/libsmartcols/src/buffer.c b/libsmartcols/src/buffer.c new file mode 100644 index 000000000..20953a5dc --- /dev/null +++ b/libsmartcols/src/buffer.c @@ -0,0 +1,152 @@ + +#include "smartcolsP.h" +#include "mbsalign.h" + +/* This is private struct to work with output data */ +struct libscols_buffer { + char *begin; /* begin of the buffer */ + char *cur; /* current end of the buffer */ + char *encdata; /* encoded buffer mbs_safe_encode() */ + + size_t bufsz; /* size of the buffer */ + size_t art_idx; /* begin of the tree ascii art or zero */ +}; + +struct libscols_buffer *new_buffer(size_t sz) +{ + struct libscols_buffer *buf = malloc(sz + sizeof(struct libscols_buffer)); + + if (!buf) + return NULL; + + buf->cur = buf->begin = ((char *) buf) + sizeof(struct libscols_buffer); + buf->encdata = NULL; + buf->bufsz = sz; + + DBG(BUFF, ul_debugobj(buf, "alloc (size=%zu)", sz)); + return buf; +} + +void free_buffer(struct libscols_buffer *buf) +{ + if (!buf) + return; + DBG(BUFF, ul_debugobj(buf, "dealloc")); + free(buf->encdata); + free(buf); +} + +int buffer_reset_data(struct libscols_buffer *buf) +{ + if (!buf) + return -EINVAL; + + /*DBG(BUFF, ul_debugobj(buf, "reset data"));*/ + buf->begin[0] = '\0'; + buf->cur = buf->begin; + buf->art_idx = 0; + return 0; +} + +int buffer_append_data(struct libscols_buffer *buf, const char *str) +{ + size_t maxsz, sz; + + if (!buf) + return -EINVAL; + if (!str || !*str) + return 0; + + sz = strlen(str); + maxsz = buf->bufsz - (buf->cur - buf->begin); + + if (maxsz <= sz) + return -EINVAL; + memcpy(buf->cur, str, sz + 1); + buf->cur += sz; + return 0; +} + +int buffer_append_ntimes(struct libscols_buffer *buf, size_t n, const char *str) +{ + size_t i; + + for (i = 0; i < n; i++) { + int rc = buffer_append_data(buf, str); + if (rc) + return rc; + } + return 0; +} + +int buffer_set_data(struct libscols_buffer *buf, const char *str) +{ + int rc = buffer_reset_data(buf); + return rc ? rc : buffer_append_data(buf, str); +} + +/* save the current buffer position to art_idx */ +void buffer_set_art_index(struct libscols_buffer *buf) +{ + if (buf) { + buf->art_idx = buf->cur - buf->begin; + /*DBG(BUFF, ul_debugobj(buf, "art index: %zu", buf->art_idx));*/ + } +} + +char *buffer_get_data(struct libscols_buffer *buf) +{ + return buf ? buf->begin : NULL; +} + +size_t buffer_get_size(struct libscols_buffer *buf) +{ + return buf ? buf->bufsz : 0; +} + +/* encode data by mbs_safe_encode() to avoid control and non-printable chars */ +char *buffer_get_safe_data(struct libscols_table *tb, + struct libscols_buffer *buf, + size_t *cells, + const char *safechars) +{ + char *data = buffer_get_data(buf); + char *res = NULL; + + if (!data) + goto nothing; + + if (!buf->encdata) { + buf->encdata = malloc(mbs_safe_encode_size(buf->bufsz) + 1); + if (!buf->encdata) + goto nothing; + } + + if (tb->no_encode) { + *cells = mbs_safe_width(data); + strcpy(buf->encdata, data); + res = buf->encdata; + } else { + res = mbs_safe_encode_to_buffer(data, cells, buf->encdata, safechars); + } + + if (!res || !*cells || *cells == (size_t) -1) + goto nothing; + return res; +nothing: + *cells = 0; + return NULL; +} + +/* returns size in bytes of the ascii art (according to art_idx) in safe encoding */ +size_t buffer_get_safe_art_size(struct libscols_buffer *buf) +{ + char *data = buffer_get_data(buf); + size_t bytes = 0; + + if (!data || !buf->art_idx) + return 0; + + mbs_safe_nwidth(data, buf->art_idx, &bytes); + return bytes; +} diff --git a/libsmartcols/src/calculate.c b/libsmartcols/src/calculate.c new file mode 100644 index 000000000..a1224adee --- /dev/null +++ b/libsmartcols/src/calculate.c @@ -0,0 +1,405 @@ +#include "smartcolsP.h" +#include "mbsalign.h" + +static void dbg_column(struct libscols_table *tb, struct libscols_column *cl) +{ + if (scols_column_is_hidden(cl)) { + DBG(COL, ul_debugobj(cl, "%s (hidden) ignored", cl->header.data)); + return; + } + + DBG(COL, ul_debugobj(cl, "%15s seq=%zu, width=%zd, " + "hint=%d, avg=%zu, max=%zu, min=%zu, " + "extreme=%s %s", + + cl->header.data, cl->seqnum, cl->width, + cl->width_hint > 1 ? (int) cl->width_hint : + (int) (cl->width_hint * tb->termwidth), + cl->width_avg, + cl->width_max, + cl->width_min, + cl->is_extreme ? "yes" : "not", + cl->flags & SCOLS_FL_TRUNC ? "trunc" : "")); +} + +static void dbg_columns(struct libscols_table *tb) +{ + struct libscols_iter itr; + struct libscols_column *cl; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) + dbg_column(tb, cl); +} + +/* + * This function counts column width. + * + * For the SCOLS_FL_NOEXTREMES columns it is possible to call this function + * two times. The first pass counts the width and average width. If the column + * contains fields that are too large (a width greater than 2 * average) then + * the column is marked as "extreme". In the second pass all extreme fields + * are ignored and the column width is counted from non-extreme fields only. + */ +static int count_column_width(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_buffer *buf) +{ + struct libscols_line *ln; + struct libscols_iter itr; + int extreme_count = 0, rc = 0, no_header = 0; + size_t extreme_sum = 0; + + assert(tb); + assert(cl); + + cl->width = 0; + if (!cl->width_min) { + if (cl->width_hint < 1 && scols_table_is_maxout(tb) && tb->is_term) { + cl->width_min = (size_t) (cl->width_hint * tb->termwidth); + if (cl->width_min && !is_last_column(cl)) + cl->width_min--; + } + if (scols_cell_get_data(&cl->header)) { + size_t len = mbs_safe_width(scols_cell_get_data(&cl->header)); + cl->width_min = max(cl->width_min, len); + } else + no_header = 1; + + if (!cl->width_min) + cl->width_min = 1; + } + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + size_t len; + char *data; + + rc = __cell_to_buffer(tb, ln, cl, buf); + if (rc) + goto done; + + data = buffer_get_data(buf); + + if (!data) + len = 0; + else if (scols_column_is_customwrap(cl)) + len = cl->wrap_chunksize(cl, data, cl->wrapfunc_data); + else + len = mbs_safe_width(data); + + if (len == (size_t) -1) /* ignore broken multibyte strings */ + len = 0; + cl->width_max = max(len, cl->width_max); + + if (cl->is_extreme && cl->width_avg && len > cl->width_avg * 2) + continue; + else if (scols_column_is_noextremes(cl)) { + extreme_sum += len; + extreme_count++; + } + cl->width = max(len, cl->width); + if (scols_column_is_tree(cl)) { + size_t treewidth = buffer_get_safe_art_size(buf); + cl->width_treeart = max(cl->width_treeart, treewidth); + } + } + + if (extreme_count && cl->width_avg == 0) { + cl->width_avg = extreme_sum / extreme_count; + if (cl->width_avg && cl->width_max > cl->width_avg * 2) + cl->is_extreme = 1; + } + + /* enlarge to minimal width */ + if (cl->width < cl->width_min && !scols_column_is_strict_width(cl)) + cl->width = cl->width_min; + + /* use absolute size for large columns */ + else if (cl->width_hint >= 1 && cl->width < (size_t) cl->width_hint + && cl->width_min < (size_t) cl->width_hint) + + cl->width = (size_t) cl->width_hint; + + + /* Column without header and data, set minimal size to zero (default is 1) */ + if (cl->width_max == 0 && no_header && cl->width_min == 1 && cl->width <= 1) + cl->width = cl->width_min = 0; + +done: + ON_DBG(COL, dbg_column(tb, cl)); + return rc; +} + +/* + * This is core of the scols_* voodoo... + */ +int __scols_calculate(struct libscols_table *tb, struct libscols_buffer *buf) +{ + struct libscols_column *cl; + struct libscols_iter itr; + size_t width = 0, width_min = 0; /* output width */ + int stage, rc = 0; + int extremes = 0, group_ncolumns = 0; + size_t colsepsz; + + + DBG(TAB, ul_debugobj(tb, "-----calculate-(termwidth=%zu)-----", tb->termwidth)); + + colsepsz = mbs_safe_width(colsep(tb)); + + if (has_groups(tb)) { + rc = scols_groups_calculate_grpset(tb); + if (rc) + goto done; + group_ncolumns = 1; + } + + /* set basic columns width + */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + int is_last; + + if (scols_column_is_hidden(cl)) + continue; + + /* we print groups chart only for the for the first tree column */ + if (scols_column_is_tree(cl) && group_ncolumns == 1) { + cl->is_groups = 1; + group_ncolumns++; + } + + rc = count_column_width(tb, cl, buf); + if (rc) + goto done; + + is_last = is_last_column(cl); + + width += cl->width + (is_last ? 0 : colsepsz); /* separator for non-last column */ + width_min += cl->width_min + (is_last ? 0 : colsepsz); + if (cl->is_extreme) + extremes++; + } + + if (!tb->is_term) { + DBG(TAB, ul_debugobj(tb, " non-terminal output")); + goto done; + } + + /* be paranoid */ + if (width_min > tb->termwidth && scols_table_is_maxout(tb)) { + DBG(TAB, ul_debugobj(tb, " min width larger than terminal! [width=%zu, term=%zu]", width_min, tb->termwidth)); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (width_min > tb->termwidth + && scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + width_min--; + cl->width_min--; + } + DBG(TAB, ul_debugobj(tb, " min width reduced to %zu", width_min)); + } + + /* reduce columns with extreme fields */ + if (width > tb->termwidth && extremes) { + DBG(TAB, ul_debugobj(tb, " reduce width (extreme columns)")); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + size_t org_width; + + if (!cl->is_extreme || scols_column_is_hidden(cl)) + continue; + + org_width = cl->width; + rc = count_column_width(tb, cl, buf); + if (rc) + goto done; + + if (org_width > cl->width) + width -= org_width - cl->width; + else + extremes--; /* hmm... nothing reduced */ + } + } + + if (width < tb->termwidth) { + if (extremes) { + DBG(TAB, ul_debugobj(tb, " enlarge width (extreme columns)")); + + /* enlarge the first extreme column */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + size_t add; + + if (!cl->is_extreme || scols_column_is_hidden(cl)) + continue; + + /* this column is too large, ignore? + if (cl->width_max - cl->width > + (tb->termwidth - width)) + continue; + */ + + add = tb->termwidth - width; + if (add && cl->width + add > cl->width_max) + add = cl->width_max - cl->width; + + cl->width += add; + width += add; + + if (width == tb->termwidth) + break; + } + } + + if (width < tb->termwidth && scols_table_is_maxout(tb)) { + DBG(TAB, ul_debugobj(tb, " enlarge width (max-out)")); + + /* try enlarging all columns */ + while (width < tb->termwidth) { + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + cl->width++; + width++; + if (width == tb->termwidth) + break; + } + } + } else if (width < tb->termwidth) { + /* enlarge the last column */ + struct libscols_column *col = list_entry( + tb->tb_columns.prev, struct libscols_column, cl_columns); + + DBG(TAB, ul_debugobj(tb, " enlarge width (last column)")); + + if (!scols_column_is_right(col) && tb->termwidth - width > 0) { + col->width += tb->termwidth - width; + width = tb->termwidth; + } + } + } + + /* bad, we have to reduce output width, this is done in three stages: + * + * 1) trunc relative with trunc flag if the column width is greater than + * expected column width (it means "width_hint * terminal_width"). + * + * 2) trunc all with trunc flag + * + * 3) trunc relative without trunc flag + * + * Note that SCOLS_FL_WRAP (if no custom wrap function is specified) is + * interpreted as SCOLS_FL_TRUNC. + */ + for (stage = 1; width > tb->termwidth && stage <= 3; ) { + size_t org_width = width; + + DBG(TAB, ul_debugobj(tb, " reduce width - #%d stage (current=%zu, wanted=%zu)", + stage, width, tb->termwidth)); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + + int trunc_flag = 0; + + DBG(TAB, ul_debugobj(cl, " checking %s (width=%zu, treeart=%zu)", + cl->header.data, cl->width, cl->width_treeart)); + if (scols_column_is_hidden(cl)) + continue; + if (width <= tb->termwidth) + break; + + /* never truncate if already minimal width */ + if (cl->width == cl->width_min) + continue; + + /* never truncate the tree */ + if (scols_column_is_tree(cl) && width <= cl->width_treeart) + continue; + + /* nothing to truncate */ + if (cl->width == 0 || width == 0) + continue; + + trunc_flag = scols_column_is_trunc(cl) + || (scols_column_is_wrap(cl) && !scols_column_is_customwrap(cl)); + + switch (stage) { + /* #1 stage - trunc relative with TRUNC flag */ + case 1: + if (!trunc_flag) /* ignore: missing flag */ + break; + if (cl->width_hint <= 0 || cl->width_hint >= 1) /* ignore: no relative */ + break; + if (cl->width < (size_t) (cl->width_hint * tb->termwidth)) /* ignore: smaller than expected width */ + break; + + DBG(TAB, ul_debugobj(tb, " reducing (relative with flag)")); + cl->width--; + width--; + break; + + /* #2 stage - trunc all with TRUNC flag */ + case 2: + if (!trunc_flag) /* ignore: missing flag */ + break; + + DBG(TAB, ul_debugobj(tb, " reducing (all with flag)")); + cl->width--; + width--; + break; + + /* #3 stage - trunc relative without flag */ + case 3: + if (cl->width_hint <= 0 || cl->width_hint >= 1) /* ignore: no relative */ + break; + + DBG(TAB, ul_debugobj(tb, " reducing (relative without flag)")); + cl->width--; + width--; + break; + } + + /* hide zero width columns */ + if (cl->width == 0) + cl->flags |= SCOLS_FL_HIDDEN; + } + + /* the current stage is without effect, go to the next */ + if (org_width == width) + stage++; + } + + /* ignore last column(s) or force last column to be truncated if + * nowrap mode enabled */ + if (tb->no_wrap && width > tb->termwidth) { + scols_reset_iter(&itr, SCOLS_ITER_BACKWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + + if (scols_column_is_hidden(cl)) + continue; + if (width <= tb->termwidth) + break; + if (width - cl->width < tb->termwidth) { + size_t r = width - tb->termwidth; + + cl->flags |= SCOLS_FL_TRUNC; + cl->width -= r; + width -= r; + } else { + cl->flags |= SCOLS_FL_HIDDEN; + width -= cl->width + colsepsz; + } + } + } +done: + DBG(TAB, ul_debugobj(tb, " final width: %zu (rc=%d)", width, rc)); + ON_DBG(TAB, dbg_columns(tb)); + + return rc; +} diff --git a/libsmartcols/src/column.c b/libsmartcols/src/column.c index 53521f6ad..4b42938f6 100644 --- a/libsmartcols/src/column.c +++ b/libsmartcols/src/column.c @@ -110,6 +110,7 @@ struct libscols_column *scols_copy_column(const struct libscols_column *cl) ret->width_hint = cl->width_hint; ret->flags = cl->flags; ret->is_extreme = cl->is_extreme; + ret->is_groups = cl->is_groups; return ret; err: diff --git a/libsmartcols/src/fput.c b/libsmartcols/src/fput.c new file mode 100644 index 000000000..b00c3d8b8 --- /dev/null +++ b/libsmartcols/src/fput.c @@ -0,0 +1,97 @@ +#include "carefulputc.h" +#include "smartcolsP.h" + +void fput_indent(struct libscols_table *tb) +{ + int i; + + for (i = 0; i <= tb->indent; i++) + fputs(" ", tb->out); +} + +void fput_table_open(struct libscols_table *tb) +{ + tb->indent = 0; + + if (scols_table_is_json(tb)) { + fputc('{', tb->out); + fputs(linesep(tb), tb->out); + + fput_indent(tb); + fputs_quoted(tb->name, tb->out); + fputs(": [", tb->out); + fputs(linesep(tb), tb->out); + + tb->indent++; + tb->indent_last_sep = 1; + } +} + +void fput_table_close(struct libscols_table *tb) +{ + tb->indent--; + + if (scols_table_is_json(tb)) { + fput_indent(tb); + fputc(']', tb->out); + tb->indent--; + fputs(linesep(tb), tb->out); + fputc('}', tb->out); + tb->indent_last_sep = 1; + } +} + +void fput_children_open(struct libscols_table *tb) +{ + if (scols_table_is_json(tb)) { + fputc(',', tb->out); + fputs(linesep(tb), tb->out); + fput_indent(tb); + fputs("\"children\": [", tb->out); + } + /* between parent and child is separator */ + fputs(linesep(tb), tb->out); + tb->indent_last_sep = 1; + tb->indent++; + tb->termlines_used++; +} + +void fput_children_close(struct libscols_table *tb) +{ + tb->indent--; + + if (scols_table_is_json(tb)) { + fput_indent(tb); + fputc(']', tb->out); + fputs(linesep(tb), tb->out); + tb->indent_last_sep = 1; + } +} + +void fput_line_open(struct libscols_table *tb) +{ + if (scols_table_is_json(tb)) { + fput_indent(tb); + fputc('{', tb->out); + tb->indent_last_sep = 0; + } + tb->indent++; +} + +void fput_line_close(struct libscols_table *tb, int last, int last_in_table) +{ + tb->indent--; + if (scols_table_is_json(tb)) { + if (tb->indent_last_sep) + fput_indent(tb); + fputs(last ? "}" : "},", tb->out); + if (!tb->no_linesep) + fputs(linesep(tb), tb->out); + + } else if (tb->no_linesep == 0 && last_in_table == 0) { + fputs(linesep(tb), tb->out); + tb->termlines_used++; + } + + tb->indent_last_sep = 1; +} diff --git a/libsmartcols/src/grouping.c b/libsmartcols/src/grouping.c new file mode 100644 index 000000000..63ed6c4b2 --- /dev/null +++ b/libsmartcols/src/grouping.c @@ -0,0 +1,587 @@ +/* + * Copyright (C) 2018 Karel Zak <kzak@redhat.com> + */ +#include "smartcolsP.h" + +/** + * SECTION: grouping + * @title: Grouping + * @short_description: lines grouing + * + * Lines groups manipulation API. The grouping API allows to create M:N + * relations between lines and on tree-like output it prints extra chart to + * visualize these relations. The group has unlimited number of members and + * group childs. See libsmartcols/sample/grouping* for more details. + */ + +/* Private API */ +void scols_ref_group(struct libscols_group *gr) +{ + if (gr) + gr->refcount++; +} + +void scols_group_remove_children(struct libscols_group *gr) +{ + if (!gr) + return; + + while (!list_empty(&gr->gr_children)) { + struct libscols_line *ln = list_entry(gr->gr_children.next, + struct libscols_line, ln_children); + + DBG(GROUP, ul_debugobj(gr, "remove child")); + list_del_init(&ln->ln_children); + scols_ref_group(ln->parent_group); + ln->parent_group = NULL; + scols_unref_line(ln); + } +} + +void scols_group_remove_members(struct libscols_group *gr) +{ + if (!gr) + return; + + while (!list_empty(&gr->gr_members)) { + struct libscols_line *ln = list_entry(gr->gr_members.next, + struct libscols_line, ln_groups); + + DBG(GROUP, ul_debugobj(gr, "remove member [%p]", ln)); + list_del_init(&ln->ln_groups); + + scols_unref_group(ln->group); + ln->group->nmembers++; + ln->group = NULL; + + scols_unref_line(ln); + } +} + +/* note group has to be already without members to deallocate */ +void scols_unref_group(struct libscols_group *gr) +{ + if (gr && --gr->refcount <= 0) { + DBG(GROUP, ul_debugobj(gr, "dealloc")); + scols_group_remove_children(gr); + list_del(&gr->gr_groups); + free(gr); + return; + } +} + + +static void groups_fix_members_order(struct libscols_line *ln) +{ + struct libscols_iter itr; + struct libscols_line *child; + + if (ln->group) { + list_add_tail(&ln->ln_groups, &ln->group->gr_members); + DBG(GROUP, ul_debugobj(ln->group, "fixing member line=%p [%zu/%zu]", + ln, ln->group->nmembers, + list_count_entries(&ln->group->gr_members))); + } + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_line_next_child(ln, &itr, &child) == 0) + groups_fix_members_order(child); + + /* + * We modify gr_members list, so is_last_group_member() does not have + * to provide reliable answer, we need to verify by list_count_entries(). + */ + if (ln->group + && is_last_group_member(ln) + && ln->group->nmembers == list_count_entries(&ln->group->gr_members)) { + + DBG(GROUP, ul_debugobj(ln->group, "fixing childs")); + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_line_next_group_child(ln, &itr, &child) == 0) + groups_fix_members_order(child); + } +} + +void scols_groups_fix_members_order(struct libscols_table *tb) +{ + struct libscols_iter itr; + struct libscols_line *ln; + struct libscols_group *gr; + + /* remove all from groups lists */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_group(tb, &itr, &gr) == 0) { + while (!list_empty(&gr->gr_members)) { + struct libscols_line *ln = list_entry(gr->gr_members.next, + struct libscols_line, ln_groups); + list_del_init(&ln->ln_groups); + } + } + + /* add again to the groups list in order we walk in tree */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + if (ln->parent || ln->parent_group) + continue; + groups_fix_members_order(ln); + } + + /* If group child is memeber of another group * + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_group(tb, &itr, &gr) == 0) { + struct libscols_iter xitr; + struct libscols_line *child; + + scols_reset_iter(&xitr, SCOLS_ITER_FORWARD); + while (scols_line_next_group_child(ln, &xitr, &child) == 0) + groups_fix_members_order(child); + } + */ +} + +static inline const char *group_state_to_string(int state) +{ + static const char *grpstates[] = { + [SCOLS_GSTATE_NONE] = "none", + [SCOLS_GSTATE_FIRST_MEMBER] = "1st-member", + [SCOLS_GSTATE_MIDDLE_MEMBER] = "middle-member", + [SCOLS_GSTATE_LAST_MEMBER] = "last-member", + [SCOLS_GSTATE_MIDDLE_CHILD] = "middle-child", + [SCOLS_GSTATE_LAST_CHILD] = "last-child", + [SCOLS_GSTATE_CONT_MEMBERS] = "continue-members", + [SCOLS_GSTATE_CONT_CHILDREN] = "continue-children" + }; + + assert(state >= 0); + assert((size_t) state < ARRAY_SIZE(grpstates)); + + return grpstates[state]; +}; + +static void grpset_debug(struct libscols_table *tb, struct libscols_line *ln) +{ + size_t i; + + for (i = 0; i < tb->grpset_size; i++) { + if (tb->grpset[i]) { + struct libscols_group *gr = tb->grpset[i]; + + if (ln) + DBG(LINE, ul_debugobj(ln, "grpset[%zu]: %p %s", i, + gr, group_state_to_string(gr->state))); + else + DBG(LINE, ul_debug("grpset[%zu]: %p %s", i, + gr, group_state_to_string(gr->state))); + } else if (ln) { + DBG(LINE, ul_debugobj(ln, "grpset[%zu]: free", i)); + } else + DBG(LINE, ul_debug("grpset[%zu]: free", i)); + } +} +static int group_state_for_line(struct libscols_group *gr, struct libscols_line *ln) +{ + if (gr->state == SCOLS_GSTATE_NONE && + (ln->group != gr || !is_first_group_member(ln))) + /* + * NONE is possible to translate to FIRST_MEMBER only, and only if + * line group matches with the current group. + */ + return SCOLS_GSTATE_NONE; + + if (ln->group != gr && ln->parent_group != gr) { + /* Not our line, continue */ + if (gr->state == SCOLS_GSTATE_FIRST_MEMBER || + gr->state == SCOLS_GSTATE_MIDDLE_MEMBER || + gr->state == SCOLS_GSTATE_CONT_MEMBERS) + return SCOLS_GSTATE_CONT_MEMBERS; + + if (gr->state == SCOLS_GSTATE_LAST_MEMBER || + gr->state == SCOLS_GSTATE_MIDDLE_CHILD || + gr->state == SCOLS_GSTATE_CONT_CHILDREN) + return SCOLS_GSTATE_CONT_CHILDREN; + + } else if (ln->group == gr && is_first_group_member(ln)) { + return SCOLS_GSTATE_FIRST_MEMBER; + + } else if (ln->group == gr && is_last_group_member(ln)) { + return SCOLS_GSTATE_LAST_MEMBER; + + } else if (ln->group == gr && is_group_member(ln)) { + return SCOLS_GSTATE_MIDDLE_MEMBER; + + } else if (ln->parent_group == gr && is_last_group_child(ln)) { + return SCOLS_GSTATE_LAST_CHILD; + + } else if (ln->parent_group == gr && is_group_child(ln)) { + return SCOLS_GSTATE_MIDDLE_CHILD; + } + + return SCOLS_GSTATE_NONE; +} + +/* For now we assume that each active group is just 3 columns width. Later we can make it dynamic... + */ +static void grpset_apply_group_state(struct libscols_group **xx, int state, struct libscols_group *gr) +{ + DBG(GROUP, ul_debugobj(gr, " appling state to grpset")); + + /* gr->state holds the old state, @state is the new state + */ + if (state == SCOLS_GSTATE_NONE) + xx[0] = xx[1] = xx[2] = NULL; + else + xx[0] = xx[1] = xx[2] = gr; + gr->state = state; +} + +static struct libscols_group **grpset_locate_freespace(struct libscols_table *tb, size_t wanted, int prepend) +{ + size_t i, avail = 0; + struct libscols_group **tmp, **first = NULL; + + if (!tb->grpset_size) + prepend = 0; + + if (prepend) { + for (i = tb->grpset_size - 1; ; i--) { + if (tb->grpset[i] == NULL) { + first = &tb->grpset[i]; + avail++; + } else + avail = 0; + if (avail == wanted) + goto done; + if (i == 0) + break; + } + } else { + for (i = 0; i < tb->grpset_size; i++) { + if (tb->grpset[i] == NULL) { + if (avail == 0) + first = &tb->grpset[i]; + avail++; + } else + avail = 0; + if (avail == wanted) + goto done; + } + } + + DBG(TAB, ul_debugobj(tb, " realocate grpset [sz: old=%zu, new=%zu]", + tb->grpset_size, tb->grpset_size + wanted)); + + tmp = realloc(tb->grpset, (tb->grpset_size + wanted) * sizeof(struct libscols_group *)); + if (!tmp) + return NULL; + + tb->grpset = tmp; + + if (prepend) { + DBG(TAB, ul_debugobj(tb, " prepending free space")); + char *dest = (char *) tb->grpset; + + memmove( dest + (wanted * sizeof(struct libscols_group *)), + tb->grpset, + tb->grpset_size * sizeof(struct libscols_group *)); + first = tb->grpset; + } else { + first = tb->grpset + tb->grpset_size; + } + + memset(first, 0, wanted * sizeof(struct libscols_group *)); + tb->grpset_size += wanted; + + grpset_debug(tb, NULL); +done: + return first; +} + +static struct libscols_group **grpset_locate_group(struct libscols_table *tb, struct libscols_group *gr) +{ + size_t i; + + for (i = 0; i < tb->grpset_size; i++) { + if (gr == tb->grpset[i]) + return &tb->grpset[i]; + } + + return NULL; +} + + + +#define SCOLS_GRPSET_MINSZ 3 + +static int grpset_update(struct libscols_table *tb, struct libscols_line *ln, struct libscols_group *gr) +{ + struct libscols_group **xx; + int state; + + DBG(LINE, ul_debugobj(ln, " group [%p] grpset update", gr)); + + /* new state, note that gr->state still holds the original state */ + state = group_state_for_line(gr, ln); + DBG(LINE, ul_debugobj(ln, " state old='%s', new='%s'", + group_state_to_string(gr->state), + group_state_to_string(state))); + + if (state == SCOLS_GSTATE_FIRST_MEMBER && gr->state != SCOLS_GSTATE_NONE) { + DBG(LINE, ul_debugobj(ln, "wrong group initialization")); + abort(); + } + if (state != SCOLS_GSTATE_NONE && gr->state == SCOLS_GSTATE_LAST_CHILD) { + DBG(LINE, ul_debugobj(ln, "wrong group termination")); + abort(); + } + if (gr->state == SCOLS_GSTATE_LAST_MEMBER && + !(state == SCOLS_GSTATE_LAST_CHILD || + state == SCOLS_GSTATE_CONT_CHILDREN || + state == SCOLS_GSTATE_MIDDLE_CHILD || + state == SCOLS_GSTATE_NONE)) { + DBG(LINE, ul_debugobj(ln, "wrong group member->child order")); + abort(); + } + + /* should not happen; probably wrong line... */ + if (gr->state == SCOLS_GSTATE_NONE && state == SCOLS_GSTATE_NONE) + return 0; + + /* locate place in grpset where we draw the group */ + if (!tb->grpset || gr->state == SCOLS_GSTATE_NONE) + xx = grpset_locate_freespace(tb, SCOLS_GRPSET_MINSZ, 1); + else + xx = grpset_locate_group(tb, gr); + if (!xx) { + DBG(LINE, ul_debugobj(ln, "failed to locate group or reallocate grpset")); + return -ENOMEM; + } + + grpset_apply_group_state(xx, state, gr); + ON_DBG(LINE, grpset_debug(tb, ln)); + return 0; +} + +static int grpset_update_active_groups(struct libscols_table *tb, struct libscols_line *ln) +{ + int rc = 0; + size_t i; + struct libscols_group *last = NULL; + + DBG(LINE, ul_debugobj(ln, " update for active groups")); + + for (i = 0; i < tb->grpset_size; i++) { + struct libscols_group *gr = tb->grpset[i]; + + if (!gr || last == gr) + continue; + last = gr; + rc = grpset_update(tb, ln, gr); + if (rc) + break; + } + + DBG(LINE, ul_debugobj(ln, " <- active groups updated [rc=%d]", rc)); + return rc; +} + +int scols_groups_update_grpset(struct libscols_table *tb, struct libscols_line *ln) +{ + int rc = 0; + + DBG(LINE, ul_debugobj(ln, " grpset update [line: group=%p, parent_group=%p", + ln->group, ln->parent_group)); + + rc = grpset_update_active_groups(tb, ln); + if (!rc && ln->group && ln->group->state == SCOLS_GSTATE_NONE) { + DBG(LINE, ul_debugobj(ln, " introduce a new group")); + rc = grpset_update(tb, ln, ln->group); + } + return rc; +} + +static int groups_calculate_grpset(struct libscols_table *tb, struct libscols_line *ln) +{ + struct libscols_iter itr; + struct libscols_line *child; + int rc = 0; + + DBG(LINE, ul_debugobj(ln, " grpset calculate")); + + /* current line */ + rc = scols_groups_update_grpset(tb, ln); + if (rc) + goto done; + + /* line's children */ + if (has_children(ln)) { + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_line_next_child(ln, &itr, &child) == 0) { + rc = groups_calculate_grpset(tb, child); + if (rc) + goto done; + } + } + + /* group's children */ + if (is_last_group_member(ln) && has_group_children(ln)) { + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_line_next_group_child(ln, &itr, &child) == 0) { + rc = groups_calculate_grpset(tb, child); + if (rc) + goto done; + } + } +done: + return rc; +} + + +int scols_groups_calculate_grpset(struct libscols_table *tb) +{ + struct libscols_iter itr; + struct libscols_line *ln; + int rc = 0; + + DBG(TAB, ul_debugobj(tb, "grpset calculate [top-level]")); + + scols_groups_reset_state(tb); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + if (ln->parent || ln->parent_group) + continue; + rc = groups_calculate_grpset(tb, ln); + if (rc) + break; + } + + scols_groups_reset_state(tb); + return rc; +} + +void scols_groups_reset_state(struct libscols_table *tb) +{ + struct libscols_iter itr; + struct libscols_group *gr; + + DBG(TAB, ul_debugobj(tb, "reset groups states")); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_group(tb, &itr, &gr) == 0) + gr->state = SCOLS_GSTATE_NONE; + + if (tb->grpset) + memset(tb->grpset, 0, tb->grpset_size); +} + +static void add_member(struct libscols_group *gr, struct libscols_line *ln) +{ + DBG(GROUP, ul_debugobj(gr, "add member")); + ln->group = gr; + gr->nmembers++; + scols_ref_group(gr); + + list_add_tail(&ln->ln_groups, &gr->gr_members); + scols_ref_line(ln); +} + +/** + * scols_table_group_lines: + * @tb: a pointer to a struct libscols_table instance + * @ln: new group member + * @member: group member + * @id: group identifier (unused, not implemented yet), use zero. + * + * This function add line @ln to group of lines represented by @member. If the + * group is not yet defined (@member is not member of any group) than a new one + * is allocated. + * + * The @ln maybe a NULL -- in this case only a new group is allocated if not + * defined yet. + * + * Note that the same line cannot be member of more groups (not implemented + * yet). The child of any group can be member of another group. + * + * The @id is not used for now, use 0. The plan is to use it to support + * multi-group membership in future. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_table_group_lines( struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_line *member, + __attribute__((__unused__)) int id) +{ + struct libscols_group *gr = NULL; + + if (!tb || (!ln && !member)) + return -EINVAL; + if (ln && member) { + if (ln->group && !member->group) + return -EINVAL; + if (ln->group && member->group && ln->group != member->group) + return -EINVAL; + } + + gr = member->group; + + /* create a new group */ + if (!gr) { + gr = calloc(1, sizeof(*gr)); + if (!gr) + return -ENOMEM; + DBG(GROUP, ul_debugobj(gr, "alloc")); + gr->refcount = 1; + INIT_LIST_HEAD(&gr->gr_members); + INIT_LIST_HEAD(&gr->gr_children); + INIT_LIST_HEAD(&gr->gr_groups); + + /* add group to the table */ + list_add_tail(&gr->gr_groups, &tb->tb_groups); + + /* add the first member */ + add_member(gr, member); + } + + /* add to group */ + if (ln && !ln->group) + add_member(gr, ln); + + return 0; +} + +/** + * scols_line_link_groups: + * @ln: line instance + * @member: group member + * @id: group identifier (unused, not implemented yet)) + * + * Define @ln as child of group represented by group @member. The line @ln + * cannot be child of any other line. It's possible to create group->child or + * parent->child relationship, but no both for the same line (child). + * + * The @id is not used for now, use 0. The plan is to use it to support + * multi-group membership in future. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_line_link_group(struct libscols_line *ln, struct libscols_line *member, + __attribute__((__unused__)) int id) +{ + if (!ln || !member || !member->group || ln->parent) + return -EINVAL; + + DBG(GROUP, ul_debugobj(member->group, "add child")); + + list_add_tail(&ln->ln_children, &member->group->gr_children); + scols_ref_line(ln); + + ln->parent_group = member->group; + scols_ref_group(member->group); + + return 0; +} diff --git a/libsmartcols/src/init.c b/libsmartcols/src/init.c index 104e43b64..dfd7510dc 100644 --- a/libsmartcols/src/init.c +++ b/libsmartcols/src/init.c @@ -25,6 +25,7 @@ UL_DEBUG_DEFINE_MASKNAMES(libsmartcols) = { "cell", SCOLS_DEBUG_CELL, "table cell utils" }, { "col", SCOLS_DEBUG_COL, "cols utils" }, { "help", SCOLS_DEBUG_HELP, "this help" }, + { "group", SCOLS_DEBUG_GROUP, "lines grouping utils" }, { "line", SCOLS_DEBUG_LINE, "table line utils" }, { "tab", SCOLS_DEBUG_TAB, "table utils" }, { NULL, 0, NULL } diff --git a/libsmartcols/src/libsmartcols.h.in b/libsmartcols/src/libsmartcols.h.in index f8be0bc04..bd47f7898 100644 --- a/libsmartcols/src/libsmartcols.h.in +++ b/libsmartcols/src/libsmartcols.h.in @@ -129,6 +129,14 @@ extern int scols_symbols_set_right(struct libscols_symbols *sy, const char *str) extern int scols_symbols_set_title_padding(struct libscols_symbols *sy, const char *str); extern int scols_symbols_set_cell_padding(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_group_vertical(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_group_horizontal(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_group_first_member(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_group_last_member(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_group_middle_member(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_group_last_child(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_group_middle_child(struct libscols_symbols *sy, const char *str); + /* cell.c */ extern int scols_reset_cell(struct libscols_cell *ce); extern int scols_cell_copy_content(struct libscols_cell *dest, @@ -314,6 +322,10 @@ extern int scols_table_print_range_to_string( struct libscols_table *tb, struct libscols_line *end, char **data); +/* grouping.c */ +int scols_line_link_group(struct libscols_line *ln, struct libscols_line *member, int id); +int scols_table_group_lines(struct libscols_table *tb, struct libscols_line *ln, + struct libscols_line *member, int id); #ifdef __cplusplus } #endif diff --git a/libsmartcols/src/libsmartcols.sym b/libsmartcols/src/libsmartcols.sym index 331a55554..e318c9054 100644 --- a/libsmartcols/src/libsmartcols.sym +++ b/libsmartcols/src/libsmartcols.sym @@ -182,3 +182,15 @@ SMARTCOLS_2.33 { scols_column_set_json_type; scols_column_get_json_type; } SMARTCOLS_2.31; + +SMARTCOLS_2.34 { + scols_table_group_lines; + scols_line_link_group; + scols_symbols_set_group_vertical; + scols_symbols_set_group_horizontal; + scols_symbols_set_group_first_member; + scols_symbols_set_group_last_member; + scols_symbols_set_group_middle_member; + scols_symbols_set_group_last_child; + scols_symbols_set_group_middle_child; +} SMARTCOLS_2.33; diff --git a/libsmartcols/src/line.c b/libsmartcols/src/line.c index 60be2c135..351bed7d5 100644 --- a/libsmartcols/src/line.c +++ b/libsmartcols/src/line.c @@ -47,6 +47,7 @@ struct libscols_line *scols_new_line(void) INIT_LIST_HEAD(&ln->ln_lines); INIT_LIST_HEAD(&ln->ln_children); INIT_LIST_HEAD(&ln->ln_branch); + INIT_LIST_HEAD(&ln->ln_groups); return ln; } @@ -75,6 +76,8 @@ void scols_unref_line(struct libscols_line *ln) DBG(CELL, ul_debugobj(ln, "dealloc")); list_del(&ln->ln_lines); list_del(&ln->ln_children); + list_del(&ln->ln_groups); + scols_unref_group(ln->group); scols_line_free_cells(ln); free(ln->color); free(ln); @@ -309,6 +312,26 @@ int scols_line_next_child(struct libscols_line *ln, return rc; } +/* private API */ +int scols_line_next_group_child(struct libscols_line *ln, + struct libscols_iter *itr, + struct libscols_line **chld) +{ + int rc = 1; + + if (!ln || !itr || !chld || !ln->group) + return -EINVAL; + *chld = NULL; + + if (!itr->head) + SCOLS_ITER_INIT(itr, &ln->group->gr_children); + if (itr->p != itr->head) { + SCOLS_ITER_ITERATE(itr, *chld, struct libscols_line, ln_children); + rc = 0; + } + + return rc; +} /** * scols_line_is_ancestor: diff --git a/libsmartcols/src/print-api.c b/libsmartcols/src/print-api.c new file mode 100644 index 000000000..48b954baf --- /dev/null +++ b/libsmartcols/src/print-api.c @@ -0,0 +1,208 @@ +#include "smartcolsP.h" + +/** + * scola_table_print_range: + * @tb: table + * @start: first printed line or NULL to print from the begin of the table + * @end: last printed line or NULL to print all from start. + * + * If the start is the first line in the table than prints table header too. + * The header is printed only once. This does not work for trees. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_print_range( struct libscols_table *tb, + struct libscols_line *start, + struct libscols_line *end) +{ + struct libscols_buffer *buf = NULL; + struct libscols_iter itr; + int rc; + + if (scols_table_is_tree(tb)) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "printing range from API")); + + rc = __scols_initialize_printing(tb, &buf); + if (rc) + return rc; + + if (start) { + itr.direction = SCOLS_ITER_FORWARD; + itr.head = &tb->tb_lines; + itr.p = &start->ln_lines; + } else + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + + if (!start || itr.p == tb->tb_lines.next) { + rc = __scols_print_header(tb, buf); + if (rc) + goto done; + } + + rc = __scols_print_range(tb, buf, &itr, end); +done: + __scols_cleanup_printing(tb, buf); + return rc; +} + +/** + * scols_table_print_range_to_string: + * @tb: table + * @start: first printed line or NULL to print from the beginning of the table + * @end: last printed line or NULL to print all from start. + * @data: pointer to the beginning of a memory area to print to + * + * The same as scols_table_print_range(), but prints to @data instead of + * stream. + * + * Returns: 0, a negative value in case of an error. + */ +#ifdef HAVE_OPEN_MEMSTREAM +int scols_table_print_range_to_string( struct libscols_table *tb, + struct libscols_line *start, + struct libscols_line *end, + char **data) +{ + FILE *stream, *old_stream; + size_t sz; + int rc; + + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "printing range to string")); + + /* create a stream for output */ + stream = open_memstream(data, &sz); + if (!stream) + return -ENOMEM; + + old_stream = scols_table_get_stream(tb); + scols_table_set_stream(tb, stream); + rc = scols_table_print_range(tb, start, end); + fclose(stream); + scols_table_set_stream(tb, old_stream); + + return rc; +} +#else +int scols_table_print_range_to_string( + struct libscols_table *tb __attribute__((__unused__)), + struct libscols_line *start __attribute__((__unused__)), + struct libscols_line *end __attribute__((__unused__)), + char **data __attribute__((__unused__))) +{ + return -ENOSYS; +} +#endif + +static int do_print_table(struct libscols_table *tb, int *is_empty) +{ + int rc = 0; + struct libscols_buffer *buf = NULL; + + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "printing")); + if (is_empty) + *is_empty = 0; + + if (list_empty(&tb->tb_columns)) { + DBG(TAB, ul_debugobj(tb, "error -- no columns")); + return -EINVAL; + } + if (list_empty(&tb->tb_lines)) { + DBG(TAB, ul_debugobj(tb, "ignore -- no lines")); + if (is_empty) + *is_empty = 1; + return 0; + } + + tb->header_printed = 0; + rc = __scols_initialize_printing(tb, &buf); + if (rc) + return rc; + + fput_table_open(tb); + + if (tb->format == SCOLS_FMT_HUMAN) + __scols_print_title(tb); + + rc = __scols_print_header(tb, buf); + if (rc) + goto done; + + if (scols_table_is_tree(tb)) + rc = __scols_print_tree(tb, buf); + else + rc = __scols_print_table(tb, buf); + + fput_table_close(tb); +done: + __scols_cleanup_printing(tb, buf); + return rc; +} + +/** + * scols_print_table: + * @tb: table + * + * Prints the table to the output stream and terminate by \n. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_print_table(struct libscols_table *tb) +{ + int empty = 0; + int rc = do_print_table(tb, &empty); + + if (rc == 0 && !empty) + fputc('\n', tb->out); + return rc; +} + +/** + * scols_print_table_to_string: + * @tb: table + * @data: pointer to the beginning of a memory area to print to + * + * Prints the table to @data. + * + * Returns: 0, a negative value in case of an error. + */ +#ifdef HAVE_OPEN_MEMSTREAM +int scols_print_table_to_string(struct libscols_table *tb, char **data) +{ + FILE *stream, *old_stream; + size_t sz; + int rc; + + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "printing to string")); + + /* create a stream for output */ + stream = open_memstream(data, &sz); + if (!stream) + return -ENOMEM; + + old_stream = scols_table_get_stream(tb); + scols_table_set_stream(tb, stream); + rc = do_print_table(tb, NULL); + fclose(stream); + scols_table_set_stream(tb, old_stream); + + return rc; +} +#else +int scols_print_table_to_string( + struct libscols_table *tb __attribute__((__unused__)), + char **data __attribute__((__unused__))) +{ + return -ENOSYS; +} +#endif diff --git a/libsmartcols/src/print.c b/libsmartcols/src/print.c new file mode 100644 index 000000000..4a45b41fb --- /dev/null +++ b/libsmartcols/src/print.c @@ -0,0 +1,1050 @@ +/* + * table.c - functions handling the data at the table level + * + * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com> + * Copyright (C) 2016 Igor Gnatenko <i.gnatenko.brain@gmail.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: table_print + * @title: Table print + * @short_description: output functions + * + * Table output API. + */ + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <termios.h> +#include <ctype.h> + +#include "mbsalign.h" +#include "carefulputc.h" +#include "smartcolsP.h" + +/* Fallback for symbols + * + * Note that by default library define all the symbols, but in case user does + * not define all symbols or if we extended the symbols struct then we need + * fallback to be more robust and backwardly compatible. + */ +#define titlepadding_symbol(tb) ((tb)->symbols->title_padding ? (tb)->symbols->title_padding : " ") +#define branch_symbol(tb) ((tb)->symbols->tree_branch ? (tb)->symbols->tree_branch : "|-") +#define vertical_symbol(tb) ((tb)->symbols->tree_vert ? (tb)->symbols->tree_vert : "| ") +#define right_symbol(tb) ((tb)->symbols->tree_right ? (tb)->symbols->tree_right : "`-") + +#define grp_vertical_symbol(tb) ((tb)->symbols->group_vert ? (tb)->symbols->group_vert : "|") +#define grp_horizontal_symbol(tb) ((tb)->symbols->group_horz ? (tb)->symbols->group_horz : "-") +#define grp_m_first_symbol(tb) ((tb)->symbols->group_first_member ? (tb)->symbols->group_first_member : ",->") +#define grp_m_last_symbol(tb) ((tb)->symbols->group_last_member ? (tb)->symbols->group_last_member : "\\->") +#define grp_m_middle_symbol(tb) ((tb)->symbols->group_middle_member ? (tb)->symbols->group_middle_member : "|->") +#define grp_c_middle_symbol(tb) ((tb)->symbols->group_middle_child ? (tb)->symbols->group_middle_child : "|-") +#define grp_c_last_symbol(tb) ((tb)->symbols->group_last_child ? (tb)->symbols->group_last_child : "`-") + +#define cellpadding_symbol(tb) ((tb)->padding_debug ? "." : \ + ((tb)->symbols->cell_padding ? (tb)->symbols->cell_padding: " ")) + +#define want_repeat_header(tb) (!(tb)->header_repeat || (tb)->header_next <= (tb)->termlines_used) + + +/* returns pointer to the end of used data */ +static int tree_ascii_art_to_buffer(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_buffer *buf) +{ + const char *art; + int rc; + + assert(ln); + assert(buf); + + if (!ln->parent) + return 0; + + rc = tree_ascii_art_to_buffer(tb, ln->parent, buf); + if (rc) + return rc; + + if (is_last_child(ln)) + art = " "; + else + art = vertical_symbol(tb); + + return buffer_append_data(buf, art); +} + +static int grpset_is_empty( struct libscols_table *tb, + size_t idx, + size_t *rest) +{ + size_t i; + + for (i = idx; i < tb->grpset_size; i++) { + if (tb->grpset[i] == NULL) { + if (rest) + (*rest)++; + } else + return 0; + } + return 1; +} + +static int groups_ascii_art_to_buffer( struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_buffer *buf) +{ + int rc, filled = 0; + size_t i, rest = 0; + const char *filler = cellpadding_symbol(tb); + + if (!has_groups(tb) || !tb->grpset) + return 0; + + DBG(LINE, ul_debugobj(ln, "printing groups chart")); + + rc = scols_groups_update_grpset(tb, ln); + if (rc) + return rc; + + for (i = 0; i < tb->grpset_size; i+=3) { + struct libscols_group *gr = tb->grpset[i]; + + if (!gr) { + buffer_append_ntimes(buf, 3, cellpadding_symbol(tb)); + continue; + } + + switch (gr->state) { + case SCOLS_GSTATE_FIRST_MEMBER: + buffer_append_data(buf, grp_m_first_symbol(tb)); + break; + case SCOLS_GSTATE_MIDDLE_MEMBER: + buffer_append_data(buf, grp_m_middle_symbol(tb)); + break; + case SCOLS_GSTATE_LAST_MEMBER: + buffer_append_data(buf, grp_m_last_symbol(tb)); + break; + case SCOLS_GSTATE_CONT_MEMBERS: + buffer_append_data(buf, grp_vertical_symbol(tb)); + buffer_append_ntimes(buf, 2, filler); + break; + case SCOLS_GSTATE_MIDDLE_CHILD: + buffer_append_data(buf, filler); + buffer_append_data(buf, grp_c_middle_symbol(tb)); + if (grpset_is_empty(tb, i + 3, &rest)) { + buffer_append_ntimes(buf, rest+1, grp_horizontal_symbol(tb)); + filled = 1; + } + filler = grp_horizontal_symbol(tb); + break; + case SCOLS_GSTATE_LAST_CHILD: + buffer_append_data(buf, cellpadding_symbol(tb)); + buffer_append_data(buf, grp_c_last_symbol(tb)); + if (grpset_is_empty(tb, i + 3, &rest)) { + buffer_append_ntimes(buf, rest+1, grp_horizontal_symbol(tb)); + filled = 1; + } + filler = grp_horizontal_symbol(tb); + break; + case SCOLS_GSTATE_CONT_CHILDREN: + buffer_append_data(buf, filler); + buffer_append_data(buf, grp_vertical_symbol(tb)); + buffer_append_data(buf, filler); + break; + } + + if (filled) + break; + } + + if (!filled) + buffer_append_data(buf, filler); + return 0; +} + +static int has_pending_data(struct libscols_table *tb) +{ + struct libscols_column *cl; + struct libscols_iter itr; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + if (cl->pending_data) + return 1; + } + return 0; +} + +/* print padding or ASCII-art instead of data of @cl */ +static void print_empty_cell(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + size_t bufsz) +{ + size_t len_pad = 0; /* in screen cells as opposed to bytes */ + + /* generate tree ASCII-art rather than padding */ + if (ln && scols_column_is_tree(cl)) { + if (!ln->parent) { + /* only print symbols->vert if followed by child */ + if (!list_empty(&ln->ln_branch)) { + fputs(vertical_symbol(tb), tb->out); + len_pad = mbs_safe_width(vertical_symbol(tb)); + } + } else { + /* use the same draw function as though we were intending to draw an L-shape */ + struct libscols_buffer *art = new_buffer(bufsz); + char *data; + + if (art) { + /* whatever the rc, len_pad will be sensible */ + tree_ascii_art_to_buffer(tb, ln, art); + if (!list_empty(&ln->ln_branch) && has_pending_data(tb)) + buffer_append_data(art, vertical_symbol(tb)); + data = buffer_get_safe_data(tb, art, &len_pad, NULL); + if (data && len_pad) + fputs(data, tb->out); + free_buffer(art); + } + } + } + + if (is_last_column(cl)) + return; + + /* fill rest of cell with space */ + for(; len_pad < cl->width; ++len_pad) + fputs(cellpadding_symbol(tb), tb->out); + + fputs(colsep(tb), tb->out); +} + + +static const char *get_cell_color(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + struct libscols_cell *ce) /* optional */ +{ + const char *color = NULL; + + if (tb && tb->colors_wanted) { + if (ce) + color = ce->color; + if (ln && !color) + color = ln->color; + if (!color) + color = cl->color; + } + return color; +} + +/* Fill the start of a line with padding (or with tree ascii-art). + * + * This is necessary after a long non-truncated column, as this requires the + * next column to be printed on the next line. For example (see 'DDD'): + * + * aaa bbb ccc ddd eee + * AAA BBB CCCCCCC + * DDD EEE + * ^^^^^^^^^^^^ + * new line padding + */ +static void print_newline_padding(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + size_t bufsz) +{ + size_t i; + + assert(tb); + assert(cl); + + fputs(linesep(tb), tb->out); /* line break */ + tb->termlines_used++; + + /* fill cells after line break */ + for (i = 0; i <= (size_t) cl->seqnum; i++) + print_empty_cell(tb, scols_table_get_column(tb, i), ln, bufsz); +} + +/* + * Pending data + * + * The first line in the multi-line cells (columns with SCOLS_FL_WRAP flag) is + * printed as usually and output is truncated to match column width. + * + * The rest of the long text is printed on next extra line(s). The extra lines + * don't exist in the table (not represented by libscols_line). The data for + * the extra lines are stored in libscols_column->pending_data_buf and the + * function print_line() adds extra lines until the buffer is not empty in all + * columns. + */ + +/* set data that will be printed by extra lines */ +static int set_pending_data(struct libscols_column *cl, const char *data, size_t sz) +{ + char *p = NULL; + + if (data && *data) { + DBG(COL, ul_debugobj(cl, "setting pending data")); + assert(sz); + p = strdup(data); + if (!p) + return -ENOMEM; + } + + free(cl->pending_data_buf); + cl->pending_data_buf = p; + cl->pending_data_sz = sz; + cl->pending_data = cl->pending_data_buf; + return 0; +} + +/* the next extra line has been printed, move pending data cursor */ +static int step_pending_data(struct libscols_column *cl, size_t bytes) +{ + DBG(COL, ul_debugobj(cl, "step pending data %zu -= %zu", cl->pending_data_sz, bytes)); + + if (bytes >= cl->pending_data_sz) + return set_pending_data(cl, NULL, 0); + + cl->pending_data += bytes; + cl->pending_data_sz -= bytes; + return 0; +} + +/* print next pending data for the column @cl */ +static int print_pending_data( + struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + struct libscols_cell *ce) +{ + const char *color = get_cell_color(tb, cl, ln, ce); + size_t width = cl->width, bytes; + size_t len = width, i; + char *data; + char *nextchunk = NULL; + + if (!cl->pending_data) + return 0; + if (!width) + return -EINVAL; + + DBG(COL, ul_debugobj(cl, "printing pending data")); + + data = strdup(cl->pending_data); + if (!data) + goto err; + + if (scols_column_is_customwrap(cl) + && (nextchunk = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data))) { + bytes = nextchunk - data; + + len = mbs_safe_nwidth(data, bytes, NULL); + } else + bytes = mbs_truncate(data, &len); + + if (bytes == (size_t) -1) + goto err; + + if (bytes) + step_pending_data(cl, bytes); + + if (color) + fputs(color, tb->out); + fputs(data, tb->out); + if (color) + fputs(UL_COLOR_RESET, tb->out); + free(data); + + if (is_last_column(cl)) + return 0; + + for (i = len; i < width; i++) + fputs(cellpadding_symbol(tb), tb->out); /* padding */ + + fputs(colsep(tb), tb->out); /* columns separator */ + return 0; +err: + free(data); + return -errno; +} + +static int print_data(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + struct libscols_cell *ce, /* optional */ + struct libscols_buffer *buf) +{ + size_t len = 0, i, width, bytes; + const char *color = NULL; + char *data, *nextchunk; + int is_last; + + assert(tb); + assert(cl); + + data = buffer_get_data(buf); + if (!data) + data = ""; + + is_last = is_last_column(cl); + + switch (tb->format) { + case SCOLS_FMT_RAW: + fputs_nonblank(data, tb->out); + if (!is_last) + fputs(colsep(tb), tb->out); + return 0; + + case SCOLS_FMT_EXPORT: + fprintf(tb->out, "%s=", scols_cell_get_data(&cl->header)); + fputs_quoted(data, tb->out); + if (!is_last) + fputs(colsep(tb), tb->out); + return 0; + + case SCOLS_FMT_JSON: + fputs_quoted_json_lower(scols_cell_get_data(&cl->header), tb->out); + fputs(":", tb->out); + switch (cl->json_type) { + case SCOLS_JSON_STRING: + if (!*data) + fputs("null", tb->out); + else + fputs_quoted_json(data, tb->out); + break; + case SCOLS_JSON_NUMBER: + if (!*data) + fputs("null", tb->out); + else + fputs(data, tb->out); + break; + case SCOLS_JSON_BOOLEAN: + fputs(!*data ? "false" : + *data == '0' ? "false" : + *data == 'N' || *data == 'n' ? "false" : "true", + tb->out); + break; + } + if (!is_last) + fputs(", ", tb->out); + return 0; + + case SCOLS_FMT_HUMAN: + break; /* continue below */ + } + + color = get_cell_color(tb, cl, ln, ce); + + /* Encode. Note that 'len' and 'width' are number of cells, not bytes. + */ + data = buffer_get_safe_data(tb, buf, &len, scols_column_get_safechars(cl)); + if (!data) + data = ""; + bytes = strlen(data); + width = cl->width; + + /* custom multi-line cell based */ + if (*data && scols_column_is_customwrap(cl) + && (nextchunk = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data))) { + set_pending_data(cl, nextchunk, bytes - (nextchunk - data)); + bytes = nextchunk - data; + len = mbs_safe_nwidth(data, bytes, NULL); + } + + if (is_last + && len < width + && !scols_table_is_maxout(tb) + && !scols_column_is_right(cl)) + width = len; + + /* truncate data */ + if (len > width && scols_column_is_trunc(cl)) { + len = width; + bytes = mbs_truncate(data, &len); /* updates 'len' */ + } + + /* standard multi-line cell */ + if (len > width && scols_column_is_wrap(cl) + && !scols_column_is_customwrap(cl)) { + set_pending_data(cl, data, bytes); + + len = width; + bytes = mbs_truncate(data, &len); + if (bytes != (size_t) -1 && bytes > 0) + step_pending_data(cl, bytes); + } + + if (bytes == (size_t) -1) { + bytes = len = 0; + data = NULL; + } + + if (data) { + if (scols_column_is_right(cl)) { + if (color) + fputs(color, tb->out); + for (i = len; i < width; i++) + fputs(cellpadding_symbol(tb), tb->out); + fputs(data, tb->out); + if (color) + fputs(UL_COLOR_RESET, tb->out); + len = width; + + } else if (color) { + char *p = data; + size_t art = buffer_get_safe_art_size(buf); + + /* we don't want to colorize tree ascii art */ + if (scols_column_is_tree(cl) && art && art < bytes) { + fwrite(p, 1, art, tb->out); + p += art; + } + + fputs(color, tb->out); + fputs(p, tb->out); + fputs(UL_COLOR_RESET, tb->out); + } else + fputs(data, tb->out); + } + for (i = len; i < width; i++) + fputs(cellpadding_symbol(tb), tb->out); /* padding */ + + if (is_last) + return 0; + + if (len > width && !scols_column_is_trunc(cl)) + print_newline_padding(tb, cl, ln, buffer_get_size(buf)); /* next column starts on next line */ + else + fputs(colsep(tb), tb->out); /* columns separator */ + + return 0; +} + +int __cell_to_buffer(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_column *cl, + struct libscols_buffer *buf) +{ + const char *data; + struct libscols_cell *ce; + int rc = 0; + + assert(tb); + assert(ln); + assert(cl); + assert(buf); + assert(cl->seqnum <= tb->ncols); + + buffer_reset_data(buf); + + ce = scols_line_get_cell(ln, cl->seqnum); + data = ce ? scols_cell_get_data(ce) : NULL; + if (!data) + return 0; + + if (!scols_column_is_tree(cl)) + return buffer_set_data(buf, data); + + /* + * Group stuff + */ + if (!scols_table_is_json(tb) && cl->is_groups) + rc = groups_ascii_art_to_buffer(tb, ln, buf); + + /* + * Tree stuff + */ + if (!rc && ln->parent && !scols_table_is_json(tb)) { + rc = tree_ascii_art_to_buffer(tb, ln->parent, buf); + + if (!rc && is_last_child(ln)) + rc = buffer_append_data(buf, right_symbol(tb)); + else if (!rc) + rc = buffer_append_data(buf, branch_symbol(tb)); + } + + if (!rc && (ln->parent || cl->is_groups) && !scols_table_is_json(tb)) + buffer_set_art_index(buf); + + if (!rc) + rc = buffer_append_data(buf, data); + return rc; +} + +/* + * Prints data. Data can be printed in more formats (raw, NAME=xxx pairs), and + * control and non-printable characters can be encoded in the \x?? encoding. + */ +static int print_line(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_buffer *buf) +{ + int rc = 0, pending = 0; + struct libscols_column *cl; + struct libscols_iter itr; + + assert(ln); + + /* regular line */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + rc = __cell_to_buffer(tb, ln, cl, buf); + if (rc == 0) + rc = print_data(tb, cl, ln, + scols_line_get_cell(ln, cl->seqnum), + buf); + if (rc == 0 && cl->pending_data) + pending = 1; + } + + /* extra lines of the multi-line cells */ + while (rc == 0 && pending) { + pending = 0; + fputs(linesep(tb), tb->out); + tb->termlines_used++; + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + if (cl->pending_data) { + rc = print_pending_data(tb, cl, ln, scols_line_get_cell(ln, cl->seqnum)); + if (rc == 0 && cl->pending_data) + pending = 1; + } else + print_empty_cell(tb, cl, ln, buffer_get_size(buf)); + } + } + + return 0; +} + +int __scols_print_title(struct libscols_table *tb) +{ + int rc, color = 0; + mbs_align_t align; + size_t width, len = 0, bufsz, titlesz; + char *title = NULL, *buf = NULL; + + assert(tb); + + if (!tb->title.data) + return 0; + + DBG(TAB, ul_debugobj(tb, "printing title")); + + /* encode data */ + if (tb->no_encode) { + len = bufsz = strlen(tb->title.data) + 1; + buf = strdup(tb->title.data); + if (!buf) { + rc = -ENOMEM; + goto done; + } + } else { + bufsz = mbs_safe_encode_size(strlen(tb->title.data)) + 1; + if (bufsz == 1) { + DBG(TAB, ul_debugobj(tb, "title is empty string -- ignore")); + return 0; + } + buf = malloc(bufsz); + if (!buf) { + rc = -ENOMEM; + goto done; + } + + if (!mbs_safe_encode_to_buffer(tb->title.data, &len, buf, NULL) || + !len || len == (size_t) -1) { + rc = -EINVAL; + goto done; + } + } + + /* truncate and align */ + width = tb->is_term ? tb->termwidth : 80; + titlesz = width + bufsz; + + title = malloc(titlesz); + if (!title) { + rc = -EINVAL; + goto done; + } + + switch (scols_cell_get_alignment(&tb->title)) { + case SCOLS_CELL_FL_RIGHT: + align = MBS_ALIGN_RIGHT; + break; + case SCOLS_CELL_FL_CENTER: + align = MBS_ALIGN_CENTER; + break; + case SCOLS_CELL_FL_LEFT: + default: + align = MBS_ALIGN_LEFT; + /* + * Don't print extra blank chars after the title if on left + * (that's same as we use for the last column in the table). + */ + if (len < width + && !scols_table_is_maxout(tb) + && isblank(*titlepadding_symbol(tb))) + width = len; + break; + + } + + /* copy from buf to title and align to width with title_padding */ + rc = mbsalign_with_padding(buf, title, titlesz, + &width, align, + 0, (int) *titlepadding_symbol(tb)); + + if (rc == -1) { + rc = -EINVAL; + goto done; + } + + if (tb->colors_wanted && tb->title.color) + color = 1; + if (color) + fputs(tb->title.color, tb->out); + + fputs(title, tb->out); + + if (color) + fputs(UL_COLOR_RESET, tb->out); + + fputc('\n', tb->out); + rc = 0; +done: + free(buf); + free(title); + DBG(TAB, ul_debugobj(tb, "printing title done [rc=%d]", rc)); + return rc; +} + +int __scols_print_header(struct libscols_table *tb, struct libscols_buffer *buf) +{ + int rc = 0; + struct libscols_column *cl; + struct libscols_iter itr; + + assert(tb); + + if ((tb->header_printed == 1 && tb->header_repeat == 0) || + scols_table_is_noheadings(tb) || + scols_table_is_export(tb) || + scols_table_is_json(tb) || + list_empty(&tb->tb_lines)) + return 0; + + DBG(TAB, ul_debugobj(tb, "printing header")); + + /* set the width according to the size of the data */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + + buffer_reset_data(buf); + + if (cl->is_groups + && scols_table_is_tree(tb) && scols_column_is_tree(cl)) { + size_t i; + for (i = 0; i < tb->grpset_size + 1; i++) { + rc = buffer_append_data(buf, " "); + if (rc) + break; + } + } + if (!rc) + rc = buffer_append_data(buf, scols_cell_get_data(&cl->header)); + if (!rc) + rc = print_data(tb, cl, NULL, &cl->header, buf); + } + + if (rc == 0) { + fputs(linesep(tb), tb->out); + tb->termlines_used++; + } + + tb->header_printed = 1; + tb->header_next = tb->termlines_used + tb->termheight; + if (tb->header_repeat) + DBG(TAB, ul_debugobj(tb, "\tnext header: %zu [current=%zu, rc=%d]", + tb->header_next, tb->termlines_used, rc)); + return rc; +} + + +int __scols_print_range(struct libscols_table *tb, + struct libscols_buffer *buf, + struct libscols_iter *itr, + struct libscols_line *end) +{ + int rc = 0; + struct libscols_line *ln; + + assert(tb); + DBG(TAB, ul_debugobj(tb, "printing range")); + + while (rc == 0 && scols_table_next_line(tb, itr, &ln) == 0) { + + int last = scols_iter_is_last(itr); + + fput_line_open(tb); + rc = print_line(tb, ln, buf); + fput_line_close(tb, last, last); + + if (end && ln == end) + break; + + if (!last && want_repeat_header(tb)) + __scols_print_header(tb, buf); + } + + return rc; + +} + +int __scols_print_table(struct libscols_table *tb, struct libscols_buffer *buf) +{ + struct libscols_iter itr; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + return __scols_print_range(tb, buf, &itr, NULL); +} + + +static int print_tree_line(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_buffer *buf, + int last, + int last_in_table) +{ + int rc, children = 0, gr_children = 0; + + DBG(LINE, ul_debugobj(ln, "printing line")); + + /* print the line */ + fput_line_open(tb); + rc = print_line(tb, ln, buf); + if (rc) + goto done; + + children = has_children(ln); + gr_children = is_last_group_member(ln) && has_group_children(ln); + + if (children || gr_children) + fput_children_open(tb); + + /* print children */ + if (children) { + struct list_head *p; + + list_for_each(p, &ln->ln_branch) { + struct libscols_line *chld = + list_entry(p, struct libscols_line, ln_children); + int last_child = !gr_children && p->next == &ln->ln_branch; + + rc = print_tree_line(tb, chld, buf, last_child, last_in_table && last_child); + if (rc) + goto done; + } + } + + /* print group's children */ + if (gr_children) { + struct list_head *p; + + list_for_each(p, &ln->group->gr_children) { + struct libscols_line *chld = + list_entry(p, struct libscols_line, ln_children); + int last_child = p->next == &ln->group->gr_children; + + rc = print_tree_line(tb, chld, buf, last_child, last_in_table && last_child); + if (rc) + goto done; + } + } + + if (children || gr_children) + fput_children_close(tb); + + if ((!children && !gr_children) || scols_table_is_json(tb)) + fput_line_close(tb, last, last_in_table); +done: + return rc; +} + +int __scols_print_tree(struct libscols_table *tb, struct libscols_buffer *buf) +{ + int rc = 0; + struct libscols_line *ln, *last = NULL; + struct libscols_iter itr; + + assert(tb); + + DBG(TAB, ul_debugobj(tb, "----printing-tree-----")); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + + while (scols_table_next_line(tb, &itr, &ln) == 0) { + if (!last || !ln->parent) + last = ln; + } + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (rc == 0 && scols_table_next_line(tb, &itr, &ln) == 0) { + if (ln->parent || ln->parent_group) + continue; + rc = print_tree_line(tb, ln, buf, ln == last, ln == last); + } + + return rc; +} + +static size_t strlen_line(struct libscols_line *ln) +{ + size_t i, sz = 0; + + assert(ln); + + for (i = 0; i < ln->ncells; i++) { + struct libscols_cell *ce = scols_line_get_cell(ln, i); + const char *data = ce ? scols_cell_get_data(ce) : NULL; + + sz += data ? strlen(data) : 0; + } + + return sz; +} + +void __scols_cleanup_printing(struct libscols_table *tb, struct libscols_buffer *buf) +{ + if (!tb) + return; + + free_buffer(buf); + + if (tb->priv_symbols) { + scols_table_set_symbols(tb, NULL); + tb->priv_symbols = 0; + } +} + +int __scols_initialize_printing(struct libscols_table *tb, struct libscols_buffer **buf) +{ + size_t bufsz, extra_bufsz = 0; + struct libscols_line *ln; + struct libscols_iter itr; + int rc; + + DBG(TAB, ul_debugobj(tb, "initialize printing")); + *buf = NULL; + + if (!tb->symbols) { + rc = scols_table_set_default_symbols(tb); + if (rc) + goto err; + tb->priv_symbols = 1; + } else + tb->priv_symbols = 0; + + if (tb->format == SCOLS_FMT_HUMAN) + tb->is_term = tb->termforce == SCOLS_TERMFORCE_NEVER ? 0 : + tb->termforce == SCOLS_TERMFORCE_ALWAYS ? 1 : + isatty(STDOUT_FILENO); + + if (tb->is_term) { + size_t width = (size_t) scols_table_get_termwidth(tb); + + if (tb->termreduce > 0 && tb->termreduce < width) { + width -= tb->termreduce; + scols_table_set_termwidth(tb, width); + } + bufsz = width; + } else + bufsz = BUFSIZ; + + if (!tb->is_term || tb->format != SCOLS_FMT_HUMAN || scols_table_is_tree(tb)) + tb->header_repeat = 0; + + /* + * Estimate extra space necessary for tree, JSON or another output + * decoration. + */ + if (scols_table_is_tree(tb)) + extra_bufsz += tb->nlines * strlen(vertical_symbol(tb)); + + switch (tb->format) { + case SCOLS_FMT_RAW: + extra_bufsz += tb->ncols; /* separator between columns */ + break; + case SCOLS_FMT_JSON: + if (tb->format == SCOLS_FMT_JSON) + extra_bufsz += tb->nlines * 3; /* indention */ + /* fallthrough */ + case SCOLS_FMT_EXPORT: + { + struct libscols_column *cl; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + + while (scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + extra_bufsz += strlen(scols_cell_get_data(&cl->header)); /* data */ + extra_bufsz += 2; /* separators */ + } + break; + } + case SCOLS_FMT_HUMAN: + break; + } + + /* + * Enlarge buffer if necessary, the buffer should be large enough to + * store line data and tree ascii art (or another decoration). + */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + size_t sz; + + sz = strlen_line(ln) + extra_bufsz; + if (sz > bufsz) + bufsz = sz; + } + + *buf = new_buffer(bufsz + 1); /* data + space for \0 */ + if (!*buf) { + rc = -ENOMEM; + goto err; + } + + /* + * Make sure groups members are in the same orders as the tree + */ + if (has_groups(tb) && scols_table_is_tree(tb)) + scols_groups_fix_members_order(tb); + + if (tb->format == SCOLS_FMT_HUMAN) { + rc = __scols_calculate(tb, *buf); + if (rc != 0) + goto err; + } + + return 0; +err: + __scols_cleanup_printing(tb, *buf); + return rc; +} + diff --git a/libsmartcols/src/smartcolsP.h b/libsmartcols/src/smartcolsP.h index f45ac3a24..2bc62800d 100644 --- a/libsmartcols/src/smartcolsP.h +++ b/libsmartcols/src/smartcolsP.h @@ -29,6 +29,7 @@ #define SCOLS_DEBUG_TAB (1 << 4) #define SCOLS_DEBUG_COL (1 << 5) #define SCOLS_DEBUG_BUFF (1 << 6) +#define SCOLS_DEBUG_GROUP (1 << 7) #define SCOLS_DEBUG_ALL 0xFFFF UL_DEBUG_DECLARE_MASK(libsmartcols); @@ -53,9 +54,19 @@ struct libscols_iter { */ struct libscols_symbols { int refcount; - char *branch; - char *vert; - char *right; + + char *tree_branch; + char *tree_vert; + char *tree_right; + + char *group_vert; + char *group_horz; + char *group_first_member; + char *group_last_member; + char *group_middle_member; + char *group_last_child; + char *group_middle_child; + char *title_padding; char *cell_padding; }; @@ -89,7 +100,6 @@ struct libscols_column { int json_type; /* SCOLS_JSON_* */ int flags; - int is_extreme; char *color; /* default column color */ char *safechars; /* do not encode this bytes */ @@ -113,6 +123,36 @@ struct libscols_column { struct list_head cl_columns; struct libscols_table *table; + + unsigned int is_extreme : 1, /* extreme width in the column */ + is_groups : 1; /* print group chart */ + +}; + +#define colsep(tb) ((tb)->colsep ? (tb)->colsep : " ") +#define linesep(tb) ((tb)->linesep ? (tb)->linesep : "\n") + +enum { + SCOLS_GSTATE_NONE = 0, /* not activate yet */ + SCOLS_GSTATE_FIRST_MEMBER, + SCOLS_GSTATE_MIDDLE_MEMBER, + SCOLS_GSTATE_LAST_MEMBER, + SCOLS_GSTATE_MIDDLE_CHILD, + SCOLS_GSTATE_LAST_CHILD, + SCOLS_GSTATE_CONT_MEMBERS, + SCOLS_GSTATE_CONT_CHILDREN +}; + +struct libscols_group { + int refcount; + + size_t nmembers; + + struct list_head gr_members; /* head of line->ln_group */ + struct list_head gr_children; /* head of line->ln_children */ + struct list_head gr_groups; /* member of table->tb_groups */ + + int state; /* SCOLS_GSTATE_* */ }; /* @@ -128,11 +168,14 @@ struct libscols_line { struct libscols_cell *cells; /* array with data */ size_t ncells; /* number of cells */ - struct list_head ln_lines; /* table lines */ - struct list_head ln_branch; /* begin of branch (head of ln_children) */ - struct list_head ln_children; + struct list_head ln_lines; /* member of table->tb_lines */ + struct list_head ln_branch; /* head of line->ln_children */ + struct list_head ln_children; /* member of line->ln_children or group->gr_children */ + struct list_head ln_groups; /* member of group->gr_groups */ struct libscols_line *parent; + struct libscols_group *parent_group; /* for group childs */ + struct libscols_group *group; /* for group members */ }; enum { @@ -162,6 +205,11 @@ struct libscols_table { struct list_head tb_columns; struct list_head tb_lines; + + struct list_head tb_groups; /* all defined groups */ + struct libscols_group **grpset; + size_t grpset_size; + struct libscols_symbols *symbols; struct libscols_cell title; /* optional table title (for humans) */ @@ -213,4 +261,156 @@ static inline int scols_iter_is_last(const struct libscols_iter *itr) return itr->p == itr->head; } +/* + * line.c + */ +int scols_line_next_group_child(struct libscols_line *ln, + struct libscols_iter *itr, + struct libscols_line **chld); + + +/* + * table.c + */ +int scols_table_next_group(struct libscols_table *tb, + struct libscols_iter *itr, + struct libscols_group **gr); + +/* + * buffer.c + */ +struct libscols_buffer; +extern struct libscols_buffer *new_buffer(size_t sz); +extern void free_buffer(struct libscols_buffer *buf); +extern int buffer_reset_data(struct libscols_buffer *buf); +extern int buffer_append_data(struct libscols_buffer *buf, const char *str); +extern int buffer_append_ntimes(struct libscols_buffer *buf, size_t n, const char *str); +extern int buffer_set_data(struct libscols_buffer *buf, const char *str); +extern void buffer_set_art_index(struct libscols_buffer *buf); +extern char *buffer_get_data(struct libscols_buffer *buf); +extern size_t buffer_get_size(struct libscols_buffer *buf); +extern char *buffer_get_safe_data(struct libscols_table *tb, + struct libscols_buffer *buf, + size_t *cells, + const char *safechars); +extern size_t buffer_get_safe_art_size(struct libscols_buffer *buf); + +/* + * grouping.c + */ +void scols_ref_group(struct libscols_group *gr); +void scols_group_remove_children(struct libscols_group *gr); +void scols_group_remove_members(struct libscols_group *gr); +void scols_unref_group(struct libscols_group *gr); +void scols_groups_fix_members_order(struct libscols_table *tb); +int scols_groups_calculate_grpset(struct libscols_table *tb); +int scols_groups_update_grpset(struct libscols_table *tb, struct libscols_line *ln); +void scols_groups_reset_state(struct libscols_table *tb); + +/* + * calculate.c + */ +extern int __scols_calculate(struct libscols_table *tb, struct libscols_buffer *buf); + +/* + * print.c + */ +extern int __cell_to_buffer(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_column *cl, + struct libscols_buffer *buf); + +void __scols_cleanup_printing(struct libscols_table *tb, struct libscols_buffer *buf); +int __scols_initialize_printing(struct libscols_table *tb, struct libscols_buffer **buf); +int __scols_print_tree(struct libscols_table *tb, struct libscols_buffer *buf); +int __scols_print_table(struct libscols_table *tb, struct libscols_buffer *buf); +int __scols_print_header(struct libscols_table *tb, struct libscols_buffer *buf); +int __scols_print_title(struct libscols_table *tb); +int __scols_print_range(struct libscols_table *tb, + struct libscols_buffer *buf, + struct libscols_iter *itr, + struct libscols_line *end); + +/* + * fput.c + */ +extern void fput_indent(struct libscols_table *tb); +extern void fput_table_open(struct libscols_table *tb); +extern void fput_table_close(struct libscols_table *tb); +extern void fput_children_open(struct libscols_table *tb); +extern void fput_children_close(struct libscols_table *tb); +extern void fput_line_open(struct libscols_table *tb); +extern void fput_line_close(struct libscols_table *tb, int last, int last_in_table); + +static inline int is_last_child(struct libscols_line *ln) +{ + if (!ln || !ln->parent) + return 1; + + return list_entry_is_last(&ln->ln_children, &ln->parent->ln_branch); +} + + +static inline int is_last_column(struct libscols_column *cl) +{ + struct libscols_column *next; + + if (list_entry_is_last(&cl->cl_columns, &cl->table->tb_columns)) + return 1; + + next = list_entry(cl->cl_columns.next, struct libscols_column, cl_columns); + if (next && scols_column_is_hidden(next) && is_last_column(next)) + return 1; + return 0; +} + +static inline int is_last_group_member(struct libscols_line *ln) +{ + if (!ln || !ln->group) + return 0; + + return list_entry_is_last(&ln->ln_groups, &ln->group->gr_members); +} + +static inline int is_first_group_member(struct libscols_line *ln) +{ + if (!ln || !ln->group) + return 0; + + return list_entry_is_first(&ln->ln_groups, &ln->group->gr_members); +} + +static inline int is_group_member(struct libscols_line *ln) +{ + return ln && ln->group; +} + +static inline int is_last_group_child(struct libscols_line *ln) +{ + if (!ln || !ln->parent_group) + return 0; + + return list_entry_is_last(&ln->ln_children, &ln->parent_group->gr_children); +} + +static inline int is_group_child(struct libscols_line *ln) +{ + return ln && ln->parent_group; +} + +static inline int has_groups(struct libscols_table *tb) +{ + return tb && !list_empty(&tb->tb_groups); +} + +static inline int has_children(struct libscols_line *ln) +{ + return ln && !list_empty(&ln->ln_branch); +} + +static inline int has_group_children(struct libscols_line *ln) +{ + return ln && ln->group && !list_empty(&ln->group->gr_children); +} + #endif /* _LIBSMARTCOLS_PRIVATE_H */ diff --git a/libsmartcols/src/symbols.c b/libsmartcols/src/symbols.c index 6ddf1869b..a78489844 100644 --- a/libsmartcols/src/symbols.c +++ b/libsmartcols/src/symbols.c @@ -59,9 +59,16 @@ void scols_ref_symbols(struct libscols_symbols *sy) void scols_unref_symbols(struct libscols_symbols *sy) { if (sy && --sy->refcount <= 0) { - free(sy->branch); - free(sy->vert); - free(sy->right); + free(sy->tree_branch); + free(sy->tree_vert); + free(sy->tree_right); + free(sy->group_last_member); + free(sy->group_middle_member); + free(sy->group_first_member); + free(sy->group_vert); + free(sy->group_horz); + free(sy->group_last_child); + free(sy->group_middle_child); free(sy->title_padding); free(sy->cell_padding); free(sy); @@ -77,7 +84,7 @@ void scols_unref_symbols(struct libscols_symbols *sy) */ int scols_symbols_set_branch(struct libscols_symbols *sy, const char *str) { - return strdup_to_struct_member(sy, branch, str); + return strdup_to_struct_member(sy, tree_branch, str); } /** @@ -89,7 +96,7 @@ int scols_symbols_set_branch(struct libscols_symbols *sy, const char *str) */ int scols_symbols_set_vertical(struct libscols_symbols *sy, const char *str) { - return strdup_to_struct_member(sy, vert, str); + return strdup_to_struct_member(sy, tree_vert, str); } /** @@ -101,7 +108,7 @@ int scols_symbols_set_vertical(struct libscols_symbols *sy, const char *str) */ int scols_symbols_set_right(struct libscols_symbols *sy, const char *str) { - return strdup_to_struct_member(sy, right, str); + return strdup_to_struct_member(sy, tree_right, str); } /** @@ -137,6 +144,105 @@ int scols_symbols_set_cell_padding(struct libscols_symbols *sy, const char *str) return strdup_to_struct_member(sy, cell_padding, str); } + +/** + * scols_symbols_set_group_vertical: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the vertival line + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_vertical(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_vert, str); +} + +/** + * scols_symbols_set_group_horizontal: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the horizontal line + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_horizontal(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_horz, str); +} + +/** + * scols_symbols_set_group_first_member: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent first member + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_first_member(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_first_member, str); +} + +/** + * scols_symbols_set_group_last_member: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent last member + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_last_member(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_last_member, str); +} + +/** + * scols_symbols_set_group_middle: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent middle member + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_middle_member(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_middle_member, str); +} + +/** + * scols_symbols_set_group_last_child: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent last child + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_last_child(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_last_child, str); +} + +/** + * scols_symbols_set_group_middle_child: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent last child + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_middle_child(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_middle_child, str); +} + /** * scols_copy_symbols: * @sy: a pointer to a struct libscols_symbols instance @@ -156,11 +262,25 @@ struct libscols_symbols *scols_copy_symbols(const struct libscols_symbols *sy) if (!ret) return NULL; - rc = scols_symbols_set_branch(ret, sy->branch); + rc = scols_symbols_set_branch(ret, sy->tree_branch); + if (!rc) + rc = scols_symbols_set_vertical(ret, sy->tree_vert); + if (!rc) + rc = scols_symbols_set_right(ret, sy->tree_right); + if (!rc) + rc = scols_symbols_set_group_vertical(ret, sy->group_vert); + if (!rc) + rc = scols_symbols_set_group_horizontal(ret, sy->group_horz); + if (!rc) + rc = scols_symbols_set_group_first_member(ret, sy->group_first_member); + if (!rc) + rc = scols_symbols_set_group_last_member(ret, sy->group_last_member); + if (!rc) + rc = scols_symbols_set_group_middle_member(ret, sy->group_middle_member); if (!rc) - rc = scols_symbols_set_vertical(ret, sy->vert); + rc = scols_symbols_set_group_middle_child(ret, sy->group_middle_child); if (!rc) - rc = scols_symbols_set_right(ret, sy->right); + rc = scols_symbols_set_group_last_child(ret, sy->group_last_child); if (!rc) rc = scols_symbols_set_title_padding(ret, sy->title_padding); if (!rc) diff --git a/libsmartcols/src/table.c b/libsmartcols/src/table.c index 3914c88b3..e4f27a916 100644 --- a/libsmartcols/src/table.c +++ b/libsmartcols/src/table.c @@ -29,10 +29,17 @@ #include "smartcolsP.h" #ifdef HAVE_WIDECHAR -#define UTF_V "\342\224\202" /* U+2502, Vertical line drawing char */ -#define UTF_VR "\342\224\234" /* U+251C, Vertical and right */ -#define UTF_H "\342\224\200" /* U+2500, Horizontal */ -#define UTF_UR "\342\224\224" /* U+2514, Up and right */ +#define UTF_V "\342\224\202" /* U+2502, Vertical line drawing char | */ +#define UTF_VR "\342\224\234" /* U+251C, Vertical and right |- */ +#define UTF_H "\342\224\200" /* U+2500, Horizontal - */ +#define UTF_UR "\342\224\224" /* U+2514, Up and right '- */ + +#define UTF_V3 "\342\224\206" /* U+2506 Triple Dash Vertical | */ +#define UTF_H3 "\342\224\210" /* U+2504 Triple Dash Horizontal - */ +#define UTF_DR "\342\224\214" /* U+250C Down and Right ,- */ +#define UTF_DH "\342\224\254" /* U+252C Down and Horizontal |' */ + +#define UTF_TR "\342\226\266" /* U+25B6 Black Right-Pointing Triangle > */ #endif /* !HAVE_WIDECHAR */ #define is_last_column(_tb, _cl) \ @@ -76,6 +83,7 @@ struct libscols_table *scols_new_table(void) INIT_LIST_HEAD(&tb->tb_lines); INIT_LIST_HEAD(&tb->tb_columns); + INIT_LIST_HEAD(&tb->tb_groups); DBG(TAB, ul_debugobj(tb, "alloc")); ON_DBG(INIT, check_padding_debug(tb)); @@ -95,6 +103,17 @@ void scols_ref_table(struct libscols_table *tb) tb->refcount++; } +static void scols_table_remove_groups(struct libscols_table *tb) +{ + while (!list_empty(&tb->tb_groups)) { + struct libscols_group *gr = list_entry(tb->tb_groups.next, + struct libscols_group, gr_groups); + scols_group_remove_children(gr); + scols_group_remove_members(gr); + scols_unref_group(gr); + } +} + /** * scols_unref_table: * @tb: a pointer to a struct libscols_table instance @@ -105,16 +124,40 @@ void scols_ref_table(struct libscols_table *tb) void scols_unref_table(struct libscols_table *tb) { if (tb && (--tb->refcount <= 0)) { - DBG(TAB, ul_debugobj(tb, "dealloc")); + DBG(TAB, ul_debugobj(tb, "dealloc <-")); + scols_table_remove_groups(tb); scols_table_remove_lines(tb); scols_table_remove_columns(tb); scols_unref_symbols(tb->symbols); scols_reset_cell(&tb->title); + free(tb->grpset); free(tb->linesep); free(tb->colsep); free(tb->name); free(tb); + DBG(TAB, ul_debug("<- done")); + } +} + +/* Private API */ +int scols_table_next_group(struct libscols_table *tb, + struct libscols_iter *itr, + struct libscols_group **gr) +{ + int rc = 1; + + if (!tb || !itr || !gr) + return -EINVAL; + *gr = NULL; + + if (!itr->head) + SCOLS_ITER_INIT(itr, &tb->tb_groups); + if (itr->p != itr->head) { + SCOLS_ITER_ITERATE(itr, *gr, struct libscols_group, gr_groups); + rc = 0; } + + return rc; } /** @@ -798,15 +841,35 @@ int scols_table_set_default_symbols(struct libscols_table *tb) #if defined(HAVE_WIDECHAR) if (!scols_table_is_ascii(tb) && !strcmp(nl_langinfo(CODESET), "UTF-8")) { + /* tree chart */ scols_symbols_set_branch(sy, UTF_VR UTF_H); scols_symbols_set_vertical(sy, UTF_V " "); scols_symbols_set_right(sy, UTF_UR UTF_H); + /* groups chart */ + scols_symbols_set_group_horizontal(sy, UTF_H3); + scols_symbols_set_group_vertical(sy, UTF_V3); + + scols_symbols_set_group_first_member(sy, UTF_DR UTF_H3 UTF_TR); + scols_symbols_set_group_last_member(sy, UTF_UR UTF_DH UTF_TR); + scols_symbols_set_group_middle_member(sy, UTF_VR UTF_H3 UTF_TR); + scols_symbols_set_group_last_child(sy, UTF_UR UTF_H3); + scols_symbols_set_group_middle_child(sy, UTF_VR UTF_H3); } else #endif { + /* tree chart */ scols_symbols_set_branch(sy, "|-"); scols_symbols_set_vertical(sy, "| "); scols_symbols_set_right(sy, "`-"); + /* groups chart */ + scols_symbols_set_group_horizontal(sy, "-"); + scols_symbols_set_group_vertical(sy, "|"); + + scols_symbols_set_group_first_member(sy, ",->"); + scols_symbols_set_group_last_member(sy, "'->"); + scols_symbols_set_group_middle_member(sy, "|->"); + scols_symbols_set_group_last_child(sy, "`-"); + scols_symbols_set_group_middle_child(sy, "|-"); } scols_symbols_set_title_padding(sy, " "); scols_symbols_set_cell_padding(sy, " "); @@ -1349,16 +1412,26 @@ static int sort_line_children(struct libscols_line *ln, struct libscols_column * { struct list_head *p; - if (list_empty(&ln->ln_branch)) - return 0; + if (!list_empty(&ln->ln_branch)) { + list_for_each(p, &ln->ln_branch) { + struct libscols_line *chld = + list_entry(p, struct libscols_line, ln_children); + sort_line_children(chld, cl); + } + + list_sort(&ln->ln_branch, cells_cmp_wrapper_children, cl); + } + + if (is_first_group_member(ln)) { + list_for_each(p, &ln->group->gr_children) { + struct libscols_line *chld = + list_entry(p, struct libscols_line, ln_children); + sort_line_children(chld, cl); + } - list_for_each(p, &ln->ln_branch) { - struct libscols_line *chld = - list_entry(p, struct libscols_line, ln_children); - sort_line_children(chld, cl); + list_sort(&ln->group->gr_children, cells_cmp_wrapper_children, cl); } - list_sort(&ln->ln_branch, cells_cmp_wrapper_children, cl); return 0; } diff --git a/libsmartcols/src/table_print.c b/libsmartcols/src/table_print.c deleted file mode 100644 index a15bf90e3..000000000 --- a/libsmartcols/src/table_print.c +++ /dev/null @@ -1,1748 +0,0 @@ -/* - * table.c - functions handling the data at the table level - * - * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com> - * Copyright (C) 2016 Igor Gnatenko <i.gnatenko.brain@gmail.com> - * - * This file may be redistributed under the terms of the - * GNU Lesser General Public License. - */ - -/** - * SECTION: table_print - * @title: Table print - * @short_description: output functions - * - * Table output API. - */ - -#include <stdlib.h> -#include <unistd.h> -#include <string.h> -#include <termios.h> -#include <ctype.h> - -#include "mbsalign.h" -#include "carefulputc.h" -#include "smartcolsP.h" - -#define colsep(tb) ((tb)->colsep ? (tb)->colsep : " ") -#define linesep(tb) ((tb)->linesep ? (tb)->linesep : "\n") - -/* Fallback for symbols - * - * Note that by default library define all the symbols, but in case user does - * not define all symbols or if we extended the symbols struct then we need - * fallback to be more robust and backwardly compatible. - */ -#define titlepadding_symbol(tb) ((tb)->symbols->title_padding ? (tb)->symbols->title_padding : " ") -#define branch_symbol(tb) ((tb)->symbols->branch ? (tb)->symbols->branch : "|-") -#define vertical_symbol(tb) ((tb)->symbols->vert ? (tb)->symbols->vert : "| ") -#define right_symbol(tb) ((tb)->symbols->right ? (tb)->symbols->right : "`-") - -#define cellpadding_symbol(tb) ((tb)->padding_debug ? "." : \ - ((tb)->symbols->cell_padding ? (tb)->symbols->cell_padding: " ")) - -#define want_repeat_header(tb) (!(tb)->header_repeat || (tb)->header_next <= (tb)->termlines_used) - - -/* This is private struct to work with output data */ -struct libscols_buffer { - char *begin; /* begin of the buffer */ - char *cur; /* current end of the buffer */ - char *encdata; /* encoded buffer mbs_safe_encode() */ - - size_t bufsz; /* size of the buffer */ - size_t art_idx; /* begin of the tree ascii art or zero */ -}; - -static struct libscols_buffer *new_buffer(size_t sz) -{ - struct libscols_buffer *buf = malloc(sz + sizeof(struct libscols_buffer)); - - if (!buf) - return NULL; - - buf->cur = buf->begin = ((char *) buf) + sizeof(struct libscols_buffer); - buf->encdata = NULL; - buf->bufsz = sz; - - DBG(BUFF, ul_debugobj(buf, "alloc (size=%zu)", sz)); - return buf; -} - -static void free_buffer(struct libscols_buffer *buf) -{ - if (!buf) - return; - DBG(BUFF, ul_debugobj(buf, "dealloc")); - free(buf->encdata); - free(buf); -} - -static int buffer_reset_data(struct libscols_buffer *buf) -{ - if (!buf) - return -EINVAL; - - /*DBG(BUFF, ul_debugobj(buf, "reset data"));*/ - buf->begin[0] = '\0'; - buf->cur = buf->begin; - buf->art_idx = 0; - return 0; -} - -static int buffer_append_data(struct libscols_buffer *buf, const char *str) -{ - size_t maxsz, sz; - - if (!buf) - return -EINVAL; - if (!str || !*str) - return 0; - - sz = strlen(str); - maxsz = buf->bufsz - (buf->cur - buf->begin); - - if (maxsz <= sz) - return -EINVAL; - memcpy(buf->cur, str, sz + 1); - buf->cur += sz; - return 0; -} - -static int buffer_set_data(struct libscols_buffer *buf, const char *str) -{ - int rc = buffer_reset_data(buf); - return rc ? rc : buffer_append_data(buf, str); -} - -/* save the current buffer position to art_idx */ -static void buffer_set_art_index(struct libscols_buffer *buf) -{ - if (buf) { - buf->art_idx = buf->cur - buf->begin; - /*DBG(BUFF, ul_debugobj(buf, "art index: %zu", buf->art_idx));*/ - } -} - -static char *buffer_get_data(struct libscols_buffer *buf) -{ - return buf ? buf->begin : NULL; -} - -/* encode data by mbs_safe_encode() to avoid control and non-printable chars */ -static char *buffer_get_safe_data(struct libscols_table *tb, - struct libscols_buffer *buf, - size_t *cells, - const char *safechars) -{ - char *data = buffer_get_data(buf); - char *res = NULL; - - if (!data) - goto nothing; - - if (!buf->encdata) { - buf->encdata = malloc(mbs_safe_encode_size(buf->bufsz) + 1); - if (!buf->encdata) - goto nothing; - } - - if (tb->no_encode) { - *cells = mbs_safe_width(data); - strcpy(buf->encdata, data); - res = buf->encdata; - } else { - res = mbs_safe_encode_to_buffer(data, cells, buf->encdata, safechars); - } - - if (!res || !*cells || *cells == (size_t) -1) - goto nothing; - return res; -nothing: - *cells = 0; - return NULL; -} - -/* returns size in bytes of the ascii art (according to art_idx) in safe encoding */ -static size_t buffer_get_safe_art_size(struct libscols_buffer *buf) -{ - char *data = buffer_get_data(buf); - size_t bytes = 0; - - if (!data || !buf->art_idx) - return 0; - - mbs_safe_nwidth(data, buf->art_idx, &bytes); - return bytes; -} - -/* returns pointer to the end of used data */ -static int line_ascii_art_to_buffer(struct libscols_table *tb, - struct libscols_line *ln, - struct libscols_buffer *buf) -{ - const char *art; - int rc; - - assert(ln); - assert(buf); - - if (!ln->parent) - return 0; - - rc = line_ascii_art_to_buffer(tb, ln->parent, buf); - if (rc) - return rc; - - if (list_entry_is_last(&ln->ln_children, &ln->parent->ln_branch)) - art = " "; - else - art = vertical_symbol(tb); - - return buffer_append_data(buf, art); -} - -static int is_last_column(struct libscols_column *cl) -{ - int rc = list_entry_is_last(&cl->cl_columns, &cl->table->tb_columns); - struct libscols_column *next; - - if (rc) - return 1; - - next = list_entry(cl->cl_columns.next, struct libscols_column, cl_columns); - if (next && scols_column_is_hidden(next) && is_last_column(next)) - return 1; - return 0; -} - - -static int has_pending_data(struct libscols_table *tb) -{ - struct libscols_column *cl; - struct libscols_iter itr; - - scols_reset_iter(&itr, SCOLS_ITER_FORWARD); - while (scols_table_next_column(tb, &itr, &cl) == 0) { - if (scols_column_is_hidden(cl)) - continue; - if (cl->pending_data) - return 1; - } - return 0; -} - -/* print padding or ASCII-art instead of data of @cl */ -static void print_empty_cell(struct libscols_table *tb, - struct libscols_column *cl, - struct libscols_line *ln, /* optional */ - size_t bufsz) -{ - size_t len_pad = 0; /* in screen cells as opposed to bytes */ - - /* generate tree ASCII-art rather than padding */ - if (ln && scols_column_is_tree(cl)) { - if (!ln->parent) { - /* only print symbols->vert if followed by child */ - if (!list_empty(&ln->ln_branch)) { - fputs(vertical_symbol(tb), tb->out); - len_pad = mbs_safe_width(vertical_symbol(tb)); - } - } else { - /* use the same draw function as though we were intending to draw an L-shape */ - struct libscols_buffer *art = new_buffer(bufsz); - char *data; - - if (art) { - /* whatever the rc, len_pad will be sensible */ - line_ascii_art_to_buffer(tb, ln, art); - if (!list_empty(&ln->ln_branch) && has_pending_data(tb)) - buffer_append_data(art, vertical_symbol(tb)); - data = buffer_get_safe_data(tb, art, &len_pad, NULL); - if (data && len_pad) - fputs(data, tb->out); - free_buffer(art); - } - } - } - - if (is_last_column(cl)) - return; - - /* fill rest of cell with space */ - for(; len_pad < cl->width; ++len_pad) - fputs(cellpadding_symbol(tb), tb->out); - - fputs(colsep(tb), tb->out); -} - - -static const char *get_cell_color(struct libscols_table *tb, - struct libscols_column *cl, - struct libscols_line *ln, /* optional */ - struct libscols_cell *ce) /* optional */ -{ - const char *color = NULL; - - if (tb && tb->colors_wanted) { - if (ce) - color = ce->color; - if (ln && !color) - color = ln->color; - if (!color) - color = cl->color; - } - return color; -} - -/* Fill the start of a line with padding (or with tree ascii-art). - * - * This is necessary after a long non-truncated column, as this requires the - * next column to be printed on the next line. For example (see 'DDD'): - * - * aaa bbb ccc ddd eee - * AAA BBB CCCCCCC - * DDD EEE - * ^^^^^^^^^^^^ - * new line padding - */ -static void print_newline_padding(struct libscols_table *tb, - struct libscols_column *cl, - struct libscols_line *ln, /* optional */ - size_t bufsz) -{ - size_t i; - - assert(tb); - assert(cl); - - fputs(linesep(tb), tb->out); /* line break */ - tb->termlines_used++; - - /* fill cells after line break */ - for (i = 0; i <= (size_t) cl->seqnum; i++) - print_empty_cell(tb, scols_table_get_column(tb, i), ln, bufsz); -} - -/* - * Pending data - * - * The first line in the multi-line cells (columns with SCOLS_FL_WRAP flag) is - * printed as usually and output is truncated to match column width. - * - * The rest of the long text is printed on next extra line(s). The extra lines - * don't exist in the table (not represented by libscols_line). The data for - * the extra lines are stored in libscols_column->pending_data_buf and the - * function print_line() adds extra lines until the buffer is not empty in all - * columns. - */ - -/* set data that will be printed by extra lines */ -static int set_pending_data(struct libscols_column *cl, const char *data, size_t sz) -{ - char *p = NULL; - - if (data && *data) { - DBG(COL, ul_debugobj(cl, "setting pending data")); - assert(sz); - p = strdup(data); - if (!p) - return -ENOMEM; - } - - free(cl->pending_data_buf); - cl->pending_data_buf = p; - cl->pending_data_sz = sz; - cl->pending_data = cl->pending_data_buf; - return 0; -} - -/* the next extra line has been printed, move pending data cursor */ -static int step_pending_data(struct libscols_column *cl, size_t bytes) -{ - DBG(COL, ul_debugobj(cl, "step pending data %zu -= %zu", cl->pending_data_sz, bytes)); - - if (bytes >= cl->pending_data_sz) - return set_pending_data(cl, NULL, 0); - - cl->pending_data += bytes; - cl->pending_data_sz -= bytes; - return 0; -} - -/* print next pending data for the column @cl */ -static int print_pending_data( - struct libscols_table *tb, - struct libscols_column *cl, - struct libscols_line *ln, /* optional */ - struct libscols_cell *ce) -{ - const char *color = get_cell_color(tb, cl, ln, ce); - size_t width = cl->width, bytes; - size_t len = width, i; - char *data; - char *nextchunk = NULL; - - if (!cl->pending_data) - return 0; - if (!width) - return -EINVAL; - - DBG(COL, ul_debugobj(cl, "printing pending data")); - - data = strdup(cl->pending_data); - if (!data) - goto err; - - if (scols_column_is_customwrap(cl) - && (nextchunk = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data))) { - bytes = nextchunk - data; - - len = mbs_safe_nwidth(data, bytes, NULL); - } else - bytes = mbs_truncate(data, &len); - - if (bytes == (size_t) -1) - goto err; - - if (bytes) - step_pending_data(cl, bytes); - - if (color) - fputs(color, tb->out); - fputs(data, tb->out); - if (color) - fputs(UL_COLOR_RESET, tb->out); - free(data); - - if (is_last_column(cl)) - return 0; - - for (i = len; i < width; i++) - fputs(cellpadding_symbol(tb), tb->out); /* padding */ - - fputs(colsep(tb), tb->out); /* columns separator */ - return 0; -err: - free(data); - return -errno; -} - -static int print_data(struct libscols_table *tb, - struct libscols_column *cl, - struct libscols_line *ln, /* optional */ - struct libscols_cell *ce, /* optional */ - struct libscols_buffer *buf) -{ - size_t len = 0, i, width, bytes; - const char *color = NULL; - char *data, *nextchunk; - int is_last; - - assert(tb); - assert(cl); - - data = buffer_get_data(buf); - if (!data) - data = ""; - - is_last = is_last_column(cl); - - switch (tb->format) { - case SCOLS_FMT_RAW: - fputs_nonblank(data, tb->out); - if (!is_last) - fputs(colsep(tb), tb->out); - return 0; - - case SCOLS_FMT_EXPORT: - fprintf(tb->out, "%s=", scols_cell_get_data(&cl->header)); - fputs_quoted(data, tb->out); - if (!is_last) - fputs(colsep(tb), tb->out); - return 0; - - case SCOLS_FMT_JSON: - fputs_quoted_json_lower(scols_cell_get_data(&cl->header), tb->out); - fputs(":", tb->out); - switch (cl->json_type) { - case SCOLS_JSON_STRING: - if (!*data) - fputs("null", tb->out); - else - fputs_quoted_json(data, tb->out); - break; - case SCOLS_JSON_NUMBER: - if (!*data) - fputs("null", tb->out); - else - fputs(data, tb->out); - break; - case SCOLS_JSON_BOOLEAN: - fputs(!*data ? "false" : - *data == '0' ? "false" : - *data == 'N' || *data == 'n' ? "false" : "true", - tb->out); - break; - } - if (!is_last) - fputs(", ", tb->out); - return 0; - - case SCOLS_FMT_HUMAN: - break; /* continue below */ - } - - color = get_cell_color(tb, cl, ln, ce); - - /* Encode. Note that 'len' and 'width' are number of cells, not bytes. - */ - data = buffer_get_safe_data(tb, buf, &len, scols_column_get_safechars(cl)); - if (!data) - data = ""; - bytes = strlen(data); - width = cl->width; - - /* custom multi-line cell based */ - if (*data && scols_column_is_customwrap(cl) - && (nextchunk = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data))) { - set_pending_data(cl, nextchunk, bytes - (nextchunk - data)); - bytes = nextchunk - data; - len = mbs_safe_nwidth(data, bytes, NULL); - } - - if (is_last - && len < width - && !scols_table_is_maxout(tb) - && !scols_column_is_right(cl)) - width = len; - - /* truncate data */ - if (len > width && scols_column_is_trunc(cl)) { - len = width; - bytes = mbs_truncate(data, &len); /* updates 'len' */ - } - - /* standard multi-line cell */ - if (len > width && scols_column_is_wrap(cl) - && !scols_column_is_customwrap(cl)) { - set_pending_data(cl, data, bytes); - - len = width; - bytes = mbs_truncate(data, &len); - if (bytes != (size_t) -1 && bytes > 0) - step_pending_data(cl, bytes); - } - - if (bytes == (size_t) -1) { - bytes = len = 0; - data = NULL; - } - - if (data) { - if (scols_column_is_right(cl)) { - if (color) - fputs(color, tb->out); - for (i = len; i < width; i++) - fputs(cellpadding_symbol(tb), tb->out); - fputs(data, tb->out); - if (color) - fputs(UL_COLOR_RESET, tb->out); - len = width; - - } else if (color) { - char *p = data; - size_t art = buffer_get_safe_art_size(buf); - - /* we don't want to colorize tree ascii art */ - if (scols_column_is_tree(cl) && art && art < bytes) { - fwrite(p, 1, art, tb->out); - p += art; - } - - fputs(color, tb->out); - fputs(p, tb->out); - fputs(UL_COLOR_RESET, tb->out); - } else - fputs(data, tb->out); - } - for (i = len; i < width; i++) - fputs(cellpadding_symbol(tb), tb->out); /* padding */ - - if (is_last) - return 0; - - if (len > width && !scols_column_is_trunc(cl)) - print_newline_padding(tb, cl, ln, buf->bufsz); /* next column starts on next line */ - else - fputs(colsep(tb), tb->out); /* columns separator */ - - return 0; -} - -static int cell_to_buffer(struct libscols_table *tb, - struct libscols_line *ln, - struct libscols_column *cl, - struct libscols_buffer *buf) -{ - const char *data; - struct libscols_cell *ce; - int rc = 0; - - assert(tb); - assert(ln); - assert(cl); - assert(buf); - assert(cl->seqnum <= tb->ncols); - - buffer_reset_data(buf); - - ce = scols_line_get_cell(ln, cl->seqnum); - data = ce ? scols_cell_get_data(ce) : NULL; - if (!data) - return 0; - - if (!scols_column_is_tree(cl)) - return buffer_set_data(buf, data); - - /* - * Tree stuff - */ - if (ln->parent && !scols_table_is_json(tb)) { - rc = line_ascii_art_to_buffer(tb, ln->parent, buf); - - if (!rc && list_entry_is_last(&ln->ln_children, &ln->parent->ln_branch)) - rc = buffer_append_data(buf, right_symbol(tb)); - else if (!rc) - rc = buffer_append_data(buf, branch_symbol(tb)); - if (!rc) - buffer_set_art_index(buf); - } - - if (!rc) - rc = buffer_append_data(buf, data); - return rc; -} - -static void fput_indent(struct libscols_table *tb) -{ - int i; - - for (i = 0; i <= tb->indent; i++) - fputs(" ", tb->out); -} - -static void fput_table_open(struct libscols_table *tb) -{ - tb->indent = 0; - - if (scols_table_is_json(tb)) { - fputc('{', tb->out); - fputs(linesep(tb), tb->out); - - fput_indent(tb); - fputs_quoted(tb->name, tb->out); - fputs(": [", tb->out); - fputs(linesep(tb), tb->out); - - tb->indent++; - tb->indent_last_sep = 1; - } -} - -static void fput_table_close(struct libscols_table *tb) -{ - tb->indent--; - - if (scols_table_is_json(tb)) { - fput_indent(tb); - fputc(']', tb->out); - tb->indent--; - fputs(linesep(tb), tb->out); - fputc('}', tb->out); - tb->indent_last_sep = 1; - } -} - -static void fput_children_open(struct libscols_table *tb) -{ - if (scols_table_is_json(tb)) { - fputc(',', tb->out); - fputs(linesep(tb), tb->out); - fput_indent(tb); - fputs("\"children\": [", tb->out); - } - /* between parent and child is separator */ - fputs(linesep(tb), tb->out); - tb->indent_last_sep = 1; - tb->indent++; - tb->termlines_used++; -} - -static void fput_children_close(struct libscols_table *tb) -{ - tb->indent--; - - if (scols_table_is_json(tb)) { - fput_indent(tb); - fputc(']', tb->out); - fputs(linesep(tb), tb->out); - tb->indent_last_sep = 1; - } -} - -static void fput_line_open(struct libscols_table *tb) -{ - if (scols_table_is_json(tb)) { - fput_indent(tb); - fputc('{', tb->out); - tb->indent_last_sep = 0; - } - tb->indent++; -} - -static void fput_line_close(struct libscols_table *tb, int last, int last_in_table) -{ - tb->indent--; - if (scols_table_is_json(tb)) { - if (tb->indent_last_sep) - fput_indent(tb); - fputs(last ? "}" : "},", tb->out); - if (!tb->no_linesep) - fputs(linesep(tb), tb->out); - - } else if (tb->no_linesep == 0 && last_in_table == 0) { - fputs(linesep(tb), tb->out); - tb->termlines_used++; - } - - tb->indent_last_sep = 1; -} - -/* - * Prints data. Data can be printed in more formats (raw, NAME=xxx pairs), and - * control and non-printable characters can be encoded in the \x?? encoding. - */ -static int print_line(struct libscols_table *tb, - struct libscols_line *ln, - struct libscols_buffer *buf) -{ - int rc = 0, pending = 0; - struct libscols_column *cl; - struct libscols_iter itr; - - assert(ln); - - DBG(TAB, ul_debugobj(tb, "printing line")); - - /* regular line */ - scols_reset_iter(&itr, SCOLS_ITER_FORWARD); - while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) { - if (scols_column_is_hidden(cl)) - continue; - rc = cell_to_buffer(tb, ln, cl, buf); - if (rc == 0) - rc = print_data(tb, cl, ln, - scols_line_get_cell(ln, cl->seqnum), - buf); - if (rc == 0 && cl->pending_data) - pending = 1; - } - - /* extra lines of the multi-line cells */ - while (rc == 0 && pending) { - pending = 0; - fputs(linesep(tb), tb->out); - tb->termlines_used++; - scols_reset_iter(&itr, SCOLS_ITER_FORWARD); - while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) { - if (scols_column_is_hidden(cl)) - continue; - if (cl->pending_data) { - rc = print_pending_data(tb, cl, ln, scols_line_get_cell(ln, cl->seqnum)); - if (rc == 0 && cl->pending_data) - pending = 1; - } else - print_empty_cell(tb, cl, ln, buf->bufsz); - } - } - - return 0; -} - -static int print_title(struct libscols_table *tb) -{ - int rc, color = 0; - mbs_align_t align; - size_t width, len = 0, bufsz, titlesz; - char *title = NULL, *buf = NULL; - - assert(tb); - - if (!tb->title.data) - return 0; - - DBG(TAB, ul_debugobj(tb, "printing title")); - - /* encode data */ - if (tb->no_encode) { - len = bufsz = strlen(tb->title.data) + 1; - buf = strdup(tb->title.data); - if (!buf) { - rc = -ENOMEM; - goto done; - } - } else { - bufsz = mbs_safe_encode_size(strlen(tb->title.data)) + 1; - if (bufsz == 1) { - DBG(TAB, ul_debugobj(tb, "title is empty string -- ignore")); - return 0; - } - buf = malloc(bufsz); - if (!buf) { - rc = -ENOMEM; - goto done; - } - - if (!mbs_safe_encode_to_buffer(tb->title.data, &len, buf, NULL) || - !len || len == (size_t) -1) { - rc = -EINVAL; - goto done; - } - } - - /* truncate and align */ - width = tb->is_term ? tb->termwidth : 80; - titlesz = width + bufsz; - - title = malloc(titlesz); - if (!title) { - rc = -EINVAL; - goto done; - } - - switch (scols_cell_get_alignment(&tb->title)) { - case SCOLS_CELL_FL_RIGHT: - align = MBS_ALIGN_RIGHT; - break; - case SCOLS_CELL_FL_CENTER: - align = MBS_ALIGN_CENTER; - break; - case SCOLS_CELL_FL_LEFT: - default: - align = MBS_ALIGN_LEFT; - /* - * Don't print extra blank chars after the title if on left - * (that's same as we use for the last column in the table). - */ - if (len < width - && !scols_table_is_maxout(tb) - && isblank(*titlepadding_symbol(tb))) - width = len; - break; - - } - - /* copy from buf to title and align to width with title_padding */ - rc = mbsalign_with_padding(buf, title, titlesz, - &width, align, - 0, (int) *titlepadding_symbol(tb)); - - if (rc == -1) { - rc = -EINVAL; - goto done; - } - - if (tb->colors_wanted && tb->title.color) - color = 1; - if (color) - fputs(tb->title.color, tb->out); - - fputs(title, tb->out); - - if (color) - fputs(UL_COLOR_RESET, tb->out); - - fputc('\n', tb->out); - rc = 0; -done: - free(buf); - free(title); - DBG(TAB, ul_debugobj(tb, "printing title done [rc=%d]", rc)); - return rc; -} - -static int print_header(struct libscols_table *tb, struct libscols_buffer *buf) -{ - int rc = 0; - struct libscols_column *cl; - struct libscols_iter itr; - - assert(tb); - - if ((tb->header_printed == 1 && tb->header_repeat == 0) || - scols_table_is_noheadings(tb) || - scols_table_is_export(tb) || - scols_table_is_json(tb) || - list_empty(&tb->tb_lines)) - return 0; - - DBG(TAB, ul_debugobj(tb, "printing header")); - - /* set the width according to the size of the data */ - scols_reset_iter(&itr, SCOLS_ITER_FORWARD); - while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) { - if (scols_column_is_hidden(cl)) - continue; - rc = buffer_set_data(buf, scols_cell_get_data(&cl->header)); - if (!rc) - rc = print_data(tb, cl, NULL, &cl->header, buf); - } - - if (rc == 0) { - fputs(linesep(tb), tb->out); - tb->termlines_used++; - } - - tb->header_printed = 1; - tb->header_next = tb->termlines_used + tb->termheight; - if (tb->header_repeat) - DBG(TAB, ul_debugobj(tb, "\tnext header: %zu [current=%zu]", - tb->header_next, tb->termlines_used)); - return rc; -} - - -static int print_range( struct libscols_table *tb, - struct libscols_buffer *buf, - struct libscols_iter *itr, - struct libscols_line *end) -{ - int rc = 0; - struct libscols_line *ln; - - assert(tb); - DBG(TAB, ul_debugobj(tb, "printing range")); - - while (rc == 0 && scols_table_next_line(tb, itr, &ln) == 0) { - - int last = scols_iter_is_last(itr); - - fput_line_open(tb); - rc = print_line(tb, ln, buf); - fput_line_close(tb, last, last); - - if (end && ln == end) - break; - - if (!last && want_repeat_header(tb)) - print_header(tb, buf); - } - - return rc; - -} - -static int print_table(struct libscols_table *tb, struct libscols_buffer *buf) -{ - struct libscols_iter itr; - - scols_reset_iter(&itr, SCOLS_ITER_FORWARD); - return print_range(tb, buf, &itr, NULL); -} - - -static int print_tree_line(struct libscols_table *tb, - struct libscols_line *ln, - struct libscols_buffer *buf, - int last, - int last_in_table) -{ - int rc; - - /* print the line */ - fput_line_open(tb); - rc = print_line(tb, ln, buf); - if (rc) - goto done; - - /* print children */ - if (!list_empty(&ln->ln_branch)) { - struct list_head *p; - - fput_children_open(tb); - - /* print all children */ - list_for_each(p, &ln->ln_branch) { - struct libscols_line *chld = - list_entry(p, struct libscols_line, ln_children); - int last_child = p->next == &ln->ln_branch; - - rc = print_tree_line(tb, chld, buf, last_child, last_in_table && last_child); - if (rc) - goto done; - } - - fput_children_close(tb); - } - - if (list_empty(&ln->ln_branch) || scols_table_is_json(tb)) - fput_line_close(tb, last, last_in_table); -done: - return rc; -} - -static int print_tree(struct libscols_table *tb, struct libscols_buffer *buf) -{ - int rc = 0; - struct libscols_line *ln, *last = NULL; - struct libscols_iter itr; - - assert(tb); - - DBG(TAB, ul_debugobj(tb, "printing tree")); - - scols_reset_iter(&itr, SCOLS_ITER_FORWARD); - - while (scols_table_next_line(tb, &itr, &ln) == 0) - if (!last || !ln->parent) - last = ln; - - scols_reset_iter(&itr, SCOLS_ITER_FORWARD); - while (rc == 0 && scols_table_next_line(tb, &itr, &ln) == 0) { - if (ln->parent) - continue; - rc = print_tree_line(tb, ln, buf, ln == last, ln == last); - } - - return rc; -} - -static void dbg_column(struct libscols_table *tb, struct libscols_column *cl) -{ - if (scols_column_is_hidden(cl)) { - DBG(COL, ul_debugobj(cl, "%s (hidden) ignored", cl->header.data)); - return; - } - - DBG(COL, ul_debugobj(cl, "%15s seq=%zu, width=%zd, " - "hint=%d, avg=%zu, max=%zu, min=%zu, " - "extreme=%s %s", - - cl->header.data, cl->seqnum, cl->width, - cl->width_hint > 1 ? (int) cl->width_hint : - (int) (cl->width_hint * tb->termwidth), - cl->width_avg, - cl->width_max, - cl->width_min, - cl->is_extreme ? "yes" : "not", - cl->flags & SCOLS_FL_TRUNC ? "trunc" : "")); -} - -static void dbg_columns(struct libscols_table *tb) -{ - struct libscols_iter itr; - struct libscols_column *cl; - - scols_reset_iter(&itr, SCOLS_ITER_FORWARD); - while (scols_table_next_column(tb, &itr, &cl) == 0) - dbg_column(tb, cl); -} - - -/* - * This function counts column width. - * - * For the SCOLS_FL_NOEXTREMES columns it is possible to call this function - * two times. The first pass counts the width and average width. If the column - * contains fields that are too large (a width greater than 2 * average) then - * the column is marked as "extreme". In the second pass all extreme fields - * are ignored and the column width is counted from non-extreme fields only. - */ -static int count_column_width(struct libscols_table *tb, - struct libscols_column *cl, - struct libscols_buffer *buf) -{ - struct libscols_line *ln; - struct libscols_iter itr; - int extreme_count = 0, rc = 0, no_header = 0; - size_t extreme_sum = 0; - - assert(tb); - assert(cl); - - cl->width = 0; - if (!cl->width_min) { - if (cl->width_hint < 1 && scols_table_is_maxout(tb) && tb->is_term) { - cl->width_min = (size_t) (cl->width_hint * tb->termwidth); - if (cl->width_min && !is_last_column(cl)) - cl->width_min--; - } - if (scols_cell_get_data(&cl->header)) { - size_t len = mbs_safe_width(scols_cell_get_data(&cl->header)); - cl->width_min = max(cl->width_min, len); - } else - no_header = 1; - - if (!cl->width_min) - cl->width_min = 1; - } - - scols_reset_iter(&itr, SCOLS_ITER_FORWARD); - while (scols_table_next_line(tb, &itr, &ln) == 0) { - size_t len; - char *data; - - rc = cell_to_buffer(tb, ln, cl, buf); - if (rc) - goto done; - - data = buffer_get_data(buf); - - if (!data) - len = 0; - else if (scols_column_is_customwrap(cl)) - len = cl->wrap_chunksize(cl, data, cl->wrapfunc_data); - else - len = mbs_safe_width(data); - - if (len == (size_t) -1) /* ignore broken multibyte strings */ - len = 0; - cl->width_max = max(len, cl->width_max); - - if (cl->is_extreme && cl->width_avg && len > cl->width_avg * 2) - continue; - else if (scols_column_is_noextremes(cl)) { - extreme_sum += len; - extreme_count++; - } - cl->width = max(len, cl->width); - if (scols_column_is_tree(cl)) { - size_t treewidth = buffer_get_safe_art_size(buf); - cl->width_treeart = max(cl->width_treeart, treewidth); - } - } - - if (extreme_count && cl->width_avg == 0) { - cl->width_avg = extreme_sum / extreme_count; - if (cl->width_avg && cl->width_max > cl->width_avg * 2) - cl->is_extreme = 1; - } - - /* enlarge to minimal width */ - if (cl->width < cl->width_min && !scols_column_is_strict_width(cl)) - cl->width = cl->width_min; - - /* use absolute size for large columns */ - else if (cl->width_hint >= 1 && cl->width < (size_t) cl->width_hint - && cl->width_min < (size_t) cl->width_hint) - - cl->width = (size_t) cl->width_hint; - - - /* Column without header and data, set minimal size to zero (default is 1) */ - if (cl->width_max == 0 && no_header && cl->width_min == 1 && cl->width <= 1) - cl->width = cl->width_min = 0; - -done: - ON_DBG(COL, dbg_column(tb, cl)); - return rc; -} - -/* - * This is core of the scols_* voodoo... - */ -static int recount_widths(struct libscols_table *tb, struct libscols_buffer *buf) -{ - struct libscols_column *cl; - struct libscols_iter itr; - size_t width = 0, width_min = 0; /* output width */ - int stage, rc = 0; - int extremes = 0; - size_t colsepsz; - - - DBG(TAB, ul_debugobj(tb, "recounting widths (termwidth=%zu)", tb->termwidth)); - - colsepsz = mbs_safe_width(colsep(tb)); - - /* set basic columns width - */ - scols_reset_iter(&itr, SCOLS_ITER_FORWARD); - while (scols_table_next_column(tb, &itr, &cl) == 0) { - int is_last; - - if (scols_column_is_hidden(cl)) - continue; - rc = count_column_width(tb, cl, buf); - if (rc) - goto done; - - is_last = is_last_column(cl); - - width += cl->width + (is_last ? 0 : colsepsz); /* separator for non-last column */ - width_min += cl->width_min + (is_last ? 0 : colsepsz); - extremes += cl->is_extreme; - } - - if (!tb->is_term) { - DBG(TAB, ul_debugobj(tb, " non-terminal output")); - goto done; - } - - /* be paranoid */ - if (width_min > tb->termwidth && scols_table_is_maxout(tb)) { - DBG(TAB, ul_debugobj(tb, " min width larger than terminal! [width=%zu, term=%zu]", width_min, tb->termwidth)); - - scols_reset_iter(&itr, SCOLS_ITER_FORWARD); - while (width_min > tb->termwidth - && scols_table_next_column(tb, &itr, &cl) == 0) { - if (scols_column_is_hidden(cl)) - continue; - width_min--; - cl->width_min--; - } - DBG(TAB, ul_debugobj(tb, " min width reduced to %zu", width_min)); - } - - /* reduce columns with extreme fields */ - if (width > tb->termwidth && extremes) { - DBG(TAB, ul_debugobj(tb, " reduce width (extreme columns)")); - - scols_reset_iter(&itr, SCOLS_ITER_FORWARD); - while (scols_table_next_column(tb, &itr, &cl) == 0) { - size_t org_width; - - if (!cl->is_extreme || scols_column_is_hidden(cl)) - continue; - - org_width = cl->width; - rc = count_column_width(tb, cl, buf); - if (rc) - goto done; - - if (org_width > cl->width) - width -= org_width - cl->width; - else - extremes--; /* hmm... nothing reduced */ - } - } - - if (width < tb->termwidth) { - if (extremes) { - DBG(TAB, ul_debugobj(tb, " enlarge width (extreme columns)")); - - /* enlarge the first extreme column */ - scols_reset_iter(&itr, SCOLS_ITER_FORWARD); - while (scols_table_next_column(tb, &itr, &cl) == 0) { - size_t add; - - if (!cl->is_extreme || scols_column_is_hidden(cl)) - continue; - - /* this column is too large, ignore? - if (cl->width_max - cl->width > - (tb->termwidth - width)) - continue; - */ - - add = tb->termwidth - width; - if (add && cl->width + add > cl->width_max) - add = cl->width_max - cl->width; - - cl->width += add; - width += add; - - if (width == tb->termwidth) - break; - } - } - - if (width < tb->termwidth && scols_table_is_maxout(tb)) { - DBG(TAB, ul_debugobj(tb, " enlarge width (max-out)")); - - /* try enlarging all columns */ - while (width < tb->termwidth) { - scols_reset_iter(&itr, SCOLS_ITER_FORWARD); - while (scols_table_next_column(tb, &itr, &cl) == 0) { - if (scols_column_is_hidden(cl)) - continue; - cl->width++; - width++; - if (width == tb->termwidth) - break; - } - } - } else if (width < tb->termwidth) { - /* enlarge the last column */ - struct libscols_column *col = list_entry( - tb->tb_columns.prev, struct libscols_column, cl_columns); - - DBG(TAB, ul_debugobj(tb, " enlarge width (last column)")); - - if (!scols_column_is_right(col) && tb->termwidth - width > 0) { - col->width += tb->termwidth - width; - width = tb->termwidth; - } - } - } - - /* bad, we have to reduce output width, this is done in three stages: - * - * 1) trunc relative with trunc flag if the column width is greater than - * expected column width (it means "width_hint * terminal_width"). - * - * 2) trunc all with trunc flag - * - * 3) trunc relative without trunc flag - * - * Note that SCOLS_FL_WRAP (if no custom wrap function is specified) is - * interpreted as SCOLS_FL_TRUNC. - */ - for (stage = 1; width > tb->termwidth && stage <= 3; ) { - size_t org_width = width; - - DBG(TAB, ul_debugobj(tb, " reduce width - #%d stage (current=%zu, wanted=%zu)", - stage, width, tb->termwidth)); - - scols_reset_iter(&itr, SCOLS_ITER_FORWARD); - while (scols_table_next_column(tb, &itr, &cl) == 0) { - - int trunc_flag = 0; - - DBG(TAB, ul_debugobj(cl, " checking %s (width=%zu, treeart=%zu)", - cl->header.data, cl->width, cl->width_treeart)); - if (scols_column_is_hidden(cl)) - continue; - if (width <= tb->termwidth) - break; - - /* never truncate if already minimal width */ - if (cl->width == cl->width_min) - continue; - - /* never truncate the tree */ - if (scols_column_is_tree(cl) && width <= cl->width_treeart) - continue; - - /* nothing to truncate */ - if (cl->width == 0 || width == 0) - continue; - - trunc_flag = scols_column_is_trunc(cl) - || (scols_column_is_wrap(cl) && !scols_column_is_customwrap(cl)); - - switch (stage) { - /* #1 stage - trunc relative with TRUNC flag */ - case 1: - if (!trunc_flag) /* ignore: missing flag */ - break; - if (cl->width_hint <= 0 || cl->width_hint >= 1) /* ignore: no relative */ - break; - if (cl->width < (size_t) (cl->width_hint * tb->termwidth)) /* ignore: smaller than expected width */ - break; - - DBG(TAB, ul_debugobj(tb, " reducing (relative with flag)")); - cl->width--; - width--; - break; - - /* #2 stage - trunc all with TRUNC flag */ - case 2: - if (!trunc_flag) /* ignore: missing flag */ - break; - - DBG(TAB, ul_debugobj(tb, " reducing (all with flag)")); - cl->width--; - width--; - break; - - /* #3 stage - trunc relative without flag */ - case 3: - if (cl->width_hint <= 0 || cl->width_hint >= 1) /* ignore: no relative */ - break; - - DBG(TAB, ul_debugobj(tb, " reducing (relative without flag)")); - cl->width--; - width--; - break; - } - - /* hide zero width columns */ - if (cl->width == 0) - cl->flags |= SCOLS_FL_HIDDEN; - } - - /* the current stage is without effect, go to the next */ - if (org_width == width) - stage++; - } - - /* ignore last column(s) or force last column to be truncated if - * nowrap mode enabled */ - if (tb->no_wrap && width > tb->termwidth) { - scols_reset_iter(&itr, SCOLS_ITER_BACKWARD); - while (scols_table_next_column(tb, &itr, &cl) == 0) { - - if (scols_column_is_hidden(cl)) - continue; - if (width <= tb->termwidth) - break; - if (width - cl->width < tb->termwidth) { - size_t r = width - tb->termwidth; - - cl->flags |= SCOLS_FL_TRUNC; - cl->width -= r; - width -= r; - } else { - cl->flags |= SCOLS_FL_HIDDEN; - width -= cl->width + colsepsz; - } - } - } -done: - DBG(TAB, ul_debugobj(tb, " final width: %zu (rc=%d)", width, rc)); - ON_DBG(TAB, dbg_columns(tb)); - - return rc; -} - -static size_t strlen_line(struct libscols_line *ln) -{ - size_t i, sz = 0; - - assert(ln); - - for (i = 0; i < ln->ncells; i++) { - struct libscols_cell *ce = scols_line_get_cell(ln, i); - const char *data = ce ? scols_cell_get_data(ce) : NULL; - - sz += data ? strlen(data) : 0; - } - - return sz; -} - -static void cleanup_printing(struct libscols_table *tb, struct libscols_buffer *buf) -{ - if (!tb) - return; - - free_buffer(buf); - - if (tb->priv_symbols) { - scols_table_set_symbols(tb, NULL); - tb->priv_symbols = 0; - } -} - -static int initialize_printing(struct libscols_table *tb, struct libscols_buffer **buf) -{ - size_t bufsz, extra_bufsz = 0; - struct libscols_line *ln; - struct libscols_iter itr; - int rc; - - DBG(TAB, ul_debugobj(tb, "initialize printing")); - *buf = NULL; - - if (!tb->symbols) { - rc = scols_table_set_default_symbols(tb); - if (rc) - goto err; - tb->priv_symbols = 1; - } else - tb->priv_symbols = 0; - - if (tb->format == SCOLS_FMT_HUMAN) - tb->is_term = tb->termforce == SCOLS_TERMFORCE_NEVER ? 0 : - tb->termforce == SCOLS_TERMFORCE_ALWAYS ? 1 : - isatty(STDOUT_FILENO); - - if (tb->is_term) { - size_t width = (size_t) scols_table_get_termwidth(tb); - - if (tb->termreduce > 0 && tb->termreduce < width) { - width -= tb->termreduce; - scols_table_set_termwidth(tb, width); - } - bufsz = width; - } else - bufsz = BUFSIZ; - - if (!tb->is_term || tb->format != SCOLS_FMT_HUMAN || scols_table_is_tree(tb)) - tb->header_repeat = 0; - - /* - * Estimate extra space necessary for tree, JSON or another output - * decoration. - */ - if (scols_table_is_tree(tb)) - extra_bufsz += tb->nlines * strlen(vertical_symbol(tb)); - - switch (tb->format) { - case SCOLS_FMT_RAW: - extra_bufsz += tb->ncols; /* separator between columns */ - break; - case SCOLS_FMT_JSON: - if (tb->format == SCOLS_FMT_JSON) - extra_bufsz += tb->nlines * 3; /* indention */ - /* fallthrough */ - case SCOLS_FMT_EXPORT: - { - struct libscols_column *cl; - - scols_reset_iter(&itr, SCOLS_ITER_FORWARD); - - while (scols_table_next_column(tb, &itr, &cl) == 0) { - if (scols_column_is_hidden(cl)) - continue; - extra_bufsz += strlen(scols_cell_get_data(&cl->header)); /* data */ - extra_bufsz += 2; /* separators */ - } - break; - } - case SCOLS_FMT_HUMAN: - break; - } - - /* - * Enlarge buffer if necessary, the buffer should be large enough to - * store line data and tree ascii art (or another decoration). - */ - scols_reset_iter(&itr, SCOLS_ITER_FORWARD); - while (scols_table_next_line(tb, &itr, &ln) == 0) { - size_t sz; - - sz = strlen_line(ln) + extra_bufsz; - if (sz > bufsz) - bufsz = sz; - } - - *buf = new_buffer(bufsz + 1); /* data + space for \0 */ - if (!*buf) { - rc = -ENOMEM; - goto err; - } - - if (tb->format == SCOLS_FMT_HUMAN) { - rc = recount_widths(tb, *buf); - if (rc != 0) - goto err; - } - - return 0; -err: - cleanup_printing(tb, *buf); - return rc; -} - -/** - * scola_table_print_range: - * @tb: table - * @start: first printed line or NULL to print from the begin of the table - * @end: last printed line or NULL to print all from start. - * - * If the start is the first line in the table than prints table header too. - * The header is printed only once. This does not work for trees. - * - * Returns: 0, a negative value in case of an error. - */ -int scols_table_print_range( struct libscols_table *tb, - struct libscols_line *start, - struct libscols_line *end) -{ - struct libscols_buffer *buf = NULL; - struct libscols_iter itr; - int rc; - - if (scols_table_is_tree(tb)) - return -EINVAL; - - DBG(TAB, ul_debugobj(tb, "printing range from API")); - - rc = initialize_printing(tb, &buf); - if (rc) - return rc; - - if (start) { - itr.direction = SCOLS_ITER_FORWARD; - itr.head = &tb->tb_lines; - itr.p = &start->ln_lines; - } else - scols_reset_iter(&itr, SCOLS_ITER_FORWARD); - - if (!start || itr.p == tb->tb_lines.next) { - rc = print_header(tb, buf); - if (rc) - goto done; - } - - rc = print_range(tb, buf, &itr, end); -done: - cleanup_printing(tb, buf); - return rc; -} - -/** - * scols_table_print_range_to_string: - * @tb: table - * @start: first printed line or NULL to print from the beginning of the table - * @end: last printed line or NULL to print all from start. - * @data: pointer to the beginning of a memory area to print to - * - * The same as scols_table_print_range(), but prints to @data instead of - * stream. - * - * Returns: 0, a negative value in case of an error. - */ -#ifdef HAVE_OPEN_MEMSTREAM -int scols_table_print_range_to_string( struct libscols_table *tb, - struct libscols_line *start, - struct libscols_line *end, - char **data) -{ - FILE *stream, *old_stream; - size_t sz; - int rc; - - if (!tb) - return -EINVAL; - - DBG(TAB, ul_debugobj(tb, "printing range to string")); - - /* create a stream for output */ - stream = open_memstream(data, &sz); - if (!stream) - return -ENOMEM; - - old_stream = scols_table_get_stream(tb); - scols_table_set_stream(tb, stream); - rc = scols_table_print_range(tb, start, end); - fclose(stream); - scols_table_set_stream(tb, old_stream); - - return rc; -} -#else -int scols_table_print_range_to_string( - struct libscols_table *tb __attribute__((__unused__)), - struct libscols_line *start __attribute__((__unused__)), - struct libscols_line *end __attribute__((__unused__)), - char **data __attribute__((__unused__))) -{ - return -ENOSYS; -} -#endif - -static int __scols_print_table(struct libscols_table *tb, int *is_empty) -{ - int rc = 0; - struct libscols_buffer *buf = NULL; - - if (!tb) - return -EINVAL; - - DBG(TAB, ul_debugobj(tb, "printing")); - if (is_empty) - *is_empty = 0; - - if (list_empty(&tb->tb_columns)) { - DBG(TAB, ul_debugobj(tb, "error -- no columns")); - return -EINVAL; - } - if (list_empty(&tb->tb_lines)) { - DBG(TAB, ul_debugobj(tb, "ignore -- no lines")); - if (is_empty) - *is_empty = 1; - return 0; - } - - tb->header_printed = 0; - rc = initialize_printing(tb, &buf); - if (rc) - return rc; - - fput_table_open(tb); - - if (tb->format == SCOLS_FMT_HUMAN) - print_title(tb); - - rc = print_header(tb, buf); - if (rc) - goto done; - - if (scols_table_is_tree(tb)) - rc = print_tree(tb, buf); - else - rc = print_table(tb, buf); - - fput_table_close(tb); -done: - cleanup_printing(tb, buf); - return rc; -} - -/** - * scols_print_table: - * @tb: table - * - * Prints the table to the output stream and terminate by \n. - * - * Returns: 0, a negative value in case of an error. - */ -int scols_print_table(struct libscols_table *tb) -{ - int empty = 0; - int rc = __scols_print_table(tb, &empty); - - if (rc == 0 && !empty) - fputc('\n', tb->out); - return rc; -} - -/** - * scols_print_table_to_string: - * @tb: table - * @data: pointer to the beginning of a memory area to print to - * - * Prints the table to @data. - * - * Returns: 0, a negative value in case of an error. - */ -#ifdef HAVE_OPEN_MEMSTREAM -int scols_print_table_to_string(struct libscols_table *tb, char **data) -{ - FILE *stream, *old_stream; - size_t sz; - int rc; - - if (!tb) - return -EINVAL; - - DBG(TAB, ul_debugobj(tb, "printing to string")); - - /* create a stream for output */ - stream = open_memstream(data, &sz); - if (!stream) - return -ENOMEM; - - old_stream = scols_table_get_stream(tb); - scols_table_set_stream(tb, stream); - rc = __scols_print_table(tb, NULL); - fclose(stream); - scols_table_set_stream(tb, old_stream); - - return rc; -} -#else -int scols_print_table_to_string( - struct libscols_table *tb __attribute__((__unused__)), - char **data __attribute__((__unused__))) -{ - return -ENOSYS; -} -#endif - diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am index c44cdda67..3043687e6 100644 --- a/misc-utils/Makemodule.am +++ b/misc-utils/Makemodule.am @@ -81,6 +81,7 @@ lsblk_SOURCES = \ misc-utils/lsblk.c \ misc-utils/lsblk-mnt.c \ misc-utils/lsblk-properties.c \ + misc-utils/lsblk-devtree.c \ misc-utils/lsblk.h lsblk_LDADD = $(LDADD) libblkid.la libmount.la libcommon.la libsmartcols.la lsblk_CFLAGS = $(AM_CFLAGS) -I$(ul_libblkid_incdir) -I$(ul_libmount_incdir) -I$(ul_libsmartcols_incdir) diff --git a/misc-utils/lsblk-devtree.c b/misc-utils/lsblk-devtree.c new file mode 100644 index 000000000..82db4f8cf --- /dev/null +++ b/misc-utils/lsblk-devtree.c @@ -0,0 +1,469 @@ +/* + * These functions implement tree of block devices. The devtree struct contains + * two basic lists: + * + * 1) devtree->devices -- This is simple list without any hierarchy. We use + * reference counting here. + * + * 2) devtree->roots -- The root nodes of the trees. The code does not use + * reference counting here due to complexity and it's unnecessary. + * + * Note that the same device maybe have more parents and more children. The + * device is allocated only once and shared within the tree. The dependence + * (devdep struct) contains reference to child as well as to parent and the + * dependence is reference by ls_childs from parent device and by ls_parents + * from child. (Yes, "childs" is used for children ;-) + * + * Copyright (C) 2018 Karel Zak <kzak@redhat.com> + */ +#include "lsblk.h" +#include "sysfs.h" + + +void lsblk_reset_iter(struct lsblk_iter *itr, int direction) +{ + if (direction == -1) + direction = itr->direction; + + memset(itr, 0, sizeof(*itr)); + itr->direction = direction; +} + +struct lsblk_device *lsblk_new_device() +{ + struct lsblk_device *dev; + + dev = calloc(1, sizeof(*dev)); + if (!dev) + return NULL; + + dev->refcount = 1; + dev->removable = -1; + dev->discard_granularity = (uint64_t) -1; + + INIT_LIST_HEAD(&dev->childs); + INIT_LIST_HEAD(&dev->parents); + INIT_LIST_HEAD(&dev->ls_roots); + INIT_LIST_HEAD(&dev->ls_devices); + + DBG(DEV, ul_debugobj(dev, "alloc")); + return dev; +} + +void lsblk_ref_device(struct lsblk_device *dev) +{ + if (dev) + dev->refcount++; +} + +/* removes dependence from child as well as from parent */ +static int remove_dependence(struct lsblk_devdep *dep) +{ + if (!dep) + return -EINVAL; + + DBG(DEP, ul_debugobj(dep, " dealloc")); + + list_del_init(&dep->ls_childs); + list_del_init(&dep->ls_parents); + + free(dep); + return 0; +} + +static int device_remove_dependences(struct lsblk_device *dev) +{ + if (!dev) + return -EINVAL; + + if (!list_empty(&dev->childs)) + DBG(DEV, ul_debugobj(dev, " %s: remove all children deps", dev->name)); + while (!list_empty(&dev->childs)) { + struct lsblk_devdep *dp = list_entry(dev->childs.next, + struct lsblk_devdep, ls_childs); + remove_dependence(dp); + } + + if (!list_empty(&dev->parents)) + DBG(DEV, ul_debugobj(dev, " %s: remove all parents deps", dev->name)); + while (!list_empty(&dev->parents)) { + struct lsblk_devdep *dp = list_entry(dev->parents.next, + struct lsblk_devdep, ls_parents); + remove_dependence(dp); + } + + return 0; +} + +void lsblk_unref_device(struct lsblk_device *dev) +{ + if (!dev) + return; + + if (--dev->refcount <= 0) { + DBG(DEV, ul_debugobj(dev, " freeing [%s] <<", dev->name)); + + device_remove_dependences(dev); + lsblk_device_free_properties(dev->properties); + + lsblk_unref_device(dev->wholedisk); + + free(dev->dm_name); + free(dev->filename); + free(dev->mountpoint); + free(dev->dedupkey); + + ul_unref_path(dev->sysfs); + + DBG(DEV, ul_debugobj(dev, " >> dealloc [%s]", dev->name)); + free(dev->name); + free(dev); + } +} + +int lsblk_device_has_child(struct lsblk_device *dev, struct lsblk_device *child) +{ + struct lsblk_device *x = NULL; + struct lsblk_iter itr; + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (lsblk_device_next_child(dev, &itr, &x) == 0) { + if (x == child) + return 1; + } + + return 0; +} + +int lsblk_device_new_dependence(struct lsblk_device *parent, struct lsblk_device *child) +{ + struct lsblk_devdep *dp; + + if (!parent || !child) + return -EINVAL; + + if (lsblk_device_has_child(parent, child)) + return 1; + + dp = calloc(1, sizeof(*dp)); + if (!dp) + return -ENOMEM; + + INIT_LIST_HEAD(&dp->ls_childs); + INIT_LIST_HEAD(&dp->ls_parents); + + dp->child = child; + list_add_tail(&dp->ls_childs, &parent->childs); + + dp->parent = parent; + list_add_tail(&dp->ls_parents, &child->parents); + + DBG(DEV, ul_debugobj(parent, "add dependence 0x%p [%s->%s]", dp, parent->name, child->name)); + + return 0; +} + +static int device_next_child(struct lsblk_device *dev, + struct lsblk_iter *itr, + struct lsblk_devdep **dp) +{ + int rc = 1; + + if (!dev || !itr || !dp) + return -EINVAL; + *dp = NULL; + + if (!itr->head) + LSBLK_ITER_INIT(itr, &dev->childs); + if (itr->p != itr->head) { + LSBLK_ITER_ITERATE(itr, *dp, struct lsblk_devdep, ls_childs); + rc = 0; + } + + return rc; +} + +int lsblk_device_next_child(struct lsblk_device *dev, + struct lsblk_iter *itr, + struct lsblk_device **child) +{ + struct lsblk_devdep *dp = NULL; + int rc = device_next_child(dev, itr, &dp); + + if (!child) + return -EINVAL; + + *child = rc == 0 ? dp->child : NULL; + return rc; +} + +int lsblk_device_is_last_parent(struct lsblk_device *dev, struct lsblk_device *parent) +{ + struct lsblk_devdep *dp = list_last_entry( + &dev->parents, + struct lsblk_devdep, ls_parents); + + return dp->parent == parent; +} + +int lsblk_device_next_parent( + struct lsblk_device *dev, + struct lsblk_iter *itr, + struct lsblk_device **parent) +{ + int rc = 1; + + if (!dev || !itr || !parent) + return -EINVAL; + *parent = NULL; + + if (!itr->head) + LSBLK_ITER_INIT(itr, &dev->parents); + if (itr->p != itr->head) { + struct lsblk_devdep *dp = NULL; + LSBLK_ITER_ITERATE(itr, dp, struct lsblk_devdep, ls_parents); + if (dp) + *parent = dp->parent; + rc = 0; + } + + return rc; +} + +struct lsblk_devtree *lsblk_new_devtree() +{ + struct lsblk_devtree *tr; + + tr = calloc(1, sizeof(*tr)); + if (!tr) + return NULL; + + tr->refcount = 1; + + INIT_LIST_HEAD(&tr->roots); + INIT_LIST_HEAD(&tr->devices); + + DBG(TREE, ul_debugobj(tr, "alloc")); + return tr; +} + +void lsblk_ref_devtree(struct lsblk_devtree *tr) +{ + if (tr) + tr->refcount++; +} + +void lsblk_unref_devtree(struct lsblk_devtree *tr) +{ + if (!tr) + return; + + if (--tr->refcount <= 0) { + DBG(TREE, ul_debugobj(tr, "dealloc")); + + while (!list_empty(&tr->devices)) { + struct lsblk_device *dev = list_entry(tr->devices.next, + struct lsblk_device, ls_devices); + lsblk_devtree_remove_device(tr, dev); + } + free(tr); + } +} + +int lsblk_devtree_add_root(struct lsblk_devtree *tr, struct lsblk_device *dev) +{ + if (!lsblk_devtree_has_device(tr, dev)) + lsblk_devtree_add_device(tr, dev); + + /* We don't increment reference counter for tr->roots list. The primary + * reference is tr->devices */ + + DBG(TREE, ul_debugobj(tr, "add root device 0x%p [%s]", dev, dev->name)); + list_add_tail(&dev->ls_roots, &tr->roots); + return 0; +} + +int lsblk_devtree_next_root(struct lsblk_devtree *tr, + struct lsblk_iter *itr, + struct lsblk_device **dev) +{ + int rc = 1; + + if (!tr || !itr || !dev) + return -EINVAL; + *dev = NULL; + if (!itr->head) + LSBLK_ITER_INIT(itr, &tr->roots); + if (itr->p != itr->head) { + LSBLK_ITER_ITERATE(itr, *dev, struct lsblk_device, ls_roots); + rc = 0; + } + return rc; +} + +int lsblk_devtree_add_device(struct lsblk_devtree *tr, struct lsblk_device *dev) +{ + lsblk_ref_device(dev); + + DBG(TREE, ul_debugobj(tr, "add device 0x%p [%s]", dev, dev->name)); + list_add_tail(&dev->ls_devices, &tr->devices); + return 0; +} + +int lsblk_devtree_next_device(struct lsblk_devtree *tr, + struct lsblk_iter *itr, + struct lsblk_device **dev) +{ + int rc = 1; + + if (!tr || !itr || !dev) + return -EINVAL; + *dev = NULL; + if (!itr->head) + LSBLK_ITER_INIT(itr, &tr->devices); + if (itr->p != itr->head) { + LSBLK_ITER_ITERATE(itr, *dev, struct lsblk_device, ls_devices); + rc = 0; + } + return rc; +} + +int lsblk_devtree_has_device(struct lsblk_devtree *tr, struct lsblk_device *dev) +{ + struct lsblk_device *x = NULL; + struct lsblk_iter itr; + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (lsblk_devtree_next_device(tr, &itr, &x) == 0) { + if (x == dev) + return 1; + } + + return 0; +} + +struct lsblk_device *lsblk_devtree_get_device(struct lsblk_devtree *tr, const char *name) +{ + struct lsblk_device *dev = NULL; + struct lsblk_iter itr; + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (lsblk_devtree_next_device(tr, &itr, &dev) == 0) { + if (strcmp(name, dev->name) == 0) + return dev; + } + + return NULL; +} + +int lsblk_devtree_remove_device(struct lsblk_devtree *tr, struct lsblk_device *dev) +{ + DBG(TREE, ul_debugobj(tr, "remove device 0x%p [%s]", dev, dev->name)); + + if (!lsblk_devtree_has_device(tr, dev)) + return 1; + + list_del_init(&dev->ls_roots); + list_del_init(&dev->ls_devices); + lsblk_unref_device(dev); + + return 0; +} + +static int device_dedupkey_is_equal( + struct lsblk_device *dev, + struct lsblk_device *pattern) +{ + assert(pattern->dedupkey); + + if (!dev->dedupkey || dev == pattern) + return 0; + if (strcmp(dev->dedupkey, pattern->dedupkey) == 0) { + if (!device_is_partition(dev) || + strcmp(dev->dedupkey, dev->wholedisk->dedupkey) != 0) { + DBG(DEV, ul_debugobj(dev, "%s: match deduplication pattern", dev->name)); + return 1; + } + } + return 0; +} + +static void device_dedup_dependencies( + struct lsblk_device *dev, + struct lsblk_device *pattern) +{ + struct lsblk_iter itr; + struct lsblk_devdep *dp; + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (device_next_child(dev, &itr, &dp) == 0) { + struct lsblk_device *child = dp->child; + + if (device_dedupkey_is_equal(child, pattern)) { + DBG(DEV, ul_debugobj(dev, "remove duplicate dependence: 0x%p [%s]", + dp->child, dp->child->name)); + remove_dependence(dp); + } else + device_dedup_dependencies(child, pattern); + } +} + +static void devtree_dedup(struct lsblk_devtree *tr, struct lsblk_device *pattern) +{ + struct lsblk_iter itr; + struct lsblk_device *dev = NULL; + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + DBG(TREE, ul_debugobj(tr, "de-duplicate by key: %s", pattern->dedupkey)); + + while (lsblk_devtree_next_root(tr, &itr, &dev) == 0) { + if (device_dedupkey_is_equal(dev, pattern)) { + DBG(TREE, ul_debugobj(tr, "remove duplicate device: 0x%p [%s]", + dev, dev->name)); + /* Note that root list does not use ref-counting; the + * primary reference is ls_devices */ + list_del_init(&dev->ls_roots); + } else + device_dedup_dependencies(dev, pattern); + } +} + +static int cmp_devices_devno(struct list_head *a, struct list_head *b, + __attribute__((__unused__)) void *data) +{ + struct lsblk_device *ax = list_entry(a, struct lsblk_device, ls_devices), + *bx = list_entry(b, struct lsblk_device, ls_devices); + + return cmp_numbers(makedev(ax->maj, ax->min), + makedev(bx->maj, bx->min)); +} + +/* Note that dev->dedupkey has to be already set */ +int lsblk_devtree_deduplicate_devices(struct lsblk_devtree *tr) +{ + struct lsblk_device *pattern = NULL; + struct lsblk_iter itr; + char *last = NULL; + + list_sort(&tr->devices, cmp_devices_devno, NULL); + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (lsblk_devtree_next_device(tr, &itr, &pattern) == 0) { + if (!pattern->dedupkey) + continue; + if (device_is_partition(pattern) && + strcmp(pattern->dedupkey, pattern->wholedisk->dedupkey) == 0) + continue; + if (last && strcmp(pattern->dedupkey, last) == 0) + continue; + + devtree_dedup(tr, pattern); + last = pattern->dedupkey; + } + return 0; +} diff --git a/misc-utils/lsblk-mnt.c b/misc-utils/lsblk-mnt.c index 890675ed5..c034e34bb 100644 --- a/misc-utils/lsblk-mnt.c +++ b/misc-utils/lsblk-mnt.c @@ -42,16 +42,16 @@ static int is_active_swap(const char *filename) return mnt_table_find_srcpath(swaps, filename, MNT_ITER_BACKWARD) != NULL; } -char *lsblk_device_get_mountpoint(struct blkdev_cxt *cxt) +char *lsblk_device_get_mountpoint(struct lsblk_device *dev) { struct libmnt_fs *fs; const char *fsroot; - assert(cxt); - assert(cxt->filename); + assert(dev); + assert(dev->filename); - if (cxt->is_mounted || cxt->is_swap) - return cxt->mountpoint; + if (dev->is_mounted || dev->is_swap) + return dev->mountpoint; if (!mtab) { mtab = mnt_new_table(); @@ -75,17 +75,17 @@ char *lsblk_device_get_mountpoint(struct blkdev_cxt *cxt) /* Note that maj:min in /proc/self/mountinfo does not have to match with * devno as returned by stat(), so we have to try devname too */ - fs = mnt_table_find_devno(mtab, makedev(cxt->maj, cxt->min), MNT_ITER_BACKWARD); + fs = mnt_table_find_devno(mtab, makedev(dev->maj, dev->min), MNT_ITER_BACKWARD); if (!fs) - fs = mnt_table_find_srcpath(mtab, cxt->filename, MNT_ITER_BACKWARD); + fs = mnt_table_find_srcpath(mtab, dev->filename, MNT_ITER_BACKWARD); if (!fs) { - if (is_active_swap(cxt->filename)) { - cxt->mountpoint = xstrdup("[SWAP]"); - cxt->is_swap = 1; + if (is_active_swap(dev->filename)) { + dev->mountpoint = xstrdup("[SWAP]"); + dev->is_swap = 1; } else - cxt->mountpoint = NULL; + dev->mountpoint = NULL; - return cxt->mountpoint; + return dev->mountpoint; } /* found */ @@ -100,7 +100,7 @@ char *lsblk_device_get_mountpoint(struct blkdev_cxt *cxt) while (mnt_table_next_fs(mtab, itr, &rfs) == 0) { fsroot = mnt_fs_get_root(rfs); if ((!fsroot || strcmp(fsroot, "/") == 0) - && mnt_fs_match_source(rfs, cxt->filename, mntcache)) { + && mnt_fs_match_source(rfs, dev->filename, mntcache)) { fs = rfs; break; } @@ -108,10 +108,10 @@ char *lsblk_device_get_mountpoint(struct blkdev_cxt *cxt) mnt_free_iter(itr); } - DBG(DEV, ul_debugobj(cxt, "mountpoint: %s", mnt_fs_get_target(fs))); - cxt->mountpoint = xstrdup(mnt_fs_get_target(fs)); - cxt->is_mounted = 1; - return cxt->mountpoint; + DBG(DEV, ul_debugobj(dev, "mountpoint: %s", mnt_fs_get_target(fs))); + dev->mountpoint = xstrdup(mnt_fs_get_target(fs)); + dev->is_mounted = 1; + return dev->mountpoint; } void lsblk_mnt_init(void) diff --git a/misc-utils/lsblk-properties.c b/misc-utils/lsblk-properties.c index 10a9846e5..30168dd48 100644 --- a/misc-utils/lsblk-properties.c +++ b/misc-utils/lsblk-properties.c @@ -36,18 +36,18 @@ void lsblk_device_free_properties(struct lsblk_devprop *p) } #ifndef HAVE_LIBUDEV -static struct lsblk_devprop *get_properties_by_udev(struct blkdev_cxt *cxt +static struct lsblk_devprop *get_properties_by_udev(struct lsblk_device *dev __attribute__((__unused__))) { return NULL; } #else -static struct lsblk_devprop *get_properties_by_udev(struct blkdev_cxt *cxt) +static struct lsblk_devprop *get_properties_by_udev(struct lsblk_device *ld) { struct udev_device *dev; - if (cxt->udev_requested) - return cxt->properties; + if (ld->udev_requested) + return ld->properties; if (lsblk->sysroot) goto done; @@ -56,14 +56,14 @@ static struct lsblk_devprop *get_properties_by_udev(struct blkdev_cxt *cxt) if (!udev) goto done; - dev = udev_device_new_from_subsystem_sysname(udev, "block", cxt->name); + dev = udev_device_new_from_subsystem_sysname(udev, "block", ld->name); if (dev) { const char *data; struct lsblk_devprop *prop; - if (cxt->properties) - lsblk_device_free_properties(cxt->properties); - prop = cxt->properties = xcalloc(1, sizeof(*cxt->properties)); + if (ld->properties) + lsblk_device_free_properties(ld->properties); + prop = ld->properties = xcalloc(1, sizeof(*ld->properties)); if ((data = udev_device_get_property_value(dev, "ID_FS_LABEL_ENC"))) { prop->label = xstrdup(data); @@ -102,28 +102,28 @@ static struct lsblk_devprop *get_properties_by_udev(struct blkdev_cxt *cxt) prop->model = xstrdup(data); udev_device_unref(dev); - DBG(DEV, ul_debugobj(cxt, "%s: found udev properties", cxt->name)); + DBG(DEV, ul_debugobj(ld, "%s: found udev properties", ld->name)); } done: - cxt->udev_requested = 1; - return cxt->properties; + ld->udev_requested = 1; + return ld->properties; } #endif /* HAVE_LIBUDEV */ -static struct lsblk_devprop *get_properties_by_blkid(struct blkdev_cxt *cxt) +static struct lsblk_devprop *get_properties_by_blkid(struct lsblk_device *dev) { blkid_probe pr = NULL; - if (cxt->blkid_requested) - return cxt->properties; + if (dev->blkid_requested) + return dev->properties; - if (!cxt->size) + if (!dev->size) goto done; if (getuid() != 0) goto done;; /* no permissions to read from the device */ - pr = blkid_new_probe_from_filename(cxt->filename); + pr = blkid_new_probe_from_filename(dev->filename); if (!pr) goto done; @@ -138,9 +138,9 @@ static struct lsblk_devprop *get_properties_by_blkid(struct blkdev_cxt *cxt) const char *data = NULL; struct lsblk_devprop *prop; - if (cxt->properties) - lsblk_device_free_properties(cxt->properties); - prop = cxt->properties = xcalloc(1, sizeof(*cxt->properties)); + if (dev->properties) + lsblk_device_free_properties(dev->properties); + prop = dev->properties = xcalloc(1, sizeof(*dev->properties)); if (!blkid_probe_lookup_value(pr, "TYPE", &data, NULL)) prop->fstype = xstrdup(data); @@ -161,22 +161,22 @@ static struct lsblk_devprop *get_properties_by_blkid(struct blkdev_cxt *cxt) if (!blkid_probe_lookup_value(pr, "PART_ENTRY_FLAGS", &data, NULL)) prop->partflags = xstrdup(data); - DBG(DEV, ul_debugobj(cxt, "%s: found blkid properties", cxt->name)); + DBG(DEV, ul_debugobj(dev, "%s: found blkid properties", dev->name)); } done: blkid_free_probe(pr); - cxt->blkid_requested = 1; - return cxt->properties; + dev->blkid_requested = 1; + return dev->properties; } -struct lsblk_devprop *lsblk_device_get_properties(struct blkdev_cxt *cxt) +struct lsblk_devprop *lsblk_device_get_properties(struct lsblk_device *dev) { - struct lsblk_devprop *p = get_properties_by_udev(cxt); + struct lsblk_devprop *p = get_properties_by_udev(dev); if (!p) - p = get_properties_by_blkid(cxt); + p = get_properties_by_blkid(dev); return p; } diff --git a/misc-utils/lsblk.8 b/misc-utils/lsblk.8 index 7cc2788db..2787569fe 100644 --- a/misc-utils/lsblk.8 +++ b/misc-utils/lsblk.8 @@ -51,13 +51,18 @@ Print the SIZE column in bytes rather than in a human-readable format. .BR \-D , " \-\-discard" Print information about the discarding capabilities (TRIM, UNMAP) for each device. .TP -.BR \-z , " \-\-zoned" -Print the zone model for each device. -.TP .BR \-d , " \-\-nodeps" Do not print holder devices or slaves. For example, \fBlsblk --nodeps /dev/sda\fR prints information about the sda device only. .TP +.BR \-E , " \-\-dedup " \fIcolumn\fP +Use \fIcolumn\fP as a de-duplication key to de-duplicate output tree. If the +key is not available for the device, or the device is a partition and parental +whole-disk device provides the same key than the device is always printed. + +The usual use case is to de-duplicate output on system multi-path devices, for +example by \fB\-E WWN\fR. +.TP .BR \-e , " \-\-exclude " \fIlist\fP Exclude the devices specified by the comma-separated \fIlist\fR of major device numbers. Note that RAM disks (major=1) are excluded by default if \fB\-\-all\fR is no specified. @@ -86,7 +91,13 @@ Use ASCII characters for tree formatting. Use JSON output format. .TP .BR \-l , " \-\-list" -Produce output in the form of a list. +Produce output in the form of a list. The output does not provide information +about relationships between devices and since version 2.34 every device is +printed only once. +.TP +.BR \-M , " \-\-merge" +Group parents of sub-trees to provide more readable output for RAIDs and +Multi-path devices. The tree-like output is required. .TP .BR \-m , " \-\-perms" Output info about device owner, group and mode. This option is equivalent to @@ -137,6 +148,9 @@ Sort output lines by \fIcolumn\fP. This option enables \fB\-\-list\fR output for It is possible to use the option \fI\-\-tree\fP to force tree-like output and than the tree branches are sorted by the \fIcolumn\fP. .TP +.BR \-z , " \-\-zoned" +Print the zone model for each device. +.TP .BR " \-\-sysroot " \fIdirectory\fP Gather data for a Linux instance other than the instance from which the lsblk command is issued. The specified directory is the system root of the Linux diff --git a/misc-utils/lsblk.c b/misc-utils/lsblk.c index d63b65a3a..7ee6dff90 100644 --- a/misc-utils/lsblk.c +++ b/misc-utils/lsblk.c @@ -1,7 +1,7 @@ /* * lsblk(8) - list block devices * - * Copyright (C) 2010,2011,2012 Red Hat, Inc. All rights reserved. + * Copyright (C) 2010-2018 Red Hat, Inc. All rights reserved. * Written by Milan Broz <mbroz@redhat.com> * Karel Zak <kzak@redhat.com> * @@ -19,7 +19,6 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - #include <stdio.h> #include <errno.h> #include <getopt.h> @@ -142,7 +141,6 @@ struct colinfo { double whint; /* width hint (N < 1 is in percent of termwidth) */ int flags; /* SCOLS_FL_* */ const char *help; - int type; /* COLTYPE_* */ }; @@ -209,14 +207,15 @@ static struct colinfo infos[] = { struct lsblk *lsblk; /* global handler */ -/* columns[] array specifies all currently wanted output column. The columns +/* + * columns[] array specifies all currently wanted output column. The columns * are defined by infos[] array and you can specify (on command line) each * column twice. That's enough, dynamically allocated array of the columns is - * unnecessary overkill and over-engineering in this case */ + * unnecessary overkill and over-engineering in this case + */ static int columns[ARRAY_SIZE(infos) * 2]; static size_t ncolumns; - static inline void add_column(int id) { if (ncolumns >= ARRAY_SIZE(columns)) @@ -232,17 +231,20 @@ static inline void add_uniq_column(int id) add_column(id); } +static void lsblk_init_debug(void) +{ + __UL_INIT_DEBUG_FROM_ENV(lsblk, LSBLK_DEBUG_, 0, LSBLK_DEBUG); +} + +/* + * exclude/include devices filter based on major device numbers + */ static int excludes[256]; static size_t nexcludes; static int includes[256]; static size_t nincludes; -static void lsblk_init_debug(void) -{ - __UL_INIT_DEBUG_FROM_ENV(lsblk, LSBLK_DEBUG_, 0, LSBLK_DEBUG); -} - static int is_maj_excluded(int maj) { size_t i; @@ -279,7 +281,7 @@ static int is_maj_included(int maj) return 0; } -/* array with IDs of enabled columns */ +/* Converts column sequential number to column ID (COL_*) */ static int get_column_id(int num) { assert(num >= 0); @@ -288,11 +290,13 @@ static int get_column_id(int num) return columns[num]; } +/* Returns column description for the column sequential number */ static struct colinfo *get_column_info(int num) { return &infos[ get_column_id(num) ]; } +/* Converts column name (as defined in the infos[] to the column ID */ static int column_name_to_id(const char *name, size_t namesz) { size_t i; @@ -307,6 +311,7 @@ static int column_name_to_id(const char *name, size_t namesz) return -1; } +/* Converts column ID (COL_*) to column sequential number */ static int column_id_to_number(int id) { size_t i; @@ -317,29 +322,13 @@ static int column_id_to_number(int id) return -1; } -static void reset_blkdev_cxt(struct blkdev_cxt *cxt) -{ - if (!cxt) - return; - - DBG(CXT, ul_debugobj(cxt, "reset")); - - free(cxt->name); - free(cxt->dm_name); - free(cxt->filename); - free(cxt->mountpoint); - - lsblk_device_free_properties(cxt->properties); - ul_unref_path(cxt->sysfs); - - memset(cxt, 0, sizeof(*cxt)); -} - +/* Checks for DM prefix in the device name */ static int is_dm(const char *name) { return strncmp(name, "dm-", 3) ? 0 : 1; } +/* This is readdir()-like function, but skips "." and ".." directory entries */ static struct dirent *xreaddir(DIR *dp) { struct dirent *d; @@ -357,30 +346,31 @@ static struct dirent *xreaddir(DIR *dp) return d; } -static char *get_device_path(struct blkdev_cxt *cxt) +/* Returns full pat to the device node (TODO: what about sysfs_blkdev_get_path()) */ +static char *get_device_path(struct lsblk_device *dev) { char path[PATH_MAX]; - assert(cxt); - assert(cxt->name); + assert(dev); + assert(dev->name); - if (is_dm(cxt->name)) - return __canonicalize_dm_name(lsblk->sysroot, cxt->name); + if (is_dm(dev->name)) + return __canonicalize_dm_name(lsblk->sysroot, dev->name); - snprintf(path, sizeof(path), "/dev/%s", cxt->name); + snprintf(path, sizeof(path), "/dev/%s", dev->name); sysfs_devname_sys_to_dev(path); return xstrdup(path); } -static int is_readonly_device(struct blkdev_cxt *cxt) +static int is_readonly_device(struct lsblk_device *dev) { int fd, ro = 0; - if (ul_path_scanf(cxt->sysfs, "ro", "%d", &ro) == 1) + if (ul_path_scanf(dev->sysfs, "ro", "%d", &ro) == 1) return ro; /* fallback if "ro" attribute does not exist */ - fd = open(cxt->filename, O_RDONLY); + fd = open(dev->filename, O_RDONLY); if (fd != -1) { if (ioctl(fd, BLKROGET, &ro) != 0) ro = 0; @@ -389,12 +379,12 @@ static int is_readonly_device(struct blkdev_cxt *cxt) return ro; } -static char *get_scheduler(struct blkdev_cxt *cxt) +static char *get_scheduler(struct lsblk_device *dev) { char buf[128]; char *p, *res = NULL; - if (ul_path_read_buffer(cxt->sysfs, buf, sizeof(buf), "queue/scheduler") == 0) + if (ul_path_read_buffer(dev->sysfs, buf, sizeof(buf), "queue/scheduler") == 0) return NULL; p = strchr(buf, '['); if (p) { @@ -409,19 +399,19 @@ static char *get_scheduler(struct blkdev_cxt *cxt) return res; } -static char *get_type(struct blkdev_cxt *cxt) +static char *get_type(struct lsblk_device *dev) { char *res = NULL, *p; - if (cxt->partition) + if (device_is_partition(dev)) return xstrdup("part"); - if (is_dm(cxt->name)) { + if (is_dm(dev->name)) { char *dm_uuid = NULL; /* The DM_UUID prefix should be set to subsystem owning * the device - LVM, CRYPT, DMRAID, MPATH, PART */ - if (ul_path_read_string(cxt->sysfs, &dm_uuid, "dm/uuid") > 0 + if (ul_path_read_string(dev->sysfs, &dm_uuid, "dm/uuid") > 0 && dm_uuid) { char *tmp = dm_uuid; char *dm_uuid_prefix = strsep(&tmp, "-"); @@ -440,20 +430,20 @@ static char *get_type(struct blkdev_cxt *cxt) /* No UUID or no prefix - just mark it as DM device */ res = xstrdup("dm"); - } else if (!strncmp(cxt->name, "loop", 4)) { + } else if (!strncmp(dev->name, "loop", 4)) { res = xstrdup("loop"); - } else if (!strncmp(cxt->name, "md", 2)) { + } else if (!strncmp(dev->name, "md", 2)) { char *md_level = NULL; - ul_path_read_string(cxt->sysfs, &md_level, "md/level"); + ul_path_read_string(dev->sysfs, &md_level, "md/level"); res = md_level ? md_level : xstrdup("md"); } else { const char *type = NULL; int x = 0; - if (ul_path_read_s32(cxt->sysfs, &x, "device/type") == 0) + if (ul_path_read_s32(dev->sysfs, &x, "device/type") == 0) type = blkdev_scsi_type_to_name(x); if (!type) type = "disk"; @@ -466,9 +456,9 @@ static char *get_type(struct blkdev_cxt *cxt) } /* Thanks to lsscsi code for idea of detection logic used here */ -static char *get_transport(struct blkdev_cxt *cxt) +static char *get_transport(struct lsblk_device *dev) { - struct path_cxt *sysfs = cxt->sysfs; + struct path_cxt *sysfs = dev->sysfs; char *attr = NULL; const char *trans = NULL; @@ -515,23 +505,23 @@ static char *get_transport(struct blkdev_cxt *cxt) trans = "ata"; free(attr); - } else if (strncmp(cxt->name, "nvme", 4) == 0) + } else if (strncmp(dev->name, "nvme", 4) == 0) trans = "nvme"; return trans ? xstrdup(trans) : NULL; } -static char *get_subsystems(struct blkdev_cxt *cxt) +static char *get_subsystems(struct lsblk_device *dev) { char path[PATH_MAX]; char *sub, *chain, *res = NULL; size_t len = 0, last = 0; - chain = sysfs_blkdev_get_devchain(cxt->sysfs, path, sizeof(path)); + chain = sysfs_blkdev_get_devchain(dev->sysfs, path, sizeof(path)); if (!chain) return NULL; - while (sysfs_blkdev_next_subsystem(cxt->sysfs, chain, &sub) == 0) { + while (sysfs_blkdev_next_subsystem(dev->sysfs, chain, &sub) == 0) { size_t sz; /* don't create "block:scsi:scsi", but "block:scsi" */ @@ -600,14 +590,20 @@ static void set_sortdata_u64(struct libscols_line *ln, int col, uint64_t x) scols_cell_set_userdata(ce, data); } -static void set_sortdata_u64_from_string(struct libscols_line *ln, int col, const char *str) +/* do not modify *data on any error */ +static void str2u64(const char *str, uint64_t *data) { - uint64_t x; + uintmax_t num; + char *end = NULL; - if (!str || sscanf(str, "%"SCNu64, &x) != 1) + errno = 0; + if (str == NULL || *str == '\0') return; + num = strtoumax(str, &end, 10); - set_sortdata_u64(ln, col, x); + if (errno || str == end || (end && *end)) + return; + *data = num; } static void unref_sortdata(struct libscols_table *tb) @@ -630,37 +626,37 @@ static void unref_sortdata(struct libscols_table *tb) scols_free_iter(itr); } -static char *get_vfs_attribute(struct blkdev_cxt *cxt, int id) +static char *get_vfs_attribute(struct lsblk_device *dev, int id) { char *sizestr; uint64_t vfs_attr = 0; char *mnt; - if (!cxt->fsstat.f_blocks) { - mnt = lsblk_device_get_mountpoint(cxt); - if (!mnt || cxt->is_swap) + if (!dev->fsstat.f_blocks) { + mnt = lsblk_device_get_mountpoint(dev); + if (!mnt || dev->is_swap) return NULL; - if (statvfs(mnt, &cxt->fsstat) != 0) + if (statvfs(mnt, &dev->fsstat) != 0) return NULL; } switch(id) { case COL_FSSIZE: - vfs_attr = cxt->fsstat.f_frsize * cxt->fsstat.f_blocks; + vfs_attr = dev->fsstat.f_frsize * dev->fsstat.f_blocks; break; case COL_FSAVAIL: - vfs_attr = cxt->fsstat.f_frsize * cxt->fsstat.f_bavail; + vfs_attr = dev->fsstat.f_frsize * dev->fsstat.f_bavail; break; case COL_FSUSED: - vfs_attr = cxt->fsstat.f_frsize * (cxt->fsstat.f_blocks - cxt->fsstat.f_bfree); + vfs_attr = dev->fsstat.f_frsize * (dev->fsstat.f_blocks - dev->fsstat.f_bfree); break; case COL_FSUSEPERC: - if (cxt->fsstat.f_blocks == 0) + if (dev->fsstat.f_blocks == 0) return xstrdup("-"); xasprintf(&sizestr, "%.0f%%", - (double)(cxt->fsstat.f_blocks - cxt->fsstat.f_bfree) / - cxt->fsstat.f_blocks * 100); + (double)(dev->fsstat.f_blocks - dev->fsstat.f_bfree) / + dev->fsstat.f_blocks * 100); return sizestr; } @@ -674,41 +670,83 @@ static char *get_vfs_attribute(struct blkdev_cxt *cxt, int id) return sizestr; } -static struct stat *device_get_stat(struct blkdev_cxt *cxt) +static struct stat *device_get_stat(struct lsblk_device *dev) { - if (!cxt->st.st_rdev) - stat(cxt->filename, &cxt->st); + if (!dev->st.st_rdev) + stat(dev->filename, &dev->st); - return &cxt->st; + return &dev->st; } -static void set_scols_data(struct blkdev_cxt *cxt, int col, int id, struct libscols_line *ln) +static int is_removable_device(struct lsblk_device *dev, struct lsblk_device *parent) +{ + struct path_cxt *pc; + + if (dev->removable != -1) + goto done; + if (ul_path_scanf(dev->sysfs, "removable", "%d", &dev->removable) == 1) + goto done; + + if (parent) { + pc = sysfs_blkdev_get_parent(dev->sysfs); + if (!pc) + goto done; + + if (pc == parent->sysfs) + /* dev is partition and parent is whole-disk */ + dev->removable = is_removable_device(parent, NULL); + else + /* parent is something else, use sysfs parent */ + ul_path_scanf(pc, "removable", "%d", &dev->removable); + } +done: + if (dev->removable == -1) + dev->removable = 0; + return dev->removable; +} + +static uint64_t device_get_discard_granularity(struct lsblk_device *dev) +{ + if (dev->discard_granularity == (uint64_t) -1 + && ul_path_read_u64(dev->sysfs, &dev->discard_granularity, + "queue/discard_granularity") != 0) + dev->discard_granularity = 0; + + return dev->discard_granularity; +} + +/* + * Generates data (string) for column specified by column ID for specified device. If sortdata + * is not NULL then returns number usable to sort the column if the data are available for the + * column. + */ +static char *device_get_data( + struct lsblk_device *dev, /* device */ + struct lsblk_device *parent, /* device parent as defined in the tree */ + int id, /* column ID (COL_*) */ + uint64_t *sortdata) /* returns sort data as number */ { struct lsblk_devprop *prop; - int sort = 0; char *str = NULL; - 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); + str = dev->dm_name ? mk_dm_name(dev->dm_name) : mk_name(dev->name); break; case COL_KNAME: - str = mk_name(cxt->name); + str = mk_name(dev->name); break; case COL_PKNAME: - if (cxt->parent) - str = mk_name(cxt->parent->name); + if (parent) + str = mk_name(parent->name); break; case COL_PATH: - if (cxt->filename) - str = xstrdup(cxt->filename); + if (dev->filename) + str = xstrdup(dev->filename); break; case COL_OWNER: { - struct stat *st = device_get_stat(cxt); + struct stat *st = device_get_stat(dev); struct passwd *pw = st ? NULL : getpwuid(st->st_uid); if (pw) str = xstrdup(pw->pw_name); @@ -716,7 +754,7 @@ static void set_scols_data(struct blkdev_cxt *cxt, int col, int id, struct libsc } case COL_GROUP: { - struct stat *st = device_get_stat(cxt); + struct stat *st = device_get_stat(dev); struct group *gr = st ? NULL : getgrgid(st->st_gid); if (gr) str = xstrdup(gr->gr_name); @@ -724,7 +762,7 @@ static void set_scols_data(struct blkdev_cxt *cxt, int col, int id, struct libsc } case COL_MODE: { - struct stat *st = device_get_stat(cxt); + struct stat *st = device_get_stat(dev); char md[11] = { '\0' }; if (st) @@ -733,14 +771,14 @@ static void set_scols_data(struct blkdev_cxt *cxt, int col, int id, struct libsc } case COL_MAJMIN: if (is_parsable(lsblk)) - xasprintf(&str, "%u:%u", cxt->maj, cxt->min); + xasprintf(&str, "%u:%u", dev->maj, dev->min); else - xasprintf(&str, "%3u:%-3u", cxt->maj, cxt->min); - if (sort) - set_sortdata_u64(ln, col, makedev(cxt->maj, cxt->min)); + xasprintf(&str, "%3u:%-3u", dev->maj, dev->min); + if (sortdata) + *sortdata = makedev(dev->maj, dev->min); break; case COL_FSTYPE: - prop = lsblk_device_get_properties(cxt); + prop = lsblk_device_get_properties(dev); if (prop && prop->fstype) str = xstrdup(prop->fstype); break; @@ -748,575 +786,635 @@ static void set_scols_data(struct blkdev_cxt *cxt, int col, int id, struct libsc case COL_FSAVAIL: case COL_FSUSED: case COL_FSUSEPERC: - str = get_vfs_attribute(cxt, id); + str = get_vfs_attribute(dev, id); break; case COL_TARGET: - str = xstrdup(lsblk_device_get_mountpoint(cxt)); + str = xstrdup(lsblk_device_get_mountpoint(dev)); break; case COL_LABEL: - prop = lsblk_device_get_properties(cxt); + prop = lsblk_device_get_properties(dev); if (prop && prop->label) str = xstrdup(prop->label); break; case COL_UUID: - prop = lsblk_device_get_properties(cxt); + prop = lsblk_device_get_properties(dev); if (prop && prop->uuid) str = xstrdup(prop->uuid); break; case COL_PTUUID: - prop = lsblk_device_get_properties(cxt); + prop = lsblk_device_get_properties(dev); if (prop && prop->ptuuid) str = xstrdup(prop->ptuuid); break; case COL_PTTYPE: - prop = lsblk_device_get_properties(cxt); + prop = lsblk_device_get_properties(dev); if (prop && prop->pttype) str = xstrdup(prop->pttype); break; case COL_PARTTYPE: - prop = lsblk_device_get_properties(cxt); + prop = lsblk_device_get_properties(dev); if (prop && prop->parttype) str = xstrdup(prop->parttype); break; case COL_PARTLABEL: - prop = lsblk_device_get_properties(cxt); + prop = lsblk_device_get_properties(dev); if (prop && prop->partlabel) str = xstrdup(prop->partlabel); break; case COL_PARTUUID: - prop = lsblk_device_get_properties(cxt); + prop = lsblk_device_get_properties(dev); if (prop && prop->partuuid) str = xstrdup(prop->partuuid); break; case COL_PARTFLAGS: - prop = lsblk_device_get_properties(cxt); + prop = lsblk_device_get_properties(dev); if (prop && prop->partflags) str = xstrdup(prop->partflags); break; case COL_WWN: - prop = lsblk_device_get_properties(cxt); + prop = lsblk_device_get_properties(dev); if (prop && prop->wwn) str = xstrdup(prop->wwn); break; case COL_RA: - ul_path_read_string(cxt->sysfs, &str, "queue/read_ahead_kb"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + ul_path_read_string(dev->sysfs, &str, "queue/read_ahead_kb"); + if (sortdata) + str2u64(str, sortdata); break; case COL_RO: - str = xstrdup(is_readonly_device(cxt) ? "1" : "0"); + str = xstrdup(is_readonly_device(dev) ? "1" : "0"); break; case COL_RM: - ul_path_read_string(cxt->sysfs, &str, "removable"); - if (!str && sysfs_blkdev_get_parent(cxt->sysfs)) - ul_path_read_string(sysfs_blkdev_get_parent(cxt->sysfs), - &str, - "removable"); + str = xstrdup(is_removable_device(dev, parent) ? "1" : "0"); break; case COL_HOTPLUG: - str = sysfs_blkdev_is_hotpluggable(cxt->sysfs) ? xstrdup("1") : xstrdup("0"); + str = sysfs_blkdev_is_hotpluggable(dev->sysfs) ? xstrdup("1") : xstrdup("0"); break; case COL_ROTA: - ul_path_read_string(cxt->sysfs, &str, "queue/rotational"); + ul_path_read_string(dev->sysfs, &str, "queue/rotational"); break; case COL_RAND: - ul_path_read_string(cxt->sysfs, &str, "queue/add_random"); + ul_path_read_string(dev->sysfs, &str, "queue/add_random"); break; case COL_MODEL: - if (!cxt->partition && cxt->nslaves == 0) { - prop = lsblk_device_get_properties(cxt); + if (!device_is_partition(dev) && dev->nslaves == 0) { + prop = lsblk_device_get_properties(dev); if (prop && prop->model) str = xstrdup(prop->model); else - ul_path_read_string(cxt->sysfs, &str, "device/model"); + ul_path_read_string(dev->sysfs, &str, "device/model"); } break; case COL_SERIAL: - if (!cxt->partition && cxt->nslaves == 0) { - prop = lsblk_device_get_properties(cxt); + if (!device_is_partition(dev) && dev->nslaves == 0) { + prop = lsblk_device_get_properties(dev); if (prop && prop->serial) str = xstrdup(prop->serial); else - ul_path_read_string(cxt->sysfs, &str, "device/serial"); + ul_path_read_string(dev->sysfs, &str, "device/serial"); } break; case COL_REV: - if (!cxt->partition && cxt->nslaves == 0) - ul_path_read_string(cxt->sysfs, &str, "device/rev"); + if (!device_is_partition(dev) && dev->nslaves == 0) + ul_path_read_string(dev->sysfs, &str, "device/rev"); break; case COL_VENDOR: - if (!cxt->partition && cxt->nslaves == 0) - ul_path_read_string(cxt->sysfs, &str, "device/vendor"); + if (!device_is_partition(dev) && dev->nslaves == 0) + ul_path_read_string(dev->sysfs, &str, "device/vendor"); break; case COL_SIZE: - if (!cxt->size) + if (!dev->size) break; if (lsblk->bytes) - xasprintf(&str, "%ju", cxt->size); + xasprintf(&str, "%ju", dev->size); else - str = size_to_human_string(SIZE_SUFFIX_1LETTER, cxt->size); - if (sort) - set_sortdata_u64(ln, col, cxt->size); + str = size_to_human_string(SIZE_SUFFIX_1LETTER, dev->size); + if (sortdata) + *sortdata = dev->size; break; case COL_STATE: - if (!cxt->partition && !cxt->dm_name) - ul_path_read_string(cxt->sysfs, &str, "device/state"); - else if (cxt->dm_name) { + if (!device_is_partition(dev) && !dev->dm_name) + ul_path_read_string(dev->sysfs, &str, "device/state"); + else if (dev->dm_name) { int x = 0; - if (ul_path_read_s32(cxt->sysfs, &x, "dm/suspended") == 0) + if (ul_path_read_s32(dev->sysfs, &x, "dm/suspended") == 0) str = xstrdup(x ? "suspended" : "running"); } break; case COL_ALIOFF: - ul_path_read_string(cxt->sysfs, &str, "alignment_offset"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + ul_path_read_string(dev->sysfs, &str, "alignment_offset"); + if (sortdata) + str2u64(str, sortdata); break; case COL_MINIO: - ul_path_read_string(cxt->sysfs, &str, "queue/minimum_io_size"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + ul_path_read_string(dev->sysfs, &str, "queue/minimum_io_size"); + if (sortdata) + str2u64(str, sortdata); break; case COL_OPTIO: - ul_path_read_string(cxt->sysfs, &str, "queue/optimal_io_size"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + ul_path_read_string(dev->sysfs, &str, "queue/optimal_io_size"); + if (sortdata) + str2u64(str, sortdata); break; case COL_PHYSEC: - ul_path_read_string(cxt->sysfs, &str, "queue/physical_block_size"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + ul_path_read_string(dev->sysfs, &str, "queue/physical_block_size"); + if (sortdata) + str2u64(str, sortdata); break; case COL_LOGSEC: - ul_path_read_string(cxt->sysfs, &str, "queue/logical_block_size"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + ul_path_read_string(dev->sysfs, &str, "queue/logical_block_size"); + if (sortdata) + str2u64(str, sortdata); break; case COL_SCHED: - str = get_scheduler(cxt); + str = get_scheduler(dev); break; case COL_RQ_SIZE: - ul_path_read_string(cxt->sysfs, &str, "queue/nr_requests"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + ul_path_read_string(dev->sysfs, &str, "queue/nr_requests"); + if (sortdata) + str2u64(str, sortdata); break; case COL_TYPE: - str = get_type(cxt); + str = get_type(dev); break; case COL_HCTL: { int h, c, t, l; - if (sysfs_blkdev_scsi_get_hctl(cxt->sysfs, &h, &c, &t, &l) == 0) + if (sysfs_blkdev_scsi_get_hctl(dev->sysfs, &h, &c, &t, &l) == 0) xasprintf(&str, "%d:%d:%d:%d", h, c, t, l); break; } case COL_TRANSPORT: - str = get_transport(cxt); + str = get_transport(dev); break; case COL_SUBSYS: - str = get_subsystems(cxt); + str = get_subsystems(dev); break; case COL_DALIGN: - if (cxt->discard) - ul_path_read_string(cxt->sysfs, &str, "discard_alignment"); + if (device_get_discard_granularity(dev) > 0) + ul_path_read_string(dev->sysfs, &str, "discard_alignment"); if (!str) str = xstrdup("0"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + if (sortdata) + str2u64(str, sortdata); break; case COL_DGRAN: if (lsblk->bytes) { - ul_path_read_string(cxt->sysfs, &str, "queue/discard_granularity"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + ul_path_read_string(dev->sysfs, &str, "queue/discard_granularity"); + if (sortdata) + str2u64(str, sortdata); } else { - uint64_t x; - if (ul_path_read_u64(cxt->sysfs, &x, "queue/discard_granularity") == 0) { - str = size_to_human_string(SIZE_SUFFIX_1LETTER, x); - if (sort) - set_sortdata_u64(ln, col, x); - } + uint64_t x = device_get_discard_granularity(dev); + str = size_to_human_string(SIZE_SUFFIX_1LETTER, x); + if (sortdata) + *sortdata = x; } break; case COL_DMAX: if (lsblk->bytes) { - ul_path_read_string(cxt->sysfs, &str, "queue/discard_max_bytes"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + ul_path_read_string(dev->sysfs, &str, "queue/discard_max_bytes"); + if (sortdata) + str2u64(str, sortdata); } else { uint64_t x; - if (ul_path_read_u64(cxt->sysfs, &x, "queue/discard_max_bytes") == 0) { + if (ul_path_read_u64(dev->sysfs, &x, "queue/discard_max_bytes") == 0) { str = size_to_human_string(SIZE_SUFFIX_1LETTER, x); - if (sort) - set_sortdata_u64(ln, col, x); + if (sortdata) + *sortdata = x; } } break; case COL_DZERO: - if (cxt->discard) - ul_path_read_string(cxt->sysfs, &str, "queue/discard_zeroes_data"); + if (device_get_discard_granularity(dev) > 0) + ul_path_read_string(dev->sysfs, &str, "queue/discard_zeroes_data"); if (!str) str = xstrdup("0"); break; case COL_WSAME: if (lsblk->bytes) { - ul_path_read_string(cxt->sysfs, &str, "queue/write_same_max_bytes"); - if (sort) - set_sortdata_u64_from_string(ln, col, str); + ul_path_read_string(dev->sysfs, &str, "queue/write_same_max_bytes"); + if (sortdata) + str2u64(str, sortdata); } else { uint64_t x; - if (ul_path_read_u64(cxt->sysfs, &x, "queue/write_same_max_bytes") == 0) { + if (ul_path_read_u64(dev->sysfs, &x, "queue/write_same_max_bytes") == 0) { str = size_to_human_string(SIZE_SUFFIX_1LETTER, x); - if (sort) - set_sortdata_u64(ln, col, x); + if (sortdata) + *sortdata = x; } } if (!str) str = xstrdup("0"); break; case COL_ZONED: - ul_path_read_string(cxt->sysfs, &str, "queue/zoned"); + ul_path_read_string(dev->sysfs, &str, "queue/zoned"); break; }; - if (str && scols_line_refer_data(ln, col, str)) - err(EXIT_FAILURE, _("failed to add output data")); + return str; } -static void fill_table_line(struct blkdev_cxt *cxt, struct libscols_line *scols_parent) +/* + * Adds data for all wanted columns about the device to the smartcols table + */ +static void device_to_scols( + struct lsblk_device *dev, + struct lsblk_device *parent, + struct libscols_table *tab, + struct libscols_line *parent_line) { size_t i; + struct libscols_line *ln; + struct lsblk_iter itr; + struct lsblk_device *child = NULL; + int link_group = 0; - cxt->scols_line = scols_table_new_line(lsblk->table, scols_parent); - if (!cxt->scols_line) + ON_DBG(DEV, if (ul_path_isopen_dirfd(dev->sysfs)) ul_debugobj(dev, "%s ---> is open!", dev->name)); + + /* Do not print device more than one in --list mode */ + if (!(lsblk->flags & LSBLK_TREE) && dev->is_printed) + return; + + if (lsblk->merge && list_count_entries(&dev->parents) > 1) { + if (!lsblk_device_is_last_parent(dev, parent)) + return; + link_group = 1; + } + + ln = scols_table_new_line(tab, link_group ? NULL : parent_line); + if (!ln) err(EXIT_FAILURE, _("failed to allocate output line")); - for (i = 0; i < ncolumns; i++) - set_scols_data(cxt, i, get_column_id(i), cxt->scols_line); + dev->is_printed = 1; + + if (link_group) { + struct lsblk_device *p; + struct libscols_line *gr = parent_line; + + /* Merge all my parents to the one group */ + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + while (lsblk_device_next_parent(dev, &itr, &p) == 0) { + if (!p->scols_line) + continue; + scols_table_group_lines(tab, gr, p->scols_line, 0); + } + + /* Link the group -- this makes group->child connection */ + scols_line_link_group(ln, gr, 0); + } + + /* read column specific data and set it to smartcols table line */ + for (i = 0; i < ncolumns; i++) { + char *data; + int id = get_column_id(i); + + if (lsblk->sort_id != id) + data = device_get_data(dev, parent, id, NULL); + else { + uint64_t sortdata = (uint64_t) -1; + + data = device_get_data(dev, parent, id, &sortdata); + if (data && sortdata != (uint64_t) -1) + set_sortdata_u64(ln, i, sortdata); + } + if (data && scols_line_refer_data(ln, i, data)) + err(EXIT_FAILURE, _("failed to add output data")); + } + + dev->scols_line = ln; + + if (dev->npartitions == 0) + /* For partitions we often read from parental whole-disk sysfs, + * otherwise we can close */ + ul_path_close_dirfd(dev->sysfs); + + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + while (lsblk_device_next_child(dev, &itr, &child) == 0) + device_to_scols(child, dev, tab, ln); + + /* Let's be careful with number of open files */ + ul_path_close_dirfd(dev->sysfs); +} + +/* + * Walks on tree and adds one line for each device to the smartcols table + */ +static void devtree_to_scols(struct lsblk_devtree *tr, struct libscols_table *tab) +{ + struct lsblk_iter itr; + struct lsblk_device *dev = NULL; + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (lsblk_devtree_next_root(tr, &itr, &dev) == 0) + device_to_scols(dev, NULL, tab, NULL); } -static int set_cxt(struct blkdev_cxt *cxt, - struct blkdev_cxt *parent, - struct blkdev_cxt *wholedisk, +/* + * Reads very basic information about the device from sysfs into the device struct + */ +static int initialize_device(struct lsblk_device *dev, + struct lsblk_device *wholedisk, const char *name) { dev_t devno; - DBG(CXT, ul_debugobj(cxt, "setting context for %s [parent=%p, wholedisk=%p]", - name, parent, wholedisk)); + DBG(DEV, ul_debugobj(dev, "initialize %s [wholedisk=%p %s]", + name, wholedisk, wholedisk ? wholedisk->name : "")); - cxt->parent = parent; - cxt->name = xstrdup(name); - cxt->partition = wholedisk != NULL; + dev->name = xstrdup(name); - cxt->filename = get_device_path(cxt); - if (!cxt->filename) { - DBG(CXT, ul_debugobj(cxt, "%s: failed to get device path", cxt->name)); - return -1; + if (wholedisk) { + dev->wholedisk = wholedisk; + lsblk_ref_device(wholedisk); } - DBG(CXT, ul_debugobj(cxt, "%s: filename=%s", cxt->name, cxt->filename)); - devno = __sysfs_devname_to_devno(lsblk->sysroot, cxt->name, wholedisk ? wholedisk->name : NULL); + dev->filename = get_device_path(dev); + if (!dev->filename) { + DBG(DEV, ul_debugobj(dev, "%s: failed to get device path", dev->name)); + return -1; + } + DBG(DEV, ul_debugobj(dev, "%s: filename=%s", dev->name, dev->filename)); + devno = __sysfs_devname_to_devno(lsblk->sysroot, dev->name, wholedisk ? wholedisk->name : NULL); if (!devno) { - DBG(CXT, ul_debugobj(cxt, "%s: unknown device name", cxt->name)); + DBG(DEV, ul_debugobj(dev, "%s: unknown device name", dev->name)); return -1; } - if (lsblk->inverse) { - cxt->sysfs = ul_new_sysfs_path(devno, wholedisk ? wholedisk->sysfs : NULL, lsblk->sysroot); - if (!cxt->sysfs) { - DBG(CXT, ul_debugobj(cxt, "%s: failed to initialize sysfs handler", cxt->name)); - return -1; - } - if (parent) - sysfs_blkdev_set_parent(parent->sysfs, cxt->sysfs); - } else { - cxt->sysfs = ul_new_sysfs_path(devno, parent ? parent->sysfs : NULL, lsblk->sysroot); - if (!cxt->sysfs) { - DBG(CXT, ul_debugobj(cxt, "%s: failed to initialize sysfs handler", cxt->name)); - return -1; - } + dev->sysfs = ul_new_sysfs_path(devno, wholedisk ? wholedisk->sysfs : NULL, lsblk->sysroot); + if (!dev->sysfs) { + DBG(DEV, ul_debugobj(dev, "%s: failed to initialize sysfs handler", dev->name)); + return -1; } - cxt->maj = major(devno); - cxt->min = minor(devno); - cxt->size = 0; + dev->maj = major(devno); + dev->min = minor(devno); + dev->size = 0; - if (ul_path_read_u64(cxt->sysfs, &cxt->size, "size") == 0) /* in sectors */ - cxt->size <<= 9; /* in bytes */ - - if (ul_path_read_s32(cxt->sysfs, &cxt->discard, - "queue/discard_granularity") != 0) - cxt->discard = 0; + if (ul_path_read_u64(dev->sysfs, &dev->size, "size") == 0) /* in sectors */ + dev->size <<= 9; /* in bytes */ /* Ignore devices of zero size */ - if (!lsblk->all_devices && cxt->size == 0) { - DBG(CXT, ul_debugobj(cxt, "zero size device -- ignore")); + if (!lsblk->all_devices && dev->size == 0) { + DBG(DEV, ul_debugobj(dev, "zero size device -- ignore")); return -1; } - if (is_dm(cxt->name)) { - ul_path_read_string(cxt->sysfs, &cxt->dm_name, "dm/name"); - if (!cxt->dm_name) { - DBG(CXT, ul_debugobj(cxt, "%s: failed to get dm name", cxt->name)); + if (is_dm(dev->name)) { + ul_path_read_string(dev->sysfs, &dev->dm_name, "dm/name"); + if (!dev->dm_name) { + DBG(DEV, ul_debugobj(dev, "%s: failed to get dm name", dev->name)); return -1; } } - cxt->npartitions = sysfs_blkdev_count_partitions(cxt->sysfs, cxt->name); - cxt->nholders = ul_path_count_dirents(cxt->sysfs, "holders"); - cxt->nslaves = ul_path_count_dirents(cxt->sysfs, "slaves"); + dev->npartitions = sysfs_blkdev_count_partitions(dev->sysfs, dev->name); + dev->nholders = ul_path_count_dirents(dev->sysfs, "holders"); + dev->nslaves = ul_path_count_dirents(dev->sysfs, "slaves"); - DBG(CXT, ul_debugobj(cxt, "%s: npartitions=%d, nholders=%d, nslaves=%d", - cxt->name, cxt->npartitions, cxt->nholders, cxt->nslaves)); + DBG(DEV, ul_debugobj(dev, "%s: npartitions=%d, nholders=%d, nslaves=%d", + dev->name, dev->npartitions, dev->nholders, dev->nslaves)); /* ignore non-SCSI devices */ - if (lsblk->scsi && sysfs_blkdev_scsi_get_hctl(cxt->sysfs, NULL, NULL, NULL, NULL)) { - DBG(CXT, ul_debugobj(cxt, "non-scsi device -- ignore")); + if (lsblk->scsi && sysfs_blkdev_scsi_get_hctl(dev->sysfs, NULL, NULL, NULL, NULL)) { + DBG(DEV, ul_debugobj(dev, "non-scsi device -- ignore")); return -1; } - DBG(CXT, ul_debugobj(cxt, "%s: context successfully initialized", cxt->name)); + DBG(DEV, ul_debugobj(dev, "%s: context successfully initialized", dev->name)); return 0; } -static int process_blkdev(struct blkdev_cxt *cxt, struct blkdev_cxt *parent, - int do_partitions, const char *part_name); +static struct lsblk_device *devtree_get_device_or_new(struct lsblk_devtree *tr, + struct lsblk_device *disk, + const char *name) +{ + struct lsblk_device *dev = lsblk_devtree_get_device(tr, name); + + if (!dev) { + dev = lsblk_new_device(); + if (!dev) + err(EXIT_FAILURE, _("failed to allocate device")); + + if (initialize_device(dev, disk, name) != 0) { + lsblk_unref_device(dev); + return NULL; + } + lsblk_devtree_add_device(tr, dev); + lsblk_unref_device(dev); /* keep it referenced by devtree only */ + } else + DBG(DEV, ul_debugobj(dev, "%s: already processed", name)); + + return dev; +} + +static int process_dependencies( + struct lsblk_devtree *tr, + struct lsblk_device *dev, + int do_partitions); /* - * List device partitions if any. + * Read devices from whole-disk device into tree */ -static int list_partitions(struct blkdev_cxt *wholedisk_cxt, struct blkdev_cxt *parent_cxt, - const char *part_name) +static int process_partitions(struct lsblk_devtree *tr, struct lsblk_device *disk) { DIR *dir; struct dirent *d; - struct blkdev_cxt part_cxt = { NULL }; - int r = -1; - assert(wholedisk_cxt); + assert(disk); /* * Do not process further if there are no partitions for * this device or the device itself is a partition. */ - if (!wholedisk_cxt->npartitions || wholedisk_cxt->partition) - return -1; + if (!disk->npartitions || device_is_partition(disk)) + return -EINVAL; - DBG(CXT, ul_debugobj(wholedisk_cxt, "probe whole-disk for partitions")); + DBG(DEV, ul_debugobj(disk, "%s: probe whole-disk for partitions", disk->name)); - dir = ul_path_opendir(wholedisk_cxt->sysfs, NULL); + dir = ul_path_opendir(disk->sysfs, NULL); if (!dir) err(EXIT_FAILURE, _("failed to open device directory in sysfs")); while ((d = xreaddir(dir))) { - /* Process particular partition only? */ - if (part_name && strcmp(part_name, d->d_name)) - continue; + struct lsblk_device *part; - if (!(sysfs_blkdev_is_partition_dirent(dir, d, wholedisk_cxt->name))) + if (!(sysfs_blkdev_is_partition_dirent(dir, d, disk->name))) continue; - DBG(CXT, ul_debugobj(wholedisk_cxt, " checking %s", d->d_name)); + DBG(DEV, ul_debugobj(disk, " checking %s", d->d_name)); - if (lsblk->inverse) { - /* - * <parent_cxt> - * `-<part_cxt> - * `-<wholedisk_cxt> - * `-... - */ - if (set_cxt(&part_cxt, parent_cxt, wholedisk_cxt, d->d_name)) - goto next; + part = devtree_get_device_or_new(tr, disk, d->d_name); + if (!part) + continue; - if (!parent_cxt && part_cxt.nholders) - goto next; + if (lsblk_device_new_dependence(disk, part) == 0) + process_dependencies(tr, part, 0); - wholedisk_cxt->parent = &part_cxt; - fill_table_line(&part_cxt, parent_cxt ? parent_cxt->scols_line : NULL); - if (!lsblk->nodeps) - process_blkdev(wholedisk_cxt, &part_cxt, 0, NULL); - } else { - /* - * <parent_cxt> - * `-<wholedisk_cxt> - * `-<part_cxt> - * `-... - */ - int ps = set_cxt(&part_cxt, wholedisk_cxt, wholedisk_cxt, d->d_name); - - /* Print whole disk only once */ - if (r) - 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); - } - next: - reset_blkdev_cxt(&part_cxt); - r = 0; + ul_path_close_dirfd(part->sysfs); } - DBG(CXT, ul_debugobj(wholedisk_cxt, "probe whole-disk for partitions -- done")); + /* For partitions we need parental (whole-disk) sysfs directory pretty + * often, so close it now when all is done */ + ul_path_close_dirfd(disk->sysfs); + + DBG(DEV, ul_debugobj(disk, "probe whole-disk for partitions -- done")); closedir(dir); - return r; + return 0; } -static int get_wholedisk_from_partition_dirent(DIR *dir, - struct dirent *d, struct blkdev_cxt *cxt) +static char *get_wholedisk_from_partition_dirent(DIR *dir, struct dirent *d, char *buf, size_t bufsz) { - char path[PATH_MAX]; char *p; int len; - if ((len = readlinkat(dirfd(dir), d->d_name, path, sizeof(path) - 1)) < 0) + if ((len = readlinkat(dirfd(dir), d->d_name, buf, bufsz - 1)) < 0) return 0; - path[len] = '\0'; + buf[len] = '\0'; /* The path ends with ".../<device>/<partition>" */ - p = strrchr(path, '/'); + p = strrchr(buf, '/'); if (!p) - return 0; + return NULL; *p = '\0'; - p = strrchr(path, '/'); + p = strrchr(buf, '/'); if (!p) - return 0; + return NULL; p++; - return set_cxt(cxt, NULL, NULL, p); + return p; } /* - * List device dependencies: partitions, holders (inverse = 0) or slaves (inverse = 1). + * Reads slaves/holders and partitions for specified device into device tree */ -static int list_deps(struct blkdev_cxt *cxt) +static int process_dependencies( + struct lsblk_devtree *tr, + struct lsblk_device *dev, + int do_partitions) { DIR *dir; struct dirent *d; - struct blkdev_cxt dep = { NULL }; const char *depname; - assert(cxt); + assert(dev); if (lsblk->nodeps) return 0; - DBG(CXT, ul_debugobj(cxt, "%s: list dependencies", cxt->name)); + /* read all or specified partition */ + if (do_partitions && dev->npartitions) + process_partitions(tr, dev); - if (!(lsblk->inverse ? cxt->nslaves : cxt->nholders)) + DBG(DEV, ul_debugobj(dev, "%s: reading dependencies", dev->name)); + + if (!(lsblk->inverse ? dev->nslaves : dev->nholders)) { + DBG(DEV, ul_debugobj(dev, " ignore (no slaves/holders)")); return 0; + } depname = lsblk->inverse ? "slaves" : "holders"; - dir = ul_path_opendir(cxt->sysfs, depname); - if (!dir) + dir = ul_path_opendir(dev->sysfs, depname); + if (!dir) { + DBG(DEV, ul_debugobj(dev, " ignore (no slaves/holders directory)")); return 0; + } + ul_path_close_dirfd(dev->sysfs); - DBG(CXT, ul_debugobj(cxt, "%s: checking for '%s' dependence", cxt->name, depname)); + DBG(DEV, ul_debugobj(dev, " %s: checking for '%s' dependence", dev->name, depname)); while ((d = xreaddir(dir))) { + struct lsblk_device *dep = NULL; + struct lsblk_device *disk = NULL; + /* Is the dependency a partition? */ if (sysfs_blkdev_is_partition_dirent(dir, d, NULL)) { - if (!get_wholedisk_from_partition_dirent(dir, d, &dep)) { - DBG(CXT, ul_debugobj(cxt, "%s: %s: dependence is partition", - cxt->name, d->d_name)); - process_blkdev(&dep, cxt, 1, d->d_name); - } - } - /* The dependency is a whole device. */ - else if (!set_cxt(&dep, cxt, NULL, d->d_name)) { - DBG(CXT, ul_debugobj(cxt, "%s: %s: dependence is whole-disk", - cxt->name, d->d_name)); - /* For inverse tree we don't want to show partitions - * if the dependence is on whole-disk */ - process_blkdev(&dep, cxt, lsblk->inverse ? 0 : 1, NULL); - } - reset_blkdev_cxt(&dep); - } - closedir(dir); - DBG(CXT, ul_debugobj(cxt, "%s: checking for '%s' -- done", cxt->name, depname)); - return 0; -} + char buf[PATH_MAX]; + char *diskname; -static int process_blkdev(struct blkdev_cxt *cxt, struct blkdev_cxt *parent, - int do_partitions, const char *part_name) -{ - if (do_partitions && cxt->npartitions) - list_partitions(cxt, parent, part_name); /* partitions + whole-disk */ - else - fill_table_line(cxt, parent ? parent->scols_line : NULL); /* whole-disk only */ + DBG(DEV, ul_debugobj(dev, " %s: dependence is partition", d->d_name)); - return list_deps(cxt); -} - -/* Iterate devices in sysfs */ -static int iterate_block_devices(void) -{ - DIR *dir; - struct dirent *d; - struct blkdev_cxt cxt = { NULL }; - struct path_cxt *pc = ul_new_path(_PATH_SYS_BLOCK); - - if (!pc) - err(EXIT_FAILURE, _("failed to allocate /sys handler")); - - ul_path_set_prefix(pc, lsblk->sysroot); - - /* TODO: reuse @pc in set_cxt(), etc. */ - dir = ul_path_opendir(pc, NULL); - if (!dir) - goto done; - - DBG(DEV, ul_debug("iterate on " _PATH_SYS_BLOCK)); - - while ((d = xreaddir(dir))) { + diskname = get_wholedisk_from_partition_dirent(dir, d, buf, sizeof(buf)); + if (diskname) + disk = devtree_get_device_or_new(tr, NULL, diskname); + if (!disk) { + DBG(DEV, ul_debugobj(dev, " ignore no wholedisk ???")); + goto next; + } - DBG(DEV, ul_debug(" %s dentry", d->d_name)); + dep = devtree_get_device_or_new(tr, disk, d->d_name); + if (!dep) + goto next; - if (set_cxt(&cxt, NULL, NULL, d->d_name)) - goto next; + if (lsblk_device_new_dependence(dev, dep) == 0) + process_dependencies(tr, dep, 1); - if (is_maj_excluded(cxt.maj) || !is_maj_included(cxt.maj)) - goto next; + if (lsblk->inverse + && lsblk_device_new_dependence(dep, disk) == 0) + process_dependencies(tr, disk, 0); + } + /* The dependency is a whole device. */ + else { + DBG(DEV, ul_debugobj(dev, " %s: %s: dependence is whole-disk", + dev->name, d->d_name)); - /* Skip devices in the middle of dependency tree. */ - if ((lsblk->inverse ? cxt.nholders : cxt.nslaves) > 0) - goto next; + dep = devtree_get_device_or_new(tr, NULL, d->d_name); + if (!dep) + goto next; - process_blkdev(&cxt, NULL, 1, NULL); - next: - reset_blkdev_cxt(&cxt); + if (lsblk_device_new_dependence(dev, dep) == 0) + /* For inverse tree we don't want to show partitions + * if the dependence is on whole-disk */ + process_dependencies(tr, dep, lsblk->inverse ? 0 : 1); + } +next: + if (dep && dep->sysfs) + ul_path_close_dirfd(dep->sysfs); + if (disk && disk->sysfs) + ul_path_close_dirfd(disk->sysfs); } - closedir(dir); -done: - ul_unref_path(pc); - DBG(DEV, ul_debug("iterate on " _PATH_SYS_BLOCK " -- done")); + + DBG(DEV, ul_debugobj(dev, "%s: checking for '%s' -- done", dev->name, depname)); return 0; } -static int process_one_device(char *devname) +/* + * Defines the device as root node in the device tree and walks on all dependencies of the device. + */ +static int __process_one_device(struct lsblk_devtree *tr, char *devname, dev_t devno) { - struct blkdev_cxt parent = { NULL }, cxt = { NULL }; - struct stat st; + struct lsblk_device *dev = NULL; + struct lsblk_device *disk = NULL; char buf[PATH_MAX + 1], *name = NULL, *diskname = NULL; - dev_t disk = 0; int real_part = 0, rc = -EINVAL; - if (stat(devname, &st) || !S_ISBLK(st.st_mode)) { - warnx(_("%s: not a block device"), devname); - goto leave; - } + if (devno == 0) { + struct stat st; + + DBG(DEV, ul_debug("%s: reading alone device", devname)); + + if (stat(devname, &st) || !S_ISBLK(st.st_mode)) { + warnx(_("%s: not a block device"), devname); + goto leave; + } + devno = st.st_rdev; + } else + DBG(DEV, ul_debug("%d:%d: reading alone device", major(devno), minor(devno))); /* TODO: sysfs_devno_to_devname() internally initializes path_cxt, it * would be better to use ul_new_sysfs_path() + sysfs_blkdev_get_name() - * and reuse path_cxt for set_cxt() + * and reuse path_cxt for initialize_device() */ - name = sysfs_devno_to_devname(st.st_rdev, buf, sizeof(buf)); + name = sysfs_devno_to_devname(devno, buf, sizeof(buf)); if (!name) { - warn(_("%s: failed to get sysfs name"), devname); + if (devname) + warn(_("%s: failed to get sysfs name"), devname); goto leave; } name = xstrdup(name); @@ -1325,47 +1423,176 @@ static int process_one_device(char *devname) /* dm mapping is never a real partition! */ real_part = 0; } else { - if (blkid_devno_to_wholedisk(st.st_rdev, buf, sizeof(buf), &disk)) { - warn(_("%s: failed to get whole-disk device number"), devname); + dev_t diskno = 0; + + if (blkid_devno_to_wholedisk(devno, buf, sizeof(buf), &diskno)) { + warn(_("%s: failed to get whole-disk device number"), name); goto leave; } diskname = buf; - real_part = st.st_rdev != disk; + real_part = devno != diskno; } if (!real_part) { /* * Device is not a partition. */ - if (set_cxt(&cxt, NULL, NULL, name)) + DBG(DEV, ul_debug(" non-partition")); + + dev = devtree_get_device_or_new(tr, NULL, name); + if (!dev) goto leave; - process_blkdev(&cxt, NULL, !lsblk->inverse, NULL); + + lsblk_devtree_add_root(tr, dev); + process_dependencies(tr, dev, !lsblk->inverse); } else { /* - * Partition, read sysfs name of the device. + * Partition, read sysfs name of the disk device */ - if (set_cxt(&parent, NULL, NULL, diskname)) + DBG(DEV, ul_debug(" partition")); + + disk = devtree_get_device_or_new(tr, NULL, diskname); + if (!disk) goto leave; - if (set_cxt(&cxt, &parent, &parent, name)) + + dev = devtree_get_device_or_new(tr, disk, name); + if (!dev) goto leave; - if (lsblk->inverse) - process_blkdev(&parent, &cxt, 1, cxt.name); + lsblk_devtree_add_root(tr, dev); + process_dependencies(tr, dev, 1); + + if (lsblk->inverse + && lsblk_device_new_dependence(dev, disk) == 0) + process_dependencies(tr, disk, 0); else - process_blkdev(&cxt, &parent, 1, NULL); + ul_path_close_dirfd(disk->sysfs); } rc = 0; leave: + if (dev && dev->sysfs) + ul_path_close_dirfd(dev->sysfs); + if (disk && disk->sysfs) + ul_path_close_dirfd(disk->sysfs); free(name); - reset_blkdev_cxt(&cxt); + return rc; +} - if (real_part) - reset_blkdev_cxt(&parent); +static int process_one_device(struct lsblk_devtree *tr, char *devname) +{ + return __process_one_device(tr, devname, 0); +} - return rc; +/* + * The /sys/block contains only root devices, and no partitions. It seems more + * simple to scan /sys/dev/block where are all devices without exceptions to get + * top-level devices for the reverse tree. + */ +static int process_all_devices_inverse(struct lsblk_devtree *tr) +{ + DIR *dir; + struct dirent *d; + struct path_cxt *pc = ul_new_path(_PATH_SYS_DEVBLOCK); + + assert(lsblk->inverse); + + if (!pc) + err(EXIT_FAILURE, _("failed to allocate /sys handler")); + + ul_path_set_prefix(pc, lsblk->sysroot); + dir = ul_path_opendir(pc, NULL); + if (!dir) + goto done; + + DBG(DEV, ul_debug("iterate on " _PATH_SYS_DEVBLOCK)); + + while ((d = xreaddir(dir))) { + dev_t devno; + int maj, min; + + DBG(DEV, ul_debug(" %s dentry", d->d_name)); + + if (sscanf(d->d_name, "%d:%d", &maj, &min) != 2) + continue; + devno = makedev(maj, min); + + if (is_maj_excluded(maj) || !is_maj_included(maj)) + continue; + if (ul_path_countf_dirents(pc, "%s/holders", d->d_name) != 0) + continue; + if (sysfs_devno_count_partitions(devno) != 0) + continue; + __process_one_device(tr, NULL, devno); + } + + closedir(dir); +done: + ul_unref_path(pc); + DBG(DEV, ul_debug("iterate on " _PATH_SYS_DEVBLOCK " -- done")); + return 0; } +/* + * Reads root nodes (devices) from /sys/block into devices tree + */ +static int process_all_devices(struct lsblk_devtree *tr) +{ + DIR *dir; + struct dirent *d; + struct path_cxt *pc; + + assert(lsblk->inverse == 0); + + pc = ul_new_path(_PATH_SYS_BLOCK); + if (!pc) + err(EXIT_FAILURE, _("failed to allocate /sys handler")); + + ul_path_set_prefix(pc, lsblk->sysroot); + dir = ul_path_opendir(pc, NULL); + if (!dir) + goto done; + + DBG(DEV, ul_debug("iterate on " _PATH_SYS_BLOCK)); + + while ((d = xreaddir(dir))) { + struct lsblk_device *dev = NULL; + + DBG(DEV, ul_debug(" %s dentry", d->d_name)); + dev = devtree_get_device_or_new(tr, NULL, d->d_name); + if (!dev) + goto next; + + /* remove unwanted devices */ + if (is_maj_excluded(dev->maj) || !is_maj_included(dev->maj)) { + DBG(DEV, ul_debug(" %s: ignore (by filter)", d->d_name)); + lsblk_devtree_remove_device(tr, dev); + goto next; + } + + if (dev->nslaves) { + DBG(DEV, ul_debug(" %s: ignore (in-middle)", d->d_name)); + goto next; + } + + lsblk_devtree_add_root(tr, dev); + process_dependencies(tr, dev, 1); +next: + /* Let's be careful with number of open files */ + if (dev && dev->sysfs) + ul_path_close_dirfd(dev->sysfs); + } + + closedir(dir); +done: + ul_unref_path(pc); + DBG(DEV, ul_debug("iterate on " _PATH_SYS_BLOCK " -- done")); + return 0; +} + +/* + * Parses major numbers as specified on lsblk command line + */ static void parse_excludes(const char *str0) { const char *str = str0; @@ -1393,6 +1620,10 @@ static void parse_excludes(const char *str0) } } +/* + * Parses major numbers as specified on lsblk command line + * (TODO: what about refactor and merge parse_excludes() and parse_includes().) + */ static void parse_includes(const char *str0) { const char *str = str0; @@ -1438,6 +1669,43 @@ static int cmp_u64_cells(struct libscols_cell *a, return *adata == *bdata ? 0 : *adata >= *bdata ? 1 : -1; } +static void device_set_dedupkey( + struct lsblk_device *dev, + struct lsblk_device *parent, + int id) +{ + struct lsblk_iter itr; + struct lsblk_device *child = NULL; + + dev->dedupkey = device_get_data(dev, parent, id, NULL); + if (dev->dedupkey) + DBG(DEV, ul_debugobj(dev, "%s: de-duplication key: %s", dev->name, dev->dedupkey)); + + if (dev->npartitions == 0) + /* For partitions we often read from parental whole-disk sysfs, + * otherwise we can close */ + ul_path_close_dirfd(dev->sysfs); + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (lsblk_device_next_child(dev, &itr, &child) == 0) + device_set_dedupkey(child, dev, id); + + /* Let's be careful with number of open files */ + ul_path_close_dirfd(dev->sysfs); +} + +static void devtree_set_dedupkeys(struct lsblk_devtree *tr, int id) +{ + struct lsblk_iter itr; + struct lsblk_device *dev = NULL; + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (lsblk_devtree_next_root(tr, &itr, &dev) == 0) + device_set_dedupkey(dev, NULL, id); +} + static void __attribute__((__noreturn__)) usage(void) { FILE *out = stdout; @@ -1450,28 +1718,30 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_("List information about block devices.\n"), out); fputs(USAGE_OPTIONS, out); + fputs(_(" -D, --discard print discard capabilities\n"), out); + fputs(_(" -E, --dedup <column> de-duplicate output by <column>\n"), out); + fputs(_(" -I, --include <list> show only devices with specified major numbers\n"), out); + fputs(_(" -J, --json use JSON output format\n"), out); + fputs(_(" -O, --output-all output all columns\n"), out); + fputs(_(" -P, --pairs use key=\"value\" output format\n"), out); + fputs(_(" -S, --scsi output info about SCSI devices\n"), out); + fputs(_(" -T, --tree use tree format output\n"), out); fputs(_(" -a, --all print all devices\n"), out); fputs(_(" -b, --bytes print SIZE in bytes rather than in human readable format\n"), out); fputs(_(" -d, --nodeps don't print slaves or holders\n"), out); - fputs(_(" -D, --discard print discard capabilities\n"), out); - fputs(_(" -z, --zoned print zone model\n"), out); fputs(_(" -e, --exclude <list> exclude devices by major number (default: RAM disks)\n"), out); fputs(_(" -f, --fs output info about filesystems\n"), out); fputs(_(" -i, --ascii use ascii characters only\n"), out); - fputs(_(" -I, --include <list> show only devices with specified major numbers\n"), out); - fputs(_(" -J, --json use JSON output format\n"), out); fputs(_(" -l, --list use list format output\n"), out); - fputs(_(" -T, --tree use tree format output\n"), out); + fputs(_(" -M, --merge group parents of sub-trees (usable for RAIDs, Multi-path)\n"), out); fputs(_(" -m, --perms output info about permissions\n"), out); fputs(_(" -n, --noheadings don't print headings\n"), out); fputs(_(" -o, --output <list> output columns\n"), out); - fputs(_(" -O, --output-all output all columns\n"), out); fputs(_(" -p, --paths print complete device path\n"), out); - fputs(_(" -P, --pairs use key=\"value\" output format\n"), out); fputs(_(" -r, --raw use raw output format\n"), 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(_(" -z, --zoned print zone model\n"), out); fputs(_(" -x, --sort <column> sort output by <column>\n"), out); fputs(_(" --sysroot <dir> use specified directory as system root\n"), out); fputs(USAGE_SEPARATOR, out); @@ -1496,7 +1766,12 @@ static void check_sysdevblock(void) int main(int argc, char *argv[]) { - struct lsblk _ls = { .sort_id = -1, .flags = LSBLK_TREE }; + struct lsblk _ls = { + .sort_id = -1, + .dedup_id = -1, + .flags = LSBLK_TREE + }; + struct lsblk_devtree *tr = NULL; int c, status = EXIT_FAILURE; char *outarg = NULL; size_t i; @@ -1511,11 +1786,13 @@ int main(int argc, char *argv[]) { "bytes", no_argument, NULL, 'b' }, { "nodeps", no_argument, NULL, 'd' }, { "discard", no_argument, NULL, 'D' }, + { "dedup", required_argument, NULL, 'E' }, { "zoned", no_argument, NULL, 'z' }, { "help", no_argument, NULL, 'h' }, { "json", no_argument, NULL, 'J' }, { "output", required_argument, NULL, 'o' }, { "output-all", no_argument, NULL, 'O' }, + { "merge", no_argument, NULL, 'M' }, { "perms", no_argument, NULL, 'm' }, { "noheadings", no_argument, NULL, 'n' }, { "list", no_argument, NULL, 'l' }, @@ -1559,7 +1836,7 @@ int main(int argc, char *argv[]) lsblk_init_debug(); while((c = getopt_long(argc, argv, - "abdDze:fhJlnmo:OpPiI:rstVSTx:", longopts, NULL)) != -1) { + "abdDzE:e:fhJlnMmo:OpPiI:rstVSTx:", longopts, NULL)) != -1) { err_exclusive_options(c, longopts, excl, excl_st); @@ -1596,6 +1873,9 @@ int main(int argc, char *argv[]) case 'l': lsblk->flags &= ~LSBLK_TREE; /* disable the default */ break; + case 'M': + lsblk->merge = 1; + break; case 'n': lsblk->flags |= LSBLK_NOHEADINGS; break; @@ -1676,6 +1956,12 @@ int main(int argc, char *argv[]) case 'V': printf(UTIL_LINUX_VERSION); return EXIT_SUCCESS; + case 'E': + lsblk->dedup_id = column_name_to_id(optarg, strlen(optarg)); + if (lsblk->dedup_id >= 0) + break; + errtryhelp(EXIT_FAILURE); + break; case 'x': lsblk->flags &= ~LSBLK_TREE; /* disable the default */ lsblk->sort_id = column_name_to_id(optarg, strlen(optarg)); @@ -1724,6 +2010,12 @@ int main(int argc, char *argv[]) lsblk->sort_hidden = 1; } + if (lsblk->dedup_id >= 0 && column_id_to_number(lsblk->dedup_id) < 0) { + /* the deduplication column is not between output columns -- add as hidden */ + add_column(lsblk->dedup_id); + lsblk->dedup_hidden = 1; + } + lsblk_mnt_init(); scols_init_debug(0); ul_path_init_debug(); @@ -1751,6 +2043,8 @@ int main(int argc, char *argv[]) fl &= ~SCOLS_FL_TREE; if (lsblk->sort_hidden && lsblk->sort_id == id) fl |= SCOLS_FL_HIDDEN; + if (lsblk->dedup_hidden && lsblk->dedup_id == id) + fl |= SCOLS_FL_HIDDEN; cl = scols_table_new_column(lsblk->table, ci->name, ci->whint, fl); if (!cl) { @@ -1784,13 +2078,21 @@ int main(int argc, char *argv[]) } } - if (optind == argc) - status = iterate_block_devices() == 0 ? EXIT_SUCCESS : EXIT_FAILURE; - else { + tr = lsblk_new_devtree(); + if (!tr) + err(EXIT_FAILURE, _("failed to allocate device tree")); + + if (optind == argc) { + int rc = lsblk->inverse ? + process_all_devices_inverse(tr) : + process_all_devices(tr); + + status = rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE; + } else { int cnt = 0, cnt_err = 0; while (optind < argc) { - if (process_one_device(argv[optind++]) != 0) + if (process_one_device(tr, argv[optind++]) != 0) cnt_err++; cnt++; } @@ -1800,6 +2102,13 @@ int main(int argc, char *argv[]) EXIT_SUCCESS; /* all success */ } + if (lsblk->dedup_id > -1) { + devtree_set_dedupkeys(tr, lsblk->dedup_id); + lsblk_devtree_deduplicate_devices(tr); + } + + devtree_to_scols(tr, lsblk->table); + if (lsblk->sort_col) scols_sort_table(lsblk->table, lsblk->sort_col); if (lsblk->force_tree_order) @@ -1815,6 +2124,7 @@ leave: lsblk_mnt_deinit(); lsblk_properties_deinit(); + lsblk_unref_devtree(tr); return status; } diff --git a/misc-utils/lsblk.h b/misc-utils/lsblk.h index baad3aa9b..a043e7fd7 100644 --- a/misc-utils/lsblk.h +++ b/misc-utils/lsblk.h @@ -14,12 +14,14 @@ #include <libsmartcols.h> #include "c.h" +#include "list.h" #include "debug.h" #define LSBLK_DEBUG_INIT (1 << 1) #define LSBLK_DEBUG_FILTER (1 << 2) #define LSBLK_DEBUG_DEV (1 << 3) -#define LSBLK_DEBUG_CXT (1 << 4) +#define LSBLK_DEBUG_TREE (1 << 4) +#define LSBLK_DEBUG_DEP (1 << 5) #define LSBLK_DEBUG_ALL 0xFFFF UL_DEBUG_DECLARE_MASK(lsblk); @@ -31,19 +33,24 @@ UL_DEBUG_DECLARE_MASK(lsblk); struct lsblk { struct libscols_table *table; /* output table */ + struct libscols_column *sort_col;/* sort output by this column */ int sort_id; + int dedup_id; + const char *sysroot; int flags; /* LSBLK_* */ 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 */ + unsigned int merge:1; /* merge sub-trees */ unsigned int nodeps:1; /* don't print slaves/holders */ unsigned int scsi:1; /* print only device with HCTL (SCSI) */ unsigned int paths:1; /* print devnames with "/dev" prefix */ unsigned int sort_hidden:1; /* sort column not between output columns */ + unsigned int dedup_hidden :1; /* deduplication column not between output columns */ unsigned int force_tree_order:1;/* sort lines by parent->tree relation */ }; @@ -64,22 +71,42 @@ struct lsblk_devprop { char *model; /* disk model */ }; -struct blkdev_cxt { - struct blkdev_cxt *parent; - struct lsblk_devprop *properties; +/* Device dependence + * + * Note that the same device may be slave/holder for more another devices. It + * means we need to allocate list member rather than use @child directly. + */ +struct lsblk_devdep { + struct list_head ls_childs; /* item in parent->childs */ + struct list_head ls_parents; /* item in child->parents */ + + struct lsblk_device *child; + struct lsblk_device *parent; +}; + +struct lsblk_device { + int refcount; + + struct list_head childs; /* list with lsblk_devdep */ + struct list_head parents; + struct list_head ls_roots; /* item in devtree->roots list */ + struct list_head ls_devices; /* item in devtree->devices list */ + + struct lsblk_device *wholedisk; /* for partitions */ - struct libscols_line *scols_line; + struct libscols_line *scols_line; + + struct lsblk_devprop *properties; struct stat st; char *name; /* kernel name in /sys/block */ char *dm_name; /* DM name (dm/block) */ char *filename; /* path to device node */ + char *dedupkey; /* de-duplication key */ struct path_cxt *sysfs; - int partition; /* is partition? TRUE/FALSE */ - char *mountpoint; /* device mountpoint */ struct statvfs fsstat; /* statvfs() result */ @@ -88,25 +115,111 @@ struct blkdev_cxt { * /sys/block/.../holders */ int nslaves; /* # of devices this device maps to */ int maj, min; /* devno */ - int discard; /* supports discard */ + + uint64_t discard_granularity; /* sunknown:-1, yes:1, not:0 */ uint64_t size; /* device size */ + int removable; /* unknown:-1, yes:1, not:0 */ unsigned int is_mounted : 1, is_swap : 1, + is_printed : 1, udev_requested : 1, blkid_requested : 1; }; +#define device_is_partition(_x) ((_x)->wholedisk != NULL) + +/* + * Note that lsblk tree uses botton devices (devices without slaves) as root + * of the tree, and partitions are interpreted as a dependence too; it means: + * sda -> sda1 -> md0 + * + * The flag 'is_inverted' turns the tree over (root is device without holders): + * md0 -> sda1 -> sda + */ +struct lsblk_devtree { + int refcount; + + struct list_head roots; /* tree root devices */ + struct list_head devices; /* all devices */ + + unsigned int is_inverse : 1; /* inverse tree */ +}; + + +/* + * Generic iterator + */ +struct lsblk_iter { + struct list_head *p; /* current position */ + struct list_head *head; /* start position */ + int direction; /* LSBLK_ITER_{FOR,BACK}WARD */ +}; + +#define LSBLK_ITER_FORWARD 0 +#define LSBLK_ITER_BACKWARD 1 + +#define IS_ITER_FORWARD(_i) ((_i)->direction == LSBLK_ITER_FORWARD) +#define IS_ITER_BACKWARD(_i) ((_i)->direction == LSBLK_ITER_BACKWARD) + +#define LSBLK_ITER_INIT(itr, list) \ + do { \ + (itr)->p = IS_ITER_FORWARD(itr) ? \ + (list)->next : (list)->prev; \ + (itr)->head = (list); \ + } while(0) + +#define LSBLK_ITER_ITERATE(itr, res, restype, member) \ + do { \ + res = list_entry((itr)->p, restype, member); \ + (itr)->p = IS_ITER_FORWARD(itr) ? \ + (itr)->p->next : (itr)->p->prev; \ + } while(0) + + /* lsblk-mnt.c */ extern void lsblk_mnt_init(void); extern void lsblk_mnt_deinit(void); -extern char *lsblk_device_get_mountpoint(struct blkdev_cxt *cxt); +extern char *lsblk_device_get_mountpoint(struct lsblk_device *dev); /* lsblk-properties.c */ extern void lsblk_device_free_properties(struct lsblk_devprop *p); -extern struct lsblk_devprop *lsblk_device_get_properties(struct blkdev_cxt *cxt); +extern struct lsblk_devprop *lsblk_device_get_properties(struct lsblk_device *dev); extern void lsblk_properties_deinit(void); +/* lsblk-devtree.c */ +void lsblk_reset_iter(struct lsblk_iter *itr, int direction); +struct lsblk_device *lsblk_new_device(void); +void lsblk_ref_device(struct lsblk_device *dev); +void lsblk_unref_device(struct lsblk_device *dev); +int lsblk_device_new_dependence(struct lsblk_device *parent, struct lsblk_device *child); +int lsblk_device_has_child(struct lsblk_device *dev, struct lsblk_device *child); +int lsblk_device_next_child(struct lsblk_device *dev, + struct lsblk_iter *itr, + struct lsblk_device **child); + +int lsblk_device_is_last_parent(struct lsblk_device *dev, struct lsblk_device *parent); +int lsblk_device_next_parent( + struct lsblk_device *dev, + struct lsblk_iter *itr, + struct lsblk_device **parent); + +struct lsblk_devtree *lsblk_new_devtree(void); +void lsblk_ref_devtree(struct lsblk_devtree *tr); +void lsblk_unref_devtree(struct lsblk_devtree *tr); +int lsblk_devtree_add_root(struct lsblk_devtree *tr, struct lsblk_device *dev); +int lsblk_devtree_next_root(struct lsblk_devtree *tr, + struct lsblk_iter *itr, + struct lsblk_device **dev); +int lsblk_devtree_add_device(struct lsblk_devtree *tr, struct lsblk_device *dev); +int lsblk_devtree_next_device(struct lsblk_devtree *tr, + struct lsblk_iter *itr, + struct lsblk_device **dev); +int lsblk_devtree_has_device(struct lsblk_devtree *tr, struct lsblk_device *dev); +struct lsblk_device *lsblk_devtree_get_device(struct lsblk_devtree *tr, const char *name); +int lsblk_devtree_remove_device(struct lsblk_devtree *tr, struct lsblk_device *dev); +int lsblk_devtree_deduplicate_devices(struct lsblk_devtree *tr); + #endif /* UTIL_LINUX_LSBLK_H */ diff --git a/misc-utils/uuidd.c b/misc-utils/uuidd.c index 8b83d91c0..e0be809dd 100644 --- a/misc-utils/uuidd.c +++ b/misc-utils/uuidd.c @@ -298,7 +298,9 @@ static void timeout_handler(int sig __attribute__((__unused__)), siginfo_t * info, void *context __attribute__((__unused__))) { +#ifdef HAVE_TIMER_CREATE if (info->si_code == SI_TIMER) +#endif errx(EXIT_FAILURE, _("timed out")); } @@ -327,18 +329,18 @@ static void server_loop(const char *socket_path, const char *pidfile_path, if (!uuidd_cxt->no_sock) /* no_sock implies no_fork and no_pid */ #endif { - static timer_t t_id; + struct ul_timer timer; struct itimerval timeout; memset(&timeout, 0, sizeof timeout); timeout.it_value.tv_sec = 30; - if (setup_timer(&t_id, &timeout, &timeout_handler)) + if (setup_timer(&timer, &timeout, &timeout_handler)) err(EXIT_FAILURE, _("cannot set up timer")); if (pidfile_path) fd_pidfile = create_pidfile(uuidd_cxt, pidfile_path); ret = call_daemon(socket_path, UUIDD_OP_GETPID, reply_buf, sizeof(reply_buf), 0, NULL); - cancel_timer(&t_id); + cancel_timer(&timer); if (ret > 0) { if (!uuidd_cxt->quiet) warnx(_("uuidd daemon is already running at pid %s"), diff --git a/sys-utils/flock.c b/sys-utils/flock.c index ed25230b9..57153c8d9 100644 --- a/sys-utils/flock.c +++ b/sys-utils/flock.c @@ -81,7 +81,9 @@ static void timeout_handler(int sig __attribute__((__unused__)), siginfo_t *info, void *context __attribute__((__unused__))) { +#ifdef HAVE_TIMER_CREATE if (info->si_code == SI_TIMER) +#endif timeout_expired = 1; } @@ -124,7 +126,7 @@ static void __attribute__((__noreturn__)) run_program(char **cmd_argv) int main(int argc, char *argv[]) { - static timer_t t_id; + struct ul_timer timer; struct itimerval timeout; int have_timeout = 0; int type = LOCK_EX; @@ -268,7 +270,7 @@ int main(int argc, char *argv[]) have_timeout = 0; block = LOCK_NB; } else - if (setup_timer(&t_id, &timeout, &timeout_handler)) + if (setup_timer(&timer, &timeout, &timeout_handler)) err(EX_OSERR, _("cannot set up timer")); } @@ -321,7 +323,7 @@ int main(int argc, char *argv[]) } if (have_timeout) - cancel_timer(&t_id); + cancel_timer(&timer); if (verbose) { struct timeval delta; diff --git a/sys-utils/fstrim.8 b/sys-utils/fstrim.8 index ff572a44b..f7e7aa9ef 100644 --- a/sys-utils/fstrim.8 +++ b/sys-utils/fstrim.8 @@ -48,7 +48,8 @@ KB (=1000), MB (=1000*1000), and so on for GB, TB, PB, EB, ZB and YB. .IP "\fB\-A, \-\-fstab\fP" Trim all mounted filesystems mentioned in \fI/etc/fstab\fR on devices that support the -discard operation. +discard operation. The root filesystem is determined from kernel command line if missing +in the file. The other supplied options, like \fB\-\-offset\fR, \fB\-\-length\fR and \fB-\-minimum\fR, are applied to all these devices. Errors from filesystems that do not support the discard operation are silently diff --git a/sys-utils/fstrim.c b/sys-utils/fstrim.c index b1823f5c5..00652cbb1 100644 --- a/sys-utils/fstrim.c +++ b/sys-utils/fstrim.c @@ -242,9 +242,26 @@ static int fstrim_all(struct fstrim_control *ctl) mnt_table_uniq_fs(tab, MNT_UNIQ_FORWARD, uniq_fs_source_cmp); if (ctl->fstab) { + char *rootdev = NULL; + cache = mnt_new_cache(); if (!cache) err(MNT_EX_FAIL, _("failed to initialize libmount cache")); + + /* Make sure we trim also root FS on --fstab */ + if (mnt_table_find_target(tab, "/", MNT_ITER_FORWARD) == NULL && + mnt_guess_system_root(0, cache, &rootdev) == 0) { + + fs = mnt_new_fs(); + if (!fs) + err(MNT_EX_FAIL, _("failed to allocate FS handler")); + mnt_fs_set_target(fs, "/"); + mnt_fs_set_source(fs, rootdev); + mnt_fs_set_fstype(fs, "auto"); + mnt_table_add_fs(tab, fs); + mnt_unref_fs(fs); + fs = NULL; + } } while (mnt_table_next_fs(tab, itr, &fs) == 0) { |