summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/TODO6
-rw-r--r--configure.ac26
-rw-r--r--include/c.h4
-rw-r--r--include/list.h21
-rw-r--r--include/path.h2
-rw-r--r--include/sysfs.h1
-rw-r--r--include/timer.h15
-rw-r--r--lib/Makemodule.am8
-rw-r--r--lib/path.c22
-rw-r--r--lib/sysfs.c14
-rw-r--r--lib/timer.c40
-rw-r--r--libmount/docs/libmount-sections.txt1
-rw-r--r--libmount/src/libmount.h.in2
-rw-r--r--libmount/src/libmount.sym4
-rw-r--r--libmount/src/mountP.h1
-rw-r--r--libmount/src/utils.c7
-rw-r--r--libsmartcols/docs/libsmartcols-sections.txt13
-rw-r--r--libsmartcols/samples/Makemodule.am9
-rw-r--r--libsmartcols/samples/grouping-overlay.c137
-rw-r--r--libsmartcols/samples/grouping-simple.c144
-rw-r--r--libsmartcols/src/Makemodule.am7
-rw-r--r--libsmartcols/src/buffer.c152
-rw-r--r--libsmartcols/src/calculate.c405
-rw-r--r--libsmartcols/src/column.c1
-rw-r--r--libsmartcols/src/fput.c97
-rw-r--r--libsmartcols/src/grouping.c587
-rw-r--r--libsmartcols/src/init.c1
-rw-r--r--libsmartcols/src/libsmartcols.h.in12
-rw-r--r--libsmartcols/src/libsmartcols.sym12
-rw-r--r--libsmartcols/src/line.c23
-rw-r--r--libsmartcols/src/print-api.c208
-rw-r--r--libsmartcols/src/print.c1050
-rw-r--r--libsmartcols/src/smartcolsP.h214
-rw-r--r--libsmartcols/src/symbols.c138
-rw-r--r--libsmartcols/src/table.c97
-rw-r--r--libsmartcols/src/table_print.c1748
-rw-r--r--misc-utils/Makemodule.am1
-rw-r--r--misc-utils/lsblk-devtree.c469
-rw-r--r--misc-utils/lsblk-mnt.c34
-rw-r--r--misc-utils/lsblk-properties.c50
-rw-r--r--misc-utils/lsblk.822
-rw-r--r--misc-utils/lsblk.c1154
-rw-r--r--misc-utils/lsblk.h133
-rw-r--r--misc-utils/uuidd.c8
-rw-r--r--sys-utils/flock.c8
-rw-r--r--sys-utils/fstrim.83
-rw-r--r--sys-utils/fstrim.c17
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) {