From 5113bc8b17fe2360304ee57a974d910a0557c595 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Oct 30 2018 04:54:37 +0000 Subject: import util-linux-2.23.2-59.el7 --- diff --git a/SOURCES/0150-libblkid-minix-Match-minix-superblock-types.patch b/SOURCES/0150-libblkid-minix-Match-minix-superblock-types.patch deleted file mode 100644 index cd86e04..0000000 --- a/SOURCES/0150-libblkid-minix-Match-minix-superblock-types.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 4ce5726c0ed93e7607c5076f587546c32cf83c2f Mon Sep 17 00:00:00 2001 -From: Nate Clark -Date: Wed, 4 Jan 2017 15:24:22 -0500 -Subject: [PATCH 150/152] libblkid/minix: Match minix superblock types - -All of the types in the minix super block are unsigned but in -probe_minix they were being treated as signed. This would cause some of -the extra sanity checks to pass on a non minix device. The types were -updated to match the return types of the helper functions in -disk-utils/minix_programs.h - -This can be checked by creating a swap partition with one of these UUIDs -35f1f264-2468-471a-bc85-acc9f4bc04a3 -35f1f264-6824-471a-bc85-acc9f4bc04a3 -35f1f264-2478-471a-bc85-acc9f4bc04a3 -35f1f264-7824-471a-bc85-acc9f4bc04a3 - -Prior to this change they would all be considered minix and swap by -blkid. - -Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1594681 -Upstream: http://github.com/karelzak/util-linux/commit/a9975c1072c4975ec2df958188a80d89cabc6171 -Signed-off-by: Nate Clark ---- - libblkid/src/superblocks/minix.c | 3 ++- - 1 file changed, 2 insertions(+), 1 deletion(-) - -diff --git a/libblkid/src/superblocks/minix.c b/libblkid/src/superblocks/minix.c -index 3e80e5b22..a20d51f2c 100644 ---- a/libblkid/src/superblocks/minix.c -+++ b/libblkid/src/superblocks/minix.c -@@ -87,7 +87,8 @@ static int probe_minix(blkid_probe pr, const struct blkid_idmag *mag) - - if (version <= 2) { - struct minix_super_block *sb = (struct minix_super_block *) data; -- int zones, ninodes, imaps, zmaps, firstz; -+ unsigned long zones, ninodes, imaps, zmaps; -+ off_t firstz; - - if (sb->s_imap_blocks == 0 || sb->s_zmap_blocks == 0) - return 1; --- -2.14.4 - diff --git a/SOURCES/0150-lsns-missing-ns-name-is-not-error.patch b/SOURCES/0150-lsns-missing-ns-name-is-not-error.patch new file mode 100644 index 0000000..33bf09e --- /dev/null +++ b/SOURCES/0150-lsns-missing-ns-name-is-not-error.patch @@ -0,0 +1,44 @@ +From 8694f63002c6b765e72c36fbf8ed46164d5303e5 Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Mon, 15 Aug 2016 11:02:18 +0200 +Subject: [PATCH 150/173] lsns: missing ns/ is not error +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +For example user namespace is optional it does not make sense to +ignore process completely if the ns/user file is missing. + +Reported-by: Michał Bartoszkiewicz +Upstream: http://github.com/karelzak/util-linux/commit/3082f8518f2739e9f68e660f1749acdd2b9d7a97 +Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1543428 +Signed-off-by: Karel Zak +--- + sys-utils/lsns.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/sys-utils/lsns.c b/sys-utils/lsns.c +index fb53a16a4..b8841b7a3 100644 +--- a/sys-utils/lsns.c ++++ b/sys-utils/lsns.c +@@ -204,7 +204,7 @@ static inline const struct colinfo *get_column_info(unsigned num) + return &infos[ get_column_id(num) ]; + } + +-static ino_t get_ns_ino(int dir, const char *nsname, ino_t *ino) ++static int get_ns_ino(int dir, const char *nsname, ino_t *ino) + { + struct stat st; + char path[16]; +@@ -269,7 +269,7 @@ static int read_process(struct lsns *ls, pid_t pid) + continue; + + rc = get_ns_ino(dirfd(dir), ns_names[i], &p->ns_ids[i]); +- if (rc && rc != -EACCES) ++ if (rc && rc != -EACCES && rc != -ENOENT) + goto done; + rc = 0; + } +-- +2.14.4 + diff --git a/SOURCES/0151-libblkid-minix-Sanity-check-superblock-s_state-for-v.patch b/SOURCES/0151-libblkid-minix-Sanity-check-superblock-s_state-for-v.patch deleted file mode 100644 index d646253..0000000 --- a/SOURCES/0151-libblkid-minix-Sanity-check-superblock-s_state-for-v.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 289f0cd75f168f86702913658ebe2dbfd8e987c6 Mon Sep 17 00:00:00 2001 -From: Nate Clark -Date: Wed, 4 Jan 2017 15:21:17 -0500 -Subject: [PATCH 151/152] libblkid/minix: Sanity check superblock s_state for v - 1 and 2 - -Swap devices with specific values in the uuid can look like minix -devices to blkid. Add an extra check to make sure the state of the -filesystem has valid state flags. - -A couple of offending swap uuids include: -35f1f264-137f-471a-bc85-acc9f4bc04a3 -35f1f264-7f13-471a-bc85-acc9f4bc04a3 -35f1f264-138f-471a-bc85-acc9f4bc04a3 -35f1f264-8f13-471a-bc85-acc9f4bc04a3 - -Without this change a swap device with any of those uuids would be -detected as minix and swap by blkid. - -Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1594681 -Upstream: http://github.com/karelzak/util-linux/commit/892553b1a41b449f58462f123eca2bf2c6c56b33 -Signed-off-by: Nate Clark ---- - libblkid/src/superblocks/minix.c | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/libblkid/src/superblocks/minix.c b/libblkid/src/superblocks/minix.c -index a20d51f2c..4e70fda8f 100644 ---- a/libblkid/src/superblocks/minix.c -+++ b/libblkid/src/superblocks/minix.c -@@ -93,6 +93,10 @@ static int probe_minix(blkid_probe pr, const struct blkid_idmag *mag) - if (sb->s_imap_blocks == 0 || sb->s_zmap_blocks == 0) - return 1; - -+ uint16_t state = minix_swab16(swabme, sb->s_state); -+ if ((state & (MINIX_VALID_FS | MINIX_ERROR_FS)) != state) -+ return 1; -+ - zones = version == 2 ? minix_swab32(swabme, sb->s_zones) : - minix_swab16(swabme, sb->s_nzones); - ninodes = minix_swab16(swabme, sb->s_ninodes); --- -2.14.4 - diff --git a/SOURCES/0151-lsns-Fix-parser-for-proc-pid-stat-which-is-including.patch b/SOURCES/0151-lsns-Fix-parser-for-proc-pid-stat-which-is-including.patch new file mode 100644 index 0000000..bad3a76 --- /dev/null +++ b/SOURCES/0151-lsns-Fix-parser-for-proc-pid-stat-which-is-including.patch @@ -0,0 +1,81 @@ +From 7a151a3d74b2972410103b684803e6d6b8fda15b Mon Sep 17 00:00:00 2001 +From: OGAWA Hirofumi +Date: Wed, 23 Nov 2016 14:13:34 +0900 +Subject: [PATCH 151/173] lsns: Fix parser for /proc//stat which is + including space in comm + +For example, child process of spamd has + + 32031 (spamd child) S 32026 32026 32026 0 -1 4210752 338 0 0 0 ... + +fscanf("%d %*s %c %d*[^\n]") in read_process() can't parse above as we +expected, because %s only skips non-whitespace. I.e. it parses like +following, + + 32031 (spamd child) S 32026 32026 32026 0 -1 4210752 338 0 0 0 ... + +---+ +----+ + + %d %*s %c + +and returns 2 (pid=32031, state=c). + +This fixes it by skipping task->comm part manually. + +Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1543428 +Upstream: http://github.com/karelzak/util-linux/commit/3fcbd7978980dc1a29c626b701333e27599e506d +Signed-off-by: OGAWA Hirofumi +--- + sys-utils/lsns.c | 30 ++++++++++++++++++++++++++---- + 1 file changed, 26 insertions(+), 4 deletions(-) + +diff --git a/sys-utils/lsns.c b/sys-utils/lsns.c +index b8841b7a3..d32756508 100644 +--- a/sys-utils/lsns.c ++++ b/sys-utils/lsns.c +@@ -217,6 +217,30 @@ static int get_ns_ino(int dir, const char *nsname, ino_t *ino) + return 0; + } + ++static int parse_proc_stat(FILE *fp, pid_t *pid, char *state, pid_t *ppid) ++{ ++ char *line = NULL, *p; ++ size_t len = 0; ++ int rc; ++ ++ if (getline(&line, &len, fp) < 0) { ++ rc = -errno; ++ goto error; ++ } ++ ++ p = strrchr(line, ')'); ++ if (p == NULL || ++ sscanf(line, "%d (", pid) != 1 || ++ sscanf(p, ") %c %d*[^\n]", state, ppid) != 2) { ++ rc = -EINVAL; ++ goto error; ++ } ++ rc = 0; ++ ++error: ++ free(line); ++ return rc; ++} + + static int read_process(struct lsns *ls, pid_t pid) + { +@@ -255,11 +279,9 @@ static int read_process(struct lsns *ls, pid_t pid) + rc = -errno; + goto done; + } +- rc = fscanf(f, "%d %*s %c %d*[^\n]", &p->pid, &p->state, &p->ppid); +- if (rc != 3) { +- rc = rc < 0 ? -errno : -EINVAL; ++ rc = parse_proc_stat(f, &p->pid, &p->state, &p->ppid); ++ if (rc < 0) + goto done; +- } + rc = 0; + + for (i = 0; i < ARRAY_SIZE(p->ns_ids); i++) { +-- +2.14.4 + diff --git a/SOURCES/0152-libblkid-minix-Use-same-checks-for-version-3.patch b/SOURCES/0152-libblkid-minix-Use-same-checks-for-version-3.patch deleted file mode 100644 index 31bb209..0000000 --- a/SOURCES/0152-libblkid-minix-Use-same-checks-for-version-3.patch +++ /dev/null @@ -1,90 +0,0 @@ -From 0ade71743e5f96fbdb0f4a85c3eef22b88363f13 Mon Sep 17 00:00:00 2001 -From: Nate Clark -Date: Wed, 4 Jan 2017 15:24:32 -0500 -Subject: [PATCH 152/152] libblkid/minix: Use same checks for version 3 - -fsck.minix performs the same sanity checks on all versions of the -superblock. Update the probe to perform the same sanity checks so it is -less likely a different type of filesystem will be identified as minix. - -Upstream: http://github.com/karelzak/util-linux/commit/f82c804869bb8613fa0924e3111b7eb55bb04fcd -Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1594681 -Signed-off-by: Nate Clark -Signed-off-by: Karel Zak ---- - libblkid/src/superblocks/minix.c | 38 +++++++++++++++++++++++--------------- - 1 file changed, 23 insertions(+), 15 deletions(-) - -diff --git a/libblkid/src/superblocks/minix.c b/libblkid/src/superblocks/minix.c -index 4e70fda8f..21b3bf8bb 100644 ---- a/libblkid/src/superblocks/minix.c -+++ b/libblkid/src/superblocks/minix.c -@@ -75,6 +75,9 @@ static int probe_minix(blkid_probe pr, const struct blkid_idmag *mag) - unsigned char *ext; - const unsigned char *data; - int version = 0, swabme = 0; -+ unsigned long zones, ninodes, imaps, zmaps; -+ off_t firstz; -+ size_t zone_size; - - data = blkid_probe_get_buffer(pr, 1024, - max(sizeof(struct minix_super_block), -@@ -85,14 +88,9 @@ static int probe_minix(blkid_probe pr, const struct blkid_idmag *mag) - if (version < 1) - return 1; - -+ - if (version <= 2) { - struct minix_super_block *sb = (struct minix_super_block *) data; -- unsigned long zones, ninodes, imaps, zmaps; -- off_t firstz; -- -- if (sb->s_imap_blocks == 0 || sb->s_zmap_blocks == 0) -- return 1; -- - uint16_t state = minix_swab16(swabme, sb->s_state); - if ((state & (MINIX_VALID_FS | MINIX_ERROR_FS)) != state) - return 1; -@@ -103,20 +101,30 @@ static int probe_minix(blkid_probe pr, const struct blkid_idmag *mag) - imaps = minix_swab16(swabme, sb->s_imap_blocks); - zmaps = minix_swab16(swabme, sb->s_zmap_blocks); - firstz = minix_swab16(swabme, sb->s_firstdatazone); -- -- /* sanity checks to be sure that the FS is really minix */ -- if (imaps * MINIX_BLOCK_SIZE * 8 < ninodes + 1) -- return 1; -- if (zmaps * MINIX_BLOCK_SIZE * 8 < zones - firstz + 1) -- return 1; -- -+ zone_size = sb->s_log_zone_size; - } else if (version == 3) { - struct minix3_super_block *sb = (struct minix3_super_block *) data; - -- if (sb->s_imap_blocks == 0 || sb->s_zmap_blocks == 0) -- return 1; -+ zones = minix_swab32(swabme, sb->s_zones); -+ ninodes = minix_swab32(swabme, sb->s_ninodes); -+ imaps = minix_swab16(swabme, sb->s_imap_blocks); -+ zmaps = minix_swab16(swabme, sb->s_zmap_blocks); -+ firstz = minix_swab16(swabme, sb->s_firstdatazone); -+ zone_size = sb->s_log_zone_size; - } - -+ /* sanity checks to be sure that the FS is really minix. -+ * see disk-utils/fsck.minix.c read_superblock -+ */ -+ if (zone_size != 0 || ninodes == 0 || ninodes == UINT32_MAX) -+ return 1; -+ if (imaps * MINIX_BLOCK_SIZE * 8 < ninodes + 1) -+ return 1; -+ if (firstz > (off_t) zones) -+ return 1; -+ if (zmaps * MINIX_BLOCK_SIZE * 8 < zones - firstz + 1) -+ return 1; -+ - /* unfortunately, some parts of ext3 is sometimes possible to - * interpreted as minix superblock. So check for extN magic - * string. (For extN magic string and offsets see ext.c.) --- -2.14.4 - diff --git a/SOURCES/0152-libsmartcols-add-basic-tools-necessary-for-new-versi.patch b/SOURCES/0152-libsmartcols-add-basic-tools-necessary-for-new-versi.patch new file mode 100644 index 0000000..31db3c6 --- /dev/null +++ b/SOURCES/0152-libsmartcols-add-basic-tools-necessary-for-new-versi.patch @@ -0,0 +1,757 @@ +From c465ce9765273e8e1227b192e1917826ac4eaaf7 Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Thu, 31 May 2018 11:13:31 +0200 +Subject: [PATCH 152/173] libsmartcols: add basic tools necessary for new + version + +Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1561350 +Signed-off-by: Karel Zak +--- + configure.ac | 3 + + include/carefulputc.h | 96 +++++++++++++++++++++++++++- + include/colors.h | 5 ++ + include/mbsalign.h | 9 ++- + include/strutils.h | 13 ++-- + include/ttyutils.h | 1 + + lib/Makemodule.am | 1 + + lib/color-names.c | 57 +++++++++++++++++ + lib/mbsalign.c | 172 +++++++++++++++++++++++++++++++++++++++++--------- + lib/ttyutils.c | 52 +++++++++++++++ + libfdisk/src/ask.c | 4 +- + 11 files changed, 373 insertions(+), 40 deletions(-) + create mode 100644 lib/color-names.c + +diff --git a/configure.ac b/configure.ac +index d561e01d0..8cf317dc0 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -133,6 +133,9 @@ AC_SUBST([BSD_WARN_CFLAGS]) + dnl libtool-2 + LT_INIT + ++dnl check supported linker flags ++AX_CHECK_VSCRIPT ++ + m4_ifndef([PKG_PROG_PKG_CONFIG], + [m4_fatal([Could not locate the pkg-config autoconf + macros. These are usually located in /usr/share/aclocal/pkg.m4. +diff --git a/include/carefulputc.h b/include/carefulputc.h +index a54498cfd..613d94c1e 100644 +--- a/include/carefulputc.h ++++ b/include/carefulputc.h +@@ -26,7 +26,87 @@ static inline int carefulputc(int c, FILE *fp) { + return (ret < 0) ? EOF : 0; + } + +-static inline void fputs_quoted(const char *data, FILE *out) ++/* ++ * Backported for RHEL7.6 libsmartcols ++ */ ++ ++/* ++ * Requirements enumerated via testing (V8, Firefox, IE11): ++ * ++ * var charsToEscape = []; ++ * for (var i = 0; i < 65535; i += 1) { ++ * try { ++ * JSON.parse('{"sample": "' + String.fromCodePoint(i) + '"}'); ++ * } catch (e) { ++ * charsToEscape.push(i); ++ * } ++ * } ++ */ ++static inline void fputs_quoted_case_json(const char *data, FILE *out, int dir) ++{ ++ const char *p; ++ ++ fputc('"', out); ++ for (p = data; p && *p; p++) { ++ ++ const unsigned char c = (unsigned char) *p; ++ ++ /* From http://www.json.org ++ * ++ * The double-quote and backslashes would break out a string or ++ * init an escape sequence if not escaped. ++ * ++ * Note that single-quotes and forward slashes, while they're ++ * in the JSON spec, don't break double-quoted strings. ++ */ ++ if (c == '"' || c == '\\') { ++ fputc('\\', out); ++ fputc(c, out); ++ continue; ++ } ++ ++ /* All non-control characters OK; do the case swap as required. */ ++ if (c >= 0x20) { ++ fputc(dir == 1 ? toupper(c) : ++ dir == -1 ? tolower(c) : *p, out); ++ continue; ++ } ++ ++ /* In addition, all chars under ' ' break Node's/V8/Chrome's, and ++ * Firefox's JSON.parse function ++ */ ++ switch (c) { ++ /* Handle short-hand cases to reduce output size. C ++ * has most of the same stuff here, so if there's an ++ * "Escape for C" function somewhere in the STL, we ++ * should probably be using it. ++ */ ++ case '\b': ++ fputs("\\b", out); ++ break; ++ case '\t': ++ fputs("\\t", out); ++ break; ++ case '\n': ++ fputs("\\n", out); ++ break; ++ case '\f': ++ fputs("\\f", out); ++ break; ++ case '\r': ++ fputs("\\r", out); ++ break; ++ default: ++ /* Other assorted control characters */ ++ fprintf(out, "\\u00%02x", c); ++ break; ++ } ++ } ++ fputc('"', out); ++} ++ ++ ++static inline void fputs_quoted_case(const char *data, FILE *out, int dir) + { + const char *p; + +@@ -34,16 +114,28 @@ static inline void fputs_quoted(const char *data, FILE *out) + for (p = data; p && *p; p++) { + if ((unsigned char) *p == 0x22 || /* " */ + (unsigned char) *p == 0x5c || /* \ */ ++ (unsigned char) *p == 0x60 || /* ` */ ++ (unsigned char) *p == 0x24 || /* $ */ + !isprint((unsigned char) *p) || + iscntrl((unsigned char) *p)) { + + fprintf(out, "\\x%02x", (unsigned char) *p); + } else +- fputc(*p, out); ++ fputc(dir == 1 ? toupper(*p) : ++ dir == -1 ? tolower(*p) : ++ *p, out); + } + fputc('"', out); + } + ++#define fputs_quoted(_d, _o) fputs_quoted_case(_d, _o, 0) ++#define fputs_quoted_upper(_d, _o) fputs_quoted_case(_d, _o, 1) ++#define fputs_quoted_lower(_d, _o) fputs_quoted_case(_d, _o, -1) ++ ++#define fputs_quoted_json(_d, _o) fputs_quoted_case_json(_d, _o, 0) ++#define fputs_quoted_json_upper(_d, _o) fputs_quoted_case_json(_d, _o, 1) ++#define fputs_quoted_json_lower(_d, _o) fputs_quoted_case_json(_d, _o, -1) ++ + static inline void fputs_nonblank(const char *data, FILE *out) + { + const char *p; +diff --git a/include/colors.h b/include/colors.h +index dd77bf6df..39c0edf46 100644 +--- a/include/colors.h ++++ b/include/colors.h +@@ -38,6 +38,11 @@ + + #define UL_COLOR_WHITE "\033[1;37m" + ++/* maximal length of human readable name of ESC seq. */ ++#define UL_COLORNAME_MAXSZ 32 ++ ++extern const char *color_sequence_from_colorname(const char *str); ++ + /* Initialize the global variable OUT_IS_TERM */ + extern int colors_init(void); + +diff --git a/include/mbsalign.h b/include/mbsalign.h +index 5eaf606e5..0c28e6f69 100644 +--- a/include/mbsalign.h ++++ b/include/mbsalign.h +@@ -46,11 +46,18 @@ extern size_t mbsalign (const char *src, char *dest, + size_t dest_size, size_t *width, + mbs_align_t align, int flags); + ++extern size_t mbsalign_with_padding (const char *src, char *dest, size_t dest_size, ++ size_t *width, mbs_align_t align, int flags, ++ int padchar); ++ + extern size_t mbs_safe_nwidth(const char *buf, size_t bufsz, size_t *sz); + extern size_t mbs_safe_width(const char *s); + + extern char *mbs_safe_encode(const char *s, size_t *width); +-extern char *mbs_safe_encode_to_buffer(const char *s, size_t *width, char *buf); ++extern char *mbs_safe_encode_to_buffer(const char *s, size_t *width, char *buf, const char *safechars); + extern size_t mbs_safe_encode_size(size_t bytes); + ++extern char *mbs_invalid_encode(const char *s, size_t *width); ++extern char *mbs_invalid_encode_to_buffer(const char *s, size_t *width, char *buf); ++ + #endif /* UTIL_LINUX_MBSALIGN_H */ +diff --git a/include/strutils.h b/include/strutils.h +index 1f028e4ed..822fb7d49 100644 +--- a/include/strutils.h ++++ b/include/strutils.h +@@ -6,6 +6,7 @@ + #include + #include + #include ++#include + + /* default strtoxx_or_err() exit code */ + #ifndef STRTOXX_EXIT_CODE +@@ -57,20 +58,24 @@ static inline void xstrncpy(char *dest, const char *src, size_t n) + dest[n-1] = 0; + } + +-static inline char *strdup_to_offset(void *stru, size_t offset, const char *str) ++static inline int strdup_to_offset(void *stru, size_t offset, const char *str) + { + char *n = NULL; +- char **o = (char **) ((char *) stru + offset); ++ char **o; + ++ if (!stru) ++ return -EINVAL; ++ ++ o = (char **) ((char *) stru + offset); + if (str) { + n = strdup(str); + if (!n) +- return NULL; ++ return -ENOMEM; + } + + free(*o); + *o = n; +- return n; ++ return 0; + } + + #define strdup_to_struct_member(_s, _m, _str) \ +diff --git a/include/ttyutils.h b/include/ttyutils.h +index 13495ba96..47fe34472 100644 +--- a/include/ttyutils.h ++++ b/include/ttyutils.h +@@ -47,6 +47,7 @@ struct chardata { + (ptr)->capslock = 0; \ + } while (0) + ++extern int get_terminal_dimension(int *cols, int *lines); + extern int get_terminal_width(void); + extern int get_terminal_name(int fd, const char **path, const char **name, + const char **number); +diff --git a/lib/Makemodule.am b/lib/Makemodule.am +index acae27afb..714233c40 100644 +--- a/lib/Makemodule.am ++++ b/lib/Makemodule.am +@@ -6,6 +6,7 @@ libcommon_la_SOURCES = \ + lib/blkdev.c \ + lib/canonicalize.c \ + lib/colors.c \ ++ lib/color-names.c \ + lib/crc32.c \ + lib/env.c \ + lib/idcache.c \ +diff --git a/lib/color-names.c b/lib/color-names.c +new file mode 100644 +index 000000000..cf37670a9 +--- /dev/null ++++ b/lib/color-names.c +@@ -0,0 +1,57 @@ ++ ++#include "c.h" ++#include "colors.h" ++ ++struct ul_color_name { ++ const char *name; ++ const char *seq; ++}; ++ ++/* ++ * qsort/bsearch buddy ++ */ ++static int cmp_color_name(const void *a0, const void *b0) ++{ ++ struct ul_color_name *a = (struct ul_color_name *) a0, ++ *b = (struct ul_color_name *) b0; ++ return strcmp(a->name, b->name); ++} ++ ++/* ++ * Maintains human readable color names ++ */ ++const char *color_sequence_from_colorname(const char *str) ++{ ++ static const struct ul_color_name basic_schemes[] = { ++ { "black", UL_COLOR_BLACK }, ++ { "blink", UL_COLOR_BLINK }, ++ { "blue", UL_COLOR_BLUE }, ++ { "bold", UL_COLOR_BOLD }, ++ { "brown", UL_COLOR_BROWN }, ++ { "cyan", UL_COLOR_CYAN }, ++ { "darkgray", UL_COLOR_DARK_GRAY }, ++ { "gray", UL_COLOR_GRAY }, ++ { "green", UL_COLOR_GREEN }, ++ { "halfbright", UL_COLOR_HALFBRIGHT }, ++ { "lightblue", UL_COLOR_BOLD_BLUE }, ++ { "lightcyan", UL_COLOR_BOLD_CYAN }, ++ { "lightgray,", UL_COLOR_GRAY }, ++ { "lightgreen", UL_COLOR_BOLD_GREEN }, ++ { "lightmagenta", UL_COLOR_BOLD_MAGENTA }, ++ { "lightred", UL_COLOR_BOLD_RED }, ++ { "magenta", UL_COLOR_MAGENTA }, ++ { "red", UL_COLOR_RED }, ++ { "reset", UL_COLOR_RESET, }, ++ { "reverse", UL_COLOR_REVERSE }, ++ { "yellow", UL_COLOR_BOLD_YELLOW }, ++ }; ++ struct ul_color_name key = { .name = (char *) str }, *res; ++ ++ if (!str) ++ return NULL; ++ ++ res = bsearch(&key, basic_schemes, ARRAY_SIZE(basic_schemes), ++ sizeof(struct ul_color_name), ++ cmp_color_name); ++ return res ? res->seq : NULL; ++} +diff --git a/lib/mbsalign.c b/lib/mbsalign.c +index b307d19f7..8fdab9ee9 100644 +--- a/lib/mbsalign.c ++++ b/lib/mbsalign.c +@@ -27,9 +27,9 @@ + + #include "c.h" + #include "mbsalign.h" ++#include "strutils.h" + #include "widechar.h" + +-#ifdef HAVE_WIDECHAR + /* Replace non printable chars. + Note \t and \n etc. are non printable. + Return 1 if replacement made, 0 otherwise. */ +@@ -43,17 +43,19 @@ + */ + size_t mbs_safe_nwidth(const char *buf, size_t bufsz, size_t *sz) + { +- mbstate_t st; + const char *p = buf, *last = buf; + size_t width = 0, bytes = 0; + ++#ifdef HAVE_WIDECHAR ++ mbstate_t st; + memset(&st, 0, sizeof(st)); +- ++#endif + if (p && *p && bufsz) + last = p + (bufsz - 1); + + while (p && *p && p <= last) { +- if (iscntrl((unsigned char) *p)) { ++ if ((p < last && *p == '\\' && *(p + 1) == 'x') ++ || iscntrl((unsigned char) *p)) { + width += 4, bytes += 4; /* *p encoded to \x?? */ + p++; + } +@@ -106,28 +108,36 @@ size_t mbs_safe_width(const char *s) + + /* + * Copy @s to @buf and replace control and non-printable chars with +- * \x?? hex sequence. The @width returns number of cells. ++ * \x?? hex sequence. The @width returns number of cells. The @safechars ++ * are not encoded. + * + * The @buf has to be big enough to store mbs_safe_encode_size(strlen(s))) + * bytes. + */ +-char *mbs_safe_encode_to_buffer(const char *s, size_t *width, char *buf) ++char *mbs_safe_encode_to_buffer(const char *s, size_t *width, char *buf, const char *safechars) + { +- mbstate_t st; + const char *p = s; + char *r; + size_t sz = s ? strlen(s) : 0; + ++#ifdef HAVE_WIDECHAR ++ mbstate_t st; ++ memset(&st, 0, sizeof(st)); ++#endif + if (!sz || !buf) + return NULL; + +- memset(&st, 0, sizeof(st)); +- + r = buf; + *width = 0; + + while (p && *p) { +- if (iscntrl((unsigned char) *p)) { ++ if (safechars && strchr(safechars, *p)) { ++ *r++ = *p++; ++ continue; ++ } ++ ++ if ((*p == '\\' && *(p + 1) == 'x') ++ || iscntrl((unsigned char) *p)) { + sprintf(r, "\\x%02x", (unsigned char) *p); + r += 4; + *width += 4; +@@ -152,13 +162,13 @@ char *mbs_safe_encode_to_buffer(const char *s, size_t *width, char *buf) + r += 4; + *width += 4; + } else { +- width++; ++ (*width)++; + *r++ = *p; + } + } else if (!iswprint(wc)) { + size_t i; + for (i = 0; i < len; i++) { +- sprintf(r, "\\x%02x", (unsigned char) *p); ++ sprintf(r, "\\x%02x", (unsigned char) p[i]); + r += 4; + *width += 4; + } +@@ -177,13 +187,76 @@ char *mbs_safe_encode_to_buffer(const char *s, size_t *width, char *buf) + *width += 4; + } else { + *r++ = *p++; +- *width++; ++ (*width)++; + } + #endif + } + + *r = '\0'; ++ return buf; ++} + ++/* ++ * Copy @s to @buf and replace broken sequences to \x?? hex sequence. The ++ * @width returns number of cells. The @safechars are not encoded. ++ * ++ * The @buf has to be big enough to store mbs_safe_encode_size(strlen(s))) ++ * bytes. ++ */ ++char *mbs_invalid_encode_to_buffer(const char *s, size_t *width, char *buf) ++{ ++ const char *p = s; ++ char *r; ++ size_t sz = s ? strlen(s) : 0; ++ ++#ifdef HAVE_WIDECHAR ++ mbstate_t st; ++ memset(&st, 0, sizeof(st)); ++#endif ++ if (!sz || !buf) ++ return NULL; ++ ++ r = buf; ++ *width = 0; ++ ++ while (p && *p) { ++#ifdef HAVE_WIDECHAR ++ wchar_t wc; ++ size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st); ++#else ++ size_t len = 1; ++#endif ++ ++ if (len == 0) ++ break; /* end of string */ ++ ++ if (len == (size_t) -1 || len == (size_t) -2) { ++ len = 1; ++ /* ++ * Not valid multibyte sequence -- maybe it's ++ * printable char according to the current locales. ++ */ ++ if (!isprint((unsigned char) *p)) { ++ sprintf(r, "\\x%02x", (unsigned char) *p); ++ r += 4; ++ *width += 4; ++ } else { ++ (*width)++; ++ *r++ = *p; ++ } ++ } else if (*p == '\\' && *(p + 1) == 'x') { ++ sprintf(r, "\\x%02x", (unsigned char) *p); ++ r += 4; ++ *width += 4; ++ } else { ++ memcpy(r, p, len); ++ r += len; ++ *width += wcwidth(wc); ++ } ++ p += len; ++ } ++ ++ *r = '\0'; + return buf; + } + +@@ -199,17 +272,39 @@ size_t mbs_safe_encode_size(size_t bytes) + char *mbs_safe_encode(const char *s, size_t *width) + { + size_t sz = s ? strlen(s) : 0; +- char *buf; ++ char *buf, *ret = NULL; + + if (!sz) + return NULL; + buf = malloc(mbs_safe_encode_size(sz)); +- if (!buf) +- return NULL; ++ if (buf) ++ ret = mbs_safe_encode_to_buffer(s, width, buf, NULL); ++ if (!ret) ++ free(buf); ++ return ret; ++} + +- return mbs_safe_encode_to_buffer(s, width, buf); ++/* ++ * Returns allocated string where all broken widechars chars are ++ * replaced with \x?? hex sequence. ++ */ ++char *mbs_invalid_encode(const char *s, size_t *width) ++{ ++ size_t sz = s ? strlen(s) : 0; ++ char *buf, *ret = NULL; ++ ++ if (!sz) ++ return NULL; ++ buf = malloc(mbs_safe_encode_size(sz)); ++ if (buf) ++ ret = mbs_invalid_encode_to_buffer(s, width, buf); ++ if (!ret) ++ free(buf); ++ return ret; + } + ++#ifdef HAVE_WIDECHAR ++ + static bool + wc_ensure_printable (wchar_t *wchars) + { +@@ -246,6 +341,7 @@ wc_truncate (wchar_t *wc, size_t width) + } + if (cells + next_cells > width) + break; ++ + cells += next_cells; + wc++; + } +@@ -273,7 +369,7 @@ rpl_wcswidth (const wchar_t *s, size_t n) + + return ret; + } +-#endif ++#endif /* HAVE_WIDECHAR */ + + /* Truncate multi-byte string to @width and returns number of + * bytes of the new string @str, and in @width returns number +@@ -290,7 +386,7 @@ mbs_truncate(char *str, size_t *width) + if (sz == (ssize_t) -1) + goto done; + +- wcs = malloc((sz + 1) * sizeof(wchar_t)); ++ wcs = calloc(1, (sz + 1) * sizeof(wchar_t)); + if (!wcs) + goto done; + +@@ -301,7 +397,7 @@ mbs_truncate(char *str, size_t *width) + done: + free(wcs); + #else +- if (*width < bytes) ++ if (bytes >= 0 && *width < (size_t) bytes) + bytes = *width; + #endif + if (bytes >= 0) +@@ -315,16 +411,23 @@ done: + A pointer to the terminating NUL is returned. */ + + static char* +-mbs_align_pad (char *dest, const char* dest_end, size_t n_spaces) ++mbs_align_pad (char *dest, const char* dest_end, size_t n_spaces, int padchar) + { + /* FIXME: Should we pad with "figure space" (\u2007) + if non ascii data present? */ +- while (n_spaces-- && (dest < dest_end)) +- *dest++ = ' '; ++ for (/* nothing */; n_spaces && (dest < dest_end); n_spaces--) ++ *dest++ = padchar; + *dest = '\0'; + return dest; + } + ++size_t ++mbsalign (const char *src, char *dest, size_t dest_size, ++ size_t *width, mbs_align_t align, int flags) ++{ ++ return mbsalign_with_padding(src, dest, dest_size, width, align, flags, ' '); ++} ++ + /* Align a string, SRC, in a field of *WIDTH columns, handling multi-byte + characters; write the result into the DEST_SIZE-byte buffer, DEST. + ALIGNMENT specifies whether to left- or right-justify or to center. +@@ -339,8 +442,14 @@ mbs_align_pad (char *dest, const char* dest_end, size_t n_spaces) + Update *WIDTH to indicate how many columns were used before padding. */ + + size_t +-mbsalign (const char *src, char *dest, size_t dest_size, +- size_t *width, mbs_align_t align, int flags) ++mbsalign_with_padding (const char *src, char *dest, size_t dest_size, ++ size_t *width, mbs_align_t align, ++#ifdef HAVE_WIDECHAR ++ int flags, ++#else ++ int flags __attribute__((__unused__)), ++#endif ++ int padchar) + { + size_t ret = -1; + size_t src_size = strlen (src) + 1; +@@ -350,10 +459,11 @@ mbsalign (const char *src, char *dest, size_t dest_size, + size_t n_cols = src_size - 1; + size_t n_used_bytes = n_cols; /* Not including NUL */ + size_t n_spaces = 0, space_left; ++ ++#ifdef HAVE_WIDECHAR + bool conversion = false; + bool wc_enabled = false; + +-#ifdef HAVE_WIDECHAR + /* In multi-byte locales convert to wide characters + to allow easy truncation. Also determine number + of screen columns used. */ +@@ -407,9 +517,9 @@ mbsalign (const char *src, char *dest, size_t dest_size, + n_cols = wc_truncate (str_wc, *width); + n_used_bytes = wcstombs (newstr, str_wc, src_size); + } +-#endif + + mbsalign_unibyte: ++#endif + + if (n_cols > *width) /* Unibyte truncation required. */ + { +@@ -451,14 +561,14 @@ mbsalign_unibyte: + abort(); + } + +- dest = mbs_align_pad (dest, dest_end, start_spaces); ++ dest = mbs_align_pad (dest, dest_end, start_spaces, padchar); + space_left = dest_end - dest; + dest = mempcpy (dest, str_to_print, min (n_used_bytes, space_left)); +- mbs_align_pad (dest, dest_end, end_spaces); ++ mbs_align_pad (dest, dest_end, end_spaces, padchar); + } +- ++#ifdef HAVE_WIDECHAR + mbsalign_cleanup: +- ++#endif + free (str_wc); + free (newstr); + +diff --git a/lib/ttyutils.c b/lib/ttyutils.c +index ea551e26c..91497e763 100644 +--- a/lib/ttyutils.c ++++ b/lib/ttyutils.c +@@ -9,6 +9,58 @@ + #include "c.h" + #include "ttyutils.h" + ++/* ++ * Backported for RHEL7.6 libsmartcols ++ */ ++static int get_env_int(const char *name) ++{ ++ const char *cp = getenv(name); ++ ++ if (cp) { ++ char *end = NULL; ++ long x; ++ ++ errno = 0; ++ x = strtol(cp, &end, 10); ++ ++ if (errno == 0 && end && *end == '\0' && end > cp && ++ x > 0 && x <= INT_MAX) ++ return x; ++ } ++ ++ return -1; ++} ++ ++int get_terminal_dimension(int *cols, int *lines) ++{ ++ int c = 0, l = 0; ++ ++#if defined(TIOCGWINSZ) ++ struct winsize w_win; ++ if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &w_win) == 0) { ++ c = w_win.ws_col; ++ l = w_win.ws_row; ++ } ++#elif defined(TIOCGSIZE) ++ struct ttysize t_win; ++ if (ioctl (STDOUT_FILENO, TIOCGSIZE, &t_win) == 0) { ++ c = t_win.ts_cols; ++ l = t_win.ts_lines; ++ } ++#endif ++ ++ if (cols && c <= 0) ++ c = get_env_int("COLUMNS"); ++ if (lines && l <= 0) ++ l = get_env_int("LINES"); ++ ++ if (cols) ++ *cols = c; ++ if (lines) ++ *lines = l; ++ return 0; ++} ++ + int get_terminal_width(void) + { + #ifdef TIOCGSIZE +diff --git a/libfdisk/src/ask.c b/libfdisk/src/ask.c +index cdb4d0124..a10f3dc82 100644 +--- a/libfdisk/src/ask.c ++++ b/libfdisk/src/ask.c +@@ -42,7 +42,7 @@ const char *fdisk_ask_get_query(struct fdisk_ask *ask) + int fdisk_ask_set_query(struct fdisk_ask *ask, const char *str) + { + assert(ask); +- return !strdup_to_struct_member(ask, query, str) ? -ENOMEM : 0; ++ return strdup_to_struct_member(ask, query, str); + } + + int fdisk_ask_get_type(struct fdisk_ask *ask) +@@ -90,7 +90,7 @@ const char *fdisk_ask_number_get_range(struct fdisk_ask *ask) + int fdisk_ask_number_set_range(struct fdisk_ask *ask, const char *range) + { + assert(ask); +- return !strdup_to_struct_member(ask, data.num.range, range) ? -ENOMEM : 0; ++ return strdup_to_struct_member(ask, data.num.range, range); + } + + uint64_t fdisk_ask_number_get_default(struct fdisk_ask *ask) +-- +2.14.4 + diff --git a/SOURCES/0153-libsmartcols-backport-upstream-version-v2.32-158-gc0.patch b/SOURCES/0153-libsmartcols-backport-upstream-version-v2.32-158-gc0.patch new file mode 100644 index 0000000..ab8ef8d --- /dev/null +++ b/SOURCES/0153-libsmartcols-backport-upstream-version-v2.32-158-gc0.patch @@ -0,0 +1,6320 @@ +From fc6853caa0f3e4f8f10404e58f2dbf9f0df88bd4 Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Thu, 31 May 2018 11:44:35 +0200 +Subject: [PATCH 153/173] libsmartcols: backport upstream version + v2.32-158-gc0bdff999 + +Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1561350 +Signed-off-by: Karel Zak +--- + Makefile.am | 1 + + configure.ac | 3 - + libsmartcols/Makemodule.am | 3 +- + libsmartcols/docs/Makefile.am | 4 +- + libsmartcols/docs/libsmartcols-docs.xml | 28 +- + libsmartcols/docs/libsmartcols-sections.txt | 47 +- + libsmartcols/samples/Makemodule.am | 37 + + libsmartcols/samples/continuous.c | 138 +++ + libsmartcols/samples/fromfile.c | 344 +++++++ + libsmartcols/samples/maxout.c | 56 ++ + libsmartcols/samples/title.c | 135 +++ + libsmartcols/{src/test.c => samples/tree.c} | 77 +- + libsmartcols/samples/wrap.c | 111 +++ + libsmartcols/src/Makemodule.am | 41 +- + libsmartcols/src/cell.c | 111 ++- + libsmartcols/src/column.c | 351 +++++-- + libsmartcols/src/iter.c | 2 +- + libsmartcols/src/libsmartcols.h.in | 156 +++- + libsmartcols/src/libsmartcols.sym | 72 ++ + libsmartcols/src/line.c | 173 ++-- + libsmartcols/src/smartcolsP.h | 70 +- + libsmartcols/src/symbols.c | 114 ++- + libsmartcols/src/table.c | 851 +++++++++++++---- + libsmartcols/src/table_print.c | 1342 ++++++++++++++++++++++----- + 24 files changed, 3530 insertions(+), 737 deletions(-) + create mode 100644 libsmartcols/samples/Makemodule.am + create mode 100644 libsmartcols/samples/continuous.c + create mode 100644 libsmartcols/samples/fromfile.c + create mode 100644 libsmartcols/samples/maxout.c + create mode 100644 libsmartcols/samples/title.c + rename libsmartcols/{src/test.c => samples/tree.c} (68%) + create mode 100644 libsmartcols/samples/wrap.c + +diff --git a/Makefile.am b/Makefile.am +index 67464e4b2..7d5fa10e9 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -118,6 +118,7 @@ edit_cmd = sed \ + -e 's|@VERSION[@]|$(VERSION)|g' \ + -e 's|@LIBUUID_VERSION[@]|$(LIBUUID_VERSION)|g' \ + -e 's|@LIBMOUNT_VERSION[@]|$(LIBMOUNT_VERSION)|g' \ ++ -e 's|@LIBSMARTCOLS_VERSION[@]|$(LIBSMARTCOLS_VERSION)|g' \ + -e 's|@LIBBLKID_VERSION[@]|$(LIBBLKID_VERSION)|g' + + CLEANFILES += $(PATHFILES) +diff --git a/configure.ac b/configure.ac +index 8cf317dc0..d561e01d0 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -133,9 +133,6 @@ AC_SUBST([BSD_WARN_CFLAGS]) + dnl libtool-2 + LT_INIT + +-dnl check supported linker flags +-AX_CHECK_VSCRIPT +- + m4_ifndef([PKG_PROG_PKG_CONFIG], + [m4_fatal([Could not locate the pkg-config autoconf + macros. These are usually located in /usr/share/aclocal/pkg.m4. +diff --git a/libsmartcols/Makemodule.am b/libsmartcols/Makemodule.am +index 0089712f1..012848b2b 100644 +--- a/libsmartcols/Makemodule.am ++++ b/libsmartcols/Makemodule.am +@@ -1,13 +1,14 @@ + if BUILD_LIBSMARTCOLS + + include libsmartcols/src/Makemodule.am ++include libsmartcols/samples/Makemodule.am + + if ENABLE_GTK_DOC + # Docs uses separate Makefiles + SUBDIRS += libsmartcols/docs + endif + +-# noinst for RHEL7: pkgconfig_DATA += libsmartcols/smartcols.pc ++pkgconfig_DATA += libsmartcols/smartcols.pc + PATHFILES += libsmartcols/smartcols.pc + EXTRA_DIST += libsmartcols/COPYING + +diff --git a/libsmartcols/docs/Makefile.am b/libsmartcols/docs/Makefile.am +index c5aa2237c..e8a7600e9 100644 +--- a/libsmartcols/docs/Makefile.am ++++ b/libsmartcols/docs/Makefile.am +@@ -32,7 +32,7 @@ SCAN_OPTIONS= + + # Extra options to supply to gtkdoc-mkdb. + # e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml +-MKDB_OPTIONS=--sgml-mode --output-format=xml --name-space mnt ++MKDB_OPTIONS=--sgml-mode --output-format=xml --name-space scols + + # Extra options to supply to gtkdoc-mktmpl + # e.g. MKTMPL_OPTIONS=--only-section-tmpl +@@ -67,7 +67,7 @@ HTML_IMAGES= + # e.g. content_files=running.sgml building.sgml changes-2.0.sgml + content_files = $(builddir)/version.xml + +-# SGML files where gtk-doc abbrevations (#GtkWidget) are expanded ++# SGML files where gtk-doc abbreviations (#GtkWidget) are expanded + # These files must be listed here *and* in content_files + # e.g. expand_content_files=running.sgml + expand_content_files= +diff --git a/libsmartcols/docs/libsmartcols-docs.xml b/libsmartcols/docs/libsmartcols-docs.xml +index 4976ba701..02ee1ffe1 100644 +--- a/libsmartcols/docs/libsmartcols-docs.xml ++++ b/libsmartcols/docs/libsmartcols-docs.xml +@@ -9,12 +9,12 @@ + libsmartcols Reference Manual + for libsmartcols version &version; + +- 2014 ++ 2014-2018 + Karel Zak <kzak@redhat.com> + + + +- ++ + libsmartcols Overview + + +@@ -22,7 +22,7 @@ The libsmartcols library is used for smart adaptive formatting of tabular data. + + + The library is part of the util-linux package since version 2.25 and is +-available from ftp://ftp.kernel.org/pub/linux/utils/util-linux/. ++available from https://www.kernel.org/pub/linux/utils/util-linux/. + + + +@@ -45,8 +45,28 @@ available from ftp://ftp.kernel.org/pub/linux/utils/util-linux/. + + + +- ++ + API Index + + ++ ++ Index of new symbols in 2.27 ++ ++ ++ ++ Index of new symbols in 2.28 ++ ++ ++ ++ Index of new symbols in 2.29 ++ ++ ++ ++ Index of new symbols in 2.30 ++ ++ ++ ++ Index of new symbols in 2.31 ++ ++ + +diff --git a/libsmartcols/docs/libsmartcols-sections.txt b/libsmartcols/docs/libsmartcols-sections.txt +index 2b8180c52..79786b544 100644 +--- a/libsmartcols/docs/libsmartcols-sections.txt ++++ b/libsmartcols/docs/libsmartcols-sections.txt +@@ -2,12 +2,15 @@ + cell + libscols_cell + scols_cell_copy_content ++scols_cell_get_alignment + scols_cell_get_color + scols_cell_get_data ++scols_cell_get_flags + scols_cell_get_userdata + scols_cell_refer_data + scols_cell_set_color + scols_cell_set_data ++scols_cell_set_flags + scols_cell_set_userdata + scols_cmpstr_cells + scols_reset_cell +@@ -19,20 +22,32 @@ libscols_column + scols_column_get_color + scols_column_get_flags + scols_column_get_header ++scols_column_get_json_type ++scols_column_get_safechars ++scols_column_get_table + scols_column_get_whint ++scols_column_get_width ++scols_column_is_customwrap ++scols_column_is_hidden + scols_column_is_noextremes + scols_column_is_right + scols_column_is_strict_width + scols_column_is_tree + scols_column_is_trunc ++scols_column_is_wrap + scols_column_set_cmpfunc + scols_column_set_color + scols_column_set_flags ++scols_column_set_json_type ++scols_column_set_safechars + scols_column_set_whint ++scols_column_set_wrapfunc + scols_copy_column + scols_new_column + scols_ref_column + scols_unref_column ++scols_wrapnl_chunksize ++scols_wrapnl_nextchunk + + +
+@@ -58,10 +73,13 @@ scols_line_get_ncells + scols_line_get_parent + scols_line_get_userdata + scols_line_has_children ++scols_line_is_ancestor + scols_line_next_child ++scols_line_refer_column_data + scols_line_refer_data + scols_line_remove_child + scols_line_set_color ++scols_line_set_column_data + scols_line_set_data + scols_line_set_userdata + scols_new_line +@@ -78,6 +96,8 @@ scols_ref_symbols + scols_symbols_set_branch + scols_symbols_set_right + scols_symbols_set_vertical ++scols_symbols_set_title_padding ++scols_symbols_set_cell_padding + scols_unref_symbols +
+ +@@ -87,29 +107,48 @@ libscols_table + scols_copy_table + scols_new_table + scols_ref_table ++scols_sort_table ++scols_sort_table_by_tree + scols_table_add_column + scols_table_add_line + scols_table_colors_wanted + scols_table_enable_ascii + scols_table_enable_colors ++scols_table_enable_noencoding + scols_table_enable_export ++scols_table_enable_header_repeat ++scols_table_enable_json + scols_table_enable_maxout + scols_table_enable_noheadings ++scols_table_enable_nolinesep ++scols_table_enable_nowrap + scols_table_enable_raw + scols_table_get_column + scols_table_get_column_separator + scols_table_get_line + scols_table_get_line_separator ++scols_table_get_name + scols_table_get_ncols + scols_table_get_nlines + scols_table_get_stream ++scols_table_get_symbols ++scols_table_get_termforce ++scols_table_get_termheight ++scols_table_get_termwidth ++scols_table_get_title + scols_table_is_ascii + scols_table_is_empty + scols_table_is_export ++scols_table_is_header_repeat ++scols_table_is_json + scols_table_is_maxout + scols_table_is_noheadings ++scols_table_is_noencoding ++scols_table_is_nolinesep ++scols_table_is_nowrap + scols_table_is_raw + scols_table_is_tree ++scols_table_move_column + scols_table_new_column + scols_table_new_line + scols_table_next_column +@@ -120,10 +159,14 @@ scols_table_remove_columns + scols_table_remove_line + scols_table_remove_lines + scols_table_set_column_separator ++scols_table_set_default_symbols + scols_table_set_line_separator ++scols_table_set_name + scols_table_set_stream + scols_table_set_symbols +-scols_sort_table ++scols_table_set_termforce ++scols_table_set_termheight ++scols_table_set_termwidth + scols_unref_table + + +@@ -131,6 +174,8 @@ scols_unref_table + table_print + scols_print_table + scols_print_table_to_string ++scols_table_print_range ++scols_table_print_range_to_string + + +
+diff --git a/libsmartcols/samples/Makemodule.am b/libsmartcols/samples/Makemodule.am +new file mode 100644 +index 000000000..0e0208f04 +--- /dev/null ++++ b/libsmartcols/samples/Makemodule.am +@@ -0,0 +1,37 @@ ++ ++check_PROGRAMS += \ ++ sample-scols-title \ ++ sample-scols-wrap \ ++ sample-scols-continuous \ ++ sample-scols-fromfile \ ++ sample-scols-maxout ++ ++sample_scols_cflags = $(AM_CFLAGS) $(NO_UNUSED_WARN_CFLAGS) \ ++ -I$(ul_libsmartcols_incdir) ++sample_scols_ldadd = libsmartcols.la $(LDADD) ++ ++check_PROGRAMS += sample-scols-tree ++sample_scols_tree_SOURCES = libsmartcols/samples/tree.c ++sample_scols_tree_LDADD = $(sample_scols_ldadd) libcommon.la ++sample_scols_tree_CFLAGS = $(sample_scols_cflags) ++ ++sample_scols_title_SOURCES = libsmartcols/samples/title.c ++sample_scols_title_LDADD = $(sample_scols_ldadd) libcommon.la ++sample_scols_title_CFLAGS = $(sample_scols_cflags) ++ ++sample_scols_wrap_SOURCES = libsmartcols/samples/wrap.c ++sample_scols_wrap_LDADD = $(sample_scols_ldadd) ++sample_scols_wrap_CFLAGS = $(sample_scols_cflags) ++ ++sample_scols_continuous_SOURCES = libsmartcols/samples/continuous.c ++sample_scols_continuous_LDADD = $(sample_scols_ldadd) libcommon.la ++sample_scols_continuous_CFLAGS = $(sample_scols_cflags) ++ ++sample_scols_maxout_SOURCES = libsmartcols/samples/maxout.c ++sample_scols_maxout_LDADD = $(sample_scols_ldadd) ++sample_scols_maxout_CFLAGS = $(sample_scols_cflags) ++ ++sample_scols_fromfile_SOURCES = libsmartcols/samples/fromfile.c ++sample_scols_fromfile_LDADD = $(sample_scols_ldadd) libcommon.la ++sample_scols_fromfile_CFLAGS = $(sample_scols_cflags) ++ +diff --git a/libsmartcols/samples/continuous.c b/libsmartcols/samples/continuous.c +new file mode 100644 +index 000000000..8f9d13e6b +--- /dev/null ++++ b/libsmartcols/samples/continuous.c +@@ -0,0 +1,138 @@ ++/* ++ * Copyright (C) 2016 Karel Zak ++ * ++ * This file may be redistributed under the terms of the ++ * GNU Lesser General Public License. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "c.h" ++#include "nls.h" ++#include "strutils.h" ++#include "xalloc.h" ++ ++#include "libsmartcols.h" ++ ++#define TIME_PERIOD 3.0 /* seconds */ ++ ++enum { COL_NUM, COL_DATA, COL_TIME }; ++ ++static double time_diff(struct timeval *a, struct timeval *b) ++{ ++ return (a->tv_sec - b->tv_sec) + (a->tv_usec - b->tv_usec) / 1E6; ++} ++ ++/* add columns to the @tb */ ++static void setup_columns(struct libscols_table *tb) ++{ ++ scols_table_enable_maxout(tb, 1); ++ if (!scols_table_new_column(tb, "#NUM", 0.1, SCOLS_FL_RIGHT)) ++ goto fail; ++ if (!scols_table_new_column(tb, "DATA", 0.7, 0)) ++ goto fail; ++ if (!scols_table_new_column(tb, "TIME", 0.2, 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, size_t i) ++{ ++ char *p; ++ struct libscols_line *ln = scols_table_new_line(tb, NULL); ++ ++ if (!ln) ++ err(EXIT_FAILURE, "failed to create output line"); ++ ++ xasprintf(&p, "%zu", i); ++ if (scols_line_refer_data(ln, COL_NUM, p)) ++ goto fail; ++ ++ xasprintf(&p, "data-%02zu-%02zu-%02zu-end", i + 1, i + 2, i + 3); ++ if (scols_line_refer_data(ln, COL_DATA, p)) ++ 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; ++ size_t i; ++ struct timeval last; ++ ++ scols_init_debug(0); ++ ++ tb = scols_new_table(); ++ if (!tb) ++ err(EXIT_FAILURE, "failed to create output table"); ++ ++ setup_columns(tb); ++ gettimeofday(&last, NULL); ++ ++ for (i = 0; i < 10; i++) { ++ struct libscols_line *line; ++ struct timeval now; ++ int done = 0; ++ char *timecell = xmalloc( sizeof(stringify_value(UINT_MAX)) ); ++ ++ line = add_line(tb, i); ++ ++ /* Make a reference from cell data to the buffer, then we can ++ * update cell data without any interaction with libsmartcols ++ */ ++ scols_line_refer_data(line, COL_TIME, timecell); ++ ++ do { ++ double diff; ++ ++ gettimeofday(&now, NULL); ++ diff = time_diff(&now, &last); ++ ++ if (now.tv_sec == last.tv_sec + (long) TIME_PERIOD) ++ done = 1; ++ else ++ usleep(100000); ++ ++ /* update "TIME" cell data */ ++ sprintf(timecell, "%f [%3d%%]", diff, ++ done ? 100 : (int)(diff / (TIME_PERIOD / 100.0))); ++ ++ /* Note that libsmartcols don't print \n for last line ++ * in the table, but if you print a line somewhere in ++ * the midle of the table you need ++ * ++ * scols_table_enable_nolinesep(tb, !done); ++ * ++ * to disable line breaks. In this example it's ++ * unnecessary as we print the latest line only. ++ */ ++ ++ /* print the line */ ++ scols_table_print_range(tb, line, NULL); ++ ++ if (!done) { ++ /* terminal is waiting for \n, fflush() to force output */ ++ fflush(scols_table_get_stream(tb)); ++ /* move to the begin of the line */ ++ fputc('\r', scols_table_get_stream(tb)); ++ } else ++ fputc('\n', scols_table_get_stream(tb)); ++ } while (!done); ++ ++ last = now; ++ } ++ ++ scols_unref_table(tb); ++ return EXIT_SUCCESS; ++} +diff --git a/libsmartcols/samples/fromfile.c b/libsmartcols/samples/fromfile.c +new file mode 100644 +index 000000000..c1ab728fd +--- /dev/null ++++ b/libsmartcols/samples/fromfile.c +@@ -0,0 +1,344 @@ ++/* ++ * Copyright (C) 2016 Karel Zak ++ * ++ * This file may be redistributed under the terms of the ++ * GNU Lesser General Public License. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "c.h" ++#include "nls.h" ++#include "strutils.h" ++#include "xalloc.h" ++#include "optutils.h" ++ ++#include "libsmartcols.h" ++ ++struct column_flag { ++ const char *name; ++ int mask; ++}; ++ ++static const struct column_flag flags[] = { ++ { "trunc", SCOLS_FL_TRUNC }, ++ { "tree", SCOLS_FL_TREE }, ++ { "right", SCOLS_FL_RIGHT }, ++ { "strictwidth",SCOLS_FL_STRICTWIDTH }, ++ { "noextremes", SCOLS_FL_NOEXTREMES }, ++ { "hidden", SCOLS_FL_HIDDEN }, ++ { "wrap", SCOLS_FL_WRAP }, ++ { "wrapnl", SCOLS_FL_WRAP }, ++ { "none", 0 } ++}; ++ ++static long name_to_flag(const char *name, size_t namesz) ++{ ++ size_t i; ++ ++ for (i = 0; i < ARRAY_SIZE(flags); i++) { ++ const char *cn = flags[i].name; ++ ++ if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) ++ return flags[i].mask; ++ } ++ warnx("unknown flag: %s", name); ++ return -1; ++} ++ ++static int parse_column_flags(char *str) ++{ ++ unsigned long num_flags = 0; ++ ++ if (string_to_bitmask(str, &num_flags, name_to_flag)) ++ err(EXIT_FAILURE, "failed to parse column flags"); ++ ++ return num_flags; ++} ++ ++static struct libscols_column *parse_column(FILE *f) ++{ ++ size_t len = 0; ++ char *line = NULL; ++ int nlines = 0; ++ ++ struct libscols_column *cl = NULL; ++ ++ while (getline(&line, &len, f) != -1) { ++ ++ char *p = strrchr(line, '\n'); ++ if (p) ++ *p = '\0'; ++ ++ switch (nlines) { ++ case 0: /* NAME */ ++ { ++ struct libscols_cell *hr; ++ ++ cl = scols_new_column(); ++ if (!cl) ++ goto fail; ++ hr = scols_column_get_header(cl); ++ if (!hr || scols_cell_set_data(hr, line)) ++ goto fail; ++ break; ++ } ++ case 1: /* WIDTH-HINT */ ++ { ++ double whint = strtod_or_err(line, "failed to parse column whint"); ++ if (scols_column_set_whint(cl, whint)) ++ goto fail; ++ break; ++ } ++ case 2: /* FLAGS */ ++ { ++ int num_flags = parse_column_flags(line); ++ if (scols_column_set_flags(cl, num_flags)) ++ goto fail; ++ if (strcmp(line, "wrapnl") == 0) { ++ scols_column_set_wrapfunc(cl, ++ scols_wrapnl_chunksize, ++ scols_wrapnl_nextchunk, ++ NULL); ++ scols_column_set_safechars(cl, "\n"); ++ } ++ break; ++ } ++ case 3: /* COLOR */ ++ if (scols_column_set_color(cl, line)) ++ goto fail; ++ break; ++ default: ++ break; ++ } ++ ++ nlines++; ++ } ++ ++ free(line); ++ return cl; ++fail: ++ free(line); ++ scols_unref_column(cl); ++ return NULL; ++} ++ ++static int parse_column_data(FILE *f, struct libscols_table *tb, int col) ++{ ++ size_t len = 0, nlines = 0; ++ int i; ++ char *str = NULL; ++ ++ while ((i = getline(&str, &len, f)) != -1) { ++ ++ struct libscols_line *ln; ++ char *p = strrchr(str, '\n'); ++ if (p) ++ *p = '\0'; ++ ++ while ((p = strrchr(str, '\\')) && *(p + 1) == 'n') { ++ *p = '\n'; ++ memmove(p + 1, p + 2, i - (p + 2 - str)); ++ } ++ ++ ln = scols_table_get_line(tb, nlines++); ++ if (!ln) ++ break; ++ ++ scols_line_set_data(ln, col, str); ++ } ++ ++ free(str); ++ return 0; ++ ++} ++ ++static struct libscols_line *get_line_with_id(struct libscols_table *tb, ++ int col_id, const char *id) ++{ ++ struct libscols_line *ln; ++ struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD); ++ ++ while (scols_table_next_line(tb, itr, &ln) == 0) { ++ struct libscols_cell *ce = scols_line_get_cell(ln, col_id); ++ const char *data = ce ? scols_cell_get_data(ce) : NULL; ++ ++ if (data && strcmp(data, id) == 0) ++ break; ++ } ++ ++ scols_free_iter(itr); ++ return ln; ++} ++ ++static void compose_tree(struct libscols_table *tb, int parent_col, int id_col) ++{ ++ struct libscols_line *ln; ++ struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD); ++ ++ while (scols_table_next_line(tb, itr, &ln) == 0) { ++ struct libscols_line *parent = NULL; ++ struct libscols_cell *ce = scols_line_get_cell(ln, parent_col); ++ const char *data = ce ? scols_cell_get_data(ce) : NULL; ++ ++ if (data) ++ parent = get_line_with_id(tb, id_col, data); ++ if (parent) ++ scols_line_add_child(parent, ln); ++ } ++ ++ scols_free_iter(itr); ++} ++ ++ ++static void __attribute__((__noreturn__)) usage(void) ++{ ++ FILE *out = stdout; ++ fprintf(out, ++ "\n %s [options] ...\n\n", program_invocation_short_name); ++ ++ fputs(" -m, --maxout fill all terminal width\n", out); ++ fputs(" -c, --column column definition\n", out); ++ fputs(" -n, --nlines number of lines\n", out); ++ fputs(" -J, --json JSON output format\n", out); ++ fputs(" -r, --raw RAW output format\n", out); ++ fputs(" -E, --export use key=\"value\" output format\n", out); ++ fputs(" -C, --colsep set columns separator\n", out); ++ fputs(" -w, --width hardcode terminal width\n", out); ++ fputs(" -p, --tree-parent-column parent column\n", out); ++ fputs(" -i, --tree-id-column id column\n", out); ++ fputs(" -h, --help this help\n", out); ++ fputs("\n", out); ++ ++ exit(EXIT_SUCCESS); ++} ++ ++int main(int argc, char *argv[]) ++{ ++ struct libscols_table *tb; ++ int c, n, nlines = 0; ++ int parent_col = -1, id_col = -1; ++ ++ static const struct option longopts[] = { ++ { "maxout", 0, NULL, 'm' }, ++ { "column", 1, NULL, 'c' }, ++ { "nlines", 1, NULL, 'n' }, ++ { "width", 1, NULL, 'w' }, ++ { "tree-parent-column", 1, NULL, 'p' }, ++ { "tree-id-column", 1, NULL, 'i' }, ++ { "json", 0, NULL, 'J' }, ++ { "raw", 0, NULL, 'r' }, ++ { "export", 0, NULL, 'E' }, ++ { "colsep", 1, NULL, 'C' }, ++ { "help", 0, NULL, 'h' }, ++ { NULL, 0, NULL, 0 }, ++ }; ++ ++ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ ++ { 'E', 'J', 'r' }, ++ { 0 } ++ }; ++ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; ++ ++ 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, "hCc:Ei:Jmn:p:rw:", longopts, NULL)) != -1) { ++ ++ err_exclusive_options(c, longopts, excl, excl_st); ++ ++ switch(c) { ++ case 'c': /* add column from file */ ++ { ++ struct libscols_column *cl; ++ FILE *f = fopen(optarg, "r"); ++ ++ if (!f) ++ err(EXIT_FAILURE, "%s: open failed", optarg); ++ cl = parse_column(f); ++ if (cl && scols_table_add_column(tb, cl)) ++ err(EXIT_FAILURE, "%s: failed to add column", optarg); ++ scols_unref_column(cl); ++ fclose(f); ++ break; ++ } ++ case 'p': ++ parent_col = strtou32_or_err(optarg, "failed to parse tree PARENT column"); ++ break; ++ case 'i': ++ id_col = strtou32_or_err(optarg, "failed to parse tree ID column"); ++ break; ++ case 'J': ++ scols_table_enable_json(tb, 1); ++ scols_table_set_name(tb, "testtable"); ++ break; ++ case 'm': ++ scols_table_enable_maxout(tb, TRUE); ++ break; ++ case 'r': ++ scols_table_enable_raw(tb, TRUE); ++ break; ++ case 'E': ++ scols_table_enable_export(tb, TRUE); ++ break; ++ case 'C': ++ scols_table_set_column_separator(tb, optarg); ++ break; ++ case 'n': ++ nlines = strtou32_or_err(optarg, "failed to parse number of lines"); ++ 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; ++ case 'h': ++ usage(); ++ default: ++ errtryhelp(EXIT_FAILURE); ++ } ++ } ++ ++ if (nlines <= 0) ++ errx(EXIT_FAILURE, "--nlines not set"); ++ ++ for (n = 0; n < nlines; n++) { ++ struct libscols_line *ln = scols_new_line(); ++ ++ if (!ln || scols_table_add_line(tb, ln)) ++ err(EXIT_FAILURE, "failed to add a new line"); ++ ++ scols_unref_line(ln); ++ } ++ ++ n = 0; ++ ++ while (optind < argc) { ++ FILE *f = fopen(argv[optind], "r"); ++ ++ if (!f) ++ err(EXIT_FAILURE, "%s: open failed", argv[optind]); ++ ++ parse_column_data(f, tb, n); ++ optind++; ++ n++; ++ } ++ ++ if (scols_table_is_tree(tb) && parent_col >= 0 && id_col >= 0) ++ compose_tree(tb, parent_col, id_col); ++ ++ scols_table_enable_colors(tb, isatty(STDOUT_FILENO)); ++ ++ scols_print_table(tb); ++ scols_unref_table(tb); ++ return EXIT_SUCCESS; ++} +diff --git a/libsmartcols/samples/maxout.c b/libsmartcols/samples/maxout.c +new file mode 100644 +index 000000000..07a05e13f +--- /dev/null ++++ b/libsmartcols/samples/maxout.c +@@ -0,0 +1,56 @@ ++/* ++ * Copyright (C) 2016 Karel Zak ++ * ++ * This file may be redistributed under the terms of the ++ * GNU Lesser General Public License. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "c.h" ++#include "nls.h" ++#include "libsmartcols.h" ++ ++enum { COL_LEFT, COL_FOO, COL_RIGHT }; ++ ++int main(int argc, char *argv[]) ++{ ++ struct libscols_table *tb; ++ int rc = -1, nlines = 3; ++ ++ 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"); ++ ++ scols_table_enable_maxout(tb, TRUE); ++ if (!scols_table_new_column(tb, "LEFT", 0, 0)) ++ goto done; ++ if (!scols_table_new_column(tb, "FOO", 0, 0)) ++ goto done; ++ if (!scols_table_new_column(tb, "RIGHT", 0, SCOLS_FL_RIGHT)) ++ goto done; ++ ++ while (nlines--) { ++ struct libscols_line *ln = scols_table_new_line(tb, NULL); ++ ++ scols_line_set_data(ln, COL_LEFT, "A"); ++ scols_line_set_data(ln, COL_FOO, "B"); ++ scols_line_set_data(ln, COL_RIGHT, "C"); ++ } ++ ++ scols_print_table(tb); ++ rc = 0; ++done: ++ scols_unref_table(tb); ++ return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE; ++} +diff --git a/libsmartcols/samples/title.c b/libsmartcols/samples/title.c +new file mode 100644 +index 000000000..131400da4 +--- /dev/null ++++ b/libsmartcols/samples/title.c +@@ -0,0 +1,135 @@ ++/* ++ * Copyright (C) 2010-2014 Karel Zak ++ * ++ * This file may be redistributed under the terms of the ++ * GNU Lesser General Public License. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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, 0)) ++ 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 void add_line(struct libscols_table *tb, const char *name, const char *data) ++{ ++ struct libscols_line *ln = scols_table_new_line(tb, NULL); ++ 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; ++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_symbols *sy; ++ struct libscols_cell *title; ++ 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 ]\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, "foo", "bla bla bla"); ++ add_line(tb, "bar", "alb alb alb"); ++ ++ title = scols_table_get_title(tb); ++ ++ /* right */ ++ scols_cell_set_data(title, "This is right title"); ++ scols_cell_set_color(title, "red"); ++ scols_cell_set_flags(title, SCOLS_CELL_FL_RIGHT); ++ scols_print_table(tb); ++ ++ /* left without padding */ ++ scols_cell_set_data(title, "This is left title (without padding)"); ++ scols_cell_set_color(title, "yellow"); ++ scols_cell_set_flags(title, SCOLS_CELL_FL_LEFT); ++ scols_print_table(tb); ++ ++ /* center */ ++ sy = scols_new_symbols(); ++ if (!sy) ++ err_oom(); ++ scols_table_set_symbols(tb, sy); ++ scols_unref_symbols(sy); ++ ++ scols_symbols_set_title_padding(sy, "="); ++ scols_cell_set_data(title, "This is center title (with padding)"); ++ scols_cell_set_color(title, "green"); ++ scols_cell_set_flags(title, SCOLS_CELL_FL_CENTER); ++ scols_print_table(tb); ++ ++ /* left with padding */ ++ scols_symbols_set_title_padding(sy, "-"); ++ scols_cell_set_data(title, "This is left title (with padding)"); ++ scols_cell_set_color(title, "blue"); ++ scols_cell_set_flags(title, SCOLS_CELL_FL_LEFT); ++ scols_print_table(tb); ++ ++ ++ scols_unref_table(tb); ++ return EXIT_SUCCESS; ++} +diff --git a/libsmartcols/src/test.c b/libsmartcols/samples/tree.c +similarity index 68% +rename from libsmartcols/src/test.c +rename to libsmartcols/samples/tree.c +index dd87fd38b..0cdb99420 100644 +--- a/libsmartcols/src/test.c ++++ b/libsmartcols/samples/tree.c +@@ -39,7 +39,7 @@ static void setup_columns(struct libscols_table *tb, int notree) + return; + fail: + scols_unref_table(tb); +- err(EXIT_FAILURE, "faild to create output columns"); ++ err(EXIT_FAILURE, "failed to create output columns"); + } + + /* add a new line to @tb, the content is based on @st */ +@@ -104,7 +104,7 @@ fail: + return -1; + } + +-/* read all entrines from directory addressed by @fd */ ++/* read all entries from directory addressed by @fd */ + static int add_children(struct libscols_table *tb, + struct libscols_line *ln, + int fd) +@@ -142,12 +142,15 @@ static void add_lines(struct libscols_table *tb, const char *dirname) + static void __attribute__((__noreturn__)) usage(FILE *out) + { + fprintf(out, " %s [options] [ ...]\n\n", program_invocation_short_name); +- fputs(" -c, --csv display a csv-like output\n", out); +- fputs(" -i, --ascii use ascii characters only\n", out); +- fputs(" -l, --list use list format output\n", out); +- fputs(" -n, --noheadings don't print headings\n", out); +- fputs(" -p, --pairs use key=\"value\" output format\n", out); +- fputs(" -r, --raw use raw output format\n", out); ++ fputs(" -c, --csv display a csv-like output\n", out); ++ fputs(" -i, --ascii use ascii characters only\n", out); ++ fputs(" -l, --list use list format output\n", out); ++ fputs(" -n, --noheadings don't print headings\n", out); ++ fputs(" -p, --pairs use key=\"value\" output format\n", out); ++ fputs(" -J, --json use JSON output format\n", out); ++ fputs(" -r, --raw use raw output format\n", out); ++ fputs(" -S, --range-start first line to print\n", out); ++ fputs(" -E, --range-end last line to print\n", out); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); + } +@@ -155,17 +158,20 @@ static void __attribute__((__noreturn__)) usage(FILE *out) + int main(int argc, char *argv[]) + { + struct libscols_table *tb; +- int c, notree = 0; ++ int c, notree = 0, nstart = -1, nend = -1; ++ + + static const struct option longopts[] = { +- { "ascii", 0, 0, 'i' }, +- { "csv", 0, 0, 'c' }, +- { "list", 0, 0, 'l' }, +- { "noheadings", 0, 0, 'n' }, +- { "pairs", 0, 0, 'p' }, +- { "raw", 0, 0, 'r' }, +- +- { NULL, 0, 0, 0 }, ++ { "ascii", 0, NULL, 'i' }, ++ { "csv", 0, NULL, 'c' }, ++ { "list", 0, NULL, 'l' }, ++ { "noheadings", 0, NULL, 'n' }, ++ { "pairs", 0, NULL, 'p' }, ++ { "json", 0, NULL, 'J' }, ++ { "raw", 0, NULL, 'r' }, ++ { "range-start",1, NULL, 'S' }, ++ { "range-end", 1, NULL, 'E' }, ++ { NULL, 0, NULL, 0 }, + }; + + setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */ +@@ -174,9 +180,9 @@ int main(int argc, char *argv[]) + + tb = scols_new_table(); + if (!tb) +- err(EXIT_FAILURE, "faild to create output table"); ++ err(EXIT_FAILURE, "failed to create output table"); + +- while((c = getopt_long(argc, argv, "cilnpr", longopts, NULL)) != -1) { ++ while((c = getopt_long(argc, argv, "ciJlnprS:E:", longopts, NULL)) != -1) { + switch(c) { + case 'c': + scols_table_set_column_separator(tb, ","); +@@ -186,6 +192,10 @@ int main(int argc, char *argv[]) + case 'i': + scols_table_enable_ascii(tb, 1); + break; ++ case 'J': ++ scols_table_set_name(tb, "scolstest"); ++ scols_table_enable_json(tb, 1); ++ break; + case 'l': + notree = 1; + break; +@@ -200,19 +210,40 @@ int main(int argc, char *argv[]) + scols_table_enable_raw(tb, 1); + notree = 1; + break; ++ case 'S': ++ nstart = strtos32_or_err(optarg, "failed to parse range start") - 1; ++ break; ++ case 'E': ++ nend = strtos32_or_err(optarg, "failed to parse range end") - 1; ++ break; + default: + usage(stderr); + } + } + +- scols_table_enable_colors(tb, 1); ++ scols_table_enable_colors(tb, isatty(STDOUT_FILENO)); + setup_columns(tb, notree); + +- while (optind < argc) ++ if (optind == argc) ++ add_lines(tb, "."); ++ else while (optind < argc) + add_lines(tb, argv[optind++]); + +- scols_print_table(tb); +- scols_unref_table(tb); ++ if (nstart >= 0 || nend >= 0) { ++ /* print subset */ ++ struct libscols_line *start = NULL, *end = NULL; ++ ++ if (nstart >= 0) ++ start = scols_table_get_line(tb, nstart); ++ if (nend >= 0) ++ end = scols_table_get_line(tb, nend); + ++ if (start || end) ++ scols_table_print_range(tb, start, end); ++ } else ++ /* print all table */ ++ scols_print_table(tb); ++ ++ scols_unref_table(tb); + return EXIT_SUCCESS; + } +diff --git a/libsmartcols/samples/wrap.c b/libsmartcols/samples/wrap.c +new file mode 100644 +index 000000000..795bef714 +--- /dev/null ++++ b/libsmartcols/samples/wrap.c +@@ -0,0 +1,111 @@ ++/* ++ * Copyright (C) 2010-2014 Karel Zak ++ * ++ * This file may be redistributed under the terms of the ++ * GNU Lesser General Public License. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "c.h" ++#include "nls.h" ++#include "strutils.h" ++#include "xalloc.h" ++ ++#include "libsmartcols.h" ++ ++ ++enum { COL_NAME, COL_DESC, COL_FOO, COL_LIKE, COL_TEXT }; ++ ++/* 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, "DESC", 0, 0)) ++ goto fail; ++ if (!scols_table_new_column(tb, "FOO", 0, SCOLS_FL_WRAP)) ++ goto fail; ++ if (!scols_table_new_column(tb, "LIKE", 0, SCOLS_FL_RIGHT)) ++ goto fail; ++ if (!scols_table_new_column(tb, "TEXT", 0, SCOLS_FL_WRAP)) ++ goto fail; ++ return; ++fail: ++ scols_unref_table(tb); ++ err(EXIT_FAILURE, "failed to create output columns"); ++} ++ ++static char *gen_text(const char *prefix, const char *sub_prefix, char *buf, size_t sz) ++{ ++ int x = snprintf(buf, sz, "%s-%s-", prefix, sub_prefix); ++ ++ for ( ; (size_t)x < sz - 1; x++) ++ buf[x] = *prefix; ++ ++ buf[x++] = 'x'; ++ buf[x] = '\0'; ++ return buf; ++} ++ ++static struct libscols_line * add_line( struct libscols_table *tb, ++ struct libscols_line *parent, ++ const char *prefix) ++{ ++ char buf[BUFSIZ]; ++ 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, gen_text(prefix, "N", buf, 15))) ++ goto fail; ++ if (scols_line_set_data(ln, COL_DESC, gen_text(prefix, "D", buf, 10))) ++ goto fail; ++ if (scols_line_set_data(ln, COL_FOO, gen_text(prefix, "U", buf, 55))) ++ goto fail; ++ if (scols_line_set_data(ln, COL_LIKE, "1")) ++ goto fail; ++ if (scols_line_set_data(ln, COL_TEXT, gen_text(prefix, "T", buf, 50))) ++ 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, *xln; ++ ++ 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"); ++ ++ scols_table_enable_colors(tb, isatty(STDOUT_FILENO)); ++ setup_columns(tb); ++ ++ ln = add_line(tb, NULL, "A"); ++ add_line(tb, ln, "aa"); ++ add_line(tb, ln, "ab"); ++ ++ ln = add_line(tb, NULL, "B"); ++ xln = add_line(tb, ln, "ba"); ++ add_line(tb, xln, "baa"); ++ add_line(tb, xln, "bab"); ++ add_line(tb, ln, "bb"); ++ ++ scols_print_table(tb); ++ scols_unref_table(tb); ++ return EXIT_SUCCESS; ++} +diff --git a/libsmartcols/src/Makemodule.am b/libsmartcols/src/Makemodule.am +index bfe8c75c1..952d5e58f 100644 +--- a/libsmartcols/src/Makemodule.am ++++ b/libsmartcols/src/Makemodule.am +@@ -1,10 +1,10 @@ + + +-## smartcols.h is generated, so it's stored in builddir! (no distribute in RHEL7) +-#smartcolsincdir = $(includedir)/libsmartcols +-#nodist_smartcolsinc_HEADERS = $(top_builddir)/libsmartcols/src/libsmartcols.h ++# smartcols.h is generated, so it's stored in builddir! ++smartcolsincdir = $(includedir)/libsmartcols ++nodist_smartcolsinc_HEADERS = libsmartcols/src/libsmartcols.h + +-noinst_LTLIBRARIES += libsmartcols.la ++usrlib_exec_LTLIBRARIES += libsmartcols.la + libsmartcols_la_SOURCES= \ + include/list.h \ + \ +@@ -17,49 +17,32 @@ libsmartcols_la_SOURCES= \ + libsmartcols/src/table.c \ + libsmartcols/src/table_print.c \ + libsmartcols/src/version.c \ +- libsmartcols/src/init.c \ +- $(nodist_smartcolsinc_HEADERS) ++ libsmartcols/src/init.c + +-nodist_libsmartcols_la_SOURCES = libsmartcols/src/smartcolsP.h +- +-libsmartcols_la_LIBADD = libcommon.la ++libsmartcols_la_LIBADD = $(LDADD) libcommon.la + + libsmartcols_la_CFLAGS = \ ++ $(AM_CFLAGS) \ + $(SOLIB_CFLAGS) \ + -I$(ul_libsmartcols_incdir) \ + -I$(top_srcdir)/libsmartcols/src + +-libsmartcols_la_DEPENDENCIES = \ +- libcommon.la \ +- libsmartcols/src/libsmartcols.sym \ +- libsmartcols/src/libsmartcols.h.in ++EXTRA_libsmartcols_la_DEPENDENCIES = \ ++ libsmartcols/src/libsmartcols.sym + + libsmartcols_la_LDFLAGS = \ + $(SOLIB_LDFLAGS) \ + -Wl,--version-script=$(top_srcdir)/libsmartcols/src/libsmartcols.sym \ + -version-info $(LIBSMARTCOLS_VERSION_INFO) + +-EXTRA_DIST += \ +- libsmartcols/src/libsmartcols.sym \ +- libsmartcols/src/libsmartcols.h.in +- +- +-if BUILD_LIBSMARTCOLS_TESTS +-check_PROGRAMS += test_smartcols +- +-libsmartcols_tests_cflags = $(libsmartcols_la_CFLAGS) +-libsmartcols_tests_ldadd = libsmartcols.la libcommon.la +- +-test_smartcols_SOURCES = libsmartcols/src/test.c +-test_smartcols_CFLAGS = $(libsmartcols_tests_cflags) +-test_smartcols_LDADD = $(libsmartcols_tests_ldadd) +-endif # BUILD_LIBSMARTCOLS_TESTS + ++EXTRA_DIST += \ ++ libsmartcols/src/libsmartcols.sym + + # move lib from $(usrlib_execdir) to $(libdir) if needed + install-exec-hook-libsmartcols: + if test "$(usrlib_execdir)" != "$(libdir)" -a -f "$(DESTDIR)$(usrlib_execdir)/libsmartcols.so"; then \ +- mkdir -p $(DESTDIR)$(libdir); \ ++ $(MKDIR_P) $(DESTDIR)$(libdir); \ + mv $(DESTDIR)$(usrlib_execdir)/libsmartcols.so.* $(DESTDIR)$(libdir); \ + so_img_name=$$(readlink $(DESTDIR)$(usrlib_execdir)/libsmartcols.so); \ + so_img_rel_target=$$(echo $(usrlib_execdir) | sed 's,\(^/\|\)[^/][^/]*,..,g'); \ +diff --git a/libsmartcols/src/cell.c b/libsmartcols/src/cell.c +index ea41f698f..0717a2d09 100644 +--- a/libsmartcols/src/cell.c ++++ b/libsmartcols/src/cell.c +@@ -11,7 +11,7 @@ + /** + * SECTION: cell + * @title: Cell +- * @short_description: cell API ++ * @short_description: container for your data + * + * An API to access and modify per-cell data and information. Note that cell is + * always part of the line. If you destroy (un-reference) a line than it +@@ -41,8 +41,6 @@ + */ + int scols_reset_cell(struct libscols_cell *ce) + { +- assert(ce); +- + if (!ce) + return -EINVAL; + +@@ -56,34 +54,21 @@ int scols_reset_cell(struct libscols_cell *ce) + /** + * scols_cell_set_data: + * @ce: a pointer to a struct libscols_cell instance +- * @str: data (used for scols_printtable()) ++ * @data: data (used for scols_print_table()) + * +- * Stores a copy of the @str in @ce. ++ * Stores a copy of the @str in @ce, the old data are deallocated by free(). + * + * Returns: 0, a negative value in case of an error. + */ +-int scols_cell_set_data(struct libscols_cell *ce, const char *str) ++int scols_cell_set_data(struct libscols_cell *ce, const char *data) + { +- char *p = NULL; +- +- assert(ce); +- +- if (!ce) +- return -EINVAL; +- if (str) { +- p = strdup(str); +- if (!p) +- return -ENOMEM; +- } +- free(ce->data); +- ce->data = p; +- return 0; ++ return strdup_to_struct_member(ce, data, data); + } + + /** + * scols_cell_refer_data: + * @ce: a pointer to a struct libscols_cell instance +- * @str: data (used for scols_printtable()) ++ * @data: data (used for scols_print_table()) + * + * Adds a reference to @str to @ce. The pointer is deallocated by + * scols_reset_cell() or scols_unref_line(). This function is mostly designed +@@ -92,14 +77,12 @@ int scols_cell_set_data(struct libscols_cell *ce, const char *str) + * + * Returns: 0, a negative value in case of an error. + */ +-int scols_cell_refer_data(struct libscols_cell *ce, char *str) ++int scols_cell_refer_data(struct libscols_cell *ce, char *data) + { +- assert(ce); +- + if (!ce) + return -EINVAL; + free(ce->data); +- ce->data = str; ++ ce->data = data; + return 0; + } + +@@ -111,7 +94,6 @@ int scols_cell_refer_data(struct libscols_cell *ce, char *str) + */ + const char *scols_cell_get_data(const struct libscols_cell *ce) + { +- assert(ce); + return ce ? ce->data : NULL; + } + +@@ -124,8 +106,6 @@ const char *scols_cell_get_data(const struct libscols_cell *ce) + */ + int scols_cell_set_userdata(struct libscols_cell *ce, void *data) + { +- assert(ce); +- + if (!ce) + return -EINVAL; + ce->userdata = data; +@@ -140,7 +120,7 @@ int scols_cell_set_userdata(struct libscols_cell *ce, void *data) + */ + void *scols_cell_get_userdata(struct libscols_cell *ce) + { +- return ce ? ce->userdata : NULL; ++ return ce->userdata; + } + + /** +@@ -178,7 +158,7 @@ int scols_cmpstr_cells(struct libscols_cell *a, + /** + * scols_cell_set_color: + * @ce: a pointer to a struct libscols_cell instance +- * @color: ESC sequence ++ * @color: color name or ESC sequence + * + * Set the color of @ce to @color. + * +@@ -186,32 +166,70 @@ int scols_cmpstr_cells(struct libscols_cell *a, + */ + int scols_cell_set_color(struct libscols_cell *ce, const char *color) + { +- char *p = NULL; ++ if (color && isalpha(*color)) { ++ color = color_sequence_from_colorname(color); ++ if (!color) ++ return -EINVAL; ++ } ++ return strdup_to_struct_member(ce, color, color); ++} + +- assert(ce); ++/** ++ * scols_cell_get_color: ++ * @ce: a pointer to a struct libscols_cell instance ++ * ++ * Returns: the current color of @ce. ++ */ ++const char *scols_cell_get_color(const struct libscols_cell *ce) ++{ ++ return ce->color; ++} + ++/** ++ * scols_cell_set_flags: ++ * @ce: a pointer to a struct libscols_cell instance ++ * @flags: SCOLS_CELL_FL_* flags ++ * ++ * Note that cells in the table are always aligned by column flags. The cell ++ * flags are used for table title only (now). ++ * ++ * Returns: 0, a negative value in case of an error. ++ */ ++int scols_cell_set_flags(struct libscols_cell *ce, int flags) ++{ + if (!ce) + return -EINVAL; +- if (color) { +- p = strdup(color); +- if (!p) +- return -ENOMEM; +- } +- free(ce->color); +- ce->color = p; ++ ce->flags = flags; + return 0; + } + + /** +- * scols_cell_get_color: ++ * scols_cell_get_flags: + * @ce: a pointer to a struct libscols_cell instance + * +- * Returns: the current color of @ce. ++ * Returns: the current flags + */ +-const char *scols_cell_get_color(const struct libscols_cell *ce) ++int scols_cell_get_flags(const struct libscols_cell *ce) + { +- assert(ce); +- return ce ? ce->color : NULL; ++ return ce->flags; ++} ++ ++/** ++ * scols_cell_get_alignment: ++ * @ce: a pointer to a struct libscols_cell instance ++ * ++ * Since: 2.30 ++ * ++ * Returns: SCOLS_CELL_FL_{RIGHT,CELNTER,LEFT} ++ */ ++int scols_cell_get_alignment(const struct libscols_cell *ce) ++{ ++ if (ce->flags & SCOLS_CELL_FL_RIGHT) ++ return SCOLS_CELL_FL_RIGHT; ++ else if (ce->flags & SCOLS_CELL_FL_CENTER) ++ return SCOLS_CELL_FL_CENTER; ++ ++ return SCOLS_CELL_FL_LEFT; /* default */ + } + + /** +@@ -228,15 +246,12 @@ int scols_cell_copy_content(struct libscols_cell *dest, + { + int rc; + +- assert(dest); +- assert(src); +- + rc = scols_cell_set_data(dest, scols_cell_get_data(src)); + if (!rc) + rc = scols_cell_set_color(dest, scols_cell_get_color(src)); + if (!rc) + dest->userdata = src->userdata; + +- DBG(CELL, ul_debugobj((void *) src, "copy into %p", dest)); ++ DBG(CELL, ul_debugobj(src, "copy")); + return rc; + } +diff --git a/libsmartcols/src/column.c b/libsmartcols/src/column.c +index d1d10a6d0..e9d6dc404 100644 +--- a/libsmartcols/src/column.c ++++ b/libsmartcols/src/column.c +@@ -11,7 +11,7 @@ + /** + * SECTION: column + * @title: Column +- * @short_description: column API ++ * @short_description: defines output columns formats, headers, etc. + * + * An API to access and modify per-column data and information. + */ +@@ -22,6 +22,8 @@ + #include + #include + ++#include "mbsalign.h" ++ + #include "smartcolsP.h" + + /** +@@ -29,7 +31,7 @@ + * + * Allocates space for a new column. + * +- * Returns: a pointer to a new struct libscols_cell instance, NULL in case of an ENOMEM error. ++ * Returns: a pointer to a new struct libscols_column instance, NULL in case of an ENOMEM error. + */ + struct libscols_column *scols_new_column(void) + { +@@ -70,6 +72,8 @@ void scols_unref_column(struct libscols_column *cl) + list_del(&cl->cl_columns); + scols_reset_cell(&cl->header); + free(cl->color); ++ free(cl->safechars); ++ free(cl->pending_data_buf); + free(cl); + } + } +@@ -86,14 +90,13 @@ struct libscols_column *scols_copy_column(const struct libscols_column *cl) + { + struct libscols_column *ret; + +- assert (cl); + if (!cl) + return NULL; + ret = scols_new_column(); + if (!ret) + return NULL; + +- DBG(COL, ul_debugobj((void *) cl, "copy to %p", ret)); ++ DBG(COL, ul_debugobj(cl, "copy")); + + if (scols_column_set_color(ret, cl->color)) + goto err; +@@ -119,14 +122,12 @@ err: + * @cl: a pointer to a struct libscols_column instance + * @whint: a width hint + * +- * Sets the width hint of column @cl to @whint. ++ * Sets the width hint of column @cl to @whint. See scols_table_new_column(). + * + * Returns: 0, a negative value in case of an error. + */ + int scols_column_set_whint(struct libscols_column *cl, double whint) + { +- assert(cl); +- + if (!cl) + return -EINVAL; + +@@ -140,10 +141,9 @@ int scols_column_set_whint(struct libscols_column *cl, double whint) + * + * Returns: The width hint of column @cl, a negative value in case of an error. + */ +-double scols_column_get_whint(struct libscols_column *cl) ++double scols_column_get_whint(const struct libscols_column *cl) + { +- assert(cl); +- return cl ? cl->width_hint : -EINVAL; ++ return cl->width_hint; + } + + /** +@@ -157,25 +157,78 @@ double scols_column_get_whint(struct libscols_column *cl) + */ + int scols_column_set_flags(struct libscols_column *cl, int flags) + { +- assert(cl); +- + if (!cl) + return -EINVAL; + ++ if (cl->table) { ++ if (!(cl->flags & SCOLS_FL_TREE) && (flags & SCOLS_FL_TREE)) ++ cl->table->ntreecols++; ++ else if ((cl->flags & SCOLS_FL_TREE) && !(flags & SCOLS_FL_TREE)) ++ cl->table->ntreecols--; ++ } ++ + cl->flags = flags; + return 0; + } + ++/** ++ * scols_column_set_json_type: ++ * @cl: a pointer to a struct libscols_column instance ++ * @type: SCOLS_JSON_* type ++ * ++ * Sets the type used for JSON formatting, the default is SCOLS_JSON_STRING. ++ * ++ * Returns: 0, a negative value in case of an error. ++ * ++ * Since: 2.33 ++ */ ++int scols_column_set_json_type(struct libscols_column *cl, int type) ++{ ++ if (!cl) ++ return -EINVAL; ++ ++ cl->json_type = type; ++ return 0; ++ ++} ++ ++/** ++ * scols_column_get_json_type: ++ * @cl: a pointer to a struct libscols_column instance ++ * ++ * Note that SCOLS_JSON_BOOLEAN interprets NULL, empty strings, '0', 'N' and ++ * 'n' as "false"; and everything else as "true". ++ * ++ * Returns: JSON type used for formatting or a negative value in case of an error. ++ * ++ * Since: 2.33 ++ */ ++int scols_column_get_json_type(const struct libscols_column *cl) ++{ ++ return cl ? cl->json_type : -EINVAL; ++} ++ ++ ++/** ++ * scols_column_get_table: ++ * @cl: a pointer to a struct libscols_column instance ++ * ++ * Returns: pointer to the table where columns is used ++ */ ++struct libscols_table *scols_column_get_table(const struct libscols_column *cl) ++{ ++ return cl->table; ++} ++ + /** + * scols_column_get_flags: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: The flag mask of @cl, a negative value in case of an error. + */ +-int scols_column_get_flags(struct libscols_column *cl) ++int scols_column_get_flags(const struct libscols_column *cl) + { +- assert(cl); +- return cl ? cl->flags : -EINVAL; ++ return cl->flags; + } + + /** +@@ -187,14 +240,13 @@ int scols_column_get_flags(struct libscols_column *cl) + */ + struct libscols_cell *scols_column_get_header(struct libscols_column *cl) + { +- assert(cl); +- return cl ? &cl->header : NULL; ++ return &cl->header; + } + + /** + * scols_column_set_color: + * @cl: a pointer to a struct libscols_column instance +- * @color: ESC sequence ++ * @color: color name or ESC sequence + * + * The default color for data cells and column header. + * +@@ -208,20 +260,12 @@ struct libscols_cell *scols_column_get_header(struct libscols_column *cl) + */ + int scols_column_set_color(struct libscols_column *cl, const char *color) + { +- char *p = NULL; +- +- assert(cl); +- if (!cl) +- return -EINVAL; +- if (color) { +- p = strdup(color); +- if (!p) +- return -ENOMEM; ++ if (color && isalpha(*color)) { ++ color = color_sequence_from_colorname(color); ++ if (!color) ++ return -EINVAL; + } +- +- free(cl->color); +- cl->color = p; +- return 0; ++ return strdup_to_struct_member(cl, color, color); + } + + /** +@@ -230,12 +274,79 @@ int scols_column_set_color(struct libscols_column *cl, const char *color) + * + * Returns: The current color setting of the column @cl. + */ +-const char *scols_column_get_color(struct libscols_column *cl) ++const char *scols_column_get_color(const struct libscols_column *cl) ++{ ++ return cl->color; ++} ++ ++/** ++ * scols_wrapnl_nextchunk: ++ * @cl: a pointer to a struct libscols_column instance ++ * @data: string ++ * @userdata: callback private data ++ * ++ * This is built-in function for scols_column_set_wrapfunc(). This function ++ * terminates the current chunk by \0 and returns pointer to the begin of ++ * the next chunk. The chunks are based on \n. ++ * ++ * For example for data "AAA\nBBB\nCCC" the next chunk is "BBB". ++ * ++ * Returns: next chunk ++ * ++ * Since: 2.29 ++ */ ++char *scols_wrapnl_nextchunk(const struct libscols_column *cl __attribute__((unused)), ++ char *data, ++ void *userdata __attribute__((unused))) + { +- assert(cl); +- return cl ? cl->color : NULL; ++ char *p = data ? strchr(data, '\n') : NULL; ++ ++ if (p) { ++ *p = '\0'; ++ return p + 1; ++ } ++ return NULL; + } + ++/** ++ * scols_wrapnl_chunksize: ++ * @cl: a pointer to a struct libscols_column instance ++ * @data: string ++ * @userdata: callback private data ++ * ++ * Analyzes @data and returns size of the largest chunk. The chunks are based ++ * on \n. For example for data "AAA\nBBB\nCCCC" the largest chunk size is 4. ++ * ++ * Note that the size has to be based on number of terminal cells rather than ++ * bytes to support multu-byte output. ++ * ++ * Returns: size of the largest chunk. ++ * ++ * Since: 2.29 ++ */ ++size_t scols_wrapnl_chunksize(const struct libscols_column *cl __attribute__((unused)), ++ const char *data, ++ void *userdata __attribute__((unused))) ++{ ++ size_t sum = 0; ++ ++ while (data && *data) { ++ const char *p; ++ size_t sz; ++ ++ p = strchr(data, '\n'); ++ if (p) { ++ sz = mbs_safe_nwidth(data, p - data, NULL); ++ p++; ++ } else ++ sz = mbs_safe_width(data); ++ ++ sum = max(sum, sz); ++ data = p; ++ } ++ ++ return sum; ++} + + /** + * scols_column_set_cmpfunc: +@@ -251,7 +362,6 @@ int scols_column_set_cmpfunc(struct libscols_column *cl, + void *), + void *data) + { +- assert(cl); + if (!cl) + return -EINVAL; + +@@ -261,19 +371,114 @@ int scols_column_set_cmpfunc(struct libscols_column *cl, + } + + /** +- * scols_column_is_trunc: ++ * scols_column_set_wrapfunc: + * @cl: a pointer to a struct libscols_column instance ++ * @wrap_chunksize: function to return size of the largest chink of data ++ * @wrap_nextchunk: function to return next zero terminated data ++ * @userdata: optional stuff for callbacks + * +- * Gets the value of @cl's flag trunc. ++ * Extends SCOLS_FL_WRAP and allows to set custom wrap function. The default ++ * is to wrap by column size, but you can create functions to wrap for example ++ * after \n or after words, etc. ++ * ++ * Returns: 0, a negative value in case of an error. + * +- * Returns: trunc flag value, negative value in case of an error. ++ * Since: 2.29 + */ +-int scols_column_is_trunc(struct libscols_column *cl) ++int scols_column_set_wrapfunc(struct libscols_column *cl, ++ size_t (*wrap_chunksize)(const struct libscols_column *, ++ const char *, ++ void *), ++ char * (*wrap_nextchunk)(const struct libscols_column *, ++ char *, ++ void *), ++ void *userdata) + { +- assert(cl); + if (!cl) + return -EINVAL; +- return cl->flags & SCOLS_FL_TRUNC; ++ ++ cl->wrap_nextchunk = wrap_nextchunk; ++ cl->wrap_chunksize = wrap_chunksize; ++ cl->wrapfunc_data = userdata; ++ return 0; ++} ++ ++/** ++ * scols_column_set_safechars: ++ * @cl: a pointer to a struct libscols_column instance ++ * @safe: safe characters (e.g. "\n\t") ++ * ++ * Use for bytes you don't want to encode on output. This is for example ++ * necessary if you want to use custom wrap function based on \n, in this case ++ * you have to set "\n" as a safe char. ++ * ++ * Returns: 0, a negative value in case of an error. ++ * ++ * Since: 2.29 ++ */ ++int scols_column_set_safechars(struct libscols_column *cl, const char *safe) ++{ ++ return strdup_to_struct_member(cl, safechars, safe); ++} ++ ++/** ++ * scols_column_get_safechars: ++ * @cl: a pointer to a struct libscols_column instance ++ * ++ * Returns: safe chars ++ * ++ * Since: 2.29 ++ */ ++const char *scols_column_get_safechars(const struct libscols_column *cl) ++{ ++ return cl->safechars; ++} ++ ++/** ++ * scols_column_get_width: ++ * @cl: a pointer to a struct libscols_column instance ++ * ++ * Important note: the column width is unknown until library starts printing ++ * (width is calculated before printing). The function is usable for example in ++ * nextchunk() callback specified by scols_column_set_wrapfunc(). ++ * ++ * See also scols_column_get_whint(), it returns wanted size (!= final size). ++ * ++ * Returns: column width ++ * ++ * Since: 2.29 ++ */ ++size_t scols_column_get_width(const struct libscols_column *cl) ++{ ++ return cl->width; ++} ++ ++/** ++ * scols_column_is_hidden: ++ * @cl: a pointer to a struct libscols_column instance ++ * ++ * Gets the value of @cl's flag hidden. ++ * ++ * Returns: 0 or 1 ++ * ++ * Since: 2.27 ++ */ ++int scols_column_is_hidden(const struct libscols_column *cl) ++{ ++ return cl->flags & SCOLS_FL_HIDDEN ? 1 : 0; ++} ++ ++/** ++ * scols_column_is_trunc: ++ * @cl: a pointer to a struct libscols_column instance ++ * ++ * Gets the value of @cl's flag trunc. ++ * ++ * Returns: 0 or 1 ++ */ ++int scols_column_is_trunc(const struct libscols_column *cl) ++{ ++ return cl->flags & SCOLS_FL_TRUNC ? 1 : 0; + } + /** + * scols_column_is_tree: +@@ -281,14 +486,11 @@ int scols_column_is_trunc(struct libscols_column *cl) + * + * Gets the value of @cl's flag tree. + * +- * Returns: tree flag value, negative value in case of an error. ++ * Returns: 0 or 1 + */ +-int scols_column_is_tree(struct libscols_column *cl) ++int scols_column_is_tree(const struct libscols_column *cl) + { +- assert(cl); +- if (!cl) +- return -EINVAL; +- return cl->flags & SCOLS_FL_TREE; ++ return cl->flags & SCOLS_FL_TREE ? 1 : 0; + } + /** + * scols_column_is_right: +@@ -296,14 +498,11 @@ int scols_column_is_tree(struct libscols_column *cl) + * + * Gets the value of @cl's flag right. + * +- * Returns: right flag value, negative value in case of an error. ++ * Returns: 0 or 1 + */ +-int scols_column_is_right(struct libscols_column *cl) ++int scols_column_is_right(const struct libscols_column *cl) + { +- assert(cl); +- if (!cl) +- return -EINVAL; +- return cl->flags & SCOLS_FL_RIGHT; ++ return cl->flags & SCOLS_FL_RIGHT ? 1 : 0; + } + /** + * scols_column_is_strict_width: +@@ -311,14 +510,11 @@ int scols_column_is_right(struct libscols_column *cl) + * + * Gets the value of @cl's flag strict_width. + * +- * Returns: strict_width flag value, negative value in case of an error. ++ * Returns: 0 or 1 + */ +-int scols_column_is_strict_width(struct libscols_column *cl) ++int scols_column_is_strict_width(const struct libscols_column *cl) + { +- assert(cl); +- if (!cl) +- return -EINVAL; +- return cl->flags & SCOLS_FL_STRICTWIDTH; ++ return cl->flags & SCOLS_FL_STRICTWIDTH ? 1 : 0; + } + /** + * scols_column_is_noextremes: +@@ -326,12 +522,37 @@ int scols_column_is_strict_width(struct libscols_column *cl) + * + * Gets the value of @cl's flag no_extremes. + * +- * Returns: no_extremes flag value, negative value in case of an error. ++ * Returns: 0 or 1 + */ +-int scols_column_is_noextremes(struct libscols_column *cl) ++int scols_column_is_noextremes(const struct libscols_column *cl) + { +- assert(cl); +- if (!cl) +- return -EINVAL; +- return cl->flags & SCOLS_FL_NOEXTREMES; ++ return cl->flags & SCOLS_FL_NOEXTREMES ? 1 : 0; ++} ++/** ++ * scols_column_is_wrap: ++ * @cl: a pointer to a struct libscols_column instance ++ * ++ * Gets the value of @cl's flag wrap. ++ * ++ * Returns: 0 or 1 ++ * ++ * Since: 2.28 ++ */ ++int scols_column_is_wrap(const struct libscols_column *cl) ++{ ++ return cl->flags & SCOLS_FL_WRAP ? 1 : 0; ++} ++/** ++ * scols_column_is_customwrap: ++ * @cl: a pointer to a struct libscols_column instance ++ * ++ * Returns: 0 or 1 ++ * ++ * Since: 2.29 ++ */ ++int scols_column_is_customwrap(const struct libscols_column *cl) ++{ ++ return (cl->flags & SCOLS_FL_WRAP) ++ && cl->wrap_chunksize ++ && cl->wrap_nextchunk ? 1 : 0; + } +diff --git a/libsmartcols/src/iter.c b/libsmartcols/src/iter.c +index 72c7865a8..91cc08009 100644 +--- a/libsmartcols/src/iter.c ++++ b/libsmartcols/src/iter.c +@@ -68,7 +68,7 @@ void scols_reset_iter(struct libscols_iter *itr, int direction) + * + * Returns: SCOLS_INTER_{FOR,BACK}WARD + */ +-int scols_iter_get_direction(struct libscols_iter *itr) ++int scols_iter_get_direction(const struct libscols_iter *itr) + { + return itr->direction; + } +diff --git a/libsmartcols/src/libsmartcols.h.in b/libsmartcols/src/libsmartcols.h.in +index e61256022..f8be0bc04 100644 +--- a/libsmartcols/src/libsmartcols.h.in ++++ b/libsmartcols/src/libsmartcols.h.in +@@ -83,12 +83,33 @@ enum { + SCOLS_FL_RIGHT = (1 << 2), /* align to the right */ + SCOLS_FL_STRICTWIDTH = (1 << 3), /* don't reduce width if column is empty */ + SCOLS_FL_NOEXTREMES = (1 << 4), /* ignore extreme fields when count column width*/ ++ SCOLS_FL_HIDDEN = (1 << 5), /* maintain data, but don't print */ ++ SCOLS_FL_WRAP = (1 << 6) /* wrap long lines to multi-line cells */ ++}; ++ ++/* ++ * Column JSON types ++ */ ++enum { ++ SCOLS_JSON_STRING = 0, /* default */ ++ SCOLS_JSON_NUMBER = 1, ++ SCOLS_JSON_BOOLEAN = 2 ++}; ++ ++/* ++ * Cell flags, see scols_cell_set_flags() before use ++ */ ++enum { ++ /* alignment evaluated in order: right,center,left */ ++ SCOLS_CELL_FL_LEFT = 0, ++ SCOLS_CELL_FL_CENTER = (1 << 0), ++ SCOLS_CELL_FL_RIGHT = (1 << 1) + }; + + extern struct libscols_iter *scols_new_iter(int direction); + extern void scols_free_iter(struct libscols_iter *itr); + extern void scols_reset_iter(struct libscols_iter *itr, int direction); +-extern int scols_iter_get_direction(struct libscols_iter *itr); ++extern int scols_iter_get_direction(const struct libscols_iter *itr); + + /* init.c */ + extern void scols_init_debug(int mask); +@@ -101,50 +122,78 @@ extern int scols_get_library_version(const char **ver_string); + extern struct libscols_symbols *scols_new_symbols(void); + extern void scols_ref_symbols(struct libscols_symbols *sy); + extern void scols_unref_symbols(struct libscols_symbols *sy); +-extern struct libscols_symbols *scols_copy_symbols(const struct libscols_symbols *sb); +-extern int scols_symbols_set_branch(struct libscols_symbols *sb, const char *str); +-extern int scols_symbols_set_vertical(struct libscols_symbols *sb, const char *str); +-extern int scols_symbols_set_right(struct libscols_symbols *sb, const char *str); ++extern struct libscols_symbols *scols_copy_symbols(const struct libscols_symbols *sy); ++extern int scols_symbols_set_branch(struct libscols_symbols *sy, const char *str); ++extern int scols_symbols_set_vertical(struct libscols_symbols *sy, const char *str); ++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); + + /* cell.c */ + extern int scols_reset_cell(struct libscols_cell *ce); + extern int scols_cell_copy_content(struct libscols_cell *dest, + const struct libscols_cell *src); +-extern int scols_cell_set_data(struct libscols_cell *ce, const char *str); +-extern int scols_cell_refer_data(struct libscols_cell *ce, char *str); ++extern int scols_cell_set_data(struct libscols_cell *ce, const char *data); ++extern int scols_cell_refer_data(struct libscols_cell *ce, char *data); + extern const char *scols_cell_get_data(const struct libscols_cell *ce); + extern int scols_cell_set_color(struct libscols_cell *ce, const char *color); + extern const char *scols_cell_get_color(const struct libscols_cell *ce); + ++extern int scols_cell_set_flags(struct libscols_cell *ce, int flags); ++extern int scols_cell_get_flags(const struct libscols_cell *ce); ++extern int scols_cell_get_alignment(const struct libscols_cell *ce); ++ + extern void *scols_cell_get_userdata(struct libscols_cell *ce); + extern int scols_cell_set_userdata(struct libscols_cell *ce, void *data); + + extern int scols_cmpstr_cells(struct libscols_cell *a, + struct libscols_cell *b, void *data); + /* column.c */ +-extern int scols_column_is_tree(struct libscols_column *cl); +-extern int scols_column_is_trunc(struct libscols_column *cl); +-extern int scols_column_is_right(struct libscols_column *cl); +-extern int scols_column_is_strict_width(struct libscols_column *cl); +-extern int scols_column_is_noextremes(struct libscols_column *cl); ++extern int scols_column_is_tree(const struct libscols_column *cl); ++extern int scols_column_is_trunc(const struct libscols_column *cl); ++extern int scols_column_is_right(const struct libscols_column *cl); ++extern int scols_column_is_strict_width(const struct libscols_column *cl); ++extern int scols_column_is_hidden(const struct libscols_column *cl); ++extern int scols_column_is_noextremes(const struct libscols_column *cl); ++extern int scols_column_is_wrap(const struct libscols_column *cl); ++extern int scols_column_is_customwrap(const struct libscols_column *cl); ++ ++extern size_t scols_column_get_width(const struct libscols_column *cl); ++ ++extern int scols_column_set_safechars(struct libscols_column *cl, const char *safe); ++extern const char *scols_column_get_safechars(const struct libscols_column *cl); ++ ++extern int scols_column_set_json_type(struct libscols_column *cl, int type); ++extern int scols_column_get_json_type(const struct libscols_column *cl); + + extern int scols_column_set_flags(struct libscols_column *cl, int flags); +-extern int scols_column_get_flags(struct libscols_column *cl); ++extern int scols_column_get_flags(const struct libscols_column *cl); + extern struct libscols_column *scols_new_column(void); + extern void scols_ref_column(struct libscols_column *cl); + extern void scols_unref_column(struct libscols_column *cl); + extern struct libscols_column *scols_copy_column(const struct libscols_column *cl); + extern int scols_column_set_whint(struct libscols_column *cl, double whint); +-extern double scols_column_get_whint(struct libscols_column *cl); ++extern double scols_column_get_whint(const struct libscols_column *cl); + extern struct libscols_cell *scols_column_get_header(struct libscols_column *cl); + extern int scols_column_set_color(struct libscols_column *cl, const char *color); +-extern const char *scols_column_get_color(struct libscols_column *cl); ++extern const char *scols_column_get_color(const struct libscols_column *cl); ++extern struct libscols_table *scols_column_get_table(const struct libscols_column *cl); + + extern int scols_column_set_cmpfunc(struct libscols_column *cl, + int (*cmp)(struct libscols_cell *a, + struct libscols_cell *b, void *), + void *data); + ++extern int scols_column_set_wrapfunc(struct libscols_column *cl, ++ size_t (*wrap_chunksize)(const struct libscols_column *, ++ const char *, void *), ++ char * (*wrap_nextchunk)(const struct libscols_column *, ++ char *, void *), ++ void *userdata); ++ ++extern char *scols_wrapnl_nextchunk(const struct libscols_column *cl, char *data, void *userdata); ++extern size_t scols_wrapnl_chunksize(const struct libscols_column *cl, const char *data, void *userdata); ++ + /* line.c */ + extern struct libscols_line *scols_new_line(void); + extern void scols_ref_line(struct libscols_line *ln); +@@ -156,36 +205,52 @@ extern void *scols_line_get_userdata(struct libscols_line *ln); + extern int scols_line_remove_child(struct libscols_line *ln, struct libscols_line *child); + extern int scols_line_add_child(struct libscols_line *ln, struct libscols_line *child); + extern int scols_line_has_children(struct libscols_line *ln); ++extern int scols_line_is_ancestor(struct libscols_line *ln, struct libscols_line *parent); + extern int scols_line_next_child(struct libscols_line *ln, + struct libscols_iter *itr, struct libscols_line **chld); +-extern struct libscols_line *scols_line_get_parent(struct libscols_line *ln); ++extern struct libscols_line *scols_line_get_parent(const struct libscols_line *ln); + extern int scols_line_set_color(struct libscols_line *ln, const char *color); +-extern const char *scols_line_get_color(struct libscols_line *ln); +-extern size_t scols_line_get_ncells(struct libscols_line *ln); ++extern const char *scols_line_get_color(const struct libscols_line *ln); ++extern size_t scols_line_get_ncells(const struct libscols_line *ln); + extern struct libscols_cell *scols_line_get_cell(struct libscols_line *ln, size_t n); + extern struct libscols_cell *scols_line_get_column_cell( + struct libscols_line *ln, + struct libscols_column *cl); + extern int scols_line_set_data(struct libscols_line *ln, size_t n, const char *data); + extern int scols_line_refer_data(struct libscols_line *ln, size_t n, char *data); +-extern struct libscols_line *scols_copy_line(struct libscols_line *ln); ++extern int scols_line_set_column_data(struct libscols_line *ln, struct libscols_column *cl, const char *data); ++extern int scols_line_refer_column_data(struct libscols_line *ln, struct libscols_column *cl, char *data); ++extern struct libscols_line *scols_copy_line(const struct libscols_line *ln); + + /* table */ +-extern int scols_table_colors_wanted(struct libscols_table *tb); +-extern int scols_table_is_raw(struct libscols_table *tb); +-extern int scols_table_is_ascii(struct libscols_table *tb); +-extern int scols_table_is_noheadings(struct libscols_table *tb); +-extern int scols_table_is_empty(struct libscols_table *tb); +-extern int scols_table_is_export(struct libscols_table *tb); +-extern int scols_table_is_maxout(struct libscols_table *tb); +-extern int scols_table_is_tree(struct libscols_table *tb); ++extern int scols_table_colors_wanted(const struct libscols_table *tb); ++extern int scols_table_set_name(struct libscols_table *tb, const char *name); ++extern const char *scols_table_get_name(const struct libscols_table *tb); ++extern struct libscols_cell *scols_table_get_title(struct libscols_table *tb); ++extern int scols_table_is_raw(const struct libscols_table *tb); ++extern int scols_table_is_ascii(const struct libscols_table *tb); ++extern int scols_table_is_json(const struct libscols_table *tb); ++extern int scols_table_is_noheadings(const struct libscols_table *tb); ++extern int scols_table_is_header_repeat(const struct libscols_table *tb); ++extern int scols_table_is_empty(const struct libscols_table *tb); ++extern int scols_table_is_export(const struct libscols_table *tb); ++extern int scols_table_is_maxout(const struct libscols_table *tb); ++extern int scols_table_is_nowrap(const struct libscols_table *tb); ++extern int scols_table_is_nolinesep(const struct libscols_table *tb); ++extern int scols_table_is_tree(const struct libscols_table *tb); ++extern int scols_table_is_noencoding(const struct libscols_table *tb); + + extern int scols_table_enable_colors(struct libscols_table *tb, int enable); + extern int scols_table_enable_raw(struct libscols_table *tb, int enable); + extern int scols_table_enable_ascii(struct libscols_table *tb, int enable); ++extern int scols_table_enable_json(struct libscols_table *tb, int enable); + extern int scols_table_enable_noheadings(struct libscols_table *tb, int enable); ++extern int scols_table_enable_header_repeat(struct libscols_table *tb, int enable); + extern int scols_table_enable_export(struct libscols_table *tb, int enable); + extern int scols_table_enable_maxout(struct libscols_table *tb, int enable); ++extern int scols_table_enable_nowrap(struct libscols_table *tb, int enable); ++extern int scols_table_enable_nolinesep(struct libscols_table *tb, int enable); ++extern int scols_table_enable_noencoding(struct libscols_table *tb, int enable); + + extern int scols_table_set_column_separator(struct libscols_table *tb, const char *sep); + extern int scols_table_set_line_separator(struct libscols_table *tb, const char *sep); +@@ -196,12 +261,13 @@ extern void scols_unref_table(struct libscols_table *tb); + extern int scols_table_add_column(struct libscols_table *tb, struct libscols_column *cl); + extern int scols_table_remove_column(struct libscols_table *tb, struct libscols_column *cl); + extern int scols_table_remove_columns(struct libscols_table *tb); ++extern int scols_table_move_column(struct libscols_table *tb, struct libscols_column *pre, struct libscols_column *cl); + extern struct libscols_column *scols_table_new_column(struct libscols_table *tb, const char *name, double whint, int flags); + extern int scols_table_next_column(struct libscols_table *tb, struct libscols_iter *itr, struct libscols_column **cl); +-extern char *scols_table_get_column_separator(struct libscols_table *tb); +-extern char *scols_table_get_line_separator(struct libscols_table *tb); +-extern int scols_table_get_ncols(struct libscols_table *tb); +-extern int scols_table_get_nlines(struct libscols_table *tb); ++extern const char *scols_table_get_column_separator(const struct libscols_table *tb); ++extern const char *scols_table_get_line_separator(const struct libscols_table *tb); ++extern size_t scols_table_get_ncols(const struct libscols_table *tb); ++extern size_t scols_table_get_nlines(const struct libscols_table *tb); + extern struct libscols_column *scols_table_get_column(struct libscols_table *tb, size_t n); + extern int scols_table_add_line(struct libscols_table *tb, struct libscols_line *ln); + extern int scols_table_remove_line(struct libscols_table *tb, struct libscols_line *ln); +@@ -211,17 +277,43 @@ extern struct libscols_line *scols_table_new_line(struct libscols_table *tb, str + extern struct libscols_line *scols_table_get_line(struct libscols_table *tb, size_t n); + extern struct libscols_table *scols_copy_table(struct libscols_table *tb); + extern int scols_table_set_symbols(struct libscols_table *tb, struct libscols_symbols *sy); ++extern int scols_table_set_default_symbols(struct libscols_table *tb); ++extern struct libscols_symbols *scols_table_get_symbols(const struct libscols_table *tb); + + extern int scols_table_set_stream(struct libscols_table *tb, FILE *stream); +-extern FILE *scols_table_get_stream(struct libscols_table *tb); ++extern FILE *scols_table_get_stream(const struct libscols_table *tb); + extern int scols_table_reduce_termwidth(struct libscols_table *tb, size_t reduce); + + extern int scols_sort_table(struct libscols_table *tb, struct libscols_column *cl); ++extern int scols_sort_table_by_tree(struct libscols_table *tb); ++/* ++ * ++ */ ++enum { ++ SCOLS_TERMFORCE_AUTO = 0, ++ SCOLS_TERMFORCE_NEVER, ++ SCOLS_TERMFORCE_ALWAYS ++}; ++extern int scols_table_set_termforce(struct libscols_table *tb, int force); ++extern int scols_table_get_termforce(const struct libscols_table *tb); ++extern int scols_table_set_termwidth(struct libscols_table *tb, size_t width); ++extern size_t scols_table_get_termwidth(const struct libscols_table *tb); ++extern int scols_table_set_termheight(struct libscols_table *tb, size_t height); ++extern size_t scols_table_get_termheight(const struct libscols_table *tb); ++ + + /* table_print.c */ + extern int scols_print_table(struct libscols_table *tb); + extern int scols_print_table_to_string(struct libscols_table *tb, char **data); + ++extern int scols_table_print_range( struct libscols_table *tb, ++ struct libscols_line *start, ++ struct libscols_line *end); ++extern int scols_table_print_range_to_string( struct libscols_table *tb, ++ struct libscols_line *start, ++ struct libscols_line *end, ++ char **data); ++ + #ifdef __cplusplus + } + #endif +diff --git a/libsmartcols/src/libsmartcols.sym b/libsmartcols/src/libsmartcols.sym +index a4de524f4..331a55554 100644 +--- a/libsmartcols/src/libsmartcols.sym ++++ b/libsmartcols/src/libsmartcols.sym +@@ -1,5 +1,7 @@ + /* + * symbols since util-linux 2.25 ++ * ++ * Copyright (C) 2014-2016 Karel Zak + */ + SMARTCOLS_2.25 { + global: +@@ -110,3 +112,73 @@ global: + local: + *; + }; ++ ++SMARTCOLS_2.27 { ++global: ++ scols_column_is_hidden; ++ scols_table_enable_json; ++ scols_table_is_json; ++ scols_table_set_name; ++} SMARTCOLS_2.25; ++ ++SMARTCOLS_2.28 { ++global: ++ scols_column_is_wrap; ++ scols_line_refer_column_data; ++ scols_line_set_column_data; ++ scols_symbols_set_title_padding; ++ scols_table_enable_nowrap; ++ scols_table_get_title; ++ scols_cell_get_flags; ++ scols_cell_set_flags; ++ scols_table_print_range; ++ scols_table_print_range_to_string; ++ scols_table_enable_nolinesep; ++} SMARTCOLS_2.27; ++ ++SMARTCOLS_2.29 { ++global: ++ scols_column_get_safechars; ++ scols_column_get_table; ++ scols_column_get_width; ++ scols_column_is_customwrap; ++ scols_column_set_safechars; ++ scols_column_set_wrapfunc; ++ scols_symbols_set_cell_padding; ++ scols_table_get_name; ++ scols_table_get_symbols; ++ scols_table_get_termforce; ++ scols_table_get_termwidth; ++ scols_table_is_nolinesep; ++ scols_table_is_nowrap; ++ scols_table_set_default_symbols; ++ scols_table_set_termforce; ++ scols_table_set_termwidth; ++ scols_wrapnl_chunksize; ++ scols_wrapnl_nextchunk; ++} SMARTCOLS_2.28; ++ ++ ++SMARTCOLS_2.30 { ++global: ++ scols_cell_get_alignment; ++ scols_table_move_column; ++ scols_sort_table_by_tree; ++ scols_line_is_ancestor; ++} SMARTCOLS_2.29; ++ ++ ++SMARTCOLS_2.31 { ++ scols_table_set_termheight; ++ scols_table_get_termheight; ++ scols_table_is_header_repeat; ++ scols_table_enable_header_repeat; ++ scols_table_enable_noencoding; ++ scols_table_is_noencoding; ++} SMARTCOLS_2.30; ++ ++ ++SMARTCOLS_2.33 { ++ scols_column_set_json_type; ++ scols_column_get_json_type; ++} SMARTCOLS_2.31; +diff --git a/libsmartcols/src/line.c b/libsmartcols/src/line.c +index debfeab78..60be2c135 100644 +--- a/libsmartcols/src/line.c ++++ b/libsmartcols/src/line.c +@@ -11,7 +11,7 @@ + /** + * SECTION: line + * @title: Line +- * @short_description: line API ++ * @short_description: cells container, also keeps tree (parent->child) information + * + * An API to access and modify per-line data and information. + */ +@@ -71,7 +71,6 @@ void scols_ref_line(struct libscols_line *ln) + */ + void scols_unref_line(struct libscols_line *ln) + { +- + if (ln && --ln->refcount <= 0) { + DBG(CELL, ul_debugobj(ln, "dealloc")); + list_del(&ln->ln_lines); +@@ -122,8 +121,6 @@ int scols_line_alloc_cells(struct libscols_line *ln, size_t n) + { + struct libscols_cell *ce; + +- assert(ln); +- + if (!ln) + return -EINVAL; + if (ln->ncells == n) +@@ -149,6 +146,35 @@ int scols_line_alloc_cells(struct libscols_line *ln, size_t n) + return 0; + } + ++int scols_line_move_cells(struct libscols_line *ln, size_t newn, size_t oldn) ++{ ++ struct libscols_cell ce; ++ ++ if (!ln || newn >= ln->ncells || oldn >= ln->ncells) ++ return -EINVAL; ++ if (oldn == newn) ++ return 0; ++ ++ DBG(LINE, ul_debugobj(ln, "move cells[%zu] -> cells[%zu]", oldn, newn)); ++ ++ /* remember data from old position */ ++ memcpy(&ce, &ln->cells[oldn], sizeof(struct libscols_cell)); ++ ++ /* remove old position (move data behind oldn to oldn) */ ++ if (oldn + 1 < ln->ncells) ++ memmove(ln->cells + oldn, ln->cells + oldn + 1, ++ (ln->ncells - oldn - 1) * sizeof(struct libscols_cell)); ++ ++ /* create a space for new position */ ++ if (newn + 1 < ln->ncells) ++ memmove(ln->cells + newn + 1, ln->cells + newn, ++ (ln->ncells - newn - 1) * sizeof(struct libscols_cell)); ++ ++ /* copy original data to new position */ ++ memcpy(&ln->cells[newn], &ce, sizeof(struct libscols_cell)); ++ return 0; ++} ++ + /** + * scols_line_set_userdata: + * @ln: a pointer to a struct libscols_line instance +@@ -160,7 +186,6 @@ int scols_line_alloc_cells(struct libscols_line *ln, size_t n) + */ + int scols_line_set_userdata(struct libscols_line *ln, void *data) + { +- assert(ln); + if (!ln) + return -EINVAL; + ln->userdata = data; +@@ -171,12 +196,11 @@ int scols_line_set_userdata(struct libscols_line *ln, void *data) + * scols_line_get_userdata: + * @ln: a pointer to a struct libscols_line instance + * +- * Returns: 0, a negative value in case of an error. ++ * Returns: user data + */ + void *scols_line_get_userdata(struct libscols_line *ln) + { +- assert(ln); +- return ln ? ln->userdata : NULL; ++ return ln->userdata; + } + + /** +@@ -190,13 +214,10 @@ void *scols_line_get_userdata(struct libscols_line *ln) + */ + int scols_line_remove_child(struct libscols_line *ln, struct libscols_line *child) + { +- assert(ln); +- assert(child); +- + if (!ln || !child) + return -EINVAL; + +- DBG(LINE, ul_debugobj(ln, "remove child %p", child)); ++ DBG(LINE, ul_debugobj(ln, "remove child")); + + list_del_init(&child->ln_children); + child->parent = NULL; +@@ -217,26 +238,22 @@ int scols_line_remove_child(struct libscols_line *ln, struct libscols_line *chil + */ + int scols_line_add_child(struct libscols_line *ln, struct libscols_line *child) + { +- assert(ln); +- assert(child); +- + if (!ln || !child) + return -EINVAL; + ++ DBG(LINE, ul_debugobj(ln, "add child")); ++ scols_ref_line(child); ++ scols_ref_line(ln); ++ + /* unref old<->parent */ + if (child->parent) + scols_line_remove_child(child->parent, child); + +- DBG(LINE, ul_debugobj(ln, "add child %p", child)); +- + /* new reference from parent to child */ + list_add_tail(&child->ln_children, &ln->ln_branch); +- scols_ref_line(child); + + /* new reference from child to parent */ + child->parent = ln; +- scols_ref_line(ln); +- + return 0; + } + +@@ -246,9 +263,8 @@ int scols_line_add_child(struct libscols_line *ln, struct libscols_line *child) + * + * Returns: a pointer to @ln's parent, NULL in case it has no parent or if there was an error. + */ +-struct libscols_line *scols_line_get_parent(struct libscols_line *ln) ++struct libscols_line *scols_line_get_parent(const struct libscols_line *ln) + { +- assert(ln); + return ln ? ln->parent : NULL; + } + +@@ -260,7 +276,6 @@ struct libscols_line *scols_line_get_parent(struct libscols_line *ln) + */ + int scols_line_has_children(struct libscols_line *ln) + { +- assert(ln); + return ln ? !list_empty(&ln->ln_branch) : 0; + } + +@@ -294,29 +309,44 @@ int scols_line_next_child(struct libscols_line *ln, + return rc; + } + ++ ++/** ++ * scols_line_is_ancestor: ++ * @ln: line ++ * @parent: potential parent ++ * ++ * The function is designed to detect circular dependencies between @ln and ++ * @parent. It checks if @ln is not any (grand) parent in the @parent's tree. ++ * ++ * Since: 2.30 ++ * ++ * Returns: 0 or 1 ++ */ ++int scols_line_is_ancestor(struct libscols_line *ln, struct libscols_line *parent) ++{ ++ while (parent) { ++ if (parent == ln) ++ return 1; ++ parent = scols_line_get_parent(parent); ++ }; ++ return 0; ++} ++ + /** + * scols_line_set_color: + * @ln: a pointer to a struct libscols_line instance +- * @color: ESC sequence ++ * @color: color name or ESC sequence + * + * Returns: 0, a negative value in case of an error. + */ + int scols_line_set_color(struct libscols_line *ln, const char *color) + { +- char *p = NULL; +- +- assert(ln); +- if (!ln) +- return -EINVAL; +- if (color) { +- p = strdup(color); +- if (!p) +- return -ENOMEM; ++ if (color && isalnum(*color)) { ++ color = color_sequence_from_colorname(color); ++ if (!color) ++ return -EINVAL; + } +- +- free(ln->color); +- ln->color = p; +- return 0; ++ return strdup_to_struct_member(ln, color, color); + } + + /** +@@ -325,22 +355,20 @@ int scols_line_set_color(struct libscols_line *ln, const char *color) + * + * Returns: @ln's color string, NULL in case of an error. + */ +-const char *scols_line_get_color(struct libscols_line *ln) ++const char *scols_line_get_color(const struct libscols_line *ln) + { +- assert(ln); +- return ln ? ln->color : NULL; ++ return ln->color; + } + + /** + * scols_line_get_ncells: + * @ln: a pointer to a struct libscols_line instance + * +- * Returns: @ln's number of cells ++ * Returns: number of cells + */ +-size_t scols_line_get_ncells(struct libscols_line *ln) ++size_t scols_line_get_ncells(const struct libscols_line *ln) + { +- assert(ln); +- return ln ? ln->ncells : 0; ++ return ln->ncells; + } + + /** +@@ -353,8 +381,6 @@ size_t scols_line_get_ncells(struct libscols_line *ln) + struct libscols_cell *scols_line_get_cell(struct libscols_line *ln, + size_t n) + { +- assert(ln); +- + if (!ln || n >= ln->ncells) + return NULL; + return &ln->cells[n]; +@@ -373,15 +399,15 @@ struct libscols_cell *scols_line_get_column_cell( + struct libscols_line *ln, + struct libscols_column *cl) + { +- assert(ln); +- assert(cl); ++ if (!ln || !cl) ++ return NULL; + + return scols_line_get_cell(ln, cl->seqnum); + } + + /** + * scols_line_set_data: +- * @ln: a pointer to a struct libscols_cell instance ++ * @ln: a pointer to a struct libscols_line instance + * @n: number of the cell, whose data is to be set + * @data: actual data to set + * +@@ -396,9 +422,28 @@ int scols_line_set_data(struct libscols_line *ln, size_t n, const char *data) + return scols_cell_set_data(ce, data); + } + ++/** ++ * scols_line_set_column_data: ++ * @ln: a pointer to a struct libscols_line instance ++ * @cl: column, whose data is to be set ++ * @data: actual data to set ++ * ++ * The same as scols_line_set_data() but cell is referenced by column object. ++ * ++ * Returns: 0, a negative value in case of an error. ++ * ++ * Since: 2.28 ++ */ ++int scols_line_set_column_data(struct libscols_line *ln, ++ struct libscols_column *cl, ++ const char *data) ++{ ++ return scols_line_set_data(ln, cl->seqnum, data); ++} ++ + /** + * scols_line_refer_data: +- * @ln: a pointer to a struct libscols_cell instance ++ * @ln: a pointer to a struct libscols_line instance + * @n: number of the cell which will refer to @data + * @data: actual data to refer to + * +@@ -413,18 +458,36 @@ int scols_line_refer_data(struct libscols_line *ln, size_t n, char *data) + return scols_cell_refer_data(ce, data); + } + ++/** ++ * scols_line_refer_column_data: ++ * @ln: a pointer to a struct libscols_line instance ++ * @cl: column, whose data is to be set ++ * @data: actual data to refer to ++ * ++ * The same as scols_line_refer_data() but cell is referenced by column object. ++ * ++ * Returns: 0, a negative value in case of an error. ++ * ++ * Since: 2.28 ++ */ ++int scols_line_refer_column_data(struct libscols_line *ln, ++ struct libscols_column *cl, ++ char *data) ++{ ++ return scols_line_refer_data(ln, cl->seqnum, data); ++} ++ + /** + * scols_copy_line: +- * @ln: a pointer to a struct libscols_cell instance ++ * @ln: a pointer to a struct libscols_line instance + * + * Returns: A newly allocated copy of @ln, NULL in case of an error. + */ +-struct libscols_line *scols_copy_line(struct libscols_line *ln) ++struct libscols_line *scols_copy_line(const struct libscols_line *ln) + { + struct libscols_line *ret; + size_t i; + +- assert (ln); + if (!ln) + return NULL; + +@@ -440,7 +503,7 @@ struct libscols_line *scols_copy_line(struct libscols_line *ln) + ret->ncells = ln->ncells; + ret->seqnum = ln->seqnum; + +- DBG(LINE, ul_debugobj(ln, "copy to %p", ret)); ++ DBG(LINE, ul_debugobj(ln, "copy")); + + for (i = 0; i < ret->ncells; ++i) { + if (scols_cell_copy_content(&ret->cells[i], &ln->cells[i])) +@@ -452,5 +515,3 @@ err: + scols_unref_line(ret); + return NULL; + } +- +- +diff --git a/libsmartcols/src/smartcolsP.h b/libsmartcols/src/smartcolsP.h +index cea4f3101..398e6f064 100644 +--- a/libsmartcols/src/smartcolsP.h ++++ b/libsmartcols/src/smartcolsP.h +@@ -13,23 +13,18 @@ + + #include "c.h" + #include "list.h" ++#include "strutils.h" + #include "colors.h" + #include "debug.h" + +-#include "libsmartcols.h" +- +-/* features */ +-#define CONFIG_LIBSMARTCOLS_ASSERT ++#include + +-#ifdef CONFIG_LIBSMARTCOLS_ASSERT +-# include +-#else +-# define assert(x) +-#endif ++#include "libsmartcols.h" + + /* + * Debug + */ ++#define SCOLS_DEBUG_HELP (1 << 0) + #define SCOLS_DEBUG_INIT (1 << 1) + #define SCOLS_DEBUG_CELL (1 << 2) + #define SCOLS_DEBUG_LINE (1 << 3) +@@ -43,7 +38,7 @@ UL_DEBUG_DECLARE_MASK(libsmartcols); + #define ON_DBG(m, x) __UL_DBG_CALL(libsmartcols, SCOLS_DEBUG_, m, x) + #define DBG_FLUSH __UL_DBG_FLUSH(libsmartcols, SCOLS_DEBUG_) + +-#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(libsmartcols) ++#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(libsmartcols) + #include "debugobj.h" + + /* +@@ -63,6 +58,8 @@ struct libscols_symbols { + char *branch; + char *vert; + char *right; ++ char *title_padding; ++ char *cell_padding; + }; + + /* +@@ -72,8 +69,10 @@ struct libscols_cell { + char *data; + char *color; + void *userdata; ++ int flags; + }; + ++extern int scols_line_move_cells(struct libscols_line *ln, size_t newn, size_t oldn); + + /* + * Table column +@@ -86,19 +85,36 @@ struct libscols_column { + size_t width_min; /* minimal width (usually header width) */ + size_t width_max; /* maximal width */ + size_t width_avg; /* average width, used to detect extreme fields */ ++ size_t width_treeart; /* size of the tree ascii art */ + double width_hint; /* hint (N < 1 is in percent of termwidth) */ + ++ int json_type; /* SCOLS_JSON_* */ ++ + int flags; + int is_extreme; + char *color; /* default column color */ ++ char *safechars; /* do not encode this bytes */ ++ ++ char *pending_data; ++ size_t pending_data_sz; ++ char *pending_data_buf; + + int (*cmpfunc)(struct libscols_cell *, + struct libscols_cell *, + void *); /* cells comparison function */ + void *cmpfunc_data; + ++ size_t (*wrap_chunksize)(const struct libscols_column *, ++ const char *, void *); ++ char *(*wrap_nextchunk)(const struct libscols_column *, ++ char *, void *); ++ void *wrapfunc_data; ++ ++ + struct libscols_cell header; + struct list_head cl_columns; ++ ++ struct libscols_table *table; + }; + + /* +@@ -124,7 +140,8 @@ struct libscols_line { + enum { + SCOLS_FMT_HUMAN = 0, /* default, human readable */ + SCOLS_FMT_RAW, /* space separated */ +- SCOLS_FMT_EXPORT /* COLNAME="data" ... */ ++ SCOLS_FMT_EXPORT, /* COLNAME="data" ... */ ++ SCOLS_FMT_JSON /* http://en.wikipedia.org/wiki/JSON */ + }; + + /* +@@ -132,11 +149,14 @@ enum { + */ + struct libscols_table { + int refcount; ++ char *name; /* optional table name (for JSON) */ + size_t ncols; /* number of columns */ + size_t ntreecols; /* number of columns with SCOLS_FL_TREE */ + size_t nlines; /* number of lines */ +- size_t termwidth; /* terminal width */ ++ size_t termwidth; /* terminal width (number of columns) */ ++ size_t termheight; /* terminal height (number of lines) */ + size_t termreduce; /* extra blank space */ ++ int termforce; /* SCOLS_TERMFORCE_* */ + FILE *out; /* output stream */ + + char *colsep; /* column separator */ +@@ -145,15 +165,28 @@ struct libscols_table { + struct list_head tb_columns; + struct list_head tb_lines; + struct libscols_symbols *symbols; ++ struct libscols_cell title; /* optional table title (for humans) */ + ++ int indent; /* indention counter */ ++ int indent_last_sep;/* last printed has been line separator */ + int format; /* SCOLS_FMT_* */ + ++ size_t termlines_used; /* printed line counter */ ++ size_t header_next; /* where repeat header */ ++ + /* flags */ + unsigned int ascii :1, /* don't use unicode */ + colors_wanted :1, /* enable colors */ + is_term :1, /* isatty() */ +- maxout :1, /* maximalize output */ +- no_headings :1; /* don't print header */ ++ padding_debug :1, /* output visible padding chars */ ++ maxout :1, /* maximize output */ ++ header_repeat :1, /* print header after libscols_table->termheight */ ++ header_printed :1, /* header already printed */ ++ priv_symbols :1, /* default private symbols */ ++ no_headings :1, /* don't print header */ ++ no_encode :1, /* don't care about control and non-printable chars */ ++ no_linesep :1, /* don't print line separator */ ++ no_wrap :1; /* never wrap lines */ + }; + + #define IS_ITER_FORWARD(_i) ((_i)->direction == SCOLS_ITER_FORWARD) +@@ -173,4 +206,13 @@ struct libscols_table { + (itr)->p->next : (itr)->p->prev; \ + } while(0) + ++ ++static inline int scols_iter_is_last(const struct libscols_iter *itr) ++{ ++ if (!itr || !itr->head || !itr->p) ++ return 0; ++ ++ return itr->p == itr->head; ++} ++ + #endif /* _LIBSMARTCOLS_PRIVATE_H */ +diff --git a/libsmartcols/src/symbols.c b/libsmartcols/src/symbols.c +index 2b8f81dc9..6ddf1869b 100644 +--- a/libsmartcols/src/symbols.c ++++ b/libsmartcols/src/symbols.c +@@ -2,6 +2,7 @@ + * symbols.c - routines for symbol handling + * + * Copyright (C) 2014 Ondrej Oprala ++ * Copyright (C) 2016 Igor Gnatenko + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. +@@ -10,7 +11,7 @@ + /** + * SECTION: symbols + * @title: Symbols +- * @short_description: symbols API ++ * @short_description: allows to overwrite default output chars (for ascii art) + * + * An API to access and modify data and information per symbol/symbol group. + */ +@@ -61,115 +62,112 @@ void scols_unref_symbols(struct libscols_symbols *sy) + free(sy->branch); + free(sy->vert); + free(sy->right); ++ free(sy->title_padding); ++ free(sy->cell_padding); + free(sy); + } + } + + /** + * scols_symbols_set_branch: +- * @sb: a pointer to a struct libscols_symbols instance ++ * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the branch part of a tree output + * + * Returns: 0, a negative value in case of an error. + */ +-int scols_symbols_set_branch(struct libscols_symbols *sb, const char *str) ++int scols_symbols_set_branch(struct libscols_symbols *sy, const char *str) + { +- char *p = NULL; +- +- assert(sb); +- +- if (!sb) +- return -EINVAL; +- if (str) { +- p = strdup(str); +- if (!p) +- return -ENOMEM; +- } +- free(sb->branch); +- sb->branch = p; +- return 0; ++ return strdup_to_struct_member(sy, branch, str); + } + + /** + * scols_symbols_set_vertical: +- * @sb: a pointer to a struct libscols_symbols instance ++ * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the vertical part of a tree output + * + * Returns: 0, a negative value in case of an error. + */ +-int scols_symbols_set_vertical(struct libscols_symbols *sb, const char *str) ++int scols_symbols_set_vertical(struct libscols_symbols *sy, const char *str) + { +- char *p = NULL; +- +- assert(sb); +- +- if (!sb) +- return -EINVAL; +- if (str) { +- p = strdup(str); +- if (!p) +- return -ENOMEM; +- } +- free(sb->vert); +- sb->vert = p; +- return 0; ++ return strdup_to_struct_member(sy, vert, str); + } + + /** + * scols_symbols_set_right: +- * @sb: a pointer to a struct libscols_symbols instance ++ * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the right part of a tree output + * + * Returns: 0, a negative value in case of an error. + */ +-int scols_symbols_set_right(struct libscols_symbols *sb, const char *str) ++int scols_symbols_set_right(struct libscols_symbols *sy, const char *str) + { +- char *p = NULL; ++ return strdup_to_struct_member(sy, right, str); ++} + +- assert(sb); ++/** ++ * scols_symbols_set_title_padding: ++ * @sy: a pointer to a struct libscols_symbols instance ++ * @str: a string which will represent the symbols which fill title output ++ * ++ * The current implementation uses only the first byte from the padding string. ++ * A multibyte chars are not supported yet. ++ * ++ * Returns: 0, a negative value in case of an error. ++ * ++ * Since: 2.28 ++ */ ++int scols_symbols_set_title_padding(struct libscols_symbols *sy, const char *str) ++{ ++ return strdup_to_struct_member(sy, title_padding, str); ++} + +- if (!sb) +- return -EINVAL; +- if (str) { +- p = strdup(str); +- if (!p) +- return -ENOMEM; +- } +- free(sb->right); +- sb->right = p; +- return 0; ++/** ++ * scols_symbols_set_cell_padding: ++ * @sy: a pointer to a struct libscols_symbols instance ++ * @str: a string which will represent the symbols which fill cells ++ * ++ * The padding char has to take up just one cell on the terminal. ++ * ++ * Returns: 0, a negative value in case of an error. ++ * ++ * Since: 2.29 ++ */ ++int scols_symbols_set_cell_padding(struct libscols_symbols *sy, const char *str) ++{ ++ return strdup_to_struct_member(sy, cell_padding, str); + } + + /** + * scols_copy_symbols: +- * @sb: a pointer to a struct libscols_symbols instance ++ * @sy: a pointer to a struct libscols_symbols instance + * +- * Returns: a newly allocated copy of the @sb symbol group or NULL in caes of an error. ++ * Returns: a newly allocated copy of the @sy symbol group or NULL in case of an error. + */ +-struct libscols_symbols *scols_copy_symbols(const struct libscols_symbols *sb) ++struct libscols_symbols *scols_copy_symbols(const struct libscols_symbols *sy) + { + struct libscols_symbols *ret; + int rc; + +- assert(sb); +- if (!sb) ++ assert(sy); ++ if (!sy) + return NULL; + + ret = scols_new_symbols(); + if (!ret) + return NULL; + +- rc = scols_symbols_set_branch(ret, sb->branch); ++ rc = scols_symbols_set_branch(ret, sy->branch); ++ if (!rc) ++ rc = scols_symbols_set_vertical(ret, sy->vert); + if (!rc) +- rc = scols_symbols_set_vertical(ret, sb->vert); ++ rc = scols_symbols_set_right(ret, sy->right); + if (!rc) +- rc = scols_symbols_set_right(ret, sb->right); ++ rc = scols_symbols_set_title_padding(ret, sy->title_padding); ++ if (!rc) ++ rc = scols_symbols_set_cell_padding(ret, sy->cell_padding); + if (!rc) + return ret; + + scols_unref_symbols(ret); + return NULL; +- + } +- +- +diff --git a/libsmartcols/src/table.c b/libsmartcols/src/table.c +index 8c404f858..979a09a39 100644 +--- a/libsmartcols/src/table.c ++++ b/libsmartcols/src/table.c +@@ -3,6 +3,7 @@ + * + * Copyright (C) 2010-2014 Karel Zak + * Copyright (C) 2014 Ondrej Oprala ++ * Copyright (C) 2016 Igor Gnatenko + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. +@@ -11,7 +12,7 @@ + /** + * SECTION: table + * @title: Table +- * @short_description: table data API ++ * @short_description: container for rows and columns + * + * Table data manipulation API. + */ +@@ -24,7 +25,7 @@ + #include + + #include "nls.h" +-#include "widechar.h" ++#include "ttyutils.h" + #include "smartcolsP.h" + + #ifdef HAVE_WIDECHAR +@@ -38,6 +39,20 @@ + list_entry_is_last(&(_cl)->cl_columns, &(_tb)->tb_columns) + + ++static void check_padding_debug(struct libscols_table *tb) ++{ ++ const char *str; ++ ++ assert(libsmartcols_debug_mask); /* debug has to be enabled! */ ++ ++ str = getenv("LIBSMARTCOLS_DEBUG_PADDING"); ++ if (!str || (strcmp(str, "on") != 0 && strcmp(str, "1") != 0)) ++ return; ++ ++ DBG(INIT, ul_debugobj(tb, "padding debug: ENABLE")); ++ tb->padding_debug = 1; ++} ++ + /** + * scols_new_table: + * +@@ -46,6 +61,7 @@ + struct libscols_table *scols_new_table(void) + { + struct libscols_table *tb; ++ int c, l; + + tb = calloc(1, sizeof(struct libscols_table)); + if (!tb) +@@ -54,10 +70,16 @@ struct libscols_table *scols_new_table(void) + tb->refcount = 1; + tb->out = stdout; + ++ get_terminal_dimension(&c, &l); ++ tb->termwidth = c > 0 ? c : 80; ++ tb->termheight = l > 0 ? l : 24; ++ + INIT_LIST_HEAD(&tb->tb_lines); + INIT_LIST_HEAD(&tb->tb_columns); + + DBG(TAB, ul_debugobj(tb, "alloc")); ++ ON_DBG(INIT, check_padding_debug(tb)); ++ + return tb; + } + +@@ -87,44 +109,102 @@ void scols_unref_table(struct libscols_table *tb) + scols_table_remove_lines(tb); + scols_table_remove_columns(tb); + scols_unref_symbols(tb->symbols); ++ scols_reset_cell(&tb->title); + free(tb->linesep); + free(tb->colsep); ++ free(tb->name); + free(tb); + } + } + ++/** ++ * scols_table_set_name: ++ * @tb: a pointer to a struct libscols_table instance ++ * @name: a name ++ * ++ * The table name is used for example for JSON top level object name. ++ * ++ * Returns: 0, a negative number in case of an error. ++ * ++ * Since: 2.27 ++ */ ++int scols_table_set_name(struct libscols_table *tb, const char *name) ++{ ++ return strdup_to_struct_member(tb, name, name); ++} ++ ++/** ++ * scols_table_get_name: ++ * @tb: a pointer to a struct libscols_table instance ++ * ++ * Returns: The current name setting of the table @tb ++ * ++ * Since: 2.29 ++ */ ++const char *scols_table_get_name(const struct libscols_table *tb) ++{ ++ return tb->name; ++} ++ ++/** ++ * scols_table_get_title: ++ * @tb: a pointer to a struct libscols_table instance ++ * ++ * The returned pointer is possible to modify by cell functions. Note that ++ * title output alignment on non-tty is hardcoded to 80 output chars. For the ++ * regular terminal it's based on terminal width. ++ * ++ * Returns: Title of the table, or NULL in case of blank title. ++ * ++ * Since: 2.28 ++ */ ++struct libscols_cell *scols_table_get_title(struct libscols_table *tb) ++{ ++ return &tb->title; ++} ++ + /** + * scols_table_add_column: + * @tb: a pointer to a struct libscols_table instance + * @cl: a pointer to a struct libscols_column instance + * +- * Adds @cl to @tb's column list. ++ * Adds @cl to @tb's column list. The column cannot be shared between more ++ * tables. + * + * Returns: 0, a negative number in case of an error. + */ + int scols_table_add_column(struct libscols_table *tb, struct libscols_column *cl) + { +- assert(tb); +- assert(cl); ++ struct libscols_iter itr; ++ struct libscols_line *ln; ++ int rc = 0; + +- if (!tb || !cl || !list_empty(&tb->tb_lines)) ++ if (!tb || !cl || cl->table) + return -EINVAL; + + if (cl->flags & SCOLS_FL_TREE) + tb->ntreecols++; + +- DBG(TAB, ul_debugobj(tb, "add column %p", cl)); ++ DBG(TAB, ul_debugobj(tb, "add column")); + list_add_tail(&cl->cl_columns, &tb->tb_columns); + cl->seqnum = tb->ncols++; ++ cl->table = tb; + scols_ref_column(cl); + +- /* TODO: +- * +- * Currently it's possible to add/remove columns only if the table is +- * empty (see list_empty(tb->tb_lines) above). It would be nice to +- * enlarge/reduce lines cells[] always when we add/remove a new column. ++ if (list_empty(&tb->tb_lines)) ++ return 0; ++ ++ scols_reset_iter(&itr, SCOLS_ITER_FORWARD); ++ ++ /* Realloc line cell arrays + */ +- return 0; ++ while (scols_table_next_line(tb, &itr, &ln) == 0) { ++ rc = scols_line_alloc_cells(ln, tb->ncols); ++ if (rc) ++ break; ++ } ++ ++ return rc; + } + + /** +@@ -139,18 +219,16 @@ int scols_table_add_column(struct libscols_table *tb, struct libscols_column *cl + int scols_table_remove_column(struct libscols_table *tb, + struct libscols_column *cl) + { +- assert(tb); +- assert(cl); +- + if (!tb || !cl || !list_empty(&tb->tb_lines)) + return -EINVAL; + + if (cl->flags & SCOLS_FL_TREE) + tb->ntreecols--; + +- DBG(TAB, ul_debugobj(tb, "remove column %p", cl)); ++ DBG(TAB, ul_debugobj(tb, "remove column")); + list_del_init(&cl->cl_columns); + tb->ncols--; ++ cl->table = NULL; + scols_unref_column(cl); + return 0; + } +@@ -165,8 +243,6 @@ int scols_table_remove_column(struct libscols_table *tb, + */ + int scols_table_remove_columns(struct libscols_table *tb) + { +- assert(tb); +- + if (!tb || !list_empty(&tb->tb_lines)) + return -EINVAL; + +@@ -179,12 +255,64 @@ int scols_table_remove_columns(struct libscols_table *tb) + return 0; + } + ++/** ++ * scols_table_move_column: ++ * @tb: table ++ * @pre: column before the column ++ * @cl: column to move ++ * ++ * Move the @cl behind @pre. If the @pre is NULL then the @col is the first ++ * column in the table. ++ * ++ * Since: 2.30 ++ * ++ * Returns: 0, a negative number in case of an error. ++ */ ++int scols_table_move_column(struct libscols_table *tb, ++ struct libscols_column *pre, ++ struct libscols_column *cl) ++{ ++ struct list_head *head; ++ struct libscols_iter itr; ++ struct libscols_column *p; ++ struct libscols_line *ln; ++ size_t n = 0, oldseq; ++ ++ if (!tb || !cl) ++ return -EINVAL; ++ ++ if (pre && pre->seqnum + 1 == cl->seqnum) ++ return 0; ++ if (pre == NULL && cl->seqnum == 0) ++ return 0; ++ ++ DBG(TAB, ul_debugobj(tb, "move column %zu behind %zu", ++ cl->seqnum, pre? pre->seqnum : 0)); ++ ++ list_del_init(&cl->cl_columns); /* remove from old position */ ++ ++ head = pre ? &pre->cl_columns : &tb->tb_columns; ++ list_add(&cl->cl_columns, head); /* add to the new place */ ++ ++ oldseq = cl->seqnum; ++ ++ /* fix seq. numbers */ ++ scols_reset_iter(&itr, SCOLS_ITER_FORWARD); ++ while (scols_table_next_column(tb, &itr, &p) == 0) ++ p->seqnum = n++; ++ ++ /* move data in lines */ ++ scols_reset_iter(&itr, SCOLS_ITER_FORWARD); ++ while (scols_table_next_line(tb, &itr, &ln) == 0) ++ scols_line_move_cells(ln, cl->seqnum, oldseq); ++ return 0; ++} + + /** + * scols_table_new_column: + * @tb: table + * @name: column header +- * @whint: column width hint (absolute width: N > 1; relative width: N < 1) ++ * @whint: column width hint (absolute width: N > 1; relative width: 0 < N < 1) + * @flags: flags integer + * + * This is shortcut for +@@ -193,17 +321,36 @@ int scols_table_remove_columns(struct libscols_table *tb) + * scols_column_set_....(cl, ...); + * scols_table_add_column(tb, cl); + * +- * The column width is possible to define by three ways: ++ * The column width is possible to define by: ++ * ++ * @whint: 0 < N < 1 : relative width, percent of terminal width ++ * ++ * @whint: N >= 1 : absolute width, empty column will be truncated to ++ * the column header width if no specified STRICTWIDTH flag + * +- * @whint = 0..1 : relative width, percent of terminal width ++ * Note that if table has disabled "maxout" flag (disabled by default) than ++ * relative width is used as a hint only. It's possible that column will be ++ * narrow if the specified size is too large for column data. + * +- * @whint = 1..N : absolute width, empty colum will be truncated to +- * the column header width + * +- * @whint = 1..N ++ * If the width of all columns is greater than terminal width then library ++ * tries to reduce width of the individual columns. It's done in three stages: + * +- * The column is necessary to address by +- * sequential number. The first defined column has the colnum = 0. For example: ++ * #1 reduce columns with SCOLS_FL_TRUNC flag and with relative width if the ++ * width is greater than width defined by @whint (@whint * terminal_width) ++ * ++ * #2 reduce all columns with SCOLS_FL_TRUNC flag ++ * ++ * #3 reduce all columns with relative width ++ * ++ * The next stage is always used if the previous stage is unsuccessful. Note ++ * that SCOLS_FL_WRAP is interpreted as SCOLS_FL_TRUNC when calculate column ++ * width (if custom wrap function is not specified), but the final text is not ++ * truncated, but wrapped to multi-line cell. ++ * ++ * ++ * The column is necessary to address by sequential number. The first defined ++ * column has the colnum = 0. For example: + * + * scols_table_new_column(tab, "FOO", 0.5, 0); // colnum = 0 + * scols_table_new_column(tab, "BAR", 0.5, 0); // colnum = 1 +@@ -222,7 +369,6 @@ struct libscols_column *scols_table_new_column(struct libscols_table *tb, + struct libscols_column *cl; + struct libscols_cell *hr; + +- assert (tb); + if (!tb) + return NULL; + +@@ -282,29 +428,26 @@ int scols_table_next_column(struct libscols_table *tb, + return rc; + } + +- + /** + * scols_table_get_ncols: + * @tb: table + * +- * Returns: the ncols table member, a negative number in case of an error. ++ * Returns: the ncols table member. + */ +-int scols_table_get_ncols(struct libscols_table *tb) ++size_t scols_table_get_ncols(const struct libscols_table *tb) + { +- assert(tb); +- return tb ? tb->ncols : -EINVAL; ++ return tb->ncols; + } + + /** + * scols_table_get_nlines: + * @tb: table + * +- * Returns: the nlines table member, a negative number in case of an error. ++ * Returns: the nlines table member. + */ +-int scols_table_get_nlines(struct libscols_table *tb) ++size_t scols_table_get_nlines(const struct libscols_table *tb) + { +- assert(tb); +- return tb ? tb->nlines : -EINVAL; ++ return tb->nlines; + } + + /** +@@ -335,10 +478,9 @@ int scols_table_set_stream(struct libscols_table *tb, FILE *stream) + * + * Returns: stream pointer, NULL in case of an error or an unset stream. + */ +-FILE *scols_table_get_stream(struct libscols_table *tb) ++FILE *scols_table_get_stream(const struct libscols_table *tb) + { +- assert(tb); +- return tb ? tb->out: NULL; ++ return tb->out; + } + + /** +@@ -346,13 +488,20 @@ FILE *scols_table_get_stream(struct libscols_table *tb) + * @tb: table + * @reduce: width + * +- * Reduce the output width to @reduce. ++ * If necessary then libsmartcols use all terminal width, the @reduce setting ++ * provides extra space (for example for borders in ncurses applications). ++ * ++ * The @reduce must be smaller than terminal width, otherwise it's silently ++ * ignored. The reduction is not applied when STDOUT_FILENO is not terminal. ++ * ++ * Note that after output initialization (scols_table_print_* calls) the width ++ * will be reduced, this behavior affects subsequenced scols_table_get_termwidth() ++ * calls. + * + * Returns: 0, a negative value in case of an error. + */ + int scols_table_reduce_termwidth(struct libscols_table *tb, size_t reduce) + { +- assert(tb); + if (!tb) + return -EINVAL; + +@@ -374,7 +523,6 @@ struct libscols_column *scols_table_get_column(struct libscols_table *tb, + struct libscols_iter itr; + struct libscols_column *cl; + +- assert(tb); + if (!tb) + return NULL; + if (n >= tb->ncols) +@@ -400,11 +548,7 @@ struct libscols_column *scols_table_get_column(struct libscols_table *tb, + */ + int scols_table_add_line(struct libscols_table *tb, struct libscols_line *ln) + { +- +- assert(tb); +- assert(ln); +- +- if (!tb || !ln) ++ if (!tb || !ln || tb->ncols == 0) + return -EINVAL; + + if (tb->ncols > ln->ncells) { +@@ -413,7 +557,7 @@ int scols_table_add_line(struct libscols_table *tb, struct libscols_line *ln) + return rc; + } + +- DBG(TAB, ul_debugobj(tb, "add line %p", ln)); ++ DBG(TAB, ul_debugobj(tb, "add line")); + list_add_tail(&ln->ln_lines, &tb->tb_lines); + ln->seqnum = tb->nlines++; + scols_ref_line(ln); +@@ -433,13 +577,10 @@ int scols_table_add_line(struct libscols_table *tb, struct libscols_line *ln) + int scols_table_remove_line(struct libscols_table *tb, + struct libscols_line *ln) + { +- assert(tb); +- assert(ln); +- + if (!tb || !ln) + return -EINVAL; + +- DBG(TAB, ul_debugobj(tb, "remove line %p", ln)); ++ DBG(TAB, ul_debugobj(tb, "remove line")); + list_del_init(&ln->ln_lines); + tb->nlines--; + scols_unref_line(ln); +@@ -454,7 +595,6 @@ int scols_table_remove_line(struct libscols_table *tb, + */ + void scols_table_remove_lines(struct libscols_table *tb) + { +- assert(tb); + if (!tb) + return; + +@@ -517,9 +657,6 @@ struct libscols_line *scols_table_new_line(struct libscols_table *tb, + { + struct libscols_line *ln; + +- assert(tb); +- assert(tb->ncols); +- + if (!tb || !tb->ncols) + return NULL; + +@@ -544,13 +681,7 @@ err: + * @tb: table + * @n: column number (0..N) + * +- * This is a shortcut for +- * +- * ln = scols_new_line(); +- * scols_line_set_....(cl, ...); +- * scols_table_add_line(tb, ln); +- * +- * Returns: a newly allocate line ++ * Returns: a line or NULL + */ + struct libscols_line *scols_table_get_line(struct libscols_table *tb, + size_t n) +@@ -558,7 +689,6 @@ struct libscols_line *scols_table_get_line(struct libscols_table *tb, + struct libscols_iter itr; + struct libscols_line *ln; + +- assert(tb); + if (!tb) + return NULL; + if (n >= tb->nlines) +@@ -588,14 +718,13 @@ struct libscols_table *scols_copy_table(struct libscols_table *tb) + struct libscols_column *cl; + struct libscols_iter itr; + +- assert(tb); + if (!tb) + return NULL; + ret = scols_new_table(); + if (!ret) + return NULL; + +- DBG(TAB, ul_debugobj(tb, "copy into %p", ret)); ++ DBG(TAB, ul_debugobj(tb, "copy")); + + if (tb->symbols) + scols_table_set_symbols(ret, tb->symbols); +@@ -639,54 +768,141 @@ err: + return NULL; + } + ++/** ++ * scols_table_set_default_symbols: ++ * @tb: table ++ * ++ * The library check the current environment to select ASCII or UTF8 symbols. ++ * This default behavior could be controlled by scols_table_enable_ascii(). ++ * ++ * Use scols_table_set_symbols() to unset symbols or use your own setting. ++ * ++ * Returns: 0, a negative value in case of an error. ++ * ++ * Since: 2.29 ++ */ ++int scols_table_set_default_symbols(struct libscols_table *tb) ++{ ++ struct libscols_symbols *sy; ++ int rc; ++ ++ if (!tb) ++ return -EINVAL; ++ ++ DBG(TAB, ul_debugobj(tb, "setting default symbols")); ++ ++ sy = scols_new_symbols(); ++ if (!sy) ++ return -ENOMEM; ++ ++#if defined(HAVE_WIDECHAR) ++ if (!scols_table_is_ascii(tb) && ++ !strcmp(nl_langinfo(CODESET), "UTF-8")) { ++ 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); ++ } else ++#endif ++ { ++ scols_symbols_set_branch(sy, "|-"); ++ scols_symbols_set_vertical(sy, "| "); ++ scols_symbols_set_right(sy, "`-"); ++ } ++ scols_symbols_set_title_padding(sy, " "); ++ scols_symbols_set_cell_padding(sy, " "); ++ ++ rc = scols_table_set_symbols(tb, sy); ++ scols_unref_symbols(sy); ++ return rc; ++} ++ ++ + /** + * scols_table_set_symbols: + * @tb: table + * @sy: symbols or NULL + * + * Add a reference to @sy from the table. The symbols are used by library to +- * draw tree output. If no symbols are specified then library checks the +- * current environment to select ASCII or UTF8 symbols. This default behavior +- * could be controlled by scols_table_enable_ascii(). ++ * draw tree output. If no symbols are used for the table then library creates ++ * default temporary symbols to draw output by scols_table_set_default_symbols(). ++ * ++ * If @sy is NULL then remove reference from the currently used symbols. + * + * Returns: 0, a negative value in case of an error. + */ + int scols_table_set_symbols(struct libscols_table *tb, + struct libscols_symbols *sy) + { +- assert(tb); +- + if (!tb) + return -EINVAL; + +- DBG(TAB, ul_debugobj(tb, "setting alternative symbols %p", sy)); +- +- if (tb->symbols) /* unref old */ ++ /* remove old */ ++ if (tb->symbols) { ++ DBG(TAB, ul_debugobj(tb, "remove symbols reference")); + scols_unref_symbols(tb->symbols); ++ tb->symbols = NULL; ++ } ++ ++ /* set new */ + if (sy) { /* ref user defined */ ++ DBG(TAB, ul_debugobj(tb, "set symbols")); + tb->symbols = sy; + scols_ref_symbols(sy); +- } else { /* default symbols */ +- tb->symbols = scols_new_symbols(); +- if (!tb->symbols) +- return -ENOMEM; +-#if defined(HAVE_WIDECHAR) +- if (!scols_table_is_ascii(tb) && +- !strcmp(nl_langinfo(CODESET), "UTF-8")) { +- scols_symbols_set_branch(tb->symbols, UTF_VR UTF_H); +- scols_symbols_set_vertical(tb->symbols, UTF_V " "); +- scols_symbols_set_right(tb->symbols, UTF_UR UTF_H); +- } else +-#endif +- { +- scols_symbols_set_branch(tb->symbols, "|-"); +- scols_symbols_set_vertical(tb->symbols, "| "); +- scols_symbols_set_right(tb->symbols, "`-"); +- } + } ++ return 0; ++} ++ ++/** ++ * scols_table_get_symbols: ++ * @tb: table ++ * ++ * Returns: pointer to symbols table. ++ * ++ * Since: 2.29 ++ */ ++struct libscols_symbols *scols_table_get_symbols(const struct libscols_table *tb) ++{ ++ return tb->symbols; ++} ++ ++/** ++ * scols_table_enable_nolinesep: ++ * @tb: table ++ * @enable: 1 or 0 ++ * ++ * Enable/disable line separator printing. This is useful if you want to ++ * re-printing the same line more than once (e.g. progress bar). Don't use it ++ * if you're not sure. ++ * ++ * Note that for the last line in the table the separator is disabled at all. ++ * The library differentiate between table terminator and line terminator ++ * (although for standard output \n byte is used in both cases). ++ * ++ * Returns: 0 on success, negative number in case of an error. ++ */ ++int scols_table_enable_nolinesep(struct libscols_table *tb, int enable) ++{ ++ if (!tb) ++ return -EINVAL; + ++ DBG(TAB, ul_debugobj(tb, "nolinesep: %s", enable ? "ENABLE" : "DISABLE")); ++ tb->no_linesep = enable ? 1 : 0; + return 0; + } ++ ++/** ++ * scols_table_is_nolinesep: ++ * @tb: a pointer to a struct libscols_table instance ++ * ++ * Returns: 1 if line separator printing is disabled. ++ * ++ * Since: 2.29 ++ */ ++int scols_table_is_nolinesep(const struct libscols_table *tb) ++{ ++ return tb->no_linesep; ++} ++ + /** + * scols_table_enable_colors: + * @tb: table +@@ -698,7 +914,6 @@ int scols_table_set_symbols(struct libscols_table *tb, + */ + int scols_table_enable_colors(struct libscols_table *tb, int enable) + { +- assert(tb); + if (!tb) + return -EINVAL; + +@@ -706,19 +921,19 @@ int scols_table_enable_colors(struct libscols_table *tb, int enable) + tb->colors_wanted = enable; + return 0; + } ++ + /** + * scols_table_enable_raw: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable raw output format. The parsable output formats +- * (export and raw) are mutually exclusive. ++ * (export, raw, JSON, ...) are mutually exclusive. + * + * Returns: 0 on success, negative number in case of an error. + */ + int scols_table_enable_raw(struct libscols_table *tb, int enable) + { +- assert(tb); + if (!tb) + return -EINVAL; + +@@ -730,6 +945,31 @@ int scols_table_enable_raw(struct libscols_table *tb, int enable) + return 0; + } + ++/** ++ * scols_table_enable_json: ++ * @tb: table ++ * @enable: 1 or 0 ++ * ++ * Enable/disable JSON output format. The parsable output formats ++ * (export, raw, JSON, ...) are mutually exclusive. ++ * ++ * Returns: 0 on success, negative number in case of an error. ++ * ++ * Since: 2.27 ++ */ ++int scols_table_enable_json(struct libscols_table *tb, int enable) ++{ ++ if (!tb) ++ return -EINVAL; ++ ++ DBG(TAB, ul_debugobj(tb, "json: %s", enable ? "ENABLE" : "DISABLE")); ++ if (enable) ++ tb->format = SCOLS_FMT_JSON; ++ else if (tb->format == SCOLS_FMT_JSON) ++ tb->format = 0; ++ return 0; ++} ++ + /** + * scols_table_enable_export: + * @tb: table +@@ -742,7 +982,6 @@ int scols_table_enable_raw(struct libscols_table *tb, int enable) + */ + int scols_table_enable_export(struct libscols_table *tb, int enable) + { +- assert(tb); + if (!tb) + return -EINVAL; + +@@ -771,7 +1010,6 @@ int scols_table_enable_export(struct libscols_table *tb, int enable) + */ + int scols_table_enable_ascii(struct libscols_table *tb, int enable) + { +- assert(tb); + if (!tb) + return -EINVAL; + +@@ -791,7 +1029,6 @@ int scols_table_enable_ascii(struct libscols_table *tb, int enable) + */ + int scols_table_enable_noheadings(struct libscols_table *tb, int enable) + { +- assert(tb); + if (!tb) + return -EINVAL; + DBG(TAB, ul_debugobj(tb, "noheading: %s", enable ? "ENABLE" : "DISABLE")); +@@ -799,6 +1036,28 @@ int scols_table_enable_noheadings(struct libscols_table *tb, int enable) + return 0; + } + ++/** ++ * scols_table_enable_header_repeat: ++ * @tb: table ++ * @enable: 1 or 0 ++ * ++ * Enable/disable header line repeat. The header line is printed only once by ++ * default. Note that the flag will be silently ignored and disabled if the ++ * output is not on terminal or output format is JSON, raw, etc. ++ * ++ * Returns: 0 on success, negative number in case of an error. ++ * ++ * Since: 2.31 ++ */ ++int scols_table_enable_header_repeat(struct libscols_table *tb, int enable) ++{ ++ if (!tb) ++ return -EINVAL; ++ DBG(TAB, ul_debugobj(tb, "header-repeat: %s", enable ? "ENABLE" : "DISABLE")); ++ tb->header_repeat = enable ? 1 : 0; ++ return 0; ++} ++ + /** + * scols_table_enable_maxout: + * @tb: table +@@ -811,7 +1070,6 @@ int scols_table_enable_noheadings(struct libscols_table *tb, int enable) + */ + int scols_table_enable_maxout(struct libscols_table *tb, int enable) + { +- assert(tb); + if (!tb) + return -EINVAL; + DBG(TAB, ul_debugobj(tb, "maxout: %s", enable ? "ENABLE" : "DISABLE")); +@@ -819,28 +1077,92 @@ int scols_table_enable_maxout(struct libscols_table *tb, int enable) + return 0; + } + ++/** ++ * scols_table_enable_nowrap: ++ * @tb: table ++ * @enable: 1 or 0 ++ * ++ * Never continue on next line, remove last column(s) when too large, truncate last column. ++ * ++ * Returns: 0 on success, negative number in case of an error. ++ * ++ * Since: 2.28 ++ */ ++int scols_table_enable_nowrap(struct libscols_table *tb, int enable) ++{ ++ if (!tb) ++ return -EINVAL; ++ DBG(TAB, ul_debugobj(tb, "nowrap: %s", enable ? "ENABLE" : "DISABLE")); ++ tb->no_wrap = enable ? 1 : 0; ++ return 0; ++} ++ ++/** ++ * scols_table_is_nowrap: ++ * @tb: a pointer to a struct libscols_table instance ++ * ++ * Returns: 1 if nowrap is enabled. ++ * ++ * Since: 2.29 ++ */ ++int scols_table_is_nowrap(const struct libscols_table *tb) ++{ ++ return tb->no_wrap; ++} ++ ++/** ++ * scols_table_enable_noencoding: ++ * @tb: table ++ * @enable: 1 or 0 ++ * ++ * The library encode non-printable and control chars by \xHEX by default. ++ * ++ * Returns: 0 on success, negative number in case of an error. ++ * ++ * Since: 2.31 ++ */ ++int scols_table_enable_noencoding(struct libscols_table *tb, int enable) ++{ ++ if (!tb) ++ return -EINVAL; ++ DBG(TAB, ul_debugobj(tb, "encoding: %s", enable ? "ENABLE" : "DISABLE")); ++ tb->no_encode = enable ? 1 : 0; ++ return 0; ++} ++ ++/** ++ * scols_table_is_noencoding: ++ * @tb: a pointer to a struct libscols_table instance ++ * ++ * Returns: 1 if encoding is disabled. ++ * ++ * Since: 2.31 ++ */ ++int scols_table_is_noencoding(const struct libscols_table *tb) ++{ ++ return tb->no_encode; ++} ++ + /** + * scols_table_colors_wanted: + * @tb: table + * + * Returns: 1 if colors are enabled. + */ +-int scols_table_colors_wanted(struct libscols_table *tb) ++int scols_table_colors_wanted(const struct libscols_table *tb) + { +- assert(tb); +- return tb && tb->colors_wanted; ++ return tb->colors_wanted; + } + + /** + * scols_table_is_empty: + * @tb: table + * +- * Returns: 1 if the table is empty. ++ * Returns: 1 if the table is empty. + */ +-int scols_table_is_empty(struct libscols_table *tb) ++int scols_table_is_empty(const struct libscols_table *tb) + { +- assert(tb); +- return !tb || !tb->nlines; ++ return !tb->nlines; + } + + /** +@@ -849,10 +1171,9 @@ int scols_table_is_empty(struct libscols_table *tb) + * + * Returns: 1 if ASCII tree is enabled. + */ +-int scols_table_is_ascii(struct libscols_table *tb) ++int scols_table_is_ascii(const struct libscols_table *tb) + { +- assert(tb); +- return tb && tb->ascii; ++ return tb->ascii; + } + + /** +@@ -861,10 +1182,22 @@ int scols_table_is_ascii(struct libscols_table *tb) + * + * Returns: 1 if header output is disabled. + */ +-int scols_table_is_noheadings(struct libscols_table *tb) ++int scols_table_is_noheadings(const struct libscols_table *tb) + { +- assert(tb); +- return tb && tb->no_headings; ++ return tb->no_headings; ++} ++ ++/** ++ * scols_table_is_header_repeat ++ * @tb: table ++ * ++ * Returns: 1 if header repeat is enabled. ++ * ++ * Since: 2.31 ++ */ ++int scols_table_is_header_repeat(const struct libscols_table *tb) ++{ ++ return tb->header_repeat; + } + + /** +@@ -873,10 +1206,9 @@ int scols_table_is_noheadings(struct libscols_table *tb) + * + * Returns: 1 if export output format is enabled. + */ +-int scols_table_is_export(struct libscols_table *tb) ++int scols_table_is_export(const struct libscols_table *tb) + { +- assert(tb); +- return tb && tb->format == SCOLS_FMT_EXPORT; ++ return tb->format == SCOLS_FMT_EXPORT; + } + + /** +@@ -885,23 +1217,33 @@ int scols_table_is_export(struct libscols_table *tb) + * + * Returns: 1 if raw output format is enabled. + */ +-int scols_table_is_raw(struct libscols_table *tb) ++int scols_table_is_raw(const struct libscols_table *tb) + { +- assert(tb); +- return tb && tb->format == SCOLS_FMT_RAW; ++ return tb->format == SCOLS_FMT_RAW; + } + ++/** ++ * scols_table_is_json: ++ * @tb: table ++ * ++ * Returns: 1 if JSON output format is enabled. ++ * ++ * Since: 2.27 ++ */ ++int scols_table_is_json(const struct libscols_table *tb) ++{ ++ return tb->format == SCOLS_FMT_JSON; ++} + + /** + * scols_table_is_maxout + * @tb: table + * +- * Returns: 1 if output maximization is enabled, negative value in case of an error. ++ * Returns: 1 if output maximization is enabled or 0 + */ +-int scols_table_is_maxout(struct libscols_table *tb) ++int scols_table_is_maxout(const struct libscols_table *tb) + { +- assert(tb); +- return tb && tb->maxout; ++ return tb->maxout; + } + + /** +@@ -910,10 +1252,9 @@ int scols_table_is_maxout(struct libscols_table *tb) + * + * Returns: returns 1 tree-like output is expected. + */ +-int scols_table_is_tree(struct libscols_table *tb) ++int scols_table_is_tree(const struct libscols_table *tb) + { +- assert(tb); +- return tb && tb->ntreecols > 0; ++ return tb->ntreecols > 0; + } + + /** +@@ -922,29 +1263,12 @@ int scols_table_is_tree(struct libscols_table *tb) + * @sep: separator + * + * Sets the column separator of @tb to @sep. +- * Please note that @sep should always take up a single cell in the output. + * + * Returns: 0, a negative value in case of an error. + */ + int scols_table_set_column_separator(struct libscols_table *tb, const char *sep) + { +- char *p = NULL; +- +- assert (tb); +- +- if (!tb) +- return -EINVAL; +- +- if (sep) { +- p = strdup(sep); +- if (!p) +- return -ENOMEM; +- } +- +- DBG(TAB, ul_debugobj(tb, "new columns separator: %s", sep)); +- free(tb->colsep); +- tb->colsep = p; +- return 0; ++ return strdup_to_struct_member(tb, colsep, sep); + } + + /** +@@ -958,23 +1282,7 @@ int scols_table_set_column_separator(struct libscols_table *tb, const char *sep) + */ + int scols_table_set_line_separator(struct libscols_table *tb, const char *sep) + { +- char *p = NULL; +- +- assert (tb); +- +- if (!tb) +- return -EINVAL; +- +- if (sep) { +- p = strdup(sep); +- if (!p) +- return -ENOMEM; +- } +- +- DBG(TAB, ul_debugobj(tb, "new lines separator: %s", sep)); +- free(tb->linesep); +- tb->linesep = p; +- return 0; ++ return strdup_to_struct_member(tb, linesep, sep); + } + + /** +@@ -983,12 +1291,8 @@ int scols_table_set_line_separator(struct libscols_table *tb, const char *sep) + * + * Returns: @tb column separator, NULL in case of an error + */ +-char *scols_table_get_column_separator(struct libscols_table *tb) ++const char *scols_table_get_column_separator(const struct libscols_table *tb) + { +- assert (tb); +- +- if (!tb) +- return NULL; + return tb->colsep; + } + +@@ -998,17 +1302,12 @@ char *scols_table_get_column_separator(struct libscols_table *tb) + * + * Returns: @tb line separator, NULL in case of an error + */ +-char *scols_table_get_line_separator(struct libscols_table *tb) ++const char *scols_table_get_line_separator(const struct libscols_table *tb) + { +- assert (tb); +- +- if (!tb) +- return NULL; + return tb->linesep; +- + } +- +-static int cells_cmp_wrapper(struct list_head *a, struct list_head *b, void *data) ++/* for lines in the struct libscols_line->ln_lines list */ ++static int cells_cmp_wrapper_lines(struct list_head *a, struct list_head *b, void *data) + { + struct libscols_column *cl = (struct libscols_column *) data; + struct libscols_line *ra, *rb; +@@ -1026,24 +1325,218 @@ static int cells_cmp_wrapper(struct list_head *a, struct list_head *b, void *dat + return cl->cmpfunc(ca, cb, cl->cmpfunc_data); + } + ++/* for lines in the struct libscols_line->ln_children list */ ++static int cells_cmp_wrapper_children(struct list_head *a, struct list_head *b, void *data) ++{ ++ struct libscols_column *cl = (struct libscols_column *) data; ++ struct libscols_line *ra, *rb; ++ struct libscols_cell *ca, *cb; ++ ++ assert(a); ++ assert(b); ++ assert(cl); ++ ++ ra = list_entry(a, struct libscols_line, ln_children); ++ rb = list_entry(b, struct libscols_line, ln_children); ++ ca = scols_line_get_cell(ra, cl->seqnum); ++ cb = scols_line_get_cell(rb, cl->seqnum); ++ ++ return cl->cmpfunc(ca, cb, cl->cmpfunc_data); ++} ++ ++ ++static int sort_line_children(struct libscols_line *ln, struct libscols_column *cl) ++{ ++ struct list_head *p; ++ ++ if (list_empty(&ln->ln_branch)) ++ return 0; ++ ++ 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); ++ return 0; ++} ++ + /** + * scols_sort_table: + * @tb: table + * @cl: order by this column + * +- * Orders the table by the column. See also scols_column_set_cmpfunc(). ++ * Orders the table by the column. See also scols_column_set_cmpfunc(). If the ++ * tree output is enabled then children in the tree are recursively sorted too. + * + * Returns: 0, a negative value in case of an error. + */ + int scols_sort_table(struct libscols_table *tb, struct libscols_column *cl) + { +- assert(tb); +- assert(cl); +- +- if (!tb || !cl) ++ if (!tb || !cl || !cl->cmpfunc) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "sorting table")); +- list_sort(&tb->tb_lines, cells_cmp_wrapper, cl); ++ list_sort(&tb->tb_lines, cells_cmp_wrapper_lines, cl); ++ ++ if (scols_table_is_tree(tb)) { ++ struct libscols_line *ln; ++ struct libscols_iter itr; ++ ++ scols_reset_iter(&itr, SCOLS_ITER_FORWARD); ++ while (scols_table_next_line(tb, &itr, &ln) == 0) ++ sort_line_children(ln, cl); ++ } ++ ++ return 0; ++} ++ ++static struct libscols_line *move_line_and_children(struct libscols_line *ln, struct libscols_line *pre) ++{ ++ if (pre) { ++ list_del_init(&ln->ln_lines); /* remove from old position */ ++ list_add(&ln->ln_lines, &pre->ln_lines); /* add to the new place (behind @pre) */ ++ } ++ pre = ln; ++ ++ if (!list_empty(&ln->ln_branch)) { ++ struct list_head *p; ++ ++ list_for_each(p, &ln->ln_branch) { ++ struct libscols_line *chld = ++ list_entry(p, struct libscols_line, ln_children); ++ pre = move_line_and_children(chld, pre); ++ } ++ } ++ ++ return pre; ++} ++ ++/** ++ * scols_sort_table_by_tree: ++ * @tb: table ++ * ++ * Reorders lines in the table by parent->child relation. Note that order of ++ * the lines in the table is independent on the tree hierarchy. ++ * ++ * Since: 2.30 ++ * ++ * Returns: 0, a negative value in case of an error. ++ */ ++int scols_sort_table_by_tree(struct libscols_table *tb) ++{ ++ struct libscols_line *ln; ++ struct libscols_iter itr; ++ ++ if (!tb) ++ return -EINVAL; ++ ++ DBG(TAB, ul_debugobj(tb, "sorting table by tree")); ++ ++ scols_reset_iter(&itr, SCOLS_ITER_FORWARD); ++ while (scols_table_next_line(tb, &itr, &ln) == 0) { ++ if (ln->parent) ++ continue; ++ ++ move_line_and_children(ln, NULL); ++ } ++ ++ return 0; ++} ++ ++ ++/** ++ * scols_table_set_termforce: ++ * @tb: table ++ * @force: SCOLS_TERMFORCE_{NEVER,ALWAYS,AUTO} ++ * ++ * Forces library to use stdout as terminal, non-terminal or use automatic ++ * detection (default). ++ * ++ * Returns: 0, a negative value in case of an error. ++ * ++ * Since: 2.29 ++ */ ++int scols_table_set_termforce(struct libscols_table *tb, int force) ++{ ++ if (!tb) ++ return -EINVAL; ++ tb->termforce = force; ++ return 0; ++} ++ ++/** ++ * scols_table_get_termforce: ++ * @tb: table ++ * ++ * Returns: SCOLS_TERMFORCE_{NEVER,ALWAYS,AUTO} or a negative value in case of an error. ++ * ++ * Since: 2.29 ++ */ ++int scols_table_get_termforce(const struct libscols_table *tb) ++{ ++ return tb->termforce; ++} ++ ++/** ++ * scols_table_set_termwidth ++ * @tb: table ++ * @width: terminal width ++ * ++ * The library automatically detects terminal width or defaults to 80 chars if ++ * detections is unsuccessful. This function override this behaviour. ++ * ++ * Returns: 0, a negative value in case of an error. ++ * ++ * Since: 2.29 ++ */ ++int scols_table_set_termwidth(struct libscols_table *tb, size_t width) ++{ ++ DBG(TAB, ul_debugobj(tb, "set terminatl width: %zu", width)); ++ tb->termwidth = width; + return 0; + } ++ ++/** ++ * scols_table_get_termwidth ++ * @tb: table ++ * ++ * Returns: terminal width. ++ */ ++size_t scols_table_get_termwidth(const struct libscols_table *tb) ++{ ++ return tb->termwidth; ++} ++ ++/** ++ * scols_table_set_termheight ++ * @tb: table ++ * @height: terminal height (number of lines) ++ * ++ * The library automatically detects terminal height or defaults to 24 lines if ++ * detections is unsuccessful. This function override this behaviour. ++ * ++ * Returns: 0, a negative value in case of an error. ++ * ++ * Since: 2.31 ++ */ ++int scols_table_set_termheight(struct libscols_table *tb, size_t height) ++{ ++ DBG(TAB, ul_debugobj(tb, "set terminatl height: %zu", height)); ++ tb->termheight = height; ++ return 0; ++} ++ ++/** ++ * scols_table_get_termheight ++ * @tb: table ++ * ++ * Returns: terminal height (number of lines). ++ * ++ * Since: 2.31 ++ */ ++size_t scols_table_get_termheight(const struct libscols_table *tb) ++{ ++ return tb->termheight; ++} +diff --git a/libsmartcols/src/table_print.c b/libsmartcols/src/table_print.c +index c9f3d8f4b..10126fd79 100644 +--- a/libsmartcols/src/table_print.c ++++ b/libsmartcols/src/table_print.c +@@ -2,6 +2,7 @@ + * table.c - functions handling the data at the table level + * + * Copyright (C) 2010-2014 Karel Zak ++ * Copyright (C) 2016 Igor Gnatenko + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. +@@ -10,7 +11,7 @@ + /** + * SECTION: table_print + * @title: Table print +- * @short_description: table print API ++ * @short_description: output functions + * + * Table output API. + */ +@@ -21,13 +22,30 @@ + #include + #include + +-#include "nls.h" + #include "mbsalign.h" +-#include "widechar.h" +-#include "ttyutils.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 */ +@@ -88,7 +106,6 @@ static int buffer_append_data(struct libscols_buffer *buf, const char *str) + + if (maxsz <= sz) + return -EINVAL; +- + memcpy(buf->cur, str, sz + 1); + buf->cur += sz; + return 0; +@@ -100,7 +117,7 @@ static int buffer_set_data(struct libscols_buffer *buf, const char *str) + return rc ? rc : buffer_append_data(buf, str); + } + +-/* save the current buffer possition to art_idx */ ++/* save the current buffer position to art_idx */ + static void buffer_set_art_index(struct libscols_buffer *buf) + { + if (buf) { +@@ -115,7 +132,10 @@ static char *buffer_get_data(struct libscols_buffer *buf) + } + + /* encode data by mbs_safe_encode() to avoid control and non-printable chars */ +-static char *buffer_get_safe_data(struct libscols_buffer *buf, size_t *cells) ++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; +@@ -129,7 +149,14 @@ static char *buffer_get_safe_data(struct libscols_buffer *buf, size_t *cells) + goto nothing; + } + +- res = mbs_safe_encode_to_buffer(data, cells, buf->encdata); ++ 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; +@@ -151,11 +178,257 @@ static size_t buffer_get_safe_art_size(struct libscols_buffer *buf) + return bytes; + } + +-#define is_last_column(_tb, _cl) \ +- list_entry_is_last(&(_cl)->cl_columns, &(_tb)->tb_columns) ++/* 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); + +-#define colsep(tb) ((tb)->colsep ? (tb)->colsep : " ") +-#define linesep(tb) ((tb)->linesep ? (tb)->linesep : "\n") ++ 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, +@@ -165,76 +438,120 @@ static int print_data(struct libscols_table *tb, + { + size_t len = 0, i, width, bytes; + const char *color = NULL; +- char *data; ++ char *data, *nextchunk; ++ int is_last; + + assert(tb); + assert(cl); + +- DBG(TAB, ul_debugobj(tb, +- " -> data, column=%p, line=%p, cell=%p, buff=%p", +- cl, ln, ce, buf)); +- + data = buffer_get_data(buf); + if (!data) + data = ""; + +- /* raw mode */ +- if (scols_table_is_raw(tb)) { ++ is_last = is_last_column(cl); ++ ++ switch (tb->format) { ++ case SCOLS_FMT_RAW: + fputs_nonblank(data, tb->out); +- if (!is_last_column(tb, cl)) ++ if (!is_last) + fputs(colsep(tb), tb->out); + return 0; +- } + +- /* NAME=value mode */ +- if (scols_table_is_export(tb)) { ++ case SCOLS_FMT_EXPORT: + fprintf(tb->out, "%s=", scols_cell_get_data(&cl->header)); + fputs_quoted(data, tb->out); +- if (!is_last_column(tb, cl)) ++ if (!is_last) + fputs(colsep(tb), tb->out); + return 0; +- } + +- if (tb->colors_wanted) { +- if (ce && !color) +- color = ce->color; +- if (ln && !color) +- color = ln->color; +- if (!color) +- color = cl->color; ++ 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 */ + } + +- /* encode, note that 'len' and 'width' are number of cells, not bytes */ +- data = buffer_get_safe_data(buf, &len); ++ 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 = ""; +- width = cl->width; + 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_column(tb, cl) && len < width && !scols_table_is_maxout(tb)) ++ 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' */ ++ } + +- if (!data || bytes == (size_t) -1) { +- bytes = len = 0; +- data = NULL; +- } ++ /* 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)) { +- size_t xw = cl->width; + if (color) + fputs(color, tb->out); +- fprintf(tb->out, "%*s", (int) xw, data); ++ for (i = len; i < width; i++) ++ fputs(cellpadding_symbol(tb), tb->out); ++ fputs(data, tb->out); + if (color) + fputs(UL_COLOR_RESET, tb->out); +- if (len < xw) +- len = xw; ++ len = width; ++ + } else if (color) { + char *p = data; + size_t art = buffer_get_safe_art_size(buf); +@@ -252,46 +569,17 @@ static int print_data(struct libscols_table *tb, + fputs(data, tb->out); + } + for (i = len; i < width; i++) +- fputs(" ", tb->out); /* padding */ +- +- if (!is_last_column(tb, cl)) { +- if (len > width && !scols_column_is_trunc(cl)) { +- fputs(linesep(tb), tb->out); +- for (i = 0; i <= (size_t) cl->seqnum; i++) { +- struct libscols_column *x = scols_table_get_column(tb, i); +- fprintf(tb->out, "%*s ", -((int)x->width), " "); +- } +- } else +- fputs(colsep(tb), tb->out); /* columns separator */ +- } +- +- return 0; +-} +- +-/* 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; ++ fputs(cellpadding_symbol(tb), tb->out); /* padding */ + +- assert(ln); +- assert(buf); +- +- if (!ln->parent) ++ if (is_last) + 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 = " "; ++ if (len > width && !scols_column_is_trunc(cl)) ++ print_newline_padding(tb, cl, ln, buf->bufsz); /* next column starts on next line */ + else +- art = tb->symbols->vert; ++ fputs(colsep(tb), tb->out); /* columns separator */ + +- return buffer_append_data(buf, art); ++ return 0; + } + + static int cell_to_buffer(struct libscols_table *tb, +@@ -322,13 +610,13 @@ static int cell_to_buffer(struct libscols_table *tb, + /* + * Tree stuff + */ +- if (ln->parent) { ++ 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, tb->symbols->right); ++ rc = buffer_append_data(buf, right_symbol(tb)); + else if (!rc) +- rc = buffer_append_data(buf, tb->symbols->branch); ++ rc = buffer_append_data(buf, branch_symbol(tb)); + if (!rc) + buffer_set_art_index(buf); + } +@@ -338,34 +626,252 @@ static int cell_to_buffer(struct libscols_table *tb, + 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 maybe be printed in more formats (raw, NAME=xxx pairs) and +- * control and non-printable chars maybe encoded in \x?? hex encoding. ++ * 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; ++ int rc = 0, pending = 0; + struct libscols_column *cl; + struct libscols_iter itr; + + assert(ln); + +- DBG(TAB, ul_debugobj(tb, "printing line, line=%p, buff=%p", ln, buf)); ++ 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) ++ 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); ++ } + } + +- if (rc == 0) +- fputs(linesep(tb), tb->out); +- return 0; ++ 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) +@@ -376,86 +882,139 @@ static int print_header(struct libscols_table *tb, struct libscols_buffer *buf) + + assert(tb); + +- if (scols_table_is_noheadings(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 width according to the size of data +- */ ++ /* 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) ++ 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_table(struct libscols_table *tb, struct libscols_buffer *buf) ++ ++static int print_range( struct libscols_table *tb, ++ struct libscols_buffer *buf, ++ struct libscols_iter *itr, ++ struct libscols_line *end) + { +- int rc; ++ int rc = 0; + struct libscols_line *ln; +- struct libscols_iter itr; + + assert(tb); ++ DBG(TAB, ul_debugobj(tb, "printing range")); + +- rc = print_header(tb, buf); ++ while (rc == 0 && scols_table_next_line(tb, itr, &ln) == 0) { + +- scols_reset_iter(&itr, SCOLS_ITER_FORWARD); +- 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) ++ struct libscols_buffer *buf, ++ int last, ++ int last_in_table) + { + int rc; +- struct list_head *p; + ++ /* print the line */ ++ fput_line_open(tb); + rc = print_line(tb, ln, buf); + if (rc) +- return rc; +- if (list_empty(&ln->ln_branch)) +- return 0; ++ goto done; + +- /* print all children */ +- list_for_each(p, &ln->ln_branch) { +- struct libscols_line *chld = +- list_entry(p, struct libscols_line, ln_children); +- rc = print_tree_line(tb, chld, buf); +- if (rc) +- break; ++ /* 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; +- struct libscols_line *ln; ++ int rc = 0; ++ struct libscols_line *ln, *last = NULL; + struct libscols_iter itr; + + assert(tb); + + DBG(TAB, ul_debugobj(tb, "printing tree")); + +- rc = print_header(tb, buf); ++ 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); ++ rc = print_tree_line(tb, ln, buf, ln == last, ln == last); + } + + return rc; +@@ -463,9 +1022,14 @@ static int print_tree(struct libscols_table *tb, struct libscols_buffer *buf) + + 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", ++ "extreme=%s %s", + + cl->header.data, cl->seqnum, cl->width, + cl->width_hint > 1 ? (int) cl->width_hint : +@@ -473,7 +1037,8 @@ static void dbg_column(struct libscols_table *tb, struct libscols_column *cl) + cl->width_avg, + cl->width_max, + cl->width_min, +- cl->is_extreme ? "yes" : "not")); ++ cl->is_extreme ? "yes" : "not", ++ cl->flags & SCOLS_FL_TRUNC ? "trunc" : "")); + } + + static void dbg_columns(struct libscols_table *tb) +@@ -486,14 +1051,15 @@ static void dbg_columns(struct libscols_table *tb) + dbg_column(tb, cl); + } + ++ + /* + * This function counts column width. + * +- * For the SCOLS_FL_NOEXTREMES columns is possible to call this function two +- * times. The first pass counts width and average width. If the column +- * contains too large fields (width greater than 2 * average) then the column +- * is marked as "extreme". In the second pass all extreme fields are ignored +- * and column width is counted from non-extreme fields only. ++ * 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, +@@ -508,6 +1074,19 @@ static int count_column_width(struct libscols_table *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); ++ } ++ if (!cl->width_min) ++ cl->width_min = 1; ++ } + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { +@@ -516,15 +1095,20 @@ static int count_column_width(struct libscols_table *tb, + + rc = cell_to_buffer(tb, ln, cl, buf); + if (rc) +- return rc; ++ goto done; + + data = buffer_get_data(buf); +- len = data ? mbs_safe_width(data) : 0; ++ ++ 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; +- if (len > cl->width_max) +- cl->width_max = len; ++ cl->width_max = max(len, cl->width_max); + + if (cl->is_extreme && len > cl->width_avg * 2) + continue; +@@ -532,81 +1116,105 @@ static int count_column_width(struct libscols_table *tb, + sum += len; + count++; + } +- if (len > cl->width) +- cl->width = len; ++ 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 (count && cl->width_avg == 0) { + cl->width_avg = sum / count; +- + if (cl->width_max > cl->width_avg * 2) + cl->is_extreme = 1; + } + +- /* check and set minimal column width */ +- if (scols_cell_get_data(&cl->header)) +- cl->width_min = mbs_safe_width(scols_cell_get_data(&cl->header)); +- + /* enlarge to minimal width */ + if (cl->width < cl->width_min && !scols_column_is_strict_width(cl)) + cl->width = cl->width_min; + +- /* use relative size for large columns */ ++ /* 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; + ++done: + ON_DBG(COL, dbg_column(tb, cl)); + return rc; + } + +- + /* +- * This is core of the scols_* voodo... ++ * 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; /* output width */ +- int trunc_only, rc = 0; ++ 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) +- return rc; ++ goto done; ++ ++ is_last = is_last_column(cl); + +- width += cl->width + (is_last_column(tb, cl) ? 0 : 1); ++ 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) +- return 0; ++ if (!tb->is_term) { ++ DBG(TAB, ul_debugobj(tb, " non-terminal output")); ++ goto done; ++ } + +- /* reduce columns with extreme fields +- */ ++ /* 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)")); ++ 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) ++ if (!cl->is_extreme || scols_column_is_hidden(cl)) + continue; + + org_width = cl->width; + rc = count_column_width(tb, cl, buf); + if (rc) +- return rc; ++ goto done; + + if (org_width > cl->width) + width -= org_width - cl->width; +@@ -617,17 +1225,17 @@ static int recount_widths(struct libscols_table *tb, struct libscols_buffer *buf + + if (width < tb->termwidth) { + if (extremes) { +- DBG(TAB, ul_debugobj(tb, " enlarge width (extreme columns)")); ++ 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) ++ if (!cl->is_extreme || scols_column_is_hidden(cl)) + continue; + +- /* this column is tooo large, ignore? ++ /* this column is too large, ignore? + if (cl->width_max - cl->width > + (tb->termwidth - width)) + continue; +@@ -646,12 +1254,14 @@ static int recount_widths(struct libscols_table *tb, struct libscols_buffer *buf + } + + if (width < tb->termwidth && scols_table_is_maxout(tb)) { +- DBG(TAB, ul_debugobj(tb, " enlarge width (max-out)")); ++ DBG(TAB, ul_debugobj(tb, " enlarge width (max-out)")); + +- /* try enlarge all columns */ ++ /* 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) +@@ -660,67 +1270,133 @@ static int recount_widths(struct libscols_table *tb, struct libscols_buffer *buf + } + } else if (width < tb->termwidth) { + /* enlarge the last column */ +- struct libscols_column *cl = list_entry( ++ struct libscols_column *col = list_entry( + tb->tb_columns.prev, struct libscols_column, cl_columns); + +- DBG(TAB, ul_debugobj(tb, " enlarge width (last column)")); ++ DBG(TAB, ul_debugobj(tb, " enlarge width (last column)")); + +- if (!scols_column_is_right(cl) && tb->termwidth - width > 0) { +- cl->width += tb->termwidth - width; ++ 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 two steps: +- * 1/ reduce columns with a relative width and with truncate flag +- * 2) reduce columns with a relative width without truncate flag ++ /* 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. + */ +- trunc_only = 1; +- while (width > tb->termwidth) { +- size_t org = width; ++ for (stage = 1; width > tb->termwidth && stage <= 3; ) { ++ size_t org_width = width; + +- DBG(TAB, ul_debugobj(tb, " reduce width (current=%zu, " +- "wanted=%zu, mode=%s)", +- width, tb->termwidth, +- trunc_only ? "trunc-only" : "all-relative")); ++ 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; +- if (cl->width_hint > 1 && !scols_column_is_trunc(cl)) +- continue; /* never truncate columns with absolute sizes */ +- if (scols_column_is_tree(cl)) +- continue; /* never truncate the tree */ +- if (trunc_only && !scols_column_is_trunc(cl)) +- continue; ++ ++ /* never truncate if already minimal width */ + if (cl->width == cl->width_min) + continue; + +- /* truncate column with relative sizes */ +- if (cl->width_hint < 1 && cl->width > 0 && width > 0 && +- cl->width > cl->width_hint * tb->termwidth) { ++ /* 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--; +- } +- /* truncate column with absolute size */ +- if (cl->width_hint > 1 && cl->width > 0 && width > 0 && +- !trunc_only) { ++ 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; + } +- if (org == width) { +- if (trunc_only) +- trunc_only = 0; +- else ++ ++ /* 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; ++ } + } + } +- +- DBG(TAB, ul_debugobj(tb, " result: %zu", width)); ++done: ++ DBG(TAB, ul_debugobj(tb, " final width: %zu (rc=%d)", width, rc)); + ON_DBG(TAB, dbg_columns(tb)); + + return rc; +@@ -742,64 +1418,281 @@ static size_t strlen_line(struct libscols_line *ln) + return sz; + } + ++static void cleanup_printing(struct libscols_table *tb, struct libscols_buffer *buf) ++{ ++ if (!tb) ++ return; + ++ free_buffer(buf); + +-/** +- * scols_print_table: +- * @tb: table +- * +- * Prints the table to the output stream. +- * +- * Returns: 0, a negative value in case of an error. +- */ +-int scols_print_table(struct libscols_table *tb) ++ 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) + { +- int rc = 0; +- size_t bufsz; ++ size_t bufsz, extra_bufsz = 0; + struct libscols_line *ln; + struct libscols_iter itr; +- struct libscols_buffer *buf; ++ int rc; + +- assert(tb); +- if (!tb) +- return -1; ++ DBG(TAB, ul_debugobj(tb, "initialize printing")); ++ *buf = NULL; + +- DBG(TAB, ul_debugobj(tb, "printing")); +- if (!tb->symbols) +- scols_table_set_symbols(tb, NULL); /* use default */ ++ 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; + +- tb->is_term = isatty(STDOUT_FILENO) ? 1 : 0; +- tb->termwidth = tb->is_term ? get_terminal_width() : 0; +- if (tb->termwidth <= 0) +- tb->termwidth = 80; +- tb->termwidth -= tb->termreduce; ++ if (!tb->is_term || tb->format != SCOLS_FMT_HUMAN || scols_table_is_tree(tb)) ++ tb->header_repeat = 0; + +- bufsz = tb->termwidth; ++ /* ++ * 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 = strlen_line(ln); ++ 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) +- return -ENOMEM; ++ *buf = new_buffer(bufsz + 1); /* data + space for \0 */ ++ if (!*buf) { ++ rc = -ENOMEM; ++ goto err; ++ } + +- if (!(scols_table_is_raw(tb) || scols_table_is_export(tb))) { +- rc = recount_widths(tb, buf); ++ 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: +- free_buffer(buf); ++ 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; + } + +@@ -812,10 +1705,10 @@ done: + * + * 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) + { +-#ifdef HAVE_OPEN_MEMSTREAM +- FILE *stream; ++ FILE *stream, *old_stream; + size_t sz; + int rc; + +@@ -829,13 +1722,20 @@ int scols_print_table_to_string(struct libscols_table *tb, char **data) + if (!stream) + return -ENOMEM; + ++ old_stream = scols_table_get_stream(tb); + scols_table_set_stream(tb, stream); +- rc = scols_print_table(tb); ++ 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 + } ++#endif + +-- +2.14.4 + diff --git a/SOURCES/0154-tests-backport-libsmartcols-tests.patch b/SOURCES/0154-tests-backport-libsmartcols-tests.patch new file mode 100644 index 0000000..e59618d --- /dev/null +++ b/SOURCES/0154-tests-backport-libsmartcols-tests.patch @@ -0,0 +1,990 @@ +From 83f79678dbb9cb48969968fa4df57d98c67a321d Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Thu, 31 May 2018 11:44:35 +0200 +Subject: [PATCH 154/173] tests: backport libsmartcols tests + +Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1561350 +Signed-off-by: Karel Zak +--- + tests/commands.sh | 2 + + tests/expected/libsmartcols/fromfile | 1 + + .../libsmartcols/fromfile-column-separator | 11 + + tests/expected/libsmartcols/fromfile-export | 10 + + tests/expected/libsmartcols/fromfile-hidden | 11 + + tests/expected/libsmartcols/fromfile-noextremes | 12 ++ + tests/expected/libsmartcols/fromfile-raw | 11 + + tests/expected/libsmartcols/fromfile-right | 11 + + tests/expected/libsmartcols/fromfile-right-maxout | 11 + + tests/expected/libsmartcols/fromfile-strictwidth | 11 + + tests/expected/libsmartcols/fromfile-tree | 11 + + tests/expected/libsmartcols/fromfile-tree-end | 11 + + tests/expected/libsmartcols/fromfile-tree-json | 29 +++ + tests/expected/libsmartcols/fromfile-tree-middle | 11 + + tests/expected/libsmartcols/fromfile-trunc | 11 + + tests/expected/libsmartcols/fromfile-wrap | 17 ++ + tests/expected/libsmartcols/fromfile-wrap-tree | 18 ++ + tests/expected/libsmartcols/fromfile-wrapnl | 19 ++ + tests/expected/libsmartcols/fromfile-wrapnl-tree | 19 ++ + tests/expected/libsmartcols/title | 16 ++ + tests/ts/libsmartcols/files/col-hidden | 3 + + tests/ts/libsmartcols/files/col-id | 3 + + tests/ts/libsmartcols/files/col-name | 3 + + tests/ts/libsmartcols/files/col-noextremes | 3 + + tests/ts/libsmartcols/files/col-number | 3 + + tests/ts/libsmartcols/files/col-parent | 3 + + tests/ts/libsmartcols/files/col-strict | 3 + + tests/ts/libsmartcols/files/col-string | 3 + + tests/ts/libsmartcols/files/col-tree | 3 + + tests/ts/libsmartcols/files/col-trunc | 3 + + tests/ts/libsmartcols/files/col-wrap | 3 + + tests/ts/libsmartcols/files/col-wrapnl | 3 + + tests/ts/libsmartcols/files/data-id | 10 + + tests/ts/libsmartcols/files/data-number | 10 + + tests/ts/libsmartcols/files/data-number-tiny | 10 + + tests/ts/libsmartcols/files/data-parent | 10 + + tests/ts/libsmartcols/files/data-string | 10 + + tests/ts/libsmartcols/files/data-string-extreme | 10 + + tests/ts/libsmartcols/files/data-string-long | 10 + + tests/ts/libsmartcols/files/data-string-nl | 10 + + tests/ts/libsmartcols/fromfile | 240 +++++++++++++++++++++ + tests/ts/libsmartcols/title | 28 +++ + 42 files changed, 637 insertions(+) + create mode 100644 tests/expected/libsmartcols/fromfile + create mode 100644 tests/expected/libsmartcols/fromfile-column-separator + create mode 100644 tests/expected/libsmartcols/fromfile-export + create mode 100644 tests/expected/libsmartcols/fromfile-hidden + create mode 100644 tests/expected/libsmartcols/fromfile-noextremes + create mode 100644 tests/expected/libsmartcols/fromfile-raw + create mode 100644 tests/expected/libsmartcols/fromfile-right + create mode 100644 tests/expected/libsmartcols/fromfile-right-maxout + create mode 100644 tests/expected/libsmartcols/fromfile-strictwidth + create mode 100644 tests/expected/libsmartcols/fromfile-tree + create mode 100644 tests/expected/libsmartcols/fromfile-tree-end + create mode 100644 tests/expected/libsmartcols/fromfile-tree-json + create mode 100644 tests/expected/libsmartcols/fromfile-tree-middle + create mode 100644 tests/expected/libsmartcols/fromfile-trunc + create mode 100644 tests/expected/libsmartcols/fromfile-wrap + create mode 100644 tests/expected/libsmartcols/fromfile-wrap-tree + create mode 100644 tests/expected/libsmartcols/fromfile-wrapnl + create mode 100644 tests/expected/libsmartcols/fromfile-wrapnl-tree + create mode 100644 tests/expected/libsmartcols/title + create mode 100644 tests/ts/libsmartcols/files/col-hidden + create mode 100644 tests/ts/libsmartcols/files/col-id + create mode 100644 tests/ts/libsmartcols/files/col-name + create mode 100644 tests/ts/libsmartcols/files/col-noextremes + create mode 100644 tests/ts/libsmartcols/files/col-number + create mode 100644 tests/ts/libsmartcols/files/col-parent + create mode 100644 tests/ts/libsmartcols/files/col-strict + create mode 100644 tests/ts/libsmartcols/files/col-string + create mode 100644 tests/ts/libsmartcols/files/col-tree + create mode 100644 tests/ts/libsmartcols/files/col-trunc + create mode 100644 tests/ts/libsmartcols/files/col-wrap + create mode 100644 tests/ts/libsmartcols/files/col-wrapnl + create mode 100644 tests/ts/libsmartcols/files/data-id + create mode 100644 tests/ts/libsmartcols/files/data-number + create mode 100644 tests/ts/libsmartcols/files/data-number-tiny + create mode 100644 tests/ts/libsmartcols/files/data-parent + create mode 100644 tests/ts/libsmartcols/files/data-string + create mode 100644 tests/ts/libsmartcols/files/data-string-extreme + create mode 100644 tests/ts/libsmartcols/files/data-string-long + create mode 100644 tests/ts/libsmartcols/files/data-string-nl + create mode 100755 tests/ts/libsmartcols/fromfile + create mode 100755 tests/ts/libsmartcols/title + +diff --git a/tests/commands.sh b/tests/commands.sh +index e769b5dac..96b8dc97c 100644 +--- a/tests/commands.sh ++++ b/tests/commands.sh +@@ -20,6 +20,8 @@ TS_HELPER_PARTITIONS="$top_builddir/sample-partitions" + TS_HELPER_PATHS="$top_builddir/test_pathnames" + TS_HELPER_STRUTILS="$top_builddir/test_strutils" + TS_HELPER_SYSINFO="$top_builddir/test_sysinfo" ++TS_HELPER_LIBSMARTCOLS_FROMFILE="$top_builddir/sample-scols-fromfile" ++TS_HELPER_LIBSMARTCOLS_TITLE="$top_builddir/sample-scols-title" + + # paths to commands + TS_CMD_BLKID=${TS_CMD_BLKID-"$top_builddir/blkid"} +diff --git a/tests/expected/libsmartcols/fromfile b/tests/expected/libsmartcols/fromfile +new file mode 100644 +index 000000000..4155aa36f +--- /dev/null ++++ b/tests/expected/libsmartcols/fromfile +@@ -0,0 +1 @@ ++...done. +diff --git a/tests/expected/libsmartcols/fromfile-column-separator b/tests/expected/libsmartcols/fromfile-column-separator +new file mode 100644 +index 000000000..8c5aa2aff +--- /dev/null ++++ b/tests/expected/libsmartcols/fromfile-column-separator +@@ -0,0 +1,11 @@ ++NAME | NUM|TRUNC ++aaaa | 0|qqqqqqqqqqqqqqqqqX ++bbb | 100|dddddddddddddX ++ccccc | 21|ffffffffffffffffffffffffffffffffffffffffX ++dddddd| 3|ssssssssssX ++ee | 411|ddddddddddddddddddddddddddX ++ffff | 5111|jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjX ++gggggg|678993321|mmmmmmmmmmmmmmmmmmmX ++hhh | 7666666|lllllllllllllllllllllllllllllllllllllX ++iiiiii| 8765|yyyyyyyyyyyyyyyyyyyyyyyyyyyyX ++jj | 987456|pppppppppX +diff --git a/tests/expected/libsmartcols/fromfile-export b/tests/expected/libsmartcols/fromfile-export +new file mode 100644 +index 000000000..f63afd882 +--- /dev/null ++++ b/tests/expected/libsmartcols/fromfile-export +@@ -0,0 +1,10 @@ ++NAME="aaaa" NUM="0" TRUNC="qqqqqqqqqqqqqqqqqX" ++NAME="bbb" NUM="100" TRUNC="dddddddddddddX" ++NAME="ccccc" NUM="21" TRUNC="ffffffffffffffffffffffffffffffffffffffffX" ++NAME="dddddd" NUM="3" TRUNC="ssssssssssX" ++NAME="ee" NUM="411" TRUNC="ddddddddddddddddddddddddddX" ++NAME="ffff" NUM="5111" TRUNC="jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjX" ++NAME="gggggg" NUM="678993321" TRUNC="mmmmmmmmmmmmmmmmmmmX" ++NAME="hhh" NUM="7666666" TRUNC="lllllllllllllllllllllllllllllllllllllX" ++NAME="iiiiii" NUM="8765" TRUNC="yyyyyyyyyyyyyyyyyyyyyyyyyyyyX" ++NAME="jj" NUM="987456" TRUNC="pppppppppX" +diff --git a/tests/expected/libsmartcols/fromfile-hidden b/tests/expected/libsmartcols/fromfile-hidden +new file mode 100644 +index 000000000..cf50bcb3e +--- /dev/null ++++ b/tests/expected/libsmartcols/fromfile-hidden +@@ -0,0 +1,11 @@ ++NAME NUM ++aaaa 0 ++bbb 100 ++ccccc 21 ++dddddd 3 ++ee 411 ++ffff 5111 ++gggggg 678993321 ++hhh 7666666 ++iiiiii 8765 ++jj 987456 +diff --git a/tests/expected/libsmartcols/fromfile-noextremes b/tests/expected/libsmartcols/fromfile-noextremes +new file mode 100644 +index 000000000..4bac08070 +--- /dev/null ++++ b/tests/expected/libsmartcols/fromfile-noextremes +@@ -0,0 +1,12 @@ ++NAME NOEXTREME NUM NAME NUM ++aaaa qqqqqqX 0 aaaa 0 ++bbb ddddddddX 100 bbb 100 ++ccccc ffffffffffffffffffffffffffffffffffX ++ 21 ccccc 21 ++dddddd sssX 3 dddddd 3 ++ee ddX 411 ee 411 ++ffff jjjjjX 5111 ffff 5111 ++gggggg mmmmmmmX 678993321 gggggg 678993321 ++hhh llllllllllX 7666666 hhh 7666666 ++iiiiii yyyyyyX 8765 iiiiii 8765 ++jj pppppX 987456 jj 987456 +diff --git a/tests/expected/libsmartcols/fromfile-raw b/tests/expected/libsmartcols/fromfile-raw +new file mode 100644 +index 000000000..cc188aaab +--- /dev/null ++++ b/tests/expected/libsmartcols/fromfile-raw +@@ -0,0 +1,11 @@ ++NAME NUM TRUNC ++aaaa 0 qqqqqqqqqqqqqqqqqX ++bbb 100 dddddddddddddX ++ccccc 21 ffffffffffffffffffffffffffffffffffffffffX ++dddddd 3 ssssssssssX ++ee 411 ddddddddddddddddddddddddddX ++ffff 5111 jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjX ++gggggg 678993321 mmmmmmmmmmmmmmmmmmmX ++hhh 7666666 lllllllllllllllllllllllllllllllllllllX ++iiiiii 8765 yyyyyyyyyyyyyyyyyyyyyyyyyyyyX ++jj 987456 pppppppppX +diff --git a/tests/expected/libsmartcols/fromfile-right b/tests/expected/libsmartcols/fromfile-right +new file mode 100644 +index 000000000..d78285a13 +--- /dev/null ++++ b/tests/expected/libsmartcols/fromfile-right +@@ -0,0 +1,11 @@ ++NAME NUM STRINGS ++aaaa 0 qqqqqqqqqqqqqqqqqX ++bbb 100 dddddddddddddX ++ccccc 21 ffffffffffffffffffffffffffffffffffffffffX ++dddddd 3 ssssssssssX ++ee 411 ddddddddddddddddddddddddddX ++ffff 5111 jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjX ++gggggg 678993321 mmmmmmmmmmmmmmmmmmmX ++hhh 7666666 lllllllllllllllllllllllllllllllllllllX ++iiiiii 8765 yyyyyyyyyyyyyyyyyyyyyyyyyyyyX ++jj 987456 pppppppppX +diff --git a/tests/expected/libsmartcols/fromfile-right-maxout b/tests/expected/libsmartcols/fromfile-right-maxout +new file mode 100644 +index 000000000..3bcb65abd +--- /dev/null ++++ b/tests/expected/libsmartcols/fromfile-right-maxout +@@ -0,0 +1,11 @@ ++NAME NUM ++aaaa 0 ++bbb 100 ++ccccc 21 ++dddddd 3 ++ee 411 ++ffff 5111 ++gggggg 678993321 ++hhh 7666666 ++iiiiii 8765 ++jj 987456 +diff --git a/tests/expected/libsmartcols/fromfile-strictwidth b/tests/expected/libsmartcols/fromfile-strictwidth +new file mode 100644 +index 000000000..41054b235 +--- /dev/null ++++ b/tests/expected/libsmartcols/fromfile-strictwidth +@@ -0,0 +1,11 @@ ++NAME STRICT NUM ++aaaa 0 0 ++bbb 1 100 ++ccccc 2 21 ++dddddd 3 3 ++ee 4 411 ++ffff 5 5111 ++gggggg 6 678993321 ++hhh 7 7666666 ++iiiiii 8 8765 ++jj 9 987456 +diff --git a/tests/expected/libsmartcols/fromfile-tree b/tests/expected/libsmartcols/fromfile-tree +new file mode 100644 +index 000000000..9d345f8dc +--- /dev/null ++++ b/tests/expected/libsmartcols/fromfile-tree +@@ -0,0 +1,11 @@ ++TREE ID PARENT STRINGS ++aaaa 1 0 qqqqqqqqqqqqqqqqqX ++|-bbb 2 1 dddddddddddddX ++| |-ee 5 2 ddddddddddddddddddddddddddX ++| `-ffff 6 2 jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjX ++|-ccccc 3 1 ffffffffffffffffffffffffffffffffffffffffX ++| `-gggggg 7 3 mmmmmmmmmmmmmmmmmmmX ++| |-hhh 8 7 lllllllllllllllllllllllllllllllllllllX ++| | `-iiiiii 9 8 yyyyyyyyyyyyyyyyyyyyyyyyyyyyX ++| `-jj 10 7 pppppppppX ++`-dddddd 4 1 ssssssssssX +diff --git a/tests/expected/libsmartcols/fromfile-tree-end b/tests/expected/libsmartcols/fromfile-tree-end +new file mode 100644 +index 000000000..41aebff56 +--- /dev/null ++++ b/tests/expected/libsmartcols/fromfile-tree-end +@@ -0,0 +1,11 @@ ++ID PARENT STRINGS TREE ++ 1 0 qqqqqqqqqqqqqqqqqX aaaa ++ 2 1 dddddddddddddX |-bbb ++ 5 2 ddddddddddddddddddddddddddX | |-ee ++ 6 2 jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjX | `-ffff ++ 3 1 ffffffffffffffffffffffffffffffffffffffffX |-ccccc ++ 7 3 mmmmmmmmmmmmmmmmmmmX | `-gggggg ++ 8 7 lllllllllllllllllllllllllllllllllllllX | |-hhh ++ 9 8 yyyyyyyyyyyyyyyyyyyyyyyyyyyyX | | `-iiiiii ++10 7 pppppppppX | `-jj ++ 4 1 ssssssssssX `-dddddd +diff --git a/tests/expected/libsmartcols/fromfile-tree-json b/tests/expected/libsmartcols/fromfile-tree-json +new file mode 100644 +index 000000000..5b3546d0a +--- /dev/null ++++ b/tests/expected/libsmartcols/fromfile-tree-json +@@ -0,0 +1,29 @@ ++{ ++ "testtable": [ ++ {"tree":"aaaa", "id":"1", "parent":"0", "strings":"qqqqqqqqqqqqqqqqqX", ++ "children": [ ++ {"tree":"bbb", "id":"2", "parent":"1", "strings":"dddddddddddddX", ++ "children": [ ++ {"tree":"ee", "id":"5", "parent":"2", "strings":"ddddddddddddddddddddddddddX"}, ++ {"tree":"ffff", "id":"6", "parent":"2", "strings":"jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjX"} ++ ] ++ }, ++ {"tree":"ccccc", "id":"3", "parent":"1", "strings":"ffffffffffffffffffffffffffffffffffffffffX", ++ "children": [ ++ {"tree":"gggggg", "id":"7", "parent":"3", "strings":"mmmmmmmmmmmmmmmmmmmX", ++ "children": [ ++ {"tree":"hhh", "id":"8", "parent":"7", "strings":"lllllllllllllllllllllllllllllllllllllX", ++ "children": [ ++ {"tree":"iiiiii", "id":"9", "parent":"8", "strings":"yyyyyyyyyyyyyyyyyyyyyyyyyyyyX"} ++ ] ++ }, ++ {"tree":"jj", "id":"10", "parent":"7", "strings":"pppppppppX"} ++ ] ++ } ++ ] ++ }, ++ {"tree":"dddddd", "id":"4", "parent":"1", "strings":"ssssssssssX"} ++ ] ++ } ++ ] ++} +diff --git a/tests/expected/libsmartcols/fromfile-tree-middle b/tests/expected/libsmartcols/fromfile-tree-middle +new file mode 100644 +index 000000000..b2183313c +--- /dev/null ++++ b/tests/expected/libsmartcols/fromfile-tree-middle +@@ -0,0 +1,11 @@ ++ID PARENT TREE STRINGS ++ 1 0 aaaa qqqqqqqqqqqqqqqqqX ++ 2 1 |-bbb dddddddddddddX ++ 5 2 | |-ee ddddddddddddddddddddddddddX ++ 6 2 | `-ffff jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjX ++ 3 1 |-ccccc ffffffffffffffffffffffffffffffffffffffffX ++ 7 3 | `-gggggg mmmmmmmmmmmmmmmmmmmX ++ 8 7 | |-hhh lllllllllllllllllllllllllllllllllllllX ++ 9 8 | | `-iiiiii yyyyyyyyyyyyyyyyyyyyyyyyyyyyX ++10 7 | `-jj pppppppppX ++ 4 1 `-dddddd ssssssssssX +diff --git a/tests/expected/libsmartcols/fromfile-trunc b/tests/expected/libsmartcols/fromfile-trunc +new file mode 100644 +index 000000000..6a095dc88 +--- /dev/null ++++ b/tests/expected/libsmartcols/fromfile-trunc +@@ -0,0 +1,11 @@ ++NAME NUM TRUNC ++aaaa 0 qqqqqqqqqqqqqqqqqX ++bbb 100 dddddddddddddX ++ccccc 21 fffffffffffffffffffffff ++dddddd 3 ssssssssssX ++ee 411 ddddddddddddddddddddddd ++ffff 5111 jjjjjjjjjjjjjjjjjjjjjjj ++gggggg 678993321 mmmmmmmmmmmmmmmmmmmX ++hhh 7666666 lllllllllllllllllllllll ++iiiiii 8765 yyyyyyyyyyyyyyyyyyyyyyy ++jj 987456 pppppppppX +diff --git a/tests/expected/libsmartcols/fromfile-wrap b/tests/expected/libsmartcols/fromfile-wrap +new file mode 100644 +index 000000000..bb94f4973 +--- /dev/null ++++ b/tests/expected/libsmartcols/fromfile-wrap +@@ -0,0 +1,17 @@ ++NAME NUM WRAP ++aaaa 0 qqqqqqqqqqqqqqqqqX ++bbb 100 dddddddddddddX ++ccccc 21 fffffffffffffffffffffff ++ fffffffffffffffffX ++dddddd 3 ssssssssssX ++ee 411 ddddddddddddddddddddddd ++ dddX ++ffff 5111 jjjjjjjjjjjjjjjjjjjjjjj ++ jjjjjjjjjjjjjjjjjjjjjjj ++ jjjX ++gggggg 678993321 mmmmmmmmmmmmmmmmmmmX ++hhh 7666666 lllllllllllllllllllllll ++ llllllllllllllX ++iiiiii 8765 yyyyyyyyyyyyyyyyyyyyyyy ++ yyyyyX ++jj 987456 pppppppppX +diff --git a/tests/expected/libsmartcols/fromfile-wrap-tree b/tests/expected/libsmartcols/fromfile-wrap-tree +new file mode 100644 +index 000000000..a2db7a4ac +--- /dev/null ++++ b/tests/expected/libsmartcols/fromfile-wrap-tree +@@ -0,0 +1,18 @@ ++TREE ID PARENT WRAP ++aaaa 1 0 qqqqqqqqqqqqqqqqqX ++|-bbb 2 1 dddddddddddddX ++| |-ee 5 2 dddddddddddddddddddd ++| | ddddddX ++| `-ffff 6 2 jjjjjjjjjjjjjjjjjjjj ++| jjjjjjjjjjjjjjjjjjjj ++| jjjjjjjjjX ++|-ccccc 3 1 ffffffffffffffffffff ++| | ffffffffffffffffffff ++| | X ++| `-gggggg 7 3 mmmmmmmmmmmmmmmmmmmX ++| |-hhh 8 7 llllllllllllllllllll ++| | | lllllllllllllllllX ++| | `-iiiiii 9 8 yyyyyyyyyyyyyyyyyyyy ++| | yyyyyyyyX ++| `-jj 10 7 pppppppppX ++`-dddddd 4 1 ssssssssssX +diff --git a/tests/expected/libsmartcols/fromfile-wrapnl b/tests/expected/libsmartcols/fromfile-wrapnl +new file mode 100644 +index 000000000..c747ebb08 +--- /dev/null ++++ b/tests/expected/libsmartcols/fromfile-wrapnl +@@ -0,0 +1,19 @@ ++NAME NUM WRAPNL ++aaaa 0 aaa ++bbb 100 bbbbb ++ccccc 21 cccc ++ CCCC ++dddddd 3 dddddddd ++ DDDD ++ DD ++ee 411 hello ++ baby ++ffff 5111 aaa ++ bbb ++ ccc ++ ddd ++gggggg 678993321 eee ++hhh 7666666 fffff ++iiiiii 8765 g ++ hhhhh ++jj 987456 ppppppppp +diff --git a/tests/expected/libsmartcols/fromfile-wrapnl-tree b/tests/expected/libsmartcols/fromfile-wrapnl-tree +new file mode 100644 +index 000000000..3862cf16a +--- /dev/null ++++ b/tests/expected/libsmartcols/fromfile-wrapnl-tree +@@ -0,0 +1,19 @@ ++TREE ID PARENT WRAPNL ++aaaa 1 0 aaa ++|-bbb 2 1 bbbbb ++| |-ee 5 2 hello ++| | baby ++| `-ffff 6 2 aaa ++| bbb ++| ccc ++| ddd ++|-ccccc 3 1 cccc ++| | CCCC ++| `-gggggg 7 3 eee ++| |-hhh 8 7 fffff ++| | `-iiiiii 9 8 g ++| | hhhhh ++| `-jj 10 7 ppppppppp ++`-dddddd 4 1 dddddddd ++ DDDD ++ DD +diff --git a/tests/expected/libsmartcols/title b/tests/expected/libsmartcols/title +new file mode 100644 +index 000000000..bb5d7225a +--- /dev/null ++++ b/tests/expected/libsmartcols/title +@@ -0,0 +1,16 @@ ++ This is right title ++NAME DATA ++foo bla bla bla ++bar alb alb alb ++This is left title (without padding) ++NAME DATA ++foo bla bla bla ++bar alb alb alb ++=======================This is center title (with padding)====================== ++NAME DATA ++foo bla bla bla ++bar alb alb alb ++This is left title (with padding)----------------------------------------------- ++NAME DATA ++foo bla bla bla ++bar alb alb alb +diff --git a/tests/ts/libsmartcols/files/col-hidden b/tests/ts/libsmartcols/files/col-hidden +new file mode 100644 +index 000000000..83182a8ee +--- /dev/null ++++ b/tests/ts/libsmartcols/files/col-hidden +@@ -0,0 +1,3 @@ ++FOO ++0 ++hidden +diff --git a/tests/ts/libsmartcols/files/col-id b/tests/ts/libsmartcols/files/col-id +new file mode 100644 +index 000000000..0188f42a0 +--- /dev/null ++++ b/tests/ts/libsmartcols/files/col-id +@@ -0,0 +1,3 @@ ++ID ++0 ++right +diff --git a/tests/ts/libsmartcols/files/col-name b/tests/ts/libsmartcols/files/col-name +new file mode 100644 +index 000000000..0a98f29cf +--- /dev/null ++++ b/tests/ts/libsmartcols/files/col-name +@@ -0,0 +1,3 @@ ++NAME ++0 ++none +diff --git a/tests/ts/libsmartcols/files/col-noextremes b/tests/ts/libsmartcols/files/col-noextremes +new file mode 100644 +index 000000000..715edce7e +--- /dev/null ++++ b/tests/ts/libsmartcols/files/col-noextremes +@@ -0,0 +1,3 @@ ++NOEXTREME ++0 ++noextremes +diff --git a/tests/ts/libsmartcols/files/col-number b/tests/ts/libsmartcols/files/col-number +new file mode 100644 +index 000000000..34a70e4a4 +--- /dev/null ++++ b/tests/ts/libsmartcols/files/col-number +@@ -0,0 +1,3 @@ ++NUM ++0 ++right +diff --git a/tests/ts/libsmartcols/files/col-parent b/tests/ts/libsmartcols/files/col-parent +new file mode 100644 +index 000000000..86fe08ced +--- /dev/null ++++ b/tests/ts/libsmartcols/files/col-parent +@@ -0,0 +1,3 @@ ++PARENT ++0 ++right +diff --git a/tests/ts/libsmartcols/files/col-strict b/tests/ts/libsmartcols/files/col-strict +new file mode 100644 +index 000000000..62bb96b9a +--- /dev/null ++++ b/tests/ts/libsmartcols/files/col-strict +@@ -0,0 +1,3 @@ ++STRICT ++20 ++strictwidth,right +diff --git a/tests/ts/libsmartcols/files/col-string b/tests/ts/libsmartcols/files/col-string +new file mode 100644 +index 000000000..7e2904b9f +--- /dev/null ++++ b/tests/ts/libsmartcols/files/col-string +@@ -0,0 +1,3 @@ ++STRINGS ++0 ++none +diff --git a/tests/ts/libsmartcols/files/col-tree b/tests/ts/libsmartcols/files/col-tree +new file mode 100644 +index 000000000..507688000 +--- /dev/null ++++ b/tests/ts/libsmartcols/files/col-tree +@@ -0,0 +1,3 @@ ++TREE ++0 ++tree +diff --git a/tests/ts/libsmartcols/files/col-trunc b/tests/ts/libsmartcols/files/col-trunc +new file mode 100644 +index 000000000..2887b4314 +--- /dev/null ++++ b/tests/ts/libsmartcols/files/col-trunc +@@ -0,0 +1,3 @@ ++TRUNC ++0 ++trunc +diff --git a/tests/ts/libsmartcols/files/col-wrap b/tests/ts/libsmartcols/files/col-wrap +new file mode 100644 +index 000000000..dc4ca340e +--- /dev/null ++++ b/tests/ts/libsmartcols/files/col-wrap +@@ -0,0 +1,3 @@ ++WRAP ++0 ++wrap +diff --git a/tests/ts/libsmartcols/files/col-wrapnl b/tests/ts/libsmartcols/files/col-wrapnl +new file mode 100644 +index 000000000..0a18fd146 +--- /dev/null ++++ b/tests/ts/libsmartcols/files/col-wrapnl +@@ -0,0 +1,3 @@ ++WRAPNL ++0 ++wrapnl +diff --git a/tests/ts/libsmartcols/files/data-id b/tests/ts/libsmartcols/files/data-id +new file mode 100644 +index 000000000..f00c965d8 +--- /dev/null ++++ b/tests/ts/libsmartcols/files/data-id +@@ -0,0 +1,10 @@ ++1 ++2 ++3 ++4 ++5 ++6 ++7 ++8 ++9 ++10 +diff --git a/tests/ts/libsmartcols/files/data-number b/tests/ts/libsmartcols/files/data-number +new file mode 100644 +index 000000000..562d75061 +--- /dev/null ++++ b/tests/ts/libsmartcols/files/data-number +@@ -0,0 +1,10 @@ ++0 ++100 ++21 ++3 ++411 ++5111 ++678993321 ++7666666 ++8765 ++987456 +diff --git a/tests/ts/libsmartcols/files/data-number-tiny b/tests/ts/libsmartcols/files/data-number-tiny +new file mode 100644 +index 000000000..8b1acc12b +--- /dev/null ++++ b/tests/ts/libsmartcols/files/data-number-tiny +@@ -0,0 +1,10 @@ ++0 ++1 ++2 ++3 ++4 ++5 ++6 ++7 ++8 ++9 +diff --git a/tests/ts/libsmartcols/files/data-parent b/tests/ts/libsmartcols/files/data-parent +new file mode 100644 +index 000000000..aa5071608 +--- /dev/null ++++ b/tests/ts/libsmartcols/files/data-parent +@@ -0,0 +1,10 @@ ++0 ++1 ++1 ++1 ++2 ++2 ++3 ++7 ++8 ++7 +diff --git a/tests/ts/libsmartcols/files/data-string b/tests/ts/libsmartcols/files/data-string +new file mode 100644 +index 000000000..dff6e9c80 +--- /dev/null ++++ b/tests/ts/libsmartcols/files/data-string +@@ -0,0 +1,10 @@ ++aaaa ++bbb ++ccccc ++dddddd ++ee ++ffff ++gggggg ++hhh ++iiiiii ++jj +diff --git a/tests/ts/libsmartcols/files/data-string-extreme b/tests/ts/libsmartcols/files/data-string-extreme +new file mode 100644 +index 000000000..6fb395d7a +--- /dev/null ++++ b/tests/ts/libsmartcols/files/data-string-extreme +@@ -0,0 +1,10 @@ ++qqqqqqX ++ddddddddX ++ffffffffffffffffffffffffffffffffffX ++sssX ++ddX ++jjjjjX ++mmmmmmmX ++llllllllllX ++yyyyyyX ++pppppX +diff --git a/tests/ts/libsmartcols/files/data-string-long b/tests/ts/libsmartcols/files/data-string-long +new file mode 100644 +index 000000000..1b5683aa4 +--- /dev/null ++++ b/tests/ts/libsmartcols/files/data-string-long +@@ -0,0 +1,10 @@ ++qqqqqqqqqqqqqqqqqX ++dddddddddddddX ++ffffffffffffffffffffffffffffffffffffffffX ++ssssssssssX ++ddddddddddddddddddddddddddX ++jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjX ++mmmmmmmmmmmmmmmmmmmX ++lllllllllllllllllllllllllllllllllllllX ++yyyyyyyyyyyyyyyyyyyyyyyyyyyyX ++pppppppppX +diff --git a/tests/ts/libsmartcols/files/data-string-nl b/tests/ts/libsmartcols/files/data-string-nl +new file mode 100644 +index 000000000..7822e57bc +--- /dev/null ++++ b/tests/ts/libsmartcols/files/data-string-nl +@@ -0,0 +1,10 @@ ++aaa ++bbbbb ++cccc\nCCCC ++dddddddd\nDDDD\nDD ++hello\nbaby ++aaa\nbbb\nccc\nddd ++eee ++fffff ++g\nhhhhh ++ppppppppp +diff --git a/tests/ts/libsmartcols/fromfile b/tests/ts/libsmartcols/fromfile +new file mode 100755 +index 000000000..311dd7e1e +--- /dev/null ++++ b/tests/ts/libsmartcols/fromfile +@@ -0,0 +1,240 @@ ++#!/bin/bash ++# ++# This file is part of util-linux. ++# ++# This file is free software; you can redistribute it and/or modify ++# it under the terms of the GNU General Public License as published by ++# the Free Software Foundation; either version 2 of the License, or ++# (at your option) any later version. ++# ++# This file is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++# GNU General Public License for more details. ++# ++# ++ ++TS_TOPDIR="${0%/*}/../.." ++TS_DESC="fromfile" ++ ++. $TS_TOPDIR/functions.sh ++ts_init "$*" ++ ++TESTPROG="$TS_HELPER_LIBSMARTCOLS_FROMFILE" ++ts_check_test_command "$TESTPROG" ++ ++ts_init_subtest "tree" ++$TESTPROG --nlines 10 \ ++ --tree-id-column 1 \ ++ --tree-parent-column 2 \ ++ --column $TS_SELF/files/col-tree \ ++ --column $TS_SELF/files/col-id \ ++ --column $TS_SELF/files/col-parent \ ++ --column $TS_SELF/files/col-string \ ++ $TS_SELF/files/data-string \ ++ $TS_SELF/files/data-id \ ++ $TS_SELF/files/data-parent \ ++ $TS_SELF/files/data-string-long \ ++ >> $TS_OUTPUT 2>&1 ++ts_finalize_subtest ++ ++ts_init_subtest "tree-json" ++$TESTPROG --nlines 10 --json \ ++ --tree-id-column 1 \ ++ --tree-parent-column 2 \ ++ --column $TS_SELF/files/col-tree \ ++ --column $TS_SELF/files/col-id \ ++ --column $TS_SELF/files/col-parent \ ++ --column $TS_SELF/files/col-string \ ++ $TS_SELF/files/data-string \ ++ $TS_SELF/files/data-id \ ++ $TS_SELF/files/data-parent \ ++ $TS_SELF/files/data-string-long \ ++ >> $TS_OUTPUT 2>&1 ++ts_finalize_subtest ++ ++ts_init_subtest "tree-middle" ++$TESTPROG --nlines 10 \ ++ --tree-id-column 0 \ ++ --tree-parent-column 1 \ ++ --column $TS_SELF/files/col-id \ ++ --column $TS_SELF/files/col-parent \ ++ --column $TS_SELF/files/col-tree \ ++ --column $TS_SELF/files/col-string \ ++ $TS_SELF/files/data-id \ ++ $TS_SELF/files/data-parent \ ++ $TS_SELF/files/data-string \ ++ $TS_SELF/files/data-string-long \ ++ >> $TS_OUTPUT 2>&1 ++ts_finalize_subtest ++ ++ts_init_subtest "tree-end" ++$TESTPROG --nlines 10 \ ++ --tree-id-column 0 \ ++ --tree-parent-column 1 \ ++ --column $TS_SELF/files/col-id \ ++ --column $TS_SELF/files/col-parent \ ++ --column $TS_SELF/files/col-string \ ++ --column $TS_SELF/files/col-tree \ ++ $TS_SELF/files/data-id \ ++ $TS_SELF/files/data-parent \ ++ $TS_SELF/files/data-string-long \ ++ $TS_SELF/files/data-string \ ++ >> $TS_OUTPUT 2>&1 ++ts_finalize_subtest ++ ++ts_init_subtest "trunc" ++$TESTPROG --nlines 10 --width 40 \ ++ --column $TS_SELF/files/col-name \ ++ --column $TS_SELF/files/col-number \ ++ --column $TS_SELF/files/col-trunc \ ++ $TS_SELF/files/data-string \ ++ $TS_SELF/files/data-number \ ++ $TS_SELF/files/data-string-long \ ++ >> $TS_OUTPUT 2>&1 ++ts_finalize_subtest ++ ++ts_init_subtest "right" ++$TESTPROG --nlines 10 \ ++ --column $TS_SELF/files/col-name \ ++ --column $TS_SELF/files/col-number \ ++ --column $TS_SELF/files/col-string \ ++ $TS_SELF/files/data-string \ ++ $TS_SELF/files/data-number \ ++ $TS_SELF/files/data-string-long \ ++ >> $TS_OUTPUT 2>&1 ++ts_finalize_subtest ++ ++ts_init_subtest "right-maxout" ++$TESTPROG --nlines 10 --maxout --width 80\ ++ --column $TS_SELF/files/col-name \ ++ --column $TS_SELF/files/col-number \ ++ $TS_SELF/files/data-string \ ++ $TS_SELF/files/data-number \ ++ >> $TS_OUTPUT 2>&1 ++ts_finalize_subtest ++ ++ts_init_subtest "strictwidth" ++$TESTPROG --nlines 10 \ ++ --column $TS_SELF/files/col-name \ ++ --column $TS_SELF/files/col-strict \ ++ --column $TS_SELF/files/col-number \ ++ $TS_SELF/files/data-string \ ++ $TS_SELF/files/data-number-tiny \ ++ $TS_SELF/files/data-number \ ++ >> $TS_OUTPUT 2>&1 ++ts_finalize_subtest ++ ++ts_init_subtest "noextremes" ++$TESTPROG --nlines 10 --width 45 \ ++ --column $TS_SELF/files/col-name \ ++ --column $TS_SELF/files/col-noextremes \ ++ --column $TS_SELF/files/col-number \ ++ --column $TS_SELF/files/col-name \ ++ --column $TS_SELF/files/col-number \ ++ $TS_SELF/files/data-string \ ++ $TS_SELF/files/data-string-extreme \ ++ $TS_SELF/files/data-number \ ++ $TS_SELF/files/data-string \ ++ $TS_SELF/files/data-number \ ++ >> $TS_OUTPUT 2>&1 ++ts_finalize_subtest ++ ++ts_init_subtest "hidden" ++$TESTPROG --nlines 10 \ ++ --column $TS_SELF/files/col-name \ ++ --column $TS_SELF/files/col-hidden \ ++ --column $TS_SELF/files/col-number \ ++ $TS_SELF/files/data-string \ ++ $TS_SELF/files/data-string-long \ ++ $TS_SELF/files/data-number \ ++ >> $TS_OUTPUT 2>&1 ++ts_finalize_subtest ++ ++ts_init_subtest "wrap" ++$TESTPROG --nlines 10 --width 40 \ ++ --column $TS_SELF/files/col-name \ ++ --column $TS_SELF/files/col-number \ ++ --column $TS_SELF/files/col-wrap \ ++ $TS_SELF/files/data-string \ ++ $TS_SELF/files/data-number \ ++ $TS_SELF/files/data-string-long \ ++ >> $TS_OUTPUT 2>&1 ++ts_finalize_subtest ++ ++ts_init_subtest "wrap-tree" ++$TESTPROG --nlines 10 --width 45 \ ++ --tree-id-column 1 \ ++ --tree-parent-column 2 \ ++ --column $TS_SELF/files/col-tree \ ++ --column $TS_SELF/files/col-id \ ++ --column $TS_SELF/files/col-parent \ ++ --column $TS_SELF/files/col-wrap \ ++ $TS_SELF/files/data-string \ ++ $TS_SELF/files/data-id \ ++ $TS_SELF/files/data-parent \ ++ $TS_SELF/files/data-string-long \ ++ >> $TS_OUTPUT 2>&1 ++ts_finalize_subtest ++ ++ts_init_subtest "wrapnl" ++$TESTPROG --nlines 10 \ ++ --column $TS_SELF/files/col-name \ ++ --column $TS_SELF/files/col-number \ ++ --column $TS_SELF/files/col-wrapnl \ ++ $TS_SELF/files/data-string \ ++ $TS_SELF/files/data-number \ ++ $TS_SELF/files/data-string-nl \ ++ >> $TS_OUTPUT 2>&1 ++ts_finalize_subtest ++ ++ts_init_subtest "wrapnl-tree" ++$TESTPROG --nlines 10 \ ++ --tree-id-column 1 \ ++ --tree-parent-column 2 \ ++ --column $TS_SELF/files/col-tree \ ++ --column $TS_SELF/files/col-id \ ++ --column $TS_SELF/files/col-parent \ ++ --column $TS_SELF/files/col-wrapnl \ ++ $TS_SELF/files/data-string \ ++ $TS_SELF/files/data-id \ ++ $TS_SELF/files/data-parent \ ++ $TS_SELF/files/data-string-nl \ ++ >> $TS_OUTPUT 2>&1 ++ts_finalize_subtest ++ ++ts_init_subtest "raw" ++$TESTPROG --nlines 10 --raw \ ++ --column $TS_SELF/files/col-name \ ++ --column $TS_SELF/files/col-number \ ++ --column $TS_SELF/files/col-trunc \ ++ $TS_SELF/files/data-string \ ++ $TS_SELF/files/data-number \ ++ $TS_SELF/files/data-string-long \ ++ >> $TS_OUTPUT 2>&1 ++ts_finalize_subtest ++ ++ts_init_subtest "export" ++$TESTPROG --nlines 10 --export \ ++ --column $TS_SELF/files/col-name \ ++ --column $TS_SELF/files/col-number \ ++ --column $TS_SELF/files/col-trunc \ ++ $TS_SELF/files/data-string \ ++ $TS_SELF/files/data-number \ ++ $TS_SELF/files/data-string-long \ ++ >> $TS_OUTPUT 2>&1 ++ts_finalize_subtest ++ ++ts_init_subtest "column-separator" ++$TESTPROG --nlines 10 --colsep \| \ ++ --column $TS_SELF/files/col-name \ ++ --column $TS_SELF/files/col-number \ ++ --column $TS_SELF/files/col-trunc \ ++ $TS_SELF/files/data-string \ ++ $TS_SELF/files/data-number \ ++ $TS_SELF/files/data-string-long \ ++ >> $TS_OUTPUT 2>&1 ++ts_finalize_subtest ++ ++ts_log "...done." ++ts_finalize +diff --git a/tests/ts/libsmartcols/title b/tests/ts/libsmartcols/title +new file mode 100755 +index 000000000..a9ba6a4fd +--- /dev/null ++++ b/tests/ts/libsmartcols/title +@@ -0,0 +1,28 @@ ++#!/bin/bash ++# ++# This file is part of util-linux. ++# ++# This file is free software; you can redistribute it and/or modify ++# it under the terms of the GNU General Public License as published by ++# the Free Software Foundation; either version 2 of the License, or ++# (at your option) any later version. ++# ++# This file is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++# GNU General Public License for more details. ++# ++# ++ ++TS_TOPDIR="${0%/*}/../.." ++TS_DESC="title" ++ ++. $TS_TOPDIR/functions.sh ++ts_init "$*" ++ ++TESTPROG="$TS_HELPER_LIBSMARTCOLS_TITLE" ++ts_check_test_command "$TESTPROG" ++ ++$TESTPROG --width 80 >> $TS_OUTPUT 2>&1 ++ ++ts_finalize +-- +2.14.4 + diff --git a/SOURCES/0155-lslogins-fix-password-verification.patch b/SOURCES/0155-lslogins-fix-password-verification.patch new file mode 100644 index 0000000..b508526 --- /dev/null +++ b/SOURCES/0155-lslogins-fix-password-verification.patch @@ -0,0 +1,114 @@ +From f769cb435c4db2e7f6d11e14fe87a1c81e0912fe Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Wed, 23 May 2018 12:43:26 +0200 +Subject: [PATCH 155/173] lslogins: fix password verification + +Let's follow the standard $id$salt$encrypted password format in +verification code. + +The current code is useless and for example PWD-LOCK column is always +FALSE. + +Upstream: http://github.com/karelzak/util-linux/commit/214fbec40abf0432b8e7968f05024ee76d11b3c7 +Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1581611 +Signed-off-by: Karel Zak +--- + login-utils/lslogins.c | 78 +++++++++++++++++++++++++++++++++++++++++++++++--- + 1 file changed, 74 insertions(+), 4 deletions(-) + +diff --git a/login-utils/lslogins.c b/login-utils/lslogins.c +index d7a24b1fb..041053625 100644 +--- a/login-utils/lslogins.c ++++ b/login-utils/lslogins.c +@@ -541,14 +541,84 @@ static int get_nprocs(const uid_t uid) + return nprocs; + } + ++static const char *get_pwd_method(const char *str, const char **next, unsigned int *sz) ++{ ++ const char *p = str; ++ const char *res = NULL; ++ ++ if (!p || *p++ != '$') ++ return NULL; ++ ++ if (sz) ++ *sz = 0; ++ ++ switch (*p) { ++ case '1': ++ res = "MD5"; ++ if (sz) ++ *sz = 22; ++ break; ++ case '2': ++ p++; ++ if (*p == 'a' || *p == 'y') ++ res = "Blowfish"; ++ break; ++ case '5': ++ res = "SHA-256"; ++ if (sz) ++ *sz = 43; ++ break; ++ case '6': ++ res = "SHA-512"; ++ if (sz) ++ *sz = 86; ++ break; ++ default: ++ return NULL; ++ } ++ p++; ++ ++ if (!*p || *p != '$') ++ return NULL; ++ if (next) ++ *next = ++p; ++ return res; ++} ++ ++#define is_valid_pwd_char(x) (isalnum((unsigned char) (x)) || (x) == '.' || (x) == '/') ++ + static int valid_pwd(const char *str) + { +- const char *p; ++ const char *p = str; ++ unsigned int sz = 0, n; ++ ++ /* $id$ */ ++ if (get_pwd_method(str, &p, &sz) == NULL) ++ return 0; ++ if (!*p) ++ return 0; + +- for (p = str; p && *p; p++) +- if (!isalnum((unsigned int) *p)) ++ /* salt$ */ ++ for (; p && *p; p++) { ++ if (*p == '$') { ++ p++; ++ break; ++ } ++ if (!is_valid_pwd_char(*p)) + return 0; +- return p > str ? 1 : 0; ++ } ++ if (!*p) ++ return 0; ++ ++ /* encrypted */ ++ for (n = 0; p && *p; p++, n++) { ++ if (!is_valid_pwd_char(*p)) ++ return 0; ++ } ++ ++ if (sz && n != sz) ++ return 0; ++ return 1; + } + + static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const char *username) +-- +2.14.4 + diff --git a/SOURCES/0156-umount-add-note-about-lazy.patch b/SOURCES/0156-umount-add-note-about-lazy.patch new file mode 100644 index 0000000..fc01a26 --- /dev/null +++ b/SOURCES/0156-umount-add-note-about-lazy.patch @@ -0,0 +1,53 @@ +From 0ae2aa6a43a852cb0326faade1532c02aa7617a4 Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Fri, 20 Apr 2018 09:50:04 +0200 +Subject: [PATCH 156/173] umount: add note about --lazy + +Unfortunately, it's pretty common that users on production systems use +lazy umount to fix some FS issues. The usual result is unwanted system +reboot, because -l is not the right way how to fix unreachable NFS or +mess with local FS with submounts. + +Note that after lazy umount /proc/self/mountinfo does not contain the +FS entry, but kernel still references the FS. It makes it very +difficult to debug. + +Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1566674 +Suggested-by: Steve Dickson +Signed-off-by: Karel Zak +Upstream: http://github.com/karelzak/util-linux/commit/031800ff6c66b4d62229cfd116195950a718a21c +--- + sys-utils/umount.8 | 10 +++++++++- + 1 file changed, 9 insertions(+), 1 deletion(-) + +diff --git a/sys-utils/umount.8 b/sys-utils/umount.8 +index f0a712b06..71c2155b4 100644 +--- a/sys-utils/umount.8 ++++ b/sys-utils/umount.8 +@@ -54,7 +54,8 @@ working directory there, or when a swap file on it is in use. The + offending process could even be + .B umount + itself - it opens libc, and libc in its turn may open for example locale +-files. A lazy unmount avoids this problem. ++files. A lazy unmount avoids this problem, but it may introduce another ++issues. See \fB\-\-lazy\fR description bellow. + .SH OPTIONS + .TP + \fB\-a\fR, \fB\-\-all\fR +@@ -107,6 +108,13 @@ Unmount without writing in + Lazy unmount. Detach the filesystem from the filesystem hierarchy now, + and cleanup all references to the filesystem as soon as it is not busy + anymore. (Requires kernel 2.4.11 or later.) ++ ++A system reboot would be expected in near future if you're going to use this ++option for network filesystem or local filesystem with submounts. The ++recommended use-case for \fBumount -l\fR is to prevent hangs on shutdown due to ++an unreachable network share where a normal umount will hang due to a downed ++server or a network partition. Remounts of the share will not be possible. ++ + .TP + \fB\-O\fR, \fB\-\-test\-opts\fR \fIoptions,list\fR + Indicate that the actions should only be taken on file systems with the +-- +2.14.4 + diff --git a/SOURCES/0157-losetup-add-info-about-lazy-detach-to-manpage.patch b/SOURCES/0157-losetup-add-info-about-lazy-detach-to-manpage.patch new file mode 100644 index 0000000..cd95dfe --- /dev/null +++ b/SOURCES/0157-losetup-add-info-about-lazy-detach-to-manpage.patch @@ -0,0 +1,31 @@ +From 0258872e2b45b589481e6796aa983f8749be9ada Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Thu, 10 Aug 2017 12:37:34 +0200 +Subject: [PATCH 157/173] losetup: add info about lazy detach to manpage + +Upstream: http://github.com/karelzak/util-linux/commit/60eedb0a53f14aa1169713a72325421db8e1c647 +Addresses: http://bugzilla.redhat.com/show_bug.cgi?id=1566390 +Signed-off-by: Karel Zak +--- + sys-utils/losetup.8 | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/sys-utils/losetup.8 b/sys-utils/losetup.8 +index 9a8c1d597..45c220187 100644 +--- a/sys-utils/losetup.8 ++++ b/sys-utils/losetup.8 +@@ -80,7 +80,10 @@ without --list) is deprecated. + .IP "\fB\-c, \-\-set-capacity\fP \fIloopdev\fP + force loop driver to reread size of the file associated with the specified loop device + .IP "\fB\-d, \-\-detach\fP \fIloopdev\fP..." +-detach the file or device associated with the specified loop device(s) ++detach the file or device associated with the specified loop device(s). Note ++that since Linux v3.7 kernel uses "lazy device destruction". The detach ++operation does not return EBUSY error anymore if device is actively used by ++system, but it is marked by autoclear flag and destroyed later. + .IP "\fB\-D, \-\-detach-all\fP" + detach all associated loop devices + .IP "\fB\-f, \-\-find\fP" +-- +2.14.4 + diff --git a/SOURCES/0158-setarch-Fix-ppc64le-architectures.patch b/SOURCES/0158-setarch-Fix-ppc64le-architectures.patch new file mode 100644 index 0000000..6e0b02d --- /dev/null +++ b/SOURCES/0158-setarch-Fix-ppc64le-architectures.patch @@ -0,0 +1,44 @@ +From e10a3a201b2b4dc6ff9957600c9bf67a2f9de38e Mon Sep 17 00:00:00 2001 +From: Anton Blanchard +Date: Mon, 7 Apr 2014 09:18:13 +1000 +Subject: [PATCH 158/173] setarch: Fix ppc64le architectures + +setarch currently fails on ppc64le because it tries to +use big endian architecture names. Fix it. + +Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1562125 +Upstream: http://github.com/karelzak/util-linux/commit/95bf26fd68ec7f0b2dde1f022dc79d04d1a6e620 +Upstream: http://github.com/karelzak/util-linux/commit/e60b6df54e27cdb68a75ea8a056a70f60df8f025 +Signed-off-by: Anton Blanchard +Signed-off-by: Karel Zak +--- + sys-utils/setarch.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/sys-utils/setarch.c b/sys-utils/setarch.c +index 051cbefcd..b03406dd3 100644 +--- a/sys-utils/setarch.c ++++ b/sys-utils/setarch.c +@@ -149,11 +149,19 @@ set_arch(const char *pers, unsigned long options, int list) + {PER_LINUX32, "linux32", NULL}, + {PER_LINUX, "linux64", NULL}, + #if defined(__powerpc__) || defined(__powerpc64__) ++#ifdef __BIG_ENDIAN__ + {PER_LINUX32, "ppc32", "ppc"}, + {PER_LINUX32, "ppc", "ppc"}, + {PER_LINUX, "ppc64", "ppc64"}, + {PER_LINUX, "ppc64pseries", "ppc64"}, + {PER_LINUX, "ppc64iseries", "ppc64"}, ++#else ++ {PER_LINUX32, "ppc32", "ppcle"}, ++ {PER_LINUX32, "ppc", "ppcle"}, ++ {PER_LINUX32, "ppc32le", "ppcle"}, ++ {PER_LINUX32, "ppcle", "ppcle"}, ++ {PER_LINUX, "ppc64le", "ppc64le"}, ++#endif + #endif + #if defined(__x86_64__) || defined(__i386__) || defined(__ia64__) + {PER_LINUX32, "i386", "i386"}, +-- +2.14.4 + diff --git a/SOURCES/0159-fallocate-backport-v2.32-164-g641af90dc.patch b/SOURCES/0159-fallocate-backport-v2.32-164-g641af90dc.patch new file mode 100644 index 0000000..615638d --- /dev/null +++ b/SOURCES/0159-fallocate-backport-v2.32-164-g641af90dc.patch @@ -0,0 +1,717 @@ +From 9f2a32a8fd08bb1b48f29c88bfa398fa4eb5f2a4 Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Wed, 6 Jun 2018 11:59:16 +0200 +Subject: [PATCH 159/173] fallocate: backport v2.32-164-g641af90dc + +* add --dig-holes +* add --collapse-range +* add --insert-range +* add --zero-range + +For backward compatibility with previous RHEL7 versions we keep +O_CREAT for open(). The current upstream uses O_CREAT only when +necessary. + +Addresses: http://bugzilla.redhat.com/show_bug.cgi?id=1528567 +Signed-off-by: Karel Zak +--- + sys-utils/fallocate.1 | 195 ++++++++++++++++++++++------ + sys-utils/fallocate.c | 349 +++++++++++++++++++++++++++++++++++++++----------- + 2 files changed, 428 insertions(+), 116 deletions(-) + +diff --git a/sys-utils/fallocate.1 b/sys-utils/fallocate.1 +index 376353013..d4821dcd1 100644 +--- a/sys-utils/fallocate.1 ++++ b/sys-utils/fallocate.1 +@@ -1,72 +1,185 @@ +-.\" -*- nroff -*- +-.TH FALLOCATE 1 "September 2011" "util-linux" "User Commands" ++.TH FALLOCATE 1 "April 2014" "util-linux" "User Commands" + .SH NAME +-fallocate \- preallocate space to a file ++fallocate \- preallocate or deallocate space to a file + .SH SYNOPSIS + .B fallocate +-.RB [ \-n ] +-.RB [ \-p ] ++.RB [ \-c | \-p | \-z ] + .RB [ \-o + .IR offset ] + .B \-l +-.IR length ++.I length ++.RB [ \-n ] ++.I filename ++.PP ++.B fallocate \-d ++.RB [ \-o ++.IR offset ] ++.RB [ \-l ++.IR length ] + .I filename + .PP + .B fallocate \-x + .RB [ \-o + .IR offset ] +-.RB \-l +-.IR length ++.B \-l ++.I length + .I filename + .SH DESCRIPTION + .B fallocate +-is used to preallocate blocks to a file. For filesystems which support the +-fallocate system call, this is done quickly by allocating blocks and marking +-them as uninitialized, requiring no IO to the data blocks. This is much faster +-than creating a file by filling it with zeros. +-.PP +-As of the Linux Kernel v2.6.31, the fallocate system call is supported by the +-btrfs, ext4, ocfs2, and xfs filesystems. ++is used to manipulate the allocated disk space for a file, ++either to deallocate or preallocate it. ++For filesystems which support the fallocate system call, ++preallocation is done quickly by allocating blocks and marking them as ++uninitialized, requiring no IO to the data blocks. ++This is much faster than creating a file by filling it with zeroes. + .PP + The exit code returned by + .B fallocate + is 0 on success and 1 on failure. +-.PP + .SH OPTIONS +-The \fIlength\fR and \fIoffset\fR arguments may be followed by the multiplicative +-suffixes KiB=1024, MiB=1024*1024, and so on for GiB, TiB, PiB, EiB, ZiB and YiB +-(the "iB" is optional, e.g. "K" has the same meaning as "KiB") or the suffixes +-KB=1000, MB=1000*1000, and so on for GB, TB, PB, EB, ZB and YB. +-.IP "\fB\-n, \-\-keep-size\fP" ++The ++.I length ++and ++.I offset ++arguments may be followed by the multiplicative suffixes KiB (=1024), ++MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB, and YiB (the "iB" is ++optional, e.g., "K" has the same meaning as "KiB") or the suffixes ++KB (=1000), MB (=1000*1000), and so on for GB, TB, PB, EB, ZB, and YB. ++.PP ++The options ++.BR \-\-collapse\-range ", " \-\-dig\-holes ", " \-\-punch\-hole , ++and ++.B \-\-zero\-range ++are mutually exclusive. ++.TP ++.BR \-c ", " \-\-collapse\-range ++Removes a byte range from a file, without leaving a hole. ++The byte range to be collapsed starts at ++.I offset ++and continues for ++.I length ++bytes. ++At the completion of the operation, ++the contents of the file starting at the location ++.IR offset + length ++will be appended at the location ++.IR offset , ++and the file will be ++.I length ++bytes smaller. ++The option ++.B \-\-keep\-size ++may not be specified for the collapse-range operation. ++.sp ++Available since Linux 3.15 for ext4 (only for extent-based files) and XFS. ++.TP ++.BR \-d ", " \-\-dig\-holes ++Detect and dig holes. ++This makes the file sparse in-place, without using extra disk space. ++The minimum size of the hole depends on filesystem I/O block size ++(usually 4096 bytes). ++Also, when using this option, ++.B \-\-keep\-size ++is implied. If no range is specified by ++.B \-\-offset ++and ++.BR \-\-length , ++then the entire file is analyzed for holes. ++.sp ++You can think of this option as doing a ++.RB """" "cp \-\-sparse" """" ++and then renaming the destination file to the original, ++without the need for extra disk space. ++.sp ++See \fB\-\-punch\-hole\fP for a list of supported filesystems. ++.TP ++.BR \-i ", " \-\-insert\-range ++Insert a hole of ++.I length ++bytes from ++.IR offset , ++shifting existing data. ++.TP ++.BR \-l ", " "\-\-length " \fIlength ++Specifies the length of the range, in bytes. ++.TP ++.BR \-n ", " \-\-keep\-size + Do not modify the apparent length of the file. This may effectively allocate + blocks past EOF, which can be removed with a truncate. +-.IP "\fB\-p, \-\-punch-hole\fP" +-Punch holes in the file, the range should not exceed the length of the file. +-.IP "\fB\-o, \-\-offset\fP \fIoffset\fP +-Specifies the beginning offset of the allocation, in bytes. +-.IP "\fB\-l, \-\-length\fP \fIlength\fP +-Specifies the length of the allocation, in bytes. +-.IP "\fB\-x , \-\-posix\fP +-Enable POSIX operation mode. In that mode allocation operation always completes, +-but it may take longer time when fast allocation is not supported by the underlying filesystem. +-.IP "\fB\-h, \-\-help\fP" +-Print help and exit. +-.IP "\fB-V, \-\-version" +-Print version and exit. ++.TP ++.BR \-o ", " "\-\-offset " \fIoffset ++Specifies the beginning offset of the range, in bytes. ++.TP ++.BR \-p ", " \-\-punch\-hole ++Deallocates space (i.e., creates a hole) in the byte range starting at ++.I offset ++and continuing for ++.I length ++bytes. ++Within the specified range, partial filesystem blocks are zeroed, ++and whole filesystem blocks are removed from the file. ++After a successful call, ++subsequent reads from this range will return zeroes. ++This option may not be specified at the same time as the ++.B \-\-zero\-range ++option. ++Also, when using this option, ++.B \-\-keep\-size ++is implied. ++.sp ++Supported for XFS (since Linux 2.6.38), ext4 (since Linux 3.0), ++Btrfs (since Linux 3.7) and tmpfs (since Linux 3.5). ++.TP ++.BR \-v ", " \-\-verbose ++Enable verbose mode. ++.TP ++.BR \-x ", " \-\-posix ++Enable POSIX operation mode. ++In that mode allocation operation always completes, ++but it may take longer time when fast allocation is not supported by ++the underlying filesystem. ++.TP ++.BR \-z ", " \-\-zero\-range ++Zeroes space in the byte range starting at ++.I offset ++and continuing for ++.I length ++bytes. ++Within the specified range, blocks are preallocated for the regions ++that span the holes in the file. ++After a successful call, ++subsequent reads from this range will return zeroes. ++.sp ++Zeroing is done within the filesystem preferably by converting the ++range into unwritten extents. This approach means that the specified ++range will not be physically zeroed out on the device (except for ++partial blocks at the either end of the range), and I/O is ++(otherwise) required only to update metadata. ++.sp ++Option \fB\-\-keep\-size\fP can be specified to prevent file length ++modification. ++.sp ++Available since Linux 3.14 for ext4 (only for extent-based files) and XFS. ++.TP ++.BR \-V ", " \-\-version ++Display version information and exit. ++.TP ++.BR \-h ", " \-\-help ++Display help text and exit. + .SH AUTHORS +-.UR sandeen@redhat.com ++.MT sandeen@redhat.com + Eric Sandeen +-.UE ++.ME + .br +-.UR kzak@redhat.com ++.MT kzak@redhat.com + Karel Zak +-.UE ++.ME + .SH SEE ALSO ++.BR truncate (1), + .BR fallocate (2), +-.BR posix_fallocate (3), +-.BR truncate (1) ++.BR posix_fallocate (3) + .SH AVAILABILITY + The fallocate command is part of the util-linux package and is available from +-.UR ftp://\:ftp.kernel.org\:/pub\:/linux\:/utils\:/util-linux/ ++.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/ + Linux Kernel Archive + .UE . +diff --git a/sys-utils/fallocate.c b/sys-utils/fallocate.c +index 17ae5fe69..75d89a7a9 100644 +--- a/sys-utils/fallocate.c ++++ b/sys-utils/fallocate.c +@@ -23,6 +23,7 @@ + */ + #include + #include ++#include + #include + #include + #include +@@ -31,50 +32,110 @@ + #include + #include + #include ++#include + + #ifndef HAVE_FALLOCATE + # include + #endif + +-#ifdef HAVE_LINUX_FALLOC_H +-# include /* for FALLOC_FL_* flags */ ++#if defined(HAVE_LINUX_FALLOC_H) && \ ++ (!defined(FALLOC_FL_KEEP_SIZE) || !defined(FALLOC_FL_PUNCH_HOLE) || \ ++ !defined(FALLOC_FL_COLLAPSE_RANGE) || !defined(FALLOC_FL_ZERO_RANGE) || \ ++ !defined(FALLOC_FL_INSERT_RANGE)) ++# include /* non-libc fallback for FALLOC_FL_* flags */ + #endif + ++ + #ifndef FALLOC_FL_KEEP_SIZE +-# define FALLOC_FL_KEEP_SIZE 1 ++# define FALLOC_FL_KEEP_SIZE 0x1 + #endif + + #ifndef FALLOC_FL_PUNCH_HOLE +-# define FALLOC_FL_PUNCH_HOLE 2 ++# define FALLOC_FL_PUNCH_HOLE 0x2 ++#endif ++ ++#ifndef FALLOC_FL_COLLAPSE_RANGE ++# define FALLOC_FL_COLLAPSE_RANGE 0x8 ++#endif ++ ++#ifndef FALLOC_FL_ZERO_RANGE ++# define FALLOC_FL_ZERO_RANGE 0x10 ++#endif ++ ++#ifndef FALLOC_FL_INSERT_RANGE ++# define FALLOC_FL_INSERT_RANGE 0x20 + #endif + + #include "nls.h" + #include "strutils.h" + #include "c.h" + #include "closestream.h" ++#include "xalloc.h" + #include "optutils.h" + +-static void __attribute__((__noreturn__)) usage(FILE *out) ++static int verbose; ++static char *filename; ++ ++static void __attribute__((__noreturn__)) usage(void) + { ++ FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, + _(" %s [options] \n"), program_invocation_short_name); ++ ++ fputs(USAGE_SEPARATOR, out); ++ fputs(_("Preallocate space to, or deallocate space from a file.\n"), out); ++ + fputs(USAGE_OPTIONS, out); +- fputs(_(" -n, --keep-size don't modify the length of the file\n" +- " -p, --punch-hole punch holes in the file\n" +- " -o, --offset offset of the allocation, in bytes\n" +- " -l, --length length of the allocation, in bytes\n"), out); ++ fputs(_(" -c, --collapse-range remove a range from the file\n"), out); ++ fputs(_(" -d, --dig-holes detect zeroes and replace with holes\n"), out); ++ fputs(_(" -i, --insert-range insert a hole at range, shifting existing data\n"), out); ++ fputs(_(" -l, --length length for range operations, in bytes\n"), out); ++ fputs(_(" -n, --keep-size maintain the apparent size of the file\n"), out); ++ fputs(_(" -o, --offset offset for range operations, in bytes\n"), out); ++ fputs(_(" -p, --punch-hole replace a range with a hole (implies -n)\n"), out); ++ fputs(_(" -z, --zero-range zero and ensure allocation of a range\n"), out); + #ifdef HAVE_POSIX_FALLOCATE +- fputs(_(" -x, --posix use posix_fallocate(3) instead of fallocate(2)\n"), out); ++ fputs(_(" -x, --posix use posix_fallocate(3) instead of fallocate(2)\n"), out); + #endif ++ fputs(_(" -v, --verbose verbose mode\n"), out); ++ + fputs(USAGE_SEPARATOR, out); +- fputs(USAGE_HELP, out); +- fputs(USAGE_VERSION, out); +- fprintf(out, USAGE_MAN_TAIL("fallocate(1)")); ++ printf(USAGE_HELP_OPTIONS(22)); ++ ++ printf(USAGE_MAN_TAIL("fallocate(1)")); + +- exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); ++ exit(EXIT_SUCCESS); + } + ++static loff_t cvtnum(char *s) ++{ ++ uintmax_t x; ++ ++ if (strtosize(s, &x)) ++ return -1LL; ++ ++ return x; ++} ++ ++static void xfallocate(int fd, int mode, off_t offset, off_t length) ++{ ++ int error; ++#ifdef HAVE_FALLOCATE ++ error = fallocate(fd, mode, offset, length); ++#else ++ error = syscall(SYS_fallocate, fd, mode, offset, length); ++#endif ++ /* ++ * EOPNOTSUPP: The FALLOC_FL_KEEP_SIZE is unsupported ++ * ENOSYS: The filesystem does not support sys_fallocate ++ */ ++ if (error < 0) { ++ if ((mode & FALLOC_FL_KEEP_SIZE) && errno == EOPNOTSUPP) ++ errx(EXIT_FAILURE, _("fallocate failed: keep size mode is unsupported")); ++ err(EXIT_FAILURE, _("fallocate failed")); ++ } ++} + + #ifdef HAVE_POSIX_FALLOCATE + static void xposix_fallocate(int fd, off_t offset, off_t length) +@@ -86,41 +147,163 @@ static void xposix_fallocate(int fd, off_t offset, off_t length) + } + #endif + ++/* The real buffer size has to be bufsize + sizeof(uintptr_t) */ ++static int is_nul(void *buf, size_t bufsize) ++{ ++ typedef uintptr_t word; ++ void const *vp; ++ char const *cbuf = buf, *cp; ++ word const *wp = buf; + +-static loff_t cvtnum(char *s) ++ /* set sentinel */ ++ memset((char *) buf + bufsize, '\1', sizeof(word)); ++ ++ /* Find first nonzero *word*, or the word with the sentinel. */ ++ while (*wp++ == 0) ++ continue; ++ ++ /* Find the first nonzero *byte*, or the sentinel. */ ++ vp = wp - 1; ++ cp = vp; ++ ++ while (*cp++ == 0) ++ continue; ++ ++ return cbuf + bufsize < cp; ++} ++ ++static void dig_holes(int fd, off_t file_off, off_t len) + { +- uintmax_t x; ++ off_t file_end = len ? file_off + len : 0; ++ off_t hole_start = 0, hole_sz = 0; ++ uintmax_t ct = 0; ++ size_t bufsz; ++ char *buf; ++ struct stat st; ++#if defined(POSIX_FADV_SEQUENTIAL) && defined(HAVE_POSIX_FADVISE) ++ off_t cache_start = file_off; ++ /* ++ * We don't want to call POSIX_FADV_DONTNEED to discard cached ++ * data in PAGE_SIZE steps. IMHO it's overkill (too many syscalls). ++ * ++ * Let's assume that 1MiB (on system with 4K page size) is just ++ * a good compromise. ++ * -- kzak Feb-2014 ++ */ ++ const size_t cachesz = getpagesize() * 256; ++#endif + +- if (strtosize(s, &x)) +- return -1LL; ++ if (fstat(fd, &st) != 0) ++ err(EXIT_FAILURE, _("stat of %s failed"), filename); + +- return x; ++ bufsz = st.st_blksize; ++ ++ if (lseek(fd, file_off, SEEK_SET) < 0) ++ err(EXIT_FAILURE, _("seek on %s failed"), filename); ++ ++ /* buffer + extra space for is_nul() sentinel */ ++ buf = xmalloc(bufsz + sizeof(uintptr_t)); ++ while (file_end == 0 || file_off < file_end) { ++ /* ++ * Detect data area (skip holes) ++ */ ++ off_t end, off; ++ ++ off = lseek(fd, file_off, SEEK_DATA); ++ if ((off == -1 && errno == ENXIO) || ++ (file_end && off >= file_end)) ++ break; ++ ++ end = lseek(fd, off, SEEK_HOLE); ++ if (file_end && end > file_end) ++ end = file_end; ++ ++#if defined(POSIX_FADV_SEQUENTIAL) && defined(HAVE_POSIX_FADVISE) ++ posix_fadvise(fd, off, end, POSIX_FADV_SEQUENTIAL); ++#endif ++ /* ++ * Dig holes in the area ++ */ ++ while (off < end) { ++ ssize_t rsz = pread(fd, buf, bufsz, off); ++ if (rsz < 0 && errno) ++ err(EXIT_FAILURE, _("%s: read failed"), filename); ++ if (end && rsz > 0 && off > end - rsz) ++ rsz = end - off; ++ if (rsz <= 0) ++ break; ++ ++ if (is_nul(buf, rsz)) { ++ if (!hole_sz) /* new hole detected */ ++ hole_start = off; ++ hole_sz += rsz; ++ } else if (hole_sz) { ++ xfallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, ++ hole_start, hole_sz); ++ ct += hole_sz; ++ hole_sz = hole_start = 0; ++ } ++ ++#if defined(POSIX_FADV_DONTNEED) && defined(HAVE_POSIX_FADVISE) ++ /* discard cached data */ ++ if (off - cache_start > (off_t) cachesz) { ++ size_t clen = off - cache_start; ++ ++ clen = (clen / cachesz) * cachesz; ++ posix_fadvise(fd, cache_start, clen, POSIX_FADV_DONTNEED); ++ cache_start = cache_start + clen; ++ } ++#endif ++ off += rsz; ++ } ++ if (hole_sz) { ++ xfallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, ++ hole_start, hole_sz); ++ ct += hole_sz; ++ } ++ file_off = off; ++ } ++ ++ free(buf); ++ ++ if (verbose) { ++ char *str = size_to_human_string(SIZE_SUFFIX_3LETTER | SIZE_SUFFIX_SPACE, ct); ++ fprintf(stdout, _("%s: %s (%ju bytes) converted to sparse holes.\n"), ++ filename, str, ct); ++ free(str); ++ } + } + + int main(int argc, char **argv) + { +- char *fname; + int c; +- int error = 0; + int fd; + int mode = 0; +- int posix = 0; ++ int dig = 0; ++ int posix = 0; + loff_t length = -2LL; + loff_t offset = 0; + + static const struct option longopts[] = { +- { "help", 0, 0, 'h' }, +- { "version", 0, 0, 'V' }, +- { "keep-size", 0, 0, 'n' }, +- { "punch-hole", 0, 0, 'p' }, +- { "offset", 1, 0, 'o' }, +- { "length", 1, 0, 'l' }, +- { "posix", 0, 0, 'x' }, +- { NULL, 0, 0, 0 } ++ { "help", no_argument, NULL, 'h' }, ++ { "version", no_argument, NULL, 'V' }, ++ { "keep-size", no_argument, NULL, 'n' }, ++ { "punch-hole", no_argument, NULL, 'p' }, ++ { "collapse-range", no_argument, NULL, 'c' }, ++ { "dig-holes", no_argument, NULL, 'd' }, ++ { "insert-range", no_argument, NULL, 'i' }, ++ { "zero-range", no_argument, NULL, 'z' }, ++ { "offset", required_argument, NULL, 'o' }, ++ { "length", required_argument, NULL, 'l' }, ++ { "posix", no_argument, NULL, 'x' }, ++ { "verbose", no_argument, NULL, 'v' }, ++ { NULL, 0, NULL, 0 } + }; + +- static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ +- { 'x', 'n', 'p' }, ++ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ ++ { 'c', 'd', 'p', 'z' }, ++ { 'c', 'n' }, ++ { 'x', 'c', 'd', 'i', 'n', 'p', 'z'}, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; +@@ -130,29 +313,39 @@ int main(int argc, char **argv) + textdomain(PACKAGE); + atexit(close_stdout); + +- while ((c = getopt_long(argc, argv, "hVnpl:o:x", longopts, NULL)) != -1) { ++ while ((c = getopt_long(argc, argv, "hvVncpdizxl:o:", longopts, NULL)) ++ != -1) { + + err_exclusive_options(c, longopts, excl, excl_st); + + switch(c) { + case 'h': +- usage(stdout); ++ usage(); + break; +- case 'V': +- printf(UTIL_LINUX_VERSION); +- return EXIT_SUCCESS; +- case 'p': +- mode |= FALLOC_FL_PUNCH_HOLE; +- /* fall through */ +- case 'n': +- mode |= FALLOC_FL_KEEP_SIZE; ++ case 'c': ++ mode |= FALLOC_FL_COLLAPSE_RANGE; ++ break; ++ case 'd': ++ dig = 1; ++ break; ++ case 'i': ++ mode |= FALLOC_FL_INSERT_RANGE; + break; + case 'l': + length = cvtnum(optarg); + break; ++ case 'n': ++ mode |= FALLOC_FL_KEEP_SIZE; ++ break; + case 'o': + offset = cvtnum(optarg); + break; ++ case 'p': ++ mode |= FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE; ++ break; ++ case 'z': ++ mode |= FALLOC_FL_ZERO_RANGE; ++ break; + case 'x': + #ifdef HAVE_POSIX_FALLOCATE + posix = 1; +@@ -160,53 +353,59 @@ int main(int argc, char **argv) + #else + errx(EXIT_FAILURE, _("posix_fallocate support is not compiled")) + #endif +- default: +- usage(stderr); ++ case 'v': ++ verbose++; + break; ++ case 'V': ++ printf(UTIL_LINUX_VERSION); ++ return EXIT_SUCCESS; ++ default: ++ errtryhelp(EXIT_FAILURE); + } + } + +- if (length == -2LL) +- errx(EXIT_FAILURE, _("no length argument specified")); +- if (length <= 0) +- errx(EXIT_FAILURE, _("invalid length value specified")); +- if (offset < 0) +- errx(EXIT_FAILURE, _("invalid offset value specified")); + if (optind == argc) +- errx(EXIT_FAILURE, _("no filename specified.")); ++ errx(EXIT_FAILURE, _("no filename specified")); ++ ++ filename = argv[optind++]; + +- fname = argv[optind++]; ++ if (optind != argc) ++ errx(EXIT_FAILURE, _("unexpected number of arguments")); + +- if (optind != argc) { +- warnx(_("unexpected number of arguments")); +- usage(stderr); ++ if (dig) { ++ /* for --dig-holes the default is analyze all file */ ++ if (length == -2LL) ++ length = 0; ++ if (length < 0) ++ errx(EXIT_FAILURE, _("invalid length value specified")); ++ } else { ++ /* it's safer to require the range specification (--length --offset) */ ++ if (length == -2LL) ++ errx(EXIT_FAILURE, _("no length argument specified")); ++ if (length <= 0) ++ errx(EXIT_FAILURE, _("invalid length value specified")); + } ++ if (offset < 0) ++ errx(EXIT_FAILURE, _("invalid offset value specified")); + +- fd = open(fname, O_WRONLY|O_CREAT, 0644); ++ /* O_CREAT makes sense only for the default fallocate(2) behavior ++ * when mode is no specified and new space is allocated ++ * ++ * RHEL7.6: for backward compatibility in RHEL7 we keep O_CREAT there. ++ */ ++ fd = open(filename, O_RDWR | O_CREAT, ++ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if (fd < 0) +- err(EXIT_FAILURE, _("cannot open %s"), fname); ++ err(EXIT_FAILURE, _("cannot open %s"), filename); + ++ if (dig) ++ dig_holes(fd, offset, length); + #ifdef HAVE_POSIX_FALLOCATE +- if (posix) ++ else if (posix) + xposix_fallocate(fd, offset, length); +- else + #endif +- +-#ifdef HAVE_FALLOCATE +- error = fallocate(fd, mode, offset, length); +-#else +- error = syscall(SYS_fallocate, fd, mode, offset, length); +-#endif +- /* +- * EOPNOTSUPP: The FALLOC_FL_KEEP_SIZE is unsupported +- * ENOSYS: The filesystem does not support sys_fallocate +- */ +- if (error < 0) { +- if ((mode & FALLOC_FL_KEEP_SIZE) && errno == EOPNOTSUPP) +- errx(EXIT_FAILURE, +- _("keep size mode (-n option) unsupported")); +- err(EXIT_FAILURE, _("%s: fallocate failed"), fname); +- } ++ else ++ xfallocate(fd, mode, offset, length); + + close(fd); + return EXIT_SUCCESS; +-- +2.14.4 + diff --git a/SOURCES/0160-libmount-fix-mnt_table_is_fs_mounted-for-rbind.patch b/SOURCES/0160-libmount-fix-mnt_table_is_fs_mounted-for-rbind.patch new file mode 100644 index 0000000..8a66eac --- /dev/null +++ b/SOURCES/0160-libmount-fix-mnt_table_is_fs_mounted-for-rbind.patch @@ -0,0 +1,54 @@ +From 7f5f61824b3b7ffa81d34481aec5a7ecca213a5a Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Wed, 3 Jan 2018 13:59:59 +0100 +Subject: [PATCH 160/173] libmount: fix mnt_table_is_fs_mounted() for rbind + +There is no difference between "bind" and "rbind" if we want to FS +root to search for the FS in mountinfo file. + +fstab: + + /dev/sdc1 /mnt/foo xfs defaults 0 0 + /mnt/foo /mnt/test none rw,rbind 0 0 + +use -a more than once: + + mount -a + mount -a + +/proc/mounts (the current result): + + /dev/sdc1 /mnt/foo xfs rw,relatime,attr2,inode64,noquota 0 0 + /dev/sdc1 /mnt/test xfs rw,relatime,attr2,inode64,noquota 0 0 + /dev/sdc1 /mnt/test xfs rw,relatime,attr2,inode64,noquota 0 0 + /dev/sdc1 /mnt/foo xfs rw,relatime,attr2,inode64,noquota 0 0 + +expected (fixed version) result: + + /dev/sdc1 /mnt/foo xfs rw,relatime,attr2,inode64,noquota 0 0 + /dev/sdc1 /mnt/test xfs rw,relatime,attr2,inode64,noquota 0 0 + +Upstream: http://github.com/karelzak/util-linux/commit/b5cc232362a60b2a7738ee250609202bd6195e49 +Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1528959 +Signed-off-by: Karel Zak +--- + libmount/src/tab.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/libmount/src/tab.c b/libmount/src/tab.c +index 5628da6e5..dfa1da822 100644 +--- a/libmount/src/tab.c ++++ b/libmount/src/tab.c +@@ -1099,7 +1099,8 @@ int mnt_table_is_fs_mounted(struct libmnt_table *tb, struct libmnt_fs *fstab_fs) + struct libmnt_fs *rootfs; + int flags = 0; + +- if (mnt_fs_get_option(fstab_fs, "bind", NULL, NULL) == 0) ++ if (mnt_fs_get_option(fstab_fs, "bind", NULL, NULL) == 0 || ++ mnt_fs_get_option(fstab_fs, "rbind", NULL, NULL) == 0) + flags = MS_BIND; + + rootfs = mnt_table_get_fs_root(tb, fstab_fs, flags, &root); +-- +2.14.4 + diff --git a/SOURCES/0161-mount-add-ext4-to-some-places-in-man-page.patch b/SOURCES/0161-mount-add-ext4-to-some-places-in-man-page.patch new file mode 100644 index 0000000..00d2d5d --- /dev/null +++ b/SOURCES/0161-mount-add-ext4-to-some-places-in-man-page.patch @@ -0,0 +1,54 @@ +From 7492cf62b7334792df931469ca783878b868d0d0 Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Wed, 6 Jun 2018 12:10:42 +0200 +Subject: [PATCH 161/173] mount: add ext4 to some places in man page + +Addresses: http://bugzilla.redhat.com/show_bug.cgi?id=1538721 +Signed-off-by: Karel Zak +--- + sys-utils/mount.8 | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/sys-utils/mount.8 b/sys-utils/mount.8 +index 49cb2818f..35d782f0e 100644 +--- a/sys-utils/mount.8 ++++ b/sys-utils/mount.8 +@@ -874,7 +874,7 @@ for example tune2fs -l output for extN filesystems). + The following options apply to any filesystem that is being + mounted (but not every filesystem actually honors them - e.g., the + .B sync +-option today has effect only for ext2, ext3, fat, vfat and ufs): ++option today has effect only for ext2, ext3, ext4, fat, vfat and ufs): + + .TP + .B async +@@ -909,7 +909,7 @@ The + .BR context= + option is useful when mounting filesystems that do not support + extended attributes, such as a floppy or hard disk formatted with VFAT, or +-systems that are not normally running under SELinux, such as an ext3 formatted ++systems that are not normally running under SELinux, such as an ext3 or ext4 formatted + disk from a non-SELinux workstation. You can also use + .BR context= + on filesystems you do not trust, such as a floppy. It also helps in compatibility with +@@ -2833,7 +2833,7 @@ not specified or the filesystem is known for libblkid, for example: + .sp + .B "mount /tmp/disk.img /mnt" + .sp +-.B "mount -t ext3 /tmp/disk.img /mnt" ++.B "mount -t ext4 /tmp/disk.img /mnt" + .sp + .RE + This type of mount knows about four options, namely +@@ -2948,7 +2948,7 @@ It is possible for a corrupted filesystem to cause a crash. + .PP + Some Linux filesystems don't support + .B "\-o sync and \-o dirsync" +-(the ext2, ext3, fat and vfat filesystems ++(the ext2, ext3, ext4, fat and vfat filesystems + .I do + support synchronous updates (a la BSD) when mounted with the + .B sync +-- +2.14.4 + diff --git a/SOURCES/0162-agetty-keep-c_iflags-unmodified-on-autologin.patch b/SOURCES/0162-agetty-keep-c_iflags-unmodified-on-autologin.patch new file mode 100644 index 0000000..a14f4c4 --- /dev/null +++ b/SOURCES/0162-agetty-keep-c_iflags-unmodified-on-autologin.patch @@ -0,0 +1,72 @@ +From 7974045302fe53629e70501f0180f30cbaa25b1e Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Wed, 6 Jun 2018 15:57:24 +0200 +Subject: [PATCH 162/173] agetty: keep c_iflags unmodified on --autologin + +agetty sets c_iflags according to interaction with serial line in +get_logname(). For --autologin it does not read from the line, so we +have no clue how to set the flags. + +The current behavior is to zeroize the flags. Unfortunately, it seems +like bad idea, because the line may be already properly initialized by +kernel (or systemd, etc.). + +The new behavior is not touch the flags on --autologin. + +Upstream: http://github.com/karelzak/util-linux/commit/2c4d86abdadab19be76abecb156da7f7dc284d81 +Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1252764 +Signed-off-by: Karel Zak +--- + term-utils/agetty.8 | 4 ++++ + term-utils/agetty.c | 17 +++++++++++++---- + 2 files changed, 17 insertions(+), 4 deletions(-) + +diff --git a/term-utils/agetty.8 b/term-utils/agetty.8 +index a42cdf158..fe4bfd427 100644 +--- a/term-utils/agetty.8 ++++ b/term-utils/agetty.8 +@@ -94,6 +94,10 @@ password. The \-f \fIusername\fP option is added to the \fB/bin/login\fP + command line by default. The \-\-login-options option changes this default + behaviour and then only \\u is replaced by the \fIusername\fP and no other + option is added to the login command line. ++ ++Note that \fB\-\-autologin\fP may affect the way how agetty initializes the ++serial line, because on auto-login agetty does not read from the line and it ++has no opportunity optimize the line setting. + .TP + \-c, \-\-noreset + Don't reset terminal cflags (control modes). See \fItermios(3)\fP for more +diff --git a/term-utils/agetty.c b/term-utils/agetty.c +index b626cdbeb..948d77246 100644 +--- a/term-utils/agetty.c ++++ b/term-utils/agetty.c +@@ -1100,13 +1100,22 @@ static void termio_init(struct options *op, struct termios *tp) + /* Flush input and output queues, important for modems! */ + tcflush(STDIN_FILENO, TCIOFLUSH); + ++ /* The defaul is set c_iflag in termio_final() according to chardata. ++ * Unfortunately, the chardata are not set according to the serial line ++ * if --autolog is enabled. In this case we do not read from the line ++ * at all. The best what we can do in this case is to keep c_iflag ++ * unmodified for --autolog. ++ */ ++ if (!op->autolog) { + #ifdef IUTF8 +- tp->c_iflag = tp->c_iflag & IUTF8; +- if (tp->c_iflag & IUTF8) +- op->flags |= F_UTF8; ++ tp->c_iflag = tp->c_iflag & IUTF8; ++ if (tp->c_iflag & IUTF8) ++ op->flags |= F_UTF8; + #else +- tp->c_iflag = 0; ++ tp->c_iflag = 0; + #endif ++ } ++ + tp->c_lflag = 0; + tp->c_oflag &= OPOST | ONLCR; + +-- +2.14.4 + diff --git a/SOURCES/0163-sulogin-don-t-use-strcpy-enlarge-pwd-line-buffer.patch b/SOURCES/0163-sulogin-don-t-use-strcpy-enlarge-pwd-line-buffer.patch new file mode 100644 index 0000000..d3b7610 --- /dev/null +++ b/SOURCES/0163-sulogin-don-t-use-strcpy-enlarge-pwd-line-buffer.patch @@ -0,0 +1,73 @@ +From 94f380e223e7496804dcd68e204fba0a15df8bd7 Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Mon, 25 May 2015 15:24:13 +0200 +Subject: [PATCH 163/173] sulogin: don't use strcpy(), enlarge pwd line buffer + +* according to "man getpwnam" 16384 bytes is enough to store one + passwd entry (let's use 2*BUFSIZE to avoid magic numbers in code) + +* don't use strcpy() to set empty password + +Upstream: http://github.com/karelzak/util-linux/commit/d681e0956cdca1a016346424939fe1b9c6a0a549 +Addresses: http://bugzilla.redhat.com/show_bug.cgi?id=1561200 +Signed-off-by: Karel Zak +--- + login-utils/sulogin.c | 14 +++++++------- + 1 file changed, 7 insertions(+), 7 deletions(-) + +diff --git a/login-utils/sulogin.c b/login-utils/sulogin.c +index bbd67b3ee..6d03bc5ae 100644 +--- a/login-utils/sulogin.c ++++ b/login-utils/sulogin.c +@@ -373,8 +373,8 @@ static struct passwd *getrootpwent(int try_manually) + struct passwd *pw; + struct spwd *spw; + FILE *fp; +- static char line[256]; +- static char sline[256]; ++ static char line[2 * BUFSIZ]; ++ static char sline[2 * BUFSIZ]; + char *p; + + /* +@@ -410,7 +410,7 @@ static struct passwd *getrootpwent(int try_manually) + /* + * Find root in the password file. + */ +- while ((p = fgets(line, 256, fp)) != NULL) { ++ while ((p = fgets(line, sizeof(line), fp)) != NULL) { + if (strncmp(line, "root:", 5) != 0) + continue; + p += 5; +@@ -439,12 +439,12 @@ static struct passwd *getrootpwent(int try_manually) + /* + * The password is invalid. If there is a shadow password, try it. + */ +- strcpy(pwd.pw_passwd, ""); ++ *pwd.pw_passwd = '\0'; + if ((fp = fopen(_PATH_SHADOW_PASSWD, "r")) == NULL) { + warn(_("cannot open %s"), _PATH_PASSWD); + return &pwd; + } +- while ((p = fgets(sline, 256, fp)) != NULL) { ++ while ((p = fgets(sline, sizeof(sline), fp)) != NULL) { + if (strncmp(sline, "root:", 5) != 0) + continue; + p += 5; +@@ -458,11 +458,11 @@ static struct passwd *getrootpwent(int try_manually) + */ + if (p == NULL) { + warnx(_("%s: no entry for root"), _PATH_SHADOW_PASSWD); +- strcpy(pwd.pw_passwd, ""); ++ *pwd.pw_passwd = '\0'; + } + if (!valid(pwd.pw_passwd)) { + warnx(_("%s: root password garbled"), _PATH_SHADOW_PASSWD); +- strcpy(pwd.pw_passwd, ""); ++ *pwd.pw_passwd = '\0'; + } + return &pwd; + } +-- +2.14.4 + diff --git a/SOURCES/0164-sulogin-improve-support-for-locked-root-account.patch b/SOURCES/0164-sulogin-improve-support-for-locked-root-account.patch new file mode 100644 index 0000000..9aa2443 --- /dev/null +++ b/SOURCES/0164-sulogin-improve-support-for-locked-root-account.patch @@ -0,0 +1,175 @@ +From a08c1a6aadaa27e4070fe58242d55464d6c7a3e7 Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Mon, 25 May 2015 15:30:52 +0200 +Subject: [PATCH 164/173] sulogin: improve support for locked root account + +Some installations and distributions don't use a root account password +for security reasons and use sudo instead. In that case, asking for the +password makes no sense, and it is not even considered as valid as it's just +"*" or "!". + +In these cases --force is required to just start a root shell and no +ask for password. + +I don't think it's a good idea to automatically start root shell when +locked account is detected. It's possible that the machine is on +public place and for example Ubuntu uses root account disabled by +default (and also Fedora when installed by yum/dnf without anaconda). + +The --force option forces admins to think about it... + +The distro maintainers can also use --force in their initscripts or +systemd emergency.service if they believe that promiscuous setting is +the right thing for the distro. + +Addresses: https://bugs.debian.org/326678 +Addresses: http://bugzilla.redhat.com/show_bug.cgi?id=1561200 +Upstream: http://github.com/karelzak/util-linux/commit/7ff1162e67164cb4ece19dd809c26272461aa254 +Signed-off-by: Karel Zak +--- + login-utils/sulogin.8 | 13 +++++++++---- + login-utils/sulogin.c | 48 ++++++++++++++++++++++++++++++++++-------------- + 2 files changed, 43 insertions(+), 18 deletions(-) + +diff --git a/login-utils/sulogin.8 b/login-utils/sulogin.8 +index b9dec165c..702487985 100644 +--- a/login-utils/sulogin.8 ++++ b/login-utils/sulogin.8 +@@ -35,8 +35,10 @@ Give root password for system maintenance + .br + (or type Control\-D for normal startup): + .PP +-.I sulogin +-will be connected to the current terminal, or to the optional tty device that ++If the root account is locked and --force is specified, no password is required. ++.PP ++.B sulogin ++will be connected to the current terminal, or to the optional \fItty\fR device that + can be specified on the command line (typically + .BR /dev/console ). + .PP +@@ -50,8 +52,11 @@ from the system fails, manually examine + .I /etc/passwd + and + .I /etc/shadow +-to get the password. If they are damaged or nonexistent, sulogin will start +-a root shell without asking for a password. ++to get the password. If these files are damaged or nonexistent, or when ++root account is locked by '!' or '*' at the begin of the password then ++.B sulogin ++will \fBstart a root shell without asking for a password\fP. ++.PP + .IP + Only use the + .B \-e +diff --git a/login-utils/sulogin.c b/login-utils/sulogin.c +index 6d03bc5ae..a17b91d71 100644 +--- a/login-utils/sulogin.c ++++ b/login-utils/sulogin.c +@@ -81,6 +81,13 @@ static volatile sig_atomic_t sigchild; + # define IUCLC 0 + #endif + ++static int locked_account_password(const char *passwd) ++{ ++ if (passwd && (*passwd == '*' || *passwd == '!')) ++ return 1; ++ return 0; ++} ++ + /* + * Fix the tty modes and set reasonable defaults. + */ +@@ -423,7 +430,6 @@ static struct passwd *getrootpwent(int try_manually) + p = line; + break; + } +- + fclose(fp); + + /* +@@ -460,7 +466,8 @@ static struct passwd *getrootpwent(int try_manually) + warnx(_("%s: no entry for root"), _PATH_SHADOW_PASSWD); + *pwd.pw_passwd = '\0'; + } +- if (!valid(pwd.pw_passwd)) { ++ /* locked accont passwords are valid too */ ++ if (!locked_account_password(pwd.pw_passwd) && !valid(pwd.pw_passwd)) { + warnx(_("%s: root password garbled"), _PATH_SHADOW_PASSWD); + *pwd.pw_passwd = '\0'; + } +@@ -470,7 +477,7 @@ static struct passwd *getrootpwent(int try_manually) + /* + * Ask by prompt for the password. + */ +-static void doprompt(const char *crypted, struct console *con) ++static void doprompt(const char *crypted, struct console *con, int deny) + { + struct termios tty; + +@@ -487,18 +494,25 @@ static void doprompt(const char *crypted, struct console *con) + if ((con->file = fdopen(con->fd, "r+")) == (FILE*)0) + goto err; + } ++ ++ if (deny) ++ fprintf(con->file, _("\nCannot open access to console, the root account is locked.\n" ++ "See sulogin(8) man page for more details.\n\n" ++ "Press Enter to continue.\n")); ++ else { + #if defined(USE_ONELINE) +- if (crypted[0]) +- fprintf(con->file, _("Give root password for login: ")); +- else +- fprintf(con->file, _("Press enter for login: ")); ++ if (crypted[0] && !locked_account_password(crypted)) ++ fprintf(con->file, _("Give root password for login: ")); ++ else ++ fprintf(con->file, _("Press Enter for login: ")); + #else +- if (crypted[0]) +- fprintf(con->file, _("Give root password for maintenance\n")); +- else +- fprintf(con->file, _("Press enter for maintenance")); +- fprintf(con->file, _("(or type Control-D to continue): ")); ++ if (crypted[0] && !locked_account_password(crypted)) ++ fprintf(con->file, _("Give root password for maintenance\n")); ++ else ++ fprintf(con->file, _("Press Enter for maintenance\n")); ++ fprintf(con->file, _("(or press Control-D to continue): ")); + #endif ++ } + fflush(con->file); + err: + if (con->flags & CON_SERIAL) +@@ -914,6 +928,7 @@ int main(int argc, char **argv) + goto nofork; + } + ++ + mask_signal(SIGCHLD, chld_handler, &saved_sigchld); + do { + con = list_entry(ptr, struct console, entry); +@@ -930,12 +945,17 @@ int main(int argc, char **argv) + const char *passwd = pwd->pw_passwd; + const char *answer; + int failed = 0, doshell = 0; ++ int deny = !opt_e && locked_account_password(pwd->pw_passwd); ++ ++ doprompt(passwd, con, deny); + +- doprompt(passwd, con); + if ((answer = getpasswd(con)) == NULL) + break; ++ if (deny) ++ exit(EXIT_FAILURE); + +- if (passwd[0] == '\0') ++ /* no password or locked account */ ++ if (!passwd[0] || locked_account_password(passwd)) + doshell++; + else { + const char *cryptbuf; +-- +2.14.4 + diff --git a/SOURCES/0165-sulogin-Always-make-echo-work-after-performing-getpa.patch b/SOURCES/0165-sulogin-Always-make-echo-work-after-performing-getpa.patch new file mode 100644 index 0000000..abc0cb9 --- /dev/null +++ b/SOURCES/0165-sulogin-Always-make-echo-work-after-performing-getpa.patch @@ -0,0 +1,34 @@ +From 6bb8e6d4c38f260109852ef8b43876ea1aff3810 Mon Sep 17 00:00:00 2001 +From: Werner Fink +Date: Tue, 22 Mar 2016 10:38:59 +0100 +Subject: [PATCH 165/173] sulogin: Always make echo work after performing + getpasswd even if root account is locked + +If the root account is locked and no password was provided then the terminal +line is not set back to do echo of the input. This correct a small overlook +in commit 7ff1162e67164cb4ece19dd809c26272461aa254 + +Upstream: http://github.com/karelzak/util-linux/commit/6988998b66b4b95c494d60599f8e3de2ffbaeece +Addresses: http://bugzilla.redhat.com/show_bug.cgi?id=1561200 +Signed-off-by: Werner Fink +--- + login-utils/sulogin.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/login-utils/sulogin.c b/login-utils/sulogin.c +index a17b91d71..a6918282e 100644 +--- a/login-utils/sulogin.c ++++ b/login-utils/sulogin.c +@@ -676,8 +676,7 @@ quit: + alarm(0); + if (tc) + tcsetattr(fd, TCSAFLUSH, &con->tio); +- if (ret && *ret != '\0') +- tcfinal(con); ++ tcfinal(con); + printf("\r\n"); + out: + return ret; +-- +2.14.4 + diff --git a/SOURCES/0166-sulogin-make-getpasswd-.-return-NULL-on-D.patch b/SOURCES/0166-sulogin-make-getpasswd-.-return-NULL-on-D.patch new file mode 100644 index 0000000..fb8e29d --- /dev/null +++ b/SOURCES/0166-sulogin-make-getpasswd-.-return-NULL-on-D.patch @@ -0,0 +1,35 @@ +From c19feb058ff5883ab7255241d13732ab66bf44f0 Mon Sep 17 00:00:00 2001 +From: Andreas Henriksson +Date: Mon, 28 Nov 2016 17:24:49 +0100 +Subject: [PATCH 166/173] sulogin: make getpasswd(...) return NULL on ^D +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This makes the caller bail out early instead of evaluating the +input as a password. + +Reported-by: Bjørn Mork +Addresses: http://bugs.debian.org/846112 +Upstream: http://github.com/karelzak/util-linux/commit/60dea9d187caa700e42f37c7955116f71be912d5 +Addresses: http://bugzilla.redhat.com/show_bug.cgi?id=1561200 +Signed-off-by: Andreas Henriksson +--- + login-utils/sulogin.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/login-utils/sulogin.c b/login-utils/sulogin.c +index a6918282e..7ec349953 100644 +--- a/login-utils/sulogin.c ++++ b/login-utils/sulogin.c +@@ -661,6 +661,7 @@ static char *getpasswd(struct console *con) + ptr--; + break; + case CEOF: ++ ret = NULL; + goto quit; + default: + if ((size_t)(ptr - &pass[0]) >= (sizeof(pass) -1 )) { +-- +2.14.4 + diff --git a/SOURCES/0167-sulogin-bail-out-from-getpasswd-.-on-timeout.patch b/SOURCES/0167-sulogin-bail-out-from-getpasswd-.-on-timeout.patch new file mode 100644 index 0000000..e0f72a7 --- /dev/null +++ b/SOURCES/0167-sulogin-bail-out-from-getpasswd-.-on-timeout.patch @@ -0,0 +1,38 @@ +From 971b2ecede4d2040e879d9cfb56332a2e210bed8 Mon Sep 17 00:00:00 2001 +From: Andreas Henriksson +Date: Mon, 28 Nov 2016 17:24:50 +0100 +Subject: [PATCH 167/173] sulogin: bail out from getpasswd(...) on timeout +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +If timeout happens while waiting in prompt, bail out instead +of retrying. + +Reported-by: Bjørn Mork +Addresses: http://bugs.debian.org/846107 +Upstream: http://github.com/karelzak/util-linux/commit/1c4b2d43926e2a7032310cd18b411d8d872cb4ed +Addresses: http://bugzilla.redhat.com/show_bug.cgi?id=1561200 +Signed-off-by: Andreas Henriksson +--- + login-utils/sulogin.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/login-utils/sulogin.c b/login-utils/sulogin.c +index 7ec349953..dd73a1c50 100644 +--- a/login-utils/sulogin.c ++++ b/login-utils/sulogin.c +@@ -611,6 +611,10 @@ static char *getpasswd(struct console *con) + while (cp->eol == '\0') { + if (read(fd, &c, 1) < 1) { + if (errno == EINTR || errno == EAGAIN) { ++ if (alarm_rised) { ++ ret = NULL; ++ goto quit; ++ } + usleep(1000); + continue; + } +-- +2.14.4 + diff --git a/SOURCES/0168-losetup-keep-f-and-devname-mutually-exclusive.patch b/SOURCES/0168-losetup-keep-f-and-devname-mutually-exclusive.patch new file mode 100644 index 0000000..28278f3 --- /dev/null +++ b/SOURCES/0168-losetup-keep-f-and-devname-mutually-exclusive.patch @@ -0,0 +1,40 @@ +From 14a4f41a94cc4bd227940c4bd976e39d9f13e8c5 Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Thu, 7 Jun 2018 12:05:08 +0200 +Subject: [PATCH 168/173] losetup: keep -f and mutually exclusive + +losetup tries to blindly use specified device as well as search for +the first free device, the result is: + + # losetup /dev/loop1 -f /tmp/tfile_loop1 + losetup: /dev/loop1: failed to set up loop device: Invalid argument + +fixed version: + + # losetup /dev/loop10 -f img + losetup: unexpected arguments + +Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1566432 +Upstream: http://github.com/karelzak/util-linux/commit/c3f5a0f1d47dbc47f6d21da232d4eb1cfb7905db +Signed-off-by: Karel Zak +--- + sys-utils/losetup.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/sys-utils/losetup.c b/sys-utils/losetup.c +index d9b0c9b61..8df831b8f 100644 +--- a/sys-utils/losetup.c ++++ b/sys-utils/losetup.c +@@ -545,6 +545,9 @@ int main(int argc, char **argv) + */ + act = A_CREATE; + file = argv[optind++]; ++ ++ if (optind < argc) ++ errx(EXIT_FAILURE, _("unexpected arguments")); + } + + if (list && !act && optind == argc) +-- +2.14.4 + diff --git a/SOURCES/0169-lscpu-fix-mzx-min-MHz-reporting.patch b/SOURCES/0169-lscpu-fix-mzx-min-MHz-reporting.patch new file mode 100644 index 0000000..dd53555 --- /dev/null +++ b/SOURCES/0169-lscpu-fix-mzx-min-MHz-reporting.patch @@ -0,0 +1,83 @@ +From 79866c4b187e9ccf37fe333e9865a8575f4a3a16 Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Wed, 6 Jun 2018 10:10:20 +0200 +Subject: [PATCH 169/173] lscpu: fix mzx/min MHz reporting + +Addresses: http://bugzilla.redhat.com/show_bug.cgi?id=1579439 +Signed-off-by: Karel Zak +--- + sys-utils/lscpu.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- + 1 file changed, 48 insertions(+), 2 deletions(-) + +diff --git a/sys-utils/lscpu.c b/sys-utils/lscpu.c +index 1ee73f34a..4c15de1d4 100644 +--- a/sys-utils/lscpu.c ++++ b/sys-utils/lscpu.c +@@ -1251,6 +1251,52 @@ read_configured(struct lscpu_desc *desc, int idx) + desc->configured[idx] = path_read_s32(_PATH_SYS_CPU "/cpu%d/configure", num); + } + ++/* Read overall maximum frequency of cpu */ ++static char * ++cpu_max_mhz(struct lscpu_desc *desc, char *buf, size_t bufsz) ++{ ++ int i; ++ float cpu_freq = 0.0; ++ size_t setsize = CPU_ALLOC_SIZE(maxcpus); ++ ++ if (desc->present) { ++ for (i = 0; i < desc->ncpuspos; i++) { ++ if (CPU_ISSET_S(real_cpu_num(desc, i), setsize, desc->present) ++ && desc->maxmhz[i]) { ++ float freq = atof(desc->maxmhz[i]); ++ ++ if (freq > cpu_freq) ++ cpu_freq = freq; ++ } ++ } ++ } ++ snprintf(buf, bufsz, "%.4f", cpu_freq); ++ return buf; ++} ++ ++/* Read overall minimum frequency of cpu */ ++static char * ++cpu_min_mhz(struct lscpu_desc *desc, char *buf, size_t bufsz) ++{ ++ int i; ++ float cpu_freq = -1.0; ++ size_t setsize = CPU_ALLOC_SIZE(maxcpus); ++ ++ if (desc->present) { ++ for (i = 0; i < desc->ncpuspos; i++) { ++ if (CPU_ISSET_S(real_cpu_num(desc, i), setsize, desc->present) ++ && desc->minmhz[i]) { ++ float freq = atof(desc->minmhz[i]); ++ ++ if (cpu_freq < 0.0 || freq < cpu_freq) ++ cpu_freq = freq; ++ } ++ } ++ } ++ snprintf(buf, bufsz, "%.4f", cpu_freq); ++ return buf; ++} ++ + static void + read_max_mhz(struct lscpu_desc *desc, int idx) + { +@@ -1898,9 +1944,9 @@ print_summary(struct lscpu_desc *desc, struct lscpu_modifier *mod) + if (desc->static_mhz) + print_s(_("CPU static MHz:"), desc->static_mhz); + if (desc->maxmhz && desc->maxmhz[0]) +- print_s(_("CPU max MHz:"), desc->maxmhz[0]); ++ print_s(_("CPU max MHz:"), cpu_max_mhz(desc, buf, sizeof(buf))); + if (desc->minmhz && desc->minmhz[0]) +- print_s(_("CPU min MHz:"), desc->minmhz[0]); ++ print_s(_("CPU min MHz:"), cpu_min_mhz(desc, buf, sizeof(buf))); + if (desc->bogomips) + print_s(_("BogoMIPS:"), desc->bogomips); + if (desc->virtflag) { +-- +2.14.4 + diff --git a/SOURCES/0170-chcpu-cleanup-return-codes.patch b/SOURCES/0170-chcpu-cleanup-return-codes.patch new file mode 100644 index 0000000..147d201 --- /dev/null +++ b/SOURCES/0170-chcpu-cleanup-return-codes.patch @@ -0,0 +1,253 @@ +From 8f57ecb12194a10f3dbe6eca9f4bf0b18c4be757 Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Wed, 5 Mar 2014 11:06:59 +0100 +Subject: [PATCH 170/173] chcpu: cleanup return codes + +The code currently always return EXIT_SUCCESS, that's strange. It +seems better to return 0 on success, 1 on complete failure and 64 on +partial success. + +Signed-off-by: Karel Zak +Upstream: http://github.com/karelzak/util-linux/commit/48fc00c1c70f3dbbd8ad6ef423bbba27dd3efb69 +Addresses: http://bugzilla.redhat.com/show_bug.cgi?id=1579439 +--- + sys-utils/chcpu.8 | 14 ++++++++++ + sys-utils/chcpu.c | 82 ++++++++++++++++++++++++++++++++++++++++--------------- + 2 files changed, 74 insertions(+), 22 deletions(-) + +diff --git a/sys-utils/chcpu.8 b/sys-utils/chcpu.8 +index d016b86f2..125d9d2ad 100644 +--- a/sys-utils/chcpu.8 ++++ b/sys-utils/chcpu.8 +@@ -80,6 +80,20 @@ Display help information and exit. + .TP + .BR \-V , " \-\-version" + Display version information and exit. ++ ++.SH RETURN CODES ++.B chcpu ++has the following return codes: ++.TP ++.BR 0 ++success ++.TP ++.BR 1 ++failure ++.TP ++.BR 64 ++partial success ++.RE + .SH AUTHOR + .MT heiko.carstens@de.ibm.com + Heiko Carstens +diff --git a/sys-utils/chcpu.c b/sys-utils/chcpu.c +index 1162888d5..304b80d7a 100644 +--- a/sys-utils/chcpu.c ++++ b/sys-utils/chcpu.c +@@ -45,6 +45,9 @@ + + #define EXCL_ERROR "--{configure,deconfigure,disable,dispatch,enable}" + ++/* partial success, otherwise we return regular EXIT_{SUCCESS,FAILURE} */ ++#define CHCPU_EXIT_SOMEOK 64 ++ + #define _PATH_SYS_CPU "/sys/devices/system/cpu" + #define _PATH_SYS_CPU_ONLINE _PATH_SYS_CPU "/online" + #define _PATH_SYS_CPU_RESCAN _PATH_SYS_CPU "/rescan" +@@ -66,21 +69,28 @@ enum { + CMD_CPU_DISPATCH_VERTICAL, + }; + ++/* returns: 0 = success ++ * < 0 = failure ++ * > 0 = partial success ++ */ + static int cpu_enable(cpu_set_t *cpu_set, size_t setsize, int enable) + { + unsigned int cpu; + int online, rc; + int configured = -1; ++ size_t fails = 0; + + for (cpu = 0; cpu < setsize; cpu++) { + if (!CPU_ISSET(cpu, cpu_set)) + continue; + if (!path_exist(_PATH_SYS_CPU "/cpu%d", cpu)) { + printf(_("CPU %d does not exist\n"), cpu); ++ fails++; + continue; + } + if (!path_exist(_PATH_SYS_CPU "/cpu%d/online", cpu)) { + printf(_("CPU %d is not hot pluggable\n"), cpu); ++ fails++; + continue; + } + online = path_read_s32(_PATH_SYS_CPU "/cpu%d/online", cpu); +@@ -96,30 +106,35 @@ static int cpu_enable(cpu_set_t *cpu_set, size_t setsize, int enable) + configured = path_read_s32(_PATH_SYS_CPU "/cpu%d/configure", cpu); + if (enable) { + rc = path_write_str("1", _PATH_SYS_CPU "/cpu%d/online", cpu); +- if ((rc == -1) && (configured == 0)) ++ if ((rc == -1) && (configured == 0)) { + warnx(_("CPU %d enable failed " + "(CPU is deconfigured)"), cpu); +- else if (rc == -1) ++ fails++; ++ } else if (rc == -1) { + warn(_("CPU %d enable failed"), cpu); +- else ++ fails++; ++ } else + printf(_("CPU %d enabled\n"), cpu); + } else { + if (onlinecpus && num_online_cpus() == 1) { + printf(_("CPU %d disable failed " + "(last enabled CPU)\n"), cpu); ++ fails++; + continue; + } + rc = path_write_str("0", _PATH_SYS_CPU "/cpu%d/online", cpu); +- if (rc == -1) ++ if (rc == -1) { + warn(_("CPU %d disable failed"), cpu); +- else { ++ fails++; ++ } else { + printf(_("CPU %d disabled\n"), cpu); + if (onlinecpus) + CPU_CLR(cpu, onlinecpus); + } + } + } +- return EXIT_SUCCESS; ++ ++ return fails == 0 ? 0 : fails == setsize ? -1 : 1; + } + + static int cpu_rescan(void) +@@ -129,7 +144,7 @@ static int cpu_rescan(void) + if (path_write_str("1", _PATH_SYS_CPU_RESCAN) == -1) + err(EXIT_FAILURE, _("Failed to trigger rescan of CPUs")); + printf(_("Triggered rescan of CPUs\n")); +- return EXIT_SUCCESS; ++ return 0; + } + + static int cpu_set_dispatch(int mode) +@@ -146,23 +161,30 @@ static int cpu_set_dispatch(int mode) + err(EXIT_FAILURE, _("Failed to set vertical dispatch mode")); + printf(_("Successfully set vertical dispatching mode\n")); + } +- return EXIT_SUCCESS; ++ return 0; + } + ++/* returns: 0 = success ++ * < 0 = failure ++ * > 0 = partial success ++ */ + static int cpu_configure(cpu_set_t *cpu_set, size_t setsize, int configure) + { + unsigned int cpu; + int rc, current; ++ size_t fails = 0; + + for (cpu = 0; cpu < setsize; cpu++) { + if (!CPU_ISSET(cpu, cpu_set)) + continue; + if (!path_exist(_PATH_SYS_CPU "/cpu%d", cpu)) { + printf(_("CPU %d does not exist\n"), cpu); ++ fails++; + continue; + } + if (!path_exist(_PATH_SYS_CPU "/cpu%d/configure", cpu)) { + printf(_("CPU %d is not configurable\n"), cpu); ++ fails++; + continue; + } + current = path_read_s32(_PATH_SYS_CPU "/cpu%d/configure", cpu); +@@ -178,23 +200,27 @@ static int cpu_configure(cpu_set_t *cpu_set, size_t setsize, int configure) + is_cpu_online(cpu)) { + printf(_("CPU %d deconfigure failed " + "(CPU is enabled)\n"), cpu); ++ fails++; + continue; + } + if (configure) { + rc = path_write_str("1", _PATH_SYS_CPU "/cpu%d/configure", cpu); +- if (rc == -1) ++ if (rc == -1) { + warn(_("CPU %d configure failed"), cpu); +- else ++ fails++; ++ } else + printf(_("CPU %d configured\n"), cpu); + } else { + rc = path_write_str("0", _PATH_SYS_CPU "/cpu%d/configure", cpu); +- if (rc == -1) ++ if (rc == -1) { + warn(_("CPU %d deconfigure failed"), cpu); +- else ++ fails++; ++ } else + printf(_("CPU %d deconfigured\n"), cpu); + } + } +- return EXIT_SUCCESS; ++ ++ return fails == 0 ? 0 : fails == setsize ? -1 : 1; + } + + static void cpu_parse(char *cpu_string, cpu_set_t *cpu_set, size_t setsize) +@@ -233,7 +259,7 @@ int main(int argc, char *argv[]) + cpu_set_t *cpu_set; + size_t setsize; + int cmd = -1; +- int c; ++ int c, rc; + + static const struct option longopts[] = { + { "configure", required_argument, 0, 'c' }, +@@ -317,19 +343,31 @@ int main(int argc, char *argv[]) + + switch (cmd) { + case CMD_CPU_ENABLE: +- return cpu_enable(cpu_set, maxcpus, 1); ++ rc = cpu_enable(cpu_set, maxcpus, 1); ++ break; + case CMD_CPU_DISABLE: +- return cpu_enable(cpu_set, maxcpus, 0); ++ rc = cpu_enable(cpu_set, maxcpus, 0); ++ break; + case CMD_CPU_CONFIGURE: +- return cpu_configure(cpu_set, maxcpus, 1); ++ rc = cpu_configure(cpu_set, maxcpus, 1); ++ break; + case CMD_CPU_DECONFIGURE: +- return cpu_configure(cpu_set, maxcpus, 0); ++ rc = cpu_configure(cpu_set, maxcpus, 0); ++ break; + case CMD_CPU_RESCAN: +- return cpu_rescan(); ++ rc = cpu_rescan(); ++ break; + case CMD_CPU_DISPATCH_HORIZONTAL: +- return cpu_set_dispatch(0); ++ rc = cpu_set_dispatch(0); ++ break; + case CMD_CPU_DISPATCH_VERTICAL: +- return cpu_set_dispatch(1); ++ rc = cpu_set_dispatch(1); ++ break; ++ default: ++ rc = -EINVAL; ++ break; + } +- return EXIT_SUCCESS; ++ ++ return rc == 0 ? EXIT_SUCCESS : ++ rc < 0 ? EXIT_FAILURE : CHCPU_EXIT_SOMEOK; + } +-- +2.14.4 + diff --git a/SOURCES/0171-chcpu-cleanup-stdout-stderr-usage.patch b/SOURCES/0171-chcpu-cleanup-stdout-stderr-usage.patch new file mode 100644 index 0000000..8f500a8 --- /dev/null +++ b/SOURCES/0171-chcpu-cleanup-stdout-stderr-usage.patch @@ -0,0 +1,79 @@ +From 26c75797103da70f85a5fdd646625edb3eaa300c Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Wed, 5 Mar 2014 11:23:16 +0100 +Subject: [PATCH 171/173] chcpu: cleanup stdout/stderr usage + +Signed-off-by: Karel Zak +Upstream: http://github.com/karelzak/util-linux/commit/69e74525bb3212944feebc8c2848317e04d95ec0 +Addresses: http://bugzilla.redhat.com/show_bug.cgi?id=1579439 +--- + sys-utils/chcpu.c | 17 +++++++---------- + 1 file changed, 7 insertions(+), 10 deletions(-) + +diff --git a/sys-utils/chcpu.c b/sys-utils/chcpu.c +index 304b80d7a..ada0eaacc 100644 +--- a/sys-utils/chcpu.c ++++ b/sys-utils/chcpu.c +@@ -84,12 +84,12 @@ static int cpu_enable(cpu_set_t *cpu_set, size_t setsize, int enable) + if (!CPU_ISSET(cpu, cpu_set)) + continue; + if (!path_exist(_PATH_SYS_CPU "/cpu%d", cpu)) { +- printf(_("CPU %d does not exist\n"), cpu); ++ warnx(_("CPU %d does not exist"), cpu); + fails++; + continue; + } + if (!path_exist(_PATH_SYS_CPU "/cpu%d/online", cpu)) { +- printf(_("CPU %d is not hot pluggable\n"), cpu); ++ warnx(_("CPU %d is not hot pluggable"), cpu); + fails++; + continue; + } +@@ -107,8 +107,7 @@ static int cpu_enable(cpu_set_t *cpu_set, size_t setsize, int enable) + if (enable) { + rc = path_write_str("1", _PATH_SYS_CPU "/cpu%d/online", cpu); + if ((rc == -1) && (configured == 0)) { +- warnx(_("CPU %d enable failed " +- "(CPU is deconfigured)"), cpu); ++ warn(_("CPU %d enable failed (CPU is deconfigured)"), cpu); + fails++; + } else if (rc == -1) { + warn(_("CPU %d enable failed"), cpu); +@@ -117,8 +116,7 @@ static int cpu_enable(cpu_set_t *cpu_set, size_t setsize, int enable) + printf(_("CPU %d enabled\n"), cpu); + } else { + if (onlinecpus && num_online_cpus() == 1) { +- printf(_("CPU %d disable failed " +- "(last enabled CPU)\n"), cpu); ++ warnx(_("CPU %d disable failed (last enabled CPU)"), cpu); + fails++; + continue; + } +@@ -178,12 +176,12 @@ static int cpu_configure(cpu_set_t *cpu_set, size_t setsize, int configure) + if (!CPU_ISSET(cpu, cpu_set)) + continue; + if (!path_exist(_PATH_SYS_CPU "/cpu%d", cpu)) { +- printf(_("CPU %d does not exist\n"), cpu); ++ warnx(_("CPU %d does not exist"), cpu); + fails++; + continue; + } + if (!path_exist(_PATH_SYS_CPU "/cpu%d/configure", cpu)) { +- printf(_("CPU %d is not configurable\n"), cpu); ++ warnx(_("CPU %d is not configurable"), cpu); + fails++; + continue; + } +@@ -198,8 +196,7 @@ static int cpu_configure(cpu_set_t *cpu_set, size_t setsize, int configure) + } + if ((current == 1) && (configure == 0) && onlinecpus && + is_cpu_online(cpu)) { +- printf(_("CPU %d deconfigure failed " +- "(CPU is enabled)\n"), cpu); ++ warnx(_("CPU %d deconfigure failed (CPU is enabled)"), cpu); + fails++; + continue; + } +-- +2.14.4 + diff --git a/SOURCES/0172-lscpu-chcpu-Avoid-use-of-the-old-CPU-macros.patch b/SOURCES/0172-lscpu-chcpu-Avoid-use-of-the-old-CPU-macros.patch new file mode 100644 index 0000000..30822d1 --- /dev/null +++ b/SOURCES/0172-lscpu-chcpu-Avoid-use-of-the-old-CPU-macros.patch @@ -0,0 +1,86 @@ +From 255a3b8bb8cbcb3f689cc3332983983bfbf9eca0 Mon Sep 17 00:00:00 2001 +From: Stanislav Brabec +Date: Wed, 9 May 2018 18:08:32 +0200 +Subject: [PATCH 172/173] lscpu, chcpu: Avoid use of the old CPU macros + +The old CPU macros are limited to 1024 cores. As a result, lscpu cannot +count sockets on large systems. Use new scalable macros. + +Signed-off-by: Stanislav Brabec +Cc: Michael Matz +Addresses: http://bugzilla.redhat.com/show_bug.cgi?id=1579439 +Upstream: http://github.com/karelzak/util-linux/commit/538b50cb0a4aac56b6b3b6e4d1e8ce886854c6d8 +--- + sys-utils/chcpu.c | 6 +++--- + sys-utils/lscpu.c | 7 +++++-- + 2 files changed, 8 insertions(+), 5 deletions(-) + +diff --git a/sys-utils/chcpu.c b/sys-utils/chcpu.c +index ada0eaacc..7843dfb22 100644 +--- a/sys-utils/chcpu.c ++++ b/sys-utils/chcpu.c +@@ -81,7 +81,7 @@ static int cpu_enable(cpu_set_t *cpu_set, size_t setsize, int enable) + size_t fails = 0; + + for (cpu = 0; cpu < setsize; cpu++) { +- if (!CPU_ISSET(cpu, cpu_set)) ++ if (!CPU_ISSET_S(cpu, setsize, cpu_set)) + continue; + if (!path_exist(_PATH_SYS_CPU "/cpu%d", cpu)) { + warnx(_("CPU %d does not exist"), cpu); +@@ -127,7 +127,7 @@ static int cpu_enable(cpu_set_t *cpu_set, size_t setsize, int enable) + } else { + printf(_("CPU %d disabled\n"), cpu); + if (onlinecpus) +- CPU_CLR(cpu, onlinecpus); ++ CPU_CLR_S(cpu, setsize, onlinecpus); + } + } + } +@@ -173,7 +173,7 @@ static int cpu_configure(cpu_set_t *cpu_set, size_t setsize, int configure) + size_t fails = 0; + + for (cpu = 0; cpu < setsize; cpu++) { +- if (!CPU_ISSET(cpu, cpu_set)) ++ if (!CPU_ISSET_S(cpu, setsize, cpu_set)) + continue; + if (!path_exist(_PATH_SYS_CPU "/cpu%d", cpu)) { + warnx(_("CPU %d does not exist"), cpu); +diff --git a/sys-utils/lscpu.c b/sys-utils/lscpu.c +index 4c15de1d4..002ad0d1c 100644 +--- a/sys-utils/lscpu.c ++++ b/sys-utils/lscpu.c +@@ -626,7 +626,7 @@ read_basicinfo(struct lscpu_desc *desc, struct lscpu_modifier *mod) + desc->idx2cpunum = xcalloc(desc->ncpuspos, sizeof(int)); + + for (num = 0, idx = 0; num < maxcpus; num++) { +- if (CPU_ISSET(num, tmp)) ++ if (CPU_ISSET_S(num, setsize, tmp)) + desc->idx2cpunum[idx++] = num; + } + cpuset_free(tmp); +@@ -2038,6 +2038,7 @@ int main(int argc, char *argv[]) + int c, i; + int columns[ARRAY_SIZE(coldescs)], ncolumns = 0; + int cpu_modifier_specified = 0; ++ size_t setsize; + + static const struct option longopts[] = { + { "all", no_argument, NULL, 'a' }, +@@ -2134,10 +2135,12 @@ int main(int argc, char *argv[]) + + read_basicinfo(desc, mod); + ++ setsize = CPU_ALLOC_SIZE(maxcpus); ++ + for (i = 0; i < desc->ncpuspos; i++) { + /* only consider present CPUs */ + if (desc->present && +- !CPU_ISSET(real_cpu_num(desc, i), desc->present)) ++ !CPU_ISSET_S(real_cpu_num(desc, i), setsize, desc->present)) + continue; + read_topology(desc, i); + read_cache(desc, i); +-- +2.14.4 + diff --git a/SOURCES/0173-chcpu-Fix-maximal-number-of-CPUs.patch b/SOURCES/0173-chcpu-Fix-maximal-number-of-CPUs.patch new file mode 100644 index 0000000..3d27014 --- /dev/null +++ b/SOURCES/0173-chcpu-Fix-maximal-number-of-CPUs.patch @@ -0,0 +1,74 @@ +From 72e09b02e7b4cf3e9e77ef02d5073d9a11f3b314 Mon Sep 17 00:00:00 2001 +From: Stanislav Brabec +Date: Wed, 9 May 2018 22:13:07 +0200 +Subject: [PATCH 173/173] chcpu: Fix maximal number of CPUs + +chcpu.c mixed maxcpus (number of cpus) and setsize (size of CPU bit +mask). It effectively limits number of CPUs to 1/8 of the supported +amount. + +Signed-off-by: Stanislav Brabec +Cc: Michael Matz +Cc: Heiko Carstens +Upstream: http://github.com/karelzak/util-linux/commit/607274943bfd3d4856b872bc4278b36903fb2182 +Addresses: http://bugzilla.redhat.com/show_bug.cgi?id=1579439 +--- + sys-utils/chcpu.c | 16 ++++++++-------- + 1 file changed, 8 insertions(+), 8 deletions(-) + +diff --git a/sys-utils/chcpu.c b/sys-utils/chcpu.c +index 7843dfb22..1a0157360 100644 +--- a/sys-utils/chcpu.c ++++ b/sys-utils/chcpu.c +@@ -75,12 +75,12 @@ enum { + */ + static int cpu_enable(cpu_set_t *cpu_set, size_t setsize, int enable) + { +- unsigned int cpu; ++ int cpu; + int online, rc; + int configured = -1; +- size_t fails = 0; ++ int fails = 0; + +- for (cpu = 0; cpu < setsize; cpu++) { ++ for (cpu = 0; cpu < maxcpus; cpu++) { + if (!CPU_ISSET_S(cpu, setsize, cpu_set)) + continue; + if (!path_exist(_PATH_SYS_CPU "/cpu%d", cpu)) { +@@ -132,7 +132,7 @@ static int cpu_enable(cpu_set_t *cpu_set, size_t setsize, int enable) + } + } + +- return fails == 0 ? 0 : fails == setsize ? -1 : 1; ++ return fails == 0 ? 0 : fails == maxcpus ? -1 : 1; + } + + static int cpu_rescan(void) +@@ -168,11 +168,11 @@ static int cpu_set_dispatch(int mode) + */ + static int cpu_configure(cpu_set_t *cpu_set, size_t setsize, int configure) + { +- unsigned int cpu; ++ int cpu; + int rc, current; +- size_t fails = 0; ++ int fails = 0; + +- for (cpu = 0; cpu < setsize; cpu++) { ++ for (cpu = 0; cpu < maxcpus; cpu++) { + if (!CPU_ISSET_S(cpu, setsize, cpu_set)) + continue; + if (!path_exist(_PATH_SYS_CPU "/cpu%d", cpu)) { +@@ -217,7 +217,7 @@ static int cpu_configure(cpu_set_t *cpu_set, size_t setsize, int configure) + } + } + +- return fails == 0 ? 0 : fails == setsize ? -1 : 1; ++ return fails == 0 ? 0 : fails == maxcpus ? -1 : 1; + } + + static void cpu_parse(char *cpu_string, cpu_set_t *cpu_set, size_t setsize) +-- +2.14.4 + diff --git a/SOURCES/0174-libblkid-minix-Match-minix-superblock-types.patch b/SOURCES/0174-libblkid-minix-Match-minix-superblock-types.patch new file mode 100644 index 0000000..5e42e3b --- /dev/null +++ b/SOURCES/0174-libblkid-minix-Match-minix-superblock-types.patch @@ -0,0 +1,44 @@ +From b4d5cf4819023ce07ecd39729cdd4cde8b59ca35 Mon Sep 17 00:00:00 2001 +From: Nate Clark +Date: Wed, 4 Jan 2017 15:24:22 -0500 +Subject: [PATCH 174/176] libblkid/minix: Match minix superblock types + +All of the types in the minix super block are unsigned but in +probe_minix they were being treated as signed. This would cause some of +the extra sanity checks to pass on a non minix device. The types were +updated to match the return types of the helper functions in +disk-utils/minix_programs.h + +This can be checked by creating a swap partition with one of these UUIDs +35f1f264-2468-471a-bc85-acc9f4bc04a3 +35f1f264-6824-471a-bc85-acc9f4bc04a3 +35f1f264-2478-471a-bc85-acc9f4bc04a3 +35f1f264-7824-471a-bc85-acc9f4bc04a3 + +Prior to this change they would all be considered minix and swap by +blkid. + +Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1594681 +Upstream: http://github.com/karelzak/util-linux/commit/a9975c1072c4975ec2df958188a80d89cabc6171 +Signed-off-by: Nate Clark +--- + libblkid/src/superblocks/minix.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/libblkid/src/superblocks/minix.c b/libblkid/src/superblocks/minix.c +index 3e80e5b22..a20d51f2c 100644 +--- a/libblkid/src/superblocks/minix.c ++++ b/libblkid/src/superblocks/minix.c +@@ -87,7 +87,8 @@ static int probe_minix(blkid_probe pr, const struct blkid_idmag *mag) + + if (version <= 2) { + struct minix_super_block *sb = (struct minix_super_block *) data; +- int zones, ninodes, imaps, zmaps, firstz; ++ unsigned long zones, ninodes, imaps, zmaps; ++ off_t firstz; + + if (sb->s_imap_blocks == 0 || sb->s_zmap_blocks == 0) + return 1; +-- +2.14.4 + diff --git a/SOURCES/0175-libblkid-minix-Sanity-check-superblock-s_state-for-v.patch b/SOURCES/0175-libblkid-minix-Sanity-check-superblock-s_state-for-v.patch new file mode 100644 index 0000000..58dfa33 --- /dev/null +++ b/SOURCES/0175-libblkid-minix-Sanity-check-superblock-s_state-for-v.patch @@ -0,0 +1,44 @@ +From 6eb3a6579768458c13c26958e1cc3fbffe2ada70 Mon Sep 17 00:00:00 2001 +From: Nate Clark +Date: Wed, 4 Jan 2017 15:21:17 -0500 +Subject: [PATCH 175/176] libblkid/minix: Sanity check superblock s_state for v + 1 and 2 + +Swap devices with specific values in the uuid can look like minix +devices to blkid. Add an extra check to make sure the state of the +filesystem has valid state flags. + +A couple of offending swap uuids include: +35f1f264-137f-471a-bc85-acc9f4bc04a3 +35f1f264-7f13-471a-bc85-acc9f4bc04a3 +35f1f264-138f-471a-bc85-acc9f4bc04a3 +35f1f264-8f13-471a-bc85-acc9f4bc04a3 + +Without this change a swap device with any of those uuids would be +detected as minix and swap by blkid. + +Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1594681 +Upstream: http://github.com/karelzak/util-linux/commit/892553b1a41b449f58462f123eca2bf2c6c56b33 +Signed-off-by: Nate Clark +--- + libblkid/src/superblocks/minix.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/libblkid/src/superblocks/minix.c b/libblkid/src/superblocks/minix.c +index a20d51f2c..4e70fda8f 100644 +--- a/libblkid/src/superblocks/minix.c ++++ b/libblkid/src/superblocks/minix.c +@@ -93,6 +93,10 @@ static int probe_minix(blkid_probe pr, const struct blkid_idmag *mag) + if (sb->s_imap_blocks == 0 || sb->s_zmap_blocks == 0) + return 1; + ++ uint16_t state = minix_swab16(swabme, sb->s_state); ++ if ((state & (MINIX_VALID_FS | MINIX_ERROR_FS)) != state) ++ return 1; ++ + zones = version == 2 ? minix_swab32(swabme, sb->s_zones) : + minix_swab16(swabme, sb->s_nzones); + ninodes = minix_swab16(swabme, sb->s_ninodes); +-- +2.14.4 + diff --git a/SOURCES/0176-libblkid-minix-Use-same-checks-for-version-3.patch b/SOURCES/0176-libblkid-minix-Use-same-checks-for-version-3.patch new file mode 100644 index 0000000..577af59 --- /dev/null +++ b/SOURCES/0176-libblkid-minix-Use-same-checks-for-version-3.patch @@ -0,0 +1,90 @@ +From b5d9bfe34e62cf1c57646efa0bd72ecad7d98258 Mon Sep 17 00:00:00 2001 +From: Nate Clark +Date: Wed, 4 Jan 2017 15:24:32 -0500 +Subject: [PATCH 176/176] libblkid/minix: Use same checks for version 3 + +fsck.minix performs the same sanity checks on all versions of the +superblock. Update the probe to perform the same sanity checks so it is +less likely a different type of filesystem will be identified as minix. + +Upstream: http://github.com/karelzak/util-linux/commit/f82c804869bb8613fa0924e3111b7eb55bb04fcd +Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1594681 +Signed-off-by: Nate Clark +Signed-off-by: Karel Zak +--- + libblkid/src/superblocks/minix.c | 38 +++++++++++++++++++++++--------------- + 1 file changed, 23 insertions(+), 15 deletions(-) + +diff --git a/libblkid/src/superblocks/minix.c b/libblkid/src/superblocks/minix.c +index 4e70fda8f..21b3bf8bb 100644 +--- a/libblkid/src/superblocks/minix.c ++++ b/libblkid/src/superblocks/minix.c +@@ -75,6 +75,9 @@ static int probe_minix(blkid_probe pr, const struct blkid_idmag *mag) + unsigned char *ext; + const unsigned char *data; + int version = 0, swabme = 0; ++ unsigned long zones, ninodes, imaps, zmaps; ++ off_t firstz; ++ size_t zone_size; + + data = blkid_probe_get_buffer(pr, 1024, + max(sizeof(struct minix_super_block), +@@ -85,14 +88,9 @@ static int probe_minix(blkid_probe pr, const struct blkid_idmag *mag) + if (version < 1) + return 1; + ++ + if (version <= 2) { + struct minix_super_block *sb = (struct minix_super_block *) data; +- unsigned long zones, ninodes, imaps, zmaps; +- off_t firstz; +- +- if (sb->s_imap_blocks == 0 || sb->s_zmap_blocks == 0) +- return 1; +- + uint16_t state = minix_swab16(swabme, sb->s_state); + if ((state & (MINIX_VALID_FS | MINIX_ERROR_FS)) != state) + return 1; +@@ -103,20 +101,30 @@ static int probe_minix(blkid_probe pr, const struct blkid_idmag *mag) + imaps = minix_swab16(swabme, sb->s_imap_blocks); + zmaps = minix_swab16(swabme, sb->s_zmap_blocks); + firstz = minix_swab16(swabme, sb->s_firstdatazone); +- +- /* sanity checks to be sure that the FS is really minix */ +- if (imaps * MINIX_BLOCK_SIZE * 8 < ninodes + 1) +- return 1; +- if (zmaps * MINIX_BLOCK_SIZE * 8 < zones - firstz + 1) +- return 1; +- ++ zone_size = sb->s_log_zone_size; + } else if (version == 3) { + struct minix3_super_block *sb = (struct minix3_super_block *) data; + +- if (sb->s_imap_blocks == 0 || sb->s_zmap_blocks == 0) +- return 1; ++ zones = minix_swab32(swabme, sb->s_zones); ++ ninodes = minix_swab32(swabme, sb->s_ninodes); ++ imaps = minix_swab16(swabme, sb->s_imap_blocks); ++ zmaps = minix_swab16(swabme, sb->s_zmap_blocks); ++ firstz = minix_swab16(swabme, sb->s_firstdatazone); ++ zone_size = sb->s_log_zone_size; + } + ++ /* sanity checks to be sure that the FS is really minix. ++ * see disk-utils/fsck.minix.c read_superblock ++ */ ++ if (zone_size != 0 || ninodes == 0 || ninodes == UINT32_MAX) ++ return 1; ++ if (imaps * MINIX_BLOCK_SIZE * 8 < ninodes + 1) ++ return 1; ++ if (firstz > (off_t) zones) ++ return 1; ++ if (zmaps * MINIX_BLOCK_SIZE * 8 < zones - firstz + 1) ++ return 1; ++ + /* unfortunately, some parts of ext3 is sometimes possible to + * interpreted as minix superblock. So check for extN magic + * string. (For extN magic string and offsets see ext.c.) +-- +2.14.4 + diff --git a/SOURCES/0177-mount-append-inverting-options-for-mount.-type-on-us.patch b/SOURCES/0177-mount-append-inverting-options-for-mount.-type-on-us.patch new file mode 100644 index 0000000..cf3ba6d --- /dev/null +++ b/SOURCES/0177-mount-append-inverting-options-for-mount.-type-on-us.patch @@ -0,0 +1,41 @@ +From 4b376d1ad1b46408bc59d949c277991a9b5035eb Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Thu, 27 Oct 2016 15:30:20 +0200 +Subject: [PATCH 177/178] mount: append inverting options for mount. on + "users" + +If you call mount(8) as root, then we need to append inverting options +(if specified by fstab) for "user" and "users" to /sbin/mount. +command line, because for UID=0 mount.nfs follows command line rather +than the fstab setting. + +This has been originally implemented by commit +a4c0cc75ff9744299f108c259efab1bd30c8007a for the old mount(8). The +same feature is supported by libmount, unfortunately for "user" only. +We need the same also for "users" to be backwardly compatible. + +Addresses: https://github.com/karelzak/util-linux/issues/368 +Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1618711 +Upstream: http://github.com/karelzak/util-linux/commit/3c4a3de0fcb8f21bffacfd8bdc3d6fbd683c71f5 +Signed-off-by: Karel Zak +--- + libmount/src/context_mount.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/libmount/src/context_mount.c b/libmount/src/context_mount.c +index 4df2646b0..0f4485592 100644 +--- a/libmount/src/context_mount.c ++++ b/libmount/src/context_mount.c +@@ -321,7 +321,8 @@ static int generate_helper_optstr(struct libmnt_context *cxt, char **optstr) + if (!*optstr) + return -ENOMEM; + +- if (cxt->user_mountflags & MNT_MS_USER) { ++ if ((cxt->user_mountflags & MNT_MS_USER) || ++ (cxt->user_mountflags & MNT_MS_USERS)) { + /* + * This is unnecessary for real user-mounts as mount. + * helpers have to always follow fstab rather than mount +-- +2.14.4 + diff --git a/SOURCES/0178-sulogin-backport-RHEL-8-version.patch b/SOURCES/0178-sulogin-backport-RHEL-8-version.patch new file mode 100644 index 0000000..f33b245 --- /dev/null +++ b/SOURCES/0178-sulogin-backport-RHEL-8-version.patch @@ -0,0 +1,839 @@ +From f720781671c9f5421fb8101dd3bcf7b56efca131 Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Mon, 20 Aug 2018 14:56:08 +0200 +Subject: [PATCH 178/178] sulogin: backport RHEL-8 version + +The new version is more robust and it does not use mmap()-ed shared +memory between sulogins instances. It also provides some fallbacks for +s390 machines. + +Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1616264 +Signed-off-by: Karel Zak +--- + configure.ac | 1 + + include/c.h | 4 + + login-utils/sulogin-consoles.c | 116 +++++++++++++------ + login-utils/sulogin.c | 253 +++++++++++++++++++++++++++-------------- + 4 files changed, 252 insertions(+), 122 deletions(-) + +diff --git a/configure.ac b/configure.ac +index d561e01d0..3f07df495 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -218,6 +218,7 @@ AC_CHECK_HEADERS([ \ + sys/prctl.h \ + sys/queue.h \ + sys/resource.h \ ++ sys/sysmacros.h \ + sys/socket.h \ + sys/sockio.h \ + sys/stat.h \ +diff --git a/include/c.h b/include/c.h +index 124035ea5..51d439297 100644 +--- a/include/c.h ++++ b/include/c.h +@@ -19,6 +19,10 @@ + # include + #endif + ++#ifdef HAVE_SYS_SYSMACROS_H ++# include /* for major, minor */ ++#endif ++ + #ifndef HAVE_USLEEP + # include + #endif +diff --git a/login-utils/sulogin-consoles.c b/login-utils/sulogin-consoles.c +index 07af33a6d..2c0eed3a4 100644 +--- a/login-utils/sulogin-consoles.c ++++ b/login-utils/sulogin-consoles.c +@@ -36,8 +36,9 @@ + # include + # include + #endif +-#include + #include ++#include ++#include + #include + + #ifdef USE_SULOGIN_EMERGENCY_MOUNT +@@ -74,7 +75,7 @@ static int consoles_debug; + } while (0) + + static inline void __attribute__ ((__format__ (__printf__, 1, 2))) +-dbgprint(const char *mesg, ...) ++dbgprint(const char * const mesg, ...) + { + va_list ap; + va_start(ap, mesg); +@@ -112,7 +113,7 @@ void emergency_do_mounts(void) + } + + if (stat("/", &rt) != 0) { +- warn("can not get file status of root file system\n"); ++ warn("cannot get file status of root file system\n"); + return; + } + +@@ -150,17 +151,19 @@ void emergency_do_mounts(void) { } + * the caller has to free the result + */ + static __attribute__((__nonnull__)) +-char *oneline(const char *file) ++char *oneline(const char * const file) + { + FILE *fp; + char *ret = NULL; +- size_t len = 0; ++ size_t dummy = 0; ++ ssize_t len; + + DBG(dbgprint("reading %s", file)); + +- if (!(fp = fopen(file, "re"))) ++ if (!(fp = fopen(file, "r" UL_CLOEXECSTR))) + return NULL; +- if (getline(&ret, &len, fp) >= 0) { ++ len = getline(&ret, &dummy, fp); ++ if (len >= 0) { + char *nl; + + if (len) +@@ -179,7 +182,7 @@ char *oneline(const char *file) + * /sys/class/tty, the caller has to free the result. + */ + static __attribute__((__malloc__)) +-char *actattr(const char *tty) ++char *actattr(const char * const tty) + { + char *ret, *path; + +@@ -198,7 +201,7 @@ char *actattr(const char *tty) + * /sys/class/tty. + */ + static +-dev_t devattr(const char *tty) ++dev_t devattr(const char * const tty) + { + dev_t dev = 0; + char *path, *value; +@@ -223,42 +226,77 @@ dev_t devattr(const char *tty) + #endif /* __linux__ */ + + /* +- * Search below /dev for the characer device in `dev_t comparedev' variable. ++ * Search below /dev for the character device in `dev_t comparedev' variable. ++ * Note that realpath(3) is used here to avoid not existent devices due the ++ * strdup(3) used in our canonicalize_path()! + */ + static + #ifdef __GNUC__ + __attribute__((__nonnull__,__malloc__,__hot__)) + #endif +-char* scandev(DIR *dir, dev_t comparedev) ++char* scandev(DIR *dir, const dev_t comparedev) + { ++ char path[PATH_MAX]; + char *name = NULL; +- struct dirent *dent; +- int fd; ++ const struct dirent *dent; ++ int len, fd; + + DBG(dbgprint("scanning /dev for %u:%u", major(comparedev), minor(comparedev))); + ++ /* ++ * Try udev links on character devices first. ++ */ ++ if ((len = snprintf(path, sizeof(path), ++ "/dev/char/%u:%u", major(comparedev), minor(comparedev))) > 0 && ++ (size_t)len < sizeof(path)) { ++ ++ name = realpath(path, NULL); ++ if (name) ++ goto out; ++ } ++ + fd = dirfd(dir); + rewinddir(dir); + while ((dent = readdir(dir))) { +- char path[PATH_MAX]; + struct stat st; ++ ++#ifdef _DIRENT_HAVE_D_TYPE ++ if (dent->d_type != DT_UNKNOWN && dent->d_type != DT_CHR) ++ continue; ++#endif + if (fstatat(fd, dent->d_name, &st, 0) < 0) + continue; + if (!S_ISCHR(st.st_mode)) + continue; + if (comparedev != st.st_rdev) + continue; +- if ((size_t)snprintf(path, sizeof(path), "/dev/%s", dent->d_name) >= sizeof(path)) ++ if ((len = snprintf(path, sizeof(path), "/dev/%s", dent->d_name)) < 0 || ++ (size_t)len >= sizeof(path)) + continue; +-#ifdef USE_SULOGIN_EMERGENCY_MOUNT +- if (emergency_flags & MNT_DEVTMPFS) +- mknod(path, S_IFCHR|S_IRUSR|S_IWUSR, comparedev); +-#endif + +- name = canonicalize_path(path); +- break; ++ name = realpath(path, NULL); ++ if (name) ++ goto out; + } + ++#ifdef USE_SULOGIN_EMERGENCY_MOUNT ++ /* ++ * There was no /dev mounted hence and no device was found hence we create our own. ++ */ ++ if (!name && (emergency_flags & MNT_DEVTMPFS)) { ++ ++ if ((len = snprintf(path, sizeof(path), ++ "/dev/tmp-%u:%u", major(comparedev), minor(comparedev))) < 0 || ++ (size_t)len >= sizeof(path)) ++ goto out; ++ ++ if (mknod(path, S_IFCHR|S_IRUSR|S_IWUSR, comparedev) < 0 && errno != EEXIST) ++ goto out; ++ ++ name = realpath(path, NULL); ++ } ++#endif ++out: + return name; + } + +@@ -273,12 +311,12 @@ char* scandev(DIR *dir, dev_t comparedev) + */ + static + #ifdef __GNUC__ +-__attribute__((__nonnull__,__hot__)) ++__attribute__((__hot__)) + #endif +-int append_console(struct list_head *consoles, const char *name) ++int append_console(struct list_head *consoles, const char * const name) + { + struct console *restrict tail; +- struct console *last = NULL; ++ const struct console *last = NULL; + + DBG(dbgprint("appenging %s", name)); + +@@ -300,7 +338,7 @@ int append_console(struct list_head *consoles, const char *name) + tail->flags = 0; + tail->fd = -1; + tail->id = last ? last->id + 1 : 0; +- tail->pid = 0; ++ tail->pid = -1; + memset(&tail->tio, 0, sizeof(tail->tio)); + + return 0; +@@ -319,11 +357,11 @@ static int detect_consoles_from_proc(struct list_head *consoles) + char fbuf[16 + 1]; + DIR *dir = NULL; + FILE *fc = NULL; +- int maj, min, rc = 1; ++ int maj, min, rc = 1, matches; + + DBG(dbgprint("trying /proc")); + +- fc = fopen("/proc/consoles", "re"); ++ fc = fopen("/proc/consoles", "r" UL_CLOEXECSTR); + if (!fc) { + rc = 2; + goto done; +@@ -332,10 +370,12 @@ static int detect_consoles_from_proc(struct list_head *consoles) + if (!dir) + goto done; + +- while (fscanf(fc, "%*s %*s (%16[^)]) %d:%d", fbuf, &maj, &min) == 3) { ++ while ((matches = fscanf(fc, "%*s %*s (%16[^)]) %d:%d", fbuf, &maj, &min)) >= 1) { + char *name; + dev_t comparedev; + ++ if (matches != 3) ++ continue; + if (!strchr(fbuf, 'E')) + continue; + comparedev = makedev(maj, min); +@@ -509,7 +549,7 @@ done: + + #ifdef TIOCGDEV + static int detect_consoles_from_tiocgdev(struct list_head *consoles, +- int fallback, ++ const int fallback, + const char *device) + { + unsigned int devnum; +@@ -579,7 +619,7 @@ done: + * Returns 1 if stdout and stderr should be reconnected and 0 + * otherwise or less than zero on error. + */ +-int detect_consoles(const char *device, int fallback, struct list_head *consoles) ++int detect_consoles(const char *device, const int fallback, struct list_head *consoles) + { + int fd, reconnect = 0, rc; + dev_t comparedev = 0; +@@ -587,7 +627,7 @@ int detect_consoles(const char *device, int fallback, struct list_head *consoles + consoles_debug = getenv("CONSOLES_DEBUG") ? 1 : 0; + + if (!device || !*device) +- fd = dup(fallback); ++ fd = fallback >= 0 ? dup(fallback) : - 1; + else { + fd = open(device, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC); + reconnect = 1; +@@ -602,6 +642,14 @@ int detect_consoles(const char *device, int fallback, struct list_head *consoles + struct stat st; + #ifdef TIOCGDEV + unsigned int devnum; ++#endif ++#ifdef __GNU__ ++ /* ++ * The Hurd always gives st_rdev as 0, which causes this ++ * method to select the first terminal it finds. ++ */ ++ close(fd); ++ goto fallback; + #endif + DBG(dbgprint("trying device/fallback file descriptor")); + +@@ -670,7 +718,7 @@ int detect_consoles(const char *device, int fallback, struct list_head *consoles + #ifdef __linux__ + console: + /* +- * Detection of devices used for Linux system consolei using ++ * Detection of devices used for Linux system console using + * the /proc/consoles API with kernel 2.6.38 and higher. + */ + rc = detect_consoles_from_proc(consoles); +@@ -754,8 +802,7 @@ int main(int argc, char *argv[]) + { + char *name = NULL; + int fd, re; +- LIST_HEAD(consoles); +- struct list_head *p; ++ struct list_head *p, consoles; + + if (argc == 2) { + name = argv[1]; +@@ -768,6 +815,7 @@ int main(int argc, char *argv[]) + if (!name) + errx(EXIT_FAILURE, "usage: %s []\n", program_invocation_short_name); + ++ INIT_LIST_HEAD(&consoles); + re = detect_consoles(name, fd, &consoles); + + list_for_each(p, &consoles) { +diff --git a/login-utils/sulogin.c b/login-utils/sulogin.c +index dd73a1c50..4620ed2da 100644 +--- a/login-utils/sulogin.c ++++ b/login-utils/sulogin.c +@@ -56,8 +56,12 @@ + + #include "c.h" + #include "closestream.h" ++#include "env.h" + #include "nls.h" + #include "pathnames.h" ++#ifdef USE_PLYMOUTH_SUPPORT ++# include "plymouth-ctrl.h" ++#endif + #include "strutils.h" + #include "ttyutils.h" + #include "sulogin-consoles.h" +@@ -66,13 +70,12 @@ + static unsigned int timeout; + static int profile; + static volatile uint32_t openfd; /* Remember higher file descriptors */ +-static volatile uint32_t *usemask; + +-struct sigaction saved_sigint; +-struct sigaction saved_sigtstp; +-struct sigaction saved_sigquit; +-struct sigaction saved_sighup; +-struct sigaction saved_sigchld; ++static struct sigaction saved_sigint; ++static struct sigaction saved_sigtstp; ++static struct sigaction saved_sigquit; ++static struct sigaction saved_sighup; ++static struct sigaction saved_sigchld; + + static volatile sig_atomic_t alarm_rised; + static volatile sig_atomic_t sigchild; +@@ -81,7 +84,12 @@ static volatile sig_atomic_t sigchild; + # define IUCLC 0 + #endif + +-static int locked_account_password(const char *passwd) ++#ifndef WEXITED ++# warning "WEXITED is missing, sulogin may not work as expected" ++# define WEXITED 0 ++#endif ++ ++static int locked_account_password(const char * const passwd) + { + if (passwd && (*passwd == '*' || *passwd == '!')) + return 1; +@@ -93,10 +101,29 @@ static int locked_account_password(const char *passwd) + */ + static void tcinit(struct console *con) + { +- int mode = 0, flags = 0; ++ int flags = 0, mode = 0; + struct termios *tio = &con->tio; +- int fd = con->fd; +- ++ const int fd = con->fd; ++#ifdef USE_PLYMOUTH_SUPPORT ++ struct termios lock; ++ int i = (plymouth_command(MAGIC_PING)) ? PLYMOUTH_TERMIOS_FLAGS_DELAY : 0; ++ if (i) ++ plymouth_command(MAGIC_QUIT); ++ while (i-- > 0) { ++ /* ++ * With plymouth the termios flags become changed after this ++ * function had changed the termios. ++ */ ++ memset(&lock, 0, sizeof(struct termios)); ++ if (ioctl(fd, TIOCGLCKTRMIOS, &lock) < 0) ++ break; ++ if (!lock.c_iflag && !lock.c_oflag && !lock.c_cflag && !lock.c_lflag) ++ break; ++ sleep(1); ++ } ++ memset(&lock, 0, sizeof(struct termios)); ++ ioctl(fd, TIOCSLCKTRMIOS, &lock); ++#endif + errno = 0; + + if (tcgetattr(fd, tio) < 0) { +@@ -189,20 +216,23 @@ setattr: + */ + static void tcfinal(struct console *con) + { +- struct termios *tio; +- int fd; ++ struct termios *tio = &con->tio; ++ const int fd = con->fd; + + if ((con->flags & CON_SERIAL) == 0) { +- setenv("TERM", "linux", 1); ++ xsetenv("TERM", "linux", 1); + return; + } +- if (con->flags & CON_NOTTY) ++ if (con->flags & CON_NOTTY) { ++ xsetenv("TERM", "dumb", 1); + return; ++ } + +- setenv("TERM", "vt102", 1); +- tio = &con->tio; +- fd = con->fd; +- ++#if defined (__s390__) || defined (__s390x__) ++ xsetenv("TERM", "dumb", 1); ++#else ++ xsetenv("TERM", "vt102", 1); ++#endif + tio->c_iflag |= (IXON | IXOFF); + tio->c_lflag |= (ICANON | ISIG | ECHO|ECHOE|ECHOK|ECHOKE); + tio->c_oflag |= OPOST; +@@ -237,11 +267,11 @@ static void tcfinal(struct console *con) + break; + case 1: /* odd parity */ + tio->c_cflag |= PARODD; +- /* fall through */ ++ /* fallthrough */ + case 2: /* even parity */ + tio->c_cflag |= PARENB; + tio->c_iflag |= (INPCK | ISTRIP); +- /* fall through */ ++ /* fallthrough */ + case (1 | 2): /* no parity bit */ + tio->c_cflag &= ~CSIZE; + tio->c_cflag |= CS7; +@@ -466,7 +496,7 @@ static struct passwd *getrootpwent(int try_manually) + warnx(_("%s: no entry for root"), _PATH_SHADOW_PASSWD); + *pwd.pw_passwd = '\0'; + } +- /* locked accont passwords are valid too */ ++ /* locked account passwords are valid too */ + if (!locked_account_password(pwd.pw_passwd) && !valid(pwd.pw_passwd)) { + warnx(_("%s: root password garbled"), _PATH_SHADOW_PASSWD); + *pwd.pw_passwd = '\0'; +@@ -524,22 +554,17 @@ err: + */ + static void setup(struct console *con) + { +- pid_t pid, pgrp, ppgrp, ttypgrp; +- int fd; ++ int fd = con->fd; ++ const pid_t pid = getpid(), pgrp = getpgid(0), ppgrp = ++ getpgid(getppid()), ttypgrp = tcgetpgrp(fd); + + if (con->flags & CON_NOTTY) + return; +- fd = con->fd; + + /* + * Only go through this trouble if the new + * tty doesn't fall in this process group. + */ +- pid = getpid(); +- pgrp = getpgid(0); +- ppgrp = getpgid(getppid()); +- ttypgrp = tcgetpgrp(fd); +- + if (pgrp != ttypgrp && ppgrp != ttypgrp) { + if (pid != getsid(0)) { + if (pid == getpgid(0)) +@@ -575,21 +600,20 @@ static void setup(struct console *con) + * Ask for the password. Note that there is no default timeout as we normally + * skip this during boot. + */ +-static char *getpasswd(struct console *con) ++static const char *getpasswd(struct console *con) + { + struct sigaction sa; + struct termios tty; + static char pass[128], *ptr; + struct chardata *cp; +- char *ret = pass; ++ const char *ret = pass; + unsigned char tc; + char c, ascval; + int eightbit; +- int fd; ++ const int fd = con->fd; + + if (con->flags & CON_NOTTY) + goto out; +- fd = con->fd; + cp = &con->cp; + tty = con->tio; + +@@ -597,6 +621,7 @@ static char *getpasswd(struct console *con) + tty.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP|ISIG); + tc = (tcsetattr(fd, TCSAFLUSH, &tty) == 0); + ++ sigemptyset(&sa.sa_mask); + sa.sa_handler = alrm_handler; + sa.sa_flags = 0; + sigaction(SIGALRM, &sa, NULL); +@@ -615,7 +640,7 @@ static char *getpasswd(struct console *con) + ret = NULL; + goto quit; + } +- usleep(1000); ++ usleep(250000); + continue; + } + ret = (char*)0; +@@ -627,7 +652,7 @@ static char *getpasswd(struct console *con) + case ENOENT: + break; + default: +- warn(_("%s: read failed"), con->tty); ++ warn(_("cannot read %s"), con->tty); + break; + } + goto quit; +@@ -694,8 +719,8 @@ static void sushell(struct passwd *pwd) + { + char shell[PATH_MAX]; + char home[PATH_MAX]; +- char *p; +- char *su_shell; ++ char const *p; ++ char const *su_shell; + + /* + * Set directory and shell. +@@ -731,16 +756,16 @@ static void sushell(struct passwd *pwd) + if (getcwd(home, sizeof(home)) == NULL) + strcpy(home, "/"); + +- setenv("HOME", home, 1); +- setenv("LOGNAME", "root", 1); +- setenv("USER", "root", 1); ++ xsetenv("HOME", home, 1); ++ xsetenv("LOGNAME", "root", 1); ++ xsetenv("USER", "root", 1); + if (!profile) +- setenv("SHLVL","0",1); ++ xsetenv("SHLVL","0",1); + + /* + * Try to execute a shell. + */ +- setenv("SHELL", su_shell, 1); ++ xsetenv("SHELL", su_shell, 1); + unmask_signal(SIGINT, &saved_sigint); + unmask_signal(SIGTSTP, &saved_sigtstp); + unmask_signal(SIGQUIT, &saved_sigquit); +@@ -765,17 +790,21 @@ static void sushell(struct passwd *pwd) + execl(su_shell, shell, NULL); + warn(_("failed to execute %s"), su_shell); + +- setenv("SHELL", "/bin/sh", 1); ++ xsetenv("SHELL", "/bin/sh", 1); + execl("/bin/sh", profile ? "-sh" : "sh", NULL); + warn(_("failed to execute %s"), "/bin/sh"); + } + +-static void usage(FILE *out) ++static void usage(void) + { ++ FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _( + " %s [options] [tty device]\n"), program_invocation_short_name); + ++ fputs(USAGE_SEPARATOR, out); ++ fputs(_("Single-user login.\n"), out); ++ + fputs(USAGE_OPTIONS, out); + fputs(_(" -p, --login-shell start a login shell\n" + " -t, --timeout max time to wait for a password (default: no limit)\n" +@@ -783,32 +812,35 @@ static void usage(FILE *out) + out); + + fputs(USAGE_SEPARATOR, out); +- fputs(USAGE_HELP, out); +- fputs(USAGE_VERSION, out); +- fprintf(out, USAGE_MAN_TAIL("sulogin(8)")); ++ printf(USAGE_HELP_OPTIONS(26)); ++ printf(USAGE_MAN_TAIL("sulogin(8)")); + } + + int main(int argc, char **argv) + { +- LIST_HEAD(consoles); +- struct list_head *ptr; ++ struct list_head *ptr, consoles; + struct console *con; + char *tty = NULL; + struct passwd *pwd; +- int c, status = 0; +- int reconnect = 0; ++ const struct timespec sigwait = { .tv_sec = 0, .tv_nsec = 50000000 }; ++ siginfo_t status = { 0 }; ++ sigset_t set; ++ int c, reconnect = 0; + int opt_e = 0; ++ int wait = 0; + pid_t pid; + + static const struct option longopts[] = { +- { "login-shell", 0, 0, 'p' }, +- { "timeout", 1, 0, 't' }, +- { "force", 0, 0, 'e' }, +- { "help", 0, 0, 'h' }, +- { "version", 0, 0, 'V' }, +- { NULL, 0, 0, 0 } ++ { "login-shell", no_argument, NULL, 'p' }, ++ { "timeout", required_argument, NULL, 't' }, ++ { "force", no_argument, NULL, 'e' }, ++ { "help", no_argument, NULL, 'h' }, ++ { "version", no_argument, NULL, 'V' }, ++ { NULL, 0, NULL, 0 } + }; + ++ INIT_LIST_HEAD(&consoles); ++ + /* + * If we are init we need to set up a own session. + */ +@@ -840,17 +872,16 @@ int main(int argc, char **argv) + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case 'h': +- usage(stdout); ++ usage(); + return EXIT_SUCCESS; + default: +- usage(stderr); +- /* Do not exit! */ ++ /* Do not exit! getopt prints a warning. */ + break; + } + } + + if (geteuid() != 0) +- errx(EXIT_FAILURE, _("only root can run this program.")); ++ errx(EXIT_FAILURE, _("only superuser can run this program")); + + mask_signal(SIGQUIT, SIG_IGN, &saved_sigquit); + mask_signal(SIGTSTP, SIG_IGN, &saved_sigtstp); +@@ -877,7 +908,7 @@ int main(int argc, char **argv) + reconnect = detect_consoles(tty, STDIN_FILENO, &consoles); + + /* +- * If previous stdin was not the speified tty and therefore reconnected ++ * If previous stdin was not the specified tty and therefore reconnected + * to the specified tty also reconnect stdout and stderr. + */ + if (reconnect) { +@@ -900,7 +931,7 @@ int main(int argc, char **argv) + * Get the root password. + */ + if ((pwd = getrootpwent(opt_e)) == NULL) { +- warnx(_("cannot open password database.")); ++ warnx(_("cannot open password database")); + sleep(2); + return EXIT_FAILURE; + } +@@ -923,9 +954,6 @@ int main(int argc, char **argv) + tcinit(con); + } + ptr = (&consoles)->next; +- usemask = (uint32_t*) mmap(NULL, sizeof(uint32_t), +- PROT_READ|PROT_WRITE, +- MAP_ANONYMOUS|MAP_SHARED, -1, 0); + + if (ptr->next == &consoles) { + con = list_entry(ptr, struct console, entry); +@@ -942,7 +970,6 @@ int main(int argc, char **argv) + switch ((con->pid = fork())) { + case 0: + mask_signal(SIGCHLD, SIG_DFL, NULL); +- /* fall through */ + nofork: + setup(con); + while (1) { +@@ -971,9 +998,7 @@ int main(int argc, char **argv) + } + + if (doshell) { +- *usemask |= (1<id); + sushell(pwd); +- *usemask &= ~(1<id); + failed++; + } + +@@ -982,7 +1007,7 @@ int main(int argc, char **argv) + mask_signal(SIGINT, SIG_IGN, &saved_sigint); + + if (failed) { +- fprintf(stderr, _("Can not execute su shell\n\n")); ++ fprintf(stderr, _("cannot execute su shell\n\n")); + break; + } + fprintf(stderr, _("Login incorrect\n\n")); +@@ -997,7 +1022,7 @@ int main(int argc, char **argv) + exit(0); + case -1: + warn(_("fork failed")); +- /* fall through */ ++ /* fallthrough */ + default: + break; + } +@@ -1006,28 +1031,80 @@ int main(int argc, char **argv) + + } while (ptr != &consoles); + +- while ((pid = wait(&status))) { +- if (errno == ECHILD) ++ do { ++ int ret; ++ ++ status.si_pid = 0; ++ ret = waitid(P_ALL, 0, &status, WEXITED); ++ ++ if (ret == 0) + break; +- if (pid < 0) +- continue; +- list_for_each(ptr, &consoles) { +- con = list_entry(ptr, struct console, entry); +- if (con->pid == pid) { +- *usemask &= ~(1<id); ++ if (ret < 0) { ++ if (errno == ECHILD) ++ break; ++ if (errno == EINTR) + continue; +- } +- if (kill(con->pid, 0) < 0) { +- *usemask &= ~(1<id); ++ } ++ ++ errx(EXIT_FAILURE, _("cannot wait on su shell\n\n")); ++ ++ } while (1); ++ ++ list_for_each(ptr, &consoles) { ++ con = list_entry(ptr, struct console, entry); ++ ++ if (con->fd < 0) ++ continue; ++ if (con->pid < 0) ++ continue; ++ if (con->pid == status.si_pid) ++ con->pid = -1; ++ else { ++ kill(con->pid, SIGTERM); ++ wait++; ++ } ++ } ++ ++ sigemptyset(&set); ++ sigaddset(&set, SIGCHLD); ++ ++ do { ++ int signum, ret; ++ ++ if (!wait) ++ break; ++ ++ status.si_pid = 0; ++ ret = waitid(P_ALL, 0, &status, WEXITED|WNOHANG); ++ ++ if (ret < 0) { ++ if (errno == ECHILD) ++ break; ++ if (errno == EINTR) + continue; ++ } ++ ++ if (!ret && status.si_pid > 0) { ++ list_for_each(ptr, &consoles) { ++ con = list_entry(ptr, struct console, entry); ++ ++ if (con->fd < 0) ++ continue; ++ if (con->pid < 0) ++ continue; ++ if (con->pid == status.si_pid) { ++ con->pid = -1; ++ wait--; ++ } + } +- if (*usemask & (1<id)) +- continue; +- kill(con->pid, SIGHUP); +- usleep(5000); +- kill(con->pid, SIGKILL); ++ continue; + } +- } ++ ++ signum = sigtimedwait(&set, NULL, &sigwait); ++ if (signum != SIGCHLD && signum < 0 && errno == EAGAIN) ++ break; ++ ++ } while (1); + + mask_signal(SIGCHLD, SIG_DFL, NULL); + return EXIT_SUCCESS; +-- +2.14.4 + diff --git a/SPECS/util-linux.spec b/SPECS/util-linux.spec index 904ef70..a7ec932 100644 --- a/SPECS/util-linux.spec +++ b/SPECS/util-linux.spec @@ -2,7 +2,7 @@ Summary: A collection of basic system utilities Name: util-linux Version: 2.23.2 -Release: 52%{?dist}.1 +Release: 59%{?dist} License: GPLv2 and GPLv2+ and LGPLv2+ and BSD with advertising and Public Domain Group: System Environment/Base URL: http://en.wikipedia.org/wiki/Util-linux @@ -77,6 +77,7 @@ Requires: audit-libs >= 1.0.6 Requires: libuuid = %{version}-%{release} Requires: libblkid = %{version}-%{release} Requires: libmount = %{version}-%{release} +Requires: libsmartcols = %{version}-%{release} ### Ready for upstream? ### @@ -387,13 +388,53 @@ Patch148: 0148-mkswap-tolerate-ENOTSUP-when-failing-to-relabel.patch Patch149: 0149-libmount-fix-debug-message.patch # -# RHEL7.5.Z +# RHEL7.6 # +# 1543428 - update lsns +Patch150: 0150-lsns-missing-ns-name-is-not-error.patch +Patch151: 0151-lsns-Fix-parser-for-proc-pid-stat-which-is-including.patch +# 1561350 - provide libsmartcols and libsmartcols-devel libraries +Patch152: 0152-libsmartcols-add-basic-tools-necessary-for-new-versi.patch +Patch153: 0153-libsmartcols-backport-upstream-version-v2.32-158-gc0.patch +Patch154: 0154-tests-backport-libsmartcols-tests.patch +# 1581611 - lslogins never shows lock passwords as PWD-LOCK +Patch155: 0155-lslogins-fix-password-verification.patch +# 1566674 - [RFE] Add warnings that have to be acknowledged to the -l flag in umount or remove the option +Patch156: 0156-umount-add-note-about-lazy.patch +# 1566390 - [RHEL-7.5] losetup can not detach all loop devices +Patch157: 0157-losetup-add-info-about-lazy-detach-to-manpage.patch +# 1562125 - "setarch ppc<*>" fails with "Unrecognized architecture" +Patch158: 0158-setarch-Fix-ppc64le-architectures.patch +# 1528567 - [RFE] Provide 'dig-holes' functionality for fallocate +Patch159: 0159-fallocate-backport-v2.32-164-g641af90dc.patch +# 1528959 - rbind creates duplicate entries in /proc/mounts +Patch160: 0160-libmount-fix-mnt_table_is_fs_mounted-for-rbind.patch +# 1538721 - mount man page says sync option has no effect for ext4 +Patch161: 0161-mount-add-ext4-to-some-places-in-man-page.patch +# 1252764 - icrnl is not set when autologin is used +Patch162: 0162-agetty-keep-c_iflags-unmodified-on-autologin.patch +# 1561200 - [RFE] - sulogin: improve support for locked root account +Patch163: 0163-sulogin-don-t-use-strcpy-enlarge-pwd-line-buffer.patch +Patch164: 0164-sulogin-improve-support-for-locked-root-account.patch +Patch165: 0165-sulogin-Always-make-echo-work-after-performing-getpa.patch +Patch166: 0166-sulogin-make-getpasswd-.-return-NULL-on-D.patch +Patch167: 0167-sulogin-bail-out-from-getpasswd-.-on-timeout.patch +# 1566432 - [RHEL-7.5] losetup: /dev/loop1: failed to set up loop device +Patch168: 0168-losetup-keep-f-and-devname-mutually-exclusive.patch +# 1579439 - RHEL 7.5 - lscpu shows wrong sockets, threads on max config (32TB) POWER8 machine (util-linux) +Patch169: 0169-lscpu-fix-mzx-min-MHz-reporting.patch +Patch170: 0170-chcpu-cleanup-return-codes.patch +Patch171: 0171-chcpu-cleanup-stdout-stderr-usage.patch +Patch172: 0172-lscpu-chcpu-Avoid-use-of-the-old-CPU-macros.patch +Patch173: 0173-chcpu-Fix-maximal-number-of-CPUs.patch # 1594681 - [RHEL7.2] blkid does not output swap area -Patch150: 0150-libblkid-minix-Match-minix-superblock-types.patch -Patch151: 0151-libblkid-minix-Sanity-check-superblock-s_state-for-v.patch -Patch152: 0152-libblkid-minix-Use-same-checks-for-version-3.patch - +Patch174: 0174-libblkid-minix-Match-minix-superblock-types.patch +Patch175: 0175-libblkid-minix-Sanity-check-superblock-s_state-for-v.patch +Patch176: 0176-libblkid-minix-Use-same-checks-for-version-3.patch +# 1618711 - exec option in mount unable to override the implicit noexec in user option +Patch177: 0177-mount-append-inverting-options-for-mount.-type-on-us.patch +# 1616264 - Login to emergency shell failed +Patch178: 0178-sulogin-backport-RHEL-8-version.patch %description The util-linux package contains a large variety of low-level system @@ -401,6 +442,27 @@ utilities that are necessary for a Linux system to function. Among others, Util-linux contains the fdisk configuration tool and the login program. +%package -n libsmartcols +Summary: Formatting library for ls-like programs. +Group: Development/Libraries +License: LGPLv2+ + +%description -n libsmartcols +This is library for ls-like terminal programs, part of util-linux. + + +%package -n libsmartcols-devel +Summary: Formatting library for ls-like programs. +Group: Development/Libraries +License: LGPLv2+ +Requires: libsmartcols = %{version}-%{release} +Requires: pkgconfig + +%description -n libsmartcols-devel +This is development library and headers for ls-like terminal programs, +part of util-linux. + + %package -n libmount Summary: Device mounting library Group: Development/Libraries @@ -653,7 +715,7 @@ rmdir ${RPM_BUILD_ROOT}%{_datadir}/getopt ln -sf ../proc/self/mounts %{buildroot}/etc/mtab # remove static libs -rm -f $RPM_BUILD_ROOT%{_libdir}/lib{uuid,blkid,mount}.a +rm -f $RPM_BUILD_ROOT%{_libdir}/lib{uuid,blkid,mount,smartcols}.a # find MO files %find_lang %name @@ -712,6 +774,9 @@ done %post -n libmount -p /sbin/ldconfig %postun -n libmount -p /sbin/ldconfig +%post -n libsmartcols -p /sbin/ldconfig +%postun -n libsmartcols -p /sbin/ldconfig + %pre -n uuidd getent group uuidd >/dev/null || groupadd -r uuidd getent passwd uuidd >/dev/null || \ @@ -1075,6 +1140,18 @@ fi %{compldir}/uuidd +%files -n libsmartcols +%defattr(-,root,root) +%doc libsmartcols/COPYING +%{_libdir}/libsmartcols.so.* + +%files -n libsmartcols-devel +%defattr(-,root,root) +%{_libdir}/libsmartcols.so +%{_includedir}/libsmartcols +%{_libdir}/pkgconfig/smartcols.pc + + %files -n libmount %defattr(-,root,root) %doc libmount/COPYING @@ -1127,8 +1204,38 @@ fi %{_libdir}/pkgconfig/uuid.pc %changelog -* Thu Jul 12 2018 Karel Zak 2.23.2-52.el7_5.1 -- fix #1594681 - blkid does not output swap area +* Mon Aug 20 2018 Karel Zak 2.23.2-59 +- fix #1618711 - exec option in mount unable to override the implicit noexec in user option +- fix #1616264 - Login to emergency shell failed + +* Wed Jul 04 2018 Karel Zak 2.23.2-58 +- fix #1594681 - [RHEL7.2] blkid does not output swap area + +* Thu Jun 14 2018 Karel Zak 2.23.2-57 +- add another ppc aliases to setarch (#1562125) + +* Wed Jun 13 2018 Karel Zak 2.23.2-56 +- backport another chcpu patches for #1579439 + +* Fri Jun 08 2018 Karel Zak 2.23.2-55 +- improve fallocate patch backward compatibility (#1528567) + +* Thu Jun 07 2018 Karel Zak 2.23.2-54 +- fix #1581611 - lslogins never shows lock passwords as PWD-LOCK +- fix #1579439 - RHEL 7.5 - lscpu shows wrong sockets, threads on max config (32TB) POWER8 machine (util-linux) +- fix #1566674 - [RFE] Add warnings that have to be acknowledged to the -l flag in umount or remove the option +- fix #1566390 - [RHEL-7.5] losetup can not detach all loop devices +- fix #1562125 - "setarch ppc<*>" fails with "Unrecognized architecture" +- fix #1528567 - [RFE] Provide 'dig-holes' functionality for fallocate +- fix #1528959 - rbind creates duplicate entries in /proc/mounts +- fix #1538721 - mount man page says sync option has no effect for ext4 +- fix #1252764 - icrnl is not set when autologin is used +- fix #1561200 - [RFE] - sulogin: improve support for locked root account +- fix #1566432 - [RHEL-7.5] losetup: /dev/loop1: failed to set up loop device + +* Thu May 31 2018 Karel Zak 2.23.2-53 +- fix #1543428 - update lsns +- fix #1561350 - provide libsmartcols and libsmartcols-devel libraries * Fri Feb 02 2018 Karel Zak 2.23.2-52 - fix #1534893 - RHEL7: util-linux: mount/unmount ASLR bypass via environment variable in libmount