diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 19:33:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 19:33:30 +0000 |
commit | c61e14d3a8412cd50d98aab604e607692c844c8a (patch) | |
tree | 4925aca0e6b64c8664ea2f3fdfa99a52dc93d5da /misc-utils | |
parent | Adding upstream version 2.39.3. (diff) | |
download | util-linux-c61e14d3a8412cd50d98aab604e607692c844c8a.tar.xz util-linux-c61e14d3a8412cd50d98aab604e607692c844c8a.zip |
Adding upstream version 2.40.upstream/2.40
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'misc-utils')
96 files changed, 7697 insertions, 2885 deletions
diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am index 71548c9..0720dda 100644 --- a/misc-utils/Makemodule.am +++ b/misc-utils/Makemodule.am @@ -92,19 +92,34 @@ lsblk_SOURCES = \ misc-utils/lsblk-properties.c \ misc-utils/lsblk-devtree.c \ misc-utils/lsblk.h -lsblk_LDADD = $(LDADD) libblkid.la libmount.la libcommon.la libsmartcols.la +lsblk_LDADD = $(LDADD) libblkid.la libmount.la libcommon.la \ + libsmartcols.la libtcolors.la lsblk_CFLAGS = $(AM_CFLAGS) -I$(ul_libblkid_incdir) -I$(ul_libmount_incdir) -I$(ul_libsmartcols_incdir) if HAVE_UDEV lsblk_LDADD += -ludev endif endif # BUILD_LSBLK +if BUILD_LIBLASTLOG2 +usrbin_exec_PROGRAMS += lastlog2 +MANPAGES += misc-utils/lastlog2.8 +dist_noinst_DATA += misc-utils/lastlog2.8.adoc +lastlog2_SOURCES = misc-utils/lastlog2.c lib/strutils.c +lastlog2_LDADD = $(LDADD) liblastlog2.la -lsqlite3 +lastlog2_CFLAGS = $(AM_CFLAGS) -I$(ul_liblastlog2_incdir) +systemdsystemunit_DATA += \ + misc-utils/lastlog2-import.service +tmpfiles_DATA += misc-utils/lastlog2-tmpfiles.conf +endif +PATHFILES += misc-utils/lastlog2-import.service \ + misc-utils/lastlog2-tmpfiles.conf + if BUILD_UUIDGEN usrbin_exec_PROGRAMS += uuidgen MANPAGES += misc-utils/uuidgen.1 dist_noinst_DATA += misc-utils/uuidgen.1.adoc uuidgen_SOURCES = misc-utils/uuidgen.c -uuidgen_LDADD = $(LDADD) libuuid.la +uuidgen_LDADD = $(LDADD) libcommon.la libuuid.la uuidgen_CFLAGS = $(AM_CFLAGS) -I$(ul_libuuid_incdir) endif @@ -128,6 +143,7 @@ uuidd_SOURCES = misc-utils/uuidd.c lib/monotonic.c lib/timer.c if HAVE_SYSTEMD uuidd_LDADD += $(SYSTEMD_LIBS) $(SYSTEMD_DAEMON_LIBS) uuidd_CFLAGS += $(SYSTEMD_CFLAGS) $(SYSTEMD_DAEMON_CFLAGS) +tmpfiles_DATA += misc-utils/uuidd-tmpfiles.conf systemdsystemunit_DATA += \ misc-utils/uuidd.service \ misc-utils/uuidd.socket @@ -142,7 +158,8 @@ endif # BUILD_UUIDD PATHFILES += \ misc-utils/uuidd.rc \ misc-utils/uuidd.service \ - misc-utils/uuidd.socket + misc-utils/uuidd.socket \ + misc-utils/uuidd-tmpfiles.conf if BUILD_BLKID sbin_PROGRAMS += blkid @@ -229,6 +246,15 @@ dist_getoptexample_DATA = \ misc-utils/getopt-example.tcsh endif +if BUILD_EXCH +usrbin_exec_PROGRAMS += exch +MANPAGES += misc-utils/exch.1 +dist_noinst_DATA += misc-utils/exch.1.adoc +exch_SOURCES = misc-utils/exch.c +exch_LDADD = $(LDADD) libcommon.la +exch_CFLAGS = $(AM_CFLAGS) +endif + if BUILD_FINCORE usrbin_exec_PROGRAMS += fincore MANPAGES += misc-utils/fincore.1 @@ -254,10 +280,6 @@ dist_noinst_DATA += misc-utils/lsfd.1.adoc lsfd_SOURCES = \ misc-utils/lsfd.c \ misc-utils/lsfd.h \ - misc-utils/lsfd-filter.h \ - misc-utils/lsfd-filter.c \ - misc-utils/lsfd-counter.h \ - misc-utils/lsfd-counter.c \ misc-utils/lsfd-decode-file-flags.c \ misc-utils/lsfd-file.c \ misc-utils/lsfd-cdev.c \ @@ -267,7 +289,7 @@ lsfd_SOURCES = \ misc-utils/lsfd-sock-xinfo.c \ misc-utils/lsfd-unkn.c \ misc-utils/lsfd-fifo.c -lsfd_LDADD = $(LDADD) libsmartcols.la libcommon.la +lsfd_LDADD = $(LDADD) $(MQ_LIBS) libsmartcols.la libcommon.la lsfd_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir) endif @@ -297,3 +319,31 @@ waitpid_SOURCES = misc-utils/waitpid.c waitpid_LDADD = $(LDADD) libcommon.la waitpid_CFLAGS = $(AM_CFLAGS) endif + +if BUILD_ENOSYS + +misc-utils/enosys.c: syscalls.h + +syscalls.h: $(top_srcdir)/tools/all_syscalls + @echo ' GEN $@' + @$(top_srcdir)/tools/all_syscalls $(CC) $(CFLAGS) + +-include syscalls.h.deps +CLEANFILES += syscalls.h syscalls.h.deps + +usrbin_exec_PROGRAMS += enosys +MANPAGES += misc-utils/enosys.1 +dist_noinst_DATA += misc-utils/enosys.1.adoc +enosys_SOURCES = misc-utils/enosys.c +enosys_LDADD = $(LDADD) libcommon.la +enosys_CFLAGS = $(AM_CFLAGS) +endif + +if BUILD_LSCLOCKS +usrbin_exec_PROGRAMS += lsclocks +MANPAGES += misc-utils/lsclocks.1 +dist_noinst_DATA += misc-utils/lsclocks.1.adoc +lsclocks_SOURCES = misc-utils/lsclocks.c +lsclocks_LDADD = $(LDADD) libcommon.la libsmartcols.la +lsclocks_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir) +endif diff --git a/misc-utils/blkid.8 b/misc-utils/blkid.8 index ebf3fcd..f6d67b3 100644 --- a/misc-utils/blkid.8 +++ b/misc-utils/blkid.8 @@ -2,12 +2,12 @@ .\" Title: blkid .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-10-23 +.\" Date: 2024-03-27 .\" Manual: System Administration -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "BLKID" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.TH "BLKID" "8" "2024-03-27" "util\-linux 2.40" "System Administration" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 diff --git a/misc-utils/blkid.c b/misc-utils/blkid.c index 6df4e07..bea5778 100644 --- a/misc-utils/blkid.c +++ b/misc-utils/blkid.c @@ -100,14 +100,14 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_( " -D, --no-part-details don't print info from partition table\n"), out); fputs(USAGE_SEPARATOR, out); - printf(USAGE_HELP_OPTIONS(28)); + fprintf(out, USAGE_HELP_OPTIONS(28)); fputs(USAGE_ARGUMENTS, out); - printf(USAGE_ARG_SIZE(_("<size> and <offset>"))); + fprintf(out, USAGE_ARG_SIZE(_("<size> and <offset>"))); fputs(USAGE_ARG_SEPARATOR, out); fputs(_(" <dev> specify device(s) to probe (default: all devices)\n"), out); - printf(USAGE_MAN_TAIL("blkid(8)")); + fprintf(out, USAGE_MAN_TAIL("blkid(8)")); exit(EXIT_SUCCESS); } diff --git a/misc-utils/cal.1 b/misc-utils/cal.1 index cde26fe..ccc137f 100644 --- a/misc-utils/cal.1 +++ b/misc-utils/cal.1 @@ -2,12 +2,12 @@ .\" Title: cal .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-11-21 +.\" Date: 2024-01-31 .\" Manual: User Commands -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "CAL" "1" "2023-11-21" "util\-linux 2.39.3" "User Commands" +.TH "CAL" "1" "2024-01-31" "util\-linux 2.40" "User Commands" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 diff --git a/misc-utils/cal.c b/misc-utils/cal.c index 21ae6c6..e6f4a6e 100644 --- a/misc-utils/cal.c +++ b/misc-utils/cal.c @@ -692,9 +692,9 @@ static void headers_init(struct cal_control *ctl) for (i = 0; i < DAYS_IN_WEEK; i++) { size_t space_left; - if (i) - strcat(cur_dh++, " "); space_left = sizeof(day_headings) - (cur_dh - day_headings); + if (i && space_left) + strncat(cur_dh++, " ", space_left--); if (space_left <= (ctl->day_width - 1)) break; @@ -1296,8 +1296,8 @@ static void __attribute__((__noreturn__)) usage(void) " %s\n", USAGE_COLORS_DEFAULT); fputs(USAGE_SEPARATOR, out); - printf(USAGE_HELP_OPTIONS(23)); - printf(USAGE_MAN_TAIL("cal(1)")); + fprintf(out, USAGE_HELP_OPTIONS(23)); + fprintf(out, USAGE_MAN_TAIL("cal(1)")); exit(EXIT_SUCCESS); } diff --git a/misc-utils/enosys.1 b/misc-utils/enosys.1 new file mode 100644 index 0000000..28bb049 --- /dev/null +++ b/misc-utils/enosys.1 @@ -0,0 +1,100 @@ +'\" t +.\" Title: enosys +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2024-03-27 +.\" Manual: User Commands +.\" Source: util-linux 2.40 +.\" Language: English +.\" +.TH "ENOSYS" "1" "2024-03-27" "util\-linux 2.40" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +enosys \- utility make syscalls fail with ENOSYS +.SH "SYNOPSIS" +.sp +\fBenosys\fP [\fB\-\-syscall\fP|\fB\-s\fP \fIsyscall\fP] command +.SH "DESCRIPTION" +.sp +\fBenosys\fP is a simple command to execute a child process for which certain +syscalls fail with errno ENOSYS. +.sp +It can be used to test the behavior of applications in the face of missing +syscalls as would happen when running on old kernels. +.SH "OPTIONS" +.sp +\fB\-s\fP, \fB\-\-syscall\fP +.RS 4 +Syscall to block. Can be specified multiple times. +.RE +.sp +\fB\-i\fP, \fB\-\-ioctl\fP +.RS 4 +Ioctl to block. Can be specified multiple times. +.RE +.sp +\fB\-l\fP, \fB\-\-list\fP +.RS 4 +List syscalls known to \fBenosys\fP. +.RE +.sp +\fB\-m\fP, \fB\-\-list\-ioctl\fP +.RS 4 +List ioctls known to \fBenosys\fP. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "EXIT STATUS" +.sp +\fBenosys\fP exits with the status code of the executed process. +The following values have special meanings: +.sp +\fB1\fP +.RS 4 +internal error +.RE +.sp +\fB2\fP +.RS 4 +system does not provide the necessary functionality +.RE +.SH "AUTHORS" +.sp +.MTO "thomas\(att\-8ch.de" "Thomas Weißschuh" "" +.SH "SEE ALSO" +.sp +\fBsyscall\fP(2) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBenosys\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/misc-utils/enosys.1.adoc b/misc-utils/enosys.1.adoc new file mode 100644 index 0000000..a9bc693 --- /dev/null +++ b/misc-utils/enosys.1.adoc @@ -0,0 +1,66 @@ +//po4a: entry man manual += enosys(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: enosys + +== NAME + +enosys - utility make syscalls fail with ENOSYS + +== SYNOPSIS + +*enosys* [*--syscall*|*-s* _syscall_] command + +== DESCRIPTION + +*enosys* is a simple command to execute a child process for which certain +syscalls fail with errno ENOSYS. + +It can be used to test the behavior of applications in the face of missing +syscalls as would happen when running on old kernels. + +== OPTIONS + +*-s*, *--syscall*:: +Syscall to block. Can be specified multiple times. + +*-i*, *--ioctl*:: +Ioctl to block. Can be specified multiple times. + +*-l*, *--list*:: +List syscalls known to *enosys*. + +*-m*, *--list-ioctl*:: +List ioctls known to *enosys*. + +include::man-common/help-version.adoc[] + +== EXIT STATUS + +*enosys* exits with the status code of the executed process. +The following values have special meanings: + +*1*:: +internal error + +*2*:: +system does not provide the necessary functionality + +== AUTHORS + +mailto:thomas@t-8ch.de[Thomas Weißschuh] + +== SEE ALSO + +*syscall*(2) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/enosys.c b/misc-utils/enosys.c new file mode 100644 index 0000000..22096df --- /dev/null +++ b/misc-utils/enosys.c @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2023 Thomas Weißschuh <thomas@t-8ch.de> + * + * This program 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 program is distributed in the hope that it would 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stddef.h> +#include <stdbool.h> +#include <getopt.h> + +#include <linux/unistd.h> +#include <linux/filter.h> +#include <linux/seccomp.h> +#include <linux/audit.h> +#include <sys/prctl.h> +#include <sys/syscall.h> +#include <sys/ioctl.h> + +#include "c.h" +#include "exitcodes.h" +#include "nls.h" +#include "bitops.h" +#include "audit-arch.h" +#include "list.h" +#include "xalloc.h" +#include "strutils.h" + +#define IS_LITTLE_ENDIAN (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + +#define syscall_nr (offsetof(struct seccomp_data, nr)) +#define syscall_arch (offsetof(struct seccomp_data, arch)) +#define _syscall_arg(n) (offsetof(struct seccomp_data, args[n])) +#define syscall_arg_lower32(n) (_syscall_arg(n) + 4 * !IS_LITTLE_ENDIAN) +#define syscall_arg_upper32(n) (_syscall_arg(n) + 4 * IS_LITTLE_ENDIAN) + +static int set_seccomp_filter(const void *prog) +{ +#if defined(__NR_seccomp) && defined(SECCOMP_SET_MODE_FILTER) && defined(SECCOMP_FILTER_FLAG_SPEC_ALLOW) + if (!syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_SPEC_ALLOW, prog)) + return 0; +#endif + + return prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, prog); +} + +struct syscall { + const char *const name; + long number; +}; + +/* When the alias arrays are empty the compiler emits -Wtype-limits warnings. + * Avoid those by going through this function. */ +static inline bool lt(size_t a, size_t b) +{ + return a < b; +} + +static const struct syscall syscalls[] = { +#define UL_SYSCALL(name, nr) { name, nr }, +#include "syscalls.h" +#undef UL_SYSCALL +}; + +static const struct syscall ioctls[] = { + { "FIOCLEX", FIOCLEX }, +}; + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] -- <command>\n"), program_invocation_short_name); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -s, --syscall syscall to block\n"), out); + fputs(_(" -i, --ioctl ioctl to block\n"), out); + fputs(_(" -l, --list list known syscalls\n"), out); + + fputs(USAGE_SEPARATOR, out); + fprintf(out, USAGE_HELP_OPTIONS(25)); + + fprintf(out, USAGE_MAN_TAIL("enosys(1)")); + + exit(EXIT_SUCCESS); +} + +struct blocked_number { + struct list_head head; + long number; +}; + +int main(int argc, char **argv) +{ + int c; + size_t i; + bool found; + static const struct option longopts[] = { + { "syscall", required_argument, NULL, 's' }, + { "ioctl", required_argument, NULL, 'i' }, + { "list", no_argument, NULL, 'l' }, + { "list-ioctl", no_argument, NULL, 'm' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { 0 } + }; + + long blocked_number; + struct blocked_number *blocked; + struct list_head *loop_ctr; + struct list_head blocked_syscalls; + bool blocking_execve = false; + INIT_LIST_HEAD(&blocked_syscalls); + struct list_head blocked_ioctls; + INIT_LIST_HEAD(&blocked_ioctls); + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + while ((c = getopt_long (argc, argv, "+Vhs:i:lm", longopts, NULL)) != -1) { + switch (c) { + case 's': + found = 0; + for (i = 0; lt(i, ARRAY_SIZE(syscalls)); i++) { + if (strcmp(optarg, syscalls[i].name) == 0) { + blocked_number = syscalls[i].number; + found = 1; + break; + } + } + if (!found) + blocked_number = str2num_or_err( + optarg, 10, _("Unknown syscall"), 0, LONG_MAX); + + blocked = xmalloc(sizeof(*blocked)); + blocked->number = blocked_number; + list_add(&blocked->head, &blocked_syscalls); + if (blocked_number == __NR_execve) + blocking_execve = true; + + break; + case 'i': + found = 0; + for (i = 0; lt(i, ARRAY_SIZE(ioctls)); i++) { + if (strcmp(optarg, ioctls[i].name) == 0) { + blocked_number = ioctls[i].number; + found = 1; + break; + } + } + if (!found) + blocked_number = str2num_or_err( + optarg, 10, _("Unknown ioctl"), 0, LONG_MAX); + + blocked = xmalloc(sizeof(*blocked)); + blocked->number = blocked_number; + list_add(&blocked->head, &blocked_ioctls); + + break; + case 'l': + for (i = 0; lt(i, ARRAY_SIZE(syscalls)); i++) + printf("%5ld %s\n", syscalls[i].number, syscalls[i].name); + return EXIT_SUCCESS; + case 'm': + for (i = 0; lt(i, ARRAY_SIZE(ioctls)); i++) + printf("%5ld %s\n", ioctls[i].number, ioctls[i].name); + return EXIT_SUCCESS; + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (optind >= argc) + errtryhelp(EXIT_FAILURE); + + struct sock_filter filter[BPF_MAXINSNS]; + struct sock_filter *f = filter; + +#define INSTR(_instruction) \ + if (f == &filter[ARRAY_SIZE(filter)]) \ + errx(EXIT_FAILURE, _("filter too big")); \ + *f++ = (struct sock_filter) _instruction + + INSTR(BPF_STMT(BPF_LD | BPF_W | BPF_ABS, syscall_arch)); + INSTR(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SECCOMP_ARCH_NATIVE, 1, 0)); + INSTR(BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_TRAP)); + + /* Blocking "execve" normally would also block our own call to + * it and the end of main. To distinguish between our execve + * and the execve to be blocked, compare the environ pointer. + * + * See https://lore.kernel.org/all/CAAnLoWnS74dK9Wq4EQ-uzQ0qCRfSK-dLqh+HCais-5qwDjrVzg@mail.gmail.com/ + */ + if (blocking_execve) { + INSTR(BPF_STMT(BPF_LD | BPF_W | BPF_ABS, syscall_nr)); + INSTR(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_execve, 0, 5)); + INSTR(BPF_STMT(BPF_LD | BPF_W | BPF_ABS, syscall_arg_lower32(2))); + INSTR(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (uint64_t)(uintptr_t) environ, 0, 3)); + INSTR(BPF_STMT(BPF_LD | BPF_W | BPF_ABS, syscall_arg_upper32(2))); + INSTR(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (uint64_t)(uintptr_t) environ >> 32, 0, 1)); + INSTR(BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)); + } + + INSTR(BPF_STMT(BPF_LD | BPF_W | BPF_ABS, syscall_nr)); + + list_for_each(loop_ctr, &blocked_syscalls) { + blocked = list_entry(loop_ctr, struct blocked_number, head); + + INSTR(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, blocked->number, 0, 1)); + INSTR(BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | ENOSYS)); + } + + if (!list_empty(&blocked_ioctls)) { + INSTR(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_ioctl, 1, 0)); + INSTR(BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)); + + list_for_each(loop_ctr, &blocked_ioctls) { + blocked = list_entry(loop_ctr, struct blocked_number, head); + + INSTR(BPF_STMT(BPF_LD | BPF_W | BPF_ABS, syscall_arg_lower32(1))); + INSTR(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (uint64_t) blocked->number, 0, 3)); + INSTR(BPF_STMT(BPF_LD | BPF_W | BPF_ABS, syscall_arg_upper32(1))); + INSTR(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (uint64_t) blocked->number >> 32, 0, 1)); + INSTR(BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | ENOTTY)); + } + } + + INSTR(BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)); + + struct sock_fprog prog = { + .len = f - filter, + .filter = filter, + }; + + /* *SET* below will return EINVAL when either the filter is invalid or + * seccomp is not supported. To distinguish those cases do a *GET* here + */ + if (prctl(PR_GET_SECCOMP) == -1 && errno == EINVAL) + err(EXIT_NOTSUPP, _("Seccomp non-functional")); + + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) + err_nosys(EXIT_FAILURE, _("Could not run prctl(PR_SET_NO_NEW_PRIVS)")); + + if (set_seccomp_filter(&prog)) + err_nosys(EXIT_FAILURE, _("Could not seccomp filter")); + + if (execvp(argv[optind], argv + optind)) + err(EXIT_NOTSUPP, _("Could not exec")); +} diff --git a/misc-utils/exch.1 b/misc-utils/exch.1 new file mode 100644 index 0000000..72b1009 --- /dev/null +++ b/misc-utils/exch.1 @@ -0,0 +1,77 @@ +'\" t +.\" Title: exch +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2024-03-20 +.\" Manual: User Commands +.\" Source: util-linux 2.40 +.\" Language: English +.\" +.TH "EXCH" "1" "2024-03-20" "util\-linux 2.40" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +exch \- atomically exchanges paths between two files +.SH "SYNOPSIS" +.sp +\fBexch\fP \fIoldpath\fP \fInewpath\fP +.SH "DESCRIPTION" +.sp +\fBexch\fP atomically exchanges oldpath and newpath. +\fBexch\fP is a simple command wrapping \fBRENAME_EXCHANGE\fP of \fBrenameat2\fP +system call. +.SH "OPTIONS" +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "EXIT STATUS" +.sp +\fBexch\fP has the following exit status values: +.sp +\fB0\fP +.RS 4 +success +.RE +.sp +\fB1\fP +.RS 4 +unspecified failure +.RE +.SH "AUTHORS" +.sp +.MTO "yamato\(atredhat.com" "Masatake YAMATO" "" +.SH "SEE ALSO" +.sp +\fBrenameat2\fP(2) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBexch\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/misc-utils/exch.1.adoc b/misc-utils/exch.1.adoc new file mode 100644 index 0000000..b1c51d4 --- /dev/null +++ b/misc-utils/exch.1.adoc @@ -0,0 +1,51 @@ +//po4a: entry man manual += exch(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: exch + +== NAME + +exch - atomically exchanges paths between two files + +== SYNOPSIS + +*exch* _oldpath_ _newpath_ + +== DESCRIPTION + +*exch* atomically exchanges oldpath and newpath. +*exch* is a simple command wrapping *RENAME_EXCHANGE* of *renameat2* +system call. + + +== OPTIONS + +include::man-common/help-version.adoc[] + +== EXIT STATUS + +*exch* has the following exit status values: + +*0*:: +success +*1*:: +unspecified failure + +== AUTHORS + +mailto:yamato@redhat.com[Masatake YAMATO] + +== SEE ALSO + +*renameat2*(2) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/exch.c b/misc-utils/exch.c new file mode 100644 index 0000000..93a9f77 --- /dev/null +++ b/misc-utils/exch.c @@ -0,0 +1,95 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This program 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. + * + * Copyright (C) 2023 Red Hat, Inc. All rights reserved. + * Written by Masatake YAMATO <yamato@redhat.com> + * + * exch(1) - a command line interface for RENAME_EXCHANGE of renameat2(2). + */ +#include "c.h" +#include "nls.h" + +#include <fcntl.h> +#include <getopt.h> + +#ifndef HAVE_RENAMEAT2 +# include <sys/syscall.h> +# include <unistd.h> +#endif + +#ifndef RENAME_EXCHANGE +# define RENAME_EXCHANGE (1 << 1) +#endif + +#if !defined(HAVE_RENAMEAT2) && defined(SYS_renameat2) +static inline int renameat2(int olddirfd, const char *oldpath, + int newdirfd, const char *newpath, unsigned int flags) +{ + return syscall (SYS_renameat2, olddirfd, oldpath, newdirfd, newpath, flags); +} +#endif + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] oldpath newpath\n"), program_invocation_short_name); + fputs(USAGE_SEPARATOR, out); + fputs(_("Atomically exchanges paths between two files.\n"), out); + + fputs(USAGE_OPTIONS, out); + fprintf(out, USAGE_HELP_OPTIONS(30)); + + fprintf(out, USAGE_MAN_TAIL("exch(1)")); + + exit(EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + int c; + int rc; + + static const struct option longopts[] = { + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { NULL } + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + while ((c = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1) { + switch (c) { + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (argc - optind < 2) { + warnx(_("too few arguments")); + errtryhelp(EXIT_FAILURE); + } else if (argc - optind > 2) { + warnx(_("too many arguments")); + errtryhelp(EXIT_FAILURE); + } + + rc = renameat2(AT_FDCWD, argv[optind], + AT_FDCWD, argv[optind + 1], RENAME_EXCHANGE); + if (rc) + warn(_("failed to exchange \"%s\" and \"%s\""), + argv[optind], argv[optind + 1]); + + return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/misc-utils/fadvise.1 b/misc-utils/fadvise.1 index 9d26d41..1778da2 100644 --- a/misc-utils/fadvise.1 +++ b/misc-utils/fadvise.1 @@ -2,12 +2,12 @@ .\" Title: fadvise .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-11-21 +.\" Date: 2024-03-20 .\" Manual: User Commands -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "FADVISE" "1" "2023-11-21" "util\-linux 2.39.3" "User Commands" +.TH "FADVISE" "1" "2024-03-20" "util\-linux 2.40" "User Commands" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 @@ -36,7 +36,7 @@ fadvise \- utility to use the posix_fadvise system call \fBfadvise\fP [\fB\-a\fP \fIadvice\fP] [\fB\-o\fP \fIoffset\fP] [\fB\-l\fP \fIlength\fP] \-d \fIfile\-descriptor\fP .SH "DESCRIPTION" .sp -\fBfadvise\fP is a simple command wrapping posix_fadvise system call +\fBfadvise\fP is a simple command wrapping \fBposix_fadvise\fP(2) system call that is for predeclaring an access pattern for file data. .SH "OPTIONS" .sp diff --git a/misc-utils/fadvise.1.adoc b/misc-utils/fadvise.1.adoc index dffbeaa..4206d2c 100644 --- a/misc-utils/fadvise.1.adoc +++ b/misc-utils/fadvise.1.adoc @@ -18,7 +18,7 @@ fadvise - utility to use the posix_fadvise system call == DESCRIPTION -*fadvise* is a simple command wrapping posix_fadvise system call +*fadvise* is a simple command wrapping *posix_fadvise*(2) system call that is for predeclaring an access pattern for file data. == OPTIONS diff --git a/misc-utils/fadvise.c b/misc-utils/fadvise.c index 9606fb4..0b8cbb0 100644 --- a/misc-utils/fadvise.c +++ b/misc-utils/fadvise.c @@ -55,7 +55,7 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" -o, --offset <num> offset for range operations, in bytes\n"), out); fputs(USAGE_SEPARATOR, out); - printf(USAGE_HELP_OPTIONS(23)); + fprintf(out, USAGE_HELP_OPTIONS(23)); fputs(_("\nAvailable values for advice:\n"), out); for (i = 0; i < ARRAY_SIZE(advices); i++) { @@ -63,7 +63,7 @@ static void __attribute__((__noreturn__)) usage(void) advices[i].name); } - printf(USAGE_MAN_TAIL("fadvise(1)")); + fprintf(out, USAGE_MAN_TAIL("fadvise(1)")); exit(EXIT_SUCCESS); } diff --git a/misc-utils/fincore.1 b/misc-utils/fincore.1 index 1d168e0..2fd1e2b 100644 --- a/misc-utils/fincore.1 +++ b/misc-utils/fincore.1 @@ -2,12 +2,12 @@ .\" Title: fincore .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-12-01 +.\" Date: 2024-03-20 .\" Manual: User Commands -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "FINCORE" "1" "2023-12-01" "util\-linux 2.39.3" "User Commands" +.TH "FINCORE" "1" "2024-03-20" "util\-linux 2.40" "User Commands" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 @@ -60,6 +60,11 @@ then omitting on purpose the mention "iB", which is part of these abbreviations. Define output columns. See the \fB\-\-help\fP output to get a list of the currently supported columns. The default list of columns may be extended if \fIlist\fP is specified in the format \fI+list\fP. .RE .sp +\fB\-\-output\-all\fP +.RS 4 +Output all available columns. +.RE +.sp \fB\-r\fP, \fB\-\-raw\fP .RS 4 Produce output in raw format. All potentially unsafe characters are hex\-escaped (\(rsx<code>). @@ -87,6 +92,7 @@ Print version and exit. \fBmincore\fP(2), \fBgetpagesize\fP(2), \fBgetconf\fP(1p) +\fBcachestat\fP(2) .SH "REPORTING BUGS" .sp For bug reports, use the issue tracker at \c diff --git a/misc-utils/fincore.1.adoc b/misc-utils/fincore.1.adoc index 54ed236..73f90c3 100644 --- a/misc-utils/fincore.1.adoc +++ b/misc-utils/fincore.1.adoc @@ -38,6 +38,9 @@ include::man-common/in-bytes.adoc[] Define output columns. See the *--help* output to get a list of the currently supported columns. The default list of columns may be extended if _list_ is specified in the format _{plus}list_. //TRANSLATORS: Keep {plus} untranslated. +*--output-all*:: +Output all available columns. + *-r*, *--raw*:: Produce output in raw format. All potentially unsafe characters are hex-escaped (\x<code>). @@ -55,6 +58,7 @@ mailto:yamato@redhat.com[Masatake YAMATO] *mincore*(2), *getpagesize*(2), *getconf*(1p) +*cachestat*(2) include::man-common/bugreports.adoc[] diff --git a/misc-utils/fincore.c b/misc-utils/fincore.c index ead6f7a..513b811 100644 --- a/misc-utils/fincore.c +++ b/misc-utils/fincore.c @@ -31,6 +31,7 @@ #include "closestream.h" #include "xalloc.h" #include "strutils.h" +#include "blkdev.h" #include "libsmartcols.h" @@ -42,26 +43,70 @@ e.g. 128MB on x86_64. ( = N_PAGES_IN_WINDOW * 4096 ). */ #define N_PAGES_IN_WINDOW ((size_t)(32 * 1024)) +#ifndef HAVE_CACHESTAT + +#ifndef SYS_cachestat +#define SYS_cachestat 451 +#endif + +struct cachestat_range { + uint64_t off; + uint64_t len; +}; + +struct cachestat { + uint64_t nr_cache; + uint64_t nr_dirty; + uint64_t nr_writeback; + uint64_t nr_evicted; + uint64_t nr_recently_evicted; +}; + +static inline int cachestat(unsigned int fd, + const struct cachestat_range *cstat_range, + struct cachestat *cstat, unsigned int flags) +{ + return syscall(SYS_cachestat, fd, cstat_range, cstat, flags); +} + +#endif // HAVE_CACHESTAT struct colinfo { - const char *name; + const char * const name; double whint; int flags; const char *help; + unsigned int pages : 1; }; enum { COL_PAGES, COL_SIZE, COL_FILE, - COL_RES + COL_RES, + COL_DIRTY_PAGES, + COL_DIRTY, + COL_WRITEBACK_PAGES, + COL_WRITEBACK, + COL_EVICTED_PAGES, + COL_EVICTED, + COL_RECENTLY_EVICTED_PAGES, + COL_RECENTLY_EVICTED, }; -static struct colinfo infos[] = { - [COL_PAGES] = { "PAGES", 1, SCOLS_FL_RIGHT, N_("file data resident in memory in pages")}, - [COL_RES] = { "RES", 5, SCOLS_FL_RIGHT, N_("file data resident in memory in bytes")}, - [COL_SIZE] = { "SIZE", 5, SCOLS_FL_RIGHT, N_("size of the file")}, - [COL_FILE] = { "FILE", 4, 0, N_("file name")}, +static const struct colinfo infos[] = { + [COL_PAGES] = { "PAGES", 1, SCOLS_FL_RIGHT, N_("file data resident in memory in pages"), 1}, + [COL_RES] = { "RES", 5, SCOLS_FL_RIGHT, N_("file data resident in memory in bytes")}, + [COL_SIZE] = { "SIZE", 5, SCOLS_FL_RIGHT, N_("size of the file")}, + [COL_FILE] = { "FILE", 4, 0, N_("file name")}, + [COL_DIRTY_PAGES] = { "DIRTY_PAGES", 1, SCOLS_FL_RIGHT, N_("number of dirty pages"), 1}, + [COL_DIRTY] = { "DIRTY", 5, SCOLS_FL_RIGHT, N_("number of dirty bytes")}, + [COL_WRITEBACK_PAGES] = { "WRITEBACK_PAGES", 1, SCOLS_FL_RIGHT, N_("number of pages marked for writeback"), 1}, + [COL_WRITEBACK] = { "WRITEBACK", 5, SCOLS_FL_RIGHT, N_("number of bytes marked for writeback")}, + [COL_EVICTED_PAGES] = { "EVICTED_PAGES", 1, SCOLS_FL_RIGHT, N_("number of evicted pages"), 1}, + [COL_EVICTED] = { "EVICTED", 5, SCOLS_FL_RIGHT, N_("number of evicted bytes")}, + [COL_RECENTLY_EVICTED_PAGES] = { "RECENTLY_EVICTED_PAGES", 1, SCOLS_FL_RIGHT, N_("number of recently evicted pages"), 1}, + [COL_RECENTLY_EVICTED] = { "RECENTLY_EVICTED", 5, SCOLS_FL_RIGHT, N_("number of recently evicted bytes")}, }; static int columns[ARRAY_SIZE(infos) * 2] = {-1}; @@ -76,6 +121,20 @@ struct fincore_control { noheadings : 1, raw : 1, json : 1; + +}; + +struct fincore_state { + const char * const name; + long long unsigned int file_size; + + struct cachestat cstat; + struct { + unsigned int dirty : 1, + writeback : 1, + evicted : 1, + recently_evicted : 1; + } cstat_fields; }; @@ -106,13 +165,49 @@ static const struct colinfo *get_column_info(int num) return &infos[ get_column_id(num) ]; } +static int get_cstat_value(const struct fincore_state *st, int column_id, + uint64_t *value) +{ + switch(column_id) { + case COL_PAGES: + case COL_RES: + *value = st->cstat.nr_cache; + return 1; + case COL_DIRTY_PAGES: + case COL_DIRTY: + if (!st->cstat_fields.dirty) + break; + *value = st->cstat.nr_dirty; + return 1; + case COL_WRITEBACK_PAGES: + case COL_WRITEBACK: + if (!st->cstat_fields.writeback) + *value = st->cstat.nr_writeback; + return 1; + case COL_EVICTED_PAGES: + case COL_EVICTED: + if (!st->cstat_fields.evicted) + break; + *value = st->cstat.nr_evicted; + return 1; + case COL_RECENTLY_EVICTED_PAGES: + case COL_RECENTLY_EVICTED: + if (!st->cstat_fields.recently_evicted) + break; + *value = st->cstat.nr_recently_evicted; + return 1; + default: + assert(0); + } + return 0; +} + static int add_output_data(struct fincore_control *ctl, - const char *name, - off_t file_size, - off_t count_incore) + struct fincore_state *st) { size_t i; char *tmp; + uint64_t value = 0; struct libscols_line *ln; assert(ctl); @@ -124,37 +219,49 @@ static int add_output_data(struct fincore_control *ctl, for (i = 0; i < ncolumns; i++) { int rc = 0; + int column_id = get_column_id(i); + int format_value = 0; - switch(get_column_id(i)) { + switch(column_id) { case COL_FILE: - rc = scols_line_set_data(ln, i, name); + rc = scols_line_set_data(ln, i, st->name); break; - case COL_PAGES: - xasprintf(&tmp, "%jd", (intmax_t) count_incore); - rc = scols_line_refer_data(ln, i, tmp); - break; - case COL_RES: - { - uintmax_t res = (uintmax_t) count_incore * ctl->pagesize; - - if (ctl->bytes) - xasprintf(&tmp, "%ju", res); - else - tmp = size_to_human_string(SIZE_SUFFIX_1LETTER, res); - rc = scols_line_refer_data(ln, i, tmp); - break; - } case COL_SIZE: if (ctl->bytes) - xasprintf(&tmp, "%jd", (intmax_t) file_size); + xasprintf(&tmp, "%jd", (intmax_t) st->file_size); else - tmp = size_to_human_string(SIZE_SUFFIX_1LETTER, file_size); + tmp = size_to_human_string(SIZE_SUFFIX_1LETTER, st->file_size); rc = scols_line_refer_data(ln, i, tmp); break; + case COL_PAGES: + case COL_RES: + case COL_DIRTY_PAGES: + case COL_DIRTY: + case COL_WRITEBACK_PAGES: + case COL_WRITEBACK: + case COL_EVICTED: + case COL_EVICTED_PAGES: + case COL_RECENTLY_EVICTED: + case COL_RECENTLY_EVICTED_PAGES: + format_value = get_cstat_value(st, column_id, &value); + break; default: return -EINVAL; } + if (format_value) { + if (get_column_info(i)->pages) { + xasprintf(&tmp, "%ju", (uintmax_t) value); + } else { + value *= ctl->pagesize; + if (ctl->bytes) + xasprintf(&tmp, "%ju", (uintmax_t) value); + else + tmp = size_to_human_string(SIZE_SUFFIX_1LETTER, value); + } + rc = scols_line_refer_data(ln, i, tmp); + } + if (rc) err(EXIT_FAILURE, _("failed to add output data")); } @@ -164,14 +271,13 @@ static int add_output_data(struct fincore_control *ctl, static int do_mincore(struct fincore_control *ctl, void *window, const size_t len, - const char *name, - off_t *count_incore) + struct fincore_state *st) { static unsigned char vec[N_PAGES_IN_WINDOW]; int n = (len / ctl->pagesize) + ((len % ctl->pagesize)? 1: 0); if (mincore (window, len, vec) < 0) { - warn(_("failed to do mincore: %s"), name); + warn(_("failed to do mincore: %s"), st->name); return -errno; } @@ -180,39 +286,37 @@ static int do_mincore(struct fincore_control *ctl, if (vec[--n] & 0x1) { vec[n] = 0; - (*count_incore)++; + st->cstat.nr_cache++; } } return 0; } -static int fincore_fd (struct fincore_control *ctl, +static int mincore_fd (struct fincore_control *ctl, int fd, - const char *name, - off_t file_size, - off_t *count_incore) + struct fincore_state *st) { size_t window_size = N_PAGES_IN_WINDOW * ctl->pagesize; - off_t file_offset, len; + long long unsigned int file_offset, len; int rc = 0; - for (file_offset = 0; file_offset < file_size; file_offset += len) { + for (file_offset = 0; file_offset < st->file_size; file_offset += len) { void *window = NULL; - len = file_size - file_offset; - if (len >= (off_t) window_size) + len = st->file_size - file_offset; + if (len >= window_size) len = window_size; /* PROT_NONE is enough for Linux, but qemu-user wants PROT_READ */ window = mmap(window, len, PROT_READ, MAP_PRIVATE, fd, file_offset); if (window == MAP_FAILED) { rc = -EINVAL; - warn(_("failed to do mmap: %s"), name); + warn(_("failed to do mmap: %s"), st->name); break; } - rc = do_mincore(ctl, window, len, name, count_incore); + rc = do_mincore(ctl, window, len, st); if (rc) break; @@ -222,33 +326,63 @@ static int fincore_fd (struct fincore_control *ctl, return rc; } +static int fincore_fd (struct fincore_control *ctl, + int fd, + struct fincore_state *st) +{ + int rc; + const struct cachestat_range cstat_range = { 0 }; + + rc = cachestat(fd, &cstat_range, &st->cstat, 0); + if (!rc) { + st->cstat_fields.dirty = 1; + st->cstat_fields.writeback = 1; + st->cstat_fields.evicted = 1; + st->cstat_fields.recently_evicted = 1; + return 0; + } + + if (errno != ENOSYS) + warn(_("failed to do cachestat: %s"), st->name); + + return mincore_fd(ctl, fd, st); +} + /* * Returns: <0 on error, 0 success, 1 ignore. */ static int fincore_name(struct fincore_control *ctl, - const char *name, - struct stat *sb, - off_t *count_incore) + struct fincore_state *st) { int fd; int rc = 0; + struct stat sb; - if ((fd = open (name, O_RDONLY)) < 0) { - warn(_("failed to open: %s"), name); + if ((fd = open (st->name, O_RDONLY)) < 0) { + warn(_("failed to open: %s"), st->name); return -errno; } - if (fstat (fd, sb) < 0) { - warn(_("failed to do fstat: %s"), name); + if (fstat (fd, &sb) < 0) { + warn(_("failed to do fstat: %s"), st->name); close (fd); return -errno; } + st->file_size = sb.st_size; - if (S_ISDIR(sb->st_mode)) - rc = 1; /* ignore */ + if (S_ISBLK(sb.st_mode)) { + rc = blkdev_get_size(fd, &st->file_size); + if (rc) + warn(_("failed ioctl to get size: %s"), st->name); + } else if (S_ISREG(sb.st_mode)) { + st->file_size = sb.st_size; + } else { + rc = 1; /* ignore things like symlinks + * and directories*/ + } - else if (sb->st_size) - rc = fincore_fd(ctl, fd, name, sb->st_size, count_incore); + if (!rc) + rc = fincore_fd(ctl, fd, st); close (fd); return rc; @@ -267,17 +401,18 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" -b, --bytes print sizes in bytes rather than in human readable format\n"), out); fputs(_(" -n, --noheadings don't print headings\n"), out); fputs(_(" -o, --output <list> output columns\n"), out); + fputs(_(" --output-all output all columns\n"), out); fputs(_(" -r, --raw use raw output format\n"), out); fputs(USAGE_SEPARATOR, out); - printf(USAGE_HELP_OPTIONS(23)); + fprintf(out, USAGE_HELP_OPTIONS(23)); - fprintf(out, USAGE_COLUMNS); + fputs(USAGE_COLUMNS, out); for (i = 0; i < ARRAY_SIZE(infos); i++) - fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help)); + fprintf(out, " %22s %s\n", infos[i].name, _(infos[i].help)); - printf(USAGE_MAN_TAIL("fincore(1)")); + fprintf(out, USAGE_MAN_TAIL("fincore(1)")); exit(EXIT_SUCCESS); } @@ -293,10 +428,14 @@ int main(int argc, char ** argv) .pagesize = getpagesize() }; + enum { + OPT_OUTPUT_ALL = CHAR_MAX + 1 + }; static const struct option longopts[] = { { "bytes", no_argument, NULL, 'b' }, { "noheadings", no_argument, NULL, 'n' }, { "output", required_argument, NULL, 'o' }, + { "output-all", no_argument, NULL, OPT_OUTPUT_ALL }, { "version", no_argument, NULL, 'V' }, { "help", no_argument, NULL, 'h' }, { "json", no_argument, NULL, 'J' }, @@ -320,6 +459,10 @@ int main(int argc, char ** argv) case 'o': outarg = optarg; break; + case OPT_OUTPUT_ALL: + for (ncolumns = 0; ncolumns < ARRAY_SIZE(infos); ncolumns++) + columns[ncolumns] = ncolumns; + break; case 'J': ctl.json = 1; break; @@ -390,13 +533,13 @@ int main(int argc, char ** argv) } for(; optind < argc; optind++) { - char *name = argv[optind]; - struct stat sb; - off_t count_incore = 0; + struct fincore_state st = { + .name = argv[optind], + }; - switch (fincore_name(&ctl, name, &sb, &count_incore)) { + switch (fincore_name(&ctl, &st)) { case 0: - add_output_data(&ctl, name, sb.st_size, count_incore); + add_output_data(&ctl, &st); break; case 1: break; /* ignore */ diff --git a/misc-utils/findfs.8 b/misc-utils/findfs.8 index 9875373..a1c9661 100644 --- a/misc-utils/findfs.8 +++ b/misc-utils/findfs.8 @@ -2,12 +2,12 @@ .\" Title: findfs .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-10-23 +.\" Date: 2024-01-31 .\" Manual: System Administration -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "FINDFS" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.TH "FINDFS" "8" "2024-01-31" "util\-linux 2.40" "System Administration" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 diff --git a/misc-utils/findfs.c b/misc-utils/findfs.c index 0997e1b..7b32dbd 100644 --- a/misc-utils/findfs.c +++ b/misc-utils/findfs.c @@ -32,8 +32,8 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_("Find a filesystem by label or UUID.\n"), out); fputs(USAGE_OPTIONS, out); - printf(USAGE_HELP_OPTIONS(16)); - printf(USAGE_MAN_TAIL("findfs(8)")); + fprintf(out, USAGE_HELP_OPTIONS(16)); + fprintf(out, USAGE_MAN_TAIL("findfs(8)")); exit(FINDFS_SUCCESS); } @@ -76,4 +76,3 @@ int main(int argc, char **argv) puts(dev); return FINDFS_SUCCESS; } - diff --git a/misc-utils/findmnt-verify.c b/misc-utils/findmnt-verify.c index 3543c36..7281c8d 100644 --- a/misc-utils/findmnt-verify.c +++ b/misc-utils/findmnt-verify.c @@ -301,7 +301,7 @@ static int add_filesystem(struct verify_context *vfy, const char *name) if (vfy->fs_alloc == 0 || vfy->fs_num + 1 <= vfy->fs_alloc) { vfy->fs_alloc = ((vfy->fs_alloc + 1 + MYCHUNK) / MYCHUNK) * MYCHUNK; - vfy->fs_ary = xrealloc(vfy->fs_ary, vfy->fs_alloc * sizeof(char *)); + vfy->fs_ary = xreallocarray(vfy->fs_ary, vfy->fs_alloc, sizeof(char *)); } vfy->fs_ary[vfy->fs_num] = xstrdup(name); diff --git a/misc-utils/findmnt.8 b/misc-utils/findmnt.8 index d5c5d4f..56fe1f1 100644 --- a/misc-utils/findmnt.8 +++ b/misc-utils/findmnt.8 @@ -2,12 +2,12 @@ .\" Title: findmnt .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-12-01 +.\" Date: 2024-03-20 .\" Manual: System Administration -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "FINDMNT" "8" "2023-12-01" "util\-linux 2.39.3" "System Administration" +.TH "FINDMNT" "8" "2024-03-20" "util\-linux 2.40" "System Administration" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 @@ -80,14 +80,9 @@ Do not canonicalize paths at all. This option affects the comparing of paths and Canonicalize all printed paths. .RE .sp -\fB\-\-deleted\fP -.RS 4 -Print filesystems where target (mountpoint) is marked as deleted by kernel. -.RE -.sp \fB\-D\fP, \fB\-\-df\fP .RS 4 -Imitate the output of \fBdf\fP(1). This option is equivalent to \fB\-o SOURCE,FSTYPE,SIZE,USED,AVAIL,USE%,TARGET\fP but excludes all pseudo filesystems. Use \fB\-\-all\fP to print all filesystems. +Imitate the output of \fBdf\fP(1). This option is equivalent to \fB\-o SOURCE,FSTYPE,SIZE,USED,AVAIL,USE%,TARGET\fP but excludes all pseudo filesystems. Use \fB\-\-all\fP to print all filesystems. See also \fB\-I\fP, \fB\-\-dfi\fP options. .RE .sp \fB\-d\fP, \fB\-\-direction\fP \fIword\fP @@ -110,6 +105,16 @@ Search in an alternative file. If used with \fB\-\-fstab\fP, \fB\-\-mtab\fP or \ Print the first matching filesystem only. .RE .sp +\fB\-H\fP, \fB\-\-list\-columns\fP +.RS 4 +List the available columns, use with \fB\-\-json\fP or \fB\-\-raw\fP to get output in machine\-readable format. +.RE +.sp +\fB\-I\fP, \fB\-\-dfi\fP +.RS 4 +Imitate the output of \fBdf\fP(1) with its \fB\-i\fP option. This option is equivalent to \fB\-o SOURCE,FSTYPE,INO.TOTAL,INO.USED,INO.AVAIL,INO.USE%,TARGET\fP but excludes all pseudo filesystems. Use \fB\-\-all\fP to print all filesystems. +.RE +.sp \fB\-i\fP, \fB\-\-invert\fP .RS 4 Invert the sense of matching. @@ -170,6 +175,8 @@ Output almost all available columns. The columns that require \fB\-\-poll\fP are \fB\-P\fP, \fB\-\-pairs\fP .RS 4 Produce output in the form of key="value" pairs. All potentially unsafe value characters are hex\-escaped (\(rsx<code>). See also option \fB\-\-shell\fP. +.sp +Note that SOURCES column, use multi\-line cells. In these cases, the column use an array\-like formatting in the output, for example \fBname=("aaa" "bbb" "ccc")\fP. .RE .sp \fB\-p\fP, \fB\-\-poll\fP[\fI=list\fP] @@ -214,6 +221,8 @@ Print recursively all submounts for the selected filesystems. The restrictions d \fB\-r\fP, \fB\-\-raw\fP .RS 4 Use raw output format. All potentially unsafe characters are hex\-escaped (\(rsx<code>). +.sp +Note that column SOURCES, use multi\-line cells. In these cases, the column may produce more strings on the same line. .RE .sp \fB\-\-real\fP diff --git a/misc-utils/findmnt.8.adoc b/misc-utils/findmnt.8.adoc index 0cb7f0e..941eb6c 100644 --- a/misc-utils/findmnt.8.adoc +++ b/misc-utils/findmnt.8.adoc @@ -48,11 +48,8 @@ Do not canonicalize paths at all. This option affects the comparing of paths and *-c*, *--canonicalize*:: Canonicalize all printed paths. -*--deleted*:: -Print filesystems where target (mountpoint) is marked as deleted by kernel. - *-D*, *--df*:: -Imitate the output of *df*(1). This option is equivalent to *-o SOURCE,FSTYPE,SIZE,USED,AVAIL,USE%,TARGET* but excludes all pseudo filesystems. Use *--all* to print all filesystems. +Imitate the output of *df*(1). This option is equivalent to *-o SOURCE,FSTYPE,SIZE,USED,AVAIL,USE%,TARGET* but excludes all pseudo filesystems. Use *--all* to print all filesystems. See also *-I*, *--dfi* options. *-d*, *--direction* _word_:: The search direction, either *forward* or *backward*. @@ -66,6 +63,12 @@ Search in an alternative file. If used with *--fstab*, *--mtab* or *--kernel*, t *-f*, *--first-only*:: Print the first matching filesystem only. +*-H*, *--list-columns*:: +List the available columns, use with *--json* or *--raw* to get output in machine-readable format. + +*-I*, *--dfi*:: +Imitate the output of *df*(1) with its *-i* option. This option is equivalent to *-o SOURCE,FSTYPE,INO.TOTAL,INO.USED,INO.AVAIL,INO.USE%,TARGET* but excludes all pseudo filesystems. Use *--all* to print all filesystems. + *-i*, *--invert*:: Invert the sense of matching. @@ -105,6 +108,8 @@ Output almost all available columns. The columns that require *--poll* are not i *-P*, *--pairs*:: Produce output in the form of key="value" pairs. All potentially unsafe value characters are hex-escaped (\x<code>). See also option *--shell*. ++ +Note that SOURCES column, use multi-line cells. In these cases, the column use an array-like formatting in the output, for example *name=("aaa" "bbb" "ccc")*. *-p*, *--poll*[_=list_]:: Monitor changes in the _/proc/self/mountinfo_ file. Supported actions are: mount, umount, remount and move. More than one action may be specified in a comma-separated list. All actions are monitored by default. @@ -131,6 +136,8 @@ Print recursively all submounts for the selected filesystems. The restrictions d *-r*, *--raw*:: Use raw output format. All potentially unsafe characters are hex-escaped (\x<code>). ++ +Note that column SOURCES, use multi-line cells. In these cases, the column may produce more strings on the same line. *--real*:: Print only real filesystems. diff --git a/misc-utils/findmnt.c b/misc-utils/findmnt.c index 733bbc1..cc397da 100644 --- a/misc-utils/findmnt.c +++ b/misc-utils/findmnt.c @@ -20,6 +20,7 @@ */ #include <stdio.h> #include <stdlib.h> +#include <stdbool.h> #include <errno.h> #include <unistd.h> #include <getopt.h> @@ -48,6 +49,7 @@ #include "optutils.h" #include "mangle.h" #include "buffer.h" +#include "column-list-table.h" #include "findmnt.h" @@ -60,6 +62,10 @@ enum { COL_FSTYPE, COL_FS_OPTIONS, COL_ID, + COL_INO_AVAIL, + COL_INO_TOTAL, + COL_INO_USED, + COL_INO_USEPERC, COL_LABEL, COL_MAJMIN, COL_OLD_OPTIONS, @@ -90,7 +96,7 @@ enum { /* column names */ struct colinfo { - const char *name; /* header */ + const char * const name; /* header */ double whint; /* width hint (N < 1 is in percent of termwidth) */ int flags; /* libsmartcols flags */ const char *help; /* column description */ @@ -101,12 +107,16 @@ struct colinfo { /* columns descriptions (don't use const, this is writable) */ static struct colinfo infos[] = { [COL_ACTION] = { "ACTION", 10, SCOLS_FL_STRICTWIDTH, N_("action detected by --poll") }, - [COL_AVAIL] = { "AVAIL", 5, SCOLS_FL_RIGHT, N_("filesystem size available") }, + [COL_AVAIL] = { "AVAIL", 5, SCOLS_FL_RIGHT, N_("filesystem size available, use <number> if --bytes is given") }, [COL_FREQ] = { "FREQ", 1, SCOLS_FL_RIGHT, N_("dump(8) period in days [fstab only]") }, [COL_FSROOT] = { "FSROOT", 0.25, SCOLS_FL_NOEXTREMES, N_("filesystem root") }, [COL_FSTYPE] = { "FSTYPE", 0.10, SCOLS_FL_TRUNC, N_("filesystem type") }, [COL_FS_OPTIONS] = { "FS-OPTIONS", 0.10, SCOLS_FL_TRUNC, N_("FS specific mount options") }, [COL_ID] = { "ID", 2, SCOLS_FL_RIGHT, N_("mount ID") }, + [COL_INO_AVAIL] = { "INO.AVAIL", 5, SCOLS_FL_RIGHT, N_("number of available inodes") }, + [COL_INO_TOTAL] = { "INO.TOTAL", 5, SCOLS_FL_RIGHT, N_("total number of inodes") }, + [COL_INO_USED] = { "INO.USED", 5, SCOLS_FL_RIGHT, N_("number of used inodes") }, + [COL_INO_USEPERC] = { "INO.USE%", 3, SCOLS_FL_RIGHT, N_("percentage of INO.USED divided by INO.TOTAL") }, [COL_LABEL] = { "LABEL", 0.10, 0, N_("filesystem label") }, [COL_MAJMIN] = { "MAJ:MIN", 6, 0, N_("major:minor device number") }, [COL_OLD_OPTIONS] = { "OLD-OPTIONS", 0.10, SCOLS_FL_TRUNC, N_("old mount options saved by --poll") }, @@ -118,12 +128,12 @@ static struct colinfo infos[] = { [COL_PARTUUID] = { "PARTUUID", 36, 0, N_("partition UUID") }, [COL_PASSNO] = { "PASSNO", 1, SCOLS_FL_RIGHT, N_("pass number on parallel fsck(8) [fstab only]") }, [COL_PROPAGATION] = { "PROPAGATION", 0.10, 0, N_("VFS propagation flags") }, - [COL_SIZE] = { "SIZE", 5, SCOLS_FL_RIGHT, N_("filesystem size") }, + [COL_SIZE] = { "SIZE", 5, SCOLS_FL_RIGHT, N_("filesystem size, use <number> if --bytes is given") }, [COL_SOURCES] = { "SOURCES", 0.25, SCOLS_FL_WRAP, N_("all possible source devices") }, [COL_SOURCE] = { "SOURCE", 0.25, SCOLS_FL_NOEXTREMES, N_("source device") }, [COL_TARGET] = { "TARGET", 0.30, SCOLS_FL_TREE| SCOLS_FL_NOEXTREMES, N_("mountpoint") }, [COL_TID] = { "TID", 4, SCOLS_FL_RIGHT, N_("task ID") }, - [COL_USED] = { "USED", 5, SCOLS_FL_RIGHT, N_("filesystem size used") }, + [COL_USED] = { "USED", 5, SCOLS_FL_RIGHT, N_("filesystem size used, use <number> if --bytes is given") }, [COL_USEPERC] = { "USE%", 3, SCOLS_FL_RIGHT, N_("filesystem use percentage") }, [COL_UUID] = { "UUID", 36, 0, N_("filesystem UUID") }, [COL_VFS_OPTIONS] = { "VFS-OPTIONS", 0.20, SCOLS_FL_TRUNC, N_("VFS specific mount options") } @@ -475,6 +485,7 @@ static char *get_vfs_attr(struct libmnt_fs *fs, int sizetype) struct statvfs buf; uint64_t vfs_attr = 0; char *sizestr; + bool no_bytes = false; if (statvfs(mnt_fs_get_target(fs), &buf) != 0) return NULL; @@ -497,11 +508,38 @@ static char *get_vfs_attr(struct libmnt_fs *fs, int sizetype) (double)(buf.f_blocks - buf.f_bfree) / buf.f_blocks * 100); return sizestr; + case COL_INO_AVAIL: + /* Quoted from startvfs(3): + * + * Under Linux, f_favail is always the same as + * f_ffree, and there's no way for a filesystem to + * report otherwise. This is not an issue, since no + * filesystems with an inode root reservation exist. + */ + no_bytes = true; + vfs_attr = buf.f_ffree; + break; + case COL_INO_TOTAL: + no_bytes = true; + vfs_attr = buf.f_files; + break; + case COL_INO_USED: + no_bytes = true; + vfs_attr = buf.f_files - buf.f_ffree; + break; + case COL_INO_USEPERC: + if (buf.f_files == 0) + return xstrdup("-"); + + xasprintf(&sizestr, "%.0f%%", + (double)(buf.f_files - buf.f_ffree) / + buf.f_files * 100); + return sizestr; } if (!vfs_attr) sizestr = xstrdup("0"); - else if (flags & FL_BYTES) + else if ((flags & FL_BYTES) || no_bytes) xasprintf(&sizestr, "%ju", vfs_attr); else sizestr = size_to_human_string(SIZE_SUFFIX_1LETTER, vfs_attr); @@ -511,7 +549,7 @@ static char *get_vfs_attr(struct libmnt_fs *fs, int sizetype) /* reads sources from libmount/libblkid */ -static char *get_data_col_sources(struct libmnt_fs *fs, int evaluate) +static char *get_data_col_sources(struct libmnt_fs *fs, int evaluate, size_t *datasiz) { const char *tag = NULL, *p = NULL; int i = 0; @@ -565,14 +603,14 @@ static char *get_data_col_sources(struct libmnt_fs *fs, int evaluate) if (!dev) continue; if (i != 0) - ul_buffer_append_data(&buf, "\n", 1); + ul_buffer_append_data(&buf, "\0", 1); ul_buffer_append_string(&buf, blkid_dev_devname(dev)); i++; } blkid_dev_iterate_end(iter); free(val); - return ul_buffer_get_data(&buf, NULL, NULL); + return ul_buffer_get_data(&buf, datasiz, NULL); nothing: free(val); @@ -581,7 +619,7 @@ nothing: /* reads FS data from libmount */ -static char *get_data(struct libmnt_fs *fs, int num) +static char *get_data(struct libmnt_fs *fs, int num, size_t *datasiz) { char *str = NULL; int col_id = get_column_id(num); @@ -589,7 +627,7 @@ static char *get_data(struct libmnt_fs *fs, int num) switch (col_id) { case COL_SOURCES: /* print all devices with the same tag (LABEL, UUID) */ - str = get_data_col_sources(fs, flags & FL_EVALUATE); + str = get_data_col_sources(fs, flags & FL_EVALUATE, datasiz); if (str) break; @@ -672,6 +710,10 @@ static char *get_data(struct libmnt_fs *fs, int num) case COL_AVAIL: case COL_USED: case COL_USEPERC: + case COL_INO_TOTAL: + case COL_INO_AVAIL: + case COL_INO_USED: + case COL_INO_USEPERC: str = get_vfs_attr(fs, col_id); break; case COL_FSROOT: @@ -730,7 +772,8 @@ static char *get_data(struct libmnt_fs *fs, int num) static char *get_tabdiff_data(struct libmnt_fs *old_fs, struct libmnt_fs *new_fs, int change, - int num) + int num, + size_t *datasiz) { char *str = NULL; @@ -769,14 +812,30 @@ static char *get_tabdiff_data(struct libmnt_fs *old_fs, break; default: if (new_fs) - str = get_data(new_fs, num); + str = get_data(new_fs, num, datasiz); else - str = get_data(old_fs, num); + str = get_data(old_fs, num, datasiz); break; } return str; } +static void set_line_data(struct libscols_line *ln, size_t i, char *data, size_t datasiz) +{ + int rc; + struct libscols_cell *ce; + + ce = scols_line_get_cell(ln, i); + if (!ce) + return; + if (datasiz) + rc = scols_cell_refer_memory(ce, data, datasiz); + else + rc = scols_cell_refer_data(ce, data); + if (rc) + err(EXIT_FAILURE, _("failed to add output data")); +} + /* adds one line to the output @tab */ static struct libscols_line *add_line(struct libscols_table *table, struct libmnt_fs *fs, struct libscols_line *parent) @@ -788,8 +847,11 @@ static struct libscols_line *add_line(struct libscols_table *table, struct libmn err(EXIT_FAILURE, _("failed to allocate output line")); for (i = 0; i < ncolumns; i++) { - if (scols_line_refer_data(line, i, get_data(fs, i))) - err(EXIT_FAILURE, _("failed to add output data")); + size_t datasiz = 0; + char *data = get_data(fs, i, &datasiz); + + if (data) + set_line_data(line, i, data, datasiz); } scols_line_set_userdata(line, fs); @@ -806,9 +868,11 @@ static struct libscols_line *add_tabdiff_line(struct libscols_table *table, stru err(EXIT_FAILURE, _("failed to allocate output line")); for (i = 0; i < ncolumns; i++) { - if (scols_line_refer_data(line, i, - get_tabdiff_data(old_fs, new_fs, change, i))) - err(EXIT_FAILURE, _("failed to add output data")); + size_t datasiz = 0; + char *data = get_tabdiff_data(old_fs, new_fs, change, i, &datasiz); + + if (data) + set_line_data(line, i, data, datasiz); } return line; @@ -901,7 +965,7 @@ static int parser_errcb(struct libmnt_table *tb __attribute__ ((__unused__)), static char **append_tabfile(char **files, int *nfiles, char *filename) { - files = xrealloc(files, sizeof(char *) * (*nfiles + 1)); + files = xreallocarray(files, *nfiles + 1, sizeof(char *)); files[(*nfiles)++] = filename; return files; } @@ -1272,9 +1336,7 @@ static int poll_table(struct libmnt_table *tb, const char *tabfile, if (count) { rc = scols_table_print_range(table, NULL, NULL); - if (rc == 0) - fputc('\n', scols_table_get_stream(table)); - fflush(stdout); + fflush(scols_table_get_stream(table)); if (rc) goto done; } @@ -1310,10 +1372,36 @@ static int uniq_fs_target_cmp( return !mnt_fs_match_target(a, mnt_fs_get_target(b), cache); } +static int get_column_json_type(int id, int scols_flags, int *multi) +{ + switch (id) { + case COL_SIZE: + case COL_AVAIL: + case COL_USED: + if (multi) + *multi = 1; + if (!(flags & FL_BYTES)) + break; + /* fallthrough */ + case COL_ID: + case COL_PARENT: + case COL_FREQ: + case COL_PASSNO: + case COL_TID: + return SCOLS_JSON_NUMBER; + + default: + if (scols_flags & SCOLS_FL_WRAP) + return SCOLS_JSON_ARRAY_STRING; + break; + } + + return SCOLS_JSON_STRING; /* default */ +} + static void __attribute__((__noreturn__)) usage(void) { FILE *out = stdout; - size_t i; fputs(USAGE_HEADER, out); fprintf(out, _( @@ -1348,13 +1436,14 @@ static void __attribute__((__noreturn__)) usage(void) " to device names\n"), out); fputs(_(" -F, --tab-file <path> alternative file for -s, -m or -k options\n"), out); fputs(_(" -f, --first-only print the first found filesystem only\n"), out); + fputs(_(" -I, --dfi imitate the output of df(1) with -i option\n"), out); fputs(_(" -i, --invert invert the sense of matching\n"), out); fputs(_(" -J, --json use JSON output format\n"), out); fputs(_(" -l, --list use list format output\n"), out); fputs(_(" -N, --task <tid> use alternative namespace (/proc/<tid>/mountinfo file)\n"), out); fputs(_(" -n, --noheadings don't print column headings\n"), out); fputs(_(" -O, --options <list> limit the set of filesystems by mount options\n"), out); - fputs(_(" -o, --output <list> the output columns to be shown\n"), out); + fputs(_(" -o, --output <list> output columns (see --list-columns)\n"), out); fputs(_(" --output-all output all available columns\n"), out); fputs(_(" -P, --pairs use key=\"value\" output format\n"), out); fputs(_(" --pseudo print only pseudo-filesystems\n"), out); @@ -1379,23 +1468,45 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" --vfs-all print all VFS options\n"), out); fputs(USAGE_SEPARATOR, out); - printf(USAGE_HELP_OPTIONS(24)); + fputs(_(" -H, --list-columns list the available columns\n"), out); + fprintf(out, USAGE_HELP_OPTIONS(24)); - fputs(USAGE_COLUMNS, out); - for (i = 0; i < ARRAY_SIZE(infos); i++) - fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help)); + fprintf(out, USAGE_MAN_TAIL("findmnt(8)")); + + exit(EXIT_SUCCESS); +} + +static void __attribute__((__noreturn__)) list_colunms(void) +{ + size_t i; + struct libscols_table *tb = xcolumn_list_table_new("findmnt-columns", stdout, + flags & FL_RAW, + flags & FL_JSON); + + for (i = 0; i < ARRAY_SIZE(infos); i++) { + const struct colinfo *ci = &infos[i]; + int multi = 0; + int json = get_column_json_type(i, ci->flags, &multi); + + xcolumn_list_table_append_line(tb, ci->name, + multi ? -1 : json, + multi ? "<string|number>" : NULL, + _(ci->help)); + } - printf(USAGE_MAN_TAIL("findmnt(8)")); + scols_print_table(tb); + scols_unref_table(tb); exit(EXIT_SUCCESS); } + int main(int argc, char *argv[]) { struct libmnt_table *tb = NULL; char **tabfiles = NULL; int direction = MNT_ITER_FORWARD; - int verify = 0; + int verify = 0, collist = 0; int c, rc = -1, timeout = -1; int ntabfiles = 0, tabtype = 0; char *outarg = NULL; @@ -1421,6 +1532,7 @@ int main(int argc, char *argv[]) { "canonicalize", no_argument, NULL, 'c' }, { "direction", required_argument, NULL, 'd' }, { "df", no_argument, NULL, 'D' }, + { "dfi", no_argument, NULL, 'I' }, { "evaluate", no_argument, NULL, 'e' }, { "first-only", no_argument, NULL, 'f' }, { "fstab", no_argument, NULL, 's' }, @@ -1458,6 +1570,7 @@ int main(int argc, char *argv[]) { "pseudo", no_argument, NULL, FINDMNT_OPT_PSEUDO }, { "vfs-all", no_argument, NULL, FINDMNT_OPT_VFS_ALL }, { "shadowed", no_argument, NULL, FINDMNT_OPT_SHADOWED }, + { "list-columns", no_argument, NULL, 'H' }, { NULL, 0, NULL, 0 } }; @@ -1484,7 +1597,7 @@ int main(int argc, char *argv[]) flags |= FL_TREE; while ((c = getopt_long(argc, argv, - "AabCcDd:ehiJfF:o:O:p::PklmM:nN:rst:uvRS:T:Uw:Vxy", + "AabCcDd:ehIiJfF:o:O:p::PklmM:nN:rst:uvRS:T:Uw:VxyH", longopts, NULL)) != -1) { err_exclusive_options(c, longopts, excl, excl_st); @@ -1521,6 +1634,10 @@ int main(int argc, char *argv[]) case 'e': flags |= FL_EVALUATE; break; + case 'I': + flags &= ~FL_TREE; + flags |= FL_DF_INODES; + break; case 'i': flags |= FL_INVERT; break; @@ -1642,6 +1759,10 @@ int main(int argc, char *argv[]) case FINDMNT_OPT_SHADOWED: flags |= FL_SHADOWED; break; + + case 'H': + collist = 1; + break; case 'h': usage(); case 'V': @@ -1651,7 +1772,19 @@ int main(int argc, char *argv[]) } } - if (!ncolumns && (flags & FL_DF)) { + if (collist) + list_colunms(); /* print end exit */ + + if (!ncolumns && (flags & FL_DF_INODES)) { + add_column(columns, ncolumns++, COL_SOURCE); + add_column(columns, ncolumns++, COL_FSTYPE); + add_column(columns, ncolumns++, COL_INO_TOTAL); + add_column(columns, ncolumns++, COL_INO_USED); + add_column(columns, ncolumns++, COL_INO_AVAIL); + add_column(columns, ncolumns++, COL_INO_USEPERC); + add_column(columns, ncolumns++, COL_TARGET); + } + else if (!ncolumns && (flags & FL_DF)) { add_column(columns, ncolumns++, COL_SOURCE); add_column(columns, ncolumns++, COL_FSTYPE); add_column(columns, ncolumns++, COL_SIZE); @@ -1793,36 +1926,13 @@ int main(int argc, char *argv[]) goto leave; } /* multi-line cells (now used for SOURCES) */ - if (fl & SCOLS_FL_WRAP) { + if (fl & SCOLS_FL_WRAP) scols_column_set_wrapfunc(cl, - scols_wrapnl_chunksize, - scols_wrapnl_nextchunk, + NULL, + scols_wrapzero_nextchunk, NULL); - scols_column_set_safechars(cl, "\n"); - } - if (flags & FL_JSON) { - switch (id) { - case COL_SIZE: - case COL_AVAIL: - case COL_USED: - if (!(flags & FL_BYTES)) - break; - /* fallthrough */ - case COL_ID: - case COL_PARENT: - case COL_FREQ: - case COL_PASSNO: - case COL_TID: - scols_column_set_json_type(cl, SCOLS_JSON_NUMBER); - break; - default: - if (fl & SCOLS_FL_WRAP) - scols_column_set_json_type(cl, SCOLS_JSON_ARRAY_STRING); - else - scols_column_set_json_type(cl, SCOLS_JSON_STRING); - break; - } - } + if (flags & FL_JSON) + scols_column_set_json_type(cl, get_column_json_type(id, fl, NULL)); } /* diff --git a/misc-utils/findmnt.h b/misc-utils/findmnt.h index ce5ddaf..5c69450 100644 --- a/misc-utils/findmnt.h +++ b/misc-utils/findmnt.h @@ -24,6 +24,7 @@ enum { FL_SHADOWED = (1 << 20), FL_DELETED = (1 << 21), FL_SHELLVAR = (1 << 22), + FL_DF_INODES = (1 << 23) | FL_DF, /* basic table settings */ FL_ASCII = (1 << 25), diff --git a/misc-utils/getopt.1 b/misc-utils/getopt.1 index 4921add..fe9003e 100644 --- a/misc-utils/getopt.1 +++ b/misc-utils/getopt.1 @@ -2,12 +2,12 @@ .\" Title: getopt .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-10-23 +.\" Date: 2024-01-31 .\" Manual: User Commands -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "GETOPT" "1" "2023-10-23" "util\-linux 2.39.3" "User Commands" +.TH "GETOPT" "1" "2024-01-31" "util\-linux 2.40" "User Commands" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 diff --git a/misc-utils/getopt.c b/misc-utils/getopt.c index 977b725..f989461 100644 --- a/misc-utils/getopt.c +++ b/misc-utils/getopt.c @@ -256,9 +256,9 @@ static void add_longopt(struct getopt_control *ctl, const char *name, int has_ar if (ctl->long_options_nr == ctl->long_options_length) { ctl->long_options_length += REALLOC_INCREMENT; - ctl->long_options = xrealloc(ctl->long_options, - sizeof(struct option) * - ctl->long_options_length); + ctl->long_options = xreallocarray(ctl->long_options, + ctl->long_options_length, + sizeof(struct option)); } if (name) { /* Not for init! */ @@ -359,8 +359,8 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" -T, --test test for getopt(1) version\n"), stdout); fputs(_(" -u, --unquoted do not quote the output\n"), stdout); fputs(USAGE_SEPARATOR, stdout); - printf(USAGE_HELP_OPTIONS(31)); - printf(USAGE_MAN_TAIL("getopt(1)")); + fprintf(stdout, USAGE_HELP_OPTIONS(31)); + fprintf(stdout, USAGE_MAN_TAIL("getopt(1)")); exit(EXIT_SUCCESS); } diff --git a/misc-utils/hardlink.1 b/misc-utils/hardlink.1 index 4aa0737..175cbc8 100644 --- a/misc-utils/hardlink.1 +++ b/misc-utils/hardlink.1 @@ -2,12 +2,12 @@ .\" Title: hardlink .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-11-21 +.\" Date: 2024-03-20 .\" Manual: User Commands -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "HARDLINK" "1" "2023-11-21" "util\-linux 2.39.3" "User Commands" +.TH "HARDLINK" "1" "2024-03-20" "util\-linux 2.40" "User Commands" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 @@ -79,7 +79,7 @@ to fit a number of cached content checksums. .sp \fB\-d\fP, \fB\-\-respect\-dir\fP .RS 4 -Only try to link files with the same directory name. The top\-level directory (as specified on the hardlink command line) is ignored. For example, \fBhardlink \-\-respect\-dir /foo /bar\fP will link \fI/foo/some/file\fP with \fI/bar/some/file\fP, but not \fI/bar/other/file\fP. If combined with \fB\-\-respect\-name\fP, then entire paths (except the top\-level directory) are compared. +Only try to link files with the same directory name. The top\-level directory (as specified on the \fBhardlink\fP command line) is ignored. For example, \fBhardlink \-\-respect\-dir /foo /bar\fP will link \fI/foo/some/file\fP with \fI/bar/some/file\fP, but not \fI/bar/other/file\fP. If combined with \fB\-\-respect\-name\fP, then entire paths (except the top\-level directory) are compared. .RE .sp \fB\-f\fP, \fB\-\-respect\-name\fP diff --git a/misc-utils/hardlink.1.adoc b/misc-utils/hardlink.1.adoc index 91d9867..471cb1c 100644 --- a/misc-utils/hardlink.1.adoc +++ b/misc-utils/hardlink.1.adoc @@ -55,7 +55,7 @@ way and I/O operation is done in the kernel. The size may be altered on the fly to fit a number of cached content checksums. *-d*, *--respect-dir*:: -Only try to link files with the same directory name. The top-level directory (as specified on the hardlink command line) is ignored. For example, *hardlink --respect-dir /foo /bar* will link _/foo/some/file_ with _/bar/some/file_, but not _/bar/other/file_. If combined with *--respect-name*, then entire paths (except the top-level directory) are compared. +Only try to link files with the same directory name. The top-level directory (as specified on the *hardlink* command line) is ignored. For example, *hardlink --respect-dir /foo /bar* will link _/foo/some/file_ with _/bar/some/file_, but not _/bar/other/file_. If combined with *--respect-name*, then entire paths (except the top-level directory) are compared. *-f*, *--respect-name*:: Only try to link files with the same (base)name. It's strongly recommended to use long options rather than *-f* which is interpreted in a different way by other *hardlink* implementations. diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index 7e66dfd..f9d3d09 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -991,7 +991,8 @@ static int is_reflink(struct file *xa, struct file *xb) if (ioctl(bf, FS_IOC_FIEMAP, (unsigned long) bmap) < 0) goto done; - if (amap->fm_mapped_extents != bmap->fm_mapped_extents) + if (amap->fm_mapped_extents == 0 || + amap->fm_mapped_extents != bmap->fm_mapped_extents) goto done; for (i = 0; i < amap->fm_mapped_extents; i++) { @@ -1192,8 +1193,8 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" --skip-reflinks skip already cloned files (enabled on --reflink)\n"), out); #endif fputs(USAGE_SEPARATOR, out); - printf(USAGE_HELP_OPTIONS(28)); - printf(USAGE_MAN_TAIL("hardlink(1)")); + fprintf(out, USAGE_HELP_OPTIONS(28)); + fprintf(out, USAGE_MAN_TAIL("hardlink(1)")); exit(EXIT_SUCCESS); } diff --git a/misc-utils/kill.1 b/misc-utils/kill.1 index 46d7e9d..12c3252 100644 --- a/misc-utils/kill.1 +++ b/misc-utils/kill.1 @@ -2,12 +2,12 @@ .\" Title: kill .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-11-21 +.\" Date: 2024-01-31 .\" Manual: User Commands -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "KILL" "1" "2023-11-21" "util\-linux 2.39.3" "User Commands" +.TH "KILL" "1" "2024-01-31" "util\-linux 2.40" "User Commands" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 diff --git a/misc-utils/kill.c b/misc-utils/kill.c index c469074..2a42267 100644 --- a/misc-utils/kill.c +++ b/misc-utils/kill.c @@ -217,8 +217,8 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" --verbose print pids that will be signaled\n"), out); fputs(USAGE_SEPARATOR, out); - printf(USAGE_HELP_OPTIONS(24)); - printf(USAGE_MAN_TAIL("kill(1)")); + fprintf(out, USAGE_HELP_OPTIONS(24)); + fprintf(out, USAGE_MAN_TAIL("kill(1)")); exit(EXIT_SUCCESS); } @@ -557,4 +557,3 @@ int main(int argc, char **argv) return KILL_EXIT_SOMEOK; /* partial success */ } - diff --git a/misc-utils/lastlog2-import.service.in b/misc-utils/lastlog2-import.service.in new file mode 100644 index 0000000..c57a897 --- /dev/null +++ b/misc-utils/lastlog2-import.service.in @@ -0,0 +1,15 @@ +[Unit] +Description=Import lastlog data into lastlog2 database +Documentation=man:lastlog2(8) +After=local-fs.target +ConditionPathExists=/var/log/lastlog +ConditionPathExists=!/var/lib/lastlog/lastlog2.db + +[Service] +Type=oneshot +ExecStart=@usrbin_execdir@/lastlog2 --import /var/log/lastlog +ExecStartPost=/usr/bin/mv /var/log/lastlog /var/log/lastlog.migrated +RemainAfterExit=true + +[Install] +WantedBy=default.target diff --git a/misc-utils/lastlog2-tmpfiles.conf.in b/misc-utils/lastlog2-tmpfiles.conf.in new file mode 100644 index 0000000..9d11957 --- /dev/null +++ b/misc-utils/lastlog2-tmpfiles.conf.in @@ -0,0 +1,5 @@ +# This file is part of lastlog2. +# +# See tmpfiles.d(5) for details +# +d /var/lib/lastlog 0755 - - - diff --git a/misc-utils/lastlog2.8 b/misc-utils/lastlog2.8 new file mode 100644 index 0000000..62eb1e2 --- /dev/null +++ b/misc-utils/lastlog2.8 @@ -0,0 +1,128 @@ +'\" t +.\" Title: lastlog2 +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2024-03-20 +.\" Manual: User Commands +.\" Source: util-linux 2.40 +.\" Language: English +.\" +.TH "LASTLOG2" "8" "2024-03-20" "util\-linux 2.40" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +lastlog2 \- display date of last login for all users or a specific one +.SH "SYNOPSIS" +.sp +\fBlastlog2\fP [options] +.SH "DESCRIPTION" +.sp +\fBlastlog2\fP displays the content of the last login database. The \fIlogin\-name\fP, +\fIlast\-login\-time\fP, \fItty\fP and \fIremote\-host\fP will be printed. +The default (no flags) causes all last login entries to be printed, sorted +by the order as written the first time into the database. +.sp +Compared to \fBlastlog\fP this command is Y2038 safe and uses sqlite3 to store the +information and not a sparse file. +.SH "OPTIONS" +.sp +\fB\-b\fP, \fB\-\-before\fP \fIDAYS\fP +.RS 4 +Print only last login records older than \fIDAYS\fP. +.RE +.sp +\fB\-C\fP, \fB\-\-clear\fP +.RS 4 +Clear last login record of a user. This option can be used only together with +\fB\-u\*(Aq (\fP\-\-user*). +.RE +.sp +\fB\-d\fP, *\-\-database \fIFILE\fP +.RS 4 +Use \fIFILE\fP as lastlog2 database. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help message and exit. +.RE +.sp +\fB\-i\fP, \fB\-\-import\fP \fIFILE\fP +.RS 4 +Import data from old lastlog file \fIFILE\fP. Existing entries in the lastlog2 +database will be overwritten. +.RE +.sp +\fB\-r\fP, \fB\-\-rename\fP \fINEWNAME\fP +.RS 4 +This option can only be used together with \fB\-u\fP (\fB\-\-user\fP). +.RE +.sp +\fB\-s\fP, \fB\-\-servive\fP \fInum\fP +.RS 4 +Display PAM service used to login in the last column. +.RE +.sp +\fB\-S\fP, \fB\-\-set\fP +.RS 4 +Set last login record of a user to the current time. This option can only be used +together with \fB\-u\fP (\fB\-\-user\fP). +.RE +.sp +\fB\-t\fP, \fB\-\-time\fP \fIDAYS\fP +.RS 4 +Print only last login records more recent than \fIDAYS\fP. +.RE +.sp +\fB\-u\fP, \fB\-\-users\fP \fILOGINS\fP +.RS 4 +Print only the last login record of the user \fILOGIN\fP. +.RE +.sp +\fB\-v\fP, \fB\-\-version\fP +.RS 4 +Print version number and exit. +.RE +.sp +If the user has never logged in the message \fBNever logged in\fP will be displayed +in the latest login time row. +.sp +Only the entries for the current users of the system will be displayed. +Other entries may exist for users that were deleted previously. +.SH "FILES" +.sp +\fB/var/lib/lastlog/lastlog2.db\fP +.RS 4 +Lastlog2 logging database file +.RE +.SH "AUTHORS" +.sp +lastlog2 was written by Thorsten Kukuk for \fBliblastlog2\fP(3). +.SH "SEE ALSO" +.sp +\fBliblastlog2\fP(3) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBlastlog2\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/misc-utils/lastlog2.8.adoc b/misc-utils/lastlog2.8.adoc new file mode 100644 index 0000000..b6be372 --- /dev/null +++ b/misc-utils/lastlog2.8.adoc @@ -0,0 +1,96 @@ +//po4a: entry man manual +//// +Copyright 2023 Thorsten Kukuk (kukuk@suse.de) +This file may be copied under the terms of the GNU Public License. +//// += lastlog2(8) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: lastlog2 + +== NAME + +lastlog2 - display date of last login for all users or a specific one + +== SYNOPSIS + +*lastlog2* [options] + +== DESCRIPTION + + +*lastlog2* displays the content of the last login database. The _login-name_, +_last-login-time_, _tty_ and _remote-host_ will be printed. +The default (no flags) causes all last login entries to be printed, sorted +by the order as written the first time into the database. + +Compared to *lastlog* this command is Y2038 safe and uses sqlite3 to store the +information and not a sparse file. + +== OPTIONS + +*-b*, *--before* _DAYS_:: +Print only last login records older than _DAYS_. + +*-C*, *--clear*:: +Clear last login record of a user. This option can be used only together with +*-u' (*--user*). + +*-d*, *--database _FILE_:: +Use _FILE_ as lastlog2 database. + +*-h*, *--help*:: +Display help message and exit. + +*-i*, *--import* _FILE_:: +Import data from old lastlog file _FILE_. Existing entries in the lastlog2 +database will be overwritten. + +*-r*, *--rename* _NEWNAME_:: +This option can only be used together with *-u* (*--user*). + +*-s*, *--servive* _num_:: +Display PAM service used to login in the last column. + +*-S*, *--set*:: +Set last login record of a user to the current time. This option can only be used +together with *-u* (*--user*). + +*-t*, *--time* _DAYS_:: +Print only last login records more recent than _DAYS_. + +*-u*, *--users* _LOGINS_:: +Print only the last login record of the user _LOGIN_. + +*-v*, *--version*:: +Print version number and exit. + +If the user has never logged in the message **Never logged in** will be displayed +in the latest login time row. + +Only the entries for the current users of the system will be displayed. +Other entries may exist for users that were deleted previously. + +== FILES + +*/var/lib/lastlog/lastlog2.db*:: +Lastlog2 logging database file + + +== AUTHORS + +lastlog2 was written by Thorsten Kukuk for *liblastlog2*(3). + +== SEE ALSO + +*liblastlog2*(3) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/lastlog2.c b/misc-utils/lastlog2.c new file mode 100644 index 0000000..4029f5e --- /dev/null +++ b/misc-utils/lastlog2.c @@ -0,0 +1,309 @@ +/* SPDX-License-Identifier: BSD-2-Clause + + Copyright (c) 2023, Thorsten Kukuk <kukuk@suse.com> + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#include <pwd.h> +#include <time.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <getopt.h> +#include <limits.h> + +#include "nls.h" +#include "c.h" +#include "strutils.h" +#include "lastlog2.h" + +static char *lastlog2_path = LL2_DEFAULT_DATABASE; + +static int bflg; +static time_t b_days; +static int tflg; +static time_t t_days; +static int sflg; + +static int print_entry(const char *user, int64_t ll_time, + const char *tty, const char *rhost, + const char *pam_service, const char *error) +{ + static int once = 0; + char *datep; + struct tm *tm, tm_buf; + char datetime[80]; + /* IPv6 address is at maximum 39 characters. + But for LL-addresses (fe80+only) the interface should be set, + so LL-address + % + IFNAMSIZ. */ + const int maxIPv6Addrlen = 42; + + /* Print only if older than b days */ + if (bflg && ((time (NULL) - ll_time) < b_days)) + return 0; + + /* Print only if newer than t days */ + if (tflg && ((time (NULL) - ll_time) > t_days)) + return 0; + /* this is necessary if you compile this on architectures with + a 32bit time_t type. */ + time_t t_time = ll_time; + tm = localtime_r(&t_time, &tm_buf); + if (tm == NULL) + datep = "(unknown)"; + else { + strftime(datetime, sizeof(datetime), "%a %b %e %H:%M:%S %z %Y", tm); + datep = datetime; + } + + if (ll_time == 0) + datep = "**Never logged in**"; + + if (!once) { + printf("Username Port From%*s Latest%*s%s\n", + maxIPv6Addrlen - 4, " ", + sflg ? (int) strlen(datep) -5 : 0, + " ", sflg ? "Service" : ""); + once = 1; + } + printf("%-16s %-8.8s %*s %s%*s%s\n", user, tty ? tty : "", + -maxIPv6Addrlen, rhost ? rhost : "", datep, + sflg ? 31 - (int) strlen(datep) : 0, + (sflg && pam_service) ? " " : "", + sflg ? (pam_service ? pam_service : "") : ""); + + if (error) + printf("\nError: %s\n", error); + + return 0; +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *output = stdout; + + fputs(USAGE_HEADER, output); + fprintf(output, _(" %s [options]\n"), program_invocation_short_name); + + fputs(USAGE_OPTIONS, output); + fputs(_(" -b, --before DAYS Print only records older than DAYS\n"), output); + fputs(_(" -C, --clear Clear record of a user (requires -u)\n"), output); + fputs(_(" -d, --database FILE Use FILE as lastlog2 database\n"), output); + fputs(_(" -i, --import FILE Import data from old lastlog file\n"), output); + fputs(_(" -r, --rename NEWNAME Rename existing user to NEWNAME (requires -u)\n"), output); + fputs(_(" -s, --service Display PAM service\n"), output); + fputs(_(" -S, --set ySet lastlog record to current time (requires -u)\n"), output); + fputs(_(" -t, --time DAYS Print only lastlog records more recent than DAYS\n"), output); + fputs(_(" -u, --user LOGIN Print lastlog record of the specified LOGIN\n"), output); + + fputs(USAGE_SEPARATOR, output); + fprintf(output, USAGE_HELP_OPTIONS(25)); + fprintf(output, USAGE_MAN_TAIL("lastlog2(8)")); + + exit(EXIT_SUCCESS); +} + +/* Check if an user exists on the system */ +#define has_user(_x) (getpwnam(_x) != NULL) + +int main(int argc, char **argv) +{ + static const struct option longopts[] = { + {"before", required_argument, NULL, 'b'}, + {"clear", no_argument, NULL, 'C'}, + {"database", required_argument, NULL, 'd'}, + {"help", no_argument, NULL, 'h'}, + {"import", required_argument, NULL, 'i'}, + {"rename", required_argument, NULL, 'r'}, + {"service", no_argument, NULL, 's'}, + {"set", no_argument, NULL, 'S'}, + {"time", required_argument, NULL, 't'}, + {"user", required_argument, NULL, 'u'}, + {"version", no_argument, NULL, 'v'}, + {NULL, 0, NULL, '\0'} + }; + char *error = NULL; + int Cflg = 0; + int iflg = 0; + int rflg = 0; + int Sflg = 0; + int uflg = 0; + const char *user = NULL; + const char *newname = NULL; + const char *lastlog_file = NULL; + struct ll2_context *db_context = NULL; + + int c; + + while ((c = getopt_long(argc, argv, "b:Cd:hi:r:sSt:u:v", longopts, NULL)) != -1) { + switch (c) { + case 'b': /* before DAYS; Print only records older than DAYS */ + { + unsigned long days; + errno = 0; + days = strtoul_or_err(optarg, _("Cannot parse days")); + b_days = (time_t) days * (24L * 3600L) /* seconds/DAY */; + bflg = 1; + } + break; + case 'C': /* clear; Clear record of a user (requires -u) */ + Cflg = 1; + break; + case 'd': /* database <FILE>; Use FILE as lastlog2 database */ + lastlog2_path = optarg; + break; + case 'h': /* help; Display this help message and exit */ + usage(); + break; + case 'i': /* import <FILE>; Import data from old lastlog file */ + lastlog_file = optarg; + iflg = 1; + break; + case 'r': /* rename <NEWNAME>; Rename existing user to NEWNAME (requires -u) */ + rflg = 1; + newname = optarg; + break; + case 's': /* service; Display PAM service */ + sflg = 1; + break; + case 'S': /* set; Set lastlog record to current time (requires -u) */ + /* Set lastlog record of a user to the current time. */ + Sflg = 1; + break; + case 't': /* time <DAYS>; Print only lastlog records more recent than DAYS */ + { + unsigned long days; + errno = 0; + days = strtoul_or_err(optarg, _("Cannot parse days")); + t_days = (time_t) days * (24L * 3600L) /* seconds/DAY */; + tflg = 1; + } + break; + case 'u': /* user <LOGIN>; Print lastlog record of the specified LOGIN */ + uflg = 1; + user = optarg; + break; + case 'v': /* version; Print version number and exit */ + print_version(EXIT_SUCCESS); + break; + default: + errtryhelp(EXIT_FAILURE); + } + } + + if ((Cflg + Sflg + iflg) > 1) + errx(EXIT_FAILURE, _("Option -C, -i and -S cannot be used together")); + + db_context = ll2_new_context(lastlog2_path); + if (!db_context) + errx(EXIT_FAILURE, _("Couldn't initialize lastlog2 environment")); + + if (iflg) { + /* Importing entries */ + if (ll2_import_lastlog(db_context, lastlog_file, &error) != 0) { + warnx(_("Couldn't import entries from '%s'"), lastlog_file); + goto err; + } + goto done; + } + + if (Cflg || Sflg || rflg) { + /* udpating, inserting and removing entries */ + if (!uflg || strlen(user) == 0) { + warnx(_("Options -C, -r and -S require option -u to specify the user")); + goto err; + } + + if ((Cflg || Sflg) && !has_user(user)) { + warnx(_("User '%s' does not exist."), user); + goto err; + } + + if (Cflg) { + if (ll2_remove_entry(db_context, user, &error) != 0) { + warnx(_("Couldn't remove entry for '%s'"), user); + goto err; + } + } + + if (Sflg) { + time_t ll_time = 0; + + if (time(&ll_time) == -1) { + warn(_("Could not determine current time")); + goto err; + } + + if (ll2_update_login_time(db_context, user, ll_time, &error) != 0) { + warnx(_("Couldn't update login time for '%s'"), user); + goto err; + } + } + + if (rflg) { + if (ll2_rename_user(db_context, user, newname, &error) != 0) { + warnx(_("Couldn't rename entry '%s' to '%s'"), user, newname); + goto err; + } + } + + goto done; + } + + if (user) { + /* print user specific information */ + int64_t ll_time = 0; + char *tty = NULL; + char *rhost = NULL; + char *service = NULL; + + if (!has_user(user)) { + warnx(_("User '%s' does not exist."), user); + goto err; + } + + /* We ignore errors, if the user is not in the database he did never login */ + ll2_read_entry(db_context, user, &ll_time, &tty, &rhost, + &service, NULL); + + print_entry(user, ll_time, tty, rhost, service, NULL); + goto done; + } + + /* print all information */ + if (ll2_read_all(db_context, print_entry, &error) != 0) { + warnx(_("Couldn't read entries for all users")); + goto err; + } + +done: + ll2_unref_context(db_context); + exit(EXIT_SUCCESS); +err: + ll2_unref_context(db_context); + if (error) + errx(EXIT_FAILURE, "%s", error); + exit(EXIT_FAILURE); +} diff --git a/misc-utils/logger.1 b/misc-utils/logger.1 index 232e1bd..d6165ad 100644 --- a/misc-utils/logger.1 +++ b/misc-utils/logger.1 @@ -2,12 +2,12 @@ .\" Title: logger .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-11-21 +.\" Date: 2024-01-31 .\" Manual: User Commands -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "LOGGER" "1" "2023-11-21" "util\-linux 2.39.3" "User Commands" +.TH "LOGGER" "1" "2024-01-31" "util\-linux 2.40" "User Commands" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 diff --git a/misc-utils/logger.c b/misc-utils/logger.c index 8174d55..e1d270d 100644 --- a/misc-utils/logger.c +++ b/misc-utils/logger.c @@ -248,7 +248,7 @@ static int unix_socket(struct logger_ctl *ctl, const char *path, int *socket_typ errx(EXIT_FAILURE, _("openlog %s: pathname too long"), path); s_addr.sun_family = AF_UNIX; - strcpy(s_addr.sun_path, path); + xstrncpy(s_addr.sun_path, path, sizeof(s_addr.sun_path)); for (i = 2; i; i--) { int st = -1; @@ -343,7 +343,7 @@ static int journald_entry(struct logger_ctl *ctl, FILE *fp) int n, lines = 0, vectors = 8, ret = 0, msgline = -1; size_t dummy = 0; - iovec = xmalloc(vectors * sizeof(struct iovec)); + iovec = xreallocarray(NULL, vectors, sizeof(struct iovec)); while (1) { buf = NULL; sz = getline(&buf, &dummy, fp); @@ -375,7 +375,7 @@ static int journald_entry(struct logger_ctl *ctl, FILE *fp) vectors *= 2; if (IOV_MAX < vectors) errx(EXIT_FAILURE, _("maximum input lines (%d) exceeded"), IOV_MAX); - iovec = xrealloc(iovec, vectors * sizeof(struct iovec)); + iovec = xreallocarray(iovec, vectors, sizeof(struct iovec)); } iovec[lines].iov_base = buf; iovec[lines].iov_len = sz; @@ -451,7 +451,7 @@ static void write_output(struct logger_ctl *ctl, const char *const msg) if (!ctl->noact && !is_connected(ctl)) logger_reopen(ctl); - /* 1) octen count */ + /* 1) octet count */ if (ctl->octet_count) { size_t len = xasprintf(&octet, "%zu ", strlen(ctl->hdr) + strlen(msg)); iovec_add_string(iov, iovlen, octet, len); @@ -1091,8 +1091,8 @@ static void __attribute__((__noreturn__)) usage(void) #endif fputs(USAGE_SEPARATOR, out); - printf(USAGE_HELP_OPTIONS(26)); - printf(USAGE_MAN_TAIL("logger(1)")); + fprintf(out, USAGE_HELP_OPTIONS(26)); + fprintf(out, USAGE_MAN_TAIL("logger(1)")); exit(EXIT_SUCCESS); } diff --git a/misc-utils/look.1 b/misc-utils/look.1 index b45090d..93fb78d 100644 --- a/misc-utils/look.1 +++ b/misc-utils/look.1 @@ -2,12 +2,12 @@ .\" Title: look .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-10-23 +.\" Date: 2024-01-31 .\" Manual: User Commands -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "LOOK" "1" "2023-10-23" "util\-linux 2.39.3" "User Commands" +.TH "LOOK" "1" "2024-01-31" "util\-linux 2.40" "User Commands" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 diff --git a/misc-utils/look.c b/misc-utils/look.c index 0e6f1ed..5e8229a 100644 --- a/misc-utils/look.c +++ b/misc-utils/look.c @@ -366,8 +366,8 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" -t, --terminate <char> define the string-termination character\n"), out); fputs(USAGE_SEPARATOR, out); - printf(USAGE_HELP_OPTIONS(26)); - printf(USAGE_MAN_TAIL("look(1)")); + fprintf(out, USAGE_HELP_OPTIONS(26)); + fprintf(out, USAGE_MAN_TAIL("look(1)")); exit(EXIT_SUCCESS); } diff --git a/misc-utils/lsblk-mnt.c b/misc-utils/lsblk-mnt.c index 9f6ba0d..f4ce676 100644 --- a/misc-utils/lsblk-mnt.c +++ b/misc-utils/lsblk-mnt.c @@ -60,8 +60,7 @@ static void add_filesystem(struct lsblk_device *dev, struct libmnt_fs *fs) assert(dev); assert(fs); - dev->fss = xrealloc(dev->fss, (dev->nfss + 1) - * sizeof(struct libmnt_fs *)); + dev->fss = xreallocarray(dev->fss, dev->nfss + 1, sizeof(struct libmnt_fs *)); dev->fss[dev->nfss] = fs; dev->nfss++; dev->is_mounted = 1; diff --git a/misc-utils/lsblk.8 b/misc-utils/lsblk.8 index f67b96c..235f88b 100644 --- a/misc-utils/lsblk.8 +++ b/misc-utils/lsblk.8 @@ -2,12 +2,12 @@ .\" Title: lsblk .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-12-01 +.\" Date: 2024-03-20 .\" Manual: System Administration -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "LSBLK" "8" "2023-12-01" "util\-linux 2.39.3" "System Administration" +.TH "LSBLK" "8" "2024-03-20" "util\-linux 2.40" "System Administration" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 @@ -38,9 +38,11 @@ lsblk \- list block devices .sp By default, the command prints all block devices (except RAM disks) in a tree\-like format. The same device can be repeated in the tree if it relates to other devices. The \fB\-\-merge\fP option is recommended for more complicated setups to gather groups of devices and describe complex N:M relationships. .sp +The tree\-like output (or \fBchildren[]\fP array in the JSON output) is enabled only if NAME column it present in the output or when \fB\-\-tree\fP command line option is used. See also \fB\-\-nodeps\fP and \fB\-\-list\fP to control the tree formatting. +.sp The default output, as well as the default output from options like \fB\-\-fs\fP and \fB\-\-topology\fP, is subject to change. So whenever possible, you should avoid using default outputs in your scripts. Always explicitly define expected columns by using \fB\-\-output\fP \fIcolumns\-list\fP and \fB\-\-list\fP in environments where a stable output is required. .sp -Use \fBlsblk \-\-help\fP to get a list of all available columns. +Use \fBlsblk \-\-list\-columns\fP to get a list of all available columns. .sp Note that \fBlsblk\fP might be executed in time when \fBudev\fP does not have all information about recently added or modified devices yet. In this case it is recommended to use \fBudevadm settle\fP before \fBlsblk\fP to synchronize with udev. .sp @@ -68,6 +70,11 @@ examples: "1 KiB" and "1 MiB" are respectively exhibited as "1 K" and "1 M", then omitting on purpose the mention "iB", which is part of these abbreviations. .RE .sp +\fB\-H\fP, \fB\-\-list\-columns\fP +.RS 4 +List the available columns, use with \fB\-\-json\fP or \fB\-\-raw\fP to get output in machine\-readable format. +.RE +.sp \fB\-D\fP, \fB\-\-discard\fP .RS 4 Print information about the discarding capabilities (TRIM, UNMAP) for each device. @@ -107,7 +114,7 @@ Use ASCII characters for tree formatting. .sp \fB\-J\fP, \fB\-\-json\fP .RS 4 -Use JSON output format. It\(cqs strongly recommended to use \fB\-\-output\fP and also \fB\-\-tree\fP if necessary. +Use JSON output format. It\(cqs strongly recommended to use \fB\-\-output\fP and also \fB\-\-tree\fP if necessary. Note that \fBchildren[]\fP is used only if NAME column or \fB\-\-tree\fP is used. .RE .sp \fB\-l\fP, \fB\-\-list\fP @@ -142,7 +149,7 @@ Do not print a header line. .sp \fB\-o\fP, \fB\-\-output\fP \fIlist\fP .RS 4 -Specify which output columns to print. Use \fB\-\-help\fP to get a list of all supported columns. The columns may affect tree\-like output. The default is to use tree for the column \*(AqNAME\*(Aq (see also \fB\-\-tree\fP). +Specify which output columns to print. Use \fB\-\-list\-columns\fP to get a list of all supported columns. The columns may affect tree\-like output. The default is to use tree for the column \*(AqNAME\*(Aq (see also \fB\-\-tree\fP). .sp The default list of columns may be extended if \fIlist\fP is specified in the format \fI+list\fP (e.g., \fBlsblk \-o +UUID\fP). .RE @@ -162,6 +169,86 @@ Produce output in the form of key="value" pairs. The output lines are still orde Print full device paths. .RE .sp +\fB\-Q\fP, \fB\-\-filter\fP \fIexpr\fP +.RS 4 +Print only the devices that meet the conditions specified by the expr. The +filter is assessed prior to lsblk collecting data for all output columns. Only +the necessary data for the lazy evaluation of the expression is retrieved from +the system. This approach can enhance performance when compared to +post\-filtering, as commonly done by tools such as grep(1). +.sp +This feature is EXPERIMENTAL. See also \fBscols\-filter\fP(5). For example +exclude sda and sdb, but print everything else (\*(Aq!~\*(Aq is a negative regular +expression matching operator): +.RE +.RS 3 +.ll -.6i +.sp +.if n .RS 4 +.nf +.fam C +lsblk \-\-filter \*(AqNAME !~ "sd[ab]"\*(Aq +.fam +.fi +.if n .RE +.br +.RE +.ll +.sp +\fB\-\-highlight\fP \fIexpr\fP +.RS 4 +Colorize lines matching the expression. +This feature is EXPERIMENTAL. See also \fBscols\-filter\fP(5). +.RE +.sp +\fB\-\-ct\fP \fIname\fP [: \fIparam\fP [: \fIfunction\fP ]] +.RS 4 +Define a custom counter. The counters are printed after the standard output. +The \fIname\fP is the custom name of the counter, the optional \fIparam\fP is the name of the column +to be used for the counter, and the optional \fIfunction\fP specifies +the aggregation function, supported functions are: count, min, max, or sum. The +default is count. +.sp +If the \fIparam\fP is not specified, then the counter counts the number of lines. This +feature is EXPERIMENTAL. See also \fB\-\-ct\-filter\fP. +.sp +For example, \fB\-\-ct MyCounter:SIZE:sum\fP will count the summary for SIZE from all lines; +and to count the number of SATA disks, it is possible to use: +.RE +.RS 3 +.ll -.6i +.sp +.if n .RS 4 +.nf +.fam C +lsblk \-\-ct\-filter \*(AqTYPE=="disk" && TRAN=="sata"\*(Aq \-\-ct "Number of SATA devices" +.fam +.fi +.if n .RE +.br +.RE +.ll +.sp +\fB\-\-ct\-filter\fP \fIexpr\fP +.RS 4 +Define a restriction for the next counter. This feature is EXPERIMENTAL. See also \fB\-\-ct\fP +and \fBscols\-filter\fP(5). For example, aggregate sizes by device type: +.RE +.RS 3 +.ll -.6i +.sp +.if n .RS 4 +.nf +.fam C +lsblk \-\-ct\-filter \*(AqTYPE=="part"\*(Aq \-\-ct Partitions:SIZE:sum \(rs + \-\-ct\-filter \*(AqTYPE=="disk"\*(Aq \-\-ct WholeDisks:SIZE:sum +.fam +.fi +.if n .RE +.br +.RE +.ll +.sp \fB\-r\fP, \fB\-\-raw\fP .RS 4 Produce output in raw format. The output lines are still ordered by dependencies. All potentially unsafe characters are hex\-escaped (\(rsx<code>) in the NAME, KNAME, LABEL, PARTLABEL and MOUNTPOINT columns. @@ -281,9 +368,10 @@ The \fBlsblk\fP command needs to be able to look up each block device by major:m .MTO "kzak\(atredhat.com" "Karel Zak" "" .SH "SEE ALSO" .sp -\fBls\fP(1), \fBblkid\fP(8), \fBfindmnt\fP(8) +\fBls\fP(1), +\fBscols\-filter\fP(5) .SH "REPORTING BUGS" .sp For bug reports, use the issue tracker at \c diff --git a/misc-utils/lsblk.8.adoc b/misc-utils/lsblk.8.adoc index d4b13f2..577ff71 100644 --- a/misc-utils/lsblk.8.adoc +++ b/misc-utils/lsblk.8.adoc @@ -20,9 +20,11 @@ lsblk - list block devices By default, the command prints all block devices (except RAM disks) in a tree-like format. The same device can be repeated in the tree if it relates to other devices. The *--merge* option is recommended for more complicated setups to gather groups of devices and describe complex N:M relationships. +The tree-like output (or *children[]* array in the JSON output) is enabled only if NAME column it present in the output or when *--tree* command line option is used. See also *--nodeps* and *--list* to control the tree formatting. + The default output, as well as the default output from options like *--fs* and *--topology*, is subject to change. So whenever possible, you should avoid using default outputs in your scripts. Always explicitly define expected columns by using *--output* _columns-list_ and *--list* in environments where a stable output is required. -Use *lsblk --help* to get a list of all available columns. +Use *lsblk --list-columns* to get a list of all available columns. Note that *lsblk* might be executed in time when *udev* does not have all information about recently added or modified devices yet. In this case it is recommended to use *udevadm settle* before *lsblk* to synchronize with udev. @@ -39,6 +41,9 @@ Disable all built-in filters and list all empty devices and RAM disk devices too *-b*, *--bytes*:: include::man-common/in-bytes.adoc[] +*-H*, *--list-columns*:: +List the available columns, use with *--json* or *--raw* to get output in machine-readable format. + *-D*, *--discard*:: Print information about the discarding capabilities (TRIM, UNMAP) for each device. @@ -63,7 +68,7 @@ Include devices specified by the comma-separated _list_ of major device numbers. Use ASCII characters for tree formatting. *-J*, *--json*:: -Use JSON output format. It's strongly recommended to use *--output* and also *--tree* if necessary. +Use JSON output format. It's strongly recommended to use *--output* and also *--tree* if necessary. Note that *children[]* is used only if NAME column or *--tree* is used. *-l*, *--list*:: Produce output in the form of a list. The output does not provide information about relationships between devices and since version 2.34 every device is printed only once if *--pairs* or *--raw* not specified (the parsable outputs are maintained in backwardly compatible way). @@ -84,7 +89,7 @@ Output info about virtio devices only. Do not print a header line. *-o*, *--output* _list_:: -Specify which output columns to print. Use *--help* to get a list of all supported columns. The columns may affect tree-like output. The default is to use tree for the column 'NAME' (see also *--tree*). +Specify which output columns to print. Use *--list-columns* to get a list of all supported columns. The columns may affect tree-like output. The default is to use tree for the column 'NAME' (see also *--tree*). + The default list of columns may be extended if _list_ is specified in the format _+list_ (e.g., *lsblk -o +UUID*). @@ -97,6 +102,50 @@ Produce output in the form of key="value" pairs. The output lines are still orde *-p*, *--paths*:: Print full device paths. +*-Q*, *--filter* _expr_:: +Print only the devices that meet the conditions specified by the expr. The +filter is assessed prior to lsblk collecting data for all output columns. Only +the necessary data for the lazy evaluation of the expression is retrieved from +the system. This approach can enhance performance when compared to +post-filtering, as commonly done by tools such as grep(1). ++ +This feature is EXPERIMENTAL. See also *scols-filter*(5). For example +exclude sda and sdb, but print everything else ('!~' is a negative regular +expression matching operator): +____ + lsblk --filter 'NAME !~ "sd[ab]"' +____ + +*--highlight* _expr_:: +Colorize lines matching the expression. +This feature is EXPERIMENTAL. See also *scols-filter*(5). + +*--ct* _name_ [: _param_ [: _function_ ]]:: +Define a custom counter. The counters are printed after the standard output. +The _name_ is the custom name of the counter, the optional _param_ is the name of the column +to be used for the counter, and the optional _function_ specifies +the aggregation function, supported functions are: count, min, max, or sum. The +default is count. ++ +If the _param_ is not specified, then the counter counts the number of lines. This +feature is EXPERIMENTAL. See also *--ct-filter*. ++ +For example, *--ct MyCounter:SIZE:sum* will count the summary for SIZE from all lines; +and to count the number of SATA disks, it is possible to use: +____ + + lsblk --ct-filter 'TYPE=="disk" && TRAN=="sata"' --ct "Number of SATA devices" +____ + + +*--ct-filter* _expr_:: +Define a restriction for the next counter. This feature is EXPERIMENTAL. See also *--ct* +and *scols-filter*(5). For example, aggregate sizes by device type: +____ + lsblk --ct-filter 'TYPE=="part"' --ct Partitions:SIZE:sum \ + --ct-filter 'TYPE=="disk"' --ct WholeDisks:SIZE:sum +____ + *-r*, *--raw*:: Produce output in raw format. The output lines are still ordered by dependencies. All potentially unsafe characters are hex-escaped (\x<code>) in the NAME, KNAME, LABEL, PARTLABEL and MOUNTPOINT columns. @@ -175,9 +224,10 @@ mailto:kzak@redhat.com[Karel Zak] == SEE ALSO -*ls*(1), *blkid*(8), *findmnt*(8) +*ls*(1), +*scols-filter*(5) include::man-common/bugreports.adoc[] diff --git a/misc-utils/lsblk.c b/misc-utils/lsblk.c index fae918b..30bd2ed 100644 --- a/misc-utils/lsblk.c +++ b/misc-utils/lsblk.c @@ -52,6 +52,8 @@ #include "fileutils.h" #include "loopdev.h" #include "buffer.h" +#include "colors.h" +#include "column-list-table.h" #include "lsblk.h" @@ -88,6 +90,8 @@ enum { COL_LABEL, COL_LOGSEC, COL_MAJMIN, + COL_MAJ, + COL_MIN, COL_MINIO, COL_MODE, COL_MODEL, @@ -158,7 +162,7 @@ enum { /* column names */ struct colinfo { - const char *name; /* header */ + const char * const name; /* header */ double whint; /* width hint (N < 1 is in percent of termwidth) */ int flags; /* SCOLS_FL_* */ const char *help; @@ -166,21 +170,21 @@ struct colinfo { }; /* columns descriptions */ -static struct colinfo infos[] = { +static const struct colinfo infos[] = { [COL_ALIOFF] = { "ALIGNMENT", 6, SCOLS_FL_RIGHT, N_("alignment offset"), COLTYPE_NUM }, [COL_ID] = { "ID", 0.1, SCOLS_FL_NOEXTREMES, N_("udev ID (based on ID-LINK)") }, [COL_IDLINK] = { "ID-LINK", 0.1, SCOLS_FL_NOEXTREMES, N_("the shortest udev /dev/disk/by-id link name") }, [COL_DALIGN] = { "DISC-ALN", 6, SCOLS_FL_RIGHT, N_("discard alignment offset"), COLTYPE_NUM }, [COL_DAX] = { "DAX", 1, SCOLS_FL_RIGHT, N_("dax-capable device"), COLTYPE_BOOL }, - [COL_DGRAN] = { "DISC-GRAN", 6, SCOLS_FL_RIGHT, N_("discard granularity"), COLTYPE_SIZE }, + [COL_DGRAN] = { "DISC-GRAN", 6, SCOLS_FL_RIGHT, N_("discard granularity, use <number> if --bytes is given"), COLTYPE_SIZE }, [COL_DISKSEQ] = { "DISK-SEQ", 1, SCOLS_FL_RIGHT, N_("disk sequence number"), COLTYPE_NUM }, - [COL_DMAX] = { "DISC-MAX", 6, SCOLS_FL_RIGHT, N_("discard max bytes"), COLTYPE_SIZE }, + [COL_DMAX] = { "DISC-MAX", 6, SCOLS_FL_RIGHT, N_("discard max bytes, use <number> if --bytes is given"), COLTYPE_SIZE }, [COL_DZERO] = { "DISC-ZERO", 1, SCOLS_FL_RIGHT, N_("discard zeroes data"), COLTYPE_BOOL }, - [COL_FSAVAIL] = { "FSAVAIL", 5, SCOLS_FL_RIGHT, N_("filesystem size available"), COLTYPE_SIZE }, + [COL_FSAVAIL] = { "FSAVAIL", 5, SCOLS_FL_RIGHT, N_("filesystem size available for unprivileged users, use <number> if --bytes is given"), COLTYPE_SIZE }, [COL_FSROOTS] = { "FSROOTS", 0.1, SCOLS_FL_WRAP, N_("mounted filesystem roots") }, - [COL_FSSIZE] = { "FSSIZE", 5, SCOLS_FL_RIGHT, N_("filesystem size"), COLTYPE_SIZE }, + [COL_FSSIZE] = { "FSSIZE", 5, SCOLS_FL_RIGHT, N_("filesystem size, use <number> if --bytes is given"), COLTYPE_SIZE }, [COL_FSTYPE] = { "FSTYPE", 0.1, SCOLS_FL_TRUNC, N_("filesystem type") }, - [COL_FSUSED] = { "FSUSED", 5, SCOLS_FL_RIGHT, N_("filesystem size used"), COLTYPE_SIZE }, + [COL_FSUSED] = { "FSUSED", 5, SCOLS_FL_RIGHT, N_("filesystem size used, use <number> if --bytes is given"), COLTYPE_SIZE }, [COL_FSUSEPERC] = { "FSUSE%", 3, SCOLS_FL_RIGHT, N_("filesystem use percentage") }, [COL_FSVERSION] = { "FSVER", 0.1, SCOLS_FL_TRUNC, N_("filesystem version") }, [COL_GROUP] = { "GROUP", 0.1, SCOLS_FL_TRUNC, N_("group name") }, @@ -190,6 +194,8 @@ static struct colinfo infos[] = { [COL_LABEL] = { "LABEL", 0.1, 0, N_("filesystem LABEL") }, [COL_LOGSEC] = { "LOG-SEC", 7, SCOLS_FL_RIGHT, N_("logical sector size"), COLTYPE_NUM }, [COL_MAJMIN] = { "MAJ:MIN", 6, 0, N_("major:minor device number"), COLTYPE_SORTNUM }, + [COL_MAJ] = { "MAJ", 3, 0, N_("major device number"), COLTYPE_SORTNUM }, + [COL_MIN] = { "MIN", 3, 0, N_("minor device number"), COLTYPE_SORTNUM }, [COL_MINIO] = { "MIN-IO", 6, SCOLS_FL_RIGHT, N_("minimum I/O size"), COLTYPE_NUM }, [COL_MODEL] = { "MODEL", 0.1, SCOLS_FL_TRUNC, N_("device identifier") }, [COL_MODE] = { "MODE", 10, 0, N_("device node permissions") }, @@ -217,8 +223,8 @@ static struct colinfo infos[] = { [COL_RQ_SIZE]= { "RQ-SIZE", 5, SCOLS_FL_RIGHT, N_("request queue size"), COLTYPE_NUM }, [COL_SCHED] = { "SCHED", 0.1, 0, N_("I/O scheduler name") }, [COL_SERIAL] = { "SERIAL", 0.1, SCOLS_FL_TRUNC, N_("disk serial number") }, - [COL_SIZE] = { "SIZE", 5, SCOLS_FL_RIGHT, N_("size of the device"), COLTYPE_SIZE }, - [COL_START] = { "START", 5, SCOLS_FL_RIGHT, N_("partition start offset"), COLTYPE_NUM }, + [COL_SIZE] = { "SIZE", 5, SCOLS_FL_RIGHT, N_("size of the device, use <number> if --bytes is given"), COLTYPE_SIZE }, + [COL_START] = { "START", 5, SCOLS_FL_RIGHT, N_("partition start offset (in 512-byte sectors)"), COLTYPE_NUM }, [COL_STATE] = { "STATE", 7, SCOLS_FL_TRUNC, N_("state of the device") }, [COL_SUBSYS] = { "SUBSYSTEMS", 0.1, SCOLS_FL_NOEXTREMES, N_("de-duplicated chain of subsystems") }, [COL_TARGETS] = { "MOUNTPOINTS", 0.10, SCOLS_FL_WRAP | SCOLS_FL_NOEXTREMES, N_("all locations where device is mounted") }, @@ -227,17 +233,23 @@ static struct colinfo infos[] = { [COL_TYPE] = { "TYPE", 4, 0, N_("device type") }, [COL_UUID] = { "UUID", 36, 0, N_("filesystem UUID") }, [COL_VENDOR] = { "VENDOR", 0.1, SCOLS_FL_TRUNC, N_("device vendor") }, - [COL_WSAME] = { "WSAME", 6, SCOLS_FL_RIGHT, N_("write same max bytes"), COLTYPE_SIZE }, + [COL_WSAME] = { "WSAME", 6, SCOLS_FL_RIGHT, N_("write same max bytes, use <number> if --bytes is given"), COLTYPE_SIZE }, [COL_WWN] = { "WWN", 18, 0, N_("unique storage identifier") }, [COL_ZONED] = { "ZONED", 0.3, 0, N_("zone model") }, - [COL_ZONE_SZ] = { "ZONE-SZ", 9, SCOLS_FL_RIGHT, N_("zone size"), COLTYPE_SIZE }, - [COL_ZONE_WGRAN] = { "ZONE-WGRAN", 10, SCOLS_FL_RIGHT, N_("zone write granularity"), COLTYPE_SIZE }, - [COL_ZONE_APP] = { "ZONE-APP", 11, SCOLS_FL_RIGHT, N_("zone append max bytes"), COLTYPE_SIZE }, + [COL_ZONE_SZ] = { "ZONE-SZ", 9, SCOLS_FL_RIGHT, N_("zone size, use <number> if --bytes is given"), COLTYPE_SIZE }, + [COL_ZONE_WGRAN] = { "ZONE-WGRAN", 10, SCOLS_FL_RIGHT, N_("zone write granularity, use <number> if --bytes is given"), COLTYPE_SIZE }, + [COL_ZONE_APP] = { "ZONE-APP", 11, SCOLS_FL_RIGHT, N_("zone append max bytes, use <number> if --bytes is given"), COLTYPE_SIZE }, [COL_ZONE_NR] = { "ZONE-NR", 8, SCOLS_FL_RIGHT, N_("number of zones"), COLTYPE_NUM }, [COL_ZONE_OMAX] = { "ZONE-OMAX", 10, SCOLS_FL_RIGHT, N_("maximum number of open zones"), COLTYPE_NUM }, [COL_ZONE_AMAX] = { "ZONE-AMAX", 10, SCOLS_FL_RIGHT, N_("maximum number of active zones"), COLTYPE_NUM }, }; +/* "userdata" used by callback for libsmartcols filter */ +struct filler_data { + struct lsblk_device *dev; + struct lsblk_device *parent; +}; + struct lsblk *lsblk; /* global handler */ /* @@ -324,7 +336,7 @@ static int get_column_id(int num) } /* Returns column description for the column sequential number */ -static struct colinfo *get_column_info(int num) +static const struct colinfo *get_column_info(int num) { return &infos[ get_column_id(num) ]; } @@ -334,12 +346,30 @@ static int column_name_to_id(const char *name, size_t namesz) { size_t i; + /* name as diplayed for users */ for (i = 0; i < ARRAY_SIZE(infos); i++) { const char *cn = infos[i].name; if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) return i; } + + /* name as used in expressions, JSON output etc. */ + if (strnchr(name, namesz, '_')) { + char *buf = NULL; + size_t bufsz = 0; + + for (i = 0; i < ARRAY_SIZE(infos); i++) { + if (scols_shellvar_name(infos[i].name, &buf, &bufsz) != 0) + continue; + if (!strncasecmp(name, buf, namesz) && !*(buf + namesz)) { + free(buf); + return i; + } + } + free(buf); + } + warnx(_("unknown column: %s"), name); return -1; } @@ -597,7 +627,7 @@ static char *mk_dm_name(const char *name) /* stores data to scols cell userdata (invisible and independent on output) * to make the original values accessible for sort functions */ -static void set_sortdata_u64(struct libscols_line *ln, int col, uint64_t x) +static void set_rawdata_u64(struct libscols_line *ln, int col, uint64_t x) { struct libscols_cell *ce = scols_line_get_cell(ln, col); uint64_t *data; @@ -625,22 +655,37 @@ static void str2u64(const char *str, uint64_t *data) *data = num; } -static void unref_sortdata(struct libscols_table *tb) +static void unref_line_rawdata(struct libscols_line *ln, struct libscols_table *tb) +{ + size_t i; + + for (i = 0; i < ncolumns; i++) { + struct libscols_column *cl = scols_table_get_column(tb, i); + struct libscols_cell *ce; + void *data; + + if (cl != lsblk->sort_col && !scols_column_has_data_func(cl)) + continue; + + ce = scols_line_get_column_cell(ln, cl); + data = scols_cell_get_userdata(ce); + free(data); + } +} + +static void unref_table_rawdata(struct libscols_table *tb) { struct libscols_iter *itr; struct libscols_line *ln; - if (!tb || !lsblk->sort_col) + if (!tb || !lsblk->rawdata) return; + itr = scols_new_iter(SCOLS_ITER_FORWARD); if (!itr) return; - while (scols_table_next_line(tb, itr, &ln) == 0) { - struct libscols_cell *ce = scols_line_get_column_cell(ln, - lsblk->sort_col); - void *data = scols_cell_get_userdata(ce); - free(data); - } + while (scols_table_next_line(tb, itr, &ln) == 0) + unref_line_rawdata(ln, tb); scols_free_iter(itr); } @@ -735,21 +780,21 @@ static uint64_t device_get_discard_granularity(struct lsblk_device *dev) } static void device_read_bytes(struct lsblk_device *dev, char *path, char **str, - uint64_t *sortdata) + uint64_t *rawdata) { uint64_t x; if (lsblk->bytes) { ul_path_read_string(dev->sysfs, str, path); - if (sortdata) - str2u64(*str, sortdata); + if (rawdata) + str2u64(*str, rawdata); return; } if (ul_path_read_u64(dev->sysfs, &x, path) == 0) { *str = size_to_human_string(SIZE_SUFFIX_1LETTER, x); - if (sortdata) - *sortdata = x; + if (rawdata) + *rawdata = x; } } @@ -771,7 +816,7 @@ static void process_mq(struct lsblk_device *dev, char **str) } /* - * Generates data (string) for column specified by column ID for specified device. If sortdata + * Generates data (string) for column specified by column ID for specified device. If rawdata * is not NULL then returns number usable to sort the column if the data are available for the * column. */ @@ -779,7 +824,8 @@ static char *device_get_data( struct lsblk_device *dev, /* device */ struct lsblk_device *parent, /* device parent as defined in the tree */ int id, /* column ID (COL_*) */ - uint64_t *sortdata) /* returns sort data as number */ + uint64_t *rawdata, /* returns sort data as number */ + size_t *datasiz) { struct lsblk_devprop *prop = NULL; char *str = NULL; @@ -841,8 +887,18 @@ static char *device_get_data( xasprintf(&str, "%u:%u", dev->maj, dev->min); else xasprintf(&str, "%3u:%-3u", dev->maj, dev->min); - if (sortdata) - *sortdata = makedev(dev->maj, dev->min); + if (rawdata) + *rawdata = makedev(dev->maj, dev->min); + break; + case COL_MAJ: + xasprintf(&str, "%u", dev->maj); + if (rawdata) + *rawdata = dev->maj; + break; + case COL_MIN: + xasprintf(&str, "%u", dev->min); + if (rawdata) + *rawdata = dev->min; break; case COL_FSTYPE: prop = lsblk_device_get_properties(dev); @@ -880,9 +936,9 @@ static char *device_get_data( else ul_buffer_append_string(&buf, mnt_fs_get_target(fs)); if (i + 1 < n) - ul_buffer_append_data(&buf, "\n", 1); + ul_buffer_append_data(&buf, "\0", 1); } - str = ul_buffer_get_data(&buf, NULL, NULL); + str = ul_buffer_get_data(&buf, datasiz, NULL); break; } case COL_FSROOTS: @@ -898,9 +954,9 @@ static char *device_get_data( continue; ul_buffer_append_string(&buf, root ? root : "/"); if (i + 1 < n) - ul_buffer_append_data(&buf, "\n", 1); + ul_buffer_append_data(&buf, "\0", 1); } - str = ul_buffer_get_data(&buf, NULL, NULL); + str = ul_buffer_get_data(&buf, datasiz, NULL); break; } case COL_LABEL: @@ -977,8 +1033,8 @@ static char *device_get_data( break; case COL_RA: ul_path_read_string(dev->sysfs, &str, "queue/read_ahead_kb"); - if (sortdata) - str2u64(str, sortdata); + if (rawdata) + str2u64(str, rawdata); break; case COL_RO: str = xstrdup(is_readonly_device(dev) ? "1" : "0"); @@ -1031,13 +1087,13 @@ static char *device_get_data( xasprintf(&str, "%ju", dev->size); else str = size_to_human_string(SIZE_SUFFIX_1LETTER, dev->size); - if (sortdata) - *sortdata = dev->size; + if (rawdata) + *rawdata = dev->size; break; case COL_START: ul_path_read_string(dev->sysfs, &str, "start"); - if (sortdata) - str2u64(str, sortdata); + if (rawdata) + str2u64(str, rawdata); break; case COL_STATE: if (!device_is_partition(dev) && !dev->dm_name) @@ -1050,36 +1106,36 @@ static char *device_get_data( break; case COL_ALIOFF: ul_path_read_string(dev->sysfs, &str, "alignment_offset"); - if (sortdata) - str2u64(str, sortdata); + if (rawdata) + str2u64(str, rawdata); break; case COL_MINIO: ul_path_read_string(dev->sysfs, &str, "queue/minimum_io_size"); - if (sortdata) - str2u64(str, sortdata); + if (rawdata) + str2u64(str, rawdata); break; case COL_OPTIO: ul_path_read_string(dev->sysfs, &str, "queue/optimal_io_size"); - if (sortdata) - str2u64(str, sortdata); + if (rawdata) + str2u64(str, rawdata); break; case COL_PHYSEC: ul_path_read_string(dev->sysfs, &str, "queue/physical_block_size"); - if (sortdata) - str2u64(str, sortdata); + if (rawdata) + str2u64(str, rawdata); break; case COL_LOGSEC: ul_path_read_string(dev->sysfs, &str, "queue/logical_block_size"); - if (sortdata) - str2u64(str, sortdata); + if (rawdata) + str2u64(str, rawdata); break; case COL_SCHED: str = get_scheduler(dev); break; case COL_RQ_SIZE: ul_path_read_string(dev->sysfs, &str, "queue/nr_requests"); - if (sortdata) - str2u64(str, sortdata); + if (rawdata) + str2u64(str, rawdata); break; case COL_TYPE: str = get_type(dev); @@ -1106,23 +1162,23 @@ static char *device_get_data( ul_path_read_string(dev->sysfs, &str, "discard_alignment"); if (!str) str = xstrdup("0"); - if (sortdata) - str2u64(str, sortdata); + if (rawdata) + str2u64(str, rawdata); break; case COL_DGRAN: if (lsblk->bytes) { ul_path_read_string(dev->sysfs, &str, "queue/discard_granularity"); - if (sortdata) - str2u64(str, sortdata); + if (rawdata) + str2u64(str, rawdata); } else { uint64_t x = device_get_discard_granularity(dev); str = size_to_human_string(SIZE_SUFFIX_1LETTER, x); - if (sortdata) - *sortdata = x; + if (rawdata) + *rawdata = x; } break; case COL_DMAX: - device_read_bytes(dev, "queue/discard_max_bytes", &str, sortdata); + device_read_bytes(dev, "queue/discard_max_bytes", &str, rawdata); break; case COL_DZERO: if (device_get_discard_granularity(dev) > 0) @@ -1131,7 +1187,7 @@ static char *device_get_data( str = xstrdup("0"); break; case COL_WSAME: - device_read_bytes(dev, "queue/write_same_max_bytes", &str, sortdata); + device_read_bytes(dev, "queue/write_same_max_bytes", &str, rawdata); if (!str) str = xstrdup("0"); break; @@ -1148,35 +1204,35 @@ static char *device_get_data( xasprintf(&str, "%ju", x); else str = size_to_human_string(SIZE_SUFFIX_1LETTER, x); - if (sortdata) - *sortdata = x; + if (rawdata) + *rawdata = x; } break; } case COL_ZONE_WGRAN: - device_read_bytes(dev, "queue/zone_write_granularity", &str, sortdata); + device_read_bytes(dev, "queue/zone_write_granularity", &str, rawdata); break; case COL_ZONE_APP: - device_read_bytes(dev, "queue/zone_append_max_bytes", &str, sortdata); + device_read_bytes(dev, "queue/zone_append_max_bytes", &str, rawdata); break; case COL_ZONE_NR: ul_path_read_string(dev->sysfs, &str, "queue/nr_zones"); - if (sortdata) - str2u64(str, sortdata); + if (rawdata) + str2u64(str, rawdata); break; case COL_ZONE_OMAX: ul_path_read_string(dev->sysfs, &str, "queue/max_open_zones"); if (!str) str = xstrdup("0"); - if (sortdata) - str2u64(str, sortdata); + if (rawdata) + str2u64(str, rawdata); break; case COL_ZONE_AMAX: ul_path_read_string(dev->sysfs, &str, "queue/max_active_zones"); if (!str) str = xstrdup("0"); - if (sortdata) - str2u64(str, sortdata); + if (rawdata) + str2u64(str, rawdata); break; case COL_DAX: ul_path_read_string(dev->sysfs, &str, "queue/dax"); @@ -1186,14 +1242,58 @@ static char *device_get_data( break; case COL_DISKSEQ: ul_path_read_string(dev->sysfs, &str, "diskseq"); - if (sortdata) - str2u64(str, sortdata); + if (rawdata) + str2u64(str, rawdata); break; }; return str; } +static void device_fill_scols_cell(struct lsblk_device *dev, + struct lsblk_device *parent, + struct libscols_line *ln, + size_t colnum) +{ + struct libscols_cell *ce; + struct libscols_column *cl = scols_table_get_column(lsblk->table, colnum); + char *data; + size_t datasiz = 0; + int rc, id = get_column_id(colnum); + + if (lsblk->sort_id == id || scols_column_has_data_func(cl)) { + uint64_t rawdata = (uint64_t) -1; + + data = device_get_data(dev, parent, id, &rawdata, &datasiz); + if (data && rawdata != (uint64_t) -1) + set_rawdata_u64(ln, colnum, rawdata); + } else + data = device_get_data(dev, parent, id, NULL, &datasiz); + + if (!data) + return; + DBG(DEV, ul_debugobj(dev, " refer data[%zu]=\"%s\"", colnum, data)); + ce = scols_line_get_cell(ln, colnum); + if (!ce) + return; + rc = datasiz ? scols_cell_refer_memory(ce, data, datasiz + 1) + : scols_cell_refer_data(ce, data); + if (rc) + err(EXIT_FAILURE, _("failed to add output data")); +} + +static int filter_filler_cb( + struct libscols_filter *fltr __attribute__((__unused__)), + struct libscols_line *ln, + size_t colnum, + void *userdata) +{ + struct filler_data *fid = (struct filler_data *) userdata; + + device_fill_scols_cell(fid->dev, fid->parent, ln, colnum); + return 0; +} + /* * Adds data for all wanted columns about the device to the smartcols table */ @@ -1207,7 +1307,7 @@ static void device_to_scols( struct libscols_line *ln; struct lsblk_iter itr; struct lsblk_device *child = NULL; - int link_group = 0; + int link_group = 0, nocount = 0; DBG(DEV, ul_debugobj(dev, "add '%s' to scols", dev->name)); @@ -1232,7 +1332,38 @@ static void device_to_scols( dev->is_printed = 1; - if (link_group) { + /* filter lines, smartcols filter can ask for data */ + if (lsblk->filter) { + int status = 0; + struct filler_data fid = { + .dev = dev, + .parent = parent + }; + + scols_filter_set_filler_cb(lsblk->filter, + filter_filler_cb, (void *) &fid); + + if (scols_line_apply_filter(ln, lsblk->filter, &status)) + err(EXIT_FAILURE, _("failed to apply filter")); + if (status == 0) { + struct libscols_line *x = scols_line_get_parent(ln); + + if (x) + scols_line_remove_child(x, ln); + unref_line_rawdata(ln, tab); + scols_table_remove_line(tab, ln); + ln = NULL; + } + } + + /* read column specific data and set it to smartcols table line */ + for (i = 0; ln && i < ncolumns; i++) { + if (scols_line_is_filled(ln, i)) + continue; + device_fill_scols_cell(dev, parent, ln, i); + } + + if (ln && link_group) { struct lsblk_device *p; struct libscols_line *gr = parent_line; @@ -1253,24 +1384,9 @@ static void device_to_scols( scols_line_link_group(ln, gr, 0); } - /* read column specific data and set it to smartcols table line */ - for (i = 0; i < ncolumns; i++) { - char *data; - int id = get_column_id(i); - - if (lsblk->sort_id != id) - data = device_get_data(dev, parent, id, NULL); - else { - uint64_t sortdata = (uint64_t) -1; - - data = device_get_data(dev, parent, id, &sortdata); - if (data && sortdata != (uint64_t) -1) - set_sortdata_u64(ln, i, sortdata); - } - DBG(DEV, ul_debugobj(dev, " refer data[%zu]=\"%s\"", i, data)); - if (data && scols_line_refer_data(ln, i, data)) - err(EXIT_FAILURE, _("failed to add output data")); - } + /* The same device could be printed more than once, don't use it in counter */ + if (dev->scols_line) + nocount = 1; dev->scols_line = ln; @@ -1286,6 +1402,21 @@ static void device_to_scols( DBG(DEV, ul_debugobj(dev, "%s <- child done", dev->name)); } + /* apply highligther */ + if (ln && lsblk->hlighter) { + int status = 0; + + if (scols_line_apply_filter(ln, lsblk->hlighter, &status) == 0 + && status) + scols_line_set_color(ln, lsblk->hlighter_seq); + } + + /* apply counters */ + if (!nocount) { + for (i = 0; ln && i < lsblk->ncts; i++) + scols_line_apply_filter(ln, lsblk->ct_filters[i], NULL); + } + /* Let's be careful with number of open files */ ul_path_close_dirfd(dev->sysfs); } @@ -1917,7 +2048,7 @@ static void parse_includes(const char *str0) } /* - * see set_sortdata_u64() and columns initialization in main() + * see set_rawdata_u64() and columns initialization in main() */ static int cmp_u64_cells(struct libscols_cell *a, struct libscols_cell *b, @@ -1935,6 +2066,13 @@ static int cmp_u64_cells(struct libscols_cell *a, return *adata == *bdata ? 0 : *adata >= *bdata ? 1 : -1; } +static void *get_u64_cell(const struct libscols_column *cl __attribute__((__unused__)), + struct libscols_cell *ce, + void *data __attribute__((__unused__))) +{ + return scols_cell_get_userdata(ce); +} + static void device_set_dedupkey( struct lsblk_device *dev, struct lsblk_device *parent, @@ -1943,7 +2081,7 @@ static void device_set_dedupkey( struct lsblk_iter itr; struct lsblk_device *child = NULL; - dev->dedupkey = device_get_data(dev, parent, id, NULL); + dev->dedupkey = device_get_data(dev, parent, id, NULL, NULL); if (dev->dedupkey) DBG(DEV, ul_debugobj(dev, "%s: de-duplication key: %s", dev->name, dev->dedupkey)); @@ -1972,10 +2110,193 @@ static void devtree_set_dedupkeys(struct lsblk_devtree *tr, int id) device_set_dedupkey(dev, NULL, id); } +static struct libscols_filter *new_filter(const char *query) +{ + struct libscols_filter *f; + + f = scols_new_filter(NULL); + if (!f) + err(EXIT_FAILURE, _("failed to allocate filter")); + if (query && scols_filter_parse_string(f, query) != 0) + errx(EXIT_FAILURE, _("failed to parse \"%s\": %s"), query, + scols_filter_get_errmsg(f)); + return f; +} + +static struct libscols_filter *new_counter_filter(const char *query) +{ + lsblk->ct_filters = xreallocarray(lsblk->ct_filters, lsblk->ncts + 1, + sizeof(struct libscols_filter *)); + + lsblk->ct_filters[lsblk->ncts] = new_filter(query); + lsblk->ncts++; + + return lsblk->ct_filters[lsblk->ncts - 1]; +} + +static void set_counter_properties(const char *str0) +{ + struct libscols_filter *fltr = NULL; + struct libscols_counter *ct; + char *p, *str = xstrdup(str0); + char *name = NULL, *param = NULL, *func = NULL; + + for (p = strtok(str, ":"); p != NULL; p = strtok((char *)0, ":")) { + if (!name) + name = p; + else if (!param) + param = p; + else if (!func) + func = p; + else + errx(EXIT_FAILURE, _("unexpected counter specification: %s"), str0); + } + + if (!name) + errx(EXIT_FAILURE, _("counter not properly specified")); + + /* use the latest counter filter (--ct-filter) or create empty */ + if (lsblk->ncts) + fltr = lsblk->ct_filters[lsblk->ncts - 1]; + else + fltr = new_counter_filter(NULL); + + ct = scols_filter_new_counter(fltr); + if (!ct) + err(EXIT_FAILURE, _("failed to allocate counter")); + + scols_counter_set_name(ct, name); + if (param) + scols_counter_set_param(ct, param); + if (func) { + int x; + + if (strcmp(func, "max") == 0) + x = SCOLS_COUNTER_MAX; + else if (strcmp(func, "min") == 0) + x = SCOLS_COUNTER_MIN; + else if (strcmp(func, "sum") == 0) + x = SCOLS_COUNTER_SUM; + else if (strcmp(func, "count") == 0) + x = SCOLS_COUNTER_COUNT; + else + errx(EXIT_FAILURE, _("unsupported counter type: %s"), func); + + scols_counter_set_func(ct, x); + } + free(str); +} + +static void print_counters(void) +{ + struct libscols_iter *itr; + size_t i; + + fputc('\n', stdout); + fputs(_("Summary:\n"), stdout); + + itr = scols_new_iter(SCOLS_ITER_FORWARD); + if (!itr) + err(EXIT_FAILURE, _("failed to allocate iterator")); + + for (i = 0; i < lsblk->ncts; i++) { + struct libscols_filter *fltr = lsblk->ct_filters[i]; + struct libscols_counter *ct = NULL; + + scols_reset_iter(itr, SCOLS_ITER_FORWARD); + while (scols_filter_next_counter(fltr, itr, &ct) == 0) { + printf("%16llu %s\n", + scols_counter_get_result(ct), + scols_counter_get_name(ct)); + } + } + + scols_free_iter(itr); +} + +static void set_column_type(const struct colinfo *ci, struct libscols_column *cl, int fl) +{ + switch (ci->type) { + case COLTYPE_SIZE: + /* See init_scols_filter(), it may overwrite the type */ + if (!lsblk->bytes) + break; + /* fallthrough */ + case COLTYPE_NUM: + scols_column_set_json_type(cl, SCOLS_JSON_NUMBER); + scols_column_set_data_type(cl, SCOLS_DATA_U64); + return; + case COLTYPE_BOOL: + scols_column_set_json_type(cl, SCOLS_JSON_BOOLEAN); + scols_column_set_data_type(cl, SCOLS_DATA_BOOLEAN); + return; + default: + break; + } + + /* default */ + if (fl & SCOLS_FL_WRAP) + scols_column_set_json_type(cl, SCOLS_JSON_ARRAY_STRING); + else + scols_column_set_json_type(cl, SCOLS_JSON_STRING); + + scols_column_set_data_type(cl, SCOLS_DATA_STRING); +} + +static void init_scols_filter(struct libscols_table *tb, struct libscols_filter *f) +{ + struct libscols_iter *itr; + const char *name = NULL; + int nerrs = 0; + + itr = scols_new_iter(SCOLS_ITER_FORWARD); + if (!itr) + err(EXIT_FAILURE, _("failed to allocate iterator")); + + while (scols_filter_next_holder(f, itr, &name, 0) == 0) { + struct libscols_column *col = scols_table_get_column_by_name(tb, name); + int id = column_name_to_id(name, strlen(name)); + const struct colinfo *ci = id >= 0 ? &infos[id] : NULL; + + if (!ci) { + nerrs++; + continue; /* report all unknown columns */ + } + if (!col) { + add_column(id); + col = scols_table_new_column(lsblk->table, ci->name, + ci->whint, SCOLS_FL_HIDDEN); + if (!col) + err(EXIT_FAILURE,_("failed to allocate output column")); + + set_column_type(ci, col, ci->flags); + } + + /* For sizes use rawdata (u64) rather than strings from table */ + if (ci->type == COLTYPE_SIZE + && !lsblk->bytes + && !scols_column_has_data_func(col)) { + + scols_column_set_data_type(col, SCOLS_DATA_U64); + scols_column_set_data_func(col, get_u64_cell, NULL); + lsblk->rawdata = 1; + } + + scols_filter_assign_column(f, itr, name, col); + } + + scols_free_iter(itr); + + if (!nerrs) + return; + + errx(EXIT_FAILURE, _("failed to initialize filter")); +} + + static void __attribute__((__noreturn__)) usage(void) { FILE *out = stdout; - size_t i; fputs(USAGE_HEADER, out); fprintf(out, _(" %s [options] [<device> ...]\n"), program_invocation_short_name); @@ -1992,6 +2313,10 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" -M, --merge group parents of sub-trees (usable for RAIDs, Multi-path)\n"), out); fputs(_(" -O, --output-all output all columns\n"), out); fputs(_(" -P, --pairs use key=\"value\" output format\n"), out); + fputs(_(" -Q, --filter <expr> print only lines maching the expression\n"), out); + fputs(_(" --highlight <expr> colorize lines maching the expression\n"), out); + fputs(_(" --ct-filter <expr> restrict the next counter\n"), out); + fputs(_(" --ct <name>[:<param>[:<func>]] define a custom counter\n"), out); fputs(_(" -S, --scsi output info about SCSI devices\n"), out); fputs(_(" -N, --nvme output info about NVMe devices\n"), out); fputs(_(" -v, --virtio output info about virtio devices\n"), out); @@ -2005,7 +2330,7 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" -l, --list use list format output\n"), out); fputs(_(" -m, --perms output info about permissions\n"), out); fputs(_(" -n, --noheadings don't print headings\n"), out); - fputs(_(" -o, --output <list> output columns\n"), out); + fputs(_(" -o, --output <list> output columns (see --list-columns)\n"), out); fputs(_(" -p, --paths print complete device path\n"), out); fputs(_(" -r, --raw use raw output format\n"), out); fputs(_(" -s, --inverse inverse dependencies\n"), out); @@ -2015,15 +2340,40 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" -y, --shell use column names to be usable as shell variable identifiers\n"), out); fputs(_(" -z, --zoned print zone related information\n"), out); fputs(_(" --sysroot <dir> use specified directory as system root\n"), out); + fputs(USAGE_SEPARATOR, out); - printf(USAGE_HELP_OPTIONS(22)); + fputs(_(" -H, --list-columns list the available columns\n"), out); + fprintf(out, USAGE_HELP_OPTIONS(22)); - fprintf(out, USAGE_COLUMNS); + fprintf(out, USAGE_MAN_TAIL("lsblk(8)")); - for (i = 0; i < ARRAY_SIZE(infos); i++) - fprintf(out, " %12s %s\n", infos[i].name, _(infos[i].help)); + exit(EXIT_SUCCESS); +} + + +static void __attribute__((__noreturn__)) list_colunms(void) +{ + size_t i; + struct libscols_table *tb = xcolumn_list_table_new("lsblk-columns", stdout, + lsblk->flags & LSBLK_RAW, + lsblk->flags & LSBLK_JSON); + + for (i = 0; i < ARRAY_SIZE(infos); i++) { + const struct colinfo *ci = &infos[i]; + + xcolumn_list_table_append_line(tb, ci->name, + ci->type == COLTYPE_SIZE ? -1 : + ci->type == COLTYPE_NUM ? SCOLS_JSON_NUMBER : + ci->type == COLTYPE_BOOL ? SCOLS_JSON_BOOLEAN : + ci->flags & SCOLS_FL_WRAP ? SCOLS_JSON_ARRAY_STRING : + SCOLS_JSON_STRING, + ci->type == COLTYPE_SIZE ? "<string|number>" : NULL, + _(ci->help)); + } - printf(USAGE_MAN_TAIL("lsblk(8)")); + + scols_print_table(tb); + scols_unref_table(tb); exit(EXIT_SUCCESS); } @@ -2044,14 +2394,17 @@ int main(int argc, char *argv[]) .tree_id = COL_NAME }; struct lsblk_devtree *tr = NULL; - int c, status = EXIT_FAILURE; + int c, status = EXIT_FAILURE, collist = 0; char *outarg = NULL; size_t i; unsigned int width = 0; int force_tree = 0, has_tree_col = 0; enum { - OPT_SYSROOT = CHAR_MAX + 1 + OPT_SYSROOT = CHAR_MAX + 1, + OPT_COUNTER_FILTER, + OPT_COUNTER, + OPT_HIGHLIGHT, }; static const struct option longopts[] = { @@ -2066,6 +2419,8 @@ int main(int argc, char *argv[]) { "json", no_argument, NULL, 'J' }, { "output", required_argument, NULL, 'o' }, { "output-all", no_argument, NULL, 'O' }, + { "filter", required_argument, NULL, 'Q' }, + { "highlight", required_argument, NULL, OPT_HIGHLIGHT }, { "merge", no_argument, NULL, 'M' }, { "perms", no_argument, NULL, 'm' }, { "noheadings", no_argument, NULL, 'n' }, @@ -2088,6 +2443,9 @@ int main(int argc, char *argv[]) { "tree", optional_argument, NULL, 'T' }, { "version", no_argument, NULL, 'V' }, { "width", required_argument, NULL, 'w' }, + { "ct-filter", required_argument, NULL, OPT_COUNTER_FILTER }, + { "ct", required_argument, NULL, OPT_COUNTER }, + { "list-columns", no_argument, NULL, 'H' }, { NULL, 0, NULL, 0 }, }; @@ -2113,9 +2471,10 @@ int main(int argc, char *argv[]) lsblk = &_ls; lsblk_init_debug(); + scols_init_debug(0); while((c = getopt_long(argc, argv, - "AabdDzE:e:fhJlNnMmo:OpPiI:rstVvST::w:x:y", + "AabdDzE:e:fHhJlNnMmo:OpPQ:iI:rstVvST::w:x:y", longopts, NULL)) != -1) { err_exclusive_options(c, longopts, excl, excl_st); @@ -2179,6 +2538,10 @@ int main(int argc, char *argv[]) lsblk->flags |= LSBLK_EXPORT; lsblk->flags &= ~LSBLK_TREE; /* disable the default */ break; + case 'Q': + lsblk->filter = new_filter(optarg); + lsblk->flags &= ~LSBLK_TREE; /* disable the default */ + break; case 'y': lsblk->flags |= LSBLK_SHELLVAR; break; @@ -2287,6 +2650,19 @@ int main(int argc, char *argv[]) errtryhelp(EXIT_FAILURE); break; + case OPT_COUNTER_FILTER: + new_counter_filter(optarg); + break; + case OPT_COUNTER: + set_counter_properties(optarg); + break; + case OPT_HIGHLIGHT: + lsblk->hlighter = new_filter(optarg); + break; + + case 'H': + collist = 1; + break; case 'h': usage(); case 'V': @@ -2296,6 +2672,9 @@ int main(int argc, char *argv[]) } } + if (collist) + list_colunms(); /* print end exit */ + if (force_tree) lsblk->flags |= LSBLK_TREE; @@ -2341,7 +2720,6 @@ int main(int argc, char *argv[]) } lsblk_mnt_init(); - scols_init_debug(0); ul_path_init_debug(); /* @@ -2364,7 +2742,7 @@ int main(int argc, char *argv[]) } for (i = 0; i < ncolumns; i++) { - struct colinfo *ci = get_column_info(i); + const struct colinfo *ci = get_column_info(i); struct libscols_column *cl; int id = get_column_id(i), fl = ci->flags; @@ -2397,6 +2775,7 @@ int main(int argc, char *argv[]) } if (!lsblk->sort_col && lsblk->sort_id == id) { lsblk->sort_col = cl; + lsblk->rawdata = 1; scols_column_set_cmpfunc(cl, ci->type == COLTYPE_NUM ? cmp_u64_cells : ci->type == COLTYPE_SIZE ? cmp_u64_cells : @@ -2404,36 +2783,23 @@ int main(int argc, char *argv[]) NULL); } /* multi-line cells (now used for MOUNTPOINTS) */ - if (fl & SCOLS_FL_WRAP) { - scols_column_set_wrapfunc(cl, - scols_wrapnl_chunksize, - scols_wrapnl_nextchunk, - NULL); - scols_column_set_safechars(cl, "\n"); - } + if (fl & SCOLS_FL_WRAP) + scols_column_set_wrapfunc(cl, NULL, scols_wrapzero_nextchunk, NULL); - if (lsblk->flags & LSBLK_JSON) { - switch (ci->type) { - case COLTYPE_SIZE: - if (!lsblk->bytes) - break; - /* fallthrough */ - case COLTYPE_NUM: - scols_column_set_json_type(cl, SCOLS_JSON_NUMBER); - break; - case COLTYPE_BOOL: - scols_column_set_json_type(cl, SCOLS_JSON_BOOLEAN); - break; - default: - if (fl & SCOLS_FL_WRAP) - scols_column_set_json_type(cl, SCOLS_JSON_ARRAY_STRING); - else - scols_column_set_json_type(cl, SCOLS_JSON_STRING); - break; - } - } + set_column_type(ci, cl, fl); } + if (lsblk->filter) + init_scols_filter(lsblk->table, lsblk->filter); + + if (lsblk->hlighter && colors_init(UL_COLORMODE_AUTO, "lsblk") > 0) { + lsblk->hlighter_seq = color_scheme_get_sequence("highlight-line", UL_COLOR_RED); + scols_table_enable_colors(lsblk->table, 1); + init_scols_filter(lsblk->table, lsblk->hlighter); + } + for (i = 0; i < lsblk->ncts; i++) + init_scols_filter(lsblk->table, lsblk->ct_filters[i]); + tr = lsblk_new_devtree(); if (!tr) err(EXIT_FAILURE, _("failed to allocate device tree")); @@ -2472,11 +2838,19 @@ int main(int argc, char *argv[]) scols_print_table(lsblk->table); + if (lsblk->ncts) + print_counters(); leave: - if (lsblk->sort_col) - unref_sortdata(lsblk->table); + if (lsblk->rawdata) + unref_table_rawdata(lsblk->table); scols_unref_table(lsblk->table); + scols_unref_filter(lsblk->filter); + scols_unref_filter(lsblk->hlighter); + + for (i = 0; i < lsblk->ncts; i++) + scols_unref_filter(lsblk->ct_filters[i]); + free(lsblk->ct_filters); lsblk_mnt_deinit(); lsblk_properties_deinit(); diff --git a/misc-utils/lsblk.h b/misc-utils/lsblk.h index a437c06..a2d9a00 100644 --- a/misc-utils/lsblk.h +++ b/misc-utils/lsblk.h @@ -41,6 +41,12 @@ struct lsblk { int dedup_id; + struct libscols_filter *filter; + struct libscols_filter *hlighter; + const char *hlighter_seq; + + struct libscols_filter **ct_filters; /* array of counters' filters */ + size_t ncts; /* number of ct filters */ const char *sysroot; int flags; /* LSBLK_* */ @@ -55,6 +61,7 @@ struct lsblk { unsigned int virtio:1; /* print virtio device only */ unsigned int paths:1; /* print devnames with "/dev" prefix */ unsigned int sort_hidden:1; /* sort column not between output columns */ + unsigned int rawdata : 1; /* has rawdata in cell userdata */ unsigned int dedup_hidden :1; /* deduplication column not between output columns */ unsigned int force_tree_order:1;/* sort lines by parent->tree relation */ unsigned int noempty:1; /* hide empty devices */ diff --git a/misc-utils/lsclocks.1 b/misc-utils/lsclocks.1 new file mode 100644 index 0000000..b38138f --- /dev/null +++ b/misc-utils/lsclocks.1 @@ -0,0 +1,177 @@ +'\" t +.\" Title: lsclocks +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2024-03-20 +.\" Manual: User Commands +.\" Source: util-linux 2.40 +.\" Language: English +.\" +.TH "LSCLOCKS" "1" "2024-03-20" "util\-linux 2.40" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +lsclocks \- display system clocks +.SH "SYNOPSIS" +.sp +\fBlsclocks\fP [option] +.SH "DESCRIPTION" +.sp +\fBlsclocks\fP is a simple command to display system clocks. +.sp +It allows to display information like current time and resolution of clocks like +CLOCK_MONOTONIC, CLOCK_REALTIME and CLOCK_BOOTTIME. +.SH "OPTIONS" +.sp +\fB\-J\fP, \fB\-\-json\fP +.RS 4 +Use JSON output format. +.RE +.sp +\fB\-n\fP, \fB\-\-noheadings\fP +.RS 4 +Don\(cqt print headings. +.RE +.sp +\fB\-o\fP, \fB\-\-output\fP \fIlist\fP +.RS 4 +Specify which output columns to print. See the \fBOUTPUT COLUMNS\fP +section for details of available columns. +.RE +.sp +\fB\-\-output\-all\fP +.RS 4 +Output all columns. +.RE +.sp +\fB\-r\fP, \fB\-\-raw\fP +.RS 4 +Use raw output format. +.RE +.sp +\fB\-r\fP, \fB\-\-time\fP \fIclock\fP +.RS 4 +Show current time of one specific clock. +.RE +.sp +\fB\-\-no\-discover\-dynamic\fP +.RS 4 +Do not try to discover dynamic clocks. +.RE +.sp +\fB\-d\fP, \fB\-\-dynamic\-clock\fP \fIpath\fP +.RS 4 +Also display specified dynamic clock. +Can be specified multiple times. +.RE +.sp +\fB\-\-no\-discover\-rtc\fP +.RS 4 +Do not try to discover RTCs. +.RE +.sp +\fB\-x\fP, \fB\-\-rtc\fP \fIpath\fP +.RS 4 +Also display specified RTC. +Can be specified multiple times. +.RE +.sp +\fB\-c\fP, \fB\-\-cpu\-clock\fP \fIpid\fP +.RS 4 +Also display CPU clock of specified process. +Can be specified multiple times. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "OUTPUT COLUMNS" +.sp +Each column has a type. Types are surround by < and >. +.sp +TYPE <\f(CRstring\fP> +.RS 4 +Clock type. +.RE +.sp +ID <\f(CRnumber\fP> +.RS 4 +Numeric clock ID. +.RE +.sp +CLOCK <\f(CRstring\fP> +.RS 4 +Name in the form \fBCLOCK_\fP +.RE +.sp +NAME <\f(CRstring\fP> +.RS 4 +Shorter, easier to read name. +.RE +.sp +TIME <\f(CRnumber\fP> +.RS 4 +Current clock timestamp as returned by \fBclock_gettime()\fP. +.RE +.sp +ISO_TIME <\f(CRstring\fP> +.RS 4 +ISO8601 formatted version of \fBTIME\fP. +.RE +.sp +RESOL_RAW <\f(CRnumber\fP> +.RS 4 +Clock resolution as returned by \fBclock_getres\fP(2). +.RE +.sp +RESOL <\f(CRnumber\fP> +.RS 4 +Human readable version of \fBRESOL_RAW\fP. +.RE +.sp +REL_TIME <\f(CRstring\fP> +.RS 4 +\fBTIME\fP time formatted as time range. +.RE +.sp +NS_OFFSET <\f(CRnumber\fP> +.RS 4 +Offset of the current namespace to the parent namespace as read from \fB/proc/self/timens_offsets\fP. +.RE +.SH "AUTHORS" +.sp +.MTO "thomas\(att\-8ch.de" "Thomas Weißschuh" "" +.SH "SEE ALSO" +.sp +\fBclock_getres\fP(2) \fBclock_gettime\fP(2) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBlsclocks\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/misc-utils/lsclocks.1.adoc b/misc-utils/lsclocks.1.adoc new file mode 100644 index 0000000..12a425b --- /dev/null +++ b/misc-utils/lsclocks.1.adoc @@ -0,0 +1,114 @@ +//po4a: entry man manual += lsclocks(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: lsclocks + +== NAME + +lsclocks - display system clocks + +== SYNOPSIS + +*lsclocks* [option] + +== DESCRIPTION + +*lsclocks* is a simple command to display system clocks. + +It allows to display information like current time and resolution of clocks like +CLOCK_MONOTONIC, CLOCK_REALTIME and CLOCK_BOOTTIME. + +== OPTIONS + +*-J*, *--json*:: +Use JSON output format. + +*-n*, *--noheadings*:: +Don't print headings. + +*-o*, *--output* _list_:: +Specify which output columns to print. See the *OUTPUT COLUMNS* +section for details of available columns. + +*--output-all*:: +Output all columns. + +*-r*, *--raw*:: +Use raw output format. + +*-r*, *--time* _clock_:: +Show current time of one specific clock. + +*--no-discover-dynamic*:: +Do not try to discover dynamic clocks. + +*-d*, *--dynamic-clock* _path_:: +Also display specified dynamic clock. +Can be specified multiple times. + +*--no-discover-rtc*:: +Do not try to discover RTCs. + +*-x*, *--rtc* _path_:: +Also display specified RTC. +Can be specified multiple times. + +*-c*, *--cpu-clock* _pid_:: +Also display CPU clock of specified process. +Can be specified multiple times. + +include::man-common/help-version.adoc[] + +== OUTPUT COLUMNS + +Each column has a type. Types are surround by < and >. + +TYPE <``string``>:: +Clock type. + +ID <``number``>:: +Numeric clock ID. + +CLOCK <``string``>:: +Name in the form *CLOCK_* + +NAME <``string``>:: +Shorter, easier to read name. + +TIME <``number``>:: +Current clock timestamp as returned by *clock_gettime()*. + +ISO_TIME <``string``>:: +ISO8601 formatted version of *TIME*. + +RESOL_RAW <``number``>:: +Clock resolution as returned by *clock_getres*(2). + +RESOL <``number``>:: +Human readable version of *RESOL_RAW*. + +REL_TIME <``string``>:: +*TIME* time formatted as time range. + +NS_OFFSET <``number``>:: +Offset of the current namespace to the parent namespace as read from */proc/self/timens_offsets*. + + +== AUTHORS + +mailto:thomas@t-8ch.de[Thomas Weißschuh] + +== SEE ALSO + +*clock_getres*(2) *clock_gettime*(2) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/lsclocks.c b/misc-utils/lsclocks.c new file mode 100644 index 0000000..867f860 --- /dev/null +++ b/misc-utils/lsclocks.c @@ -0,0 +1,697 @@ +/* + * lsclocks(1) - display system clocks + * + * Copyright (C) 2023 Thomas Weißschuh <thomas@t-8ch.de> + * + * This program 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 program is distributed in the hope that it would 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdbool.h> +#include <time.h> +#include <inttypes.h> +#include <getopt.h> +#include <glob.h> +#include <sys/ioctl.h> + +#include <libsmartcols.h> + +#include <linux/rtc.h> + +#include "c.h" +#include "nls.h" +#include "strutils.h" +#include "timeutils.h" +#include "closestream.h" +#include "xalloc.h" +#include "pathnames.h" +#include "all-io.h" +#include "list.h" + +#ifndef CLOCK_REALTIME +#define CLOCK_REALTIME 0 +#endif + +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC 1 +#endif + +#ifndef CLOCK_MONOTONIC_RAW +#define CLOCK_MONOTONIC_RAW 4 +#endif + +#ifndef CLOCK_REALTIME_COARSE +#define CLOCK_REALTIME_COARSE 5 +#endif + +#ifndef CLOCK_MONOTONIC_COARSE +#define CLOCK_MONOTONIC_COARSE 6 +#endif + +#ifndef CLOCK_BOOTTIME +#define CLOCK_BOOTTIME 7 +#endif + +#ifndef CLOCK_REALTIME_ALARM +#define CLOCK_REALTIME_ALARM 8 +#endif + +#ifndef CLOCK_BOOTTIME_ALARM +#define CLOCK_BOOTTIME_ALARM 9 +#endif + +#ifndef CLOCK_TAI +#define CLOCK_TAI 11 +#endif + +enum CLOCK_TYPE { + CT_SYS, + CT_PTP, + CT_CPU, + CT_RTC, +}; + +static const char *clock_type_name(enum CLOCK_TYPE type) +{ + switch (type) { + case CT_SYS: + return "sys"; + case CT_PTP: + return "ptp"; + case CT_CPU: + return "cpu"; + case CT_RTC: + return "rtc"; + } + errx(EXIT_FAILURE, _("Unknown clock type %d"), type); +} + +struct clockinfo { + enum CLOCK_TYPE type; + clockid_t id; + const char * const id_name; + const char * const name; + const char * const ns_offset_name; + bool no_id; +}; + +static const struct clockinfo clocks[] = { + { CT_SYS, CLOCK_REALTIME, "CLOCK_REALTIME", "realtime" }, + { CT_SYS, CLOCK_MONOTONIC, "CLOCK_MONOTONIC", "monotonic", + .ns_offset_name = "monotonic" }, + { CT_SYS, CLOCK_MONOTONIC_RAW, "CLOCK_MONOTONIC_RAW", "monotonic-raw" }, + { CT_SYS, CLOCK_REALTIME_COARSE, "CLOCK_REALTIME_COARSE", "realtime-coarse" }, + { CT_SYS, CLOCK_MONOTONIC_COARSE, "CLOCK_MONOTONIC_COARSE", "monotonic-coarse" }, + { CT_SYS, CLOCK_BOOTTIME, "CLOCK_BOOTTIME", "boottime", + .ns_offset_name = "boottime" }, + { CT_SYS, CLOCK_REALTIME_ALARM, "CLOCK_REALTIME_ALARM", "realtime-alarm" }, + { CT_SYS, CLOCK_BOOTTIME_ALARM, "CLOCK_BOOTTIME_ALARM", "boottime-alarm" }, + { CT_SYS, CLOCK_TAI, "CLOCK_TAI", "tai" }, +}; + +/* column IDs */ +enum { + COL_TYPE, + COL_ID, + COL_CLOCK, + COL_NAME, + COL_TIME, + COL_ISO_TIME, + COL_RESOL, + COL_RESOL_RAW, + COL_REL_TIME, + COL_NS_OFFSET, +}; + +/* column names */ +struct colinfo { + const char * const name; /* header */ + double whint; /* width hint (N < 1 is in percent of termwidth) */ + int flags; /* SCOLS_FL_* */ + int json_type; /* SCOLS_JSON_* */ + const char * const help; +}; + +/* columns descriptions */ +static const struct colinfo infos[] = { + [COL_TYPE] = { "TYPE", 1, 0, SCOLS_JSON_STRING, N_("type") }, + [COL_ID] = { "ID", 1, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, N_("numeric id") }, + [COL_CLOCK] = { "CLOCK", 1, 0, SCOLS_JSON_STRING, N_("symbolic name") }, + [COL_NAME] = { "NAME", 1, 0, SCOLS_JSON_STRING, N_("readable name") }, + [COL_TIME] = { "TIME", 1, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, N_("numeric time") }, + [COL_ISO_TIME] = { "ISO_TIME", 1, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, N_("human readable ISO time") }, + [COL_RESOL] = { "RESOL", 1, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, N_("human readable resolution") }, + [COL_RESOL_RAW] = { "RESOL_RAW", 1, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, N_("resolution") }, + [COL_REL_TIME] = { "REL_TIME", 1, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, N_("human readable relative time") }, + [COL_NS_OFFSET] = { "NS_OFFSET", 1, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, N_("namespace offset") }, +}; + +static int column_name_to_id(const char *name, size_t namesz) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(infos); i++) { + const char *cn = infos[i].name; + + if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) + return i; + } + warnx(_("unknown column: %s"), name); + + return -1; +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + size_t i; + + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options]\n"), program_invocation_short_name); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -J, --json use JSON output format\n"), out); + fputs(_(" -n, --noheadings don't print headings\n"), out); + fputs(_(" -o, --output <list> output columns\n"), out); + fputs(_(" --output-all output all columns\n"), out); + fputs(_(" -r, --raw use raw output format\n"), out); + fputs(_(" -t, --time <clock> show current time of single clock\n"), out); + fputs(_(" --no-discover-dynamic do not try to discover dynamic clocks\n"), out); + fputs(_(" -d, --dynamic-clock <path> also display specified dynamic clock\n"), out); + fputs(_(" -c, --cpu-clock <pid> also display CPU clock of specified process\n"), out); + + fputs(USAGE_SEPARATOR, out); + fprintf(out, USAGE_HELP_OPTIONS(29)); + + fputs(USAGE_COLUMNS, out); + + for (i = 0; i < ARRAY_SIZE(infos); i++) + fprintf(out, " %16s %-10s%s\n", infos[i].name, + infos[i].json_type == SCOLS_JSON_STRING? "<string>": + infos[i].json_type == SCOLS_JSON_ARRAY_STRING? "<string>": + infos[i].json_type == SCOLS_JSON_ARRAY_NUMBER? "<string>": + infos[i].json_type == SCOLS_JSON_NUMBER? "<number>": + "<boolean>", + _(infos[i].help)); + + fprintf(out, USAGE_MAN_TAIL("lsclocks(1)")); + + exit(EXIT_SUCCESS); +} + +__attribute__ ((__format__ (__printf__, 3, 4))) +static void scols_line_asprintf(struct libscols_line *ln, size_t n, const char *format, ...) +{ + char *data; + va_list args; + + va_start(args, format); + xvasprintf(&data, format, args); + va_end(args); + + scols_line_refer_data(ln, n, data); +} + +static void scols_line_format_timespec(struct libscols_line *ln, size_t n, const struct timespec *ts) +{ + scols_line_asprintf(ln, n, "%ju.%09" PRId32, (uintmax_t) ts->tv_sec, (uint32_t) ts->tv_nsec); +} + +static clockid_t parse_clock(const char *name) +{ + size_t i; + uint32_t id = -1; + int rc; + + rc = ul_strtou32(name, &id, 10); + + for (i = 0; i < ARRAY_SIZE(clocks); i++) { + if (!strcmp(name, clocks[i].id_name) + || !strcmp(name, clocks[i].name)) + return clocks[i].id; + if (rc == 0 && (clockid_t) id == clocks[i].id) + return id; + } + + errx(EXIT_FAILURE, _("Unknown clock: %s"), name); +} + +static int64_t get_namespace_offset(const char *name) +{ + char *tokstr, *buf, *saveptr, *line, *space; + uint64_t ret; + int fd; + + fd = open(_PATH_PROC_TIMENS_OFF, O_RDONLY); + if (fd == -1) + err(EXIT_FAILURE, _("Could not open %s"), _PATH_PROC_TIMENS_OFF); + + read_all_alloc(fd, &buf); + + for (tokstr = buf; ; tokstr = NULL) { + line = strtok_r(tokstr, "\n", &saveptr); + if (!line) + continue; + line = (char *) startswith(line, name); + if (!line || line[0] != ' ') + continue; + + line = (char *) skip_blank(line); + space = strchr(line, ' '); + if (space) + *space = '\0'; + ret = strtos64_or_err(line, _("Invalid offset")); + break; + } + + free(buf); + return ret; +} + +static void add_clock_line(struct libscols_table *tb, const int *columns, + size_t ncolumns, const struct clockinfo *clockinfo, + const struct timespec *now, const struct timespec *resolution) +{ + char buf[FORMAT_TIMESTAMP_MAX]; + struct libscols_line *ln; + size_t i; + int rc; + + ln = scols_table_new_line(tb, NULL); + if (!ln) + errx(EXIT_FAILURE, _("failed to allocate output line")); + + for (i = 0; i < ncolumns; i++) { + switch (columns[i]) { + case COL_TYPE: + scols_line_set_data(ln, i, clock_type_name(clockinfo->type)); + break; + case COL_ID: + if (!clockinfo->no_id) + scols_line_asprintf(ln, i, "%ju", (uintmax_t) clockinfo->id); + break; + case COL_CLOCK: + scols_line_set_data(ln, i, clockinfo->id_name); + break; + case COL_NAME: + scols_line_set_data(ln, i, clockinfo->name); + break; + case COL_TIME: + if (now->tv_nsec == -1) + break; + + scols_line_format_timespec(ln, i, now); + break; + case COL_ISO_TIME: + if (now->tv_nsec == -1) + break; + + rc = strtimespec_iso(now, + ISO_GMTIME | ISO_DATE | ISO_TIME | ISO_T | ISO_DOTNSEC | ISO_TIMEZONE, + buf, sizeof(buf)); + if (rc) + errx(EXIT_FAILURE, _("failed to format iso time")); + scols_line_set_data(ln, i, buf); + break; + case COL_RESOL: + if (resolution->tv_nsec == -1) + break; + + rc = strtimespec_relative(resolution, buf, sizeof(buf)); + if (rc) + errx(EXIT_FAILURE, _("failed to format relative time")); + scols_line_set_data(ln, i, buf); + break; + case COL_RESOL_RAW: + if (resolution->tv_nsec == -1) + break; + scols_line_format_timespec(ln, i, resolution); + break; + case COL_REL_TIME: + if (now->tv_nsec == -1) + break; + rc = strtimespec_relative(now, buf, sizeof(buf)); + if (rc) + errx(EXIT_FAILURE, _("failed to format relative time")); + scols_line_set_data(ln, i, buf); + break; + case COL_NS_OFFSET: + if (clockinfo->ns_offset_name) + scols_line_asprintf(ln, i, "%"PRId64, + get_namespace_offset(clockinfo->ns_offset_name)); + break; + } + } +} + +static void add_posix_clock_line(struct libscols_table *tb, const int *columns, + size_t ncolumns, const struct clockinfo *clockinfo) +{ + struct timespec resolution, now; + int rc; + + rc = clock_gettime(clockinfo->id, &now); + if (rc) + now.tv_nsec = -1; + + rc = clock_getres(clockinfo->id, &resolution); + if (rc) + resolution.tv_nsec = -1; + + add_clock_line(tb, columns, ncolumns, clockinfo, &now, &resolution); +} + +struct path_clock { + struct list_head head; + const char * path; +}; + +static void add_dynamic_clock_from_path(struct libscols_table *tb, + const int *columns, size_t ncolumns, + const char *path, bool explicit) +{ + int fd = open(path, O_RDONLY); + if (fd == -1) { + if (explicit) + err(EXIT_FAILURE, _("Could not open %s"), path); + else + return; + } + + struct clockinfo clockinfo = { + .type = CT_PTP, + .no_id = true, + .id_name = path, + .name = path, + }; + add_posix_clock_line(tb, columns, ncolumns, &clockinfo); + close(fd); +} + +static void add_dynamic_clocks_from_discovery(struct libscols_table *tb, + const int *columns, size_t ncolumns) +{ + int rc; + size_t i; + glob_t state; + + rc = glob("/dev/ptp*", 0, NULL, &state); + if (rc == GLOB_NOMATCH) + return; + else if (rc) + errx(EXIT_FAILURE, _("Could not glob: %d"), rc); + + for (i = 0; i < state.gl_pathc; i++) + add_dynamic_clock_from_path(tb, columns, ncolumns, + state.gl_pathv[i], false); + + globfree(&state); +} + +static void add_rtc_clock_from_path(struct libscols_table *tb, + const int *columns, size_t ncolumns, + const char *path, bool explicit) +{ + int fd, rc; + struct rtc_time rtc_time; + struct tm tm = { 0 }; + struct timespec now = { 0 }, resolution = { .tv_nsec = -1 }; + + fd = open(path, O_RDONLY); + if (fd == -1) { + if (explicit) + err(EXIT_FAILURE, _("Could not open %s"), path); + else + return; + } + + rc = ioctl(fd, RTC_RD_TIME, &rtc_time); + if (rc) + err(EXIT_FAILURE, + _("ioctl(RTC_RD_NAME) to %s to read the time failed"), path); + + tm.tm_sec = rtc_time.tm_sec; + tm.tm_min = rtc_time.tm_min; + tm.tm_hour = rtc_time.tm_hour; + tm.tm_mday = rtc_time.tm_mday; + tm.tm_mon = rtc_time.tm_mon; + tm.tm_year = rtc_time.tm_year; + tm.tm_wday = rtc_time.tm_wday; + tm.tm_yday = rtc_time.tm_yday; + + now.tv_sec = mktime(&tm); + + struct clockinfo clockinfo = { + .type = CT_RTC, + .no_id = true, + .id_name = path, + .name = path, + }; + add_clock_line(tb, columns, ncolumns, &clockinfo, &now, &resolution); + + close(fd); +} + +static void add_rtc_clocks_from_discovery(struct libscols_table *tb, + const int *columns, size_t ncolumns) +{ + int rc; + size_t i; + glob_t state; + + rc = glob("/dev/rtc*", 0, NULL, &state); + if (rc == GLOB_NOMATCH) + return; + if (rc) + errx(EXIT_FAILURE, _("Could not glob: %d"), rc); + + for (i = 0; i < state.gl_pathc; i++) + add_rtc_clock_from_path(tb, columns, ncolumns, + state.gl_pathv[i], false); + + globfree(&state); +} + +struct cpu_clock { + struct list_head head; + pid_t pid; + char name[sizeof(stringify_value(SINT_MAX(pid_t)))]; +}; + +static void add_cpu_clock(struct libscols_table *tb, + const int *columns, size_t ncolumns, + pid_t pid, const char *name) +{ + int rc; + clockid_t clockid; + + rc = clock_getcpuclockid(pid, &clockid); + if (rc) + errx(EXIT_FAILURE, _("Could not get CPU clock of process %jd: %s"), + (intmax_t) pid, strerror(rc)); + + struct clockinfo clockinfo = { + .type = CT_CPU, + .id = clockid, + .name = name, + .no_id = true, + }; + add_posix_clock_line(tb, columns, ncolumns, &clockinfo); +} + + +int main(int argc, char **argv) +{ + size_t i; + int c, rc; + const struct colinfo *colinfo; + + struct libscols_table *tb; + struct libscols_column *col; + + bool noheadings = false, raw = false, json = false, + disc_dynamic = true, disc_rtc = true; + const char *outarg = NULL; + int columns[ARRAY_SIZE(infos) * 2]; + size_t ncolumns = 0; + clockid_t clock = -1; + struct path_clock *path_clock; + struct cpu_clock *cpu_clock; + struct list_head *current_path_clock, *current_cpu_clock; + struct list_head dynamic_clocks, cpu_clocks, rtc_clocks; + + struct timespec now; + + enum { + OPT_OUTPUT_ALL = CHAR_MAX + 1, + OPT_NO_DISC_DYN, + OPT_NO_DISC_RTC, + }; + static const struct option longopts[] = { + { "noheadings", no_argument, NULL, 'n' }, + { "output", required_argument, NULL, 'o' }, + { "output-all", no_argument, NULL, OPT_OUTPUT_ALL }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { "json", no_argument, NULL, 'J' }, + { "raw", no_argument, NULL, 'r' }, + { "time", required_argument, NULL, 't' }, + { "no-discover-dynamic", no_argument, NULL, OPT_NO_DISC_DYN }, + { "dynamic-clock", required_argument, NULL, 'd' }, + { "cpu-clock", required_argument, NULL, 'c' }, + { "no-discover-rtc", no_argument, NULL, OPT_NO_DISC_RTC }, + { "rtc", required_argument, NULL, 'x' }, + { 0 } + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + INIT_LIST_HEAD(&dynamic_clocks); + INIT_LIST_HEAD(&cpu_clocks); + INIT_LIST_HEAD(&rtc_clocks); + + while ((c = getopt_long(argc, argv, "no:Jrt:d:c:x:Vh", longopts, NULL)) != -1) { + switch (c) { + case 'n': + noheadings = true; + break; + case 'o': + outarg = optarg; + break; + case OPT_OUTPUT_ALL: + for (ncolumns = 0; ncolumns < ARRAY_SIZE(infos); ncolumns++) + columns[ncolumns] = ncolumns; + break; + case 'J': + json = true; + break; + case 'r': + raw = true; + break; + case 't': + clock = parse_clock(optarg); + break; + case 'd': + path_clock = xmalloc(sizeof(*path_clock)); + path_clock->path = optarg; + list_add(&path_clock->head, &dynamic_clocks); + break; + case OPT_NO_DISC_DYN: + disc_dynamic = false; + break; + case 'c': + cpu_clock = xmalloc(sizeof(*cpu_clock)); + cpu_clock->pid = strtopid_or_err(optarg, _("failed to parse pid")); + snprintf(cpu_clock->name, sizeof(cpu_clock->name), + "%jd", (intmax_t) cpu_clock->pid); + list_add(&cpu_clock->head, &cpu_clocks); + break; + case 'x': + path_clock = xmalloc(sizeof(*path_clock)); + path_clock->path = optarg; + list_add(&path_clock->head, &rtc_clocks); + break; + case OPT_NO_DISC_RTC: + disc_rtc = false; + break; + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (argv[optind]) + errtryhelp(EXIT_FAILURE); + + if (clock != -1) { + rc = clock_gettime(clock, &now); + if (rc) + err(EXIT_FAILURE, _("failed to get time")); + printf("%ju.%09"PRId32"\n", (uintmax_t) now.tv_sec, (uint32_t) now.tv_nsec); + return EXIT_SUCCESS; + } + + if (!ncolumns) { + columns[ncolumns++] = COL_ID; + columns[ncolumns++] = COL_NAME; + columns[ncolumns++] = COL_TYPE; + columns[ncolumns++] = COL_TIME; + columns[ncolumns++] = COL_RESOL; + columns[ncolumns++] = COL_ISO_TIME; + } + + if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns), + &ncolumns, column_name_to_id) < 0) + return EXIT_FAILURE; + + scols_init_debug(0); + + tb = scols_new_table(); + if (!tb) + errx(EXIT_FAILURE, _("failed to allocate output table")); + scols_table_set_name(tb, "clocks"); + + for (i = 0; i < ncolumns; i++) { + colinfo = &infos[columns[i]]; + + col = scols_table_new_column(tb, colinfo->name, colinfo->whint, colinfo->flags); + if (!col) + errx(EXIT_FAILURE, _("failed to allocate output column")); + + scols_column_set_json_type(col, colinfo->json_type); + } + + for (i = 0; i < ARRAY_SIZE(clocks); i++) + add_posix_clock_line(tb, columns, ncolumns, &clocks[i]); + + if (disc_dynamic) + add_dynamic_clocks_from_discovery(tb, columns, ncolumns); + + list_for_each(current_path_clock, &dynamic_clocks) { + path_clock = list_entry(current_path_clock, struct path_clock, head); + add_dynamic_clock_from_path(tb, columns, ncolumns, path_clock->path, true); + } + + list_free(&dynamic_clocks, struct path_clock, head, free); + + if (disc_rtc) + add_rtc_clocks_from_discovery(tb, columns, ncolumns); + + list_for_each(current_path_clock, &rtc_clocks) { + path_clock = list_entry(current_path_clock, struct path_clock, head); + add_rtc_clock_from_path(tb, columns, ncolumns, path_clock->path, true); + } + + list_free(&rtc_clocks, struct path_clock, head, free); + + list_for_each(current_cpu_clock, &cpu_clocks) { + cpu_clock = list_entry(current_cpu_clock, struct cpu_clock, head); + add_cpu_clock(tb, columns, ncolumns, cpu_clock->pid, cpu_clock->name); + } + + list_free(&cpu_clocks, struct cpu_clock, head, free); + + scols_table_enable_json(tb, json); + scols_table_enable_raw(tb, raw); + scols_table_enable_noheadings(tb, noheadings); + scols_print_table(tb); + scols_unref_table(tb); +} diff --git a/misc-utils/lsfd-bdev.c b/misc-utils/lsfd-bdev.c index 7be99db..b61fdbb 100644 --- a/misc-utils/lsfd-bdev.c +++ b/misc-utils/lsfd-bdev.c @@ -19,10 +19,6 @@ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "xalloc.h" -#include "nls.h" -#include "libsmartcols.h" - #include "lsfd.h" static struct list_head partitions; diff --git a/misc-utils/lsfd-cdev.c b/misc-utils/lsfd-cdev.c index 4e35f15..cbb2d05 100644 --- a/misc-utils/lsfd-cdev.c +++ b/misc-utils/lsfd-cdev.c @@ -19,13 +19,10 @@ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "xalloc.h" -#include "nls.h" -#include "libsmartcols.h" - #include "lsfd.h" static struct list_head miscdevs; +static struct list_head ttydrvs; struct miscdev { struct list_head miscdevs; @@ -33,70 +30,85 @@ struct miscdev { char *name; }; +struct ttydrv { + struct list_head ttydrvs; + unsigned long major; + unsigned long minor_start, minor_end; + char *name; + unsigned int is_ptmx: 1; + unsigned int is_pts: 1; +}; + +struct cdev { + struct file file; + const char *devdrv; + const struct cdev_ops *cdev_ops; + void *cdev_data; +}; + +struct cdev_ops { + const struct cdev_ops *parent; + bool (*probe)(struct cdev *); + char * (*get_name)(struct cdev *); + bool (*fill_column)(struct proc *, + struct cdev *, + struct libscols_line *, + int, + size_t, + char **); + void (*init)(const struct cdev *); + void (*free)(const struct cdev *); + void (*attach_xinfo)(struct cdev *); + int (*handle_fdinfo)(struct cdev *, const char *, const char *); + const struct ipc_class * (*get_ipc_class)(struct cdev *); +}; + static bool cdev_fill_column(struct proc *proc __attribute__((__unused__)), - struct file *file __attribute__((__unused__)), + struct file *file, struct libscols_line *ln, int column_id, size_t column_index) { + struct cdev *cdev = (struct cdev *)file; + const struct cdev_ops *ops = cdev->cdev_ops; char *str = NULL; - const char *devdrv; - const char *miscdev; switch(column_id) { + case COL_NAME: + if (cdev->cdev_ops->get_name) { + str = cdev->cdev_ops->get_name(cdev); + if (str) + break; + } + return false; case COL_TYPE: if (scols_line_set_data(ln, column_index, "CHR")) err(EXIT_FAILURE, _("failed to add output data")); return true; - case COL_MISCDEV: - devdrv = get_chrdrv(major(file->stat.st_rdev)); - if (devdrv && strcmp(devdrv, "misc") == 0) { - miscdev = get_miscdev(minor(file->stat.st_rdev)); - if (miscdev) - str = xstrdup(miscdev); - else - xasprintf(&str, "%u", - minor(file->stat.st_rdev)); - break; - } - return true; case COL_DEVTYPE: if (scols_line_set_data(ln, column_index, "char")) err(EXIT_FAILURE, _("failed to add output data")); return true; case COL_CHRDRV: - devdrv = get_chrdrv(major(file->stat.st_rdev)); - if (devdrv) - str = xstrdup(devdrv); + if (cdev->devdrv) + str = xstrdup(cdev->devdrv); else xasprintf(&str, "%u", major(file->stat.st_rdev)); break; - case COL_SOURCE: - devdrv = get_chrdrv(major(file->stat.st_rdev)); - miscdev = NULL; - if (devdrv && strcmp(devdrv, "misc") == 0) - miscdev = get_miscdev(minor(file->stat.st_rdev)); - if (devdrv) { - if (miscdev) { - xasprintf(&str, "misc:%s", miscdev); - } else { - xasprintf(&str, "%s:%u", devdrv, - minor(file->stat.st_rdev)); - } - break; - } - /* FALL THROUGH */ - case COL_MAJMIN: - xasprintf(&str, "%u:%u", - major(file->stat.st_rdev), - minor(file->stat.st_rdev)); - break; default: + while (ops) { + if (ops->fill_column + && ops->fill_column(proc, cdev, ln, + column_id, column_index, &str)) + goto out; + ops = ops->parent; + } return false; } + out: if (!str) err(EXIT_FAILURE, _("failed to add output data")); if (scols_line_refer_data(ln, column_index, str)) @@ -139,22 +151,108 @@ static void read_misc(struct list_head *miscdevs_list, FILE *misc_fp) } } +#define TTY_DRIVERS_LINE_LEN0 1023 +#define TTY_DRIVERS_LINE_LEN (TTY_DRIVERS_LINE_LEN0 + 1) +static struct ttydrv *new_ttydrv(unsigned int major, + unsigned int minor_start, unsigned int minor_end, + const char *name) +{ + struct ttydrv *ttydrv = xmalloc(sizeof(*ttydrv)); + + INIT_LIST_HEAD(&ttydrv->ttydrvs); + + ttydrv->major = major; + ttydrv->minor_start = minor_start; + ttydrv->minor_end = minor_end; + ttydrv->name = xstrdup(name); + ttydrv->is_ptmx = 0; + if (strcmp(name, "ptmx") == 0) + ttydrv->is_ptmx = 1; + ttydrv->is_pts = 0; + if (strcmp(name, "pts") == 0) + ttydrv->is_pts = 1; + + return ttydrv; +} + +static void free_ttydrv(struct ttydrv *ttydrv) +{ + free(ttydrv->name); + free(ttydrv); +} + +static bool is_pty(const struct ttydrv *ttydrv) +{ + return ttydrv->is_ptmx || ttydrv->is_pts; +} + +static struct ttydrv *read_ttydrv(const char *line) +{ + const char *p; + char name[TTY_DRIVERS_LINE_LEN]; + unsigned long major; + unsigned long minor_range[2]; + + p = strchr(line, ' '); + if (p == NULL) + return NULL; + + p = strstr(p, "/dev/"); + if (p == NULL) + return NULL; + p += (sizeof("/dev/") - 1); /* Ignore the last null byte. */ + + if (sscanf(p, "%" stringify_value(TTY_DRIVERS_LINE_LEN0) "[^ ]", name) != 1) + return NULL; + + p += strlen(name); + if (sscanf(p, " %lu %lu-%lu ", &major, + minor_range, minor_range + 1) != 3) { + if (sscanf(p, " %lu %lu ", &major, minor_range) == 2) + minor_range[1] = minor_range[0]; + else + return NULL; + } + + return new_ttydrv(major, minor_range[0], minor_range[1], name); +} + +static void read_tty_drivers(struct list_head *ttydrvs_list, FILE *ttydrvs_fp) +{ + char line[TTY_DRIVERS_LINE_LEN]; + + while (fgets(line, sizeof(line), ttydrvs_fp)) { + struct ttydrv *ttydrv = read_ttydrv(line); + if (ttydrv) + list_add_tail(&ttydrv->ttydrvs, ttydrvs_list); + } +} + static void cdev_class_initialize(void) { FILE *misc_fp; + FILE *ttydrvs_fp; INIT_LIST_HEAD(&miscdevs); + INIT_LIST_HEAD(&ttydrvs); misc_fp = fopen("/proc/misc", "r"); if (misc_fp) { read_misc(&miscdevs, misc_fp); fclose(misc_fp); } + + ttydrvs_fp = fopen("/proc/tty/drivers", "r"); + if (ttydrvs_fp) { + read_tty_drivers(&ttydrvs, ttydrvs_fp); + fclose(ttydrvs_fp); + } } static void cdev_class_finalize(void) { list_free(&miscdevs, struct miscdev, miscdevs, free_miscdev); + list_free(&ttydrvs, struct ttydrv, ttydrvs, free_ttydrv); } const char *get_miscdev(unsigned long minor) @@ -168,11 +266,466 @@ const char *get_miscdev(unsigned long minor) return NULL; } +static const struct ttydrv *get_ttydrv(unsigned long major, + unsigned long minor) +{ + struct list_head *c; + + list_for_each(c, &ttydrvs) { + struct ttydrv *ttydrv = list_entry(c, struct ttydrv, ttydrvs); + if (ttydrv->major == major + && ttydrv->minor_start <= minor + && minor <= ttydrv->minor_end) + return ttydrv; + } + + return NULL; +} + + +/* + * generic (fallback implementation) + */ +static bool cdev_generic_probe(struct cdev *cdev __attribute__((__unused__))) { + return true; +} + +static bool cdev_generic_fill_column(struct proc *proc __attribute__((__unused__)), + struct cdev *cdev, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct file *file = &cdev->file; + + switch(column_id) { + case COL_SOURCE: + if (cdev->devdrv) { + xasprintf(str, "%s:%u", cdev->devdrv, + minor(file->stat.st_rdev)); + return true; + } + /* FALL THROUGH */ + case COL_MAJMIN: + xasprintf(str, "%u:%u", + major(file->stat.st_rdev), + minor(file->stat.st_rdev)); + return true; + default: + return false; + } +} + +static struct cdev_ops cdev_generic_ops = { + .probe = cdev_generic_probe, + .fill_column = cdev_generic_fill_column, +}; + +/* + * misc device driver + */ +static bool cdev_misc_probe(struct cdev *cdev) { + return cdev->devdrv && strcmp(cdev->devdrv, "misc") == 0; +} + +static bool cdev_misc_fill_column(struct proc *proc __attribute__((__unused__)), + struct cdev *cdev, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct file *file = &cdev->file; + const char *miscdev; + + switch(column_id) { + case COL_MISCDEV: + miscdev = get_miscdev(minor(file->stat.st_rdev)); + if (miscdev) + *str = xstrdup(miscdev); + else + xasprintf(str, "%u", + minor(file->stat.st_rdev)); + return true; + case COL_SOURCE: + miscdev = get_miscdev(minor(file->stat.st_rdev)); + if (miscdev) + xasprintf(str, "misc:%s", miscdev); + else + xasprintf(str, "misc:%u", + minor(file->stat.st_rdev)); + return true; + } + return false; +} + +static struct cdev_ops cdev_misc_ops = { + .parent = &cdev_generic_ops, + .probe = cdev_misc_probe, + .fill_column = cdev_misc_fill_column, +}; + +/* + * tun devcie driver + */ +static bool cdev_tun_probe(struct cdev *cdev) +{ + const char *miscdev; + + if ((!cdev->devdrv) || strcmp(cdev->devdrv, "misc")) + return false; + + miscdev = get_miscdev(minor(cdev->file.stat.st_rdev)); + if (miscdev && strcmp(miscdev, "tun") == 0) + return true; + return false; +} + +static void cdev_tun_free(const struct cdev *cdev) +{ + if (cdev->cdev_data) + free(cdev->cdev_data); +} + +static char * cdev_tun_get_name(struct cdev *cdev) +{ + char *str = NULL; + + if (cdev->cdev_data == NULL) + return NULL; + + xasprintf(&str, "iface=%s", (const char *)cdev->cdev_data); + return str; +} + +static bool cdev_tun_fill_column(struct proc *proc __attribute__((__unused__)), + struct cdev *cdev, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + switch(column_id) { + case COL_MISCDEV: + *str = xstrdup("tun"); + return true; + case COL_SOURCE: + *str = xstrdup("misc:tun"); + return true; + case COL_TUN_IFACE: + if (cdev->cdev_data) { + *str = xstrdup(cdev->cdev_data); + return true; + } + } + return false; +} + +static int cdev_tun_handle_fdinfo(struct cdev *cdev, const char *key, const char *val) +{ + if (strcmp(key, "iff") == 0 && cdev->cdev_data == NULL) { + cdev->cdev_data = xstrdup(val); + return 1; + } + return false; +} + +static struct cdev_ops cdev_tun_ops = { + .parent = &cdev_misc_ops, + .probe = cdev_tun_probe, + .free = cdev_tun_free, + .get_name = cdev_tun_get_name, + .fill_column = cdev_tun_fill_column, + .handle_fdinfo = cdev_tun_handle_fdinfo, +}; + +/* + * tty devices + */ +struct ttydata { + struct cdev *cdev; + const struct ttydrv *drv; +#define NO_TTY_INDEX -1 + int tty_index; /* used only in ptmx devices */ + struct ipc_endpoint endpoint; +}; + +static bool cdev_tty_probe(struct cdev *cdev) { + const struct ttydrv *ttydrv = get_ttydrv(major(cdev->file.stat.st_rdev), + minor(cdev->file.stat.st_rdev)); + struct ttydata *data; + + if (!ttydrv) + return false; + + data = xmalloc(sizeof(struct ttydata)); + data->cdev = cdev; + data->drv = ttydrv; + data->tty_index = NO_TTY_INDEX; + cdev->cdev_data = data; + + return true; +} + +static void cdev_tty_free(const struct cdev *cdev) +{ + if (cdev->cdev_data) + free(cdev->cdev_data); +} + +static char * cdev_tty_get_name(struct cdev *cdev) +{ + struct ttydata *data = cdev->cdev_data; + char *str = NULL; + + if (!data->drv->is_ptmx) + return NULL; + + if (data->tty_index == NO_TTY_INDEX) + str = xstrdup("tty-index="); + else + xasprintf(&str, "tty-index=%d", data->tty_index); + return str; +} + +static inline char *cdev_tty_xstrendpoint(struct file *file) +{ + char *str = NULL; + xasprintf(&str, "%d,%s,%d%c%c", + file->proc->pid, file->proc->command, file->association, + (file->mode & S_IRUSR)? 'r': '-', + (file->mode & S_IWUSR)? 'w': '-'); + return str; +} + +static bool cdev_tty_fill_column(struct proc *proc __attribute__((__unused__)), + struct cdev *cdev, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct file *file = &cdev->file; + struct ttydata *data = cdev->cdev_data; + + switch(column_id) { + case COL_SOURCE: + if (data->drv->minor_start == data->drv->minor_end) + *str = xstrdup(data->drv->name); + else + xasprintf(str, "%s:%u", data->drv->name, + minor(file->stat.st_rdev)); + return true; + case COL_PTMX_TTY_INDEX: + if (data->drv->is_ptmx) { + xasprintf(str, "%d", data->tty_index); + return true; + } + return false; + case COL_ENDPOINTS: + if (is_pty(data->drv)) { + struct ttydata *this = data; + struct list_head *e; + foreach_endpoint(e, data->endpoint) { + char *estr; + struct ttydata *other = list_entry(e, struct ttydata, endpoint.endpoints); + if (this == other) + continue; + + if ((this->drv->is_ptmx && !other->drv->is_pts) + || (this->drv->is_pts && !other->drv->is_ptmx)) + continue; + + if (*str) + xstrputc(str, '\n'); + estr = cdev_tty_xstrendpoint(&other->cdev->file); + xstrappend(str, estr); + free(estr); + } + if (*str) + return true; + } + return false; + default: + return false; + } +} + +static int cdev_tty_handle_fdinfo(struct cdev *cdev, const char *key, const char *val) +{ + struct ttydata *data = cdev->cdev_data; + + if (!data->drv->is_ptmx) + return 0; + + if (strcmp(key, "tty-index") == 0) { + errno = 0; + data->tty_index = (int)strtol(val, NULL, 10); + if (errno) { + data->tty_index = NO_TTY_INDEX; + return 0; + } + return 1; + } + + return 0; +} + +struct cdev_pty_ipc { + struct ipc ipc; + int tty_index; +}; + +static unsigned int cdev_pty_get_hash(struct file *file) +{ + struct cdev *cdev = (struct cdev *)file; + struct ttydata *data = cdev->cdev_data; + + return data->drv->is_ptmx? + (unsigned int)data->tty_index: + (unsigned int)minor(file->stat.st_rdev); +} + +static bool cdev_pty_is_suitable_ipc(struct ipc *ipc, struct file *file) +{ + struct cdev *cdev = (struct cdev *)file; + struct ttydata *data = cdev->cdev_data; + struct cdev_pty_ipc *cdev_pty_ipc = (struct cdev_pty_ipc *)ipc; + + return (data->drv->is_ptmx)? + cdev_pty_ipc->tty_index == (int)data->tty_index: + cdev_pty_ipc->tty_index == (int)minor(file->stat.st_rdev); +} + +static const struct ipc_class *cdev_tty_get_ipc_class(struct cdev *cdev) +{ + static const struct ipc_class cdev_pty_ipc_class = { + .size = sizeof(struct cdev_pty_ipc), + .get_hash = cdev_pty_get_hash, + .is_suitable_ipc = cdev_pty_is_suitable_ipc, + }; + + struct ttydata *data = cdev->cdev_data; + + if (is_pty(data->drv)) + return &cdev_pty_ipc_class; + + return NULL; +} + +static void cdev_tty_attach_xinfo(struct cdev *cdev) +{ + struct ttydata *data = cdev->cdev_data; + struct ipc *ipc; + unsigned int hash; + + + if (! is_pty(data->drv)) + return; + + init_endpoint(&data->endpoint); + ipc = get_ipc(&cdev->file); + if (ipc) + goto link; + + ipc = new_ipc(cdev_tty_get_ipc_class(cdev)); + hash = cdev_pty_get_hash(&cdev->file); + ((struct cdev_pty_ipc *)ipc)->tty_index = (int)hash; + + add_ipc(ipc, hash); + link: + add_endpoint(&data->endpoint, ipc); +} + +static struct cdev_ops cdev_tty_ops = { + .parent = &cdev_generic_ops, + .probe = cdev_tty_probe, + .free = cdev_tty_free, + .get_name = cdev_tty_get_name, + .fill_column = cdev_tty_fill_column, + .attach_xinfo = cdev_tty_attach_xinfo, + .handle_fdinfo = cdev_tty_handle_fdinfo, + .get_ipc_class = cdev_tty_get_ipc_class, +}; + +static const struct cdev_ops *cdev_ops[] = { + &cdev_tun_ops, + &cdev_misc_ops, + &cdev_tty_ops, + &cdev_generic_ops /* This must be at the end. */ +}; + +static const struct cdev_ops *cdev_probe(struct cdev *cdev) +{ + const struct cdev_ops *r = NULL; + + for (size_t i = 0; i < ARRAY_SIZE(cdev_ops); i++) { + if (cdev_ops[i]->probe(cdev)) { + r = cdev_ops[i]; + break; + } + } + + assert(r); + return r; +} + +static void init_cdev_content(struct file *file) +{ + struct cdev *cdev = (struct cdev *)file; + + cdev->devdrv = get_chrdrv(major(file->stat.st_rdev)); + + cdev->cdev_data = NULL; + cdev->cdev_ops = cdev_probe(cdev); + if (cdev->cdev_ops->init) + cdev->cdev_ops->init(cdev); +} + +static void free_cdev_content(struct file *file) +{ + struct cdev *cdev = (struct cdev *)file; + + if (cdev->cdev_ops->free) + cdev->cdev_ops->free(cdev); +} + +static void cdev_attach_xinfo(struct file *file) +{ + struct cdev *cdev = (struct cdev *)file; + + if (cdev->cdev_ops->attach_xinfo) + cdev->cdev_ops->attach_xinfo(cdev); +} + +static int cdev_handle_fdinfo(struct file *file, const char *key, const char *value) +{ + struct cdev *cdev = (struct cdev *)file; + + if (cdev->cdev_ops->handle_fdinfo) + return cdev->cdev_ops->handle_fdinfo(cdev, key, value); + return 0; /* Should be handled in parents */ +} + +static const struct ipc_class *cdev_get_ipc_class(struct file *file) +{ + struct cdev *cdev = (struct cdev *)file; + + if (cdev->cdev_ops->get_ipc_class) + return cdev->cdev_ops->get_ipc_class(cdev); + return NULL; +} + const struct file_class cdev_class = { .super = &file_class, - .size = sizeof(struct file), + .size = sizeof(struct cdev), .initialize_class = cdev_class_initialize, .finalize_class = cdev_class_finalize, .fill_column = cdev_fill_column, - .free_content = NULL, + .initialize_content = init_cdev_content, + .free_content = free_cdev_content, + .attach_xinfo = cdev_attach_xinfo, + .handle_fdinfo = cdev_handle_fdinfo, + .get_ipc_class = cdev_get_ipc_class, }; diff --git a/misc-utils/lsfd-counter.c b/misc-utils/lsfd-counter.c deleted file mode 100644 index 55f0fde..0000000 --- a/misc-utils/lsfd-counter.c +++ /dev/null @@ -1,56 +0,0 @@ -/* - * lsfd-counter.c - counter implementation used in --summary option - * - * Copyright (C) 2021 Red Hat, Inc. - * Copyright (C) 2021 Masatake YAMATO <yamato@redhat.com> - * - * This file may be redistributed under the terms of the - * GNU Lesser General Public License. - */ - -#include "lsfd-counter.h" - -#include "xalloc.h" - -struct lsfd_counter { - char *name; - size_t value; - struct lsfd_filter *filter; -}; - -struct lsfd_counter *lsfd_counter_new(const char *const name, struct lsfd_filter *filter) -{ - struct lsfd_counter *counter = xmalloc(sizeof(struct lsfd_counter)); - - counter->name = xstrdup(name); - counter->value = 0; - counter->filter = filter; - - return counter; -} - -void lsfd_counter_free(struct lsfd_counter *counter) -{ - lsfd_filter_free(counter->filter); - free(counter->name); - free(counter); -} - -bool lsfd_counter_accumulate(struct lsfd_counter *counter, struct libscols_line *ln) -{ - if (lsfd_filter_apply(counter->filter, ln)) { - counter->value++; - return true; - } - return false; -} - -const char *lsfd_counter_name(struct lsfd_counter *counter) -{ - return counter->name; -} - -size_t lsfd_counter_value(struct lsfd_counter *counter) -{ - return counter->value; -} diff --git a/misc-utils/lsfd-counter.h b/misc-utils/lsfd-counter.h deleted file mode 100644 index bac11a9..0000000 --- a/misc-utils/lsfd-counter.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * lsfd-counter.h - counter implementation used in --summary option - * - * Copyright (C) 2021 Red Hat, Inc. - * Copyright (C) 2021 Masatake YAMATO <yamato@redhat.com> - * - * This file may be redistributed under the terms of the - * GNU Lesser General Public License. - */ -#ifndef UTIL_LINUX_LSFD_COUNTER_H -#define UTIL_LINUX_LSFD_COUNTER_H - -#include "libsmartcols.h" -#include "lsfd-filter.h" -#include <stdbool.h> - -struct lsfd_counter; - -/* The created counter takes the ownership of the filter; the filter is - * freed in lsfd_counter_free(). - */ -struct lsfd_counter *lsfd_counter_new(const char *const name, struct lsfd_filter *filter); -void lsfd_counter_free(struct lsfd_counter *counter); - -bool lsfd_counter_accumulate(struct lsfd_counter *counter, struct libscols_line *ln); - -const char *lsfd_counter_name(struct lsfd_counter *counter); -size_t lsfd_counter_value(struct lsfd_counter *counter); - -#endif /* UTIL_LINUX_LSFD_COUNTER_H */ diff --git a/misc-utils/lsfd-fifo.c b/misc-utils/lsfd-fifo.c index f736192..b78e6d4 100644 --- a/misc-utils/lsfd-fifo.c +++ b/misc-utils/lsfd-fifo.c @@ -19,10 +19,6 @@ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "xalloc.h" -#include "nls.h" -#include "libsmartcols.h" - #include "lsfd.h" struct fifo { @@ -68,8 +64,8 @@ static bool fifo_fill_column(struct proc *proc __attribute__((__unused__)), case COL_ENDPOINTS: { struct fifo *this = (struct fifo *)file; struct list_head *e; - char *estr; - list_for_each_backwardly(e, &this->endpoint.ipc->endpoints) { + foreach_endpoint(e, this->endpoint) { + char *estr; struct fifo *other = list_entry(e, struct fifo, endpoint.endpoints); if (this == other) continue; @@ -107,6 +103,7 @@ static bool fifo_is_suitable_ipc(struct ipc *ipc, struct file *file) static const struct ipc_class *fifo_get_ipc_class(struct file *file __attribute__((__unused__))) { static const struct ipc_class fifo_ipc_class = { + .size = sizeof(struct fifo_ipc), .get_hash = fifo_get_hash, .is_suitable_ipc = fifo_is_suitable_ipc, .free = NULL, @@ -120,22 +117,18 @@ static void fifo_initialize_content(struct file *file) struct ipc *ipc; unsigned int hash; - INIT_LIST_HEAD(&fifo->endpoint.endpoints); + init_endpoint(&fifo->endpoint); ipc = get_ipc(file); if (ipc) goto link; - ipc = xmalloc(sizeof(struct fifo_ipc)); - ipc->class = fifo_get_ipc_class(file); - INIT_LIST_HEAD(&ipc->endpoints); - INIT_LIST_HEAD(&ipc->ipcs); + ipc = new_ipc(fifo_get_ipc_class(file)); ((struct fifo_ipc *)ipc)->ino = file->stat.st_ino; hash = fifo_get_hash(file); add_ipc(ipc, hash); link: - fifo->endpoint.ipc = ipc; - list_add(&fifo->endpoint.endpoints, &ipc->endpoints); + add_endpoint(&fifo->endpoint, ipc); } const struct file_class fifo_class = { diff --git a/misc-utils/lsfd-file.c b/misc-utils/lsfd-file.c index bdaac3f..9b91462 100644 --- a/misc-utils/lsfd-file.c +++ b/misc-utils/lsfd-file.c @@ -32,19 +32,24 @@ # endif #endif #include <linux/sched.h> +#include <sys/shm.h> + +#include <fcntl.h> +#include <sys/stat.h> +#include <mqueue.h> /* mq_open */ -#include "xalloc.h" -#include "nls.h" #include "buffer.h" #include "idcache.h" #include "strutils.h" -#include "libsmartcols.h" +#include "procfs.h" #include "lsfd.h" static struct idcache *username_cache; +static size_t pagesize; + static const char *assocstr[N_ASSOCS] = { [ASSOC_CWD] = "cwd", [ASSOC_EXE] = "exe", @@ -102,16 +107,58 @@ static uint64_t get_map_length(struct file *file) { uint64_t res = 0; - if (is_association(file, SHM) || is_association(file, MEM)) { - static size_t pagesize = 0; + if (is_association(file, SHM) || is_association(file, MEM)) + res = (file->map_end - file->map_start) / pagesize; - if (!pagesize) - pagesize = getpagesize(); + return res; +} - res = (file->map_end - file->map_start) / pagesize; +void decode_source(char *buf, size_t bufsize, + unsigned int dev_major, unsigned int dev_minor, + enum decode_source_level level) +{ + if (bufsize == 0) + return; + + buf[0] = '\0'; + + if (level & DECODE_SOURCE_FILESYS_BIT) { + if (dev_major == 0) { + const char *filesystem = get_nodev_filesystem(dev_minor); + if (filesystem) { + xstrncpy(buf, filesystem, bufsize); + return; + } + } } - return res; + if (level & DECODE_SOURCE_PARTITION_BIT) { + dev_t dev = makedev(dev_major, dev_minor); + const char *partition = get_partition(dev); + if (partition) { + xstrncpy(buf, partition, bufsize); + return; + } + } + + if (level & DECODE_SOURCE_MAJMIN_BIT) + snprintf(buf, bufsize, "%u:%u", + dev_major, + dev_minor); +} + +static char *strnrstr(const char *haystack, const char *needle, size_t needle_len) +{ + char *last = strstr(haystack, needle); + if (last == NULL) + return NULL; + + do { + char *current = strstr(last + needle_len, needle); + if (current == NULL) + return last; + last = current; + } while (1); } static bool file_fill_column(struct proc *proc, @@ -122,7 +169,7 @@ static bool file_fill_column(struct proc *proc, { char *str = NULL; mode_t ftype; - const char *partition; + char buf[BUFSIZ]; switch(column_id) { case COL_COMMAND: @@ -130,8 +177,22 @@ static bool file_fill_column(struct proc *proc, && scols_line_set_data(ln, column_index, proc->command)) err(EXIT_FAILURE, _("failed to add output data")); return true; - case COL_KNAME: case COL_NAME: + if (file->name && file->stat.st_nlink == 0) { + char *d = strnrstr(file->name, "(deleted)", + sizeof("(deleted)") - 1); + if (d) { + int r; + *d = '\0'; + r = scols_line_set_data(ln, column_index, file->name); + *d = '('; + if (r) + err(EXIT_FAILURE, _("failed to add output data")); + return true; + } + } + /* FALL THROUGH */ + case COL_KNAME: if (file->name && scols_line_set_data(ln, column_index, file->name)) err(EXIT_FAILURE, _("failed to add output data")); @@ -172,33 +233,27 @@ static bool file_fill_column(struct proc *proc, int assoc = file->association * -1; if (assoc >= N_ASSOCS) return false; /* INTERNAL ERROR */ - xasprintf(&str, "%s", assocstr[assoc]); + str = xstrdup(assocstr[assoc]); } break; case COL_INODE: xasprintf(&str, "%llu", (unsigned long long)file->stat.st_ino); break; case COL_SOURCE: - if (major(file->stat.st_dev) == 0) { - const char *filesystem = get_nodev_filesystem(minor(file->stat.st_dev)); - if (filesystem) { - xasprintf(&str, "%s", filesystem); - break; - } - } - /* FALL THROUGH */ + decode_source(buf, sizeof(buf), major(file->stat.st_dev), minor(file->stat.st_dev), + DECODE_SOURCE_FILESYS); + str = xstrdup(buf); + break; case COL_PARTITION: - partition = get_partition(file->stat.st_dev); - if (partition) { - str = xstrdup(partition); - break; - } - /* FALL THROUGH */ + decode_source(buf, sizeof(buf), major(file->stat.st_dev), minor(file->stat.st_dev), + DECODE_SOURCE_PARTITION); + str = xstrdup(buf); + break; case COL_DEV: case COL_MAJMIN: - xasprintf(&str, "%u:%u", - major(file->stat.st_dev), - minor(file->stat.st_dev)); + decode_source(buf, sizeof(buf), major(file->stat.st_dev), minor(file->stat.st_dev), + DECODE_SOURCE_MAJMIN); + str = xstrdup(buf); break; case COL_RDEV: xasprintf(&str, "%u:%u", @@ -242,6 +297,24 @@ static bool file_fill_column(struct proc *proc, else xasprintf(&str, "---"); break; + case COL_XMODE: { + char r, w, x; + char D = file->stat.st_nlink == 0? 'D': '-'; + char L = file->locked.write? 'L' + :file->locked.read? 'l' + : '-'; + char m = file->multiplexed? 'm': '-'; + + if (does_file_has_fdinfo_alike(file)) { + r = file->mode & S_IRUSR? 'r': '-'; + w = file->mode & S_IWUSR? 'w': '-'; + x = (is_mapped_file(file) + && file->mode & S_IXUSR)? 'x': '-'; + } else + r = w = x = '-'; + xasprintf(&str, "%c%c%c%c%c%c", r, w, x, D, L, m); + break; + } case COL_POS: xasprintf(&str, "%" PRIu64, (does_file_has_fdinfo_alike(file))? file->pos: 0); @@ -268,7 +341,7 @@ static bool file_fill_column(struct proc *proc, break; default: return false; - }; + } if (!str) err(EXIT_FAILURE, _("failed to add output data")); @@ -277,6 +350,40 @@ static bool file_fill_column(struct proc *proc, return true; } +enum lock_mode { + LOCK_NONE, + READ_LOCK, + WRITE_LOCK, +}; + +static unsigned int parse_lock_line(const char *line) +{ + char mode[6] = {0}; + + /* Exapmles of lines: + ---------------------------------------------------- + 1: FLOCK ADVISORY READ 2283292 fd:03:26219728 0 EOF + 1: FLOCK ADVISORY WRITE 2283321 fd:03:26219728 0 EOF + 1: POSIX ADVISORY READ 2283190 fd:03:26219728 0 0 + 1: POSIX ADVISORY WRITE 2283225 fd:03:26219728 0 0 + 1: OFDLCK ADVISORY READ -1 fd:03:26219728 0 0 + 1: OFDLCK ADVISORY WRITE -1 fd:03:26219728 0 0 + 1: LEASE ACTIVE WRITE 2328907 fd:03:26219472 0 EOF + 1: LEASE ACTIVE READ 2326777 fd:03:26219472 0 EOF + ---------------------------------------------------- */ + + if (sscanf(line, "%*d: %*s %*s %5s %*s", mode) != 1) + return LOCK_NONE; + + if (strcmp(mode, "READ") == 0) + return READ_LOCK; + + if (strcmp(mode, "WRITE") == 0) + return WRITE_LOCK; + + return LOCK_NONE; +} + static int file_handle_fdinfo(struct file *file, const char *key, const char* value) { int rc; @@ -290,6 +397,16 @@ static int file_handle_fdinfo(struct file *file, const char *key, const char* va } else if (strcmp(key, "mnt_id") == 0) { rc = ul_strtou32(value, &file->mnt_id, 10); + } else if (strcmp(key, "lock") == 0) { + switch (parse_lock_line(value)) { + case READ_LOCK: + file->locked.read = 1; + break; + case WRITE_LOCK: + file->locked.write = 1; + break; + } + rc = 1; } else return 0; /* ignore -- unknown item */ @@ -304,11 +421,95 @@ static void file_free_content(struct file *file) free(file->name); } +static unsigned long get_minor_for_sysvipc(void) +{ + int id; + void *start; + + pid_t self = getpid(); + struct path_cxt *pc = NULL; + char map_file[sizeof("map_files/0000000000000000-ffffffffffffffff")]; + + struct stat sb; + unsigned long m = 0; + + id = shmget(IPC_PRIVATE, pagesize, IPC_CREAT | 0600); + if (id == -1) + return 0; + + start = shmat(id, NULL, SHM_RDONLY); + if (start == (void *) -1) { + shmctl(id, IPC_RMID, NULL); + return 0; + } + + pc = ul_new_path(NULL); + if (!pc) + goto out; + + if (procfs_process_init_path(pc, self) != 0) + goto out; + + snprintf(map_file, sizeof(map_file), + "map_files/%lx-%lx", (long)start, (long)start + pagesize); + if (ul_path_stat(pc, &sb, 0, map_file) < 0) + goto out; + + m = minor(sb.st_dev); + out: + if (pc) + ul_unref_path(pc); + shmdt(start); + shmctl(id, IPC_RMID, NULL); + return m; +} + +static unsigned long get_minor_for_mqueue(void) +{ + mqd_t mq; + char mq_name[BUFSIZ]; + struct mq_attr attr = { + .mq_maxmsg = 1, + .mq_msgsize = 1, + }; + + pid_t self = getpid(); + struct stat sb; + + snprintf(mq_name, sizeof(mq_name), "/.lsfd-mqueue-nodev-test:%d", self); + mq = mq_open(mq_name, O_CREAT|O_EXCL | O_RDONLY, S_IRUSR | S_IWUSR, &attr); + if (mq < 0) + return 0; + + if (fstat((int)mq, &sb) < 0) { + mq_close(mq); + mq_unlink(mq_name); + return 0; + } + + mq_close(mq); + mq_unlink(mq_name); + return minor(sb.st_dev); +} + static void file_class_initialize(void) { + unsigned long m; + + if (!pagesize) + pagesize = getpagesize(); + username_cache = new_idcache(); if (!username_cache) err(EXIT_FAILURE, _("failed to allocate UID cache")); + + m = get_minor_for_sysvipc(); + if (m) + add_nodev(m, "tmpfs"); + + m = get_minor_for_mqueue(); + if (m) + add_nodev(m, "mqueue"); } static void file_class_finalize(void) @@ -373,25 +574,25 @@ static void init_nsfs_file_content(struct file *file) int ns_fd; int ns_type; - if (is_association (file, NS_CGROUP)) + if (is_association(file, NS_CGROUP)) nsfs_file->clone_type = CLONE_NEWCGROUP; - else if (is_association (file, NS_IPC)) + else if (is_association(file, NS_IPC)) nsfs_file->clone_type = CLONE_NEWIPC; - else if (is_association (file, NS_MNT)) + else if (is_association(file, NS_MNT)) nsfs_file->clone_type = CLONE_NEWNS; - else if (is_association (file, NS_NET)) + else if (is_association(file, NS_NET)) nsfs_file->clone_type = CLONE_NEWNET; - else if (is_association (file, NS_PID) - || is_association (file, NS_PID4C)) + else if (is_association(file, NS_PID) + || is_association(file, NS_PID4C)) nsfs_file->clone_type = CLONE_NEWPID; #ifdef CLONE_NEWTIME - else if (is_association (file, NS_TIME) - || is_association (file, NS_TIME4C)) + else if (is_association(file, NS_TIME) + || is_association(file, NS_TIME4C)) nsfs_file->clone_type = CLONE_NEWTIME; #endif - else if (is_association (file, NS_USER)) + else if (is_association(file, NS_USER)) nsfs_file->clone_type = CLONE_NEWUSER; - else if (is_association (file, NS_UTS)) + else if (is_association(file, NS_UTS)) nsfs_file->clone_type = CLONE_NEWUTS; if (nsfs_file->clone_type != -1) @@ -463,3 +664,122 @@ const struct file_class nsfs_file_class = { .fill_column = nsfs_file_fill_column, .handle_fdinfo = NULL, }; + +/* + * POSIX Mqueue + */ +struct mqueue_file { + struct file file; + struct ipc_endpoint endpoint; +}; + +struct mqueue_file_ipc { + struct ipc ipc; + ino_t ino; +}; + +bool is_mqueue_dev(dev_t dev) +{ + const char *fs = get_nodev_filesystem(minor(dev)); + + if (fs && (strcmp (fs, "mqueue") == 0)) + return true; + + return false; +} + +static inline char *mqueue_file_xstrendpoint(struct file *file) +{ + char *str = NULL; + xasprintf(&str, "%d,%s,%d%c%c", + file->proc->pid, file->proc->command, file->association, + (file->mode & S_IRUSR)? 'r': '-', + (file->mode & S_IWUSR)? 'w': '-'); + return str; +} + +static bool mqueue_file_fill_column(struct proc *proc __attribute__((__unused__)), + struct file *file __attribute__((__unused__)), + struct libscols_line *ln, + int column_id, + size_t column_index) +{ + switch (column_id) { + case COL_TYPE: + if (scols_line_set_data(ln, column_index, "mqueue")) + err(EXIT_FAILURE, _("failed to add output data")); + return true; + case COL_ENDPOINTS: { + char *str = NULL; + struct mqueue_file *this = (struct mqueue_file *)file; + struct list_head *e; + foreach_endpoint(e, this->endpoint) { + char *estr; + struct mqueue_file *other = list_entry(e, struct mqueue_file, + endpoint.endpoints); + if (this == other) + continue; + if (str) + xstrputc(&str, '\n'); + estr = mqueue_file_xstrendpoint(&other->file); + xstrappend(&str, estr); + free(estr); + } + if (!str) + return false; + if (scols_line_refer_data(ln, column_index, str)) + err(EXIT_FAILURE, _("failed to add output data")); + return true; + } + default: + return false; + } +} + +static unsigned int mqueue_file_get_hash(struct file *file) +{ + return (unsigned int)(file->stat.st_ino % UINT_MAX); +} + +static bool mqueue_file_is_suitable_ipc(struct ipc *ipc, struct file *file) +{ + return ((struct mqueue_file_ipc *)ipc)->ino == file->stat.st_ino; +} + +static const struct ipc_class *mqueue_file_get_ipc_class(struct file *file __attribute__((__unused__))) +{ + static const struct ipc_class mqueue_file_ipc_class = { + .size = sizeof(struct mqueue_file_ipc), + .get_hash = mqueue_file_get_hash, + .is_suitable_ipc = mqueue_file_is_suitable_ipc, + }; + return &mqueue_file_ipc_class; +} + +static void init_mqueue_file_content(struct file *file) +{ + struct mqueue_file *mqueue_file = (struct mqueue_file *)file; + struct ipc *ipc; + unsigned int hash; + + init_endpoint(&mqueue_file->endpoint); + ipc = get_ipc(file); + if (ipc) + goto link; + + ipc = new_ipc(mqueue_file_get_ipc_class(file)); + ((struct mqueue_file_ipc *)ipc)->ino = file->stat.st_ino; + + hash = mqueue_file_get_hash(file); + add_ipc(ipc, hash); + link: + add_endpoint(&mqueue_file->endpoint, ipc); +} + +const struct file_class mqueue_file_class = { + .super = &file_class, + .size = sizeof(struct mqueue_file), + .initialize_content = init_mqueue_file_content, + .fill_column = mqueue_file_fill_column, + .get_ipc_class = mqueue_file_get_ipc_class, +}; diff --git a/misc-utils/lsfd-filter.c b/misc-utils/lsfd-filter.c deleted file mode 100644 index 8a4a4d8..0000000 --- a/misc-utils/lsfd-filter.c +++ /dev/null @@ -1,1409 +0,0 @@ -/* - * lsfd-filter.c - filtering engine for lsfd - * - * Copyright (C) 2021 Red Hat, Inc. - * Copyright (C) 2021 Masatake YAMATO <yamato@redhat.com> - * - * This file may be redistributed under the terms of the - * GNU Lesser General Public License. - */ - -#include "lsfd-filter.h" - -#include "nls.h" -#include "strutils.h" -#include "xalloc.h" - -#include <string.h> -#include <ctype.h> -#include <regex.h> /* regcomp(), regexec() */ - -/* - * Definitions - */ -#define COL_HEADER_EXTRA_CHARS ":-_%." /* ??? */ -#define GOT_ERROR(PARSERorFILTER)(*((PARSERorFILTER)->errmsg)) - -/* - * Types - */ - -enum token_type { - TOKEN_NAME, /* [A-Za-z_][-_:%.A-Za-z0-9]* */ - TOKEN_STR, /* "...", '...' */ - TOKEN_DEC, /* [1-9][0-9]+, NOTE: negative value is no dealt. */ - TOKEN_HEX, /* 0x[0-9a-f]+ not implemented */ - TOKEN_OCT, /* 0[1-7]+ not implemented */ - TOKEN_TRUE, /* true */ - TOKEN_FALSE, /* false */ - TOKEN_OPEN, /* ( */ - TOKEN_CLOSE, /* ) */ - TOKEN_OP1, /* !, not */ - TOKEN_OP2, /* TODO: =*, !* (glob match with fnmatch() */ - TOKEN_EOF, -}; - -enum op1_type { - OP1_NOT, -}; - -enum op2_type { - OP2_EQ, - OP2_NE, - OP2_AND, - OP2_OR, - OP2_LT, - OP2_LE, - OP2_GT, - OP2_GE, - OP2_RE_MATCH, - OP2_RE_UNMATCH, -}; - -struct token { - enum token_type type; - union { - char *str; - unsigned long long num; - enum op1_type op1; - enum op2_type op2; - } val; -}; - -struct token_class { - const char *name; - void (*free)(struct token *); - void (*dump)(struct token *, FILE *); -}; - -struct parameter { - struct libscols_column *cl; - bool has_value; - union { - const char *str; - unsigned long long num; - bool boolean; - } val; -}; - -struct parser { - const char *expr; - const char *cursor; - int paren_level; - struct libscols_table *tb; - int (*column_name_to_id)(const char *, void *); - struct libscols_column *(*add_column_by_id)(struct libscols_table *, int, void*); - void *data; - struct parameter *parameters; - char errmsg[128]; -}; - -enum node_type { - NODE_STR, - NODE_NUM, - NODE_BOOL, - NODE_RE, - NODE_OP1, - NODE_OP2, -}; - -struct node { - enum node_type type; -}; - -struct op1_class { - const char *name; - /* Return true if acceptable. */ - bool (*is_acceptable)(struct node *, struct parameter *, struct libscols_line *); - /* Return true if o.k. */ - bool (*check_type)(struct parser *, struct op1_class *, struct node *); -}; - -struct op2_class { - const char *name; - /* Return true if acceptable. */ - bool (*is_acceptable)(struct node *, struct node *, struct parameter *, struct libscols_line *); - /* Return true if o.k. */ - bool (*check_type)(struct parser *, struct op2_class *, struct node *, struct node *); -}; - -#define VAL(NODE,FIELD) (((struct node_val *)(NODE))->val.FIELD) -#define PINDEX(NODE) (((struct node_val *)(NODE))->pindex) -struct node_val { - struct node base; - int pindex; - union { - char *str; - unsigned long long num; - bool boolean; - regex_t re; - } val; -}; - -struct node_op1 { - struct node base; - struct op1_class *opclass; - struct node *arg; -}; - -struct node_op2 { - struct node base; - struct op2_class *opclass; - struct node *args[2]; -}; - -struct node_class { - const char *name; - void (*free)(struct node *); - void (*dump)(struct node *, struct parameter*, int, FILE *); -}; - -struct lsfd_filter { - struct libscols_table *table; - struct node *node; - struct parameter *parameters; - int nparams; - char errmsg[ sizeof_member(struct parser, errmsg) ]; -}; - -/* - * Prototypes - */ -static struct node *node_val_new(enum node_type, int pindex); -static void node_free (struct node *); -static bool node_apply(struct node *, struct parameter *, struct libscols_line *); -static void node_dump (struct node *, struct parameter *, int, FILE *); - -static struct token *token_new (void); -static void token_free(struct token *); -#ifdef DEBUG -static void token_dump(struct token *, FILE *); -#endif /* DEBUG */ - -static void token_free_str(struct token *); - -static void token_dump_str(struct token *, FILE *); -static void token_dump_num(struct token *, FILE *); -static void token_dump_op1(struct token *, FILE *); -static void token_dump_op2(struct token *, FILE *); - -static bool op1_not(struct node *, struct parameter*, struct libscols_line *); -static bool op1_check_type_bool_or_op(struct parser *, struct op1_class *, struct node *); - -static bool op2_eq (struct node *, struct node *, struct parameter*, struct libscols_line *); -static bool op2_ne (struct node *, struct node *, struct parameter*, struct libscols_line *); -static bool op2_and(struct node *, struct node *, struct parameter*, struct libscols_line *); -static bool op2_or (struct node *, struct node *, struct parameter*, struct libscols_line *); -static bool op2_lt (struct node *, struct node *, struct parameter*, struct libscols_line *); -static bool op2_le (struct node *, struct node *, struct parameter*, struct libscols_line *); -static bool op2_gt (struct node *, struct node *, struct parameter*, struct libscols_line *); -static bool op2_ge (struct node *, struct node *, struct parameter*, struct libscols_line *); -static bool op2_re_match (struct node *, struct node *, struct parameter*, struct libscols_line *); -static bool op2_re_unmatch (struct node *, struct node *, struct parameter*, struct libscols_line *); - -static bool op2_check_type_eq_or_bool_or_op(struct parser *, struct op2_class *, struct node *, struct node *); -static bool op2_check_type_boolean_or_op (struct parser *, struct op2_class *, struct node *, struct node *); -static bool op2_check_type_num (struct parser *, struct op2_class *, struct node *, struct node *); -static bool op2_check_type_re (struct parser *, struct op2_class *, struct node *, struct node *); - -static void node_str_free(struct node *); -static void node_re_free (struct node *); -static void node_op1_free(struct node *); -static void node_op2_free(struct node *); - -static void node_str_dump (struct node *, struct parameter*, int, FILE *); -static void node_num_dump (struct node *, struct parameter*, int, FILE *); -static void node_bool_dump(struct node *, struct parameter*, int, FILE *); -static void node_re_dump (struct node *, struct parameter*, int, FILE *); -static void node_op1_dump (struct node *, struct parameter*, int, FILE *); -static void node_op2_dump (struct node *, struct parameter*, int, FILE *); - -static struct node *dparser_compile(struct parser *); - -/* - * Data - */ -#define TOKEN_CLASS(TOKEN) (&token_classes[(TOKEN)->type]) -static struct token_class token_classes [] = { - [TOKEN_NAME] = { - .name = "NAME", - .free = token_free_str, - .dump = token_dump_str, - }, - [TOKEN_STR] = { - .name = "STR", - .free = token_free_str, - .dump = token_dump_str, - }, - [TOKEN_DEC] = { - .name = "DEC", - .dump = token_dump_num, - }, - [TOKEN_TRUE] = { - .name = "true", - }, - [TOKEN_FALSE] = { - .name = "false", - }, - [TOKEN_OPEN] = { - .name = "OPEN", - }, - [TOKEN_CLOSE] = { - .name = "CLOSE", - }, - [TOKEN_OP1] = { - .name = "OP1", - .dump = token_dump_op1, - }, - [TOKEN_OP2] = { - .name = "OP2", - .dump = token_dump_op2, - }, - [TOKEN_EOF] = { - .name = "TOKEN_EOF", - }, -}; - -#define TOKEN_OP1_CLASS(TOKEN) (&(op1_classes[(TOKEN)->val.op1])) -static struct op1_class op1_classes [] = { - [OP1_NOT] = { - .name = "!", - .is_acceptable = op1_not, - .check_type = op1_check_type_bool_or_op, - }, -}; - -#define TOKEN_OP2_CLASS(TOKEN) (&(op2_classes[(TOKEN)->val.op2])) -static struct op2_class op2_classes [] = { - [OP2_EQ] = { - .name = "==", - .is_acceptable = op2_eq, - .check_type = op2_check_type_eq_or_bool_or_op - }, - [OP2_NE] = { - .name = "!=", - .is_acceptable = op2_ne, - .check_type = op2_check_type_eq_or_bool_or_op, - }, - [OP2_AND] = { - .name = "&&", - .is_acceptable = op2_and, - .check_type = op2_check_type_boolean_or_op, - }, - [OP2_OR] = { - .name = "||", - .is_acceptable = op2_or, - .check_type = op2_check_type_boolean_or_op, - }, - [OP2_LT] = { - .name = "<", - .is_acceptable = op2_lt, - .check_type = op2_check_type_num, - }, - [OP2_LE] = { - .name = "<=", - .is_acceptable = op2_le, - .check_type = op2_check_type_num, - }, - [OP2_GT] = { - .name = ">", - .is_acceptable = op2_gt, - .check_type = op2_check_type_num, - }, - [OP2_GE] = { - .name = ">=", - .is_acceptable = op2_ge, - .check_type = op2_check_type_num, - }, - [OP2_RE_MATCH] = { - .name = "=~", - .is_acceptable = op2_re_match, - .check_type = op2_check_type_re, - }, - [OP2_RE_UNMATCH] = { - .name = "!~", - .is_acceptable = op2_re_unmatch, - .check_type = op2_check_type_re, - }, -}; - -#define NODE_CLASS(NODE) (&node_classes[(NODE)->type]) -static struct node_class node_classes[] = { - [NODE_STR] = { - .name = "STR", - .free = node_str_free, - .dump = node_str_dump, - }, - [NODE_NUM] = { - .name = "NUM", - .dump = node_num_dump, - }, - [NODE_BOOL] = { - .name = "BOOL", - .dump = node_bool_dump, - }, - [NODE_RE] = { - .name = "STR", - .free = node_re_free, - .dump = node_re_dump, - }, - [NODE_OP1] = { - .name = "OP1", - .free = node_op1_free, - .dump = node_op1_dump, - }, - [NODE_OP2] = { - .name = "OP2", - .free = node_op2_free, - .dump = node_op2_dump, - } -}; - -/* - * Functions - */ -static int strputc(char **a, const char b) -{ - return strappend(a, (char [2]){b, '\0'}); -} - -static void xstrputc(char **a, const char b) -{ - int rc = strputc(a, b); - if (rc < 0) - errx(EXIT_FAILURE, _("failed to allocate memory")); -} - -static void parser_init(struct parser *parser, const char *const expr, struct libscols_table *tb, - int ncols, - int (*column_name_to_id)(const char *, void *), - struct libscols_column *(*add_column_by_id)(struct libscols_table *, int, void*), - void *data) -{ - parser->expr = expr; - parser->cursor = parser->expr; - parser->paren_level = 0; - parser->tb = tb; - parser->column_name_to_id = column_name_to_id; - parser->add_column_by_id = add_column_by_id; - parser->data = data; - parser->parameters = xcalloc(ncols, sizeof(struct parameter)); - parser->errmsg[0] = '\0'; -} - -static char parser_getc(struct parser *parser) -{ - char c = *parser->cursor; - if (c != '\0') - parser->cursor++; - return c; -} - -static void parser_ungetc(struct parser *parser, char c) -{ - assert(parser->cursor > parser->expr); - if (c != '\0') - parser->cursor--; -} - -static void parser_read_str(struct parser *parser, struct token *token, char delimiter) -{ - bool escape = false; - while (1) { - char c = parser_getc(parser); - - if (c == '\0') { - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: string literal is not terminated: %s"), - token->val.str? : ""); - return; - } else if (escape) { - switch (c) { - case '\\': - case '\'': - case '"': - xstrputc(&token->val.str, c); - break; - case 'n': - xstrputc(&token->val.str, '\n'); - break; - case 't': - xstrputc(&token->val.str, '\t'); - break; - /* TODO: \f, \r, ... */ - default: - xstrputc(&token->val.str, '\\'); - xstrputc(&token->val.str, c); - return; - } - escape = false; - } else if (c == delimiter) { - if (token->val.str == NULL) - token->val.str = xstrdup(""); - return; - } else if (c == '\\') - escape = true; - else - xstrputc(&token->val.str, c); - } -} - -static void parser_read_name(struct parser *parser, struct token *token) -{ - while (1) { - char c = parser_getc(parser); - if (c == '\0') - break; - if (strchr(COL_HEADER_EXTRA_CHARS, c) || isalnum((unsigned char)c)) { - xstrputc(&token->val.str, c); - continue; - } - parser_ungetc(parser, c); - break; - } -} - -static int parser_read_dec(struct parser *parser, struct token *token) -{ - int rc = 0; - while (1) { - char c = parser_getc(parser); - if (c == '\0') - break; - if (isdigit((unsigned char)c)) { - xstrputc(&token->val.str, c); - continue; - } - parser_ungetc(parser, c); - break; - } - - errno = 0; - unsigned long long num = strtoull(token->val.str, NULL, 10); - rc = errno; - free(token->val.str); - token->val.num = num; - return rc; -} - -static struct token *parser_read(struct parser *parser) -{ - struct token *t = token_new(); - char c, c0; - - do - c = parser_getc(parser); - while (isspace((unsigned char)c)); - - switch (c) { - case '\0': - t->type = TOKEN_EOF; - break; - case '(': - t->type = TOKEN_OPEN; - parser->paren_level++; - break; - case ')': - t->type = TOKEN_CLOSE; - parser->paren_level--; - if (parser->paren_level < 0) - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: unbalanced parenthesis: %s"), parser->cursor - 1); - break; - case '!': - c0 = parser_getc(parser); - if (c0 == '=') { - t->type = TOKEN_OP2; - t->val.op2 = OP2_NE; - break; - } else if (c0 == '~') { - t->type = TOKEN_OP2; - t->val.op2 = OP2_RE_UNMATCH; - break; - } - parser_ungetc(parser, c0); - t->type = TOKEN_OP1; - t->val.op1 = OP1_NOT; - break; - case '<': - t->type = TOKEN_OP2; - c0 = parser_getc(parser); - if (c0 == '=') { - t->val.op2 = OP2_LE; - break; - } - parser_ungetc(parser, c0); - t->val.op2 = OP2_LT; - break; - case '>': - t->type = TOKEN_OP2; - c0 = parser_getc(parser); - if (c0 == '=') { - t->val.op2 = OP2_GE; - break; - } - parser_ungetc(parser, c0); - t->val.op2 = OP2_GT; - break; - case '=': - c0 = parser_getc(parser); - if (c0 == '=') { - t->type = TOKEN_OP2; - t->val.op2 = OP2_EQ; - break; - } else if (c0 == '~') { - t->type = TOKEN_OP2; - t->val.op2 = OP2_RE_MATCH; - break; - } - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: unexpected character %c after ="), c0); - break; - case '&': - c0 = parser_getc(parser); - if (c0 == '&') { - t->type = TOKEN_OP2; - t->val.op2 = OP2_AND; - break; - } - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: unexpected character %c after ="), c0); - break; - case '|': - c0 = parser_getc(parser); - if (c0 == '|') { - t->type = TOKEN_OP2; - t->val.op2 = OP2_OR; - break; - } - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: unexpected character %c after ="), c0); - break; - case '"': - case '\'': - t->type = TOKEN_STR; - parser_read_str(parser, t, c); - break; - default: - if (isalpha((unsigned char)c) || c == '_') { - xstrputc(&t->val.str, c); - parser_read_name(parser, t); - if (strcmp(t->val.str, "true") == 0) { - free(t->val.str); - t->type = TOKEN_TRUE; - } else if (strcmp(t->val.str, "false") == 0) { - free(t->val.str); - t->type = TOKEN_FALSE; - } else if (strcmp(t->val.str, "or") == 0) { - free(t->val.str); - t->type = TOKEN_OP2; - t->val.op2 = OP2_OR; - } else if (strcmp(t->val.str, "and") == 0) { - free(t->val.str); - t->type = TOKEN_OP2; - t->val.op2 = OP2_AND; - } else if (strcmp(t->val.str, "eq") == 0) { - free(t->val.str); - t->type = TOKEN_OP2; - t->val.op2 = OP2_EQ; - } else if (strcmp(t->val.str, "ne") == 0) { - free(t->val.str); - t->type = TOKEN_OP2; - t->val.op2 = OP2_NE; - } else if (strcmp(t->val.str, "lt") == 0) { - free(t->val.str); - t->type = TOKEN_OP2; - t->val.op2 = OP2_LT; - } else if (strcmp(t->val.str, "le") == 0) { - free(t->val.str); - t->type = TOKEN_OP2; - t->val.op2 = OP2_LE; - } else if (strcmp(t->val.str, "gt") == 0) { - free(t->val.str); - t->type = TOKEN_OP2; - t->val.op2 = OP2_GT; - } else if (strcmp(t->val.str, "ge") == 0) { - free(t->val.str); - t->type = TOKEN_OP2; - t->val.op2 = OP2_GE; - } else if (strcmp(t->val.str, "not") == 0) { - free(t->val.str); - t->type = TOKEN_OP1; - t->val.op1 = OP1_NOT; - } else - t->type = TOKEN_NAME; - break; - } else if (isdigit((unsigned char)c)) { - t->type = TOKEN_DEC; - xstrputc(&t->val.str, c); - if (parser_read_dec(parser, t) != 0) - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: failed to convert input to number")); - break; - } - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: unexpected character %c"), c); - break; - } - return t; -} - -static void parameter_init(struct parameter *param, struct libscols_column *cl) -{ - param->cl = cl; - param->has_value = false; -} - -static struct libscols_column *search_column(struct libscols_table *tb, const char *name) -{ - size_t len = scols_table_get_ncols(tb); - size_t i; - - for (i = 0; i < len; i++) { - struct libscols_column *cl = scols_table_get_column(tb, i); - const char *n = scols_column_get_name(cl); - - if (n && strcmp(n, name) == 0) - return cl; - } - return NULL; -} - -static struct node *dparser_compile1(struct parser *parser, struct node *last) -{ - struct token *t = parser_read(parser); - - if (GOT_ERROR(parser)) { - token_free(t); - return NULL; - } - - if (t->type == TOKEN_EOF) { - token_free(t); - return last; - } - if (t->type == TOKEN_CLOSE) { - token_free(t); - return last; - } - - if (last) { - switch (t->type) { - case TOKEN_NAME: - case TOKEN_STR: - case TOKEN_DEC: - case TOKEN_TRUE: - case TOKEN_FALSE: - case TOKEN_OPEN: - case TOKEN_OP1: - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: unexpected token: %s after %s"), t->val.str, - NODE_CLASS(last)->name); - token_free(t); - return NULL; - default: - break; - } - } else { - switch (t->type) { - case TOKEN_OP2: - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: empty left side expression: %s"), - TOKEN_OP2_CLASS(t)->name); - token_free(t); - return NULL; - default: - break; - } - } - - struct node *node = NULL; - switch (t->type) { - case TOKEN_NAME: { - int col_id = parser->column_name_to_id(t->val.str, parser->data); - if (col_id == LSFD_FILTER_UNKNOWN_COL_ID) { - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: no such column: %s"), t->val.str); - token_free(t); - return NULL; - - } - - struct libscols_column *cl = search_column(parser->tb, t->val.str); - if (!cl) { - cl = parser->add_column_by_id(parser->tb, col_id, parser->data); - if (!cl) { - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: cannot add a column to table: %s"), t->val.str); - token_free(t); - return NULL; - } - scols_column_set_flags(cl, SCOLS_FL_HIDDEN); - } - parameter_init(parser->parameters + col_id, cl); - - int jtype = scols_column_get_json_type(cl); - int ntype; - switch (jtype) { - case SCOLS_JSON_STRING: - case SCOLS_JSON_ARRAY_STRING: - case SCOLS_JSON_ARRAY_NUMBER: - /* We handles SCOLS_JSON_ARRAY_* as a string - * till we implement operators for arrays. */ - ntype = NODE_STR; - break; - case SCOLS_JSON_NUMBER: - ntype = NODE_NUM; - break; - case SCOLS_JSON_BOOLEAN: - ntype = NODE_BOOL; - break; - default: - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: unsupported column data type: %d, column: %s"), - jtype, t->val.str); - return NULL; - } - node = node_val_new(ntype, col_id); - token_free(t); - return node; - } - - case TOKEN_STR: - node = node_val_new(NODE_STR, -1); - VAL(node, str) = xstrdup(t->val.str); - token_free(t); - return node; - - case TOKEN_DEC: - node = node_val_new(NODE_NUM, -1); - VAL(node, num) = t->val.num; - token_free(t); - return node; - - case TOKEN_TRUE: - case TOKEN_FALSE: - node = node_val_new(NODE_BOOL, -1); - VAL(node, boolean) = (t->type == TOKEN_TRUE); - token_free(t); - return node; - - case TOKEN_OPEN: - token_free(t); - return dparser_compile(parser); - - case TOKEN_OP1: { - struct node *op1_right = dparser_compile1(parser, NULL); - struct op1_class *op1_class = TOKEN_OP1_CLASS(t); - - token_free(t); - - if (GOT_ERROR(parser)) { - node_free(op1_right); - return NULL; - } - - if (op1_right == NULL) { - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: empty right side expression: %s"), - op1_class->name); - return NULL; - } - - if (!op1_class->check_type(parser, op1_class, op1_right)) { - node_free(op1_right); - return NULL; - } - - node = xmalloc(sizeof(struct node_op1)); - node->type = NODE_OP1; - ((struct node_op1 *)node)->opclass = op1_class; - ((struct node_op1 *)node)->arg = op1_right; - - return node; - } - - case TOKEN_OP2: { - struct node *op2_right = dparser_compile1(parser, NULL); - struct op2_class *op2_class = TOKEN_OP2_CLASS(t); - - token_free(t); - - if (GOT_ERROR(parser)) { - node_free(op2_right); - return NULL; - } - if (op2_right == NULL) { - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: empty right side expression: %s"), - op2_class->name); - return NULL; - } - - if (!op2_class->check_type(parser, op2_class, last, op2_right)) { - node_free(op2_right); - return NULL; - } - - node = xmalloc(sizeof(struct node_op2)); - node->type = NODE_OP2; - ((struct node_op2 *)node)->opclass = op2_class; - ((struct node_op2 *)node)->args[0] = last; - ((struct node_op2 *)node)->args[1] = op2_right; - - return node; - } - - default: - warnx("unexpected token type: %d", t->type); - token_free(t); - return NULL; - } -} - -static struct node *dparser_compile(struct parser *parser) -{ - struct node *node = NULL; - - while (true) { - struct node *node0 = dparser_compile1(parser, node); - if (GOT_ERROR(parser)) { - node_free(node); - return NULL; - } - - if (node == node0) { - if (node == NULL) - xstrncpy(parser->errmsg, - _("error: empty filter expression"), - sizeof(parser->errmsg)); - return node; - } - node = node0; - } -} - -static struct token *token_new(void) -{ - return xcalloc(1, sizeof(struct token)); -} - -static void token_free(struct token *token) -{ - if (TOKEN_CLASS(token)->free) - TOKEN_CLASS(token)->free(token); - free(token); -} - -#ifdef DEBUG -static void token_dump(struct token *token, FILE *stream) -{ - fprintf(stream, "<%s>", TOKEN_CLASS(token)->name); - if (TOKEN_CLASS(token)->dump) - TOKEN_CLASS(token)->dump(token, stream); - fputc('\n', stream); -} -#endif /* DEBUG */ - -static void token_free_str(struct token *token) -{ - free(token->val.str); -} - -static void token_dump_str(struct token *token, FILE *stream) -{ - fputs(token->val.str, stream); -} - -static void token_dump_num(struct token *token, FILE *stream) -{ - fprintf(stream, "%llu", token->val.num); -} - -static void token_dump_op1(struct token *token, FILE *stream) -{ - fputs(TOKEN_OP1_CLASS(token)->name, stream); -} - -static void token_dump_op2(struct token *token, FILE *stream) -{ - fputs(TOKEN_OP2_CLASS(token)->name, stream); -} - -static struct node *node_val_new(enum node_type type, int pindex) -{ - struct node *node = xmalloc(sizeof(struct node_val)); - node->type = type; - PINDEX(node) = pindex; - return node; -} - -static void node_free(struct node *node) -{ - if (node == NULL) - return; - if (NODE_CLASS(node)->free) - NODE_CLASS(node)->free(node); - free(node); -} - -static bool node_apply(struct node *node, struct parameter *params, struct libscols_line *ln) -{ - if (!node) - return true; - - switch (node->type) { - case NODE_OP1: { - struct node_op1 *node_op1 = (struct node_op1*)node; - return node_op1->opclass->is_acceptable(node_op1->arg, params, ln); - } - case NODE_OP2: { - struct node_op2 *node_op2 = (struct node_op2*)node; - return node_op2->opclass->is_acceptable(node_op2->args[0], node_op2->args[1], params, ln); - } - case NODE_BOOL: - if (PINDEX(node) < 0) - return VAL(node,boolean); - - if (!params[PINDEX(node)].has_value) { - const char *data = scols_line_get_column_data(ln, params[PINDEX(node)].cl); - if (data == NULL) - return false; - params[PINDEX(node)].val.boolean = !*data ? false : - *data == '0' ? false : - *data == 'N' || *data == 'n' ? false : true; - params[PINDEX(node)].has_value = true; - } - return params[PINDEX(node)].val.boolean; - default: - warnx(_("unexpected type in filter application: %s"), NODE_CLASS(node)->name); - return false; - } -} - -static void node_dump(struct node *node, struct parameter *param, int depth, FILE *stream) -{ - int i; - - if (!node) - return; - - for (i = 0; i < depth; i++) - fputc(' ', stream); - fputs(NODE_CLASS(node)->name, stream); - if (NODE_CLASS(node)->dump) - NODE_CLASS(node)->dump(node, param, depth, stream); -} - -static void node_str_dump(struct node *node, struct parameter* params, int depth __attribute__((__unused__)), FILE *stream) -{ - if (PINDEX(node) >= 0) - fprintf(stream, ": |%s|\n", scols_column_get_name(params[PINDEX(node)].cl)); - else - fprintf(stream, ": '%s'\n", VAL(node,str)); -} - -static void node_num_dump(struct node *node, struct parameter* params, int depth __attribute__((__unused__)), FILE *stream) -{ - if (PINDEX(node) >= 0) - fprintf(stream, ": |%s|\n", scols_column_get_name(params[PINDEX(node)].cl)); - else - fprintf(stream, ": %llu\n", VAL(node,num)); -} - -static void node_bool_dump(struct node *node, struct parameter* params, int depth __attribute__((__unused__)), FILE *stream) -{ - if (PINDEX(node) >= 0) - fprintf(stream, ": |%s|\n", scols_column_get_name(params[PINDEX(node)].cl)); - else - fprintf(stream, ": %s\n", - VAL(node,boolean) - ? token_classes[TOKEN_TRUE].name - : token_classes[TOKEN_FALSE].name); -} - -static void node_re_dump(struct node *node, struct parameter* params __attribute__((__unused__)), - int depth __attribute__((__unused__)), FILE *stream) -{ - fprintf(stream, ": #<regexp %p>\n", &VAL(node,re)); -} - -static void node_op1_dump(struct node *node, struct parameter* params, int depth, FILE *stream) -{ - fprintf(stream, ": %s\n", ((struct node_op1 *)node)->opclass->name); - node_dump(((struct node_op1 *)node)->arg, params, depth + 4, stream); -} - -static void node_op2_dump(struct node *node, struct parameter* params, int depth, FILE *stream) -{ - int i; - - fprintf(stream, ": %s\n", ((struct node_op2 *)node)->opclass->name); - for (i = 0; i < 2; i++) - node_dump(((struct node_op2 *)node)->args[i], params, depth + 4, stream); -} - -static void node_str_free(struct node *node) -{ - if (PINDEX(node) < 0) - free(VAL(node,str)); -} - -static void node_re_free(struct node *node) -{ - regfree(&VAL(node,re)); -} - -static void node_op1_free(struct node *node) -{ - node_free(((struct node_op1 *)node)->arg); -} - -static void node_op2_free(struct node *node) -{ - int i; - - for (i = 0; i < 2; i++) - node_free(((struct node_op2 *)node)->args[i]); -} - -static bool op1_not(struct node *node, struct parameter* params, struct libscols_line * ln) -{ - return !node_apply(node, params, ln); -} - -static bool op1_check_type_bool_or_op(struct parser* parser, struct op1_class *op1_class, - struct node *node) -{ - if (! (node->type == NODE_OP1 || node->type == NODE_OP2 || node->type == NODE_BOOL)) { - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: unexpected operand type %s for: %s"), - NODE_CLASS(node)->name, - op1_class->name); - return false; - } - return true; -} - -#define OP2_GET_STR(NODE,DEST) do { \ - int pindex = PINDEX(NODE); \ - if (pindex < 0) \ - DEST = VAL(NODE,str); \ - else { \ - struct parameter *p = params + pindex; \ - if (!p->has_value) { \ - p->val.str = scols_line_get_column_data(ln, p->cl); \ - if (p->val.str == NULL) return false; \ - p->has_value = true; \ - } \ - DEST = p->val.str; \ - } \ -} while(0) - -#define OP2_GET_NUM(NODE,DEST) do { \ - int pindex = PINDEX(NODE); \ - if (pindex < 0) \ - DEST = VAL(NODE,num); \ - else { \ - struct parameter *p = params + pindex; \ - if (!p->has_value) { \ - const char *tmp = scols_line_get_column_data(ln, p->cl); \ - if (tmp == NULL) return false; \ - p->val.num = strtoull(tmp, NULL, 10); \ - p->has_value = true; \ - } \ - DEST = p->val.num; \ - } \ -} while(0) - -#define OP2_EQ_BODY(OP,ELSEVAL) do { \ - if (left->type == NODE_STR) { \ - const char *lv, *rv; \ - OP2_GET_STR(left,lv); \ - OP2_GET_STR(right,rv); \ - return strcmp(lv, rv) OP 0; \ - } else if (left->type == NODE_NUM) { \ - unsigned long long lv, rv; \ - OP2_GET_NUM(left,lv); \ - OP2_GET_NUM(right,rv); \ - return lv OP rv; \ - } else { \ - return node_apply(left, params, ln) OP node_apply(right, params, ln); \ - } \ -} while(0) - -#define OP2_CMP_BODY(OP) do { \ - unsigned long long lv, rv; \ - OP2_GET_NUM(left,lv); \ - OP2_GET_NUM(right,rv); \ - return (lv OP rv); \ -} while(0) -static bool op2_eq(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln) -{ - OP2_EQ_BODY(==, false); -} - -static bool op2_ne(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln) -{ - OP2_EQ_BODY(!=, true); -} - -static bool op2_and(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln) -{ - return node_apply(left, params, ln) && node_apply(right, params, ln); -} - -static bool op2_or(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln) -{ - return node_apply(left, params, ln) || node_apply(right, params, ln); -} - -static bool op2_lt(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln) -{ - OP2_CMP_BODY(<); -} - -static bool op2_le(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln) -{ - OP2_CMP_BODY(<=); -} - -static bool op2_gt(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln) -{ - OP2_CMP_BODY(>); -} - -static bool op2_ge(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln) -{ - OP2_CMP_BODY(>=); -} - -static bool op2_re_match(struct node *left, struct node *right, - struct parameter *params, struct libscols_line *ln) -{ - const char *str; - OP2_GET_STR(left, str); - - return (regexec(&VAL(right,re), str, 0, NULL, 0) == 0); -} - -static bool op2_re_unmatch(struct node *left, struct node *right, - struct parameter *params, struct libscols_line *ln) -{ - return !op2_re_match(left, right, params, ln); -} - -static bool op2_check_type_boolean_or_op(struct parser* parser, struct op2_class *op2_class, - struct node *left, struct node *right) -{ - enum node_type lt = left->type, rt = right->type; - - if (!(lt == NODE_OP1 || lt == NODE_OP2 || lt == NODE_BOOL)) { - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: unexpected left operand type %s for: %s"), - NODE_CLASS(left)->name, - op2_class->name); - return false; - } - - if (! (rt == NODE_OP1 || rt == NODE_OP2 || rt == NODE_BOOL)) { - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: unexpected right operand type %s for: %s"), - NODE_CLASS(right)->name, - op2_class->name); - return false; - } - - return true; -} - -static bool op2_check_type_eq_or_bool_or_op(struct parser* parser, struct op2_class *op2_class, - struct node *left, struct node *right) -{ - enum node_type lt = left->type, rt = right->type; - - if (lt == rt) - return true; - - return op2_check_type_boolean_or_op(parser, op2_class, left, right); -} - -static bool op2_check_type_num(struct parser* parser, struct op2_class *op2_class, - struct node *left, struct node *right) -{ - if (left->type != NODE_NUM) { - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: unexpected left operand type %s for: %s"), - NODE_CLASS(left)->name, - op2_class->name); - return false; - } - - if (right->type != NODE_NUM) { - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: unexpected right operand type %s for: %s"), - NODE_CLASS(right)->name, - op2_class->name); - return false; - } - - return true; -} - -static bool op2_check_type_re(struct parser* parser, struct op2_class *op2_class, - struct node *left, struct node *right) -{ - if (left->type != NODE_STR) { - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: unexpected left operand type %s for: %s"), - NODE_CLASS(left)->name, - op2_class->name); - return false; - } - - if (right->type != NODE_STR) { - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: unexpected right operand type %s for: %s"), - NODE_CLASS(right)->name, - op2_class->name); - return false; - } - if (PINDEX(right) >= 0) { - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: string literal is expected as right operand for: %s"), - op2_class->name); - return false; - } - - char *regex = VAL(right, str); - VAL(right, str) = NULL; - - int err = regcomp(&VAL(right, re), regex, REG_NOSUB | REG_EXTENDED); - if (err != 0) { - size_t size = regerror(err, &VAL(right, re), NULL, 0); - char *buf = xmalloc(size + 1); - - regerror(err, &VAL(right, re), buf, size); - - snprintf(parser->errmsg, sizeof(parser->errmsg), - _("error: could not compile regular expression %s: %s"), - regex, buf); - free(buf); - return false; - } - right->type = NODE_RE; - free(regex); - return true; -} - -struct lsfd_filter *lsfd_filter_new(const char *const expr, struct libscols_table *tb, - int ncols, - int (*column_name_to_id)(const char *, void *), - struct libscols_column *(*add_column_by_id)(struct libscols_table *, int, void*), - void *data) -{ - struct parser parser; - int i; - struct node *node; - struct lsfd_filter *filter; - - parser_init(&parser, expr, tb, ncols, - column_name_to_id, - add_column_by_id, - data); - - node = dparser_compile(&parser); - filter = xcalloc(1, sizeof(struct lsfd_filter)); - - if (GOT_ERROR(&parser)) { - xstrncpy(filter->errmsg, parser.errmsg, sizeof(filter->errmsg)); - return filter; - } - assert(node); - if (parser.paren_level > 0) { - node_free(node); - xstrncpy(filter->errmsg, _("error: unbalanced parenthesis: ("), sizeof(filter->errmsg)); - return filter; - } - if (*parser.cursor != '\0') { - node_free(node); - snprintf(filter->errmsg, sizeof(filter->errmsg), - _("error: garbage at the end of expression: %s"), parser.cursor); - return filter; - } - if (node->type == NODE_STR || node->type == NODE_NUM) { - node_free(node); - snprintf(filter->errmsg, sizeof(filter->errmsg), - _("error: bool expression is expected: %s"), expr); - return filter; - } - - filter->table = tb; - scols_ref_table(filter->table); - filter->node = node; - filter->parameters = parser.parameters; - filter->nparams = ncols; - for (i = 0; i < filter->nparams; i++) { - if (filter->parameters[i].cl) - scols_ref_column(filter->parameters[i].cl); - } - return filter; -} - -const char *lsfd_filter_get_errmsg(struct lsfd_filter *filter) -{ - if (GOT_ERROR(filter)) - return filter->errmsg; - - return NULL; -} - -void lsfd_filter_dump(struct lsfd_filter *filter, FILE *stream) -{ - if (!filter) { - fputs("EMPTY\n", stream); - return; - } - - if (GOT_ERROR(filter)) { - fprintf(stream, "ERROR: %s\n", filter->errmsg); - return; - } - - node_dump(filter->node, filter->parameters, 0, stream); -} - -void lsfd_filter_free(struct lsfd_filter *filter) -{ - int i; - - if (!filter) - return; - - if (!GOT_ERROR(filter)) { - for (i = 0; i < filter->nparams; i++) { - if (filter->parameters[i].cl) - scols_unref_column(filter->parameters[i].cl); - } - scols_unref_table(filter->table); - node_free(filter->node); - } - free(filter->parameters); - free(filter); -} - -bool lsfd_filter_apply(struct lsfd_filter *filter, struct libscols_line * ln) -{ - int i; - - if (!filter) - return true; - - if (GOT_ERROR(filter)) - return false; - - for (i = 0; i < filter->nparams; i++) - filter->parameters[i].has_value = false; - - return node_apply(filter->node, filter->parameters, ln); -} diff --git a/misc-utils/lsfd-filter.h b/misc-utils/lsfd-filter.h deleted file mode 100644 index db5cbd6..0000000 --- a/misc-utils/lsfd-filter.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * lsfd-filter.c - filtering engine for lsfd - * - * Copyright (C) 2021 Red Hat, Inc. - * Copyright (C) 2021 Masatake YAMATO <yamato@redhat.com> - * - * This file may be redistributed under the terms of the - * GNU Lesser General Public License. - */ -#ifndef UTIL_LINUX_LSFD_FILTER_H -#define UTIL_LINUX_LSFD_FILTER_H - -#include "libsmartcols.h" -#include <stdio.h> -#include <stdbool.h> - -#define LSFD_FILTER_UNKNOWN_COL_ID -1 - -struct lsfd_filter; - -/* - * @column_name_to_id: a function converting a column name to its id. - * - * @column_name_to_id should return LSFD_FILTER_UNKNOWN_COL_ID if - * an unknown column name is given. - */ -struct lsfd_filter *lsfd_filter_new(const char *const expr, struct libscols_table *tb, - int ncols, - int (*column_name_to_id)(const char *, void *), - struct libscols_column *(*add_column_by_id)(struct libscols_table *, int, void*), - void *data); - -/* Call lsfd_filter_get_errmsg() after lsfd_filter_new() to detect - * whether lsfd_filter_new() is failed or not. Returning NULL means, - * lsfd_filter_new() is successful. */ -const char *lsfd_filter_get_errmsg(struct lsfd_filter *filter); -void lsfd_filter_free(struct lsfd_filter *filter); -bool lsfd_filter_apply(struct lsfd_filter *filter, struct libscols_line *ln); - -/* Dumping AST. */ -void lsfd_filter_dump(struct lsfd_filter *filter, FILE *stream); - -#endif /* UTIL_LINUX_LSFD_FILTER_H */ diff --git a/misc-utils/lsfd-sock-xinfo.c b/misc-utils/lsfd-sock-xinfo.c index 61b8607..a293806 100644 --- a/misc-utils/lsfd-sock-xinfo.c +++ b/misc-utils/lsfd-sock-xinfo.c @@ -1,5 +1,5 @@ /* - * lsfd-sock-xinfo.c - read various information from files under /proc/net/ + * lsfd-sock-xinfo.c - read various information from files under /proc/net/ and NETLINK_SOCK_DIAG * * Copyright (C) 2022 Red Hat, Inc. All rights reserved. * Written by Masatake YAMATO <yamato@redhat.com> @@ -26,37 +26,40 @@ #include <net/if.h> /* if_nametoindex */ #include <linux/if_ether.h> /* ETH_P_* */ #include <linux/net.h> /* SS_* */ -#include <linux/netlink.h> /* NETLINK_* */ +#include <linux/netlink.h> /* NETLINK_*, NLMSG_* */ +#include <linux/rtnetlink.h> /* RTA_*, struct rtattr, */ +#include <linux/sock_diag.h> /* SOCK_DIAG_BY_FAMILY */ #include <linux/un.h> /* UNIX_PATH_MAX */ +#include <linux/unix_diag.h> /* UNIX_DIAG_*, UDIAG_SHOW_*, + struct unix_diag_req */ #include <sched.h> /* for setns(2) */ -#include <search.h> +#include <search.h> /* tfind, tsearch */ #include <stdint.h> #include <string.h> #include <sys/socket.h> /* SOCK_* */ -#include "xalloc.h" -#include "nls.h" -#include "libsmartcols.h" #include "sysfs.h" #include "bitops.h" #include "lsfd.h" #include "lsfd-sock.h" -static void load_xinfo_from_proc_icmp(ino_t netns_inode); -static void load_xinfo_from_proc_icmp6(ino_t netns_inode); +static void load_xinfo_from_proc_icmp(ino_t netns_inode, enum sysfs_byteorder byteorder); +static void load_xinfo_from_proc_icmp6(ino_t netns_inode, enum sysfs_byteorder byteorder); static void load_xinfo_from_proc_unix(ino_t netns_inode); -static void load_xinfo_from_proc_raw(ino_t netns_inode); -static void load_xinfo_from_proc_tcp(ino_t netns_inode); -static void load_xinfo_from_proc_udp(ino_t netns_inode); -static void load_xinfo_from_proc_udplite(ino_t netns_inode); -static void load_xinfo_from_proc_tcp6(ino_t netns_inode); -static void load_xinfo_from_proc_udp6(ino_t netns_inode); -static void load_xinfo_from_proc_udplite6(ino_t netns_inode); -static void load_xinfo_from_proc_raw6(ino_t netns_inode); +static void load_xinfo_from_proc_raw(ino_t netns_inode, enum sysfs_byteorder byteorder); +static void load_xinfo_from_proc_tcp(ino_t netns_inode, enum sysfs_byteorder byteorder); +static void load_xinfo_from_proc_udp(ino_t netns_inode, enum sysfs_byteorder byteorder); +static void load_xinfo_from_proc_udplite(ino_t netns_inode, enum sysfs_byteorder byteorder); +static void load_xinfo_from_proc_tcp6(ino_t netns_inode, enum sysfs_byteorder byteorder); +static void load_xinfo_from_proc_udp6(ino_t netns_inode, enum sysfs_byteorder byteorder); +static void load_xinfo_from_proc_udplite6(ino_t netns_inode, enum sysfs_byteorder byteorder); +static void load_xinfo_from_proc_raw6(ino_t netns_inode, enum sysfs_byteorder byteorder); static void load_xinfo_from_proc_netlink(ino_t netns_inode); static void load_xinfo_from_proc_packet(ino_t netns_inode); +static void load_xinfo_from_diag_unix(int diag, ino_t netns_inode); + static int self_netns_fd = -1; static struct stat self_netns_sb; @@ -158,21 +161,29 @@ static struct netns *mark_sock_xinfo_loaded(ino_t ino) static void load_sock_xinfo_no_nsswitch(struct netns *nsobj) { ino_t netns = nsobj? nsobj->inode: 0; + int diagsd; + enum sysfs_byteorder byteorder = sysfs_get_byteorder(NULL); load_xinfo_from_proc_unix(netns); - load_xinfo_from_proc_tcp(netns); - load_xinfo_from_proc_udp(netns); - load_xinfo_from_proc_udplite(netns); - load_xinfo_from_proc_raw(netns); - load_xinfo_from_proc_tcp6(netns); - load_xinfo_from_proc_udp6(netns); - load_xinfo_from_proc_udplite6(netns); - load_xinfo_from_proc_raw6(netns); - load_xinfo_from_proc_icmp(netns); - load_xinfo_from_proc_icmp6(netns); + load_xinfo_from_proc_tcp(netns, byteorder); + load_xinfo_from_proc_udp(netns, byteorder); + load_xinfo_from_proc_udplite(netns, byteorder); + load_xinfo_from_proc_raw(netns, byteorder); + load_xinfo_from_proc_tcp6(netns, byteorder); + load_xinfo_from_proc_udp6(netns, byteorder); + load_xinfo_from_proc_udplite6(netns, byteorder); + load_xinfo_from_proc_raw6(netns, byteorder); + load_xinfo_from_proc_icmp(netns, byteorder); + load_xinfo_from_proc_icmp6(netns, byteorder); load_xinfo_from_proc_netlink(netns); load_xinfo_from_proc_packet(netns); + diagsd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_SOCK_DIAG); + if (diagsd >= 0) { + load_xinfo_from_diag_unix(diagsd, netns); + close(diagsd); + } + if (nsobj) load_ifaces_from_getifaddrs(nsobj); } @@ -214,8 +225,12 @@ void initialize_sock_xinfos(void) load_sock_xinfo_no_nsswitch(NULL); else { if (fstat(self_netns_fd, &self_netns_sb) == 0) { + unsigned long m; struct netns *nsobj = mark_sock_xinfo_loaded(self_netns_sb.st_ino); load_sock_xinfo_no_nsswitch(nsobj); + + m = minor(self_netns_sb.st_dev); + add_nodev(m, "nsfs"); } } @@ -281,9 +296,9 @@ static void add_sock_info(struct sock_xinfo *xinfo) errx(EXIT_FAILURE, _("failed to allocate memory")); } -struct sock_xinfo *get_sock_xinfo(ino_t netns_inode) +struct sock_xinfo *get_sock_xinfo(ino_t inode) { - struct sock_xinfo key = { .inode = netns_inode }; + struct sock_xinfo key = { .inode = inode }; struct sock_xinfo **xinfo = tfind(&key, &xinfo_tree, xinfo_compare); if (xinfo) @@ -318,6 +333,61 @@ static const char *sock_decode_type(uint16_t type) } } +static void send_diag_request(int diagsd, void *req, size_t req_size, + bool (*cb)(ino_t, size_t, void *), + ino_t netns) +{ + struct sockaddr_nl nladdr = { + .nl_family = AF_NETLINK, + }; + + struct nlmsghdr nlh = { + .nlmsg_len = sizeof(nlh) + req_size, + .nlmsg_type = SOCK_DIAG_BY_FAMILY, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP, + }; + + struct iovec iovecs[] = { + { &nlh, sizeof(nlh) }, + { req, req_size }, + }; + + const struct msghdr mhd = { + .msg_namelen = sizeof(nladdr), + .msg_name = &nladdr, + .msg_iovlen = ARRAY_SIZE(iovecs), + .msg_iov = iovecs, + }; + + __attribute__((aligned(sizeof(void *)))) uint8_t buf[8192]; + + if (sendmsg(diagsd, &mhd, 0) < 0) + return; + + for (;;) { + const struct nlmsghdr *h; + int r = recvfrom(diagsd, buf, sizeof(buf), 0, NULL, NULL); + if (r < 0) + return; + + h = (void *) buf; + if (!NLMSG_OK(h, (size_t)r)) + return; + + for (; NLMSG_OK(h, (size_t)r); h = NLMSG_NEXT(h, r)) { + if (h->nlmsg_type == NLMSG_DONE) + return; + if (h->nlmsg_type == NLMSG_ERROR) + return; + + if (h->nlmsg_type == SOCK_DIAG_BY_FAMILY) { + if (!cb(netns, h->nlmsg_len, NLMSG_DATA(h))) + return; + } + } + } +} + /* * Protocol specific code */ @@ -325,11 +395,21 @@ static const char *sock_decode_type(uint16_t type) /* * UNIX */ +struct unix_ipc { + struct ipc ipc; + ino_t inode; + ino_t ipeer; +}; + struct unix_xinfo { struct sock_xinfo sock; int acceptcon; /* flags */ uint16_t type; uint8_t st; +#define is_shutdown_mask_set(mask) ((mask) & (1 << 2)) +#define set_shutdown_mask(mask) ((mask) |= (1 << 2)) + uint8_t shutdown_mask:3; + struct unix_ipc *unix_ipc; char path[ UNIX_PATH_MAX + 1 /* for @ */ @@ -407,15 +487,98 @@ static bool unix_get_listening(struct sock_xinfo *sock_xinfo, return ux->acceptcon; } +static unsigned int unix_get_hash(struct file *file) +{ + return (unsigned int)(file->stat.st_ino % UINT_MAX); +} + +static bool unix_is_suitable_ipc(struct ipc *ipc, struct file *file) +{ + return ((struct unix_ipc *)ipc)->inode == file->stat.st_ino; +} + +/* For looking up an ipc struct for a sock inode, we need a sock strcuct + * for the inode. See the signature o get_ipc(). + * + * However, there is a case that we have no sock strcuct for the inode; + * in the context we know only the sock inode. + * For the case, unix_make_dumy_sock() provides the way to make a + * dummy sock struct for the inode. + */ +static void unix_make_dumy_sock(struct sock *original, ino_t ino, struct sock *dummy) +{ + *dummy = *original; + dummy->file.stat.st_ino = ino; +} + +static struct ipc_class unix_ipc_class = { + .size = sizeof(struct unix_ipc), + .get_hash = unix_get_hash, + .is_suitable_ipc = unix_is_suitable_ipc, + .free = NULL, +}; + +static struct ipc_class *unix_get_ipc_class(struct sock_xinfo *sock_xinfo __attribute__((__unused__)), + struct sock *sock __attribute__((__unused__))) +{ + return &unix_ipc_class; +} + +static bool unix_shutdown_chars(struct unix_xinfo *ux, char rw[2]) +{ + uint8_t mask = ux->shutdown_mask; + + if (is_shutdown_mask_set(mask)) { + rw[0] = ((mask & (1 << 0))? '-': 'r'); + rw[1] = ((mask & (1 << 1))? '-': 'w'); + return true; + } + + return false; +} + +static inline char *unix_xstrendpoint(struct sock *sock) +{ + char *str = NULL; + char shutdown_chars[3] = { 0 }; + + if (!unix_shutdown_chars(((struct unix_xinfo *)sock->xinfo), shutdown_chars)) { + shutdown_chars[0] = '?'; + shutdown_chars[1] = '?'; + } + xasprintf(&str, "%d,%s,%d%c%c", + sock->file.proc->pid, sock->file.proc->command, sock->file.association, + shutdown_chars[0], shutdown_chars[1]); + + return str; +} + +static struct ipc *unix_get_peer_ipc(struct unix_xinfo *ux, + struct sock *sock) +{ + struct unix_ipc *unix_ipc; + struct sock dummy_peer_sock; + + unix_ipc = ux->unix_ipc; + if (!unix_ipc) + return NULL; + + unix_make_dumy_sock(sock, unix_ipc->ipeer, &dummy_peer_sock); + return get_ipc(&dummy_peer_sock.file); +} + static bool unix_fill_column(struct proc *proc __attribute__((__unused__)), struct sock_xinfo *sock_xinfo, - struct sock *sock __attribute__((__unused__)), + struct sock *sock, struct libscols_line *ln __attribute__((__unused__)), int column_id, size_t column_index __attribute__((__unused__)), char **str) { struct unix_xinfo *ux = (struct unix_xinfo *)sock_xinfo; + struct ipc *peer_ipc; + struct list_head *e; + char shutdown_chars[3] = { 0 }; switch (column_id) { case COL_UNIX_PATH: @@ -424,6 +587,30 @@ static bool unix_fill_column(struct proc *proc __attribute__((__unused__)), return true; } break; + case COL_ENDPOINTS: + peer_ipc = unix_get_peer_ipc(ux, sock); + if (!peer_ipc) + break; + + list_for_each_backwardly(e, &peer_ipc->endpoints) { + struct sock *peer_sock = list_entry(e, struct sock, endpoint.endpoints); + char *estr; + + if (*str) + xstrputc(str, '\n'); + estr = unix_xstrendpoint(peer_sock); + xstrappend(str, estr); + free(estr); + } + if (*str) + return true; + break; + case COL_SOCK_SHUTDOWN: + if (unix_shutdown_chars(ux, shutdown_chars)) { + *str = xstrdup(shutdown_chars); + return true; + } + break; } return false; @@ -435,6 +622,7 @@ static const struct sock_xinfo_class unix_xinfo_class = { .get_state = unix_get_state, .get_listening = unix_get_listening, .fill_column = unix_fill_column, + .get_ipc_class = unix_get_ipc_class, .free = NULL, }; @@ -494,6 +682,96 @@ static void load_xinfo_from_proc_unix(ino_t netns_inode) fclose(unix_fp); } +/* The path name extracted from /proc/net/unix is unreliable; the line oriented interface cannot + * represent a file name including newlines. With unix_refill_name(), we patch the path + * member of unix_xinfos with information received via netlink diag interface. */ +static void unix_refill_name(struct sock_xinfo *xinfo, const char *name, size_t len) +{ + struct unix_xinfo *ux = (struct unix_xinfo *)xinfo; + size_t min_len; + + if (len == 0) + return; + + min_len = min(sizeof(ux->path) - 1, len); + memcpy(ux->path, name, min_len); + if (ux->path[0] == '\0') { + ux->path[0] = '@'; + } + ux->path[min_len] = '\0'; +} + +static bool handle_diag_unix(ino_t netns __attribute__((__unused__)), + size_t nlmsg_len, void *nlmsg_data) +{ + const struct unix_diag_msg *diag = nlmsg_data; + size_t rta_len; + ino_t inode; + struct sock_xinfo *xinfo; + struct unix_xinfo *unix_xinfo; + + if (diag->udiag_family != AF_UNIX) + return false; + + if (nlmsg_len < NLMSG_LENGTH(sizeof(*diag))) + return false; + + inode = (ino_t)diag->udiag_ino; + xinfo = get_sock_xinfo(inode); + + if (xinfo == NULL) + /* The socket is found in the diag response + but not in the proc fs. */ + return true; + + if (xinfo->class != &unix_xinfo_class) + return true; + unix_xinfo = (struct unix_xinfo *)xinfo; + + rta_len = nlmsg_len - NLMSG_LENGTH(sizeof(*diag)); + for (struct rtattr *attr = (struct rtattr *)(diag + 1); + RTA_OK(attr, rta_len); + attr = RTA_NEXT(attr, rta_len)) { + size_t len = RTA_PAYLOAD(attr); + + switch (attr->rta_type) { + case UNIX_DIAG_NAME: + unix_refill_name(xinfo, RTA_DATA(attr), len); + break; + + case UNIX_DIAG_SHUTDOWN: + if (len < 1) + break; + + unix_xinfo->shutdown_mask = *(uint8_t *)RTA_DATA(attr); + set_shutdown_mask(unix_xinfo->shutdown_mask); + break; + + case UNIX_DIAG_PEER: + if (len < 4) + break; + + unix_xinfo->unix_ipc = (struct unix_ipc *)new_ipc(&unix_ipc_class); + unix_xinfo->unix_ipc->inode = inode; + unix_xinfo->unix_ipc->ipeer = (ino_t)(*(uint32_t *)RTA_DATA(attr)); + add_ipc(&unix_xinfo->unix_ipc->ipc, inode % UINT_MAX); + break; + } + } + return true; +} + +static void load_xinfo_from_diag_unix(int diagsd, ino_t netns) +{ + struct unix_diag_req udr = { + .sdiag_family = AF_UNIX, + .udiag_states = -1, /* set the all bits. */ + .udiag_show = UDIAG_SHOW_NAME | UDIAG_SHOW_PEER | UNIX_DIAG_SHUTDOWN, + }; + + send_diag_request(diagsd, &udr, sizeof(udr), handle_diag_unix, netns); +} + /* * AF_INET */ @@ -548,7 +826,7 @@ enum l4_state { static const char *l4_decode_state(enum l4_state st) { - const char * table [] = { + const char * const table [] = { [TCP_ESTABLISHED] = "established", [TCP_SYN_SENT] = "syn-sent", [TCP_SYN_RECV] = "syn-recv", @@ -813,7 +1091,8 @@ static bool L4_verify_initial_line(const char *line) #define TCP_LINE_LEN 256 static void load_xinfo_from_proc_inet_L4(ino_t netns_inode, const char *proc_file, - const struct l4_xinfo_class *class) + const struct l4_xinfo_class *class, + enum sysfs_byteorder byteorder) { char line[TCP_LINE_LEN]; FILE *tcp_fp; @@ -828,8 +1107,6 @@ static void load_xinfo_from_proc_inet_L4(ino_t netns_inode, const char *proc_fil /* Unexpected line */ goto out; - enum sysfs_byteorder byteorder = sysfs_get_byteorder(NULL); - while (fgets(line, sizeof(line), tcp_fp)) { struct sock_xinfo *sock = class->scan_line(&class->sock, line, netns_inode, byteorder); if (sock) @@ -840,11 +1117,12 @@ static void load_xinfo_from_proc_inet_L4(ino_t netns_inode, const char *proc_fil fclose(tcp_fp); } -static void load_xinfo_from_proc_tcp(ino_t netns_inode) +static void load_xinfo_from_proc_tcp(ino_t netns_inode, enum sysfs_byteorder byteorder) { load_xinfo_from_proc_inet_L4(netns_inode, "/proc/net/tcp", - &tcp_xinfo_class); + &tcp_xinfo_class, + byteorder); } /* @@ -915,11 +1193,12 @@ static const struct l4_xinfo_class udp_xinfo_class = { .l3_decorator = {"", ""}, }; -static void load_xinfo_from_proc_udp(ino_t netns_inode) +static void load_xinfo_from_proc_udp(ino_t netns_inode, enum sysfs_byteorder byteorder) { load_xinfo_from_proc_inet_L4(netns_inode, "/proc/net/udp", - &udp_xinfo_class); + &udp_xinfo_class, + byteorder); } /* @@ -953,11 +1232,12 @@ static const struct l4_xinfo_class udplite_xinfo_class = { .l3_decorator = {"", ""}, }; -static void load_xinfo_from_proc_udplite(ino_t netns_inode) +static void load_xinfo_from_proc_udplite(ino_t netns_inode, enum sysfs_byteorder byteorder) { load_xinfo_from_proc_inet_L4(netns_inode, "/proc/net/udplite", - &udplite_xinfo_class); + &udplite_xinfo_class, + byteorder); } /* @@ -1082,11 +1362,12 @@ static const struct l4_xinfo_class raw_xinfo_class = { .l3_decorator = {"", ""}, }; -static void load_xinfo_from_proc_raw(ino_t netns_inode) +static void load_xinfo_from_proc_raw(ino_t netns_inode, enum sysfs_byteorder byteorder) { load_xinfo_from_proc_inet_L4(netns_inode, "/proc/net/raw", - &raw_xinfo_class); + &raw_xinfo_class, + byteorder); } /* @@ -1140,11 +1421,12 @@ static const struct l4_xinfo_class ping_xinfo_class = { .l3_decorator = {"", ""}, }; -static void load_xinfo_from_proc_icmp(ino_t netns_inode) +static void load_xinfo_from_proc_icmp(ino_t netns_inode, enum sysfs_byteorder byteorder) { load_xinfo_from_proc_inet_L4(netns_inode, "/proc/net/icmp", - &ping_xinfo_class); + &ping_xinfo_class, + byteorder); } /* @@ -1235,11 +1517,12 @@ static const struct l4_xinfo_class tcp6_xinfo_class = { .l3_decorator = {"[", "]"}, }; -static void load_xinfo_from_proc_tcp6(ino_t netns_inode) +static void load_xinfo_from_proc_tcp6(ino_t netns_inode, enum sysfs_byteorder byteorder) { load_xinfo_from_proc_inet_L4(netns_inode, "/proc/net/tcp6", - &tcp6_xinfo_class); + &tcp6_xinfo_class, + byteorder); } /* @@ -1273,11 +1556,12 @@ static const struct l4_xinfo_class udp6_xinfo_class = { .l3_decorator = {"[", "]"}, }; -static void load_xinfo_from_proc_udp6(ino_t netns_inode) +static void load_xinfo_from_proc_udp6(ino_t netns_inode, enum sysfs_byteorder byteorder) { load_xinfo_from_proc_inet_L4(netns_inode, "/proc/net/udp6", - &udp6_xinfo_class); + &udp6_xinfo_class, + byteorder); } /* @@ -1311,11 +1595,12 @@ static const struct l4_xinfo_class udplite6_xinfo_class = { .l3_decorator = {"[", "]"}, }; -static void load_xinfo_from_proc_udplite6(ino_t netns_inode) +static void load_xinfo_from_proc_udplite6(ino_t netns_inode, enum sysfs_byteorder byteorder) { load_xinfo_from_proc_inet_L4(netns_inode, "/proc/net/udplite6", - &udplite6_xinfo_class); + &udplite6_xinfo_class, + byteorder); } /* @@ -1402,11 +1687,12 @@ static const struct l4_xinfo_class raw6_xinfo_class = { .l3_decorator = {"[", "]"}, }; -static void load_xinfo_from_proc_raw6(ino_t netns_inode) +static void load_xinfo_from_proc_raw6(ino_t netns_inode, enum sysfs_byteorder byteorder) { load_xinfo_from_proc_inet_L4(netns_inode, "/proc/net/raw6", - &raw6_xinfo_class); + &raw6_xinfo_class, + byteorder); } /* @@ -1448,11 +1734,12 @@ static const struct l4_xinfo_class ping6_xinfo_class = { .l3_decorator = {"[", "]"}, }; -static void load_xinfo_from_proc_icmp6(ino_t netns_inode) +static void load_xinfo_from_proc_icmp6(ino_t netns_inode, enum sysfs_byteorder byteorder) { load_xinfo_from_proc_inet_L4(netns_inode, "/proc/net/icmp6", - &ping6_xinfo_class); + &ping6_xinfo_class, + byteorder); } /* diff --git a/misc-utils/lsfd-sock.c b/misc-utils/lsfd-sock.c index 3264516..4c75e6e 100644 --- a/misc-utils/lsfd-sock.c +++ b/misc-utils/lsfd-sock.c @@ -22,17 +22,29 @@ #include <sys/types.h> #include <sys/xattr.h> -#include "xalloc.h" -#include "nls.h" -#include "libsmartcols.h" - #include "lsfd.h" #include "lsfd-sock.h" static void attach_sock_xinfo(struct file *file) { struct sock *sock = (struct sock *)file; + sock->xinfo = get_sock_xinfo(file->stat.st_ino); + if (sock->xinfo) { + struct ipc *ipc = get_ipc(file); + if (ipc) + add_endpoint(&sock->endpoint, ipc); + } +} + +static const struct ipc_class *sock_get_ipc_class(struct file *file) +{ + struct sock *sock = (struct sock *)file; + + if (sock->xinfo && sock->xinfo->class->get_ipc_class) + return sock->xinfo->class->get_ipc_class(sock->xinfo, sock); + + return NULL; } static bool sock_fill_column(struct proc *proc __attribute__((__unused__)), @@ -43,6 +55,15 @@ static bool sock_fill_column(struct proc *proc __attribute__((__unused__)), { char *str = NULL; struct sock *sock = (struct sock *)file; + + if (sock->xinfo && sock->xinfo->class + && sock->xinfo->class->fill_column) { + if (sock->xinfo->class->fill_column(proc, sock->xinfo, sock, ln, + column_id, column_index, + &str)) + goto out; + } + switch(column_id) { case COL_TYPE: if (!sock->protoname) @@ -99,17 +120,14 @@ static bool sock_fill_column(struct proc *proc __attribute__((__unused__)), ? "1" : "0"); break; + case COL_SOCK_SHUTDOWN: + str = xstrdup("??"); + break; default: - if (sock->xinfo && sock->xinfo->class - && sock->xinfo->class->fill_column) { - if (sock->xinfo->class->fill_column(proc, sock->xinfo, sock, ln, - column_id, column_index, - &str)) - break; - } return false; } + out: if (!str) err(EXIT_FAILURE, _("failed to add output data")); if (scols_line_refer_data(ln, column_index, str)) @@ -120,13 +138,13 @@ static bool sock_fill_column(struct proc *proc __attribute__((__unused__)), static void init_sock_content(struct file *file) { int fd; + struct sock *sock = (struct sock *)file; assert(file); fd = file->association; if (fd >= 0 || fd == -ASSOC_MEM || fd == -ASSOC_SHM) { - struct sock *sock = (struct sock *)file; char path[PATH_MAX] = {'\0'}; char buf[256]; ssize_t len; @@ -147,6 +165,8 @@ static void init_sock_content(struct file *file) sock->protoname = xstrdup(buf); } } + + init_endpoint(&sock->endpoint); } static void free_sock_content(struct file *file) @@ -177,4 +197,5 @@ const struct file_class sock_class = { .free_content = free_sock_content, .initialize_class = initialize_sock_class, .finalize_class = finalize_sock_class, + .get_ipc_class = sock_get_ipc_class, }; diff --git a/misc-utils/lsfd-sock.h b/misc-utils/lsfd-sock.h index d488d0f..50360b7 100644 --- a/misc-utils/lsfd-sock.h +++ b/misc-utils/lsfd-sock.h @@ -43,6 +43,7 @@ struct sock { struct file file; char *protoname; struct sock_xinfo *xinfo; + struct ipc_endpoint endpoint; }; struct sock_xinfo_class { @@ -60,6 +61,7 @@ struct sock_xinfo_class { int, size_t, char **str); + struct ipc_class *(*get_ipc_class)(struct sock_xinfo *, struct sock *); void (*free)(struct sock_xinfo *); }; @@ -67,6 +69,6 @@ struct sock_xinfo_class { void initialize_sock_xinfos(void); void finalize_sock_xinfos(void); -struct sock_xinfo *get_sock_xinfo(ino_t netns_inode); +struct sock_xinfo *get_sock_xinfo(ino_t inode); #endif /* UTIL_LINUX_LSFD_SOCK_H */ diff --git a/misc-utils/lsfd-unkn.c b/misc-utils/lsfd-unkn.c index 087e31d..8f6e908 100644 --- a/misc-utils/lsfd-unkn.c +++ b/misc-utils/lsfd-unkn.c @@ -19,12 +19,19 @@ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "xalloc.h" -#include "nls.h" -#include "libsmartcols.h" +#include <linux/bpf.h> +#include <sys/syscall.h> +#include <sys/timerfd.h> +#include <time.h> + +#include "signames.h" +#include "timeutils.h" #include "lsfd.h" +#define offsetofend(TYPE, MEMBER) \ + (offsetof(TYPE, MEMBER) + sizeof_member(TYPE, MEMBER)) + struct unkn { struct file file; const struct anon_ops *anon_ops; @@ -33,6 +40,7 @@ struct unkn { struct anon_ops { const char *class; + bool (*probe)(const char *); char * (*get_name)(struct unkn *); /* Return true is handled the column. */ bool (*fill_column)(struct proc *, @@ -44,10 +52,11 @@ struct anon_ops { void (*init)(struct unkn *); void (*free)(struct unkn *); int (*handle_fdinfo)(struct unkn *, const char *, const char *); + void (*attach_xinfo)(struct unkn *); + const struct ipc_class *ipc_class; }; -static const struct anon_ops anon_generic_ops; -static const struct anon_ops anon_pidfd_ops; +static const struct anon_ops *anon_probe(const char *); static char * anon_get_class(struct unkn *unkn) { @@ -117,6 +126,22 @@ static bool unkn_fill_column(struct proc *proc, return true; } +static void unkn_attach_xinfo(struct file *file) +{ + struct unkn *unkn = (struct unkn *)file; + if (unkn->anon_ops && unkn->anon_ops->attach_xinfo) + unkn->anon_ops->attach_xinfo(unkn); +} + +static const struct ipc_class *unkn_get_ipc_class(struct file *file) +{ + struct unkn *unkn = (struct unkn *)file; + + if (unkn->anon_ops && unkn->anon_ops->ipc_class) + return unkn->anon_ops->ipc_class; + return NULL; +} + static void unkn_init_content(struct file *file) { struct unkn *unkn = (struct unkn *)file; @@ -129,10 +154,7 @@ static void unkn_init_content(struct file *file) && strncmp(file->name, "anon_inode:", 11) == 0) { const char *rest = file->name + 11; - if (strncmp(rest, "[pidfd]", 7) == 0) - unkn->anon_ops = &anon_pidfd_ops; - else - unkn->anon_ops = &anon_generic_ops; + unkn->anon_ops = anon_probe(rest); if (unkn->anon_ops->init) unkn->anon_ops->init(unkn); @@ -166,6 +188,11 @@ struct anon_pidfd_data { char *nspid; }; +static bool anon_pidfd_probe(const char *str) +{ + return strncmp(str, "[pidfd]", 7) == 0; +} + static char *anon_pidfd_get_name(struct unkn *unkn) { char *str = NULL; @@ -207,8 +234,7 @@ static int anon_pidfd_handle_fdinfo(struct unkn *unkn, const char *key, const ch return 0; /* ignore -- parse failed */ ((struct anon_pidfd_data *)unkn->anon_data)->pid = (pid_t)pid; return 1; - } - else if (strcmp(key, "NSpid") == 0) { + } else if (strcmp(key, "NSpid") == 0) { ((struct anon_pidfd_data *)unkn->anon_data)->nspid = xstrdup(value); return 1; @@ -253,6 +279,7 @@ static bool anon_pidfd_fill_column(struct proc *proc __attribute__((__unused__) static const struct anon_ops anon_pidfd_ops = { .class = "pidfd", + .probe = anon_pidfd_probe, .get_name = anon_pidfd_get_name, .fill_column = anon_pidfd_fill_column, .init = anon_pidfd_init, @@ -261,6 +288,1070 @@ static const struct anon_ops anon_pidfd_ops = { }; /* + * eventfd + */ +struct anon_eventfd_data { + int id; + struct unkn *backptr; + struct ipc_endpoint endpoint; +}; + +struct eventfd_ipc { + struct ipc ipc; + int id; +}; + +static unsigned int anon_eventfd_get_hash(struct file *file) +{ + struct unkn *unkn = (struct unkn *)file; + struct anon_eventfd_data *data = (struct anon_eventfd_data *)unkn->anon_data; + + return (unsigned int)data->id; +} + +static bool anon_eventfd_is_suitable_ipc(struct ipc *ipc, struct file *file) +{ + struct unkn *unkn = (struct unkn *)file; + struct anon_eventfd_data *data = (struct anon_eventfd_data *)unkn->anon_data; + + return ((struct eventfd_ipc *)ipc)->id == data->id; +} + +static const struct ipc_class anon_eventfd_ipc_class = { + .size = sizeof(struct eventfd_ipc), + .get_hash = anon_eventfd_get_hash, + .is_suitable_ipc = anon_eventfd_is_suitable_ipc, + .free = NULL, +}; + +static bool anon_eventfd_probe(const char *str) +{ + return strncmp(str, "[eventfd]", 9) == 0; +} + +static char *anon_eventfd_get_name(struct unkn *unkn) +{ + char *str = NULL; + struct anon_eventfd_data *data = (struct anon_eventfd_data *)unkn->anon_data; + + xasprintf(&str, "id=%d", data->id); + return str; +} + +static void anon_eventfd_init(struct unkn *unkn) +{ + struct anon_eventfd_data *data = xcalloc(1, sizeof(struct anon_eventfd_data)); + init_endpoint(&data->endpoint); + data->backptr = unkn; + unkn->anon_data = data; +} + +static void anon_eventfd_free(struct unkn *unkn) +{ + free(unkn->anon_data); +} + +static void anon_eventfd_attach_xinfo(struct unkn *unkn) +{ + struct anon_eventfd_data *data = (struct anon_eventfd_data *)unkn->anon_data; + unsigned int hash; + struct ipc *ipc = get_ipc(&unkn->file); + if (ipc) + goto link; + + ipc = new_ipc(&anon_eventfd_ipc_class); + ((struct eventfd_ipc *)ipc)->id = data->id; + + hash = anon_eventfd_get_hash(&unkn->file); + add_ipc(ipc, hash); + + link: + add_endpoint(&data->endpoint, ipc); +} + +static int anon_eventfd_handle_fdinfo(struct unkn *unkn, const char *key, const char *value) +{ + if (strcmp(key, "eventfd-id") == 0) { + int64_t id; + + int rc = ul_strtos64(value, &id, 10); + if (rc < 0) + return 0; + ((struct anon_eventfd_data *)unkn->anon_data)->id = (int)id; + return 1; + } + return 0; +} + +static inline char *anon_eventfd_data_xstrendpoint(struct file *file) +{ + char *str = NULL; + xasprintf(&str, "%d,%s,%d", + file->proc->pid, file->proc->command, file->association); + return str; +} + +static bool anon_eventfd_fill_column(struct proc *proc __attribute__((__unused__)), + struct unkn *unkn, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct anon_eventfd_data *data = (struct anon_eventfd_data *)unkn->anon_data; + + switch(column_id) { + case COL_EVENTFD_ID: + xasprintf(str, "%d", data->id); + return true; + case COL_ENDPOINTS: { + struct list_head *e; + char *estr; + foreach_endpoint(e, data->endpoint) { + struct anon_eventfd_data *other = list_entry(e, + struct anon_eventfd_data, + endpoint.endpoints); + if (data == other) + continue; + if (*str) + xstrputc(str, '\n'); + estr = anon_eventfd_data_xstrendpoint(&other->backptr->file); + xstrappend(str, estr); + free(estr); + } + if (!*str) + return false; + return true; + } + default: + return false; + } +} + +static const struct anon_ops anon_eventfd_ops = { + .class = "eventfd", + .probe = anon_eventfd_probe, + .get_name = anon_eventfd_get_name, + .fill_column = anon_eventfd_fill_column, + .init = anon_eventfd_init, + .free = anon_eventfd_free, + .handle_fdinfo = anon_eventfd_handle_fdinfo, + .attach_xinfo = anon_eventfd_attach_xinfo, + .ipc_class = &anon_eventfd_ipc_class, +}; + +/* + * eventpoll + */ +struct anon_eventpoll_data { + size_t count; + int *tfds; + struct list_head siblings; +}; + +static bool anon_eventpoll_probe(const char *str) +{ + return strncmp(str, "[eventpoll]", 11) == 0; +} + +static void anon_eventpoll_init(struct unkn *unkn) +{ + struct anon_eventpoll_data *data = xcalloc(1, sizeof(struct anon_eventpoll_data)); + INIT_LIST_HEAD(&data->siblings); + unkn->anon_data = data; +} + +static void anon_eventpoll_free(struct unkn *unkn) +{ + struct anon_eventpoll_data *data = unkn->anon_data; + free(data->tfds); + free(data); +} + +static int anon_eventpoll_handle_fdinfo(struct unkn *unkn, const char *key, const char *value) +{ + struct anon_eventpoll_data *data; + if (strcmp(key, "tfd") == 0) { + unsigned long tfd; + char *end = NULL; + + errno = 0; + tfd = strtoul(value, &end, 0); + if (errno != 0) + return 0; /* ignore -- parse failed */ + + data = (struct anon_eventpoll_data *)unkn->anon_data; + data->tfds = xreallocarray(data->tfds, ++data->count, sizeof(int)); + data->tfds[data->count - 1] = (int)tfd; + return 1; + } + return 0; +} + +static int intcmp(const void *a, const void *b) +{ + int ai = *(int *)a; + int bi = *(int *)b; + + return ai - bi; +} + +static void anon_eventpoll_attach_xinfo(struct unkn *unkn) +{ + struct anon_eventpoll_data *data = (struct anon_eventpoll_data *)unkn->anon_data; + if (data->count > 0) { + qsort(data->tfds, data->count, sizeof(data->tfds[0]), + intcmp); + list_add_tail(&data->siblings, + &unkn->file.proc->eventpolls); + } +} + +static char *anon_eventpoll_make_tfds_string(struct anon_eventpoll_data *data, + const char *prefix, + const char sep) +{ + char *str = prefix? xstrdup(prefix): NULL; + + char buf[256]; + for (size_t i = 0; i < data->count; i++) { + size_t offset = 0; + + if (i > 0) { + buf[0] = sep; + offset = 1; + } + snprintf(buf + offset, sizeof(buf) - offset, "%d", data->tfds[i]); + xstrappend(&str, buf); + } + return str; +} + +static char *anon_eventpoll_get_name(struct unkn *unkn) +{ + return anon_eventpoll_make_tfds_string((struct anon_eventpoll_data *)unkn->anon_data, + "tfds=", ','); +} + +static bool anon_eventpoll_fill_column(struct proc *proc __attribute__((__unused__)), + struct unkn *unkn, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct anon_eventpoll_data *data = (struct anon_eventpoll_data *)unkn->anon_data; + + switch(column_id) { + case COL_EVENTPOLL_TFDS: + *str =anon_eventpoll_make_tfds_string(data, NULL, '\n'); + if (*str) + return true; + break; + } + + return false; +} + +static const struct anon_ops anon_eventpoll_ops = { + .class = "eventpoll", + .probe = anon_eventpoll_probe, + .get_name = anon_eventpoll_get_name, + .fill_column = anon_eventpoll_fill_column, + .init = anon_eventpoll_init, + .free = anon_eventpoll_free, + .handle_fdinfo = anon_eventpoll_handle_fdinfo, + .attach_xinfo = anon_eventpoll_attach_xinfo, +}; + +static int numcomp(const void *a, const void *b) +{ + return *(int *)a - *(int *)b; +} + +bool is_multiplexed_by_eventpoll(int fd, struct list_head *eventpolls) +{ + struct list_head *t; + list_for_each (t, eventpolls) { + struct anon_eventpoll_data *data = list_entry(t, struct anon_eventpoll_data, siblings); + if (data->count) { + if (bsearch(&fd, data->tfds, + data->count, sizeof(data->tfds[0]), + numcomp)) + return true; + } + } + return false; +} + +/* + * timerfd + */ +struct anon_timerfd_data { + int clockid; + struct itimerspec itimerspec; +}; + +static bool anon_timerfd_probe(const char *str) +{ + return strncmp(str, "[timerfd]", 9) == 0; +} + +static void anon_timerfd_init(struct unkn *unkn) +{ + unkn->anon_data = xcalloc(1, sizeof(struct anon_timerfd_data)); +} + +static void anon_timerfd_free(struct unkn *unkn) +{ + struct anon_timerfd_data *data = unkn->anon_data; + free(data); +} + +static int anon_timerfd_handle_fdinfo(struct unkn *unkn, const char *key, const char *value) +{ + struct anon_timerfd_data *data = (struct anon_timerfd_data *)unkn->anon_data; + + if (strcmp(key, "clockid") == 0) { + unsigned long clockid; + char *end = NULL; + + errno = 0; + clockid = strtoul(value, &end, 0); + if (errno != 0) + return 0; /* ignore -- parse failed */ + if (*end != '\0') + return 0; /* ignore -- garbage remains. */ + + data->clockid = clockid; + return 1; + } else { + struct timespec *t; + uint64_t tv_sec; + uint64_t tv_nsec; + + if (strcmp(key, "it_value") == 0) + t = &data->itimerspec.it_value; + else if (strcmp(key, "it_interval") == 0) + t = &data->itimerspec.it_interval; + else + return 0; + + if (sscanf(value, "(%"SCNu64", %"SCNu64")", + &tv_sec, &tv_nsec) == 2) { + t->tv_sec = (time_t)tv_sec; + t->tv_nsec = (long)tv_nsec; + return 1; + } + + return 0; + } +} + +static const char *anon_timerfd_decode_clockid(int clockid) +{ + switch (clockid) { + case CLOCK_REALTIME: + return "realtime"; + case CLOCK_MONOTONIC: + return "monotonic"; + case CLOCK_BOOTTIME: + return "boottime"; + case CLOCK_REALTIME_ALARM: + return "realtime-alarm"; + case CLOCK_BOOTTIME_ALARM: + return "boottime-alarm"; + default: + return "unknown"; + } +} + +static void anon_timerfd_render_timespec_string(char *buf, size_t size, + const char *prefix, + const struct timespec *t) +{ + snprintf(buf, size, "%s%llu.%09ld", + prefix? prefix: "", + (unsigned long long)t->tv_sec, t->tv_nsec); +} + +static char *anon_timerfd_get_name(struct unkn *unkn) +{ + char *str = NULL; + + struct anon_timerfd_data *data = (struct anon_timerfd_data *)unkn->anon_data; + const struct timespec *exp; + const struct timespec *ival; + + const char *clockid_name; + char exp_buf[BUFSIZ] = {'\0'}; + char ival_buf[BUFSIZ] = {'\0'}; + + clockid_name = anon_timerfd_decode_clockid(data->clockid); + + exp = &data->itimerspec.it_value; + if (is_timespecset(exp)) + anon_timerfd_render_timespec_string(exp_buf, sizeof(exp_buf), + " remaining=", exp); + + ival = &data->itimerspec.it_interval; + if (is_timespecset(ival)) + anon_timerfd_render_timespec_string(ival_buf, sizeof(ival_buf), + " interval=", ival); + + xasprintf(&str, "clockid=%s%s%s", clockid_name, exp_buf, ival_buf); + return str; +} + +static bool anon_timerfd_fill_column(struct proc *proc __attribute__((__unused__)), + struct unkn *unkn, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct anon_timerfd_data *data = (struct anon_timerfd_data *)unkn->anon_data; + char buf[BUFSIZ] = {'\0'}; + + switch(column_id) { + case COL_TIMERFD_CLOCKID: + *str = xstrdup(anon_timerfd_decode_clockid(data->clockid)); + return true; + case COL_TIMERFD_INTERVAL: + anon_timerfd_render_timespec_string(buf, sizeof(buf), NULL, + &data->itimerspec.it_interval); + *str = xstrdup(buf); + return true; + case COL_TIMERFD_REMAINING: + anon_timerfd_render_timespec_string(buf, sizeof(buf), NULL, + &data->itimerspec.it_value); + *str = xstrdup(buf); + return true; + } + + return false; +} + +static const struct anon_ops anon_timerfd_ops = { + .class = "timerfd", + .probe = anon_timerfd_probe, + .get_name = anon_timerfd_get_name, + .fill_column = anon_timerfd_fill_column, + .init = anon_timerfd_init, + .free = anon_timerfd_free, + .handle_fdinfo = anon_timerfd_handle_fdinfo, +}; + +/* + * signalfd + */ +struct anon_signalfd_data { + uint64_t sigmask; +}; + +static bool anon_signalfd_probe(const char *str) +{ + return strncmp(str, "[signalfd]", 10) == 0; +} + +static void anon_signalfd_init(struct unkn *unkn) +{ + unkn->anon_data = xcalloc(1, sizeof(struct anon_signalfd_data)); +} + +static void anon_signalfd_free(struct unkn *unkn) +{ + struct anon_signalfd_data *data = unkn->anon_data; + free(data); +} + +static int anon_signalfd_handle_fdinfo(struct unkn *unkn, const char *key, const char *value) +{ + struct anon_signalfd_data *data = (struct anon_signalfd_data *)unkn->anon_data; + + if (strcmp(key, "sigmask") == 0) { + if (ul_strtou64(value, &data->sigmask, 16) < 0) { + data->sigmask = 0; + return 0; + } + } + return 0; +} + +static char *anon_signalfd_make_mask_string(const char* prefix, uint64_t sigmask) +{ + char *str = NULL; + + for (size_t i = 0; i < sizeof(sigmask) * 8; i++) { + if ((((uint64_t)0x1) << i) & sigmask) { + const int signum = i + 1; + const char *signame = signum_to_signame(signum); + + if (str) + xstrappend(&str, ","); + else if (prefix) + xstrappend(&str, prefix); + + if (signame) { + xstrappend(&str, signame); + } else { + char buf[BUFSIZ]; + snprintf(buf, sizeof(buf), "%d", signum); + xstrappend(&str, buf); + } + } + } + + return str; +} + +static char *anon_signalfd_get_name(struct unkn *unkn) +{ + struct anon_signalfd_data *data = (struct anon_signalfd_data *)unkn->anon_data; + return anon_signalfd_make_mask_string("mask=", data->sigmask); +} + +static bool anon_signalfd_fill_column(struct proc *proc __attribute__((__unused__)), + struct unkn *unkn, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct anon_signalfd_data *data = (struct anon_signalfd_data *)unkn->anon_data; + + switch(column_id) { + case COL_SIGNALFD_MASK: + *str = anon_signalfd_make_mask_string(NULL, data->sigmask); + return true; + default: + return false; + } +} + +static const struct anon_ops anon_signalfd_ops = { + .class = "signalfd", + .probe = anon_signalfd_probe, + .get_name = anon_signalfd_get_name, + .fill_column = anon_signalfd_fill_column, + .init = anon_signalfd_init, + .free = anon_signalfd_free, + .handle_fdinfo = anon_signalfd_handle_fdinfo, +}; + +/* + * inotify + */ +struct anon_inotify_data { + struct list_head inodes; +}; + +struct anon_inotify_inode { + ino_t ino; + dev_t sdev; + struct list_head inodes; +}; + +static bool anon_inotify_probe(const char *str) +{ + return strncmp(str, "inotify", 7) == 0; +} + +/* A device number appeared in fdinfo of an inotify file uses the kernel + * internal representation. It is different from what we are familiar with; + * major(3) and minor(3) don't work with the representation. + * See linux/include/linux/kdev_t.h. */ +#define ANON_INOTIFY_MINORBITS 20 +#define ANON_INOTIFY_MINORMASK ((1U << ANON_INOTIFY_MINORBITS) - 1) + +#define ANON_INOTIFY_MAJOR(dev) ((unsigned int) ((dev) >> ANON_INOTIFY_MINORBITS)) +#define ANON_INOTIFY_MINOR(dev) ((unsigned int) ((dev) & ANON_INOTIFY_MINORMASK)) + +static char *anon_inotify_make_inodes_string(const char *prefix, + const char *sep, + enum decode_source_level decode_level, + struct anon_inotify_data *data) +{ + char *str = NULL; + char buf[BUFSIZ] = {'\0'}; + bool first_element = true; + + struct list_head *i; + list_for_each(i, &data->inodes) { + char source[BUFSIZ/2] = {'\0'}; + struct anon_inotify_inode *inode = list_entry(i, + struct anon_inotify_inode, + inodes); + + decode_source(source, sizeof(source), + ANON_INOTIFY_MAJOR(inode->sdev), ANON_INOTIFY_MINOR(inode->sdev), + decode_level); + snprintf(buf, sizeof(buf), "%s%llu@%s", first_element? prefix: sep, + (unsigned long long)inode->ino, source); + first_element = false; + + xstrappend(&str, buf); + } + + return str; +} + +static char *anon_inotify_get_name(struct unkn *unkn) +{ + return anon_inotify_make_inodes_string("inodes=", ",", DECODE_SOURCE_FULL, + (struct anon_inotify_data *)unkn->anon_data); +} + +static void anon_inotify_init(struct unkn *unkn) +{ + struct anon_inotify_data *data = xcalloc(1, sizeof(struct anon_inotify_data)); + INIT_LIST_HEAD (&data->inodes); + unkn->anon_data = data; +} + +static void anon_inotify_free(struct unkn *unkn) +{ + struct anon_inotify_data *data = unkn->anon_data; + + list_free(&data->inodes, struct anon_inotify_inode, inodes, + free); + free(data); +} + +static void add_inode(struct anon_inotify_data *data, ino_t ino, dev_t sdev) +{ + struct anon_inotify_inode *inode = xmalloc(sizeof(*inode)); + + INIT_LIST_HEAD (&inode->inodes); + inode->ino = ino; + inode->sdev = sdev; + + list_add_tail(&inode->inodes, &data->inodes); +} + +static int anon_inotify_handle_fdinfo(struct unkn *unkn, const char *key, const char *value) +{ + struct anon_inotify_data *data = (struct anon_inotify_data *)unkn->anon_data; + + if (strcmp(key, "inotify wd") == 0) { + unsigned long long ino; + unsigned long long sdev; + + if (sscanf(value, "%*d ino:%llx sdev:%llx %*s", &ino, &sdev) == 2) { + add_inode(data, (ino_t)ino, (dev_t)sdev); + return 1; + } + } + return 0; +} + +static bool anon_inotify_fill_column(struct proc *proc __attribute__((__unused__)), + struct unkn *unkn, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct anon_inotify_data *data = (struct anon_inotify_data *)unkn->anon_data; + + switch(column_id) { + case COL_INOTIFY_INODES: + *str = anon_inotify_make_inodes_string("", "\n", DECODE_SOURCE_FULL, + data); + if (*str) + return true; + break; + case COL_INOTIFY_INODES_RAW: + *str = anon_inotify_make_inodes_string("", "\n", DECODE_SOURCE_MAJMIN, + data); + if (*str) + return true; + break; + } + + return false; +} + +static const struct anon_ops anon_inotify_ops = { + .class = "inotify", + .probe = anon_inotify_probe, + .get_name = anon_inotify_get_name, + .fill_column = anon_inotify_fill_column, + .init = anon_inotify_init, + .free = anon_inotify_free, + .handle_fdinfo = anon_inotify_handle_fdinfo, +}; + +/* + * bpf-prog + * + * Generally, we use "-" as the word separators in lsfd's output. + * However, about bpf*, we use "_" because bpftool uses "_". + */ +static const char *bpf_prog_type_table[] = { + [0] = "unspec", /* BPF_PROG_TYPE_UNSPEC*/ + [1] = "socket_filter", /* BPF_PROG_TYPE_SOCKET_FILTER*/ + [2] = "kprobe", /* BPF_PROG_TYPE_KPROBE*/ + [3] = "sched_cls", /* BPF_PROG_TYPE_SCHED_CLS*/ + [4] = "sched_act", /* BPF_PROG_TYPE_SCHED_ACT*/ + [5] = "tracepoint", /* BPF_PROG_TYPE_TRACEPOINT*/ + [6] = "xdp", /* BPF_PROG_TYPE_XDP*/ + [7] = "perf_event", /* BPF_PROG_TYPE_PERF_EVENT*/ + [8] = "cgroup_skb", /* BPF_PROG_TYPE_CGROUP_SKB*/ + [9] = "cgroup_sock", /* BPF_PROG_TYPE_CGROUP_SOCK*/ + [10] = "lwt_in", /* BPF_PROG_TYPE_LWT_IN*/ + [11] = "lwt_out", /* BPF_PROG_TYPE_LWT_OUT*/ + [12] = "lwt_xmit", /* BPF_PROG_TYPE_LWT_XMIT*/ + [13] = "sock_ops", /* BPF_PROG_TYPE_SOCK_OPS*/ + [14] = "sk_skb", /* BPF_PROG_TYPE_SK_SKB*/ + [15] = "cgroup_device", /* BPF_PROG_TYPE_CGROUP_DEVICE*/ + [16] = "sk_msg", /* BPF_PROG_TYPE_SK_MSG*/ + [17] = "raw_tracepoint", /* BPF_PROG_TYPE_RAW_TRACEPOINT*/ + [18] = "cgroup_sock_addr", /* BPF_PROG_TYPE_CGROUP_SOCK_ADDR*/ + [19] = "lwt_seg6local", /* BPF_PROG_TYPE_LWT_SEG6LOCAL*/ + [20] = "lirc_mode2", /* BPF_PROG_TYPE_LIRC_MODE2*/ + [21] = "sk_reuseport", /* BPF_PROG_TYPE_SK_REUSEPORT*/ + [22] = "flow_dissector", /* BPF_PROG_TYPE_FLOW_DISSECTOR*/ + [23] = "cgroup_sysctl", /* BPF_PROG_TYPE_CGROUP_SYSCTL*/ + [24] = "raw_tracepoint_writable", /* BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE*/ + [25] = "cgroup_sockopt", /* BPF_PROG_TYPE_CGROUP_SOCKOPT*/ + [26] = "tracing", /* BPF_PROG_TYPE_TRACING*/ + [27] = "struct_ops", /* BPF_PROG_TYPE_STRUCT_OPS*/ + [28] = "ext", /* BPF_PROG_TYPE_EXT*/ + [29] = "lsm", /* BPF_PROG_TYPE_LSM*/ + [30] = "sk_lookup", /* BPF_PROG_TYPE_SK_LOOKUP*/ + [31] = "syscall", /* BPF_PROG_TYPE_SYSCALL*/ +}; + +struct anon_bpf_prog_data { + int type; + int id; + char name[BPF_OBJ_NAME_LEN + 1]; +}; + +static bool anon_bpf_prog_probe(const char *str) +{ + return strncmp(str, "bpf-prog", 8) == 0; +} + +static const char *anon_bpf_prog_get_prog_type_name(int type) +{ + if (0 <= type && type < (int)ARRAY_SIZE(bpf_prog_type_table)) + return bpf_prog_type_table[type]; + return NULL; +} + +static bool anon_bpf_prog_fill_column(struct proc *proc __attribute__((__unused__)), + struct unkn *unkn, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct anon_bpf_prog_data *data = (struct anon_bpf_prog_data *)unkn->anon_data; + const char *t; + + switch(column_id) { + case COL_BPF_PROG_ID: + xasprintf(str, "%d", data->id); + return true; + case COL_BPF_PROG_TYPE_RAW: + xasprintf(str, "%d", data->type); + return true; + case COL_BPF_PROG_TYPE: + t = anon_bpf_prog_get_prog_type_name(data->type); + if (t) + *str = xstrdup(t); + else + xasprintf(str, "UNKNOWN(%d)", data->type); + return true; + case COL_BPF_NAME: + *str = xstrdup(data->name); + return true; + default: + return false; + } +} + +static char *anon_bpf_prog_get_name(struct unkn *unkn) +{ + const char *t; + char *str = NULL; + struct anon_bpf_prog_data *data = (struct anon_bpf_prog_data *)unkn->anon_data; + + t = anon_bpf_prog_get_prog_type_name(data->type); + if (t) + xasprintf(&str, "id=%d type=%s", data->id, t); + else + xasprintf(&str, "id=%d type=UNKNOWN(%d)", data->id, data->type); + + if (*data->name) + xstrfappend(&str, " name=%s", data->name); + + return str; +} + + +static void anon_bpf_prog_init(struct unkn *unkn) +{ + struct anon_bpf_prog_data *data = xmalloc(sizeof(*data)); + data->type = -1; + data->id = -1; + data->name[0] = '\0'; + unkn->anon_data = data; +} + +static void anon_bpf_prog_free(struct unkn *unkn) +{ + struct anon_bpf_prog_data *data = (struct anon_bpf_prog_data *)unkn->anon_data; + free(data); +} + +static void anon_bpf_prog_get_more_info(struct anon_bpf_prog_data *prog_data) +{ + union bpf_attr attr = { + .prog_id = (int32_t)prog_data->id, + .next_id = 0, + .open_flags = 0, + }; + struct bpf_prog_info info = { 0 }; + union bpf_attr info_attr = { + .info.info_len = sizeof(info), + .info.info = (uint64_t)(uintptr_t)&info, + }; + + int bpf_fd = syscall(SYS_bpf, BPF_PROG_GET_FD_BY_ID, &attr, sizeof(attr)); + if (bpf_fd < 0) + return; + + info_attr.info.bpf_fd = bpf_fd; + if (syscall(SYS_bpf, BPF_OBJ_GET_INFO_BY_FD, &info_attr, offsetofend(union bpf_attr, info)) == 0) { + memcpy(prog_data->name, + info.name, + BPF_OBJ_NAME_LEN); + prog_data->name[BPF_OBJ_NAME_LEN] = '\0'; + } + close(bpf_fd); +} + +static int anon_bpf_prog_handle_fdinfo(struct unkn *unkn, const char *key, const char *value) +{ + if (strcmp(key, "prog_id") == 0) { + int32_t t = -1; + int rc = ul_strtos32(value, &t, 10); + if (rc < 0) + return 0; /* ignore -- parse failed */ + ((struct anon_bpf_prog_data *)unkn->anon_data)->id = (int)t; + anon_bpf_prog_get_more_info((struct anon_bpf_prog_data *)unkn->anon_data); + return 1; + } + + if (strcmp(key, "prog_type") == 0) { + int32_t t = -1; + int rc = ul_strtos32(value, &t, 10); + if (rc < 0) + return 0; /* ignore -- parse failed */ + ((struct anon_bpf_prog_data *)unkn->anon_data)->type = (int)t; + return 1; + } + + return 0; +} + +static const struct anon_ops anon_bpf_prog_ops = { + .class = "bpf-prog", + .probe = anon_bpf_prog_probe, + .get_name = anon_bpf_prog_get_name, + .fill_column = anon_bpf_prog_fill_column, + .init = anon_bpf_prog_init, + .free = anon_bpf_prog_free, + .handle_fdinfo = anon_bpf_prog_handle_fdinfo, +}; + +/* + * bpf-map + */ +static const char *bpf_map_type_table[] = { + [0] = "unspec", /* BPF_MAP_TYPE_UNSPEC */ + [1] = "hash", /* BPF_MAP_TYPE_HASH */ + [2] = "array", /* BPF_MAP_TYPE_ARRAY */ + [3] = "prog-array", /* BPF_MAP_TYPE_PROG_ARRAY */ + [4] = "perf-event-array", /* BPF_MAP_TYPE_PERF_EVENT_ARRAY */ + [5] = "percpu-hash", /* BPF_MAP_TYPE_PERCPU_HASH */ + [6] = "percpu-array", /* BPF_MAP_TYPE_PERCPU_ARRAY */ + [7] = "stack-trace", /* BPF_MAP_TYPE_STACK_TRACE */ + [8] = "cgroup-array", /* BPF_MAP_TYPE_CGROUP_ARRAY */ + [9] = "lru-hash", /* BPF_MAP_TYPE_LRU_HASH */ + [10] = "lru-percpu-hash", /* BPF_MAP_TYPE_LRU_PERCPU_HASH */ + [11] = "lpm-trie", /* BPF_MAP_TYPE_LPM_TRIE */ + [12] = "array-of-maps", /* BPF_MAP_TYPE_ARRAY_OF_MAPS */ + [13] = "hash-of-maps", /* BPF_MAP_TYPE_HASH_OF_MAPS */ + [14] = "devmap", /* BPF_MAP_TYPE_DEVMAP */ + [15] = "sockmap", /* BPF_MAP_TYPE_SOCKMAP */ + [16] = "cpumap", /* BPF_MAP_TYPE_CPUMAP */ + [17] = "xskmap", /* BPF_MAP_TYPE_XSKMAP */ + [18] = "sockhash", /* BPF_MAP_TYPE_SOCKHASH */ + [19] = "cgroup-storage", /* BPF_MAP_TYPE_CGROUP_STORAGE */ + [20] = "reuseport-sockarray", /* BPF_MAP_TYPE_REUSEPORT_SOCKARRAY */ + [21] = "percpu-cgroup-storage", /* BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE */ + [22] = "queue", /* BPF_MAP_TYPE_QUEUE */ + [23] = "stack", /* BPF_MAP_TYPE_STACK */ + [24] = "sk-storage", /* BPF_MAP_TYPE_SK_STORAGE */ + [25] = "devmap-hash", /* BPF_MAP_TYPE_DEVMAP_HASH */ + [26] = "struct-ops", /* BPF_MAP_TYPE_STRUCT_OPS */ + [27] = "ringbuf", /* BPF_MAP_TYPE_RINGBUF */ + [28] = "inode-storage", /* BPF_MAP_TYPE_INODE_STORAGE */ + [29] = "task-storage", /* BPF_MAP_TYPE_TASK_STORAGE */ + [30] = "bloom-filter", /* BPF_MAP_TYPE_BLOOM_FILTER */ + [31] = "user-ringbuf", /* BPF_MAP_TYPE_USER_RINGBUF */ + [32] = "cgrp-storage", /* BPF_MAP_TYPE_CGRP_STORAGE */ +}; + +struct anon_bpf_map_data { + int type; + int id; + char name[BPF_OBJ_NAME_LEN + 1]; +}; + +static bool anon_bpf_map_probe(const char *str) +{ + return strncmp(str, "bpf-map", 8) == 0; +} + +static const char *anon_bpf_map_get_map_type_name(int type) +{ + if (0 <= type && type < (int)ARRAY_SIZE(bpf_map_type_table)) + return bpf_map_type_table[type]; + return NULL; +} + +static bool anon_bpf_map_fill_column(struct proc *proc __attribute__((__unused__)), + struct unkn *unkn, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct anon_bpf_prog_data *data = (struct anon_bpf_prog_data *)unkn->anon_data; + const char *t; + + switch(column_id) { + case COL_BPF_MAP_ID: + xasprintf(str, "%d", data->id); + return true; + case COL_BPF_MAP_TYPE_RAW: + xasprintf(str, "%d", data->type); + return true; + case COL_BPF_MAP_TYPE: + t = anon_bpf_map_get_map_type_name(data->type); + if (t) + *str = xstrdup(t); + else + xasprintf(str, "UNKNOWN(%d)", data->type); + return true; + case COL_BPF_NAME: + *str = xstrdup(data->name); + return true; + default: + return false; + } +} + +static char *anon_bpf_map_get_name(struct unkn *unkn) +{ + const char *t; + char *str = NULL; + struct anon_bpf_map_data *data = (struct anon_bpf_map_data *)unkn->anon_data; + + t = anon_bpf_map_get_map_type_name(data->type); + if (t) + xasprintf(&str, "id=%d type=%s", data->id, t); + else + xasprintf(&str, "id=%d type=UNKNOWN(%d)", data->id, data->type); + + if (*data->name) + xstrfappend(&str, " name=%s", data->name); + + return str; +} + +static void anon_bpf_map_init(struct unkn *unkn) +{ + struct anon_bpf_map_data *data = xmalloc(sizeof(*data)); + data->type = -1; + data->id = -1; + data->name[0] = '\0'; + unkn->anon_data = data; +} + +static void anon_bpf_map_free(struct unkn *unkn) +{ + struct anon_bpf_map_data *data = (struct anon_bpf_map_data *)unkn->anon_data; + free(data); +} + +static void anon_bpf_map_get_more_info(struct anon_bpf_map_data *map_data) +{ + union bpf_attr attr = { + .map_id = (int32_t)map_data->id, + .next_id = 0, + .open_flags = 0, + }; + struct bpf_map_info info = { 0 }; + union bpf_attr info_attr = { + .info.info_len = sizeof(info), + .info.info = (uint64_t)(uintptr_t)&info, + }; + + int bpf_fd = syscall(SYS_bpf, BPF_MAP_GET_FD_BY_ID, &attr, sizeof(attr)); + if (bpf_fd < 0) + return; + + info_attr.info.bpf_fd = bpf_fd; + if (syscall(SYS_bpf, BPF_OBJ_GET_INFO_BY_FD, &info_attr, offsetofend(union bpf_attr, info)) == 0) { + memcpy(map_data->name, + info.name, + BPF_OBJ_NAME_LEN); + map_data->name[BPF_OBJ_NAME_LEN] = '\0'; + } + close(bpf_fd); +} + +static int anon_bpf_map_handle_fdinfo(struct unkn *unkn, const char *key, const char *value) +{ + if (strcmp(key, "map_id") == 0) { + int32_t t = -1; + int rc = ul_strtos32(value, &t, 10); + if (rc < 0) + return 0; /* ignore -- parse failed */ + ((struct anon_bpf_map_data *)unkn->anon_data)->id = (int)t; + anon_bpf_map_get_more_info((struct anon_bpf_map_data *)unkn->anon_data); + return 1; + } + + if (strcmp(key, "map_type") == 0) { + int32_t t = -1; + int rc = ul_strtos32(value, &t, 10); + if (rc < 0) + return 0; /* ignore -- parse failed */ + ((struct anon_bpf_map_data *)unkn->anon_data)->type = (int)t; + return 1; + } + + return 0; +} + +static const struct anon_ops anon_bpf_map_ops = { + .class = "bpf-map", + .probe = anon_bpf_map_probe, + .get_name = anon_bpf_map_get_name, + .fill_column = anon_bpf_map_fill_column, + .init = anon_bpf_map_init, + .free = anon_bpf_map_free, + .handle_fdinfo = anon_bpf_map_handle_fdinfo, +}; + +/* * generic (fallback implementation) */ static const struct anon_ops anon_generic_ops = { @@ -272,6 +1363,25 @@ static const struct anon_ops anon_generic_ops = { .handle_fdinfo = NULL, }; +static const struct anon_ops *anon_ops[] = { + &anon_pidfd_ops, + &anon_eventfd_ops, + &anon_eventpoll_ops, + &anon_timerfd_ops, + &anon_signalfd_ops, + &anon_inotify_ops, + &anon_bpf_prog_ops, + &anon_bpf_map_ops, +}; + +static const struct anon_ops *anon_probe(const char *str) +{ + for (size_t i = 0; i < ARRAY_SIZE(anon_ops); i++) + if (anon_ops[i]->probe(str)) + return anon_ops[i]; + return &anon_generic_ops; +} + const struct file_class unkn_class = { .super = &file_class, .size = sizeof(struct unkn), @@ -279,4 +1389,6 @@ const struct file_class unkn_class = { .initialize_content = unkn_init_content, .free_content = unkn_content_free, .handle_fdinfo = unkn_handle_fdinfo, + .attach_xinfo = unkn_attach_xinfo, + .get_ipc_class = unkn_get_ipc_class, }; diff --git a/misc-utils/lsfd.1 b/misc-utils/lsfd.1 index 9baaa42..efc229c 100644 --- a/misc-utils/lsfd.1 +++ b/misc-utils/lsfd.1 @@ -2,12 +2,12 @@ .\" Title: lsfd .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-12-01 +.\" Date: 2024-03-27 .\" Manual: User Commands -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "LSFD" "1" "2023-12-01" "util\-linux 2.39.3" "User Commands" +.TH "LSFD" "1" "2024-03-27" "util\-linux 2.40" "User Commands" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 @@ -45,7 +45,7 @@ default outputs in your scripts. Always explicitly define expected columns by us \fB\-\-output\fP \fIcolumns\-list\fP in environments where a stable output is required. .sp \fBlsfd\fP uses Libsmartcols for output formatting and filtering. See the description of \fB\-\-output\fP -option for customizing the output format, and \fB\-\-filter\fP option for filtering. Use \fBlsfd \-\-help\fP +option for customizing the output format, and \fB\-\-filter\fP option for filtering. Use \fBlsfd \-\-list\-columns\fP to get a list of all available columns. .SH "OPTIONS" .sp @@ -103,7 +103,7 @@ List only IPv4 sockets and/or IPv6 sockets. \fB\-Q\fP, \fB\-\-filter\fP \fIexpr\fP .RS 4 Print only the files matching the condition represented by the \fIexpr\fP. -See also \fBFILTER EXAMPLES\fP. +See also \fBscols\-filter\fP(5) and \fBFILTER EXAMPLES\fP. .RE .sp \fB\-C\fP, \fB\-\-counter\fP \fIlabel\fP:\fIfilter_expr\fP @@ -114,7 +114,7 @@ matching \fIfilter_expr\fP, and stores the counted number to the counter named \fIlabel\fP. \fBlsfd\fP applies filters defined with \fB\-\-filter\fP options before counting; files excluded by the filters are not counted. .sp -See \fBFILTER EXPRESSION\fP about \fIfilter_expr\fP. +See \fBscols\-filter\fP(5) about \fIfilter_expr\fP. \fIlabel\fP should not include \f(CR{\fP nor \f(CR:\fP. You can define multiple counters by specifying this option multiple times. .sp @@ -146,6 +146,11 @@ only for \fBlsfd\fP developers. Dump the definition of counters used in \fB\-\-summary\fP output. .RE .sp +\fB\-H\fP, \fB\-\-list\-columns\fP +.RS 4 +List available columns that you can specify at \fB\-\-output\fP option. +.RE +.sp \fB\-h\fP, \fB\-\-help\fP .RS 4 Display help text and exit. @@ -177,6 +182,41 @@ BLKDRV <\f(CRstring\fP> Block device driver name resolved by \f(CR/proc/devices\fP. .RE .sp +BPF\-MAP.ID <\f(CRnumber\fP> +.RS 4 +Bpf map ID. +.RE +.sp +BPF\-MAP.TYPE <\f(CRstring\fP> +.RS 4 +Decoded name of bpf map type. +.RE +.sp +BPF\-MAP.TYPE.RAW <\f(CRnumber\fP> +.RS 4 +Bpf map type (raw). +.RE +.sp +BPF.NAME <\f(CRstring\fP> +.RS 4 +Bpf object name. +.RE +.sp +BPF\-PROG.ID <\f(CRnumber\fP> +.RS 4 +Bpf program ID. +.RE +.sp +BPF\-PROG.TYPE <\f(CRstring\fP> +.RS 4 +Decoded name of bpf program type. +.RE +.sp +BPF\-PROG.TYPE.RAW <\f(CRnumber\fP> +.RS 4 +Bpf program type (raw). +.RE +.sp CHRDRV <\f(CRstring\fP> .RS 4 Character device driver name resolved by \f(CR/proc/devices\fP. @@ -205,10 +245,15 @@ Device type (\f(CRblk\fP, \f(CRchar\fP, or \f(CRnodev\fP). ENDPOINT <\f(CRstring\fP> .RS 4 IPC endpoints information communicated with the fd. +.sp +\fBlsfd\fP collects endpoints within the processes that +\fBlsfd\fP scans; \fBlsfd\fP may miss some endpoints +if you limits the processes with \fB\-p\fP option. +.sp The format of the column depends on the object associated with the fd: .sp -FIFO type +FIFO type, mqueue type, ptmx and pts sources .RS 4 \fIPID\fP,\fICOMMAND\fP,\fIASSOC\fP[\-r][\-w] .sp @@ -216,9 +261,28 @@ The last characters ([\-r][\-w]) represents the read and/or write mode of the endpoint. .RE .sp -\fBlsfd\fP collects endpoints within the processes that -\fBlsfd\fP scans; \fBlsfd\fP may miss some endpoints -if you limits the processes with \fB\-p\fP option. +eventfd type +.RS 4 +\fIPID\fP,\fICOMMAND\fP,\fIASSOC\fP +.RE +.sp +UNIX\-STREAM +.RS 4 +\fIPID\fP,\fICOMMAND\fP,\fIASSOC\fP[\-r?][\-w?] +.sp +About the last characters ([\-r?][\-w?]), see the description +of \fISOCK.SHUTDOWN\fP. +.RE +.RE +.sp +EVENTFD.ID <\f(CRnumber\fP> +.RS 4 +Eventfd ID. +.RE +.sp +EVENTPOLL.TFDS <\f(CRstring\fP> +.RS 4 +File descriptors targeted by the eventpoll file. .RE .sp FD <\f(CRnumber\fP> @@ -261,6 +325,19 @@ INODE <\f(CRnumber\fP> Inode number. .RE .sp +INOTIFY.INODES <\f(CRstring\fP> +.RS 4 +Cooked version of INOTIFY.INODES.RAW. +The format of the element is +\fIinode\-number\fP,\fIsource\-of\-inode\fP. +.RE +.sp +INOTIFY.INODES.RAW <\f(CRstring\fP> +.RS 4 +List of monitoring inodes. The format of the element +is \fIinode\-number\fP\f(CR,\fP\fIdevice\-major\fP\f(CR:\fP\fIdevice\-minor\fP. +.RE +.sp KNAME <\f(CRstring\fP> .RS 4 Raw file name extracted from @@ -303,6 +380,36 @@ Cooked version of KNAME. It is mostly same as KNAME. .sp Some files have special formats and information sources: .sp +bpf\-map +.RS 4 +id=\fIBPF\-MAP.ID\fP type=\fIBPF\-MAP.TYPE\fP[ name=\fIBPF.NAME\fP] +.RE +.sp +bpf\-prog +.RS 4 +id=\fIBPF\-PROG.ID\fP type=\fIBPF\-PROG.TYPE\fP[ name=\fIBPF.NAME\fP] +.RE +.sp +eventpoll +.RS 4 +tfds=\fIEVENTPOLL.TFDS\fP +.RE +.sp +eventfd +.RS 4 +id=\fIEVENTFD.ID\fP +.RE +.sp +inotify +.RS 4 +inodes=\fIINOTIFY.INODES\fP +.RE +.sp +misc:tun +.RS 4 +iface=\fITUN.IFACE\fP +.RE +.sp NETLINK .RS 4 protocol=\fINETLINK.PROTOCOL\fP[ lport=\fINETLINK.LPORT\fP[ group=\fINETLINK.GROUPS\fP]] @@ -331,6 +438,14 @@ PINGv6 state=\fISOCK.STATE\fP[ id=\fIPING.ID\fP][ laddr=\fIINET6.LADDR\fP [ raddr=\fIINET6.RADDR\fP]] .RE .sp +ptmx +.RS 4 +tty\-index=\fIPTMX.TTY\-INDEX\fP +.sp +\fBlsfd\fP extracts \fIPTMX.TTY\-INDEX\fP from +\f(CR/proc/\fP\fIpid\fP\f(CR/fdinfo/\fP\fIfd\fP. +.RE +.sp RAW .RS 4 state=\fISOCK.STATE\fP[ protocol=\fIRAW.PROTOCOL\fP [ laddr=\fIINET.LADDR\fP [ raddr=\fIINET.RADDR\fP]]] @@ -341,11 +456,21 @@ RAWv6 state=\fISOCK.STATE\fP[ protocol=\fIRAW.PROTOCOL\fP [ laddr=\fIINET6.LADDR\fP [ raddr=\fIINET6.RADDR\fP]]] .RE .sp +signalfd +.RS 4 +mask=\fISIGNALFD.MASK\fP +.RE +.sp TCP, TCPv6 .RS 4 state=\fISOCK.STATE\fP[ laddr=\fITCP.LADDR\fP [ raddr=\fITCP.RADDR\fP]] .RE .sp +timerfd +.RS 4 +clockid=\fITIMERFD.CLOCKID\fP[ remaining=\fITIMERFD.REMAINING\fP [ interval=\fITIMERFD.INTERVAL\fP]] +.RE +.sp UDP, UDPv6 .RS 4 state=\fISOCK.STATE\fP[ laddr=\fIUDP.LADDR\fP [ raddr=\fIUDP.RADDR\fP]] @@ -368,18 +493,27 @@ UNIX state=\fISOCK.STATE\fP[ path=\fIUNIX.PATH\fP] type=\fISOCK.TYPE\fP .RE .RE +.RS 3 +.ll -.6i .sp -NETLINK.GROUPS <\f(CRnumber\fP>> +Note that \f(CR(deleted)\fP markers are removed from this column. +Refer to \fIKNAME\fP, \fIDELETED\fP, or \fIXMODE\fP to know the +readability of the file from the file system. +.br +.RE +.ll +.sp +NETLINK.GROUPS <\f(CRnumber\fP> .RS 4 Netlink multicast groups. .RE .sp -NETLINK.LPORT <\f(CRnumber\fP>> +NETLINK.LPORT <\f(CRnumber\fP> .RS 4 Netlink local port id. .RE .sp -NETLINK.PROTOCOL <\f(CRstring\fP>> +NETLINK.PROTOCOL <\f(CRstring\fP> .RS 4 Netlink protocol. .RE @@ -477,6 +611,11 @@ RDEV <\f(CRstring\fP> Device ID (if special file). .RE .sp +SIGNALFD.MASK <\f(CRstring\fP> +.RS 4 +Masked signals. +.RE +.sp SIZE <\f(CRnumber\fP> .RS 4 File size. @@ -497,6 +636,25 @@ SOCK.PROTONAME <\f(CRstring\fP> Protocol name. .RE .sp +SOCK.SHUTDOWN <\f(CRstring\fP> +.RS 4 +Shutdown state of socket. +.sp +[\-r?] +.RS 4 +If the first character is \fIr\fP, the receptions are allowed. +If it is \fI\-\fP, the receptions are disallowed. +If it is \fI?\fP, the state is unknown. +.RE +.sp +[\-w?] +.RS 4 +If the second character is \fIw\fP, the transmissions are allowed. +If it is \fI\-\fP, the transmissions are disallowed. +If it is \fI?\fP, the state is unknown. +.RE +.RE +.sp SOCK.STATE <\f(CRstring\fP> .RS 4 State of socket. @@ -597,20 +755,20 @@ Raw file types returned from \fBstat\fP(2): BLK, CHR, DIR, FIFO, LINK, REG, SOCK .sp TCP.LADDR <\f(CRstring\fP> .RS 4 -Local L3 (INET.LADDR or INET6.LADDR) address and local TCP port. +Local L3 (\fIINET.LADDR\fP or \fIINET6.LADDR\fP) address and local TCP port. .RE .sp -TCP.LPORT <\f(CRinteger\fP> +TCP.LPORT <\f(CRnumber\fP> .RS 4 Local TCP port. .RE .sp TCP.RADDR <\f(CRstring\fP> .RS 4 -Remote L3 (INET.RADDR or INET6.RADDR) address and remote TCP port. +Remote L3 (\fIINET.RADDR\fP or \fIINET6.RADDR\fP) address and remote TCP port. .RE .sp -TCP.RPORT <\f(CRinteger\fP> +TCP.RPORT <\f(CRnumber\fP> .RS 4 Remote TCP port. .RE @@ -620,11 +778,36 @@ TID <\f(CRnumber\fP> Thread ID of the process opening the file. .RE .sp +TIMERFD.CLOCKID <\f(CRstring\fP> +.RS 4 +Clockid. +.RE +.sp +TIMERFD.INTERVAL <\f(CRnumber\fP> +.RS 4 +Interval. +.RE +.sp +TIMERFD.REMAINING <\f(CRnumber\fP> +.RS 4 +Remaining time. +.RE +.sp +PTMX.TTY\-INDEX <\f(CRnumber\fP> +.RS 4 +TTY index of the counterpart. +.RE +.sp +TUN.IFACE <\f(CRstring\fP> +.RS 4 +Network interface behind the tun device. +.RE +.sp TYPE <\f(CRstring\fP> .RS 4 -Cooked version of STTYPE. It is same as STTYPE with exceptions. -For SOCK, print the value for SOCK.PROTONAME. -For UNKN, print the value for AINODECLASS if SOURCE is anon_inodefs. +Cooked version of \fISTTYPE\fP. It is same as \fISTTYPE\fP with exceptions. +For \fISOCK\fP, print the value for \fISOCK.PROTONAME\fP. +For \fIUNKN\fP, print the value for \fIAINODECLASS\fP if \fISOURCE\fP is \f(CRanon_inodefs\fP. .RE .sp UDP.LADDR <\f(CRstring\fP> @@ -632,7 +815,7 @@ UDP.LADDR <\f(CRstring\fP> Local IP address and local UDP port. .RE .sp -UDP.LPORT <\f(CRinteger\fP> +UDP.LPORT <\f(CRnumber\fP> .RS 4 Local UDP port. .RE @@ -642,7 +825,7 @@ UDP.RADDR <\f(CRstring\fP> Remote IP address and remote UDP port. .RE .sp -UDP.RPORT <\f(CRinteger\fP> +UDP.RPORT <\f(CRnumber\fP> .RS 4 Remote UDP port. .RE @@ -652,7 +835,7 @@ UDPLITE.LADDR <\f(CRstring\fP> Local IP address and local UDPLite port. .RE .sp -UDPLITE.LPORT <\f(CRinteger\fP> +UDPLITE.LPORT <\f(CRnumber\fP> .RS 4 Local UDP port. .RE @@ -662,7 +845,7 @@ UDPLITE.RADDR <\f(CRstring\fP> Remote IP address and remote UDPLite port. .RE .sp -UDPLITE.RPORT <\f(CRinteger\fP> +UDPLITE.RPORT <\f(CRnumber\fP> .RS 4 Remote UDP port. .RE @@ -681,158 +864,50 @@ USER <\f(CRstring\fP> .RS 4 User of the process. .RE -.SH "FILTER EXPRESSION" -.sp -\fBlsfd\fP evaluates the expression passed to \fB\-\-filter\fP option every time -before printing a file line. \fBlsfd\fP prints the line only if the result -of evaluation is \f(CRtrue\fP. -.sp -An expression consists of column names, literals and, operators like: -\f(CRDELETED\fP, \f(CR(PID == 1)\fP, \f(CR(NAME == "/etc/passwd")\fP, \f(CR(PID == 1) && DELETED\fP. -\f(CRDELETED\fP, \f(CRPID\fP, and \f(CRNAME\fP are column names in the example. -\f(CR1\fP and "/etc/passwd" are literals. -\f(CR==\fP and \f(CR&&\fP are operators. -.sp -Before evaluation, \fBlsfd\fP substitutes column names in the given -expression with actual column values in the line. There are three -different data types: \f(CRboolean\fP, \f(CRstring\fP, and \f(CRnumber\fP. For columns -with a \f(CRboolean\fP type, the value can be stand\-alone. For \f(CRstring\fP and -\f(CRnumber\fP values, the value must be an operand of an operator, for -example, \f(CR(PID == 1)\fP. See \fBOUTPUT COLUMNS\fP about the types of -columns. -.sp -Literal is for representing a value directly. See BOOLLIT, STRLIT, and -NUMLIT. Different data types have different literal syntax. -.sp -An operator works with one or two operand(s). An operator has an -expectation about the data type(s) of its operands. Giving an -unexpected data type to an operator causes a syntax error. -.sp -Operators taking two operands are \f(CRand\fP, \f(CRor\fP, \f(CReq\fP, \f(CRne\fP, \f(CRle\fP, \f(CRlt\fP, \f(CRge\fP, \f(CRgt\fP, \f(CR=~\fP, \f(CR!~\fP. -Alphabetically named operators have C\-language -flavored aliases: \f(CR&&\fP, \f(CR||\fP, \f(CR==\fP, \f(CR!=\fP, \f(CR<\fP, \f(CR\(lA\fP, \f(CR>=\fP, and \f(CR>\fP. -.sp -\f(CR!\fP is the only operator that takes one operand. -.sp -\f(CReq\fP, \f(CRne\fP, and their aliases expect operands have the same data type. -Applying these operators return a \f(CRboolean\fP. -.sp -\f(CRand\fP, \f(CRor\fP, \f(CRnot\fP and their aliases expect operands have \f(CRboolean\fP data -type. Applying these operators return a \f(CRboolean\fP. -.sp -\f(CRlt\fP, \f(CRle\fP, \f(CRgt\fP, \f(CRge\fP, and their aliases expect operands have -\f(CRnumber\fP data types. Applying these operators return a \f(CRboolean\fP. .sp -\f(CR=~\fP is for regular expression matching; if a string at the right side -matches a regular expression at the left side, the result is true. -The right side operand must be a string literal. See STRLIT about the -syntax. -.sp -\f(CR!~\fP is a short\-hand version of \f(CRnot (STR =~ PAT)\fP; it inverts the -result of \f(CR=~\fP. -.SS "Limitations" -.sp -The current implementation does not define precedences within -operators. Use \f(CR(\fP and \f(CR)\fP explicitly for grouping the -sub\-expressions if your expression uses more than two operators. -.sp -About \f(CRnumber\fP typed values, the filter engine supports only -non\-negative integers. -.SS "Semi\-formal syntax" -.sp -EXPR -.RS 4 -BOOLEXP -.RE -.sp -BOOLEXP0 -.RS 4 -COLUMN <\f(CRboolean\fP> | BOOLLIT | \fI(\fP BOOLEXP \fI)\fP -.RE -.sp -BOOLEXP -.RS 4 -BOOLEXP0 | BOOLOP1 | BOOLOP2 | BOOLOP2BL | BOOLOP2CMP | BOOLOP2REG -.RE -.sp -COLUMN -.RS 4 -[_A\-Za\-z][\-_:A\-Za\-z0\-9]* -.RE -.sp -BOOLOP1 -.RS 4 -\fI!\fP BOOLEXP0 | \fInot\fP BOOLEXP0 -.RE -.sp -STREXP -.RS 4 -COLUMN <\f(CRstring\fP> | STRLIT -.RE -.sp -NUMEXP -.RS 4 -COLUMN <\f(CRnumber\fP> | NUMLIT -.RE -.sp -BOOLLIT -.RS 4 -\fItrue\fP | \fIfalse\fP -.RE -.sp -CHARS -.RS 4 -( [^\(rs] | \fI\(rs\(rs\fP | \fI\(rs\*(Aq\fP | \fI\(rs"\fP )* -.RE -.sp -STRLIT -.RS 4 -\fI\*(Aq\fP CHARS \fI\*(Aq\fP | \fI"\fP CHARS \fI"\fP -.RE -.sp -NUMLIT -.RS 4 -[1\-9][0\-9]* | \fI0\fP -.RE -.sp -BOOLOP2 +XMODE <\f(CRstring\fP> .RS 4 -STREXP OP2 STREXP | NUMEXP OP2 NUMEXP | BOOLEXP0 OP2 BOOLEXP0 -.RE +Extended version of \fIMODE\fP. This column may grow; new letters may be +appended to \fIXMODE\fP when \fBlsfd\fP supports a new state of file descriptors +and/or memory mappings. .sp -OP2 +[\-r] .RS 4 -\fI==\fP | \fIeq\fP | \fI!=\fP | \fIne\fP +opened of mapped for reading. This is also in \fIMODE\fP. .RE .sp -BOOLOP2BL +[\-w] .RS 4 -BOOLEXP0 OP2BL BOOLEXP0 +opened of mapped for writing. This is also in \fIMODE\fP. .RE .sp -OP2BL +[\-x] .RS 4 -\fI&&\fP | \fIand\fP | \fI||\fP | \fIor\fP +mapped for executing the code. This is also in \fIMODE\fP. .RE .sp -BOOLOP2CMP +[\-D] .RS 4 -NUMEXP OP2CMP NUMEXP +deleted from the file system. See also \fIDELETED\fP. .RE .sp -OP2CMP +[\-Ll] .RS 4 -\fI<\fP | \fIlt\fP | \fI<=\fP | \fIle\fP | \fI>\fP | \fIgt\fP | \fI>=\fP | \fIge\fP +locked or leased. \fIl\fP represents a read, a shared lock or a read lease. +\fIL\fP represents a write or an exclusive lock or a write lease. If both +read/shared and write/exclusive locks or leases are taken by a file +descriptor, \fIL\fP is used as the flag. .RE .sp -BOOLOP2REG +[\-m] .RS 4 -STREXP OP2REG STRLIT +Multiplexed. If the file descriptor is targeted by a eventpoll file +or classical system calls for multiplexing (select, pselect, poll, and +ppoll), this bit flag is set. Note that if an invocation of the +classical system calls is interrupted, \fBlsfd\fP may fail to mark \fIm\fP +on the file descriptors monitored by the invocation. +See \fBrestart_syscall\fP(2). .RE -.sp -OP2REG -.RS 4 -\fI=~\fP | \fI!~\fP .RE .SH "FILTER EXAMPLES" .sp @@ -1032,14 +1107,14 @@ List files opened in a QEMU virtual machine: .fi .if n .RE .sp -Hide files associated to kernel threads: +List timerfd files expired within 0.5 seconds: .RS 4 .RE .sp .if n .RS 4 .nf .fam C -# lsfd \-Q \*(Aq!KTHREAD\*(Aq +# lsfd \-Q \*(Aq(TIMERFD.remaining < 0.5) and (TIMERFD.remaining > 0.0)\*(Aq .fam .fi .if n .RE @@ -1095,10 +1170,15 @@ The \fBlsfd\fP command is part of the util\-linux package since v2.38. .MTO "kzak\(atredhat.com" "Karel Zak" "" .SH "SEE ALSO" .sp +\fBbpftool\fP(8) +\fBbps\fP(8) +\fBlslocks\fP(8) \fBlsof\fP(8) \fBpidof\fP(1) \fBproc\fP(5) +\fBscols\-filter\fP(5) \fBsocket\fP(2) +\fBss\fP(8) \fBstat\fP(2) .SH "REPORTING BUGS" .sp diff --git a/misc-utils/lsfd.1.adoc b/misc-utils/lsfd.1.adoc index 23eee28..512fb23 100644 --- a/misc-utils/lsfd.1.adoc +++ b/misc-utils/lsfd.1.adoc @@ -33,7 +33,7 @@ default outputs in your scripts. Always explicitly define expected columns by us *--output* _columns-list_ in environments where a stable output is required. *lsfd* uses Libsmartcols for output formatting and filtering. See the description of *--output* -option for customizing the output format, and *--filter* option for filtering. Use *lsfd --help* +option for customizing the output format, and *--filter* option for filtering. Use *lsfd --list-columns* to get a list of all available columns. == OPTIONS @@ -75,7 +75,7 @@ List only IPv4 sockets and/or IPv6 sockets. *-Q*, *--filter* _expr_:: Print only the files matching the condition represented by the _expr_. -See also *FILTER EXAMPLES*. +See also *scols-filter*(5) and *FILTER EXAMPLES*. *-C*, *--counter* __label__:__filter_expr__:: Define a custom counter used in *--summary* output. *lsfd* makes a @@ -84,7 +84,7 @@ matching _filter_expr_, and stores the counted number to the counter named _label_. *lsfd* applies filters defined with *--filter* options before counting; files excluded by the filters are not counted. + -See *FILTER EXPRESSION* about _filter_expr_. +See *scols-filter*(5) about _filter_expr_. _label_ should not include `{` nor `:`. You can define multiple counters by specifying this option multiple times. + @@ -110,6 +110,9 @@ only for *lsfd* developers. *--dump-counters*:: Dump the definition of counters used in *--summary* output. +*-H*, *--list-columns*:: +List available columns that you can specify at *--output* option. + include::man-common/help-version.adoc[] == OUTPUT COLUMNS @@ -129,6 +132,27 @@ Association between file and process. BLKDRV <``string``>:: Block device driver name resolved by `/proc/devices`. +BPF-MAP.ID <``number``>:: +Bpf map ID. + +BPF-MAP.TYPE <``string``>:: +Decoded name of bpf map type. + +BPF-MAP.TYPE.RAW <``number``>:: +Bpf map type (raw). + +BPF.NAME <``string``>:: +Bpf object name. + +BPF-PROG.ID <``number``>:: +Bpf program ID. + +BPF-PROG.TYPE <``string``>:: +Decoded name of bpf program type. + +BPF-PROG.TYPE.RAW <``number``>:: +Bpf program type (raw). + CHRDRV <``string``>:: Character device driver name resolved by `/proc/devices`. @@ -146,19 +170,36 @@ Device type (`blk`, `char`, or `nodev`). ENDPOINT <``string``>:: IPC endpoints information communicated with the fd. ++ +*lsfd* collects endpoints within the processes that +*lsfd* scans; *lsfd* may miss some endpoints +if you limits the processes with *-p* option. ++ The format of the column depends on the object associated with the fd: -+ + FIFO type::: +mqueue type::: +ptmx and pts sources::: _PID_,_COMMAND_,_ASSOC_[-r][-w] + The last characters ([-r][-w]) represents the read and/or write mode of the endpoint. +eventfd type::: +_PID_,_COMMAND_,_ASSOC_ + +UNIX-STREAM::: +_PID_,_COMMAND_,_ASSOC_[-r?][-w?] + -*lsfd* collects endpoints within the processes that -*lsfd* scans; *lsfd* may miss some endpoints -if you limits the processes with *-p* option. +About the last characters ([-r?][-w?]), see the description +of _SOCK.SHUTDOWN_. + +EVENTFD.ID <``number``>:: +Eventfd ID. + +EVENTPOLL.TFDS <``string``>:: +File descriptors targeted by the eventpoll file. FD <``number``>:: File descriptor for the file. @@ -184,6 +225,15 @@ Remote IP6 address. INODE <``number``>:: Inode number. +INOTIFY.INODES <``string``>:: +Cooked version of INOTIFY.INODES.RAW. +The format of the element is +_inode-number_,_source-of-inode_. + +INOTIFY.INODES.RAW <``string``>:: +List of monitoring inodes. The format of the element +is _inode-number_``,``_device-major_``:``_device-minor_. + KNAME <``string``>:: // // It seems that the manpage backend of asciidoctor has limitations @@ -219,6 +269,24 @@ Cooked version of KNAME. It is mostly same as KNAME. + Some files have special formats and information sources: + +bpf-map::: +id=_BPF-MAP.ID_ type=_BPF-MAP.TYPE_[ name=_BPF.NAME_] ++ +bpf-prog::: +id=_BPF-PROG.ID_ type=_BPF-PROG.TYPE_[ name=_BPF.NAME_] ++ +eventpoll::: +tfds=_EVENTPOLL.TFDS_ ++ +eventfd::: +id=_EVENTFD.ID_ ++ +inotify::: +inodes=_INOTIFY.INODES_ ++ +misc:tun::: +iface=_TUN.IFACE_ ++ NETLINK::: protocol=_NETLINK.PROTOCOL_[ lport=_NETLINK.LPORT_[ group=_NETLINK.GROUPS_]] + @@ -237,16 +305,28 @@ state=_SOCK.STATE_[ id=_PING.ID_][ laddr=_INET.LADDR_ [ raddr=_INET.RADDR_]] PINGv6::: state=_SOCK.STATE_[ id=_PING.ID_][ laddr=_INET6.LADDR_ [ raddr=_INET6.RADDR_]] + +ptmx::: +tty-index=_PTMX.TTY-INDEX_ ++ +*lsfd* extracts _PTMX.TTY-INDEX_ from +``/proc/``_pid_``/fdinfo/``_fd_. ++ RAW::: state=_SOCK.STATE_[ protocol=_RAW.PROTOCOL_ [ laddr=_INET.LADDR_ [ raddr=_INET.RADDR_]]] + RAWv6::: state=_SOCK.STATE_[ protocol=_RAW.PROTOCOL_ [ laddr=_INET6.LADDR_ [ raddr=_INET6.RADDR_]]] + +signalfd::: +mask=_SIGNALFD.MASK_ ++ TCP::: TCPv6::: state=_SOCK.STATE_[ laddr=_TCP.LADDR_ [ raddr=_TCP.RADDR_]] + +timerfd::: +clockid=_TIMERFD.CLOCKID_[ remaining=_TIMERFD.REMAINING_ [ interval=_TIMERFD.INTERVAL_]] ++ UDP::: UDPv6::: state=_SOCK.STATE_[ laddr=_UDP.LADDR_ [ raddr=_UDP.RADDR_]] @@ -263,13 +343,19 @@ state=_SOCK.STATE_[ path=_UNIX.PATH_] UNIX::: state=_SOCK.STATE_[ path=_UNIX.PATH_] type=_SOCK.TYPE_ -NETLINK.GROUPS <``number``>>:: +____ +Note that `(deleted)` markers are removed from this column. +Refer to _KNAME_, _DELETED_, or _XMODE_ to know the +readability of the file from the file system. +____ + +NETLINK.GROUPS <``number``>:: Netlink multicast groups. -NETLINK.LPORT <``number``>>:: +NETLINK.LPORT <``number``>:: Netlink local port id. -NETLINK.PROTOCOL <``string``>>:: +NETLINK.PROTOCOL <``string``>:: Netlink protocol. NLINK <``number``>:: @@ -332,6 +418,9 @@ Protocol number of the raw socket. RDEV <``string``>:: Device ID (if special file). +SIGNALFD.MASK <``string``>:: +Masked signals. + SIZE <``number``>:: File size. @@ -344,6 +433,19 @@ Inode identifying network namespace where the socket belongs to. SOCK.PROTONAME <``string``>:: Protocol name. +SOCK.SHUTDOWN <``string``>:: +Shutdown state of socket. ++ +[-r?]::: +If the first character is _r_, the receptions are allowed. +If it is _-_, the receptions are disallowed. +If it is _?_, the state is unknown. ++ +[-w?]::: +If the second character is _w_, the transmissions are allowed. +If it is _-_, the transmissions are disallowed. +If it is _?_, the state is unknown. + SOCK.STATE <``string``>:: State of socket. @@ -366,47 +468,62 @@ STTYPE <``string``>:: Raw file types returned from *stat*(2): BLK, CHR, DIR, FIFO, LINK, REG, SOCK, or UNKN. TCP.LADDR <``string``>:: -Local L3 (INET.LADDR or INET6.LADDR) address and local TCP port. +Local L3 (_INET.LADDR_ or _INET6.LADDR_) address and local TCP port. -TCP.LPORT <``integer``>:: +TCP.LPORT <``number``>:: Local TCP port. TCP.RADDR <``string``>:: -Remote L3 (INET.RADDR or INET6.RADDR) address and remote TCP port. +Remote L3 (_INET.RADDR_ or _INET6.RADDR_) address and remote TCP port. -TCP.RPORT <``integer``>:: +TCP.RPORT <``number``>:: Remote TCP port. TID <``number``>:: Thread ID of the process opening the file. +TIMERFD.CLOCKID <``string``>:: +Clockid. + +TIMERFD.INTERVAL <``number``>:: +Interval. + +TIMERFD.REMAINING <``number``>:: +Remaining time. + +PTMX.TTY-INDEX <``number``>:: +TTY index of the counterpart. + +TUN.IFACE <``string``>:: +Network interface behind the tun device. + TYPE <``string``>:: -Cooked version of STTYPE. It is same as STTYPE with exceptions. -For SOCK, print the value for SOCK.PROTONAME. -For UNKN, print the value for AINODECLASS if SOURCE is anon_inodefs. +Cooked version of _STTYPE_. It is same as _STTYPE_ with exceptions. +For _SOCK_, print the value for _SOCK.PROTONAME_. +For _UNKN_, print the value for _AINODECLASS_ if _SOURCE_ is `anon_inodefs`. UDP.LADDR <``string``>:: Local IP address and local UDP port. -UDP.LPORT <``integer``>:: +UDP.LPORT <``number``>:: Local UDP port. UDP.RADDR <``string``>:: Remote IP address and remote UDP port. -UDP.RPORT <``integer``>:: +UDP.RPORT <``number``>:: Remote UDP port. UDPLITE.LADDR <``string``>:: Local IP address and local UDPLite port. -UDPLITE.LPORT <``integer``>:: +UDPLITE.LPORT <``number``>:: Local UDP port. UDPLITE.RADDR <``string``>:: Remote IP address and remote UDPLite port. -UDPLITE.RPORT <``integer``>:: +UDPLITE.RPORT <``number``>:: Remote UDP port. UID <``number``>:: @@ -418,105 +535,36 @@ Filesystem pathname for UNIX domain socket. USER <``string``>:: User of the process. -== FILTER EXPRESSION - -*lsfd* evaluates the expression passed to *--filter* option every time -before printing a file line. *lsfd* prints the line only if the result -of evaluation is `true`. - -An expression consists of column names, literals and, operators like: -`DELETED`, `(PID == 1)`, `(NAME == "/etc/passwd")`, `(PID == 1) && DELETED`. -`DELETED`, `PID`, and `NAME` are column names in the example. -`1` and "/etc/passwd" are literals. -`==` and `&&` are operators. - -Before evaluation, *lsfd* substitutes column names in the given -expression with actual column values in the line. There are three -different data types: `boolean`, `string`, and `number`. For columns -with a `boolean` type, the value can be stand-alone. For `string` and -`number` values, the value must be an operand of an operator, for -example, `(PID == 1)`. See *OUTPUT COLUMNS* about the types of -columns. - -Literal is for representing a value directly. See BOOLLIT, STRLIT, and -NUMLIT. Different data types have different literal syntax. - -An operator works with one or two operand(s). An operator has an -expectation about the data type(s) of its operands. Giving an -unexpected data type to an operator causes a syntax error. - -Operators taking two operands are `and`, `or`, `eq`, `ne`, `le`, `lt`, `ge`, `gt`, `=~`, `!~`. -Alphabetically named operators have C-language -flavored aliases: `&&`, `||`, `==`, `!=`, `<`, `<=`, `>=`, and `>`. - -`!` is the only operator that takes one operand. - -`eq`, `ne`, and their aliases expect operands have the same data type. -Applying these operators return a `boolean`. - -`and`, `or`, `not` and their aliases expect operands have `boolean` data -type. Applying these operators return a `boolean`. - -`lt`, `le`, `gt`, `ge`, and their aliases expect operands have -`number` data types. Applying these operators return a `boolean`. - -`=~` is for regular expression matching; if a string at the right side -matches a regular expression at the left side, the result is true. -The right side operand must be a string literal. See STRLIT about the -syntax. - -`!~` is a short-hand version of `not (STR =~ PAT)`; it inverts the -result of `=~`. - -=== Limitations - -The current implementation does not define precedences within -operators. Use `(` and `)` explicitly for grouping the -sub-expressions if your expression uses more than two operators. - -About `number` typed values, the filter engine supports only -non-negative integers. - -=== Semi-formal syntax - -//TRANSLATORS: In the following messages, translate only the <``variables``>. -EXPR :: BOOLEXP - -BOOLEXP0 :: COLUMN <``boolean``> | BOOLLIT | _(_ BOOLEXP _)_ - -BOOLEXP :: BOOLEXP0 | BOOLOP1 | BOOLOP2 | BOOLOP2BL | BOOLOP2CMP | BOOLOP2REG - -COLUMN :: [\_A-Za-z][-_:A-Za-z0-9]* - -BOOLOP1 :: _!_ BOOLEXP0 | _not_ BOOLEXP0 - -STREXP :: COLUMN <``string``> | STRLIT - -NUMEXP :: COLUMN <``number``> | NUMLIT - -BOOLLIT :: _true_ | _false_ - -CHARS :: ( [^\] | _\\_ | _\'_ | _\"_ )* - -STRLIT :: _'_ CHARS _'_ | _"_ CHARS _"_ - -NUMLIT :: [1-9][0-9]* | _0_ - -BOOLOP2 :: STREXP OP2 STREXP | NUMEXP OP2 NUMEXP | BOOLEXP0 OP2 BOOLEXP0 - -OP2 :: _==_ | _eq_ | _!=_ | _ne_ - -BOOLOP2BL :: BOOLEXP0 OP2BL BOOLEXP0 - -OP2BL :: _&&_ | _and_ | _||_ | _or_ - -BOOLOP2CMP :: NUMEXP OP2CMP NUMEXP - -OP2CMP :: _<_ | _lt_ | _\<=_ | _le_ | _>_ | _gt_ | _>=_ | _ge_ - -BOOLOP2REG :: STREXP OP2REG STRLIT - -OP2REG :: _=~_ | _!~_ +XMODE <``string``>:: +Extended version of _MODE_. This column may grow; new letters may be +appended to _XMODE_ when *lsfd* supports a new state of file descriptors +and/or memory mappings. ++ +[-r]::: +opened of mapped for reading. This is also in _MODE_. ++ +[-w]::: +opened of mapped for writing. This is also in _MODE_. ++ +[-x]::: +mapped for executing the code. This is also in _MODE_. ++ +[-D]::: +deleted from the file system. See also _DELETED_. ++ +[-Ll]::: +locked or leased. _l_ represents a read, a shared lock or a read lease. +_L_ represents a write or an exclusive lock or a write lease. If both +read/shared and write/exclusive locks or leases are taken by a file +descriptor, _L_ is used as the flag. ++ +[-m]::: +Multiplexed. If the file descriptor is targeted by a eventpoll file +or classical system calls for multiplexing (select, pselect, poll, and +ppoll), this bit flag is set. Note that if an invocation of the +classical system calls is interrupted, *lsfd* may fail to mark _m_ +on the file descriptors monitored by the invocation. +See *restart_syscall*(2). == FILTER EXAMPLES @@ -605,9 +653,9 @@ List files opened in a QEMU virtual machine: :: # lsfd -Q '(COMMAND =~ ".\*qemu.*") and (FD >= 0)' .... -Hide files associated to kernel threads: :: +List timerfd files expired within 0.5 seconds: :: .... -# lsfd -Q '!KTHREAD' +# lsfd -Q '(TIMERFD.remaining < 0.5) and (TIMERFD.remaining > 0.0)' .... == COUNTER EXAMPLES @@ -651,11 +699,15 @@ mailto:yamato@redhat.com[Masatake YAMATO], mailto:kzak@redhat.com[Karel Zak] == SEE ALSO - +*bpftool*(8) +*bps*(8) +*lslocks*(8) *lsof*(8) *pidof*(1) *proc*(5) +*scols-filter*(5) *socket*(2) +*ss*(8) *stat*(2) include::man-common/bugreports.adoc[] diff --git a/misc-utils/lsfd.c b/misc-utils/lsfd.c index f8537a7..771daef 100644 --- a/misc-utils/lsfd.c +++ b/misc-utils/lsfd.c @@ -31,36 +31,58 @@ #include <getopt.h> #include <ctype.h> #include <search.h> +#include <poll.h> +#include <sys/select.h> +#include <sys/uio.h> #include <linux/sched.h> #include <sys/syscall.h> -#include <linux/kcmp.h> + +#ifdef HAVE_LINUX_KCMP_H +# include <linux/kcmp.h> static int kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2) { return syscall(SYS_kcmp, pid1, pid2, type, idx1, idx2); } +#else +# ifndef KCMP_FS +# define KCMP_FS 0 +# endif +# ifndef KCMP_VM +# define KCMP_VM 0 +# endif +# ifndef KCMP_FILES +# define KCMP_FILES 0 +# endif +static int kcmp(pid_t pid1 __attribute__((__unused__)), + pid_t pid2 __attribute__((__unused__)), + int type __attribute__((__unused__)), + unsigned long idx1 __attribute__((__unused__)), + unsigned long idx2 __attribute__((__unused__))) +{ + /* lsfd uses kcmp only for optimization. If the platform doesn't provide + * kcmp, just returning an error is acceptable. */ + errno = ENOSYS; + return -1; +} +#endif /* See proc(5). * Defined in linux/include/linux/sched.h private header file. */ #define PF_KTHREAD 0x00200000 /* I am a kernel thread */ #include "c.h" -#include "nls.h" -#include "xalloc.h" #include "list.h" #include "closestream.h" +#include "column-list-table.h" #include "strutils.h" #include "procfs.h" #include "fileutils.h" #include "idcache.h" #include "pathnames.h" -#include "libsmartcols.h" - #include "lsfd.h" -#include "lsfd-filter.h" -#include "lsfd-counter.h" /* * /proc/$pid/mountinfo entries @@ -129,6 +151,27 @@ static const struct colinfo infos[] = { [COL_BLKDRV] = { "BLKDRV", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, N_("block device driver name resolved by /proc/devices") }, + [COL_BPF_MAP_ID] = { "BPF-MAP.ID", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("bpf map id associated with the fd") }, + [COL_BPF_MAP_TYPE] = { "BPF-MAP.TYPE", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("bpf map type (decoded)") }, + [COL_BPF_MAP_TYPE_RAW]= { "BPF-MAP.TYPE.RAW", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("bpf map type (raw)") }, + [COL_BPF_NAME] = { "BPF.NAME", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("bpf object name") }, + [COL_BPF_PROG_ID] = { "BPF-PROG.ID", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("bpf program id associated with the fd") }, + [COL_BPF_PROG_TYPE] = { "BPF-PROG.TYPE", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("bpf program type (decoded)") }, + [COL_BPF_PROG_TYPE_RAW]= { "BPF-PROG.TYPE.RAW", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("bpf program type (raw)") }, [COL_CHRDRV] = { "CHRDRV", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, N_("character device driver name resolved by /proc/devices") }, @@ -147,18 +190,21 @@ static const struct colinfo infos[] = { [COL_ENDPOINTS] = { "ENDPOINTS", 0, SCOLS_FL_WRAP, SCOLS_JSON_ARRAY_STRING, N_("IPC endpoints information communicated with the fd") }, - [COL_FLAGS] = { "FLAGS", - 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, - N_("flags specified when opening the file") }, + [COL_EVENTFD_ID] = {"EVENTFD.ID", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("eventfd ID") }, + [COL_EVENTPOLL_TFDS] = {"EVENTPOLL.TFDS", + 0, SCOLS_FL_WRAP, SCOLS_JSON_ARRAY_NUMBER, + N_("file descriptors targeted by the eventpoll file") }, [COL_FD] = { "FD", 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, N_("file descriptor for the file") }, + [COL_FLAGS] = { "FLAGS", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("flags specified when opening the file") }, [COL_FUID] = { "FUID", 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, N_("user ID number of the file's owner") }, - [COL_INODE] = { "INODE", - 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, - N_("inode number") }, [COL_INET_LADDR] = { "INET.LADDR", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, N_("local IP address") }, @@ -171,6 +217,15 @@ static const struct colinfo infos[] = { [COL_INET6_RADDR] = { "INET6.RADDR", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, N_("remote IPv6 address") }, + [COL_INODE] = { "INODE", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("inode number") }, + [COL_INOTIFY_INODES] = { "INOTIFY.INODES", + 0, SCOLS_FL_WRAP, SCOLS_JSON_ARRAY_STRING, + N_("list of monitoring inodes (cooked)") }, + [COL_INOTIFY_INODES_RAW]={ "INOTIFY.INODES.RAW", + 0, SCOLS_FL_WRAP, SCOLS_JSON_ARRAY_STRING, + N_("list of monitoring inodes (raw, don't decode devices)") }, [COL_KNAME] = { "KNAME", 0.4, SCOLS_FL_TRUNC, SCOLS_JSON_STRING, N_("name of the file (raw)") }, @@ -243,12 +298,18 @@ static const struct colinfo infos[] = { [COL_POS] = { "POS", 5, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, N_("file position") }, + [COL_PTMX_TTY_INDEX] = { "PTMX.TTY-INDEX", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("tty index of the counterpart") }, [COL_RAW_PROTOCOL] = { "RAW.PROTOCOL", 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, N_("protocol number of the raw socket") }, [COL_RDEV] = { "RDEV", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, N_("device ID (if special file)") }, + [COL_SIGNALFD_MASK] = { "SIGNALFD.MASK", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("masked signals") }, [COL_SIZE] = { "SIZE", 4, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, N_("file size"), }, @@ -261,12 +322,15 @@ static const struct colinfo infos[] = { [COL_SOCK_PROTONAME] = { "SOCK.PROTONAME", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, N_("protocol name") }, + [COL_SOCK_SHUTDOWN] = { "SOCK.SHUTDOWN", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("shutdown state of socket ([-r?][-w?])") }, [COL_SOCK_STATE] = { "SOCK.STATE", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, - N_("State of socket") }, + N_("state of socket") }, [COL_SOCK_TYPE] = { "SOCK.TYPE", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, - N_("Type of socket") }, + N_("type of socket") }, [COL_SOURCE] = { "SOURCE", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, N_("file system, partition, or device containing file") }, @@ -288,6 +352,18 @@ static const struct colinfo infos[] = { [COL_TID] = { "TID", 5, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, N_("thread ID of the process opening the file") }, + [COL_TIMERFD_CLOCKID] = { "TIMERFD.CLOCKID", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("clockid") }, + [COL_TIMERFD_INTERVAL] = { "TIMERFD.INTERVAL", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_FLOAT, + N_("interval") }, + [COL_TIMERFD_REMAINING]= { "TIMERFD.REMAINING", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_FLOAT, + N_("remaining time") }, + [COL_TUN_IFACE] = { "TUN.IFACE", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("network interface behind the tun device") }, [COL_TYPE] = { "TYPE", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, N_("file type (cooked)") }, @@ -324,6 +400,9 @@ static const struct colinfo infos[] = { [COL_USER] = { "USER", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, N_("user of the process") }, + [COL_XMODE] = { "XMODE", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("extended version of MDOE (rwxD[Ll]m)") }, }; static const int default_columns[] = { @@ -331,7 +410,7 @@ static const int default_columns[] = { COL_PID, COL_USER, COL_ASSOC, - COL_MODE, + COL_XMODE, COL_TYPE, COL_SOURCE, COL_MNT_ID, @@ -345,7 +424,7 @@ static const int default_threads_columns[] = { COL_TID, COL_USER, COL_ASSOC, - COL_MODE, + COL_XMODE, COL_TYPE, COL_SOURCE, COL_MNT_ID, @@ -432,6 +511,12 @@ static const struct counter_spec default_counter_specs[] = { } }; +/* "userdata" used by callback for libsmartcols filter */ +struct filler_data { + struct proc *proc; + struct file *file; +}; + struct lsfd_control { struct libscols_table *tb; /* output */ struct list_head procs; /* list of all processes */ @@ -443,10 +528,11 @@ struct lsfd_control { threads : 1, show_main : 1, /* print main table */ show_summary : 1, /* print summary/counters */ - sockets_only : 1; /* display only SOCKETS */ + sockets_only : 1, /* display only SOCKETS */ + show_xmode : 1; /* XMODE column is enabled. */ - struct lsfd_filter *filter; - struct lsfd_counter **counters; /* NULL terminated array. */ + struct libscols_filter *filter; /* filter */ + struct libscols_filter **ct_filters; /* counters (NULL terminated array) */ }; static void *proc_tree; /* for tsearch/tfind */ @@ -476,13 +562,7 @@ static int column_name_to_id(const char *name, size_t namesz) return i; } warnx(_("unknown column: %s"), name); - - return LSFD_FILTER_UNKNOWN_COL_ID; -} - -static int column_name_to_id_cb(const char *name, void *data __attribute__((__unused__))) -{ - return column_name_to_id(name, strlen(name)); + return -1; } static int get_column_id(int num) @@ -499,12 +579,13 @@ static const struct colinfo *get_column_info(int num) return &infos[ get_column_id(num) ]; } -static struct libscols_column *add_column(struct libscols_table *tb, const struct colinfo *col) +static struct libscols_column *add_column(struct libscols_table *tb, + const struct colinfo *col, int extra) { struct libscols_column *cl; int flags = col->flags; - cl = scols_table_new_column(tb, col->name, col->whint, flags); + cl = scols_table_new_column(tb, col->name, col->whint, flags | extra); if (cl) { scols_column_set_json_type(cl, col->json_type); if (col->flags & SCOLS_FL_WRAP) { @@ -519,7 +600,8 @@ static struct libscols_column *add_column(struct libscols_table *tb, const struc return cl; } -static struct libscols_column *add_column_by_id_cb(struct libscols_table *tb, int colid, void *data) +static struct libscols_column *add_column_by_id(struct lsfd_control *ctl, + int colid, int extra) { struct libscols_column *cl; @@ -528,15 +610,13 @@ static struct libscols_column *add_column_by_id_cb(struct libscols_table *tb, in assert(colid < LSFD_N_COLS); - cl = add_column(tb, infos + colid); + cl = add_column(ctl->tb, infos + colid, extra); if (!cl) err(EXIT_FAILURE, _("failed to allocate output column")); columns[ncolumns++] = colid; - if (colid == COL_TID) { - struct lsfd_control *ctl = data; + if (colid == COL_TID) ctl->threads = 1; - } return cl; } @@ -560,8 +640,8 @@ static void add_mnt_ns(ino_t id) nmax = (nspaces + 16) / 16 * 16; if (nmax <= nspaces + 1) { nmax += 16; - mnt_namespaces = xrealloc(mnt_namespaces, - sizeof(ino_t) * nmax); + mnt_namespaces = xreallocarray(mnt_namespaces, + nmax, sizeof(ino_t)); } mnt_namespaces[nspaces++] = id; } @@ -592,6 +672,9 @@ static const struct file_class *stat2class(struct stat *sb) if (is_nsfs_dev(dev)) return &nsfs_file_class; + if (is_mqueue_dev(dev)) + return &mqueue_file_class; + return &file_class; default: break; @@ -604,7 +687,10 @@ static struct file *new_file(struct proc *proc, const struct file_class *class) { struct file *file; + assert(class); file = xcalloc(1, class->size); + file->class = class; + file->proc = proc; INIT_LIST_HEAD(&file->files); @@ -631,11 +717,6 @@ static struct file *copy_file(struct file *old) static void file_set_path(struct file *file, struct stat *sb, const char *name, int association) { - const struct file_class *class = stat2class(sb); - - assert(class); - - file->class = class; file->association = association; file->name = xstrdup(name); file->stat = *sb; @@ -660,7 +741,7 @@ static void free_file(struct file *file) } -static struct proc *new_process(pid_t pid, struct proc *leader) +static struct proc *new_proc(pid_t pid, struct proc *leader) { struct proc *proc = xcalloc(1, sizeof(*proc)); @@ -670,6 +751,7 @@ static struct proc *new_process(pid_t pid, struct proc *leader) INIT_LIST_HEAD(&proc->files); INIT_LIST_HEAD(&proc->procs); + INIT_LIST_HEAD(&proc->eventpolls); proc->kthread = 0; return proc; @@ -737,11 +819,12 @@ static struct file *collect_file_symlink(struct path_cxt *pc, class = stat2class(&sb); if (sockets_only - /* A nsfs is not a socket but the nsfs can be used to - * collect information from other network namespaces. - * Besed on the information, various columns of sockets. + /* A nsfs file is not a socket but the nsfs file can + * be used as a entry point to collect information from + * other network namespaces. Besed on the information, + * various columns of sockets can be filled. */ - && (class != &sock_class)&& (class != &nsfs_file_class)) + && (class != &sock_class) && (class != &nsfs_file_class)) return NULL; f = new_file(proc, class); file_set_path(f, &sb, sym, assoc); @@ -809,7 +892,7 @@ static void parse_maps_line(struct path_cxt *pc, char *buf, struct proc *proc) "-%"SCNx64 /* end */ " %4[^ ]" /* mode */ " %"SCNx64 /* offset */ - " %lx:%lx" /* maj:min */ + " %lx:%lx" /* maj:min */ " %"SCNu64, /* inode */ &start, &end, modestr, &offset, @@ -841,9 +924,6 @@ static void parse_maps_line(struct path_cxt *pc, char *buf, struct proc *proc) */ goto try_map_files; f = new_file(proc, stat2class(&sb)); - if (!f) - return; - file_set_path(f, &sb, path, -assoc); } else { /* As used in tcpdump, AF_PACKET socket can be mmap'ed. */ @@ -857,9 +937,6 @@ static void parse_maps_line(struct path_cxt *pc, char *buf, struct proc *proc) if (ul_path_readlink(pc, sym, sizeof(sym), map_file) < 0) return; f = new_file(proc, stat2class(&sb)); - if (!f) - return; - file_set_path(f, &sb, sym, -assoc); } @@ -920,7 +997,7 @@ static void collect_execve_file(struct path_cxt *pc, struct proc *proc, static void collect_fs_files(struct path_cxt *pc, struct proc *proc, bool sockets_only) { - enum association assocs[] = { ASSOC_EXE, ASSOC_CWD, ASSOC_ROOT }; + enum association assocs[] = { ASSOC_CWD, ASSOC_ROOT }; const char *names[] = { [ASSOC_CWD] = "cwd", [ASSOC_ROOT] = "root", @@ -929,12 +1006,26 @@ static void collect_fs_files(struct path_cxt *pc, struct proc *proc, sockets_only); } -static void collect_namespace_files(struct path_cxt *pc, struct proc *proc) +static void collect_namespace_files_tophalf(struct path_cxt *pc, struct proc *proc) { enum association assocs[] = { ASSOC_NS_CGROUP, ASSOC_NS_IPC, ASSOC_NS_MNT, + }; + const char *names[] = { + [ASSOC_NS_CGROUP] = "ns/cgroup", + [ASSOC_NS_IPC] = "ns/ipc", + [ASSOC_NS_MNT] = "ns/mnt", + }; + collect_outofbox_files(pc, proc, assocs, names, ARRAY_SIZE(assocs), + /* Namespace information is alwasys needed. */ + false); +} + +static void collect_namespace_files_bottomhalf(struct path_cxt *pc, struct proc *proc) +{ + enum association assocs[] = { ASSOC_NS_NET, ASSOC_NS_PID, ASSOC_NS_PID4C, @@ -944,9 +1035,6 @@ static void collect_namespace_files(struct path_cxt *pc, struct proc *proc) ASSOC_NS_UTS, }; const char *names[] = { - [ASSOC_NS_CGROUP] = "ns/cgroup", - [ASSOC_NS_IPC] = "ns/ipc", - [ASSOC_NS_MNT] = "ns/mnt", [ASSOC_NS_NET] = "ns/net", [ASSOC_NS_PID] = "ns/pid", [ASSOC_NS_PID4C] = "ns/pid_for_children", @@ -977,6 +1065,14 @@ static void free_nodev(struct nodev *nodev) free(nodev); } +void add_nodev(unsigned long minor, const char *filesystem) +{ + struct nodev *nodev = new_nodev(minor, filesystem); + unsigned long slot = nodev->minor % NODEV_TABLE_SIZE; + + list_add_tail(&nodev->nodevs, &nodev_table.tables[slot]); +} + static void initialize_nodevs(void) { int i; @@ -1008,7 +1104,7 @@ const char *get_nodev_filesystem(unsigned long minor) return NULL; } -static void add_nodevs(FILE *mnt) +static void read_mountinfo(FILE *mnt) { /* This can be very long. A line in mountinfo can have more than 3 * paths. */ @@ -1017,15 +1113,12 @@ static void add_nodevs(FILE *mnt) while (fgets(line, sizeof(line), mnt)) { unsigned long major, minor; char filesystem[256]; - struct nodev *nodev; - int slot; - /* 23 61 0:22 / /sys rw,nosuid,nodev,noexec,relatime shared:2 - sysfs sysfs rw,seclabel */ - if(sscanf(line, "%*d %*d %lu:%lu %*s %*s %*s %*[^-] - %s %*[^\n]", + if(sscanf(line, "%*d %*d %lu:%lu %*s %*s %*s %*[^-] - %255s %*[^\n]", &major, &minor, filesystem) != 3) /* 1600 1458 0:55 / / rw,nodev,relatime - overlay overlay rw,context="s... */ - if (sscanf(line, "%*d %*d %lu:%lu %*s %*s %*s - %s %*[^\n]", + if (sscanf(line, "%*d %*d %lu:%lu %*s %*s %*s - %255s %*[^\n]", &major, &minor, filesystem) != 3) continue; @@ -1034,10 +1127,7 @@ static void add_nodevs(FILE *mnt) if (get_nodev_filesystem(minor)) continue; - nodev = new_nodev(minor, filesystem); - slot = minor % NODEV_TABLE_SIZE; - - list_add_tail(&nodev->nodevs, &nodev_table.tables[slot]); + add_nodev(minor, filesystem); } } @@ -1060,6 +1150,15 @@ static void finalize_ipc_table(void) list_free(&ipc_table.tables[i], struct ipc, ipcs, free_ipc); } +struct ipc *new_ipc(const struct ipc_class *class) +{ + struct ipc *ipc = xcalloc(1, class->size); + ipc->class = class; + INIT_LIST_HEAD(&ipc->endpoints); + INIT_LIST_HEAD(&ipc->ipcs); + return ipc; +} + struct ipc *get_ipc(struct file *file) { int slot; @@ -1090,6 +1189,18 @@ void add_ipc(struct ipc *ipc, unsigned int hash) list_add(&ipc->ipcs, &ipc_table.tables[slot]); } +void init_endpoint(struct ipc_endpoint *endpoint) +{ + INIT_LIST_HEAD(&endpoint->endpoints); +} + +void add_endpoint(struct ipc_endpoint *endpoint, struct ipc *ipc) +{ + endpoint->ipc = ipc; + list_add(&endpoint->endpoints, &ipc->endpoints); +} + + static void fill_column(struct proc *proc, struct file *file, struct libscols_line *ln, @@ -1107,6 +1218,18 @@ static void fill_column(struct proc *proc, } } +static int filter_filler_cb( + struct libscols_filter *fltr __attribute__((__unused__)), + struct libscols_line *ln, + size_t colnum, + void *userdata) +{ + struct filler_data *fid = (struct filler_data *) userdata; + + fill_column(fid->proc, fid->file, ln, get_column_id(colnum), colnum); + return 0; +} + static void convert_file(struct proc *proc, struct file *file, struct libscols_line *ln) @@ -1114,8 +1237,11 @@ static void convert_file(struct proc *proc, { size_t i; - for (i = 0; i < ncolumns; i++) + for (i = 0; i < ncolumns; i++) { + if (scols_line_is_filled(ln, i)) + continue; fill_column(proc, file, ln, get_column_id(i), i); + } } static void convert(struct list_head *procs, struct lsfd_control *ctl) @@ -1129,23 +1255,34 @@ static void convert(struct list_head *procs, struct lsfd_control *ctl) list_for_each (f, &proc->files) { struct file *file = list_entry(f, struct file, files); struct libscols_line *ln = scols_table_new_line(ctl->tb, NULL); - struct lsfd_counter **counter = NULL; + struct libscols_filter **ct_fltr = NULL; if (!ln) err(EXIT_FAILURE, _("failed to allocate output line")); + if (ctl->filter) { + int status = 0; + struct filler_data fid = { + .proc = proc, + .file = file + }; + + scols_filter_set_filler_cb(ctl->filter, + filter_filler_cb, (void *) &fid); + if (scols_line_apply_filter(ln, ctl->filter, &status)) + err(EXIT_FAILURE, _("failed to apply filter")); + if (status == 0) { + scols_table_remove_line(ctl->tb, ln); + continue; + } + } convert_file(proc, file, ln); - if (!lsfd_filter_apply(ctl->filter, ln)) { - scols_table_remove_line(ctl->tb, ln); - continue; - } - - if (!ctl->counters) + if (!ctl->ct_filters) continue; - for (counter = ctl->counters; *counter; counter++) - lsfd_counter_accumulate(*counter, ln); + for (ct_fltr = ctl->ct_filters; *ct_fltr; ct_fltr++) + scols_line_apply_filter(ln, *ct_fltr, NULL); } } } @@ -1161,12 +1298,13 @@ static void delete(struct list_head *procs, struct lsfd_control *ctl) list_free(procs, struct proc, procs, free_proc); scols_unref_table(ctl->tb); - lsfd_filter_free(ctl->filter); - if (ctl->counters) { - struct lsfd_counter **counter; - for (counter = ctl->counters; *counter; counter++) - lsfd_counter_free(*counter); - free(ctl->counters); + scols_unref_filter(ctl->filter); + + if (ctl->ct_filters) { + struct libscols_filter **ct_fltr; + for (ct_fltr = ctl->ct_filters; *ct_fltr; ct_fltr++) + scols_unref_filter(*ct_fltr); + free(ctl->ct_filters); } } @@ -1365,6 +1503,169 @@ unsigned long add_name(struct name_manager *nm, const char *name) return e->id; } +static void walk_threads(struct lsfd_control *ctl, struct path_cxt *pc, + pid_t pid, struct proc *proc, + void (*cb)(struct lsfd_control *, struct path_cxt *, + pid_t, struct proc *)) +{ + DIR *sub = NULL; + pid_t tid = 0; + + while (procfs_process_next_tid(pc, &sub, &tid) == 0) { + if (tid == pid) + continue; + (*cb)(ctl, pc, tid, proc); + } +} + +static int pollfdcmp(const void *a, const void *b) +{ + const struct pollfd *apfd = a, *bpfd = b; + + return apfd->fd - bpfd->fd; +} + +static void mark_poll_fds_as_multiplexed(char *buf, + pid_t pid, struct proc *proc) +{ + long fds; + long nfds; + + struct iovec local; + struct iovec remote; + ssize_t n; + + struct list_head *f; + + if (sscanf(buf, "%lx %lx", &fds, &nfds) != 2) + return; + + if (nfds == 0) + return; + + local.iov_len = sizeof(struct pollfd) * nfds; + local.iov_base = xmalloc(local.iov_len); + remote.iov_len = local.iov_len; + remote.iov_base = (void *)fds; + + n = process_vm_readv(pid, &local, 1, &remote, 1, 0); + if (n < 0 || ((size_t)n) != local.iov_len) + goto out; + + qsort(local.iov_base, nfds, sizeof(struct pollfd), pollfdcmp); + + list_for_each (f, &proc->files) { + struct file *file = list_entry(f, struct file, files); + if (is_opened_file(file) && !file->multiplexed) { + int fd = file->association; + if (bsearch(&(struct pollfd){.fd = fd,}, local.iov_base, + nfds, sizeof(struct pollfd), pollfdcmp)) + file->multiplexed = 1; + } + } + + out: + free(local.iov_base); +} + +static void mark_select_fds_as_multiplexed(char *buf, + pid_t pid, struct proc *proc) +{ + long nfds; + long fds[3]; + + struct iovec local[3]; + fd_set local_set[3]; + struct iovec remote[3]; + ssize_t n; + ssize_t expected_n = 0; + + struct list_head *f; + + if (sscanf(buf, "%lx %lx %lx %lx", &nfds, fds + 0, fds + 1, fds + 2) != 4) + return; + + if (nfds == 0) + return; + + for (int i = 0; i < 3; i++) { + /* If the remote address for the fd_set is 0x0, no set is tehre. */ + remote[i].iov_len = local[i].iov_len = fds[i]? sizeof(local_set[i]): 0; + expected_n += (ssize_t)local[i].iov_len; + local[i].iov_base = local_set + i; + remote[i].iov_base = (void *)(fds[i]); + } + + n = process_vm_readv(pid, local, 3, remote, 3, 0); + if (n < 0 || n != expected_n) + return; + + list_for_each (f, &proc->files) { + struct file *file = list_entry(f, struct file, files); + if (is_opened_file(file) && !file->multiplexed) { + int fd = file->association; + if (nfds <= fd) + continue; + if ((fds[0] && FD_ISSET(fd, (fd_set *)local[0].iov_base)) + || (fds[1] && FD_ISSET(fd, (fd_set *)local[1].iov_base)) + || (fds[2] && FD_ISSET(fd, (fd_set *)local[2].iov_base))) + file->multiplexed = 1; + } + } +} + +static void parse_proc_syscall(struct lsfd_control *ctl __attribute__((__unused__)), + struct path_cxt *pc, pid_t pid, struct proc *proc) +{ + char buf[BUFSIZ]; + char *ptr = NULL; + long scn; + + if (procfs_process_get_syscall(pc, buf, sizeof(buf)) <= 0) + return; + + errno = 0; + scn = strtol(buf, &ptr, 10); + if (errno) + return; + if (scn < 0) + return; + + switch (scn) { +#ifdef SYS_poll + case SYS_poll: + mark_poll_fds_as_multiplexed(ptr, pid, proc); + break; +#endif +#ifdef SYS_ppoll + case SYS_ppoll: + mark_poll_fds_as_multiplexed(ptr, pid, proc); + break; +#endif +#ifdef SYS_ppoll_time64 + case SYS_ppoll_time64: + mark_poll_fds_as_multiplexed(ptr, pid, proc); + break; +#endif + +#ifdef SYS_select + case SYS_select: + mark_select_fds_as_multiplexed(ptr, pid, proc); + break; +#endif +#ifdef SYS_pselect6 + case SYS_pselect6: + mark_select_fds_as_multiplexed(ptr, pid, proc); + break; +#endif +#ifdef SYS_pselect6_time64 + case SYS_pselect6_time64: + mark_select_fds_as_multiplexed(ptr, pid, proc); + break; +#endif + } +} + static void read_process(struct lsfd_control *ctl, struct path_cxt *pc, pid_t pid, struct proc *leader) { @@ -1374,7 +1675,7 @@ static void read_process(struct lsfd_control *ctl, struct path_cxt *pc, if (procfs_process_init_path(pc, pid) != 0) return; - proc = new_process(pid, leader); + proc = new_proc(pid, leader); proc->command = procfs_process_get_cmdname(pc, buf, sizeof(buf)) > 0 ? xstrdup(buf) : xstrdup(_("(unknown)")); procfs_process_get_uid(pc, &proc->uid); @@ -1397,6 +1698,10 @@ static void read_process(struct lsfd_control *ctl, struct path_cxt *pc, proc->kthread = !!(flags & PF_KTHREAD); free(pat); } + if (proc->kthread && !ctl->threads) { + free_proc(proc); + goto out; + } collect_execve_file(pc, proc, ctl->sockets_only); @@ -1404,20 +1709,43 @@ static void read_process(struct lsfd_control *ctl, struct path_cxt *pc, || kcmp(proc->leader->pid, proc->pid, KCMP_FS, 0, 0) != 0) collect_fs_files(pc, proc, ctl->sockets_only); + /* Reading /proc/$pid/mountinfo is expensive. + * mnt_namespaces is a table for avoiding reading mountinfo files + * for an identical mnt namespace. + * + * After reading a mountinfo file for a mnt namespace, we store $mnt_id + * identifying the mnt namespace to mnt_namespaces. + * + * Before reading a mountinfo, we look up the mnt_namespaces with $mnt_id + * as a key. If we find the key, we can skip the reading. + * + * To utilize mnt_namespaces, we need $mnt_id. + * So we read /proc/$pid/ns/mnt here. However, we should not read + * /proc/$pid/ns/net here. When reading /proc/$pid/ns/net, we need + * the information about backing device of "nsfs" file system. + * The information is available in a mountinfo file. + */ + + /* 1/3. read /proc/$pid/ns/mnt */ + if (proc->ns_mnt == 0) + collect_namespace_files_tophalf(pc, proc); + + /* 2/3. read /proc/$pid/mountinfo */ if (proc->ns_mnt == 0 || !has_mnt_ns(proc->ns_mnt)) { FILE *mnt = ul_path_fopen(pc, "r", "mountinfo"); if (mnt) { - add_nodevs(mnt); + read_mountinfo(mnt); if (proc->ns_mnt) add_mnt_ns(proc->ns_mnt); fclose(mnt); } } - collect_namespace_files(pc, proc); + /* 3/3. read /proc/$pid/ns/{the other than mnt} */ + collect_namespace_files_bottomhalf(pc, proc); /* If kcmp is not available, - * there is no way to no whether threads share resources. + * there is no way to know whether threads share resources. * In such cases, we must pay the costs: call collect_mem_files() * and collect_fd_files(). */ @@ -1434,22 +1762,20 @@ static void read_process(struct lsfd_control *ctl, struct path_cxt *pc, if (tsearch(proc, &proc_tree, proc_tree_compare) == NULL) errx(EXIT_FAILURE, _("failed to allocate memory")); + if (ctl->show_xmode) + parse_proc_syscall(ctl, pc, pid, proc); + /* The tasks collecting overwrites @pc by /proc/<task-pid>/. Keep it as * the last path based operation in read_process() */ - if (ctl->threads && leader == NULL) { - DIR *sub = NULL; - pid_t tid = 0; - - while (procfs_process_next_tid(pc, &sub, &tid) == 0) { - if (tid == pid) - continue; - read_process(ctl, pc, tid, proc); - } - } + if (ctl->threads && leader == NULL) + walk_threads(ctl, pc, pid, proc, read_process); + else if (ctl->show_xmode) + walk_threads(ctl, pc, pid, proc, parse_proc_syscall); + out: /* Let's be careful with number of open files */ - ul_path_close_dirfd(pc); + ul_path_close_dirfd(pc); } static void parse_pids(const char *str, pid_t **pids, int *count) @@ -1470,7 +1796,7 @@ static void parse_pids(const char *str, pid_t **pids, int *count) errx(EXIT_FAILURE, _("out of range value for pid specification: %ld"), v); (*count)++; - *pids = xrealloc(*pids, (*count) * sizeof(**pids)); + *pids = xreallocarray(*pids, *count, sizeof(**pids)); (*pids)[*count - 1] = (pid_t)v; while (next && *next != '\0' @@ -1530,44 +1856,66 @@ static void collect_processes(struct lsfd_control *ctl, const pid_t pids[], int ul_unref_path(pc); } +static void __attribute__((__noreturn__)) list_colunms(const char *table_name, + FILE *out, + int raw, + int json) +{ + struct libscols_table *col_tb = xcolumn_list_table_new(table_name, out, raw, json); + + for (size_t i = 0; i < ARRAY_SIZE(infos); i++) + xcolumn_list_table_append_line(col_tb, infos[i].name, + infos[i].json_type, "<boolean>", + _(infos[i].help)); + + scols_print_table(col_tb); + scols_unref_table(col_tb); + + exit(EXIT_SUCCESS); +} + +static void print_columns(FILE *out, const char *prefix, const int cols[], size_t n_cols) +{ + fprintf(out, "%15s: ", prefix); + for (size_t i = 0; i < n_cols; i++) { + if (i) + fputc(',', out); + fputs(infos[cols[i]].name, out); + } + fputc('\n', out); +} + static void __attribute__((__noreturn__)) usage(void) { FILE *out = stdout; - size_t i; fputs(USAGE_HEADER, out); fprintf(out, _(" %s [options]\n"), program_invocation_short_name); fputs(USAGE_OPTIONS, out); - fputs(_(" -l, --threads list in threads level\n"), out); - fputs(_(" -J, --json use JSON output format\n"), out); - fputs(_(" -n, --noheadings don't print headings\n"), out); - fputs(_(" -o, --output <list> output columns\n"), out); - fputs(_(" -r, --raw use raw output format\n"), out); - fputs(_(" -u, --notruncate don't truncate text in columns\n"), out); - fputs(_(" -p, --pid <pid(s)> collect information only specified processes\n"), out); - fputs(_(" -i[4|6], --inet[=4|6] list only IPv4 and/or IPv6 sockets\n"), out); - fputs(_(" -Q, --filter <expr> apply display filter\n"), out); - fputs(_(" --debug-filter dump the internal data structure of filter and exit\n"), out); - fputs(_(" -C, --counter <name>:<expr>\n" - " define custom counter for --summary output\n"), out); - fputs(_(" --dump-counters dump counter definitions\n"), out); - fputs(_(" --summary[=<when>] print summary information (only, append, or never)\n"), out); + fputs(_(" -l, --threads list in threads level\n"), out); + fputs(_(" -J, --json use JSON output format\n"), out); + fputs(_(" -n, --noheadings don't print headings\n"), out); + fputs(_(" -o, --output <list> output columns (see --list-columns)\n"), out); + fputs(_(" -r, --raw use raw output format\n"), out); + fputs(_(" -u, --notruncate don't truncate text in columns\n"), out); + fputs(_(" -p, --pid <pid(s)> collect information only specified processes\n"), out); + fputs(_(" -i[4|6], --inet[=4|=6] list only IPv4 and/or IPv6 sockets\n"), out); + fputs(_(" -Q, --filter <expr> apply display filter\n"), out); + fputs(_(" --debug-filter dump the internal data structure of filter and exit\n"), out); + fputs(_(" -C, --counter <name>:<expr> define custom counter for --summary output\n"), out); + fputs(_(" --dump-counters dump counter definitions\n"), out); + fputs(_(" --summary[=<when>] print summary information (only, append, or never)\n"), out); fputs(USAGE_SEPARATOR, out); - printf(USAGE_HELP_OPTIONS(30)); - - fprintf(out, USAGE_COLUMNS); + fputs(_(" -H, --list-columns list the available columns\n"), out); + fprintf(out, USAGE_HELP_OPTIONS(30)); - for (i = 0; i < ARRAY_SIZE(infos); i++) - fprintf(out, " %16s %-10s%s\n", infos[i].name, - infos[i].json_type == SCOLS_JSON_STRING? "<string>": - infos[i].json_type == SCOLS_JSON_ARRAY_STRING? "<string>": - infos[i].json_type == SCOLS_JSON_NUMBER? "<number>": - "<boolean>", - _(infos[i].help)); + fputs(USAGE_DEFAULT_COLUMNS, out); + print_columns(out, _("Default"), default_columns, ARRAY_SIZE(default_columns)); + print_columns(out, _("With --threads"), default_threads_columns, ARRAY_SIZE(default_threads_columns)); - printf(USAGE_MAN_TAIL("lsfd(1)")); + fprintf(out, USAGE_MAN_TAIL("lsfd(1)")); exit(EXIT_SUCCESS); } @@ -1595,24 +1943,49 @@ static void append_filter_expr(char **a, const char *b, bool and) free(tmp); } -static struct lsfd_filter *new_filter(const char *expr, bool debug, const char *err_prefix, struct lsfd_control *ctl) +static struct libscols_filter *new_filter(const char *expr, bool debug, struct lsfd_control *ctl) { - struct lsfd_filter *filter; - const char *errmsg; + struct libscols_filter *f; + struct libscols_iter *itr; + int nerrs = 0; + const char *name = NULL; - filter = lsfd_filter_new(expr, ctl->tb, - LSFD_N_COLS, - column_name_to_id_cb, - add_column_by_id_cb, ctl); - errmsg = lsfd_filter_get_errmsg(filter); - if (errmsg) - errx(EXIT_FAILURE, "%s%s", err_prefix, errmsg); - if (debug) { - lsfd_filter_dump(filter, stdout); - exit(EXIT_SUCCESS); + f = scols_new_filter(NULL); + if (!f) + err(EXIT_FAILURE, _("failed to allocate filter")); + if (expr && scols_filter_parse_string(f, expr) != 0) + errx(EXIT_FAILURE, _("failed to parse \"%s\": %s"), expr, + scols_filter_get_errmsg(f)); + + itr = scols_new_iter(SCOLS_ITER_FORWARD); + if (!itr) + err(EXIT_FAILURE, _("failed to allocate iterator")); + + while (scols_filter_next_holder(f, itr, &name, 0) == 0) { + struct libscols_column *col = scols_table_get_column_by_name(ctl->tb, name); + + if (!col) { + int id = column_name_to_id(name, strlen(name)); + if (id >= 0) + col = add_column_by_id(ctl, id, SCOLS_FL_HIDDEN); + if (!col) { + nerrs++; /* report all unknown columns */ + continue; + } + } + scols_filter_assign_column(f, itr, name, col); } - return filter; + scols_free_iter(itr); + + if (debug) + scols_dump_filter(f, stdout); + if (nerrs) + exit(EXIT_FAILURE); + if (debug) + exit(EXIT_SUCCESS); + + return f; } static struct counter_spec *new_counter_spec(const char *spec_str) @@ -1660,47 +2033,54 @@ static void free_counter_spec(struct counter_spec *counter_spec) free(counter_spec); } -static struct lsfd_counter *new_counter(const struct counter_spec *spec, struct lsfd_control *ctl) +static struct libscols_filter *new_counter(const struct counter_spec *spec, struct lsfd_control *ctl) { - struct lsfd_filter *filter; + struct libscols_filter *f; + struct libscols_counter *ct; + + f = new_filter(spec->expr, false, ctl); + + ct = scols_filter_new_counter(f); + if (!ct) + err(EXIT_FAILURE, _("failed to allocate counter")); + + scols_counter_set_name(ct, spec->name); + scols_counter_set_func(ct, SCOLS_COUNTER_COUNT); - filter = new_filter(spec->expr, false, - _("failed in making filter for a counter: "), - ctl); - return lsfd_counter_new(spec->name, filter); + return f; } -static struct lsfd_counter **new_counters(struct list_head *specs, struct lsfd_control *ctl) +static struct libscols_filter **new_counters(struct list_head *specs, struct lsfd_control *ctl) { - struct lsfd_counter **counters; + struct libscols_filter **ct_filters; size_t len = list_count_entries(specs); size_t i = 0; struct list_head *s; - counters = xcalloc(len + 1, sizeof(struct lsfd_counter *)); + ct_filters = xcalloc(len + 1, sizeof(struct libscols_filter *)); list_for_each(s, specs) { struct counter_spec *spec = list_entry(s, struct counter_spec, specs); - counters[i++] = new_counter(spec, ctl); + ct_filters[i++] = new_counter(spec, ctl); } - assert(counters[len] == NULL); + assert(ct_filters[len] == NULL); - return counters; + return ct_filters; } -static struct lsfd_counter **new_default_counters(struct lsfd_control *ctl) +static struct libscols_filter **new_default_counters(struct lsfd_control *ctl) { - struct lsfd_counter **counters; + struct libscols_filter **ct_filters; size_t len = ARRAY_SIZE(default_counter_specs); size_t i; - counters = xcalloc(len + 1, sizeof(struct lsfd_counter *)); + ct_filters = xcalloc(len + 1, sizeof(struct libscols_filter *)); for (i = 0; i < len; i++) { const struct counter_spec *spec = default_counter_specs + i; - counters[i] = new_counter(spec, ctl); + ct_filters[i] = new_counter(spec, ctl); } - assert(counters[len] == NULL); + assert(ct_filters[len] == NULL); - return counters; + return ct_filters; } static void dump_default_counter_specs(void) @@ -1758,28 +2138,35 @@ static struct libscols_table *new_summary_table(struct lsfd_control *ctl) return tb; } -static void fill_summary_line(struct libscols_line *ln, struct lsfd_counter *counter) +static void emit_summary(struct lsfd_control *ctl) { - char *str = NULL; + struct libscols_iter *itr; + struct libscols_filter **ct_fltr; + struct libscols_table *tb = new_summary_table(ctl); - xasprintf(&str, "%llu", (unsigned long long)lsfd_counter_value(counter)); - if (!str) - err(EXIT_FAILURE, _("failed to add summary data")); - if (scols_line_refer_data(ln, 0, str)) - err(EXIT_FAILURE, _("failed to add summary data")); + itr = scols_new_iter(SCOLS_ITER_FORWARD); - if (scols_line_set_data(ln, 1, lsfd_counter_name(counter))) - err(EXIT_FAILURE, _("failed to add summary data")); -} + for (ct_fltr = ctl->ct_filters; *ct_fltr; ct_fltr++) { + struct libscols_counter *ct = NULL; -static void emit_summary(struct lsfd_control *ctl, struct lsfd_counter **counter) -{ - struct libscols_table *tb = new_summary_table(ctl); + scols_reset_iter(itr, SCOLS_ITER_FORWARD); + while (scols_filter_next_counter(*ct_fltr, itr, &ct) == 0) { + char *str = NULL; + struct libscols_line *ln; + + ln = scols_table_new_line(tb, NULL); + if (!ln) + err(EXIT_FAILURE, _("failed to allocate summary line")); - for (; *counter; counter++) { - struct libscols_line *ln = scols_table_new_line(tb, NULL); - fill_summary_line(ln, *counter); + xasprintf(&str, "%llu", scols_counter_get_result(ct)); + if (scols_line_refer_data(ln, 0, str)) + err(EXIT_FAILURE, _("failed to add summary data")); + if (scols_line_set_data(ln, 1, scols_counter_get_name(ct))) + err(EXIT_FAILURE, _("failed to add summary data")); + } } + + scols_free_iter(itr); scols_print_table(tb); scols_unref_table(tb); @@ -1801,6 +2188,23 @@ static void attach_xinfos(struct list_head *procs) } } +static void set_multiplexed_flags(struct list_head *procs) +{ + struct list_head *p; + list_for_each (p, procs) { + struct proc *proc = list_entry(p, struct proc, procs); + struct list_head *f; + list_for_each (f, &proc->files) { + struct file *file = list_entry(f, struct file, files); + if (is_opened_file(file) && !file->multiplexed) { + int fd = file->association; + if (is_multiplexed_by_eventpoll(fd, &proc->eventpolls)) + file->multiplexed = 1; + } + } + } +} + /* Filter expressions for implementing -i option. * * To list up the protocol names, use the following command line @@ -1826,7 +2230,7 @@ static const char *inet46_subexpr = INET_SUBEXP_BEGIN int main(int argc, char *argv[]) { - int c; + int c, collist = 0; size_t i; char *outarg = NULL; char *filter_expr = NULL; @@ -1863,6 +2267,7 @@ int main(int argc, char *argv[]) { "summary", optional_argument, NULL, OPT_SUMMARY }, { "counter", required_argument, NULL, 'C' }, { "dump-counters",no_argument, NULL, OPT_DUMP_COUNTERS }, + { "list-columns",no_argument, NULL, 'H' }, { NULL, 0, NULL, 0 }, }; @@ -1871,7 +2276,7 @@ int main(int argc, char *argv[]) textdomain(PACKAGE); close_stdout_atexit(); - while ((c = getopt_long(argc, argv, "no:JrVhluQ:p:i::C:s", longopts, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "no:JrVhluQ:p:i::C:sH", longopts, NULL)) != -1) { switch (c) { case 'n': ctl.noheadings = 1; @@ -1943,10 +2348,17 @@ int main(int argc, char *argv[]) print_version(EXIT_SUCCESS); case 'h': usage(); + case 'H': + collist = 1; + break; default: errtryhelp(EXIT_FAILURE); } } + + if (collist) + list_colunms("lsfd-columns", stdout, ctl.raw, ctl.json); /* print and exit */ + if (argv[optind]) errtryhelp(EXIT_FAILURE); @@ -1982,7 +2394,7 @@ int main(int argc, char *argv[]) /* create output columns */ for (i = 0; i < ncolumns; i++) { const struct colinfo *col = get_column_info(i); - struct libscols_column *cl = add_column(ctl.tb, col); + struct libscols_column *cl = add_column(ctl.tb, col, 0); if (!cl) err(EXIT_FAILURE, _("failed to allocate output column")); @@ -1994,9 +2406,9 @@ int main(int argc, char *argv[]) } } - /* make fitler */ + /* make filter */ if (filter_expr) { - ctl.filter = new_filter(filter_expr, debug_filter, "", &ctl); + ctl.filter = new_filter(filter_expr, debug_filter, &ctl); free(filter_expr); } @@ -2011,9 +2423,9 @@ int main(int argc, char *argv[]) /* make counters */ if (ctl.show_summary) { if (list_empty(&counter_specs)) - ctl.counters = new_default_counters(&ctl); + ctl.ct_filters = new_default_counters(&ctl); else { - ctl.counters = new_counters(&counter_specs, &ctl); + ctl.ct_filters = new_counters(&counter_specs, &ctl); list_free(&counter_specs, struct counter_spec, specs, free_counter_spec); } @@ -2022,16 +2434,26 @@ int main(int argc, char *argv[]) if (n_pids > 0) sort_pids(pids, n_pids); - /* collect data */ + if (scols_table_get_column_by_name(ctl.tb, "XMODE")) + ctl.show_xmode = 1; + + /* collect data + * + * The call initialize_ipc_table() must come before + * initialize_classes. + */ initialize_nodevs(); + initialize_ipc_table(); initialize_classes(); initialize_devdrvs(); - initialize_ipc_table(); collect_processes(&ctl, pids, n_pids); free(pids); attach_xinfos(&ctl.procs); + if (ctl.show_xmode) + set_multiplexed_flags(&ctl.procs); + convert(&ctl.procs, &ctl); @@ -2039,15 +2461,15 @@ int main(int argc, char *argv[]) if (ctl.show_main) emit(&ctl); - if (ctl.show_summary && ctl.counters) - emit_summary(&ctl, ctl.counters); + if (ctl.show_summary && ctl.ct_filters) + emit_summary(&ctl); /* cleanup */ delete(&ctl.procs, &ctl); - finalize_ipc_table(); finalize_devdrvs(); finalize_classes(); + finalize_ipc_table(); finalize_nodevs(); return 0; diff --git a/misc-utils/lsfd.h b/misc-utils/lsfd.h index ea1c342..1859dc7 100644 --- a/misc-utils/lsfd.h +++ b/misc-utils/lsfd.h @@ -29,9 +29,12 @@ #include <dirent.h> #include <inttypes.h> +#include "libsmartcols.h" #include "list.h" +#include "nls.h" #include "path.h" #include "strutils.h" +#include "xalloc.h" /* * column IDs @@ -40,19 +43,31 @@ enum { COL_AINODECLASS, COL_ASSOC, COL_BLKDRV, + COL_BPF_MAP_ID, + COL_BPF_MAP_TYPE, + COL_BPF_MAP_TYPE_RAW, + COL_BPF_NAME, + COL_BPF_PROG_ID, + COL_BPF_PROG_TYPE, + COL_BPF_PROG_TYPE_RAW, COL_CHRDRV, COL_COMMAND, COL_DELETED, COL_DEV, COL_DEVTYPE, COL_ENDPOINTS, + COL_EVENTFD_ID, + COL_EVENTPOLL_TFDS, COL_FD, COL_FLAGS, - COL_INODE, + COL_FUID, /* file */ COL_INET_LADDR, COL_INET_RADDR, COL_INET6_LADDR, COL_INET6_RADDR, + COL_INODE, + COL_INOTIFY_INODES, + COL_INOTIFY_INODES_RAW, COL_KNAME, COL_KTHREAD, COL_MAJMIN, @@ -67,6 +82,7 @@ enum { COL_NLINK, COL_NS_NAME, COL_NS_TYPE, + COL_OWNER, /* file */ COL_PACKET_IFACE, COL_PACKET_PROTOCOL, COL_PARTITION, @@ -76,12 +92,15 @@ enum { COL_PIDFD_PID, COL_PING_ID, COL_POS, + COL_PTMX_TTY_INDEX, COL_RAW_PROTOCOL, COL_RDEV, + COL_SIGNALFD_MASK, COL_SIZE, COL_SOCK_LISTENING, COL_SOCK_NETNS, COL_SOCK_PROTONAME, + COL_SOCK_SHUTDOWN, COL_SOCK_STATE, COL_SOCK_TYPE, COL_SOURCE, @@ -91,6 +110,10 @@ enum { COL_TCP_LPORT, COL_TCP_RPORT, COL_TID, + COL_TIMERFD_CLOCKID, + COL_TIMERFD_INTERVAL, + COL_TIMERFD_REMAINING, + COL_TUN_IFACE, COL_TYPE, COL_UDP_LADDR, COL_UDP_RADDR, @@ -103,8 +126,7 @@ enum { COL_UID, /* process */ COL_UNIX_PATH, COL_USER, /* process */ - COL_FUID, /* file */ - COL_OWNER, /* file */ + COL_XMODE, LSFD_N_COLS /* This must be at last. */ }; @@ -139,6 +161,7 @@ struct proc { struct list_head procs; struct list_head files; unsigned int kthread: 1; + struct list_head eventpolls; }; struct proc *get_proc(pid_t pid); @@ -161,6 +184,11 @@ struct file { unsigned int sys_flags; unsigned int mnt_id; + + struct { + uint8_t read:1, write:1; + } locked; + uint8_t multiplexed; }; #define is_opened_file(_f) ((_f)->association >= 0) @@ -185,7 +213,7 @@ struct file_class { }; extern const struct file_class file_class, cdev_class, bdev_class, sock_class, unkn_class, fifo_class, - nsfs_file_class; + nsfs_file_class, mqueue_file_class; /* * IPC @@ -202,14 +230,34 @@ struct ipc_endpoint { }; struct ipc_class { + size_t size; unsigned int (*get_hash)(struct file *file); bool (*is_suitable_ipc)(struct ipc *ipc, struct file *file); void (*free)(struct ipc *ipc); }; +struct ipc *new_ipc(const struct ipc_class *class); struct ipc *get_ipc(struct file *file); void add_ipc(struct ipc *ipc, unsigned int hash); +void init_endpoint(struct ipc_endpoint *endpoint); +void add_endpoint(struct ipc_endpoint *endpoint, struct ipc *ipc); +#define foreach_endpoint(E,ENDPOINT) list_for_each_backwardly(E, &((ENDPOINT).ipc->endpoints)) + +enum decode_source_bit { + DECODE_SOURCE_MAJMIN_BIT = 1 << 0, + DECODE_SOURCE_PARTITION_BIT = 1 << 1, + DECODE_SOURCE_FILESYS_BIT = 1 << 2, +}; +enum decode_source_level { + DECODE_SOURCE_MAJMIN = DECODE_SOURCE_MAJMIN_BIT, + DECODE_SOURCE_PARTITION = DECODE_SOURCE_PARTITION_BIT | DECODE_SOURCE_MAJMIN, + DECODE_SOURCE_FILESYS = DECODE_SOURCE_FILESYS_BIT | DECODE_SOURCE_PARTITION, + DECODE_SOURCE_FULL = DECODE_SOURCE_FILESYS, +}; + +void decode_source(char *buf, size_t bufsize, unsigned int dev_major, unsigned int dev_minor, + enum decode_source_level level); /* * Name managing */ @@ -225,18 +273,7 @@ const char *get_blkdrv(unsigned long major); const char *get_chrdrv(unsigned long major); const char *get_miscdev(unsigned long minor); const char *get_nodev_filesystem(unsigned long minor); - -static inline void xstrappend(char **a, const char *b) -{ - if (strappend(a, b) < 0) - err(XALLOC_EXIT_CODE, _("failed to allocate memory for string")); -} - -static inline void xstrputc(char **a, char c) -{ - char b[] = {c, '\0'}; - xstrappend(a, b); -} +void add_nodev(unsigned long minor, const char *filesystem); /* * Net namespace @@ -244,4 +281,15 @@ static inline void xstrputc(char **a, char c) void load_sock_xinfo(struct path_cxt *pc, const char *name, ino_t netns); bool is_nsfs_dev(dev_t dev); +/* + * POSIX Mqueue + */ +/* 0 is assumed as the major dev for DEV. */ +bool is_mqueue_dev(dev_t dev); + +/* + * Eventpoll + */ +bool is_multiplexed_by_eventpoll(int fd, struct list_head *eventpolls); + #endif /* UTIL_LINUX_LSFD_H */ diff --git a/misc-utils/lslocks.8 b/misc-utils/lslocks.8 index 255742d..3f593a2 100644 --- a/misc-utils/lslocks.8 +++ b/misc-utils/lslocks.8 @@ -2,12 +2,12 @@ .\" Title: lslocks .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-12-01 +.\" Date: 2024-03-20 .\" Manual: System Administration -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "LSLOCKS" "8" "2023-12-01" "util\-linux 2.39.3" "System Administration" +.TH "LSLOCKS" "8" "2024-03-20" "util\-linux 2.40" "System Administration" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 @@ -35,8 +35,6 @@ lslocks \- list local system locks .SH "DESCRIPTION" .sp \fBlslocks\fP lists information about all the currently held file locks in a Linux system. -.sp -Note that lslocks also lists OFD (Open File Description) locks, these locks are not associated with any process (PID is \-1). OFD locks are associated with the open file description on which they are acquired. This lock type is available since Linux 3.15, see \fBfcntl\fP(2) for more details. .SH "OPTIONS" .sp \fB\-b\fP, \fB\-\-bytes\fP @@ -50,6 +48,11 @@ examples: "1 KiB" and "1 MiB" are respectively exhibited as "1 K" and "1 M", then omitting on purpose the mention "iB", which is part of these abbreviations. .RE .sp +\fB\-H\fP, \fB\-\-list\-columns\fP +.RS 4 +List the available columns, use with \fB\-\-json\fP or \fB\-\-raw\fP to get output in machine\-readable format. +.RE +.sp \fB\-i\fP, \fB\-\-noinaccessible\fP .RS 4 Ignore lock files which are inaccessible for the current user. @@ -110,12 +113,12 @@ The command name of the process holding the lock. .sp PID .RS 4 -The process ID of the process which holds the lock or \-1 for OFDLCK. +The process ID of the process. .RE .sp TYPE .RS 4 -The type of lock; can be FLOCK (created with \fBflock\fP(2)), POSIX (created with \fBfcntl\fP(2) and \fBlockf\fP(3)) or OFDLCK (created with \fBfcntl\fP(2)). +The type of lock; can be LEASE (created with \fBfcntl\fP(2)), FLOCK (created with \fBflock\fP(2)), POSIX (created with \fBfcntl\fP(2) and \fBlockf\fP(3)) or OFDLCK (created with \fBfcntl\fP(2)). .RE .sp SIZE @@ -123,6 +126,16 @@ SIZE Size of the locked file. .RE .sp +INODE +.RS 4 +The inode number. +.RE +.sp +MAJ:MIN +.RS 4 +The major:minor device number. +.RE +.sp MODE .RS 4 The lock\(cqs access permissions (read, write). If the process is blocked and waiting for the lock, then the mode is postfixed with an \*(Aq*\*(Aq (asterisk). @@ -152,11 +165,25 @@ BLOCKER .RS 4 The PID of the process which blocks the lock. .RE +.sp +HOLDERS +.RS 4 +The holder(s) of the lock. The format of the holder is \fIPID\fP,\fICOMMAND\fP,\fIFD\fP. +If a lock is an open file description\-oriented lock, there can be more than one holder for the lock. +See the NOTES below. +.RE .SH "NOTES" .sp The \fBlslocks\fP command is meant to replace the \fBlslk\fP(8) command, originally written by \c .MTO "abe\(atpurdue.edu" "Victor A. Abell" "" and unmaintained since 2001. +.sp +"The process holding the lock" for leases, FLOCK locks, and +OFD locks is a fake\-concept. They are associated with the open file +description on which they are acquired. With \fBfork\fP(2) and/or +\fBcmsg\fP(3), multiple processes can share an open file description. So +the holder process of a lease (or a lock) is not uniquely determined. +\fBlslocks\fP shows the one of the holder processes in COMMAND and PID columns. .SH "AUTHORS" .sp .MTO "dave\(atgnu.org" "Davidlohr Bueso" "" diff --git a/misc-utils/lslocks.8.adoc b/misc-utils/lslocks.8.adoc index 21ad643..2084d96 100644 --- a/misc-utils/lslocks.8.adoc +++ b/misc-utils/lslocks.8.adoc @@ -29,13 +29,14 @@ lslocks - list local system locks *lslocks* lists information about all the currently held file locks in a Linux system. -Note that lslocks also lists OFD (Open File Description) locks, these locks are not associated with any process (PID is -1). OFD locks are associated with the open file description on which they are acquired. This lock type is available since Linux 3.15, see *fcntl*(2) for more details. - == OPTIONS *-b*, *--bytes*:: include::man-common/in-bytes.adoc[] +*-H*, *--list-columns*:: +List the available columns, use with *--json* or *--raw* to get output in machine-readable format. + *-i*, *--noinaccessible*:: Ignore lock files which are inaccessible for the current user. @@ -71,14 +72,20 @@ COMMAND:: The command name of the process holding the lock. PID:: -The process ID of the process which holds the lock or -1 for OFDLCK. +The process ID of the process. TYPE:: -The type of lock; can be FLOCK (created with *flock*(2)), POSIX (created with *fcntl*(2) and *lockf*(3)) or OFDLCK (created with *fcntl*(2)). +The type of lock; can be LEASE (created with *fcntl*(2)), FLOCK (created with *flock*(2)), POSIX (created with *fcntl*(2) and *lockf*(3)) or OFDLCK (created with *fcntl*(2)). SIZE:: Size of the locked file. +INODE:: +The inode number. + +MAJ:MIN:: +The major:minor device number. + MODE:: The lock's access permissions (read, write). If the process is blocked and waiting for the lock, then the mode is postfixed with an '*' (asterisk). @@ -97,10 +104,22 @@ Full path of the lock. If none is found, or there are no permissions to read the BLOCKER:: The PID of the process which blocks the lock. +HOLDERS:: +The holder(s) of the lock. The format of the holder is _PID_,_COMMAND_,_FD_. +If a lock is an open file description-oriented lock, there can be more than one holder for the lock. +See the NOTES below. + == NOTES The *lslocks* command is meant to replace the *lslk*(8) command, originally written by mailto:abe@purdue.edu[Victor A. Abell] and unmaintained since 2001. +"The process holding the lock" for leases, FLOCK locks, and +OFD locks is a fake-concept. They are associated with the open file +description on which they are acquired. With *fork*(2) and/or +*cmsg*(3), multiple processes can share an open file description. So +the holder process of a lease (or a lock) is not uniquely determined. +*lslocks* shows the one of the holder processes in COMMAND and PID columns. + == AUTHORS mailto:dave@gnu.org[Davidlohr Bueso] diff --git a/misc-utils/lslocks.c b/misc-utils/lslocks.c index caca13f..3d70b04 100644 --- a/misc-utils/lslocks.c +++ b/misc-utils/lslocks.c @@ -31,6 +31,8 @@ #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> +#include <stdbool.h> +#include <search.h> #include <libmount.h> #include <libsmartcols.h> @@ -45,6 +47,8 @@ #include "closestream.h" #include "optutils.h" #include "procfs.h" +#include "column-list-table.h" +#include "fileutils.h" /* column IDs */ enum { @@ -59,15 +63,16 @@ enum { COL_START, COL_END, COL_PATH, - COL_BLOCKER + COL_BLOCKER, + COL_HOLDERS, }; /* column names */ struct colinfo { - const char *name; /* header */ - double whint; /* width hint (N < 1 is in percent of termwidth) */ - int flags; /* SCOLS_FL_* */ - const char *help; + const char * const name; /* header */ + double whint; /* width hint (N < 1 is in percent of termwidth) */ + int flags; /* SCOLS_FL_* */ + const char *help; }; /* columns descriptions */ @@ -75,7 +80,7 @@ static struct colinfo infos[] = { [COL_SRC] = { "COMMAND",15, 0, N_("command of the process holding the lock") }, [COL_PID] = { "PID", 5, SCOLS_FL_RIGHT, N_("PID of the process holding the lock") }, [COL_TYPE] = { "TYPE", 5, SCOLS_FL_RIGHT, N_("kind of lock") }, - [COL_SIZE] = { "SIZE", 4, SCOLS_FL_RIGHT, N_("size of the lock") }, + [COL_SIZE] = { "SIZE", 4, SCOLS_FL_RIGHT, N_("size of the lock, use <number> if --bytes is given") }, [COL_INODE] = { "INODE", 5, SCOLS_FL_RIGHT, N_("inode number") }, [COL_MAJMIN] = { "MAJ:MIN", 6, 0, N_("major:minor device number") }, [COL_MODE] = { "MODE", 5, 0, N_("lock access mode") }, @@ -83,14 +88,13 @@ static struct colinfo infos[] = { [COL_START] = { "START", 10, SCOLS_FL_RIGHT, N_("relative byte offset of the lock")}, [COL_END] = { "END", 10, SCOLS_FL_RIGHT, N_("ending offset of the lock")}, [COL_PATH] = { "PATH", 0, SCOLS_FL_TRUNC, N_("path of the locked file")}, - [COL_BLOCKER] = { "BLOCKER", 0, SCOLS_FL_RIGHT, N_("PID of the process blocking the lock") } + [COL_BLOCKER] = { "BLOCKER", 0, SCOLS_FL_RIGHT, N_("PID of the process blocking the lock") }, + [COL_HOLDERS] = { "HOLDERS", 0, SCOLS_FL_WRAP, N_("HOLDERS of the lock") }, }; static int columns[ARRAY_SIZE(infos) * 2]; static size_t ncolumns; -static pid_t pid = 0; - static struct libmnt_table *tab; /* /proc/self/mountinfo */ /* basic output flags */ @@ -115,9 +119,56 @@ struct lock { unsigned int mandatory :1, blocked :1; uint64_t size; + int fd; int id; }; +struct lock_tnode { + dev_t dev; + ino_t inode; + + struct list_head chain; +}; + +static int lock_tnode_compare(const void *a, const void *b) +{ + struct lock_tnode *anode = ((struct lock_tnode *)a); + struct lock_tnode *bnode = ((struct lock_tnode *)b); + + if (anode->dev > bnode->dev) + return 1; + else if (anode->dev < bnode->dev) + return -1; + + if (anode->inode > bnode->inode) + return 1; + else if (anode->inode < bnode->inode) + return -1; + + return 0; +} + +static void add_to_tree(void *troot, struct lock *l) +{ + struct lock_tnode tmp = { .dev = l->dev, .inode = l->inode, }; + struct lock_tnode **head = tfind(&tmp, troot, lock_tnode_compare); + struct lock_tnode *new_head; + + if (head) { + list_add_tail(&l->locks, &(*head)->chain); + return; + } + + new_head = xmalloc(sizeof(*new_head)); + new_head->dev = l->dev; + new_head->inode = l->inode; + INIT_LIST_HEAD(&new_head->chain); + if (tsearch(new_head, troot, lock_tnode_compare) == NULL) + errx(EXIT_FAILURE, _("failed to allocate memory")); + + list_add_tail(&l->locks, &new_head->chain); +} + static void rem_lock(struct lock *lock) { if (!lock) @@ -170,13 +221,16 @@ static char *get_filename_sz(ino_t inode, pid_t lock_pid, size_t *size) struct stat sb; struct dirent *dp; DIR *dirp; - size_t len; + size_t sz; int fd; - char path[PATH_MAX], sym[PATH_MAX], *ret = NULL; + char path[PATH_MAX] = { 0 }, + sym[PATH_MAX] = { 0 }, *ret = NULL; *size = 0; - memset(path, 0, sizeof(path)); - memset(sym, 0, sizeof(sym)); + + if (lock_pid < 0) + /* pid could be -1 for OFD locks */ + return NULL; /* * We know the pid so we don't have to @@ -187,16 +241,14 @@ static char *get_filename_sz(ino_t inode, pid_t lock_pid, size_t *size) if (!(dirp = opendir(path))) return NULL; - if ((len = strlen(path)) >= (sizeof(path) - 2)) + if ((sz = strlen(path)) >= (sizeof(path) - 2)) goto out; if ((fd = dirfd(dirp)) < 0 ) goto out; - while ((dp = readdir(dirp))) { - if (!strcmp(dp->d_name, ".") || - !strcmp(dp->d_name, "..")) - continue; + while ((dp = xreaddir(dirp))) { + ssize_t len; errno = 0; @@ -237,99 +289,260 @@ static ino_t get_dev_inode(char *str, dev_t *dev) return inum; } -static int get_local_locks(struct list_head *locks) +struct override_info { + pid_t pid; + const char *cmdname; +}; + +static bool is_holder(struct lock *l, struct lock *m) { - int i; - FILE *fp; - char buf[PATH_MAX], *tok = NULL; - size_t sz; - struct lock *l; + return (l->start == m->start && + l->end == m->end && + l->inode == m->inode && + l->dev == m->dev && + l->mandatory == m->mandatory && + l->blocked == m->blocked && + strcmp(l->type, m->type) == 0 && + strcmp(l->mode, m->mode) == 0); +} - if (!(fp = fopen(_PATH_PROC_LOCKS, "r"))) - return -1; +static void patch_lock(struct lock *l, void *fallback) +{ + struct lock_tnode tmp = { .dev = l->dev, .inode = l->inode, }; + struct lock_tnode **head = tfind(&tmp, fallback, lock_tnode_compare); + struct list_head *p; - while (fgets(buf, sizeof(buf), fp)) { + if (!head) + return; - l = xcalloc(1, sizeof(*l)); - INIT_LIST_HEAD(&l->locks); + list_for_each(p, &(*head)->chain) { + struct lock *m = list_entry(p, struct lock, locks); + if (is_holder(l, m)) { + /* size and id can be ignored. */ + l->pid = m->pid; + l->cmdname = xstrdup(m->cmdname); + break; + } + } +} - for (tok = strtok(buf, " "), i = 0; tok; - tok = strtok(NULL, " "), i++) { +static void add_to_list(void *locks, struct lock *l) +{ + list_add(&l->locks, locks); +} - /* - * /proc/locks has *exactly* 8 "blocks" of text - * separated by ' ' - check <kernel>/fs/locks.c - */ - switch (i) { - case 0: /* ID: */ +static struct lock *get_lock(char *buf, struct override_info *oinfo, void *fallback) +{ + int i; + char *tok = NULL; + size_t sz; + struct lock *l = xcalloc(1, sizeof(*l)); + INIT_LIST_HEAD(&l->locks); + l->fd = -1; + + bool cmdname_unknown = false; + + for (tok = strtok(buf, " "), i = 0; tok; + tok = strtok(NULL, " "), i++) { + + /* + * /proc/locks has *exactly* 8 "blocks" of text + * separated by ' ' - check <kernel>/fs/locks.c + */ + switch (i) { + case 0: /* ID: */ + if (oinfo) + l->id = -1; + else { tok[strlen(tok) - 1] = '\0'; l->id = strtos32_or_err(tok, _("failed to parse ID")); - break; - case 1: /* posix, flock, etc */ - if (strcmp(tok, "->") == 0) { /* optional field */ - l->blocked = 1; - i--; - } else - l->type = xstrdup(tok); - break; + } + break; + case 1: /* posix, flock, etc */ + if (strcmp(tok, "->") == 0) { /* optional field */ + l->blocked = 1; + i--; + } else + l->type = xstrdup(tok); + break; - case 2: /* is this a mandatory lock? other values are advisory or noinode */ - l->mandatory = *tok == 'M' ? 1 : 0; - break; - case 3: /* lock mode */ - l->mode = xstrdup(tok); - break; + case 2: /* is this a mandatory lock? other values are advisory or noinode */ + l->mandatory = *tok == 'M' ? 1 : 0; + break; + case 3: /* lock mode */ + l->mode = xstrdup(tok); + break; - case 4: /* PID */ - /* - * If user passed a pid we filter it later when adding - * to the list, no need to worry now. OFD locks use -1 PID. - */ + case 4: /* PID */ + /* + * If user passed a pid we filter it later when adding + * to the list, no need to worry now. OFD locks use -1 PID. + */ + if (oinfo) { + l->pid = oinfo->pid; + l->cmdname = xstrdup(oinfo->cmdname); + } else { l->pid = strtos32_or_err(tok, _("failed to parse pid")); if (l->pid > 0) { l->cmdname = pid_get_cmdname(l->pid); - if (!l->cmdname) - l->cmdname = xstrdup(_("(unknown)")); + if (!l->cmdname) { + l->cmdname = NULL; + cmdname_unknown = true; + } } else - l->cmdname = xstrdup(_("(undefined)")); - break; + l->cmdname = NULL; + } + break; - case 5: /* device major:minor and inode number */ - l->inode = get_dev_inode(tok, &l->dev); - break; + case 5: /* device major:minor and inode number */ + l->inode = get_dev_inode(tok, &l->dev); + break; - case 6: /* start */ - l->start = !strcmp(tok, "EOF") ? 0 : - strtou64_or_err(tok, _("failed to parse start")); - break; + case 6: /* start */ + l->start = !strcmp(tok, "EOF") ? 0 : + strtou64_or_err(tok, _("failed to parse start")); + break; - case 7: /* end */ - /* replace '\n' character */ - tok[strlen(tok)-1] = '\0'; - l->end = !strcmp(tok, "EOF") ? 0 : - strtou64_or_err(tok, _("failed to parse end")); - break; - default: - break; - } + case 7: /* end */ + /* replace '\n' character */ + tok[strlen(tok)-1] = '\0'; + l->end = !strcmp(tok, "EOF") ? 0 : + strtou64_or_err(tok, _("failed to parse end")); + break; + default: + break; } + } + + if ((!l->blocked) && fallback && !l->cmdname) + patch_lock(l, fallback); + if (!l->cmdname) { + if (cmdname_unknown) + l->cmdname = xstrdup(_("(unknown)")); + else + l->cmdname = xstrdup(_("(undefined)")); + } + l->path = get_filename_sz(l->inode, l->pid, &sz); + + /* no permissions -- ignore */ + if (!l->path && no_inaccessible) { + rem_lock(l); + return NULL; + } - l->path = get_filename_sz(l->inode, l->pid, &sz); + if (!l->path) { + /* probably no permission to peek into l->pid's path */ + l->path = get_fallback_filename(l->dev); + l->size = 0; + } else + l->size = sz; - /* no permissions -- ignore */ - if (!l->path && no_inaccessible) { - rem_lock(l); + return l; +} + +static int get_pid_lock(void *locks, void (*add_lock)(void *, struct lock *), FILE *fp, + pid_t pid, const char *cmdname, int fd) +{ + char buf[PATH_MAX]; + struct override_info oinfo = { + .pid = pid, + .cmdname = cmdname, + }; + + while (fgets(buf, sizeof(buf), fp)) { + struct lock *l; + if (strncmp(buf, "lock:\t", 6)) continue; + l = get_lock(buf + 6, &oinfo, NULL); + if (l) { + add_lock(locks, l); + l->fd = fd; } + /* no break here. + Multiple recode locks can be taken via one fd. */ + } - if (!l->path) { - /* probably no permission to peek into l->pid's path */ - l->path = get_fallback_filename(l->dev); - l->size = 0; - } else - l->size = sz; + return 0; +} - list_add(&l->locks, locks); +static int get_pid_locks(void *locks, void (*add_lock)(void *, struct lock *), struct path_cxt *pc, + pid_t pid, const char *cmdname) +{ + DIR *sub = NULL; + struct dirent *d = NULL; + int rc = 0; + + while (ul_path_next_dirent(pc, &sub, "fdinfo", &d) == 0) { + uint64_t num; + FILE *fdinfo; + + if (ul_strtou64(d->d_name, &num, 10) != 0) /* only numbers */ + continue; + + fdinfo = ul_path_fopenf(pc, "r", "fdinfo/%ju", num); + if (fdinfo == NULL) + continue; + + get_pid_lock(locks, add_lock, fdinfo, pid, cmdname, (int)num); + fclose(fdinfo); + } + + return rc; +} + +static int get_pids_locks(void *locks, void (*add_lock)(void *, struct lock *)) +{ + DIR *dir; + struct dirent *d; + struct path_cxt *pc = NULL; + int rc = 0; + + pc = ul_new_path(NULL); + if (!pc) + err(EXIT_FAILURE, _("failed to alloc procfs handler")); + + dir = opendir(_PATH_PROC); + if (!dir) + err(EXIT_FAILURE, _("failed to open /proc")); + + while ((d = readdir(dir))) { + pid_t pid; + char buf[BUFSIZ]; + const char *cmdname = NULL; + + if (procfs_dirent_get_pid(d, &pid) != 0) + continue; + + if (procfs_process_init_path(pc, pid) != 0) { + rc = -1; + break; + } + + if (procfs_process_get_cmdname(pc, buf, sizeof(buf)) <= 0) + continue; + cmdname = buf; + + get_pid_locks(locks, add_lock, pc, pid, cmdname); + } + + closedir(dir); + ul_unref_path(pc); + + return rc; +} + +static int get_proc_locks(void *locks, void (*add_lock)(void *, struct lock *), void *fallback) +{ + FILE *fp; + char buf[PATH_MAX]; + + if (!(fp = fopen(_PATH_PROC_LOCKS, "r"))) + return -1; + + while (fgets(buf, sizeof(buf), fp)) { + struct lock *l = get_lock(buf, NULL, fallback); + if (l) + add_lock(locks, l); } fclose(fp); @@ -362,7 +575,7 @@ static inline int get_column_id(int num) } -static inline struct colinfo *get_column_info(unsigned num) +static inline const struct colinfo *get_column_info(unsigned num) { return &infos[ get_column_id(num) ]; } @@ -381,7 +594,13 @@ static pid_t get_blocker(int id, struct list_head *locks) return 0; } -static void add_scols_line(struct libscols_table *table, struct lock *l, struct list_head *locks) +static void xstrcoholder(char **str, struct lock *l) +{ + xstrfappend(str, "%d,%s,%d", + l->pid, l->cmdname, l->fd); +} + +static void add_scols_line(struct libscols_table *table, struct lock *l, struct list_head *locks, void *pid_locks) { size_t i; struct libscols_line *line; @@ -450,6 +669,28 @@ static void add_scols_line(struct libscols_table *table, struct lock *l, struct get_blocker(l->id, locks) : 0; if (bl) xasprintf(&str, "%d", (int) bl); + break; + } + case COL_HOLDERS: + { + struct lock_tnode tmp = { .dev = l->dev, .inode = l->inode, }; + struct lock_tnode **head = tfind(&tmp, pid_locks, lock_tnode_compare); + struct list_head *p; + + if (!head) + break; + + list_for_each(p, &(*head)->chain) { + struct lock *m = list_entry(p, struct lock, locks); + + if (!is_holder(l, m)) + continue; + + if (str) + xstrputc(&str, '\n'); + xstrcoholder(&str, m); + } + break; } default: break; @@ -460,11 +701,52 @@ static void add_scols_line(struct libscols_table *table, struct lock *l, struct } } -static int show_locks(struct list_head *locks) +static void rem_locks(struct list_head *locks) +{ + struct list_head *p, *pnext; + + /* destroy the list */ + list_for_each_safe(p, pnext, locks) { + struct lock *l = list_entry(p, struct lock, locks); + rem_lock(l); + } +} + +static void rem_tnode(void *node) +{ + struct lock_tnode *tnode = node; + + rem_locks(&tnode->chain); + free(node); +} + +static int get_json_type_for_column(int column_id, int representing_in_bytes) +{ + switch (column_id) { + case COL_SIZE: + if (!representing_in_bytes) + return SCOLS_JSON_STRING; + /* fallthrough */ + case COL_PID: + case COL_START: + case COL_END: + case COL_BLOCKER: + case COL_INODE: + return SCOLS_JSON_NUMBER; + case COL_M: + return SCOLS_JSON_BOOLEAN; + case COL_HOLDERS: + return SCOLS_JSON_ARRAY_STRING; + default: + return SCOLS_JSON_STRING; + } +} + +static int show_locks(struct list_head *locks, pid_t target_pid, void *pid_locks) { int rc = 0; size_t i; - struct list_head *p, *pnext; + struct list_head *p; struct libscols_table *table; table = scols_new_table(); @@ -480,34 +762,24 @@ static int show_locks(struct list_head *locks) for (i = 0; i < ncolumns; i++) { struct libscols_column *cl; - struct colinfo *col = get_column_info(i); + const struct colinfo *col = get_column_info(i); cl = scols_table_new_column(table, col->name, col->whint, col->flags); if (!cl) err(EXIT_FAILURE, _("failed to allocate output column")); + if (col->flags & SCOLS_FL_WRAP) { + scols_column_set_wrapfunc(cl, + scols_wrapnl_chunksize, + scols_wrapnl_nextchunk, + NULL); + scols_column_set_safechars(cl, "\n"); + } + if (json) { int id = get_column_id(i); - - switch (id) { - case COL_SIZE: - if (!bytes) - break; - /* fallthrough */ - case COL_PID: - case COL_START: - case COL_END: - case COL_BLOCKER: - case COL_INODE: - scols_column_set_json_type(cl, SCOLS_JSON_NUMBER); - break; - case COL_M: - scols_column_set_json_type(cl, SCOLS_JSON_BOOLEAN); - break; - default: - scols_column_set_json_type(cl, SCOLS_JSON_STRING); - break; - } + int json_type = get_json_type_for_column(id, bytes); + scols_column_set_json_type(cl, json_type); } } @@ -516,16 +788,10 @@ static int show_locks(struct list_head *locks) list_for_each(p, locks) { struct lock *l = list_entry(p, struct lock, locks); - if (pid && pid != l->pid) + if (target_pid && target_pid != l->pid) continue; - add_scols_line(table, l, locks); - } - - /* destroy the list */ - list_for_each_safe(p, pnext, locks) { - struct lock *l = list_entry(p, struct lock, locks); - rem_lock(l); + add_scols_line(table, l, locks, pid_locks); } scols_print_table(table); @@ -537,7 +803,6 @@ static int show_locks(struct list_head *locks) static void __attribute__((__noreturn__)) usage(void) { FILE *out = stdout; - size_t i; fputs(USAGE_HEADER, out); @@ -552,29 +817,48 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" -J, --json use JSON output format\n"), out); fputs(_(" -i, --noinaccessible ignore locks without read permissions\n"), out); fputs(_(" -n, --noheadings don't print headings\n"), out); - fputs(_(" -o, --output <list> define which output columns to use\n"), out); + fputs(_(" -o, --output <list> output columns (see --list-columns)\n"), out); fputs(_(" --output-all output all columns\n"), out); fputs(_(" -p, --pid <pid> display only locks held by this process\n"), out); fputs(_(" -r, --raw use the raw output format\n"), out); fputs(_(" -u, --notruncate don't truncate text in columns\n"), out); fputs(USAGE_SEPARATOR, out); - printf(USAGE_HELP_OPTIONS(24)); + fputs(_(" -H, --list-columns list the available columns\n"), out); + fprintf(out, USAGE_HELP_OPTIONS(24)); + fprintf(out, USAGE_MAN_TAIL("lslocks(8)")); - fputs(USAGE_COLUMNS, out); + exit(EXIT_SUCCESS); +} - for (i = 0; i < ARRAY_SIZE(infos); i++) - fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help)); +static void __attribute__((__noreturn__)) list_colunms(void) +{ + struct libscols_table *col_tb = xcolumn_list_table_new( + "lslocks-columns", stdout, raw, json); + + for (size_t i = 0; i < ARRAY_SIZE(infos); i++) { + if (i != COL_SIZE) { + int json_type = get_json_type_for_column(i, bytes); + xcolumn_list_table_append_line(col_tb, infos[i].name, + json_type, NULL, + _(infos[i].help)); + } else + xcolumn_list_table_append_line(col_tb, infos[i].name, + -1, "<string|number>", + _(infos[i].help)); + } - printf(USAGE_MAN_TAIL("lslocks(8)")); + scols_print_table(col_tb); + scols_unref_table(col_tb); exit(EXIT_SUCCESS); } int main(int argc, char *argv[]) { - int c, rc = 0; - struct list_head locks; + int c, rc = 0, collist = 0; + struct list_head proc_locks; + void *pid_locks = NULL; char *outarg = NULL; enum { OPT_OUTPUT_ALL = CHAR_MAX + 1 @@ -591,6 +875,7 @@ int main(int argc, char *argv[]) { "noheadings", no_argument, NULL, 'n' }, { "raw", no_argument, NULL, 'r' }, { "noinaccessible", no_argument, NULL, 'i' }, + { "list-columns", no_argument, NULL, 'H' }, { NULL, 0, NULL, 0 } }; @@ -599,13 +884,15 @@ int main(int argc, char *argv[]) { 0 } }; int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + pid_t target_pid = 0; + setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); close_stdout_atexit(); while ((c = getopt_long(argc, argv, - "biJp:o:nruhV", long_opts, NULL)) != -1) { + "biJp:o:nruhVH", long_opts, NULL)) != -1) { err_exclusive_options(c, long_opts, excl, excl_st); @@ -620,7 +907,7 @@ int main(int argc, char *argv[]) json = 1; break; case 'p': - pid = strtos32_or_err(optarg, _("invalid PID argument")); + target_pid = strtos32_or_err(optarg, _("invalid PID argument")); break; case 'o': outarg = optarg; @@ -639,6 +926,9 @@ int main(int argc, char *argv[]) disable_columns_truncate(); break; + case 'H': + collist = 1; + break; case 'V': print_version(EXIT_SUCCESS); case 'h': @@ -648,7 +938,10 @@ int main(int argc, char *argv[]) } } - INIT_LIST_HEAD(&locks); + if (collist) + list_colunms(); /* print end exit */ + + INIT_LIST_HEAD(&proc_locks); if (!ncolumns) { /* default columns */ @@ -669,10 +962,18 @@ int main(int argc, char *argv[]) scols_init_debug(0); - rc = get_local_locks(&locks); + /* get_pids_locks() get locks related information from "lock:" fields + * of /proc/$pid/fdinfo/$fd as fallback information. + * get_proc_locks() used the fallback information if /proc/locks + * doesn't provides enough information or provides staled information. */ + get_pids_locks(&pid_locks, add_to_tree); + rc = get_proc_locks(&proc_locks, add_to_list, &pid_locks); + + if (!rc && !list_empty(&proc_locks)) + rc = show_locks(&proc_locks, target_pid, &pid_locks); - if (!rc && !list_empty(&locks)) - rc = show_locks(&locks); + tdestroy(pid_locks, rem_tnode); + rem_locks(&proc_locks); mnt_unref_table(tab); return rc; diff --git a/misc-utils/mcookie.1 b/misc-utils/mcookie.1 index 0062930..b2e8284 100644 --- a/misc-utils/mcookie.1 +++ b/misc-utils/mcookie.1 @@ -2,12 +2,12 @@ .\" Title: mcookie .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-10-23 +.\" Date: 2024-01-31 .\" Manual: User Commands -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "MCOOKIE" "1" "2023-10-23" "util\-linux 2.39.3" "User Commands" +.TH "MCOOKIE" "1" "2024-01-31" "util\-linux 2.40" "User Commands" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 diff --git a/misc-utils/mcookie.c b/misc-utils/mcookie.c index be5c34a..99df4f4 100644 --- a/misc-utils/mcookie.c +++ b/misc-utils/mcookie.c @@ -1,4 +1,8 @@ -/* mcookie.c -- Generates random numbers for xauth +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * mcookie.c -- Generates random numbers for xauth * Created: Fri Feb 3 10:42:48 1995 by faith@cs.unc.edu * Revised: Fri Mar 19 07:48:01 1999 by faith@acm.org * Public Domain 1995, 1999 Rickard E. Faith (faith@acm.org) @@ -91,12 +95,12 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" -v, --verbose explain what is being done\n"), out); fputs(USAGE_SEPARATOR, out); - printf(USAGE_HELP_OPTIONS(23)); + fprintf(out, USAGE_HELP_OPTIONS(23)); fputs(USAGE_ARGUMENTS, out); - printf(USAGE_ARG_SIZE(_("<num>"))); + fprintf(out, USAGE_ARG_SIZE(_("<num>"))); - printf(USAGE_MAN_TAIL("mcookie(1)")); + fprintf(out, USAGE_MAN_TAIL("mcookie(1)")); exit(EXIT_SUCCESS); } diff --git a/misc-utils/meson.build b/misc-utils/meson.build index 7d21d02..847b101 100644 --- a/misc-utils/meson.build +++ b/misc-utils/meson.build @@ -12,6 +12,11 @@ look_sources = files( 'look.c', ) +lastlog2_sources = files( + 'lastlog2.c', +) + \ + strutils_c + mcookie_sources = files( 'mcookie.c', ) + \ @@ -42,10 +47,6 @@ lsblk_sources = files( lsfd_sources = files ( 'lsfd.c', 'lsfd.h', - 'lsfd-filter.h', - 'lsfd-filter.c', - 'lsfd-counter.h', - 'lsfd-counter.c', 'lsfd-decode-file-flags.c', 'lsfd-file.c', 'lsfd-cdev.c', @@ -75,7 +76,33 @@ test_uuidd_sources = files( 'test_uuidd.c', ) +if build_liblastlog2 and systemd.found() + lastlog2_tmpfiles = configure_file( + input : 'lastlog2-tmpfiles.conf.in', + output : 'lastlog2-tmpfiles.conf', + configuration : conf) + install_data( + lastlog2_tmpfiles, + install_dir : '/usr/lib/tmpfiles.d') + + lastlog2_service = configure_file( + input : 'lastlog2-import.service.in', + output : 'lastlog2-import.service', + configuration : conf) + install_data( + lastlog2_service, + install_dir : systemdsystemunitdir) +endif + if build_uuidd and systemd.found() + uuidd_tmpfiles = configure_file( + input : 'uuidd-tmpfiles.conf.in', + output : 'uuidd-tmpfiles.conf', + configuration : conf) + install_data( + uuidd_tmpfiles, + install_dir : '/usr/lib/tmpfiles.d') + uuidd_service = configure_file( input : 'uuidd.service.in', output : 'uuidd.service', @@ -140,6 +167,10 @@ install_data( install_dir : docdir, install_mode: 'rwxr-xr-x') +exch_sources = files( + 'exch.c', +) + fincore_sources = files( 'fincore.c', ) @@ -165,3 +196,7 @@ fadvise_sources = files( waitpid_sources = files( 'waitpid.c', ) + +lsclocks_sources = files( + 'lsclocks.c', +) diff --git a/misc-utils/namei.1 b/misc-utils/namei.1 index ad35f1d..6eb1766 100644 --- a/misc-utils/namei.1 +++ b/misc-utils/namei.1 @@ -2,12 +2,12 @@ .\" Title: namei .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-11-21 +.\" Date: 2024-03-20 .\" Manual: User Commands -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "NAMEI" "1" "2023-11-21" "util\-linux 2.39.3" "User Commands" +.TH "NAMEI" "1" "2024-03-20" "util\-linux 2.40" "User Commands" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 @@ -112,7 +112,7 @@ To be discovered. The original \fBnamei\fP program was written by \c .MTO "rogers\(atamadeus.wr.tek.com" "Roger Southwick" "." .sp -The program was rewritten by Karel Zak \c +The program was rewritten by \c .MTO "kzak\(atredhat.com" "Karel Zak" "." .SH "SEE ALSO" .sp diff --git a/misc-utils/namei.1.adoc b/misc-utils/namei.1.adoc index 2cfeac1..a26cb01 100644 --- a/misc-utils/namei.1.adoc +++ b/misc-utils/namei.1.adoc @@ -70,7 +70,7 @@ To be discovered. The original *namei* program was written by mailto:rogers@amadeus.wr.tek.com[Roger Southwick]. -The program was rewritten by Karel Zak mailto:kzak@redhat.com[Karel Zak]. +The program was rewritten by mailto:kzak@redhat.com[Karel Zak]. == SEE ALSO diff --git a/misc-utils/namei.c b/misc-utils/namei.c index 3d41407..0a9ed8d 100644 --- a/misc-utils/namei.c +++ b/misc-utils/namei.c @@ -373,9 +373,9 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_( " -Z, --context print any security context of each file \n"), out); #endif - printf(USAGE_HELP_OPTIONS(21)); + fprintf(out, USAGE_HELP_OPTIONS(21)); - printf(USAGE_MAN_TAIL("namei(1)")); + fprintf(out, USAGE_MAN_TAIL("namei(1)")); exit(EXIT_SUCCESS); } @@ -488,4 +488,3 @@ main(int argc, char **argv) return rc; } - diff --git a/misc-utils/pipesz.1 b/misc-utils/pipesz.1 index b0da040..b6ebcf5 100644 --- a/misc-utils/pipesz.1 +++ b/misc-utils/pipesz.1 @@ -2,12 +2,12 @@ .\" Title: pipesz .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-11-21 +.\" Date: 2024-01-31 .\" Manual: User Commands -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "PIPESZ" "1" "2023-11-21" "util\-linux 2.39.3" "User Commands" +.TH "PIPESZ" "1" "2024-01-31" "util\-linux 2.40" "User Commands" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 diff --git a/misc-utils/pipesz.c b/misc-utils/pipesz.c index f586acb..48f07ae 100644 --- a/misc-utils/pipesz.c +++ b/misc-utils/pipesz.c @@ -62,37 +62,37 @@ static char opt_verbose = 0; /* --verbose */ static void __attribute__((__noreturn__)) usage(void) { fputs(USAGE_HEADER, stdout); - printf(_(" %s [options] [--set <size>] [--] [command]\n"), program_invocation_short_name); - printf(_(" %s [options] --get\n"), program_invocation_short_name); + fprintf(stdout, _(" %s [options] [--set <size>] [--] [command]\n"), program_invocation_short_name); + fprintf(stdout, _(" %s [options] --get\n"), program_invocation_short_name); fputs(USAGE_SEPARATOR, stdout); /* TRANSLATORS: 'command' refers to a program argument */ - puts(_("Set or examine pipe buffer sizes and optionally execute command.")); + fputsln(_("Set or examine pipe buffer sizes and optionally execute command."), stdout); fputs(USAGE_OPTIONS, stdout); - puts(_(" -g, --get examine pipe buffers")); + fputsln(_(" -g, --get examine pipe buffers"), stdout); /* TRANSLATORS: '%s' refers to a system file */ - printf( + fprintf(stdout, _(" -s, --set <size> set pipe buffer sizes\n" " size defaults to %s\n"), PIPESZ_DEFAULT_SIZE_FILE); fputs(USAGE_SEPARATOR, stdout); - puts(_(" -f, --file <path> act on a file")); - puts(_(" -n, --fd <num> act on a file descriptor")); - puts(_(" -i, --stdin act on standard input")); - puts(_(" -o, --stdout act on standard output")); - puts(_(" -e, --stderr act on standard error")); + fputsln(_(" -f, --file <path> act on a file"), stdout); + fputsln(_(" -n, --fd <num> act on a file descriptor"), stdout); + fputsln(_(" -i, --stdin act on standard input"), stdout); + fputsln(_(" -o, --stdout act on standard output"), stdout); + fputsln(_(" -e, --stderr act on standard error"), stdout); fputs(USAGE_SEPARATOR, stdout); - puts(_(" -c, --check do not continue after an error")); - puts(_(" -q, --quiet do not warn of non-fatal errors")); - puts(_(" -v, --verbose provide detailed output")); + fputsln(_(" -c, --check do not continue after an error"), stdout); + fputsln(_(" -q, --quiet do not warn of non-fatal errors"), stdout); + fputsln(_(" -v, --verbose provide detailed output"), stdout); fputs(USAGE_SEPARATOR, stdout); - printf(USAGE_HELP_OPTIONS(20)); + fprintf(stdout, USAGE_HELP_OPTIONS(20)); - printf(USAGE_MAN_TAIL("pipesz(1)")); + fprintf(stdout, USAGE_MAN_TAIL("pipesz(1)")); exit(EXIT_SUCCESS); } @@ -260,7 +260,7 @@ int main(int argc, char **argv) ++n_opt_pipe; break; case 'n': - fd = strtos32_or_err(optarg, _("invalid fd argument")); + (void) strtos32_or_err(optarg, _("invalid fd argument")); ++n_opt_pipe; break; case 'o': diff --git a/misc-utils/rename.1 b/misc-utils/rename.1 index 073415e..63ca2ea 100644 --- a/misc-utils/rename.1 +++ b/misc-utils/rename.1 @@ -2,12 +2,12 @@ .\" Title: rename .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-11-21 +.\" Date: 2024-03-20 .\" Manual: User Commands -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "RENAME" "1" "2023-11-21" "util\-linux 2.39.3" "User Commands" +.TH "RENAME" "1" "2024-03-20" "util\-linux 2.40" "User Commands" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 diff --git a/misc-utils/rename.1.adoc b/misc-utils/rename.1.adoc index 1f6225f..692f4b5 100644 --- a/misc-utils/rename.1.adoc +++ b/misc-utils/rename.1.adoc @@ -1,4 +1,8 @@ -//po4a: entry man manual +// +// No copyright is claimed. This code is in the public domain; do with +// it what you wish. +// +// po4a: entry man manual // Written by Andries E. Brouwer (aeb@cwi.nl) // Placed in the public domain = rename(1) diff --git a/misc-utils/rename.c b/misc-utils/rename.c index 04e61ed..9ab3869 100644 --- a/misc-utils/rename.c +++ b/misc-utils/rename.c @@ -28,6 +28,7 @@ for i in $@; do N=`echo "$i" | sed "s/$FROM/$TO/g"`; mv "$i" "$N"; done #include <errno.h> #include <getopt.h> #include <fcntl.h> +#include <libgen.h> #include <unistd.h> #include <termios.h> #include <sys/types.h> @@ -58,12 +59,7 @@ static char *find_initial_replace(char *from, char *to, char *orig) /* We only want to search in the final path component. Don't include the final '/' in that component; if `from` is empty, we want it to first match after the '/', not before. */ - search_start = strrchr(orig, '/'); - - if (search_start == NULL) - search_start = orig; - else - search_start++; + search_start = basename(orig); } return strstr(search_start, from); @@ -270,8 +266,8 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" -o, --no-overwrite don't overwrite existing files\n"), out); fputs(_(" -i, --interactive prompt before overwrite\n"), out); fputs(USAGE_SEPARATOR, out); - printf(USAGE_HELP_OPTIONS(21)); - printf(USAGE_MAN_TAIL("rename(1)")); + fprintf(out, USAGE_HELP_OPTIONS(21)); + fprintf(out, USAGE_MAN_TAIL("rename(1)")); exit(EXIT_SUCCESS); } diff --git a/misc-utils/uuidd-tmpfiles.conf.in b/misc-utils/uuidd-tmpfiles.conf.in new file mode 100644 index 0000000..b362930 --- /dev/null +++ b/misc-utils/uuidd-tmpfiles.conf.in @@ -0,0 +1,6 @@ +# This file is part of uuidd. +# +# See tmpfiles.d(5) for details +# +d @runstatedir@/uuidd 2775 uuidd uuidd - +d @localstatedir@/lib/libuuid 0755 uuidd uuidd - diff --git a/misc-utils/uuidd.8 b/misc-utils/uuidd.8 index 0e209de..082ffee 100644 --- a/misc-utils/uuidd.8 +++ b/misc-utils/uuidd.8 @@ -2,12 +2,12 @@ .\" Title: uuidd .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-11-21 +.\" Date: 2024-03-20 .\" Manual: System Administration -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "UUIDD" "8" "2023-11-21" "util\-linux 2.39.3" "System Administration" +.TH "UUIDD" "8" "2024-03-20" "util\-linux 2.40" "System Administration" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 @@ -41,9 +41,9 @@ The \fBuuidd\fP daemon is used by the UUID library to generate universally uniqu .RS 4 Activate continuous clock handling for time based UUIDs. \fBuuidd\fP could use all possible clock values, beginning with the daemon\(cqs start time. The optional argument can be used to set a value for the max_clock_offset. This gurantees, that a clock value of a UUID will always be within the range of the max_clock_offset. .sp -The option \*(Aq\-C\*(Aq or \*(Aq\-\-cont\-clock\*(Aq enables the feature with a default max_clock_offset of 2 hours. +The option \fB\-C\fP or \fB\-\-cont\-clock\fP enables the feature with a default max_clock_offset of 2 hours. .sp -The option \*(Aq\-C<NUM>[hd]\*(Aq or \*(Aq\-\-cont\-clock=<NUM>[hd]\*(Aq enables the feature with a max_clock_offset of NUM seconds. In case of an appended h or d, the NUM value is read in hours or days. The minimum value is 60 seconds, the maximum value is 365 days. +The option \fB\-C<NUM>[hd]\fP or \fB\-\-cont\-clock=<NUM>[hd]\fP enables the feature with a max_clock_offset of NUM seconds. In case of an appended h or d, the NUM value is read in hours or days. The minimum value is 60 seconds, the maximum value is 365 days. .RE .sp \fB\-d\fP, \fB\-\-debug\fP diff --git a/misc-utils/uuidd.8.adoc b/misc-utils/uuidd.8.adoc index b0f4850..c1c6625 100644 --- a/misc-utils/uuidd.8.adoc +++ b/misc-utils/uuidd.8.adoc @@ -27,9 +27,9 @@ The *uuidd* daemon is used by the UUID library to generate universally unique id *-C*, *--cont-clock*[=_time_]:: Activate continuous clock handling for time based UUIDs. *uuidd* could use all possible clock values, beginning with the daemon's start time. The optional argument can be used to set a value for the max_clock_offset. This gurantees, that a clock value of a UUID will always be within the range of the max_clock_offset. + -The option '-C' or '--cont-clock' enables the feature with a default max_clock_offset of 2 hours. +The option *-C* or *--cont-clock* enables the feature with a default max_clock_offset of 2 hours. + -The option '-C<NUM>[hd]' or '--cont-clock=<NUM>[hd]' enables the feature with a max_clock_offset of NUM seconds. In case of an appended h or d, the NUM value is read in hours or days. The minimum value is 60 seconds, the maximum value is 365 days. +The option *-C<NUM>[hd]* or *--cont-clock=<NUM>[hd]* enables the feature with a max_clock_offset of NUM seconds. In case of an appended h or d, the NUM value is read in hours or days. The minimum value is 60 seconds, the maximum value is 365 days. *-d*, *--debug*:: Run *uuidd* in debugging mode. This prevents *uuidd* from running as a daemon. diff --git a/misc-utils/uuidd.c b/misc-utils/uuidd.c index db8b0c7..42a252d 100644 --- a/misc-utils/uuidd.c +++ b/misc-utils/uuidd.c @@ -113,8 +113,8 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" -d, --debug run in debugging mode\n"), out); fputs(_(" -q, --quiet turn on quiet mode\n"), out); fputs(USAGE_SEPARATOR, out); - printf(USAGE_HELP_OPTIONS(25)); - printf(USAGE_MAN_TAIL("uuidd(8)")); + fprintf(out, USAGE_HELP_OPTIONS(25)); + fprintf(out, USAGE_MAN_TAIL("uuidd(8)")); exit(EXIT_SUCCESS); } @@ -442,15 +442,6 @@ static void server_loop(const char *socket_path, const char *pidfile_path, pfd[POLLFD_SOCKET].fd = s; pfd[POLLFD_SIGNAL].events = pfd[POLLFD_SOCKET].events = POLLIN | POLLERR | POLLHUP; - num = 1; - if (uuidd_cxt->cont_clock_offset) { - /* trigger initialization */ - (void) __uuid_generate_time_cont(uu, &num, uuidd_cxt->cont_clock_offset); - if (uuidd_cxt->debug) - fprintf(stderr, _("max_clock_offset = %u sec\n"), - uuidd_cxt->cont_clock_offset); - } - while (1) { ret = poll(pfd, ARRAY_SIZE(pfd), uuidd_cxt->timeout ? diff --git a/misc-utils/uuidd.rc.in b/misc-utils/uuidd.rc.in index 7943945..4919049 100644 --- a/misc-utils/uuidd.rc.in +++ b/misc-utils/uuidd.rc.in @@ -17,8 +17,9 @@ PATH=/bin:/usr/bin:/sbin:/usr/sbin DAEMON=/usr/sbin/uuidd UUIDD_USER=uuidd UUIDD_GROUP=uuidd -UUIDD_DIR=@runstatedir@/uuidd -PIDFILE=$UUIDD_DIR/uuidd.pid +UUIDD_RUNSTATEDIR=@runstatedir@/uuidd +UUIDD_LOCALSTATEDIR=@localstatedir@/lib/libuuid +PIDFILE=$UUIDD_RUNSTATEDIR/uuidd.pid test -x $DAEMON || exit 0 @@ -27,9 +28,13 @@ test -x $DAEMON || exit 0 case "$1" in start) log_daemon_msg "Starting uuid generator" "uuidd" - if ! test -d $UUIDD_DIR; then - mkdir -p $UUIDD_DIR - chown -R $UUIDD_USER:$UUIDD_GROUP $UUIDD_DIR + if ! test -d $UUIDD_RUNSTATEDIR; then + mkdir -p $UUIDD_RUNSTATEDIR + chown -R $UUIDD_USER:$UUIDD_GROUP $UUIDD_RUNSTATEDIR + fi + if ! test -d $UUIDD_LOCALSTATEDIR; then + mkdir -p $UUIDD_LOCALSTATEDIR + chown -R $UUIDD_USER:$UUIDD_GROUP $UUIDD_LOCALSTATEDIR fi start_daemon -p $PIDFILE $DAEMON log_end_msg $? diff --git a/misc-utils/uuidd.service.in b/misc-utils/uuidd.service.in index 4ad6d97..529d723 100644 --- a/misc-utils/uuidd.service.in +++ b/misc-utils/uuidd.service.in @@ -4,7 +4,7 @@ Documentation=man:uuidd(8) Requires=uuidd.socket [Service] -ExecStart=@usrsbin_execdir@/uuidd --socket-activation +ExecStart=@usrsbin_execdir@/uuidd --socket-activation --cont-clock Restart=no User=uuidd Group=uuidd @@ -16,7 +16,7 @@ ProtectKernelTunables=yes ProtectKernelModules=yes ProtectControlGroups=yes MemoryDenyWriteExecute=yes -ReadWritePaths=/var/lib/libuuid/ +ReadWritePaths=@localstatedir@/lib/libuuid/ SystemCallFilter=@default @file-system @basic-io @system-service @signal @io-event @network-io [Install] diff --git a/misc-utils/uuidgen.1 b/misc-utils/uuidgen.1 index da396f7..4987f6d 100644 --- a/misc-utils/uuidgen.1 +++ b/misc-utils/uuidgen.1 @@ -2,12 +2,12 @@ .\" Title: uuidgen .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-12-01 +.\" Date: 2024-03-20 .\" Manual: User Commands -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "UUIDGEN" "1" "2023-12-01" "util\-linux 2.39.3" "User Commands" +.TH "UUIDGEN" "1" "2024-03-20" "util\-linux 2.40" "User Commands" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 @@ -81,6 +81,11 @@ Generate the hash with the \fInamespace\fP prefix. The \fInamespace\fP is UUID, Generate the hash of the \fIname\fP. .RE .sp +\fB\-C\fP, \fB\-\-count\fP \fInum\fP +.RS 4 +Generate multiple UUIDs using the enhanced capability of the libuuid to cache time\-based UUIDs, thus resulting in improved performance. However, this holds no significance for other UUID types. +.RE +.sp \fB\-x\fP, \fB\-\-hex\fP .RS 4 Interpret name \fIname\fP as a hexadecimal string. diff --git a/misc-utils/uuidgen.1.adoc b/misc-utils/uuidgen.1.adoc index 3f71aa9..e1d2cae 100644 --- a/misc-utils/uuidgen.1.adoc +++ b/misc-utils/uuidgen.1.adoc @@ -48,6 +48,9 @@ Generate the hash with the _namespace_ prefix. The _namespace_ is UUID, or '@ns' *-N*, *--name* _name_:: Generate the hash of the _name_. +*-C*, *--count* _num_:: +Generate multiple UUIDs using the enhanced capability of the libuuid to cache time-based UUIDs, thus resulting in improved performance. However, this holds no significance for other UUID types. + *-x*, *--hex*:: Interpret name _name_ as a hexadecimal string. diff --git a/misc-utils/uuidgen.c b/misc-utils/uuidgen.c index 7dcd1f4..57769c1 100644 --- a/misc-utils/uuidgen.c +++ b/misc-utils/uuidgen.c @@ -17,6 +17,9 @@ #include "nls.h" #include "c.h" #include "closestream.h" +#include "strutils.h" +#include "optutils.h" +#include "xalloc.h" static void __attribute__((__noreturn__)) usage(void) { @@ -29,17 +32,18 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_("Create a new UUID value.\n"), out); fputs(USAGE_OPTIONS, out); - fputs(_(" -r, --random generate random-based uuid\n"), out); - fputs(_(" -t, --time generate time-based uuid\n"), out); - fputs(_(" -n, --namespace ns generate hash-based uuid in this namespace\n"), out); - printf(_(" available namespaces: %s\n"), "@dns @url @oid @x500"); - fputs(_(" -N, --name name generate hash-based uuid from this name\n"), out); - fputs(_(" -m, --md5 generate md5 hash\n"), out); - fputs(_(" -s, --sha1 generate sha1 hash\n"), out); - fputs(_(" -x, --hex interpret name as hex string\n"), out); + fputs(_(" -r, --random generate random-based uuid\n"), out); + fputs(_(" -t, --time generate time-based uuid\n"), out); + fputs(_(" -n, --namespace <ns> generate hash-based uuid in this namespace\n"), out); + fprintf(out, _(" available namespaces: %s\n"), "@dns @url @oid @x500"); + fputs(_(" -N, --name <name> generate hash-based uuid from this name\n"), out); + fputs(_(" -m, --md5 generate md5 hash\n"), out); + fputs(_(" -C, --count <num> generate more uuids in loop\n"), out); + fputs(_(" -s, --sha1 generate sha1 hash\n"), out); + fputs(_(" -x, --hex interpret name as hex string\n"), out); fputs(USAGE_SEPARATOR, out); - printf(USAGE_HELP_OPTIONS(21)); - printf(USAGE_MAN_TAIL("uuidgen(1)")); + fprintf(out, USAGE_HELP_OPTIONS(21)); + fprintf(out, USAGE_MAN_TAIL("uuidgen(1)")); exit(EXIT_SUCCESS); } @@ -54,7 +58,7 @@ badstring: errtryhelp(EXIT_FAILURE); } - value2 = malloc(*valuelen / 2 + 1); + value2 = xmalloc(*valuelen / 2 + 1); for (x = n = 0; n < *valuelen; n++) { c = value[n]; @@ -88,6 +92,7 @@ main (int argc, char *argv[]) char *namespace = NULL, *name = NULL; size_t namelen = 0; uuid_t ns, uu; + unsigned int count = 1, i; static const struct option longopts[] = { {"random", no_argument, NULL, 'r'}, @@ -97,17 +102,30 @@ main (int argc, char *argv[]) {"namespace", required_argument, NULL, 'n'}, {"name", required_argument, NULL, 'N'}, {"md5", no_argument, NULL, 'm'}, + {"count", required_argument, NULL, 'C'}, {"sha1", no_argument, NULL, 's'}, {"hex", no_argument, NULL, 'x'}, {NULL, 0, NULL, 0} }; + static const ul_excl_t excl[] = { + { 'C', 'm', 's' }, + { 'N', 'r', 't' }, + { 'm', 'r', 's', 't' }, + { 'n', 'r', 't' }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); close_stdout_atexit(); - while ((c = getopt_long(argc, argv, "rtVhn:N:msx", longopts, NULL)) != -1) + while ((c = getopt_long(argc, argv, "C:rtVhn:N:msx", longopts, NULL)) != -1) { + + err_exclusive_options(c, longopts, excl, excl_st); + switch (c) { case 't': do_type = UUID_TYPE_DCE_TIME; @@ -124,6 +142,9 @@ main (int argc, char *argv[]) case 'm': do_type = UUID_TYPE_DCE_MD5; break; + case 'C': + count = strtou32_or_err(optarg, _("invalid count argument")); + break; case 's': do_type = UUID_TYPE_DCE_SHA1; break; @@ -138,6 +159,7 @@ main (int argc, char *argv[]) default: errtryhelp(EXIT_FAILURE); } + } if (namespace) { if (!name) { @@ -165,43 +187,45 @@ main (int argc, char *argv[]) name = unhex(name, &namelen); } - switch (do_type) { - case UUID_TYPE_DCE_TIME: - uuid_generate_time(uu); - break; - case UUID_TYPE_DCE_RANDOM: - uuid_generate_random(uu); - break; - case UUID_TYPE_DCE_MD5: - case UUID_TYPE_DCE_SHA1: - if (namespace[0] == '@' && namespace[1] != '\0') { - const uuid_t *uuidptr; - - uuidptr = uuid_get_template(&namespace[1]); - if (uuidptr == NULL) { - warnx(_("unknown namespace alias: '%s'"), namespace); - errtryhelp(EXIT_FAILURE); - } - memcpy(ns, *uuidptr, sizeof(ns)); - } else { - if (uuid_parse(namespace, ns) != 0) { - warnx(_("invalid uuid for namespace: '%s'"), namespace); - errtryhelp(EXIT_FAILURE); + for (i = 0; i < count; i++) { + switch (do_type) { + case UUID_TYPE_DCE_TIME: + uuid_generate_time(uu); + break; + case UUID_TYPE_DCE_RANDOM: + uuid_generate_random(uu); + break; + case UUID_TYPE_DCE_MD5: + case UUID_TYPE_DCE_SHA1: + if (namespace[0] == '@' && namespace[1] != '\0') { + const uuid_t *uuidptr; + + uuidptr = uuid_get_template(&namespace[1]); + if (uuidptr == NULL) { + warnx(_("unknown namespace alias: '%s'"), namespace); + errtryhelp(EXIT_FAILURE); + } + memcpy(ns, *uuidptr, sizeof(ns)); + } else { + if (uuid_parse(namespace, ns) != 0) { + warnx(_("invalid uuid for namespace: '%s'"), namespace); + errtryhelp(EXIT_FAILURE); + } } + if (do_type == UUID_TYPE_DCE_MD5) + uuid_generate_md5(uu, ns, name, namelen); + else + uuid_generate_sha1(uu, ns, name, namelen); + break; + default: + uuid_generate(uu); + break; } - if (do_type == UUID_TYPE_DCE_MD5) - uuid_generate_md5(uu, ns, name, namelen); - else - uuid_generate_sha1(uu, ns, name, namelen); - break; - default: - uuid_generate(uu); - break; - } - uuid_unparse(uu, str); + uuid_unparse(uu, str); - printf("%s\n", str); + printf("%s\n", str); + } if (is_hex) free(name); diff --git a/misc-utils/uuidparse.1 b/misc-utils/uuidparse.1 index 2109361..25e93d5 100644 --- a/misc-utils/uuidparse.1 +++ b/misc-utils/uuidparse.1 @@ -2,12 +2,12 @@ .\" Title: uuidparse .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-10-23 +.\" Date: 2024-01-31 .\" Manual: User Commands -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "UUIDPARSE" "1" "2023-10-23" "util\-linux 2.39.3" "User Commands" +.TH "UUIDPARSE" "1" "2024-01-31" "util\-linux 2.40" "User Commands" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 diff --git a/misc-utils/uuidparse.c b/misc-utils/uuidparse.c index 2dc6dfb..2dd7fb3 100644 --- a/misc-utils/uuidparse.c +++ b/misc-utils/uuidparse.c @@ -97,17 +97,17 @@ static void __attribute__((__noreturn__)) usage(void) fprintf(stdout, _(" %s [options] <uuid ...>\n"), program_invocation_short_name); fputs(USAGE_OPTIONS, stdout); - puts(_(" -J, --json use JSON output format")); - puts(_(" -n, --noheadings don't print headings")); - puts(_(" -o, --output <list> COLUMNS to display (see below)")); - puts(_(" -r, --raw use the raw output format")); - printf(USAGE_HELP_OPTIONS(24)); + fputsln(_(" -J, --json use JSON output format"), stdout); + fputsln(_(" -n, --noheadings don't print headings"), stdout); + fputsln(_(" -o, --output <list> COLUMNS to display (see below)"), stdout); + fputsln(_(" -r, --raw use the raw output format"), stdout); + fprintf(stdout, USAGE_HELP_OPTIONS(24)); fputs(USAGE_COLUMNS, stdout); for (i = 0; i < ARRAY_SIZE(infos); i++) fprintf(stdout, " %8s %s\n", infos[i].name, _(infos[i].help)); - printf(USAGE_MAN_TAIL("uuidparse(1)")); + fprintf(stdout, USAGE_MAN_TAIL("uuidparse(1)")); exit(EXIT_SUCCESS); } diff --git a/misc-utils/waitpid.1 b/misc-utils/waitpid.1 index 98ca155..8dca30d 100644 --- a/misc-utils/waitpid.1 +++ b/misc-utils/waitpid.1 @@ -2,12 +2,12 @@ .\" Title: waitpid .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-11-21 +.\" Date: 2024-01-31 .\" Manual: User Commands -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "WAITPID" "1" "2023-11-21" "util\-linux 2.39.3" "User Commands" +.TH "WAITPID" "1" "2024-01-31" "util\-linux 2.40" "User Commands" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 diff --git a/misc-utils/waitpid.c b/misc-utils/waitpid.c index b01a2f0..0a4e03d 100644 --- a/misc-utils/waitpid.c +++ b/misc-utils/waitpid.c @@ -41,9 +41,6 @@ #define TIMEOUT_SOCKET_IDX UINT64_MAX -#define err_nosys(exitcode, ...) \ - err(errno == ENOSYS ? EXIT_NOTSUPP : exitcode, __VA_ARGS__) - static bool verbose = false; static struct timespec timeout; static bool allow_exited = false; @@ -67,7 +64,8 @@ static int *open_pidfds(size_t n_pids, pid_t *pids) pidfds[i] = pidfd_open(pids[i], 0); if (pidfds[i] == -1) { if (allow_exited && errno == ESRCH) { - warnx(_("PID %d has exited, skipping"), pids[i]); + if (verbose) + warnx(_("PID %d has exited, skipping"), pids[i]); continue; } err_nosys(EXIT_FAILURE, _("could not open pid %u"), pids[i]); diff --git a/misc-utils/whereis.1 b/misc-utils/whereis.1 index c982144..005f40b 100644 --- a/misc-utils/whereis.1 +++ b/misc-utils/whereis.1 @@ -2,12 +2,12 @@ .\" Title: whereis .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-11-21 +.\" Date: 2024-01-31 .\" Manual: User Commands -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "WHEREIS" "1" "2023-11-21" "util\-linux 2.39.3" "User Commands" +.TH "WHEREIS" "1" "2024-01-31" "util\-linux 2.40" "User Commands" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 diff --git a/misc-utils/whereis.c b/misc-utils/whereis.c index d21b434..c11efa5 100644 --- a/misc-utils/whereis.c +++ b/misc-utils/whereis.c @@ -220,8 +220,8 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" -l output effective lookup paths\n"), out); fputs(USAGE_SEPARATOR, out); - printf(USAGE_HELP_OPTIONS(16)); - printf(USAGE_MAN_TAIL("whereis(1)")); + fprintf(out, USAGE_HELP_OPTIONS(16)); + fprintf(out, USAGE_MAN_TAIL("whereis(1)")); exit(EXIT_SUCCESS); } diff --git a/misc-utils/wipefs.8 b/misc-utils/wipefs.8 index 6323872..50ff2ad 100644 --- a/misc-utils/wipefs.8 +++ b/misc-utils/wipefs.8 @@ -2,12 +2,12 @@ .\" Title: wipefs .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.20 -.\" Date: 2023-10-23 +.\" Date: 2024-03-20 .\" Manual: System Administration -.\" Source: util-linux 2.39.3 +.\" Source: util-linux 2.40 .\" Language: English .\" -.TH "WIPEFS" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.TH "WIPEFS" "8" "2024-03-20" "util\-linux 2.40" "System Administration" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 @@ -42,9 +42,9 @@ wipefs \- wipe a signature from a device .sp When used without any options, \fBwipefs\fP lists all visible filesystems and the offsets of their basic signatures. The default output is subject to change. So whenever possible, you should avoid using default outputs in your scripts. Always explicitly define expected columns by using \fB\-\-output\fP \fIcolumns\-list\fP in environments where a stable output is required. .sp -\fBwipefs\fP calls the \fBBLKRRPART\fP ioctl when it has erased a partition\-table signature to inform the kernel about the change. The ioctl is called as the last step and when all specified signatures from all specified devices are already erased. This feature can be used to wipe content on partitions devices as well as partition table on a disk device, for example by \fBwipefs \-a /dev/sdc1 /dev/sdc2 /dev/sdc\fP. +\fBwipefs\fP calls the \fBBLKRRPART\fP ioctl when it has erased a partition\-table signature to inform the kernel about the change. The ioctl is called as the last step and when all specified signatures from all specified devices are already erased. This feature can be used to wipe content on partition devices as well as a partition table on a disk device, for example by \fBwipefs \-a /dev/sdc1 /dev/sdc2 /dev/sdc\fP. .sp -Note that some filesystems and some partition tables store more magic strings on the device (e.g., FAT, ZFS, GPT). The \fBwipefs\fP command (since v2.31) lists all the offset where a magic strings have been detected. +Note that some filesystems and some partition tables store more magic strings on the device (e.g., FAT, ZFS, GPT). The \fBwipefs\fP command (since v2.31) lists all the offsets where magic strings have been detected. .sp When option \fB\-a\fP is used, all magic strings that are visible for \fBlibblkid\fP(3) are erased. In this case the \fBwipefs\fP scans the device again after each modification (erase) until no magic string is found. .sp @@ -56,9 +56,9 @@ Note that by default \fBwipefs\fP does not erase nested partition tables on non\ Erase all available signatures. The set of erased signatures can be restricted with the \fB\-t\fP option. .RE .sp -\fB\-b\fP, \fB\-\-backup\fP +\fB\-b\fP, \fB\-\-backup\fP[=\fIdir\fP] .RS 4 -Create a signature backup to the file \fI$HOME/wipefs\-<devname>\-<offset>.bak\fP. For more details see the \fBEXAMPLE\fP section. +Create a signature backup to the file \fIwipefs\-<devname>\-<offset>.bak\fP in \fI$HOME\fP or the directory specified as the optional argument. For more details see the \fBEXAMPLE\fP section. .RE .sp \fB\-f\fP, \fB\-\-force\fP diff --git a/misc-utils/wipefs.8.adoc b/misc-utils/wipefs.8.adoc index d53b768..184c723 100644 --- a/misc-utils/wipefs.8.adoc +++ b/misc-utils/wipefs.8.adoc @@ -27,9 +27,9 @@ wipefs - wipe a signature from a device When used without any options, *wipefs* lists all visible filesystems and the offsets of their basic signatures. The default output is subject to change. So whenever possible, you should avoid using default outputs in your scripts. Always explicitly define expected columns by using *--output* _columns-list_ in environments where a stable output is required. -*wipefs* calls the *BLKRRPART* ioctl when it has erased a partition-table signature to inform the kernel about the change. The ioctl is called as the last step and when all specified signatures from all specified devices are already erased. This feature can be used to wipe content on partitions devices as well as partition table on a disk device, for example by *wipefs -a /dev/sdc1 /dev/sdc2 /dev/sdc*. +*wipefs* calls the *BLKRRPART* ioctl when it has erased a partition-table signature to inform the kernel about the change. The ioctl is called as the last step and when all specified signatures from all specified devices are already erased. This feature can be used to wipe content on partition devices as well as a partition table on a disk device, for example by *wipefs -a /dev/sdc1 /dev/sdc2 /dev/sdc*. -Note that some filesystems and some partition tables store more magic strings on the device (e.g., FAT, ZFS, GPT). The *wipefs* command (since v2.31) lists all the offset where a magic strings have been detected. +Note that some filesystems and some partition tables store more magic strings on the device (e.g., FAT, ZFS, GPT). The *wipefs* command (since v2.31) lists all the offsets where magic strings have been detected. When option *-a* is used, all magic strings that are visible for *libblkid*(3) are erased. In this case the *wipefs* scans the device again after each modification (erase) until no magic string is found. @@ -40,8 +40,8 @@ Note that by default *wipefs* does not erase nested partition tables on non-whol *-a*, *--all*:: Erase all available signatures. The set of erased signatures can be restricted with the *-t* option. -*-b*, *--backup*:: -Create a signature backup to the file _$HOME/wipefs-<devname>-<offset>.bak_. For more details see the *EXAMPLE* section. +*-b*, *--backup*[=_dir_]:: +Create a signature backup to the file _wipefs-<devname>-<offset>.bak_ in _$HOME_ or the directory specified as the optional argument. For more details see the *EXAMPLE* section. *-f*, *--force*:: Force erasure, even if the filesystem is mounted. This is required in order to erase a partition-table signature on a block device. diff --git a/misc-utils/wipefs.c b/misc-utils/wipefs.c index 6be470b..0d6cfa5 100644 --- a/misc-utils/wipefs.c +++ b/misc-utils/wipefs.c @@ -65,6 +65,7 @@ struct wipe_control { char *devname; const char *type_pattern; /* -t <pattern> */ const char *lockmode; + const char *backup; /* location of backups */ struct libscols_table *outtab; struct wipe_desc *offsets; /* -o <offset> -o <offset> ... */ @@ -77,7 +78,6 @@ struct wipe_control { unsigned int noact : 1, all : 1, quiet : 1, - backup : 1, force : 1, json : 1, no_headings : 1, @@ -535,12 +535,9 @@ static int do_wipe(struct wipe_control *ctl) } if (ctl->backup) { - const char *home = getenv ("HOME"); char *tmp = xstrdup(ctl->devname); - if (!home) - errx(EXIT_FAILURE, _("failed to create a signature backup, $HOME undefined")); - xasprintf (&backup, "%s/wipefs-%s-", home, basename(tmp)); + xasprintf(&backup, "%s/wipefs-%s-", ctl->backup, basename(tmp)); free(tmp); } @@ -630,36 +627,36 @@ usage(void) size_t i; fputs(USAGE_HEADER, stdout); - printf(_(" %s [options] <device>\n"), program_invocation_short_name); + fprintf(stdout, _(" %s [options] <device>\n"), program_invocation_short_name); fputs(USAGE_SEPARATOR, stdout); - puts(_("Wipe signatures from a device.")); + fputsln(_("Wipe signatures from a device."), stdout); fputs(USAGE_OPTIONS, stdout); - puts(_(" -a, --all wipe all magic strings (BE CAREFUL!)")); - puts(_(" -b, --backup create a signature backup in $HOME")); - puts(_(" -f, --force force erasure")); - puts(_(" -i, --noheadings don't print headings")); - puts(_(" -J, --json use JSON output format")); - puts(_(" -n, --no-act do everything except the actual write() call")); - puts(_(" -o, --offset <num> offset to erase, in bytes")); - puts(_(" -O, --output <list> COLUMNS to display (see below)")); - puts(_(" -p, --parsable print out in parsable instead of printable format")); - puts(_(" -q, --quiet suppress output messages")); - puts(_(" -t, --types <list> limit the set of filesystem, RAIDs or partition tables")); - printf( + fputsln(_(" -a, --all wipe all magic strings (BE CAREFUL!)"), stdout); + fputsln(_(" -b, --backup[=<dir>] create a signature backup in <dir> or $HOME"), stdout); + fputsln(_(" -f, --force force erasure"), stdout); + fputsln(_(" -i, --noheadings don't print headings"), stdout); + fputsln(_(" -J, --json use JSON output format"), stdout); + fputsln(_(" -n, --no-act do everything except the actual write() call"), stdout); + fputsln(_(" -o, --offset <num> offset to erase, in bytes"), stdout); + fputsln(_(" -O, --output <list> COLUMNS to display (see below)"), stdout); + fputsln(_(" -p, --parsable print out in parsable instead of printable format"), stdout); + fputsln(_(" -q, --quiet suppress output messages"), stdout); + fputsln(_(" -t, --types <list> limit the set of filesystem, RAIDs or partition tables"), stdout); + fprintf(stdout, _(" --lock[=<mode>] use exclusive device lock (%s, %s or %s)\n"), "yes", "no", "nonblock"); - printf(USAGE_HELP_OPTIONS(21)); + fprintf(stdout, USAGE_HELP_OPTIONS(22)); fputs(USAGE_ARGUMENTS, stdout); - printf(USAGE_ARG_SIZE(_("<num>"))); + fprintf(stdout, USAGE_ARG_SIZE(_("<num>"))); fputs(USAGE_COLUMNS, stdout); for (i = 0; i < ARRAY_SIZE(infos); i++) fprintf(stdout, " %8s %s\n", infos[i].name, _(infos[i].help)); - printf(USAGE_MAN_TAIL("wipefs(8)")); + fprintf(stdout, USAGE_MAN_TAIL("wipefs(8)")); exit(EXIT_SUCCESS); } @@ -676,7 +673,7 @@ main(int argc, char **argv) }; static const struct option longopts[] = { { "all", no_argument, NULL, 'a' }, - { "backup", no_argument, NULL, 'b' }, + { "backup", optional_argument, NULL, 'b' }, { "force", no_argument, NULL, 'f' }, { "help", no_argument, NULL, 'h' }, { "lock", optional_argument, NULL, OPT_LOCK }, @@ -703,7 +700,7 @@ main(int argc, char **argv) textdomain(PACKAGE); close_stdout_atexit(); - while ((c = getopt_long(argc, argv, "abfhiJnO:o:pqt:V", longopts, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "ab::fhiJnO:o:pqt:V", longopts, NULL)) != -1) { err_exclusive_options(c, longopts, excl, excl_st); @@ -712,7 +709,14 @@ main(int argc, char **argv) ctl.all = 1; break; case 'b': - ctl.backup = 1; + if (optarg) { + ctl.backup = optarg; + } else { + ctl.backup = getenv("HOME"); + if (!ctl.backup) + errx(EXIT_FAILURE, + _("failed to create a signature backup, $HOME undefined")); + } break; case 'f': ctl.force = 1; |