diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 19:10:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 19:10:49 +0000 |
commit | cfe5e3905201349e9cf3f95d52ff4bd100bde37d (patch) | |
tree | d0baf160cbee3195249d095f85e52d20c21acf02 /misc-utils | |
parent | Initial commit. (diff) | |
download | util-linux-cfe5e3905201349e9cf3f95d52ff4bd100bde37d.tar.xz util-linux-cfe5e3905201349e9cf3f95d52ff4bd100bde37d.zip |
Adding upstream version 2.39.3.upstream/2.39.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
100 files changed, 36288 insertions, 0 deletions
diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am new file mode 100644 index 0000000..71548c9 --- /dev/null +++ b/misc-utils/Makemodule.am @@ -0,0 +1,299 @@ +if BUILD_CAL +usrbin_exec_PROGRAMS += cal +MANPAGES += misc-utils/cal.1 +dist_noinst_DATA += misc-utils/cal.1.adoc +cal_SOURCES = misc-utils/cal.c +cal_CFLAGS = $(AM_CFLAGS) +cal_LDADD = $(LDADD) libcommon.la libtcolors.la +# tinfo or ncurses are optional +if HAVE_TINFO +cal_LDADD += $(TINFO_LIBS) +cal_CFLAGS += $(TINFO_CFLAGS) +else +if HAVE_NCURSES +cal_LDADD += $(NCURSES_LIBS) +cal_CFLAGS += $(NCURSES_CFLAGS) +endif +endif # !HAVE_TINFO + +check_PROGRAMS += test_cal +test_cal_SOURCES = $(cal_SOURCES) +test_cal_LDADD = $(cal_LDADD) +test_cal_CFLAGS = -DTEST_CAL $(cal_CFLAGS) +endif # BUILD_CAL + + +if BUILD_LOGGER +usrbin_exec_PROGRAMS += logger +MANPAGES += misc-utils/logger.1 +dist_noinst_DATA += misc-utils/logger.1.adoc +logger_SOURCES = misc-utils/logger.c lib/strutils.c lib/strv.c +logger_LDADD = $(LDADD) libcommon.la +logger_CFLAGS = $(AM_CFLAGS) +if HAVE_SYSTEMD +logger_LDADD += $(SYSTEMD_LIBS) $(SYSTEMD_DAEMON_LIBS) $(SYSTEMD_JOURNAL_LIBS) +logger_CFLAGS += $(SYSTEMD_CFLAGS) $(SYSTEMD_DAEMON_CFLAGS) $(SYSTEMD_JOURNAL_CFLAGS) +endif + +check_PROGRAMS += test_logger +test_logger_SOURCES = $(logger_SOURCES) +test_logger_LDADD = $(logger_LDADD) +test_logger_CFLAGS = -DTEST_LOGGER $(logger_CFLAGS) +endif # BUILD_LOGGER + + +if BUILD_LOOK +usrbin_exec_PROGRAMS += look +MANPAGES += misc-utils/look.1 +dist_noinst_DATA += misc-utils/look.1.adoc +look_SOURCES = misc-utils/look.c +endif + +if BUILD_MCOOKIE +usrbin_exec_PROGRAMS += mcookie +MANPAGES += misc-utils/mcookie.1 +dist_noinst_DATA += misc-utils/mcookie.1.adoc +mcookie_SOURCES = misc-utils/mcookie.c lib/md5.c +mcookie_LDADD = $(LDADD) libcommon.la +endif + +if BUILD_NAMEI +usrbin_exec_PROGRAMS += namei +MANPAGES += misc-utils/namei.1 +dist_noinst_DATA += misc-utils/namei.1.adoc +namei_SOURCES = misc-utils/namei.c lib/strutils.c lib/idcache.c +namei_LDADD = $(LDADD) $(SELINUX_LIBS) +endif + +if BUILD_WHEREIS +usrbin_exec_PROGRAMS += whereis +MANPAGES += misc-utils/whereis.1 +dist_noinst_DATA += misc-utils/whereis.1.adoc +whereis_SOURCES = misc-utils/whereis.c +whereis_LDADD = $(LDADD) libcommon.la +endif + +if BUILD_LSLOCKS +usrbin_exec_PROGRAMS += lslocks +MANPAGES += misc-utils/lslocks.8 +dist_noinst_DATA += misc-utils/lslocks.8.adoc +lslocks_LDADD = $(LDADD) libcommon.la libmount.la libsmartcols.la +lslocks_SOURCES = misc-utils/lslocks.c +lslocks_CFLAGS = $(AM_CFLAGS) -I$(ul_libmount_incdir) -I$(ul_libsmartcols_incdir) +endif + +if BUILD_LSBLK +bin_PROGRAMS += lsblk +MANPAGES += misc-utils/lsblk.8 +dist_noinst_DATA += misc-utils/lsblk.8.adoc +lsblk_SOURCES = \ + misc-utils/lsblk.c \ + misc-utils/lsblk-mnt.c \ + misc-utils/lsblk-properties.c \ + misc-utils/lsblk-devtree.c \ + misc-utils/lsblk.h +lsblk_LDADD = $(LDADD) libblkid.la libmount.la libcommon.la libsmartcols.la +lsblk_CFLAGS = $(AM_CFLAGS) -I$(ul_libblkid_incdir) -I$(ul_libmount_incdir) -I$(ul_libsmartcols_incdir) +if HAVE_UDEV +lsblk_LDADD += -ludev +endif +endif # BUILD_LSBLK + +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_CFLAGS = $(AM_CFLAGS) -I$(ul_libuuid_incdir) +endif + +if BUILD_UUIDPARSE +usrbin_exec_PROGRAMS += uuidparse +MANPAGES += misc-utils/uuidparse.1 +dist_noinst_DATA += misc-utils/uuidparse.1.adoc +uuidparse_SOURCES = misc-utils/uuidparse.c +uuidparse_LDADD = $(LDADD) libcommon.la libuuid.la libsmartcols.la +uuidparse_CFLAGS = $(AM_CFLAGS) -I$(ul_libuuid_incdir) -I$(ul_libsmartcols_incdir) +endif + +if BUILD_UUIDD +usrsbin_exec_PROGRAMS += uuidd +MANPAGES += misc-utils/uuidd.8 +dist_noinst_DATA += misc-utils/uuidd.8.adoc +uuidd_LDADD = $(LDADD) libuuid.la libcommon.la $(REALTIME_LIBS) +uuidd_CFLAGS = $(DAEMON_CFLAGS) $(AM_CFLAGS) -I$(ul_libuuid_incdir) +uuidd_LDFLAGS = $(DAEMON_LDFLAGS) $(AM_LDFLAGS) +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) +systemdsystemunit_DATA += \ + misc-utils/uuidd.service \ + misc-utils/uuidd.socket +endif + +check_PROGRAMS += test_uuidd +test_uuidd_SOURCES = misc-utils/test_uuidd.c +test_uuidd_LDADD = $(LDADD) libcommon.la libuuid.la -lpthread +test_uuidd_CFLAGS = $(AM_CFLAGS) -I$(ul_libuuid_incdir) +endif # BUILD_UUIDD + +PATHFILES += \ + misc-utils/uuidd.rc \ + misc-utils/uuidd.service \ + misc-utils/uuidd.socket + +if BUILD_BLKID +sbin_PROGRAMS += blkid +MANPAGES += misc-utils/blkid.8 +dist_noinst_DATA += misc-utils/blkid.8.adoc +blkid_SOURCES = misc-utils/blkid.c \ + lib/ismounted.c +blkid_LDADD = $(LDADD) libblkid.la libcommon.la +blkid_CFLAGS = $(AM_CFLAGS) -I$(ul_libblkid_incdir) + +if HAVE_STATIC_BLKID +sbin_PROGRAMS += blkid.static +blkid_static_SOURCES = $(blkid_SOURCES) +blkid_static_LDFLAGS = -all-static +blkid_static_LDADD = $(LDADD) libblkid.la +blkid_static_CFLAGS = $(AM_CFLAGS) -I$(ul_libblkid_incdir) +endif +endif # BUILD_BLKID + + +if BUILD_FINDFS +sbin_PROGRAMS += findfs +MANPAGES += misc-utils/findfs.8 +dist_noinst_DATA += misc-utils/findfs.8.adoc +findfs_LDADD = $(LDADD) libblkid.la +findfs_SOURCES = misc-utils/findfs.c +findfs_CFLAGS = $(AM_CFLAGS) -I$(ul_libblkid_incdir) +endif + +if BUILD_WIPEFS +sbin_PROGRAMS += wipefs +MANPAGES += misc-utils/wipefs.8 +dist_noinst_DATA += misc-utils/wipefs.8.adoc +wipefs_SOURCES = misc-utils/wipefs.c +wipefs_LDADD = $(LDADD) libblkid.la libcommon.la libsmartcols.la +wipefs_CFLAGS = $(AM_CFLAGS) -I$(ul_libblkid_incdir) -I$(ul_libsmartcols_incdir) +endif + +if BUILD_FINDMNT +bin_PROGRAMS += findmnt +MANPAGES += misc-utils/findmnt.8 +dist_noinst_DATA += misc-utils/findmnt.8.adoc +findmnt_LDADD = $(LDADD) libmount.la \ + libcommon.la \ + libsmartcols.la \ + libblkid.la +findmnt_CFLAGS = $(AM_CFLAGS) \ + -I$(ul_libmount_incdir) \ + -I$(ul_libsmartcols_incdir) \ + -I$(ul_libblkid_incdir) +findmnt_SOURCES = misc-utils/findmnt.c \ + misc-utils/findmnt-verify.c \ + misc-utils/findmnt.h +if HAVE_UDEV +findmnt_LDADD += -ludev +endif +endif # BUILD_FINDMNT + + +if BUILD_KILL +bin_PROGRAMS += kill +MANPAGES += misc-utils/kill.1 +dist_noinst_DATA += misc-utils/kill.1.adoc +kill_SOURCES = misc-utils/kill.c +kill_LDADD = $(LDADD) libcommon.la +endif + +if BUILD_RENAME +usrbin_exec_PROGRAMS += rename +MANPAGES += misc-utils/rename.1 +dist_noinst_DATA += misc-utils/rename.1.adoc +rename_SOURCES = misc-utils/rename.c +endif + +if BUILD_GETOPT +usrbin_exec_PROGRAMS += getopt +MANPAGES += misc-utils/getopt.1 +dist_noinst_DATA += misc-utils/getopt.1.adoc +getopt_SOURCES = misc-utils/getopt.c +getopt_LDADD = $(LDADD) libcommon.la +getoptexampledir = $(docdir) +dist_getoptexample_DATA = \ + misc-utils/getopt-example.bash \ + misc-utils/getopt-example.tcsh +endif + +if BUILD_FINCORE +usrbin_exec_PROGRAMS += fincore +MANPAGES += misc-utils/fincore.1 +dist_noinst_DATA += misc-utils/fincore.1.adoc +fincore_SOURCES = misc-utils/fincore.c +fincore_LDADD = $(LDADD) libsmartcols.la libcommon.la +fincore_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir) +endif + +if BUILD_HARDLINK +usrbin_exec_PROGRAMS += hardlink +MANPAGES += misc-utils/hardlink.1 +dist_noinst_DATA += misc-utils/hardlink.1.adoc +hardlink_SOURCES = misc-utils/hardlink.c lib/monotonic.c lib/fileeq.c +hardlink_LDADD = $(LDADD) libcommon.la $(REALTIME_LIBS) +hardlink_CFLAGS = $(AM_CFLAGS) +endif + +if BUILD_LSFD +bin_PROGRAMS += lsfd +MANPAGES += misc-utils/lsfd.1 +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 \ + misc-utils/lsfd-bdev.c \ + misc-utils/lsfd-sock.c \ + misc-utils/lsfd-sock.h \ + misc-utils/lsfd-sock-xinfo.c \ + misc-utils/lsfd-unkn.c \ + misc-utils/lsfd-fifo.c +lsfd_LDADD = $(LDADD) libsmartcols.la libcommon.la +lsfd_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir) +endif + +if BUILD_PIPESZ +bin_PROGRAMS += pipesz +MANPAGES += misc-utils/pipesz.1 +dist_noinst_DATA += misc-utils/pipesz.1.adoc +pipesz_SOURCES = misc-utils/pipesz.c +pipesz_LDADD = $(LDADD) libcommon.la +pipesz_CFLAGS = $(AM_CFLAGS) +endif + +if BUILD_FADVISE +usrbin_exec_PROGRAMS += fadvise +MANPAGES += misc-utils/fadvise.1 +dist_noinst_DATA += misc-utils/fadvise.1.adoc +fadvise_SOURCES = misc-utils/fadvise.c +fadvise_LDADD = $(LDADD) libcommon.la +fadvise_CFLAGS = $(AM_CFLAGS) +endif + +if BUILD_WAITPID +usrbin_exec_PROGRAMS += waitpid +MANPAGES += misc-utils/waitpid.1 +dist_noinst_DATA += misc-utils/waitpid.1.adoc +waitpid_SOURCES = misc-utils/waitpid.c +waitpid_LDADD = $(LDADD) libcommon.la +waitpid_CFLAGS = $(AM_CFLAGS) +endif diff --git a/misc-utils/blkid.8 b/misc-utils/blkid.8 new file mode 100644 index 0000000..ebf3fcd --- /dev/null +++ b/misc-utils/blkid.8 @@ -0,0 +1,270 @@ +'\" t +.\" Title: blkid +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "BLKID" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.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" +blkid \- locate/print block device attributes +.SH "SYNOPSIS" +.sp +\fBblkid\fP \fB\-\-label\fP \fIlabel\fP | \fB\-\-uuid\fP \fIuuid\fP +.sp +\fBblkid\fP [\fB\-\-no\-encoding\fP \fB\-\-garbage\-collect\fP \fB\-\-list\-one\fP \fB\-\-cache\-file\fP \fIfile\fP] [\fB\-\-output\fP \fIformat\fP] [\fB\-\-match\-tag\fP \fItag\fP] [\fB\-\-match\-token\fP \fINAME=value\fP] [\fIdevice\fP...] +.sp +\fBblkid\fP \fB\-\-probe\fP [\fB\-\-offset\fP \fIoffset\fP] [\fB\-\-output\fP \fIformat\fP] [\fB\-\-size\fP \fIsize\fP] [\fB\-\-match\-tag\fP \fItag\fP] [\fB\-\-match\-types\fP \fIlist\fP] [\fB\-\-usages\fP \fIlist\fP] [\fB\-\-no\-part\-details\fP] \fIdevice\fP... +.sp +\fBblkid\fP \fB\-\-info\fP [\fB\-\-output format\fP] [\fB\-\-match\-tag\fP \fItag\fP] \fIdevice\fP... +.SH "DESCRIPTION" +.sp +The \fBblkid\fP program is the command\-line interface to working with the \fBlibblkid\fP(3) library. It can determine the type of content (e.g., filesystem or swap) that a block device holds, and also the attributes (tokens, NAME=value pairs) from the content metadata (e.g., LABEL or UUID fields). +.sp +\fBIt is recommended to use\fP \fBlsblk\fP(8) \fBcommand to get information about block devices, or lsblk \-\-fs to get an overview of filesystems, or\fP \fBfindmnt\fP(8) \fBto search in already mounted filesystems.\fP +.RS 3 +.ll -.6i +.sp +\fBlsblk\fP(8) provides more information, better control on output formatting, easy to use in scripts and it does not require root permissions to get actual information. \fBblkid\fP reads information directly from devices and for non\-root users it returns cached unverified information. \fBblkid\fP is mostly designed for system services and to test \fBlibblkid\fP(3) functionality. +.br +.RE +.ll +.sp +When \fIdevice\fP is specified, tokens from only this device are displayed. It is possible to specify multiple \fIdevice\fP arguments on the command line. If none is given, all partitions or unpartitioned devices which appear in \fI/proc/partitions\fP are shown, if they are recognized. +.sp +\fBblkid\fP has two main forms of operation: either searching for a device with a specific NAME=value pair, or displaying NAME=value pairs for one or more specified devices. +.sp +For security reasons \fBblkid\fP silently ignores all devices where the probing result is ambivalent (multiple colliding filesystems are detected). The low\-level probing mode (\fB\-p\fP) provides more information and extra exit status in this case. It\(cqs recommended to use \fBwipefs\fP(8) to get a detailed overview and to erase obsolete stuff (magic strings) from the device. +.SH "OPTIONS" +.sp +The \fIsize\fP and \fIoffset\fP arguments may be followed by the multiplicative suffixes like KiB (=1024), MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB and YiB (the "iB" is optional, e.g., "K" has the same meaning as "KiB"), or the suffixes KB (=1000), MB (=1000*1000), and so on for GB, TB, PB, EB, ZB and YB. +.sp +\fB\-c\fP, \fB\-\-cache\-file\fP \fIcachefile\fP +.RS 4 +Read from \fIcachefile\fP instead of reading from the default cache file (see the \fBCONFIGURATION FILE\fP section for more details). If you want to start with a clean cache (i.e., don\(cqt report devices previously scanned but not necessarily available at this time), specify \fI/dev/null\fP. +.RE +.sp +\fB\-d\fP, \fB\-\-no\-encoding\fP +.RS 4 +Don\(cqt encode non\-printing characters. The non\-printing characters are encoded by ^ and M\- notation by default. Note that the \fB\-\-output udev\fP output format uses a different encoding which cannot be disabled. +.RE +.sp +\fB\-D\fP, \fB\-\-no\-part\-details\fP +.RS 4 +Don\(cqt print information (PART_ENTRY_* tags) from partition table in low\-level probing mode. +.RE +.sp +\fB\-g\fP, \fB\-\-garbage\-collect\fP +.RS 4 +Perform a garbage collection pass on the blkid cache to remove devices which no longer exist. +.RE +.sp +\fB\-H\fP, \fB\-\-hint\fP \fIsetting\fP +.RS 4 +Set probing hint. The hints are an optional way to force probing functions to +check, for example, another location. The currently supported is +"session_offset=\fInumber\fP" to set session offset on multi\-session UDF. +.RE +.sp +\fB\-i\fP, \fB\-\-info\fP +.RS 4 +Display information about I/O Limits (aka I/O topology). The \*(Aqexport\*(Aq output format is automatically enabled. This option can be used together with the \fB\-\-probe\fP option. +.RE +.sp +\fB\-k\fP, \fB\-\-list\-filesystems\fP +.RS 4 +List all known filesystems and RAIDs and exit. +.RE +.sp +\fB\-l\fP, \fB\-\-list\-one\fP +.RS 4 +Look up only one device that matches the search parameter specified with the \fB\-\-match\-token\fP option. If there are multiple devices that match the specified search parameter, then the device with the highest priority is returned, and/or the first device found at a given priority (but see below note about udev). Device types in order of decreasing priority are: Device Mapper, EVMS, LVM, MD, and finally regular block devices. If this option is not specified, \fBblkid\fP will print all of the devices that match the search parameter. +.sp +This option forces \fBblkid\fP to use udev when used for LABEL or UUID tokens in \fB\-\-match\-token\fP. The goal is to provide output consistent with other utils (like \fBmount\fP(8), etc.) on systems where the same tag is used for multiple devices. +.RE +.sp +\fB\-L\fP, \fB\-\-label\fP \fIlabel\fP +.RS 4 +Look up the device that uses this filesystem \fIlabel\fP; this is equal to \fB\-\-list\-one \-\-output device \-\-match\-token LABEL=\fP\fIlabel\fP. This lookup method is able to reliably use /dev/disk/by\-label udev symlinks (dependent on a setting in \fI/etc/blkid.conf\fP). Avoid using the symlinks directly; it is not reliable to use the symlinks without verification. The \fB\-\-label\fP option works on systems with and without udev. +.sp +Unfortunately, the original \fBblkid\fP(8) from e2fsprogs uses the \fB\-L\fP option as a synonym for \fB\-o list\fP. For better portability, use \fB\-l \-o device \-t LABEL=\fP\fIlabel\fP and \fB\-o list\fP in your scripts rather than the \fB\-L\fP option. +.RE +.sp +\fB\-n\fP, \fB\-\-match\-types\fP \fIlist\fP +.RS 4 +Restrict the probing functions to the specified (comma\-separated) \fIlist\fP of superblock types (names). The list items may be prefixed with "no" to specify the types which should be ignored. For example: +.sp +\fBblkid \-\-probe \-\-match\-types vfat,ext3,ext4 /dev/sda1\fP +.sp +probes for vfat, ext3 and ext4 filesystems, and +.sp +\fBblkid \-\-probe \-\-match\-types nominix /dev/sda1\fP +.sp +probes for all supported formats except minix filesystems. This option is only useful together with \fB\-\-probe\fP. +.RE +.sp +\fB\-o\fP, \fB\-\-output\fP \fIformat\fP +.RS 4 +Use the specified output format. Note that the order of variables and devices is not fixed. See also option \fB\-s\fP. The \fIformat\fP parameter may be: +.sp +\fBfull\fP +.RS 4 +print all tags (the default) +.RE +.sp +\fBvalue\fP +.RS 4 +print the value of the tags +.RE +.sp +\fBlist\fP +.RS 4 +print the devices in a user\-friendly format; this output format is unsupported for low\-level probing (\fB\-\-probe\fP or \fB\-\-info\fP). +.sp +This output format is \fBDEPRECATED\fP in favour of the \fBlsblk\fP(8) command. +.RE +.sp +\fBdevice\fP +.RS 4 +print the device name only; this output format is always enabled for the \fB\-\-label\fP and \fB\-\-uuid\fP options +.RE +.sp +\fBudev\fP +.RS 4 +print key="value" pairs for easy import into the udev environment; the keys are prefixed by ID_FS_ or ID_PART_ prefixes. The value may be modified to be safe for udev environment; allowed is plain ASCII, hex\-escaping and valid UTF\-8, everything else (including whitespaces) is replaced with \*(Aq_\*(Aq. The keys with \fI_ENC\fP postfix use hex\-escaping for unsafe chars. +.sp +The udev output returns the ID_FS_AMBIVALENT tag if more superblocks are detected, and ID_PART_ENTRY_* tags are always returned for all partitions including empty partitions. +.sp +This output format is \fBDEPRECATED\fP. +.RE +.sp +\fBexport\fP +.RS 4 +print key=value pairs for easy import into the environment; this output format is automatically enabled when I/O Limits (\fB\-\-info\fP option) are requested. +.sp +The non\-printing characters are encoded by ^ and M\- notation and all potentially unsafe characters are escaped. +.RE +.RE +.sp +\fB\-O\fP, \fB\-\-offset\fP \fIoffset\fP +.RS 4 +Probe at the given \fIoffset\fP (only useful with \fB\-\-probe\fP). This option can be used together with the \fB\-\-info\fP option. +.RE +.sp +\fB\-p\fP, \fB\-\-probe\fP +.RS 4 +Switch to low\-level superblock probing mode (bypassing the cache). +.sp +Note that low\-level probing also returns information about partition table type (PTTYPE tag) and partitions (PART_ENTRY_* tags). The tag names produced by low\-level probing are based on names used internally by libblkid and it may be different than when executed without \fB\-\-probe\fP (for example PART_ENTRY_UUID= vs PARTUUID=). See also \fB\-\-no\-part\-details\fP. +.RE +.sp +\fB\-s\fP, \fB\-\-match\-tag\fP \fItag\fP +.RS 4 +For each (specified) device, show only the tags that match \fItag\fP. It is possible to specify multiple \fB\-\-match\-tag\fP options. If no tag is specified, then all tokens are shown for all (specified) devices. In order to just refresh the cache without showing any tokens, use \fB\-\-match\-tag none\fP with no other options. +.RE +.sp +\fB\-S\fP, \fB\-\-size\fP \fIsize\fP +.RS 4 +Override the size of device/file (only useful with \fB\-\-probe\fP). +.RE +.sp +\fB\-t\fP, \fB\-\-match\-token\fP \fINAME=value\fP +.RS 4 +Search for block devices with tokens named \fINAME\fP that have the value \fIvalue\fP, and display any devices which are found. Common values for \fINAME\fP include \fBTYPE\fP, \fBLABEL\fP, and \fBUUID\fP. If there are no devices specified on the command line, all block devices will be searched; otherwise only the specified devices are searched. +.RE +.sp +\fB\-u\fP, \fB\-\-usages\fP \fIlist\fP +.RS 4 +Restrict the probing functions to the specified (comma\-separated) \fIlist\fP of "usage" types. Supported usage types are: filesystem, raid, crypto and other. The list items may be prefixed with "no" to specify the usage types which should be ignored. For example: +.sp +\fBblkid \-\-probe \-\-usages filesystem,other /dev/sda1\fP +.sp +probes for all filesystem and other (e.g., swap) formats, and +.sp +\fBblkid \-\-probe \-\-usages noraid /dev/sda1\fP +.sp +probes for all supported formats except RAIDs. This option is only useful together with \fB\-\-probe\fP. +.RE +.sp +\fB\-U\fP, \fB\-\-uuid\fP \fIuuid\fP +.RS 4 +Look up the device that uses this filesystem \fIuuid\fP. For more details see the \fB\-\-label\fP option. +.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 +If the specified device or device addressed by specified token (option \fB\-\-match\-token\fP) was found and it\(cqs possible to gather any information about the device, an exit status 0 is returned. Note the option \fB\-\-match\-tag\fP filters output tags, but it does not affect exit status. +.sp +If the specified token was not found, or no (specified) devices could be identified, or it is impossible to gather any information about the device identifiers or device content an exit status of 2 is returned. +.sp +For usage or other errors, an exit status of 4 is returned. +.sp +If an ambivalent probing result was detected by low\-level probing mode (\fB\-p\fP), an exit status of 8 is returned. +.SH "CONFIGURATION FILE" +.sp +The standard location of the \fI/etc/blkid.conf\fP config file can be overridden by the environment variable \fBBLKID_CONF\fP. The following options control the libblkid library: +.sp +\fISEND_UEVENT=<yes|not>\fP +.RS 4 +Sends uevent when \fI/dev/disk/by\-{label,uuid,partuuid,partlabel}/\fP symlink does not match with LABEL, UUID, PARTUUID or PARTLABEL on the device. Default is "yes". +.RE +.sp +\fICACHE_FILE=<path>\fP +.RS 4 +Overrides the standard location of the cache file. This setting can be overridden by the environment variable \fBBLKID_FILE\fP. Default is \fI/run/blkid/blkid.tab\fP, or \fI/etc/blkid.tab\fP on systems without a \fI/run\fP directory. +.RE +.sp +\fIEVALUATE=<methods>\fP +.RS 4 +Defines LABEL and UUID evaluation method(s). Currently, the libblkid library supports the "udev" and "scan" methods. More than one method may be specified in a comma\-separated list. Default is "udev,scan". The "udev" method uses udev \fI/dev/disk/by\-*\fP symlinks and the "scan" method scans all block devices from the \fI/proc/partitions\fP file. +.RE +.SH "ENVIRONMENT" +.sp +Setting \fILIBBLKID_DEBUG=all\fP enables debug output. +.SH "AUTHORS" +.sp +\fBblkid\fP was written by Andreas Dilger for libblkid and improved by Theodore Ts\(cqo and Karel Zak. +.SH "SEE ALSO" +.sp +\fBlibblkid\fP(3), +\fBfindfs\fP(8), +\fBlsblk\fP(8), +\fBwipefs\fP(8) +.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 \fBblkid\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/blkid.8.adoc b/misc-utils/blkid.8.adoc new file mode 100644 index 0000000..5e8002d --- /dev/null +++ b/misc-utils/blkid.8.adoc @@ -0,0 +1,192 @@ +//po4a: entry man manual +// Copyright 2000 Andreas Dilger (adilger@turbolinux.com) +// This file may be copied under the terms of the GNU Public License. += blkid(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: blkid +:underscore: _ + +== NAME + +blkid - locate/print block device attributes + +== SYNOPSIS + +*blkid* *--label* _label_ | *--uuid* _uuid_ + +*blkid* [*--no-encoding* *--garbage-collect* *--list-one* *--cache-file* _file_] [*--output* _format_] [*--match-tag* _tag_] [*--match-token* _NAME=value_] [_device_...] + +*blkid* *--probe* [*--offset* _offset_] [*--output* _format_] [*--size* _size_] [*--match-tag* _tag_] [*--match-types* _list_] [*--usages* _list_] [*--no-part-details*] _device_... + +*blkid* *--info* [*--output format*] [*--match-tag* _tag_] _device_... + +== DESCRIPTION + +The *blkid* program is the command-line interface to working with the *libblkid*(3) library. It can determine the type of content (e.g., filesystem or swap) that a block device holds, and also the attributes (tokens, NAME=value pairs) from the content metadata (e.g., LABEL or UUID fields). + +*It is recommended to use* *lsblk*(8) *command to get information about block devices, or lsblk --fs to get an overview of filesystems, or* *findmnt*(8) *to search in already mounted filesystems.* + +____ +*lsblk*(8) provides more information, better control on output formatting, easy to use in scripts and it does not require root permissions to get actual information. *blkid* reads information directly from devices and for non-root users it returns cached unverified information. *blkid* is mostly designed for system services and to test *libblkid*(3) functionality. +____ + +When _device_ is specified, tokens from only this device are displayed. It is possible to specify multiple _device_ arguments on the command line. If none is given, all partitions or unpartitioned devices which appear in _/proc/partitions_ are shown, if they are recognized. + +*blkid* has two main forms of operation: either searching for a device with a specific NAME=value pair, or displaying NAME=value pairs for one or more specified devices. + +For security reasons *blkid* silently ignores all devices where the probing result is ambivalent (multiple colliding filesystems are detected). The low-level probing mode (*-p*) provides more information and extra exit status in this case. It's recommended to use *wipefs*(8) to get a detailed overview and to erase obsolete stuff (magic strings) from the device. + +== OPTIONS + +The _size_ and _offset_ arguments may be followed by the multiplicative suffixes like KiB (=1024), MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB and YiB (the "iB" is optional, e.g., "K" has the same meaning as "KiB"), or the suffixes KB (=1000), MB (=1000*1000), and so on for GB, TB, PB, EB, ZB and YB. + +*-c*, *--cache-file* _cachefile_:: +Read from _cachefile_ instead of reading from the default cache file (see the *CONFIGURATION FILE* section for more details). If you want to start with a clean cache (i.e., don't report devices previously scanned but not necessarily available at this time), specify _/dev/null_. + +*-d*, *--no-encoding*:: +Don't encode non-printing characters. The non-printing characters are encoded by ^ and M- notation by default. Note that the *--output udev* output format uses a different encoding which cannot be disabled. + +*-D*, *--no-part-details*:: +Don't print information (PART_ENTRY_* tags) from partition table in low-level probing mode. + +*-g*, *--garbage-collect*:: +Perform a garbage collection pass on the blkid cache to remove devices which no longer exist. + +*-H*, *--hint* _setting_:: +Set probing hint. The hints are an optional way to force probing functions to +check, for example, another location. The currently supported is +"session_offset=_number_" to set session offset on multi-session UDF. + +*-i*, *--info*:: +Display information about I/O Limits (aka I/O topology). The 'export' output format is automatically enabled. This option can be used together with the *--probe* option. + +*-k*, *--list-filesystems*:: +List all known filesystems and RAIDs and exit. + +*-l*, *--list-one*:: +Look up only one device that matches the search parameter specified with the *--match-token* option. If there are multiple devices that match the specified search parameter, then the device with the highest priority is returned, and/or the first device found at a given priority (but see below note about udev). Device types in order of decreasing priority are: Device Mapper, EVMS, LVM, MD, and finally regular block devices. If this option is not specified, *blkid* will print all of the devices that match the search parameter. ++ +This option forces *blkid* to use udev when used for LABEL or UUID tokens in *--match-token*. The goal is to provide output consistent with other utils (like *mount*(8), etc.) on systems where the same tag is used for multiple devices. + +*-L*, *--label* _label_:: +Look up the device that uses this filesystem _label_; this is equal to **--list-one --output device --match-token LABEL=**__label__. This lookup method is able to reliably use /dev/disk/by-label udev symlinks (dependent on a setting in _/etc/blkid.conf_). Avoid using the symlinks directly; it is not reliable to use the symlinks without verification. The *--label* option works on systems with and without udev. ++ +Unfortunately, the original *blkid*(8) from e2fsprogs uses the *-L* option as a synonym for *-o list*. For better portability, use **-l -o device -t LABEL=**__label__ and *-o list* in your scripts rather than the *-L* option. + +*-n*, *--match-types* _list_:: +Restrict the probing functions to the specified (comma-separated) _list_ of superblock types (names). The list items may be prefixed with "no" to specify the types which should be ignored. For example: ++ +*blkid --probe --match-types vfat,ext3,ext4 /dev/sda1* ++ +probes for vfat, ext3 and ext4 filesystems, and ++ +*blkid --probe --match-types nominix /dev/sda1* ++ +probes for all supported formats except minix filesystems. This option is only useful together with *--probe*. + +*-o*, *--output* _format_:: +Use the specified output format. Note that the order of variables and devices is not fixed. See also option *-s*. The _format_ parameter may be: ++ +*full*;; +print all tags (the default) +*value*;; +print the value of the tags +*list*;; +print the devices in a user-friendly format; this output format is unsupported for low-level probing (*--probe* or *--info*). ++ +This output format is *DEPRECATED* in favour of the *lsblk*(8) command. +*device*;; +print the device name only; this output format is always enabled for the *--label* and *--uuid* options +*udev*;; +//TRANSLATORS: Please keep {underscore} untranslated. +print key="value" pairs for easy import into the udev environment; the keys are prefixed by ID_FS_ or ID_PART_ prefixes. The value may be modified to be safe for udev environment; allowed is plain ASCII, hex-escaping and valid UTF-8, everything else (including whitespaces) is replaced with '{underscore}'. The keys with __ENC_ postfix use hex-escaping for unsafe chars. ++ +The udev output returns the ID_FS_AMBIVALENT tag if more superblocks are detected, and ID_PART_ENTRY_* tags are always returned for all partitions including empty partitions. ++ +This output format is *DEPRECATED*. +*export*;; +print key=value pairs for easy import into the environment; this output format is automatically enabled when I/O Limits (*--info* option) are requested. ++ +The non-printing characters are encoded by ^ and M- notation and all potentially unsafe characters are escaped. + +*-O*, *--offset* _offset_:: +Probe at the given _offset_ (only useful with *--probe*). This option can be used together with the *--info* option. + +*-p*, *--probe*:: +Switch to low-level superblock probing mode (bypassing the cache). ++ +Note that low-level probing also returns information about partition table type (PTTYPE tag) and partitions (PART_ENTRY_* tags). The tag names produced by low-level probing are based on names used internally by libblkid and it may be different than when executed without *--probe* (for example PART_ENTRY_UUID= vs PARTUUID=). See also *--no-part-details*. + +*-s*, *--match-tag* _tag_:: +For each (specified) device, show only the tags that match _tag_. It is possible to specify multiple *--match-tag* options. If no tag is specified, then all tokens are shown for all (specified) devices. In order to just refresh the cache without showing any tokens, use *--match-tag none* with no other options. + +*-S*, *--size* _size_:: +Override the size of device/file (only useful with *--probe*). + +*-t*, *--match-token* _NAME=value_:: +Search for block devices with tokens named _NAME_ that have the value _value_, and display any devices which are found. Common values for _NAME_ include *TYPE*, *LABEL*, and *UUID*. If there are no devices specified on the command line, all block devices will be searched; otherwise only the specified devices are searched. + +*-u*, *--usages* _list_:: +Restrict the probing functions to the specified (comma-separated) _list_ of "usage" types. Supported usage types are: filesystem, raid, crypto and other. The list items may be prefixed with "no" to specify the usage types which should be ignored. For example: ++ +*blkid --probe --usages filesystem,other /dev/sda1* ++ +probes for all filesystem and other (e.g., swap) formats, and ++ +*blkid --probe --usages noraid /dev/sda1* ++ +probes for all supported formats except RAIDs. This option is only useful together with *--probe*. + +*-U*, *--uuid* _uuid_:: +Look up the device that uses this filesystem _uuid_. For more details see the *--label* option. + +include::man-common/help-version.adoc[] + +== EXIT STATUS + +If the specified device or device addressed by specified token (option *--match-token*) was found and it's possible to gather any information about the device, an exit status 0 is returned. Note the option *--match-tag* filters output tags, but it does not affect exit status. + +If the specified token was not found, or no (specified) devices could be identified, or it is impossible to gather any information about the device identifiers or device content an exit status of 2 is returned. + +For usage or other errors, an exit status of 4 is returned. + +If an ambivalent probing result was detected by low-level probing mode (*-p*), an exit status of 8 is returned. + +== CONFIGURATION FILE + +The standard location of the _/etc/blkid.conf_ config file can be overridden by the environment variable *BLKID_CONF*. The following options control the libblkid library: + +_SEND_UEVENT=<yes|not>_:: +Sends uevent when _/dev/disk/by-{label,uuid,partuuid,partlabel}/_ symlink does not match with LABEL, UUID, PARTUUID or PARTLABEL on the device. Default is "yes". + +_CACHE_FILE=<path>_:: +Overrides the standard location of the cache file. This setting can be overridden by the environment variable *BLKID_FILE*. Default is _/run/blkid/blkid.tab_, or _/etc/blkid.tab_ on systems without a _/run_ directory. + +_EVALUATE=<methods>_:: +Defines LABEL and UUID evaluation method(s). Currently, the libblkid library supports the "udev" and "scan" methods. More than one method may be specified in a comma-separated list. Default is "udev,scan". The "udev" method uses udev _/dev/disk/by-*_ symlinks and the "scan" method scans all block devices from the _/proc/partitions_ file. + +== ENVIRONMENT + +Setting _LIBBLKID_DEBUG=all_ enables debug output. + +== AUTHORS + +*blkid* was written by Andreas Dilger for libblkid and improved by Theodore Ts'o and Karel Zak. + +== SEE ALSO + +*libblkid*(3), +*findfs*(8), +*lsblk*(8), +*wipefs*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/blkid.c b/misc-utils/blkid.c new file mode 100644 index 0000000..6df4e07 --- /dev/null +++ b/misc-utils/blkid.c @@ -0,0 +1,1007 @@ +/* + * blkid.c - User command-line interface for libblkid + * + * Copyright (C) 2001 Andreas Dilger + * + * %Begin-Header% + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * %End-Header% + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <getopt.h> + +#define OUTPUT_FULL (1 << 0) +#define OUTPUT_VALUE_ONLY (1 << 1) +#define OUTPUT_DEVICE_ONLY (1 << 2) +#define OUTPUT_PRETTY_LIST (1 << 3) /* deprecated */ +#define OUTPUT_UDEV_LIST (1 << 4) /* deprecated */ +#define OUTPUT_EXPORT_LIST (1 << 5) + +#define BLKID_EXIT_NOTFOUND 2 /* token or device not found */ +#define BLKID_EXIT_OTHER 4 /* bad usage or other error */ +#define BLKID_EXIT_AMBIVAL 8 /* ambivalent low-level probing detected */ + +#include <blkid.h> + +#include "ismounted.h" + +#include "strutils.h" +#define OPTUTILS_EXIT_CODE BLKID_EXIT_OTHER /* exclusive_option() */ +#include "optutils.h" +#define CLOSE_EXIT_CODE BLKID_EXIT_OTHER /* close_stdout() */ +#include "closestream.h" + +#include "nls.h" +#include "ttyutils.h" + +#define XALLOC_EXIT_CODE BLKID_EXIT_OTHER /* x.*alloc(), xstrndup() */ +#include "xalloc.h" + +#include "sysfs.h" + +struct blkid_control { + int output; + uintmax_t offset; + uintmax_t size; + char *show[128]; + unsigned int + eval:1, + gc:1, + lookup:1, + lowprobe:1, + lowprobe_superblocks:1, + lowprobe_topology:1, + no_part_details:1, + raw_chars:1; +}; + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + + fputs(USAGE_HEADER, out); + fprintf(out, _( " %s --label <label> | --uuid <uuid>\n\n"), program_invocation_short_name); + fprintf(out, _( " %s [--cache-file <file>] [-ghlLv] [--output <format>] [--match-tag <tag>] \n" + " [--match-token <token>] [<dev> ...]\n\n"), program_invocation_short_name); + fprintf(out, _( " %s -p [--match-tag <tag>] [--offset <offset>] [--size <size>] \n" + " [--output <format>] <dev> ...\n\n"), program_invocation_short_name); + fprintf(out, _( " %s -i [--match-tag <tag>] [--output <format>] <dev> ...\n"), program_invocation_short_name); + fputs(USAGE_OPTIONS, out); + fputs(_( " -c, --cache-file <file> read from <file> instead of reading from the default\n" + " cache file (-c /dev/null means no cache)\n"), out); + fputs(_( " -d, --no-encoding don't encode non-printing characters\n"), out); + fputs(_( " -g, --garbage-collect garbage collect the blkid cache\n"), out); + fputs(_( " -o, --output <format> output format; can be one of:\n" + " value, device, export or full; (default: full)\n"), out); + fputs(_( " -k, --list-filesystems list all known filesystems/RAIDs and exit\n"), out); + fputs(_( " -s, --match-tag <tag> show specified tag(s) (default show all tags)\n"), out); + fputs(_( " -t, --match-token <token> find device with a specific token (NAME=value pair)\n"), out); + fputs(_( " -l, --list-one look up only first device with token specified by -t\n"), out); + fputs(_( " -L, --label <label> convert LABEL to device name\n"), out); + fputs(_( " -U, --uuid <uuid> convert UUID to device name\n"), out); + fputs( "\n", out); + fputs(_( "Low-level probing options:\n"), out); + fputs(_( " -p, --probe low-level superblocks probing (bypass cache)\n"), out); + fputs(_( " -i, --info gather information about I/O limits\n"), out); + fputs(_( " -H, --hint <value> set hint for probing function\n"), out); + fputs(_( " -S, --size <size> overwrite device size\n"), out); + fputs(_( " -O, --offset <offset> probe at the given offset\n"), out); + fputs(_( " -u, --usages <list> filter by \"usage\" (e.g. -u filesystem,raid)\n"), out); + fputs(_( " -n, --match-types <list> filter by filesystem type (e.g. -n vfat,ext3)\n"), out); + fputs(_( " -D, --no-part-details don't print info from partition table\n"), out); + + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(28)); + + fputs(USAGE_ARGUMENTS, out); + printf(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)")); + exit(EXIT_SUCCESS); +} + +/* + * This function does "safe" printing. It will convert non-printable + * ASCII characters using '^' and M- notation. + * + * If 'esc' is defined then escape all chars from esc by \. + */ +static void safe_print(const struct blkid_control *ctl, const char *cp, int len, + const char *esc) +{ + unsigned char ch; + + if (len < 0) + len = strlen(cp); + + while (len--) { + ch = *cp++; + if (!ctl->raw_chars) { + if (ch >= 128) { + fputs("M-", stdout); + ch -= 128; + } + if ((ch < 32) || (ch == 0x7f)) { + fputc('^', stdout); + ch ^= 0x40; /* ^@, ^A, ^B; ^? for DEL */ + + } else if (esc && strchr(esc, ch)) + fputc('\\', stdout); + } + fputc(ch, stdout); + } +} + +static int pretty_print_word(const char *str, int max_len, + int left_len, int overflow_nl) +{ + int len = strlen(str) + left_len; + int ret = 0; + + fputs(str, stdout); + if (overflow_nl && len > max_len) { + fputc('\n', stdout); + len = 0; + } else if (len > max_len) + ret = len - max_len; + do { + fputc(' ', stdout); + } while (len++ < max_len); + return ret; +} + +static void pretty_print_line(const char *device, const char *fs_type, + const char *label, const char *mtpt, + const char *uuid) +{ + static int device_len = 10, fs_type_len = 7; + static int label_len = 8, mtpt_len = 14; + static int term_width = -1; + int len, w; + + if (term_width < 0) { + term_width = get_terminal_width(80); + } + if (term_width > 80) { + term_width -= 80; + w = term_width / 10; + if (w > 8) + w = 8; + term_width -= 2*w; + label_len += w; + fs_type_len += w; + w = term_width/2; + device_len += w; + mtpt_len +=w; + } + + len = pretty_print_word(device, device_len, 0, 1); + len = pretty_print_word(fs_type, fs_type_len, len, 0); + len = pretty_print_word(label, label_len, len, 0); + pretty_print_word(mtpt, mtpt_len, len, 0); + + fputs(uuid, stdout); + fputc('\n', stdout); +} + +static void pretty_print_dev(blkid_dev dev) +{ + blkid_tag_iterate iter; + const char *type, *value, *devname; + const char *uuid = "", *fs_type = "", *label = ""; + int len, mount_flags; + char mtpt[80]; + int retval; + + if (dev == NULL) { + pretty_print_line("device", "fs_type", "label", + "mount point", "UUID"); + for (len=get_terminal_width(0)-1; len > 0; len--) + fputc('-', stdout); + fputc('\n', stdout); + return; + } + + devname = blkid_dev_devname(dev); + if (access(devname, F_OK)) + return; + + /* Get the uuid, label, type */ + iter = blkid_tag_iterate_begin(dev); + while (blkid_tag_next(iter, &type, &value) == 0) { + if (!strcmp(type, "UUID")) + uuid = value; + if (!strcmp(type, "TYPE")) + fs_type = value; + if (!strcmp(type, "LABEL")) + label = value; + } + blkid_tag_iterate_end(iter); + + /* Get the mount point */ + mtpt[0] = 0; + retval = check_mount_point(devname, &mount_flags, mtpt, sizeof(mtpt)); + if (retval == 0) { + const char *msg = NULL; + + if (mount_flags & MF_MOUNTED) { + if (!mtpt[0]) + msg = _("(mounted, mtpt unknown)"); + } else if (mount_flags & MF_BUSY) + msg = _("(in use)"); + else + msg = _("(not mounted)"); + + if (msg) + xstrncpy(mtpt, msg, sizeof(mtpt)); + } + + pretty_print_line(devname, fs_type, label, mtpt, uuid); +} + +static void print_udev_format(const char *name, const char *value) +{ + char enc[265], safe[256]; + size_t namelen = strlen(name); + + *safe = *enc = '\0'; + + if (!strcmp(name, "TYPE") + || !strcmp(name, "VERSION") + || !strcmp(name, "SYSTEM_ID") + || !strcmp(name, "PUBLISHER_ID") + || !strcmp(name, "APPLICATION_ID") + || !strcmp(name, "BOOT_SYSTEM_ID") + || !strcmp(name, "VOLUME_ID") + || !strcmp(name, "LOGICAL_VOLUME_ID") + || !strcmp(name, "VOLUME_SET_ID") + || !strcmp(name, "DATA_PREPARER_ID")) { + blkid_encode_string(value, enc, sizeof(enc)); + printf("ID_FS_%s=%s\n", name, enc); + + } else if (!strcmp(name, "UUID") || + !strncmp(name, "LABEL", 5) || + !strcmp(name, "UUID_SUB")) { + + blkid_safe_string(value, safe, sizeof(safe)); + printf("ID_FS_%s=%s\n", name, safe); + + blkid_encode_string(value, enc, sizeof(enc)); + printf("ID_FS_%s_ENC=%s\n", name, enc); + + } else if (!strcmp(name, "PTUUID")) { + printf("ID_PART_TABLE_UUID=%s\n", value); + + } else if (!strcmp(name, "PTTYPE")) { + printf("ID_PART_TABLE_TYPE=%s\n", value); + + } else if (!strcmp(name, "PART_ENTRY_NAME") || + !strcmp(name, "PART_ENTRY_TYPE")) { + + blkid_encode_string(value, enc, sizeof(enc)); + printf("ID_%s=%s\n", name, enc); + + } else if (!strncmp(name, "PART_ENTRY_", 11)) + printf("ID_%s=%s\n", name, value); + + else if (namelen >= 15 && ( + !strcmp(name + (namelen - 12), "_SECTOR_SIZE") || + !strcmp(name + (namelen - 8), "_IO_SIZE") || + !strcmp(name, "ALIGNMENT_OFFSET"))) + printf("ID_IOLIMIT_%s=%s\n", name, value); + else + printf("ID_FS_%s=%s\n", name, value); +} + +static int has_item(const struct blkid_control *ctl, const char *item) +{ + char * const *p; + + for (p = ctl->show; *p != NULL; p++) + if (!strcmp(item, *p)) + return 1; + return 0; +} + +static void print_value(const struct blkid_control *ctl, int num, + const char *devname, const char *value, + const char *name, size_t valsz) +{ + if (ctl->output & OUTPUT_VALUE_ONLY) { + fputs(value, stdout); + fputc('\n', stdout); + + } else if (ctl->output & OUTPUT_UDEV_LIST) { + print_udev_format(name, value); + + } else if (ctl->output & OUTPUT_EXPORT_LIST) { + if (num == 1 && devname) + printf("DEVNAME=%s\n", devname); + fputs(name, stdout); + fputs("=", stdout); + safe_print(ctl, value, valsz, " \\\"'$`<>"); + fputs("\n", stdout); + + } else { + if (num == 1 && devname) + printf("%s:", devname); + fputs(" ", stdout); + fputs(name, stdout); + fputs("=\"", stdout); + safe_print(ctl, value, valsz, "\"\\"); + fputs("\"", stdout); + } +} + +static void print_tags(const struct blkid_control *ctl, blkid_dev dev) +{ + blkid_tag_iterate iter; + const char *type, *value, *devname; + int num = 1; + static int first = 1; + + if (!dev) + return; + + if (ctl->output & OUTPUT_PRETTY_LIST) { + pretty_print_dev(dev); + return; + } + + devname = blkid_dev_devname(dev); + + if (ctl->output & OUTPUT_DEVICE_ONLY) { + printf("%s\n", devname); + return; + } + + iter = blkid_tag_iterate_begin(dev); + while (blkid_tag_next(iter, &type, &value) == 0) { + if (ctl->show[0] && !has_item(ctl, type)) + continue; + + if (num == 1 && !first && + (ctl->output & (OUTPUT_UDEV_LIST | OUTPUT_EXPORT_LIST))) + /* add extra line between output from more devices */ + fputc('\n', stdout); + + print_value(ctl, num++, devname, value, type, strlen(value)); + } + blkid_tag_iterate_end(iter); + + if (num > 1) { + if (!(ctl->output & (OUTPUT_VALUE_ONLY | OUTPUT_UDEV_LIST | + OUTPUT_EXPORT_LIST))) + printf("\n"); + first = 0; + } +} + + +static int append_str(char **res, size_t *sz, const char *a, const char *b) +{ + char *str = *res; + size_t asz = a ? strlen(a) : 0; + size_t bsz = b ? strlen(b) : 0; + size_t len = *sz + asz + bsz; + + if (!len) + return -1; + + *res = str = xrealloc(str, len + 1); + str += *sz; + + if (a) + str = mempcpy(str, a, asz); + if (b) + str = mempcpy(str, b, bsz); + + *str = '\0'; + *sz = len; + return 0; +} + +/* + * Compose and print ID_FS_AMBIVALENT for udev + */ +static int print_udev_ambivalent(blkid_probe pr) +{ + char *val = NULL; + size_t valsz = 0; + int count = 0, rc = -1; + + while (!blkid_do_probe(pr)) { + const char *usage_txt = NULL, *type = NULL, *version = NULL; + char enc[256]; + + blkid_probe_lookup_value(pr, "USAGE", &usage_txt, NULL); + blkid_probe_lookup_value(pr, "TYPE", &type, NULL); + blkid_probe_lookup_value(pr, "VERSION", &version, NULL); + + if (!usage_txt || !type) + continue; + + blkid_encode_string(usage_txt, enc, sizeof(enc)); + if (append_str(&val, &valsz, enc, ":")) + goto done; + + blkid_encode_string(type, enc, sizeof(enc)); + if (append_str(&val, &valsz, enc, version ? ":" : " ")) + goto done; + + if (version) { + blkid_encode_string(version, enc, sizeof(enc)); + if (append_str(&val, &valsz, enc, " ")) + goto done; + } + count++; + } + + if (count > 1) { + *(val + valsz - 1) = '\0'; /* rem tailing whitespace */ + printf("ID_FS_AMBIVALENT=%s\n", val); + rc = 0; + } +done: + free(val); + return rc; +} + +static int lowprobe_superblocks(blkid_probe pr, struct blkid_control *ctl) +{ + struct stat st; + int rc, fd = blkid_probe_get_fd(pr); + + if (fd < 0 || fstat(fd, &st)) + return -1; + + blkid_probe_enable_partitions(pr, 1); + + if (!S_ISCHR(st.st_mode) && blkid_probe_get_size(pr) <= 1024 * 1440 && + blkid_probe_is_wholedisk(pr)) { + /* + * check if the small disk is partitioned, if yes then + * don't probe for filesystems. + */ + blkid_probe_enable_superblocks(pr, 0); + + rc = blkid_do_fullprobe(pr); + if (rc < 0) + return rc; /* -1 = error, 1 = nothing, 0 = success */ + + if (blkid_probe_lookup_value(pr, "PTTYPE", NULL, NULL) == 0) + return 0; /* partition table detected */ + } + + if (!ctl->no_part_details) + blkid_probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS); + blkid_probe_enable_superblocks(pr, 1); + + return blkid_do_safeprobe(pr); +} + +static int lowprobe_topology(blkid_probe pr) +{ + /* enable topology probing only */ + blkid_probe_enable_topology(pr, 1); + + blkid_probe_enable_superblocks(pr, 0); + blkid_probe_enable_partitions(pr, 0); + + return blkid_do_fullprobe(pr); +} + +static int lowprobe_device(blkid_probe pr, const char *devname, + struct blkid_control *ctl) +{ + const char *data; + const char *name; + int nvals = 0, n, num = 1; + size_t len; + int fd; + int rc = 0; + static int first = 1; + + fd = open(devname, O_RDONLY|O_CLOEXEC|O_NONBLOCK); + if (fd < 0) { + warn(_("error: %s"), devname); + return BLKID_EXIT_NOTFOUND; + } + errno = 0; + if (blkid_probe_set_device(pr, fd, ctl->offset, ctl->size)) { + if (errno) + warn(_("error: %s"), devname); + goto done; + } + + if (ctl->lowprobe_topology) + rc = lowprobe_topology(pr); + if (rc >= 0 && ctl->lowprobe_superblocks) + rc = lowprobe_superblocks(pr, ctl); + if (rc < 0) + goto done; + + if (!rc) + nvals = blkid_probe_numof_values(pr); + + if (nvals && !first && ctl->output & (OUTPUT_UDEV_LIST | OUTPUT_EXPORT_LIST)) + /* add extra line between output from devices */ + fputc('\n', stdout); + + if (nvals && (ctl->output & OUTPUT_DEVICE_ONLY)) { + printf("%s\n", devname); + goto done; + } + + for (n = 0; n < nvals; n++) { + if (blkid_probe_get_value(pr, n, &name, &data, &len)) + continue; + if (ctl->show[0] && !has_item(ctl, name)) + continue; + len = strnlen(data, len); + print_value(ctl, num++, devname, data, name, len); + } + + if (first) + first = 0; + + if (nvals >= 1 && !(ctl->output & (OUTPUT_VALUE_ONLY | + OUTPUT_UDEV_LIST | OUTPUT_EXPORT_LIST))) + printf("\n"); +done: + if (rc == -2) { + if (ctl->output & OUTPUT_UDEV_LIST) + print_udev_ambivalent(pr); + else + warnx(_("%s: ambivalent result (probably more " + "filesystems on the device, use wipefs(8) " + "to see more details)"), + devname); + } + close(fd); + + if (rc == -2) + return BLKID_EXIT_AMBIVAL; /* ambivalent probing result */ + if (!nvals) + return BLKID_EXIT_NOTFOUND; /* nothing detected */ + + return 0; /* success */ +} + +/* converts comma separated list to BLKID_USAGE_* mask */ +static int list_to_usage(const char *list, int *flag) +{ + int mask = 0; + const char *word = NULL, *p = list; + + if (p && strncmp(p, "no", 2) == 0) { + *flag = BLKID_FLTR_NOTIN; + p += 2; + } + if (!p || !*p) + goto err; + while(p) { + word = p; + p = strchr(p, ','); + if (p) + p++; + if (!strncmp(word, "filesystem", 10)) + mask |= BLKID_USAGE_FILESYSTEM; + else if (!strncmp(word, "raid", 4)) + mask |= BLKID_USAGE_RAID; + else if (!strncmp(word, "crypto", 6)) + mask |= BLKID_USAGE_CRYPTO; + else if (!strncmp(word, "other", 5)) + mask |= BLKID_USAGE_OTHER; + else + goto err; + } + return mask; +err: + *flag = 0; + warnx(_("unknown keyword in -u <list> argument: '%s'"), + word ? word : list); + exit(BLKID_EXIT_OTHER); +} + +/* converts comma separated list to types[] */ +static char **list_to_types(const char *list, int *flag) +{ + int i; + const char *p = list; + char **res = NULL; + + if (p && strncmp(p, "no", 2) == 0) { + *flag = BLKID_FLTR_NOTIN; + p += 2; + } + if (!p || !*p) { + warnx(_("error: -u <list> argument is empty")); + goto err; + } + for (i = 1; p && (p = strchr(p, ',')); i++, p++); + + res = xcalloc(i + 1, sizeof(char *)); + p = *flag & BLKID_FLTR_NOTIN ? list + 2 : list; + i = 0; + + while(p) { + const char *word = p; + p = strchr(p, ','); + res[i++] = p ? xstrndup(word, p - word) : xstrdup(word); + if (p) + p++; + } + res[i] = NULL; + return res; +err: + *flag = 0; + free(res); + exit(BLKID_EXIT_OTHER); +} + +static void free_types_list(char *list[]) +{ + char **n; + + if (!list) + return; + for (n = list; *n; n++) + free(*n); + free(list); +} + +int main(int argc, char **argv) +{ + struct blkid_control ctl = { .output = OUTPUT_FULL, 0 }; + blkid_cache cache = NULL; + char **devices = NULL; + char *search_type = NULL, *search_value = NULL; + char *read = NULL, *hint = NULL; + int fltr_usage = 0; + char **fltr_type = NULL; + int fltr_flag = BLKID_FLTR_ONLYIN; + unsigned int numdev = 0, numtag = 0; + int err = BLKID_EXIT_OTHER; + unsigned int i; + int c; + + static const struct option longopts[] = { + { "cache-file", required_argument, NULL, 'c' }, + { "no-encoding", no_argument, NULL, 'd' }, + { "no-part-details", no_argument, NULL, 'D' }, + { "garbage-collect", no_argument, NULL, 'g' }, + { "output", required_argument, NULL, 'o' }, + { "list-filesystems", no_argument, NULL, 'k' }, + { "match-tag", required_argument, NULL, 's' }, + { "match-token", required_argument, NULL, 't' }, + { "list-one", no_argument, NULL, 'l' }, + { "label", required_argument, NULL, 'L' }, + { "uuid", required_argument, NULL, 'U' }, + { "probe", no_argument, NULL, 'p' }, + { "hint", required_argument, NULL, 'H' }, + { "info", no_argument, NULL, 'i' }, + { "size", required_argument, NULL, 'S' }, + { "offset", required_argument, NULL, 'O' }, + { "usages", required_argument, NULL, 'u' }, + { "match-types", required_argument, NULL, 'n' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { 'n','u' }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + strutils_set_exitcode(BLKID_EXIT_OTHER); + + while ((c = getopt_long (argc, argv, + "c:DdgH:hilL:n:ko:O:ps:S:t:u:U:w:Vv", longopts, NULL)) != -1) { + + err_exclusive_options(c, longopts, excl, excl_st); + + switch (c) { + case 'c': + read = optarg; + break; + case 'd': + ctl.raw_chars = 1; + break; + case 'D': + ctl.no_part_details = 1; + break; + case 'H': + hint = optarg; + break; + case 'L': + ctl.eval = 1; + search_value = xstrdup(optarg); + search_type = xstrdup("LABEL"); + break; + case 'n': + fltr_type = list_to_types(optarg, &fltr_flag); + break; + case 'u': + fltr_usage = list_to_usage(optarg, &fltr_flag); + break; + case 'U': + ctl.eval = 1; + search_value = xstrdup(optarg); + search_type = xstrdup("UUID"); + break; + case 'i': + ctl.lowprobe_topology = 1; + break; + case 'l': + ctl.lookup = 1; + break; + case 'g': + ctl.gc = 1; + break; + case 'k': + { + size_t idx = 0; + const char *name = NULL; + + while (blkid_superblocks_get_name(idx++, &name, NULL) == 0) + printf("%s\n", name); + exit(EXIT_SUCCESS); + } + case 'o': + if (!strcmp(optarg, "value")) + ctl.output = OUTPUT_VALUE_ONLY; + else if (!strcmp(optarg, "device")) + ctl.output = OUTPUT_DEVICE_ONLY; + else if (!strcmp(optarg, "list")) + ctl.output = OUTPUT_PRETTY_LIST; /* deprecated */ + else if (!strcmp(optarg, "udev")) + ctl.output = OUTPUT_UDEV_LIST; + else if (!strcmp(optarg, "export")) + ctl.output = OUTPUT_EXPORT_LIST; + else if (!strcmp(optarg, "full")) + ctl.output = 0; + else + errx(BLKID_EXIT_OTHER, _("unsupported output format %s"), optarg); + break; + case 'O': + ctl.offset = strtosize_or_err(optarg, _("invalid offset argument")); + break; + case 'p': + ctl.lowprobe_superblocks = 1; + break; + case 's': + if (numtag + 1 >= sizeof(ctl.show) / sizeof(*ctl.show)) { + warnx(_("Too many tags specified")); + errtryhelp(err); + } + ctl.show[numtag++] = optarg; + break; + case 'S': + ctl.size = strtosize_or_err(optarg, _("invalid size argument")); + break; + case 't': + if (search_type) { + warnx(_("Can only search for " + "one NAME=value pair")); + errtryhelp(err); + } + if (blkid_parse_tag_string(optarg, + &search_type, + &search_value)) { + warnx(_("-t needs NAME=value pair")); + errtryhelp(err); + } + break; + case 'V': + case 'v': + fprintf(stdout, _("%s from %s (libblkid %s, %s)\n"), + program_invocation_short_name, PACKAGE_STRING, + LIBBLKID_VERSION, LIBBLKID_DATE); + err = 0; + goto exit; + case 'w': + /* ignore - backward compatibility */ + break; + case 'h': + usage(); + break; + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (ctl.lowprobe_topology || ctl.lowprobe_superblocks) + ctl.lowprobe = 1; + + /* The rest of the args are device names */ + if (optind < argc) { + devices = xcalloc(argc - optind, sizeof(char *)); + while (optind < argc) { + char *dev = argv[optind++]; + struct stat sb; + + if (stat(dev, &sb) != 0) + continue; + else if (S_ISBLK(sb.st_mode)) + ; + else if (S_ISREG(sb.st_mode)) + ; + else if (S_ISCHR(sb.st_mode)) { + char buf[PATH_MAX]; + + if (!sysfs_chrdev_devno_to_devname( + sb.st_rdev, buf, sizeof(buf))) + continue; + if (strncmp(buf, "ubi", 3) != 0) + continue; + } else + continue; + + devices[numdev++] = dev; + } + + if (!numdev) { + /* only unsupported devices specified */ + err = BLKID_EXIT_NOTFOUND; + goto exit; + } + } + + /* convert LABEL/UUID lookup to evaluate request */ + if (ctl.lookup && ctl.output == OUTPUT_DEVICE_ONLY && search_type && + (!strcmp(search_type, "LABEL") || !strcmp(search_type, "UUID"))) { + ctl.eval = 1; + ctl.lookup = 0; + } + + if (!ctl.lowprobe && !ctl.eval && blkid_get_cache(&cache, read) < 0) + goto exit; + + if (ctl.gc) { + blkid_gc_cache(cache); + err = 0; + goto exit; + } + err = BLKID_EXIT_NOTFOUND; + + if (ctl.eval == 0 && (ctl.output & OUTPUT_PRETTY_LIST)) { + if (ctl.lowprobe) + errx(BLKID_EXIT_OTHER, + _("The low-level probing mode does not " + "support 'list' output format")); + pretty_print_dev(NULL); + } + + if (ctl.lowprobe) { + /* + * Low-level API + */ + blkid_probe pr; + + if (!numdev) + errx(BLKID_EXIT_OTHER, + _("The low-level probing mode " + "requires a device")); + + /* automatically enable 'export' format for I/O Limits */ + if (!ctl.output && ctl.lowprobe_topology) + ctl.output = OUTPUT_EXPORT_LIST; + + pr = blkid_new_probe(); + if (!pr) + goto exit; + if (hint && blkid_probe_set_hint(pr, hint, 0) != 0) { + warn(_("Failed to use probing hint: %s"), hint); + goto exit; + } + + if (ctl.lowprobe_superblocks) { + blkid_probe_set_superblocks_flags(pr, + BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID | + BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE | + BLKID_SUBLKS_USAGE | BLKID_SUBLKS_VERSION | + BLKID_SUBLKS_FSINFO); + + if (fltr_usage && + blkid_probe_filter_superblocks_usage(pr, fltr_flag, fltr_usage)) + goto exit; + + else if (fltr_type && + blkid_probe_filter_superblocks_type(pr, fltr_flag, fltr_type)) + goto exit; + } + + for (i = 0; i < numdev; i++) { + err = lowprobe_device(pr, devices[i], &ctl); + if (err) + break; + } + blkid_free_probe(pr); + } else if (ctl.eval) { + /* + * Evaluate API + */ + char *res = blkid_evaluate_tag(search_type, search_value, NULL); + if (res) { + err = 0; + printf("%s\n", res); + } + } else if (ctl.lookup) { + /* + * Classic (cache based) API + */ + blkid_dev dev; + + if (!search_type) + errx(BLKID_EXIT_OTHER, + _("The lookup option requires a " + "search type specified using -t")); + /* Load any additional devices not in the cache */ + for (i = 0; i < numdev; i++) + blkid_get_dev(cache, devices[i], BLKID_DEV_NORMAL); + + if ((dev = blkid_find_dev_with_tag(cache, search_type, + search_value))) { + print_tags(&ctl, dev); + err = 0; + } + /* If we didn't specify a single device, show all available devices */ + } else if (!numdev) { + blkid_dev_iterate iter; + blkid_dev dev; + + blkid_probe_all(cache); + + iter = blkid_dev_iterate_begin(cache); + blkid_dev_set_search(iter, search_type, search_value); + while (blkid_dev_next(iter, &dev) == 0) { + dev = blkid_verify(cache, dev); + if (!dev) + continue; + print_tags(&ctl, dev); + err = 0; + } + blkid_dev_iterate_end(iter); + /* Add all specified devices to cache (optionally display tags) */ + } else for (i = 0; i < numdev; i++) { + blkid_dev dev = blkid_get_dev(cache, devices[i], + BLKID_DEV_NORMAL); + + if (dev) { + if (search_type && + !blkid_dev_has_tag(dev, search_type, + search_value)) + continue; + print_tags(&ctl, dev); + err = 0; + } + } + +exit: + free(search_type); + free(search_value); + free_types_list(fltr_type); + if (!ctl.lowprobe && !ctl.eval) + blkid_put_cache(cache); + free(devices); + return err; +} diff --git a/misc-utils/cal.1 b/misc-utils/cal.1 new file mode 100644 index 0000000..cde26fe --- /dev/null +++ b/misc-utils/cal.1 @@ -0,0 +1,290 @@ +'\" t +.\" Title: cal +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-11-21 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "CAL" "1" "2023-11-21" "util\-linux 2.39.3" "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" +cal \- display a calendar +.SH "SYNOPSIS" +.sp +\fBcal\fP [options] [[[\fIday\fP] \fImonth\fP] \fIyear\fP] +.sp +\fBcal\fP [options] [\fItimestamp\fP|\fImonthname\fP] +.SH "DESCRIPTION" +.sp +\fBcal\fP displays a simple calendar. If no arguments are specified, the current month is displayed. +.sp +The \fImonth\fP may be specified as a number (1\-12), as a month name or as an abbreviated month name according to the current locales. +.sp +Two different calendar systems are used, Gregorian and Julian. These are nearly identical systems with Gregorian making a small adjustment to the frequency of leap years; this facilitates improved synchronization with solar events like the equinoxes. The Gregorian calendar reform was introduced in 1582, but its adoption continued up to 1923. By default \fBcal\fP uses the adoption date of 3 Sept 1752. From that date forward the Gregorian calendar is displayed; previous dates use the Julian calendar system. 11 days were removed at the time of adoption to bring the calendar in sync with solar events. So Sept 1752 has a mix of Julian and Gregorian dates by which the 2nd is followed by the 14th (the 3rd through the 13th are absent). +.sp +Optionally, either the proleptic Gregorian calendar or the Julian calendar may be used exclusively. See \fB\-\-reform\fP below. +.SH "OPTIONS" +.sp +\fB\-1\fP, \fB\-\-one\fP +.RS 4 +Display single month output. (This is the default.) +.RE +.sp +\fB\-3\fP, \fB\-\-three\fP +.RS 4 +Display three months spanning the date. +.RE +.sp +\fB\-n , \-\-months\fP \fInumber\fP +.RS 4 +Display \fInumber\fP of months, starting from the month containing the date. +.RE +.sp +\fB\-S, \-\-span\fP +.RS 4 +Display months spanning the date. +.RE +.sp +\fB\-s\fP, \fB\-\-sunday\fP +.RS 4 +Display Sunday as the first day of the week. +.RE +.sp +\fB\-m\fP, \fB\-\-monday\fP +.RS 4 +Display Monday as the first day of the week. +.RE +.sp +\fB\-v\fP, \fB\-\-vertical\fP +.RS 4 +Display using a vertical layout (aka \fBncal\fP(1) mode). +.RE +.sp +\fB\-\-iso\fP +.RS 4 +Display the proleptic Gregorian calendar exclusively. This option does not affect week numbers and the first day of the week. See \fB\-\-reform\fP below. +.RE +.sp +\fB\-j\fP, \fB\-\-julian\fP +.RS 4 +Use day\-of\-year numbering for all calendars. These are also called ordinal days. Ordinal days range from 1 to 366. This option does not switch from the Gregorian to the Julian calendar system, that is controlled by the \fB\-\-reform\fP option. +.sp +Sometimes Gregorian calendars using ordinal dates are referred to as Julian calendars. This can be confusing due to the many date related conventions that use Julian in their name: (ordinal) julian date, julian (calendar) date, (astronomical) julian date, (modified) julian date, and more. This option is named julian, because ordinal days are identified as julian by the POSIX standard. However, be aware that \fBcal\fP also uses the Julian calendar system. See \fBDESCRIPTION\fP above. +.RE +.sp +\fB\-\-reform\fP \fIval\fP +.RS 4 +This option sets the adoption date of the Gregorian calendar reform. Calendar dates previous to reform use the Julian calendar system. Calendar dates after reform use the Gregorian calendar system. The argument \fIval\fP can be: +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fI1752\fP \- sets 3 September 1752 as the reform date (default). This is when the Gregorian calendar reform was adopted by the British Empire. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fIgregorian\fP \- display Gregorian calendars exclusively. This special placeholder sets the reform date below the smallest year that \fBcal\fP can use; meaning all calendar output uses the Gregorian calendar system. This is called the proleptic Gregorian calendar, because dates prior to the calendar system\(cqs creation use extrapolated values. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fIiso\fP \- alias of \fIgregorian\fP. The ISO 8601 standard for the representation of dates and times in information interchange requires using the proleptic Gregorian calendar. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fIjulian\fP \- display Julian calendars exclusively. This special placeholder sets the reform date above the largest year that \fBcal\fP can use; meaning all calendar output uses the Julian calendar system. +.sp +See \fBDESCRIPTION\fP above. +.RE +.RE +.sp +\fB\-y\fP, \fB\-\-year\fP +.RS 4 +Display a calendar for the whole year. +.RE +.sp +\fB\-Y, \-\-twelve\fP +.RS 4 +Display a calendar for the next twelve months. +.RE +.sp +\fB\-w\fP, \fB\-\-week\fP[=\fInumber\fP] +.RS 4 +Display week numbers in the calendar (US or ISO\-8601). See the \fBNOTES\fP section for more details. +.RE +.sp +\fB\-\-color\fP[=\fIwhen\fP] +.RS 4 +Colorize the output. The optional argument \fIwhen\fP can be \fBauto\fP, \fBnever\fP or \fBalways\fP. If the \fIwhen\fP argument is omitted, it defaults to \fBauto\fP. The colors can be disabled; for the current built\-in default see the \fB\-\-help\fP output. See also the \fBCOLORS\fP section. +.RE +.sp +\fB\-c, \-\-columns\fP=\fIcolumns\fP +.RS 4 +Number of columns to use. \fBauto\fP uses as many as fit the terminal. +.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 "PARAMETERS" +.sp +\fBSingle digits\-only parameter (e.g., \*(Aqcal 2020\*(Aq)\fP +.RS 4 +Specifies the \fIyear\fP to be displayed; note the year must be fully specified: \fBcal 89\fP will not display a calendar for 1989. +.RE +.sp +\fBSingle string parameter (e.g., \*(Aqcal tomorrow\*(Aq or \*(Aqcal August\*(Aq)\fP +.RS 4 +Specifies \fItimestamp\fP or a \fImonth name\fP (or abbreviated name) according to the current locales. +.sp +The special placeholders are accepted when parsing timestamp, "now" may be used to refer to the current time, "today", "yesterday", "tomorrow" refer to of the current day, the day before or the next day, respectively. +.sp +The relative date specifications are also accepted, in this case "+" is evaluated to the current time plus the specified time span. Correspondingly, a time span that is prefixed with "\-" is evaluated to the current time minus the specified time span, for example \*(Aq+2days\*(Aq. Instead of prefixing the time span with "+" or "\-", it may also be suffixed with a space and the word "left" or "ago" (for example \*(Aq1 week ago\*(Aq). +.RE +.sp +\fBTwo parameters (e.g., \*(Aqcal 11 2020\*(Aq)\fP +.RS 4 +Denote the \fImonth\fP (1 \- 12) and \fIyear\fP. +.RE +.sp +\fBThree parameters (e.g., \*(Aqcal 25 11 2020\*(Aq)\fP +.RS 4 +Denote the \fIday\fP (1\-31), \fImonth and year\fP, and the day will be highlighted if the calendar is displayed on a terminal. If no parameters are specified, the current month\(cqs calendar is displayed. +.RE +.SH "NOTES" +.sp +A year starts on January 1. The first day of the week is determined by the locale or the \fB\-\-sunday\fP and \fB\-\-monday\fP options. +.sp +The week numbering depends on the choice of the first day of the week. If it is Sunday then the customary North American numbering is used, where 1 January is in week number 1. If it is Monday (\fB\-m\fP) then the ISO 8601 standard week numbering is used, where the first Thursday is in week number 1. +.SH "COLORS" +.sp +The output colorization is implemented by \fBterminal\-colors.d\fP(5) functionality. +Implicit coloring can be disabled by an empty file +.RS 3 +.ll -.6i +.sp +\fI/etc/terminal\-colors.d/cal.disable\fP +.br +.RE +.ll +.sp +for the \fBcal\fP command or for all tools by +.RS 3 +.ll -.6i +.sp +\fI/etc/terminal\-colors.d/disable\fP +.br +.RE +.ll +.sp +The user\-specific \fI$XDG_CONFIG_HOME/terminal\-colors.d\fP +or \fI$HOME/.config/terminal\-colors.d\fP overrides the global setting. +.sp +Note that the output colorization may be enabled by default, and in this case +\fIterminal\-colors.d\fP directories do not have to exist yet. +.sp +The logical color names supported by \fBcal\fP are: +.sp +\fBtoday\fP +.RS 4 +The current day. +.RE +.sp +\fBweeknumber\fP +.RS 4 +The number of the week. +.RE +.sp +\fBheader\fP +.RS 4 +The header of a month. +.RE +.sp +\fBworkday\fP +.RS 4 +Days that fall within the work\-week. +.RE +.sp +\fBweekend\fP +.RS 4 +Days that fall outside the work\-week. +.RE +.sp +For example: +.RS 3 +.ll -.6i +.sp +echo \-e \*(Aqweekend 35\(rsntoday 1;41\(rsnheader yellow\*(Aq > $HOME/.config/terminal\-colors.d/cal.scheme +.br +.RE +.ll +.SH "HISTORY" +.sp +A \fBcal\fP command appeared in Version 6 AT&T UNIX. +.SH "BUGS" +.sp +The default \fBcal\fP output uses 3 September 1752 as the Gregorian calendar reform date. The historical reform dates for the other locales, including its introduction in October 1582, are not implemented. +.sp +Alternative calendars, such as the Umm al\-Qura, the Solar Hijri, the Ge\(cqez, or the lunisolar Hindu, are not supported. +.SH "SEE ALSO" +.sp +\fBterminal\-colors.d\fP(5) +.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 \fBcal\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/cal.1.adoc b/misc-utils/cal.1.adoc new file mode 100644 index 0000000..fd508bc --- /dev/null +++ b/misc-utils/cal.1.adoc @@ -0,0 +1,193 @@ +//po4a: entry man manual +//// +Copyright (c) 1989, 1990, 1993 + The Regents of the University of California. All rights reserved. + +This code is derived from software contributed to Berkeley by +Kim Letkeman. + +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. +3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by the University of + California, Berkeley and its contributors. +4. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + + @(#)cal.1 8.1 (Berkeley) 6/6/93 +//// += cal(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: cal +:plus: + + +== NAME + +cal - display a calendar + +== SYNOPSIS + +*cal* [options] [[[_day_] _month_] _year_] + +*cal* [options] [_timestamp_|_monthname_] + +== DESCRIPTION + +*cal* displays a simple calendar. If no arguments are specified, the current month is displayed. + +The _month_ may be specified as a number (1-12), as a month name or as an abbreviated month name according to the current locales. + +Two different calendar systems are used, Gregorian and Julian. These are nearly identical systems with Gregorian making a small adjustment to the frequency of leap years; this facilitates improved synchronization with solar events like the equinoxes. The Gregorian calendar reform was introduced in 1582, but its adoption continued up to 1923. By default *cal* uses the adoption date of 3 Sept 1752. From that date forward the Gregorian calendar is displayed; previous dates use the Julian calendar system. 11 days were removed at the time of adoption to bring the calendar in sync with solar events. So Sept 1752 has a mix of Julian and Gregorian dates by which the 2nd is followed by the 14th (the 3rd through the 13th are absent). + +Optionally, either the proleptic Gregorian calendar or the Julian calendar may be used exclusively. See *--reform* below. + +== OPTIONS + +*-1*, *--one*:: +Display single month output. (This is the default.) + +*-3*, *--three*:: +Display three months spanning the date. + +*-n , --months* _number_:: +Display _number_ of months, starting from the month containing the date. + +*-S, --span*:: +Display months spanning the date. + +*-s*, *--sunday*:: +Display Sunday as the first day of the week. + +*-m*, *--monday*:: +Display Monday as the first day of the week. + +*-v*, *--vertical*:: +Display using a vertical layout (aka *ncal*(1) mode). + +*--iso*:: +Display the proleptic Gregorian calendar exclusively. This option does not affect week numbers and the first day of the week. See *--reform* below. + +*-j*, *--julian*:: +Use day-of-year numbering for all calendars. These are also called ordinal days. Ordinal days range from 1 to 366. This option does not switch from the Gregorian to the Julian calendar system, that is controlled by the *--reform* option. ++ +Sometimes Gregorian calendars using ordinal dates are referred to as Julian calendars. This can be confusing due to the many date related conventions that use Julian in their name: (ordinal) julian date, julian (calendar) date, (astronomical) julian date, (modified) julian date, and more. This option is named julian, because ordinal days are identified as julian by the POSIX standard. However, be aware that *cal* also uses the Julian calendar system. See *DESCRIPTION* above. + +*--reform* _val_:: +This option sets the adoption date of the Gregorian calendar reform. Calendar dates previous to reform use the Julian calendar system. Calendar dates after reform use the Gregorian calendar system. The argument _val_ can be: ++ +* _1752_ - sets 3 September 1752 as the reform date (default). This is when the Gregorian calendar reform was adopted by the British Empire. +* _gregorian_ - display Gregorian calendars exclusively. This special placeholder sets the reform date below the smallest year that *cal* can use; meaning all calendar output uses the Gregorian calendar system. This is called the proleptic Gregorian calendar, because dates prior to the calendar system's creation use extrapolated values. +* _iso_ - alias of _gregorian_. The ISO 8601 standard for the representation of dates and times in information interchange requires using the proleptic Gregorian calendar. +* _julian_ - display Julian calendars exclusively. This special placeholder sets the reform date above the largest year that *cal* can use; meaning all calendar output uses the Julian calendar system. ++ +See *DESCRIPTION* above. + +*-y*, *--year*:: +Display a calendar for the whole year. + +*-Y, --twelve*:: +Display a calendar for the next twelve months. + +*-w*, *--week*[=_number_]:: +Display week numbers in the calendar (US or ISO-8601). See the *NOTES* section for more details. + +*--color*[=_when_]:: +Colorize the output. The optional argument _when_ can be *auto*, *never* or *always*. If the _when_ argument is omitted, it defaults to *auto*. The colors can be disabled; for the current built-in default see the *--help* output. See also the *COLORS* section. + +*-c, --columns*=_columns_:: +Number of columns to use. *auto* uses as many as fit the terminal. + +include::man-common/help-version.adoc[] + +== PARAMETERS + +*Single digits-only parameter (e.g., 'cal 2020')*:: +Specifies the _year_ to be displayed; note the year must be fully specified: *cal 89* will not display a calendar for 1989. + +*Single string parameter (e.g., 'cal tomorrow' or 'cal August')*:: +Specifies _timestamp_ or a _month name_ (or abbreviated name) according to the current locales. ++ +The special placeholders are accepted when parsing timestamp, "now" may be used to refer to the current time, "today", "yesterday", "tomorrow" refer to of the current day, the day before or the next day, respectively. ++ +The relative date specifications are also accepted, in this case "{plus}" is evaluated to the current time plus the specified time span. Correspondingly, a time span that is prefixed with "-" is evaluated to the current time minus the specified time span, for example '{plus}2days'. Instead of prefixing the time span with "{plus}" or "-", it may also be suffixed with a space and the word "left" or "ago" (for example '1 week ago'). +//TRANSLATORS: Please keep {plus} untranslated. + +*Two parameters (e.g., 'cal 11 2020')*:: +Denote the _month_ (1 - 12) and _year_. + +*Three parameters (e.g., 'cal 25 11 2020')*:: +Denote the _day_ (1-31), _month and year_, and the day will be highlighted if the calendar is displayed on a terminal. If no parameters are specified, the current month's calendar is displayed. + +== NOTES + +A year starts on January 1. The first day of the week is determined by the locale or the *--sunday* and *--monday* options. + +The week numbering depends on the choice of the first day of the week. If it is Sunday then the customary North American numbering is used, where 1 January is in week number 1. If it is Monday (*-m*) then the ISO 8601 standard week numbering is used, where the first Thursday is in week number 1. + +include::man-common/colors.adoc[] +The logical color names supported by *cal* are: + +*today*:: +The current day. + +*weeknumber*:: +The number of the week. + +*header*:: +The header of a month. + +*workday*:: +Days that fall within the work-week. + +*weekend*:: +Days that fall outside the work-week. + +For example: +____ +echo -e 'weekend 35\ntoday 1;41\nheader yellow' > $HOME/.config/terminal-colors.d/cal.scheme +____ + +== HISTORY + +A *cal* command appeared in Version 6 AT&T UNIX. + +== BUGS + +The default *cal* output uses 3 September 1752 as the Gregorian calendar reform date. The historical reform dates for the other locales, including its introduction in October 1582, are not implemented. + +Alternative calendars, such as the Umm al-Qura, the Solar Hijri, the Ge'ez, or the lunisolar Hindu, are not supported. + +== SEE ALSO + +*terminal-colors.d*(5) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/cal.c b/misc-utils/cal.c new file mode 100644 index 0000000..21ae6c6 --- /dev/null +++ b/misc-utils/cal.c @@ -0,0 +1,1303 @@ +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kim Letkeman. + * + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + */ + +/* 1999-02-01 Jean-Francois Bignolles: added option '-m' to display + * monday as the first day of the week. + * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + * + * 2000-09-01 Michael Charles Pruznick <dummy@netwiz.net> + * Added "-3" option to print prev/next month with current. + * Added overridable default MONTHS_IN_ROW and "-1" option to + * get traditional output when -3 is the default. I hope that + * enough people will like -3 as the default that one day the + * product can be shipped that way. + * + * 2001-05-07 Pablo Saratxaga <pablo@mandrakesoft.com> + * Fixed the bugs with multi-byte charset (zg: cjk, utf-8) + * displaying. made the 'month year' ("%s %d") header translatable + * so it can be adapted to conventions used by different languages + * added support to read "first_weekday" locale information + * still to do: support for 'cal_direction' (will require a major + * rewrite of the displaying) and proper handling of RTL scripts + */ + +#include <sys/types.h> + +#include <ctype.h> +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <errno.h> + +#include "c.h" +#include "closestream.h" +#include "colors.h" +#include "nls.h" +#include "mbsalign.h" +#include "strutils.h" +#include "optutils.h" +#include "timeutils.h" +#include "ttyutils.h" +#include "xalloc.h" + +#define DOY_MONTH_WIDTH 27 /* -j month width */ +#define DOM_MONTH_WIDTH 20 /* month width */ + +enum { + CAL_COLOR_TODAY, + CAL_COLOR_HEADER, + CAL_COLOR_WEEKNUMBER, + CAL_COLOR_WORKDAY, + CAL_COLOR_WEEKEND +}; + +static const struct { const char * const scheme; const char * dflt; } colors[] = +{ + [CAL_COLOR_TODAY] = { "today", UL_COLOR_REVERSE }, + [CAL_COLOR_WEEKNUMBER] = { "weeknumber", UL_COLOR_REVERSE }, + [CAL_COLOR_HEADER] = { "header", "" }, + [CAL_COLOR_WORKDAY] = { "workday", "" }, + [CAL_COLOR_WEEKEND] = { "weekend", "" } +}; + +static inline void cal_enable_color(int id) +{ + color_scheme_enable(colors[id].scheme, colors[id].dflt); +} + +static inline const char *cal_get_color_sequence(int id) +{ + return color_scheme_get_sequence(colors[id].scheme, colors[id].dflt); +} + +static inline void cal_disable_color(int id) +{ + const char *seq = cal_get_color_sequence(id); + if (seq && seq[0]) + color_disable(); +} + +static inline const char *cal_get_color_disable_sequence(int id) +{ + const char *seq = cal_get_color_sequence(id); + if (seq && seq[0]) + return UL_COLOR_RESET; + else + return ""; +} + +#include "widechar.h" + +enum { + GREGORIAN = INT32_MIN, + ISO = INT32_MIN, + GB1752 = 1752, + DEFAULT_REFORM_YEAR = 1752, + JULIAN = INT32_MAX +}; + +enum { + SUNDAY = 0, + MONDAY, + TUESDAY, + WEDNESDAY, + THURSDAY, + FRIDAY, + SATURDAY, + DAYS_IN_WEEK, + NONEDAY +}; + +enum { + JANUARY = 1, + FEBRUARY, + MARCH, + APRIL, + MAY, + JUNE, + JULY, + AUGUST, + SEPTEMBER, + OCTOBER, + NOVEMBER, + DECEMBER +}; + +#define REFORMATION_MONTH SEPTEMBER +#define NUMBER_MISSING_DAYS 11 /* 11 day correction */ +#define YDAY_AFTER_MISSING 258 /* 14th in Sep 1752 */ + +#define MONTHS_IN_YEAR DECEMBER +#define DAYS_IN_MONTH 31 +#define MAXDAYS 42 /* slots in a month array */ +#define SPACE -1 /* used in day array */ + +#define SMALLEST_YEAR 1 + +#define DAY_LEN 3 /* 3 spaces per day */ +#define WEEK_LEN (DAYS_IN_WEEK * DAY_LEN) +#define MONTHS_IN_YEAR_ROW 3 /* month columns in year view */ +#define WNUM_LEN 3 + +#define FMT_ST_CHARS 300 /* 90 suffices in most locales */ + +static const int days_in_month[2][13] = { + {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, + {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, +}; + +enum { + WEEK_NUM_DISABLED = 0, + WEEK_NUM_MASK=0xff, + WEEK_NUM_ISO=0x100, + WEEK_NUM_US=0x200, +}; + +enum { + COLUMNS_MAX_THREE = -1, + COLUMNS_AUTO = -2, +}; + +/* utf-8 can have up to 6 bytes per char; and an extra byte for ending \0 */ +static char day_headings[(WEEK_LEN + 1) * 6 + 1]; + +struct cal_request { + int day; + int month; + int32_t year; + int week; + int start_month; +}; + +struct cal_control { + const char *full_month[MONTHS_IN_YEAR]; /* month names */ + const char *abbr_month[MONTHS_IN_YEAR]; /* abbreviated month names */ + const char *weekdays[DAYS_IN_WEEK]; /* day names */ + + int reform_year; /* Gregorian reform year */ + int colormode; /* day and week number highlight */ + int num_months; /* number of requested months */ + int span_months; /* span the date */ + int months_in_row; /* number of months horizontally in print out */ + int weekstart; /* day the week starts, often Sun or Mon */ + int weektype; /* WEEK_TYPE_{NONE,ISO,US} */ + size_t day_width; /* day width in characters in printout */ + size_t week_width; /* 7 * day_width + possible week num */ + size_t month_width; /* width of a month (vertical mode) */ + int gutter_width; /* spaces in between horizontal month outputs */ + struct cal_request req; /* the times user is interested */ + unsigned int julian:1, /* julian output */ + header_year:1, /* print year number */ + header_hint:1, /* does month name + year need two lines to fit */ + vertical:1; /* display the output in vertical */ +}; + +struct cal_month { + int days[MAXDAYS]; /* the day numbers, or SPACE */ + int weeks[MAXDAYS / DAYS_IN_WEEK]; + int month; + int32_t year; + struct cal_month *next; +}; +/* function prototypes */ +static int leap_year(const struct cal_control *ctl, int32_t year); +static int monthname_to_number(struct cal_control *ctl, const char *name); +static void weekdays_init(struct cal_control *ctl); +static void headers_init(struct cal_control *ctl); +static void cal_fill_month(struct cal_month *month, const struct cal_control *ctl); +static void cal_output_header(struct cal_month *month, const struct cal_control *ctl); +static void cal_output_months(struct cal_month *month, const struct cal_control *ctl); +static void cal_vert_output_months(struct cal_month *month, const struct cal_control *ctl); +static void monthly(const struct cal_control *ctl); +static void yearly(const struct cal_control *ctl); +static int day_in_year(const struct cal_control *ctl, int day, + int month, int32_t year); +static int day_in_week(const struct cal_control *ctl, int day, + int month, int32_t year); +static int week_number(int day, int month, int32_t year, const struct cal_control *ctl); +static int week_to_day(const struct cal_control *ctl); +static int center_str(const char *src, char *dest, size_t dest_size, size_t width); +static void center(const char *str, size_t len, int separate); +static int left_str(const char *src, char *dest, size_t dest_size, size_t width); +static void left(const char *str, size_t len, int separate); +static int parse_reform_year(const char *reform_year); +static void __attribute__((__noreturn__)) usage(void); + +#ifdef TEST_CAL +static time_t cal_time(time_t *t) +{ + char *str = getenv("CAL_TEST_TIME"); + + if (str) { + uint64_t x = strtou64_or_err(str, "failed to parse CAL_TEST_TIME"); + + *t = x; + return *t; + } + + return time(t); +} +#else +# define cal_time(t) time(t) +#endif + +int main(int argc, char **argv) +{ + struct tm local_time; + time_t now; + int ch = 0, yflag = 0, Yflag = 0, cols = COLUMNS_MAX_THREE; + + static struct cal_control ctl = { + .reform_year = DEFAULT_REFORM_YEAR, + .weekstart = SUNDAY, + .span_months = 0, + .colormode = UL_COLORMODE_UNDEF, + .weektype = WEEK_NUM_DISABLED, + .day_width = DAY_LEN, + .gutter_width = 2, + .req.day = 0, + .req.month = 0 + }; + + enum { + OPT_COLOR = CHAR_MAX + 1, + OPT_ISO, + OPT_REFORM + }; + + static const struct option longopts[] = { + {"one", no_argument, NULL, '1'}, + {"three", no_argument, NULL, '3'}, + {"sunday", no_argument, NULL, 's'}, + {"monday", no_argument, NULL, 'm'}, + {"julian", no_argument, NULL, 'j'}, + {"months", required_argument, NULL, 'n'}, + {"span", no_argument, NULL, 'S'}, + {"year", no_argument, NULL, 'y'}, + {"week", optional_argument, NULL, 'w'}, + {"color", optional_argument, NULL, OPT_COLOR}, + {"reform", required_argument, NULL, OPT_REFORM}, + {"iso", no_argument, NULL, OPT_ISO}, + {"version", no_argument, NULL, 'V'}, + {"twelve", no_argument, NULL, 'Y'}, + {"help", no_argument, NULL, 'h'}, + {"vertical", no_argument, NULL,'v'}, + {"columns", required_argument, NULL,'c'}, + {NULL, 0, NULL, 0} + }; + + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { 'Y','n','y' }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + +/* + * The traditional Unix cal utility starts the week at Sunday, + * while ISO 8601 starts at Monday. We read the start day from + * the locale database, which can be overridden with the + * -s (Sunday) or -m (Monday) options. + */ +#if HAVE_DECL__NL_TIME_WEEK_1STDAY + /* + * You need to use 2 locale variables to get the first day of the week. + * This is needed to support first_weekday=2 and first_workday=1 for + * the rare case where working days span across 2 weeks. + * This shell script shows the combinations and calculations involved: + * + * for LANG in en_US ru_RU fr_FR csb_PL POSIX; do + * printf "%s:\t%s + %s -1 = " $LANG $(locale week-1stday first_weekday) + * date -d"$(locale week-1stday) +$(($(locale first_weekday)-1))day" +%w + * done + * + * en_US: 19971130 + 1 -1 = 0 #0 = sunday + * ru_RU: 19971130 + 2 -1 = 1 + * fr_FR: 19971201 + 1 -1 = 1 + * csb_PL: 19971201 + 2 -1 = 2 + * POSIX: 19971201 + 7 -1 = 0 + */ + { + int wfd; + union { unsigned int word; char *string; } val; + val.string = nl_langinfo(_NL_TIME_WEEK_1STDAY); + + wfd = val.word; + wfd = day_in_week(&ctl, wfd % 100, (wfd / 100) % 100, + wfd / (100 * 100)); + ctl.weekstart = (wfd + *nl_langinfo(_NL_TIME_FIRST_WEEKDAY) - 1) % DAYS_IN_WEEK; + } +#endif + while ((ch = getopt_long(argc, argv, "13mjn:sSywYvc:Vh", longopts, NULL)) != -1) { + + err_exclusive_options(ch, longopts, excl, excl_st); + + switch(ch) { + case '1': + ctl.num_months = 1; + break; + case '3': + ctl.num_months = 3; + ctl.span_months = 1; + break; + case 's': + ctl.weekstart = SUNDAY; /* default */ + break; + case 'm': + ctl.weekstart = MONDAY; + break; + case 'j': + ctl.julian = 1; + ctl.day_width = DAY_LEN + 1; + break; + case 'y': + yflag = 1; + break; + case 'Y': + Yflag = 1; + break; + case 'n': + ctl.num_months = strtou32_or_err(optarg, + _("invalid month argument")); + break; + case 'S': + ctl.span_months = 1; + break; + case 'w': + if (optarg) { + ctl.req.week = strtos32_or_err(optarg, + _("invalid week argument")); + if (ctl.req.week < 1 || 54 < ctl.req.week) + errx(EXIT_FAILURE,_("illegal week value: use 1-54")); + } + ctl.weektype = WEEK_NUM_US; /* default per weekstart */ + break; + case OPT_COLOR: + ctl.colormode = UL_COLORMODE_AUTO; + if (optarg) + ctl.colormode = colormode_or_err(optarg, + _("unsupported color mode")); + break; + case OPT_REFORM: + ctl.reform_year = parse_reform_year(optarg); + break; + case OPT_ISO: + ctl.reform_year = ISO; + break; + case 'v': + ctl.vertical = 1; + break; + case 'c': + if (strcmp(optarg, "auto") == 0) + cols = COLUMNS_AUTO; + else + cols = strtosize_or_err(optarg, + _("failed to parse columns")); + break; + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + argc -= optind; + argv += optind; + + if (ctl.weektype) { + ctl.weektype = ctl.req.week & WEEK_NUM_MASK; + ctl.weektype |= (ctl.weekstart == MONDAY ? WEEK_NUM_ISO : WEEK_NUM_US); + ctl.week_width = (ctl.day_width * DAYS_IN_WEEK) + WNUM_LEN; + } else + ctl.week_width = ctl.day_width * DAYS_IN_WEEK; + /* + * The day_width includes the space between days, + * as there is no leading space, remove 1 + * */ + ctl.week_width -= 1; + + if (argc == 1 && !isdigit_string(*argv)) { + usec_t x; + /* cal <timestamp> */ + if (parse_timestamp(*argv, &x) == 0) + now = (time_t) (x / 1000000); + /* cal <monthname> */ + else if ((ctl.req.month = monthname_to_number(&ctl, *argv)) > 0) + cal_time(&now); /* this year */ + else + errx(EXIT_FAILURE, _("failed to parse timestamp or unknown month name: %s"), *argv); + argc = 0; + } else + cal_time(&now); + + localtime_r(&now, &local_time); + + switch(argc) { + case 3: + ctl.req.day = strtos32_or_err(*argv++, _("illegal day value")); + if (ctl.req.day < 1 || DAYS_IN_MONTH < ctl.req.day) + errx(EXIT_FAILURE, _("illegal day value: use 1-%d"), DAYS_IN_MONTH); + /* fallthrough */ + case 2: + if (isdigit(**argv)) + ctl.req.month = strtos32_or_err(*argv++, _("illegal month value: use 1-12")); + else { + ctl.req.month = monthname_to_number(&ctl, *argv); + if (ctl.req.month < 0) + errx(EXIT_FAILURE, _("unknown month name: %s"), *argv); + argv++; + } + if (ctl.req.month < 1 || MONTHS_IN_YEAR < ctl.req.month) + errx(EXIT_FAILURE, _("illegal month value: use 1-12")); + /* fallthrough */ + case 1: + ctl.req.year = strtos32_or_err(*argv++, _("illegal year value")); + if (ctl.req.year < SMALLEST_YEAR) + errx(EXIT_FAILURE, _("illegal year value: use positive integer")); + if (ctl.req.year == JULIAN) + errx(EXIT_FAILURE, _("illegal year value")); + if (ctl.req.day) { + int dm = days_in_month[leap_year(&ctl, ctl.req.year)] + [ctl.req.month]; + if (ctl.req.day > dm) + errx(EXIT_FAILURE, _("illegal day value: use 1-%d"), dm); + ctl.req.day = day_in_year(&ctl, ctl.req.day, + ctl.req.month, ctl.req.year); + } else if ((int32_t) (local_time.tm_year + 1900) == ctl.req.year) { + ctl.req.day = local_time.tm_yday + 1; + } + if (!ctl.req.month && !ctl.req.week) { + ctl.req.month = local_time.tm_mon + 1; + if (!ctl.num_months) + yflag = 1; + } + break; + case 0: + ctl.req.day = local_time.tm_yday + 1; + ctl.req.year = local_time.tm_year + 1900; + if (!ctl.req.month) + ctl.req.month = local_time.tm_mon + 1; + break; + default: + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + + if (0 < ctl.req.week) { + int yday = week_to_day(&ctl); + int leap = leap_year(&ctl, ctl.req.year); + int m = 1; + + if (yday < 1) + errx(EXIT_FAILURE, _("illegal week value: year %d " + "doesn't have week %d"), + ctl.req.year, ctl.req.week); + while (m <= DECEMBER && yday > days_in_month[leap][m]) + yday -= days_in_month[leap][m++]; + if (DECEMBER < m && ctl.weektype & WEEK_NUM_ISO) { + /* In some years (e.g. 2010 in ISO mode) it's possible + * to have a remnant of week 53 starting the year yet + * the year in question ends during 52, in this case + * we're assuming that early remnant is being referred + * to if 53 is given as argument. */ + if (ctl.req.week != week_number(31, DECEMBER, ctl.req.year - 1, &ctl)) + errx(EXIT_FAILURE, + _("illegal week value: year %d " + "doesn't have week %d"), + ctl.req.year, ctl.req.week); + } + if (!ctl.req.month) + ctl.req.month = MONTHS_IN_YEAR < m ? 1 : m; + } + + weekdays_init(&ctl); + headers_init(&ctl); + + if (colors_init(ctl.colormode, "cal") == 0) { + /* disable */ + ctl.req.day = 0; + ctl.weektype &= ~WEEK_NUM_MASK; + } + + if (yflag || Yflag) { + ctl.gutter_width = 3; + if (!ctl.num_months) + ctl.num_months = MONTHS_IN_YEAR; + if (yflag) { + ctl.req.start_month = 1; /* start from Jan */ + ctl.header_year = 1; /* print year number */ + } + } + + if (ctl.vertical) + ctl.gutter_width = 1; + + if (ctl.num_months > 1 && ctl.months_in_row == 0) { + ctl.months_in_row = MONTHS_IN_YEAR_ROW; /* default */ + + if (cols > 0) + ctl.months_in_row = cols; + else if (isatty(STDOUT_FILENO)) { + int w, mw, extra, new_n; + + w = get_terminal_width(80); + mw = ctl.julian ? DOY_MONTH_WIDTH : DOM_MONTH_WIDTH; + + if (w < mw) + w = mw; + + extra = ((w / mw) - 1) * ctl.gutter_width; + new_n = (w - extra) / mw; + + switch (cols) { + case COLUMNS_MAX_THREE: + if (new_n < MONTHS_IN_YEAR_ROW) + ctl.months_in_row = new_n > 0 ? new_n : 1; + break; + case COLUMNS_AUTO: + ctl.months_in_row = new_n > 0 ? new_n : 1; + break; + } + } + } else if (!ctl.months_in_row) + ctl.months_in_row = 1; + + if (!ctl.num_months) + ctl.num_months = 1; /* display at least one month */ + + if (yflag || Yflag) + yearly(&ctl); + else + monthly(&ctl); + + return EXIT_SUCCESS; +} + +/* leap year -- account for gregorian reformation in 1752 */ +static int leap_year(const struct cal_control *ctl, int32_t year) +{ + if (year <= ctl->reform_year) + return !(year % 4); + + return ( !(year % 4) && (year % 100) ) || !(year % 400); +} + +static void init_monthnames(struct cal_control *ctl) +{ + size_t i; + + if (ctl->full_month[0] != NULL) + return; /* already initialized */ + + for (i = 0; i < MONTHS_IN_YEAR; i++) + ctl->full_month[i] = nl_langinfo(ALTMON_1 + i); +} + +static void init_abbr_monthnames(struct cal_control *ctl) +{ + size_t i; + + if (ctl->abbr_month[0] != NULL) + return; /* already initialized */ + + for (i = 0; i < MONTHS_IN_YEAR; i++) + ctl->abbr_month[i] = nl_langinfo(_NL_ABALTMON_1 + i); +} + +static int monthname_to_number(struct cal_control *ctl, const char *name) +{ + size_t i; + + init_monthnames(ctl); + for (i = 0; i < MONTHS_IN_YEAR; i++) + if (strcasecmp(ctl->full_month[i], name) == 0) + return i + 1; + + init_abbr_monthnames(ctl); + for (i = 0; i < MONTHS_IN_YEAR; i++) + if (strcasecmp(ctl->abbr_month[i], name) == 0) + return i + 1; + + return -EINVAL; +} + +static void weekdays_init(struct cal_control *ctl) +{ + size_t wd; + int i; + + for (i = 0; i < DAYS_IN_WEEK; i++) { + wd = (i + ctl->weekstart) % DAYS_IN_WEEK; + ctl->weekdays[i] = nl_langinfo(ABDAY_1 + wd); + } +} +static void headers_init(struct cal_control *ctl) +{ + size_t i; + char *cur_dh = day_headings; + char tmp[FMT_ST_CHARS]; + int year_len; + + year_len = snprintf(tmp, sizeof(tmp), "%04d", ctl->req.year); + + if (year_len < 0 || (size_t)year_len >= sizeof(tmp)) { + /* XXX impossible error */ + return; + } + + 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 (space_left <= (ctl->day_width - 1)) + break; + cur_dh += center_str(ctl->weekdays[i], cur_dh, + space_left, ctl->day_width - 1); + } + + init_monthnames(ctl); + + for (i = 0; i < MONTHS_IN_YEAR; i++) { + /* The +1 after year_len is space in between month and year. */ + if (ctl->week_width < strlen(ctl->full_month[i]) + year_len) + ctl->header_hint = 1; + } +} + +static void cal_fill_month(struct cal_month *month, const struct cal_control *ctl) +{ + int first_week_day = day_in_week(ctl, 1, month->month, month->year); + int month_days; + int i, j, weeklines = 0; + + if (ctl->julian) + j = day_in_year(ctl, 1, month->month, month->year); + else + j = 1; + month_days = j + days_in_month[leap_year(ctl, month->year)][month->month]; + + /* True when Sunday is not first day in the output week. */ + if (ctl->weekstart) { + first_week_day -= ctl->weekstart; + if (first_week_day < 0) + first_week_day = DAYS_IN_WEEK - ctl->weekstart; + month_days += ctl->weekstart - 1; + } + + /* Fill day array. */ + for (i = 0; i < MAXDAYS; i++) { + if (0 < first_week_day) { + month->days[i] = SPACE; + first_week_day--; + continue; + } + if (j < month_days) { + if (month->year == ctl->reform_year && + month->month == REFORMATION_MONTH && + (j == 3 || j == 247)) + j += NUMBER_MISSING_DAYS; + month->days[i] = j; + j++; + continue; + } + month->days[i] = SPACE; + weeklines++; + } + + /* Add week numbers */ + if (ctl->weektype) { + int weeknum = week_number(1, month->month, month->year, ctl); + weeklines = MAXDAYS / DAYS_IN_WEEK - weeklines / DAYS_IN_WEEK; + for (i = 0; i < MAXDAYS / DAYS_IN_WEEK; i++) { + if (0 < weeklines) { + if (52 < weeknum) + weeknum = week_number(month->days[i * DAYS_IN_WEEK], month->month, month->year, ctl); + month->weeks[i] = weeknum++; + } else + month->weeks[i] = SPACE; + weeklines--; + } + } +} + +static void cal_output_header(struct cal_month *month, const struct cal_control *ctl) +{ + char out[FMT_ST_CHARS]; + struct cal_month *i; + + cal_enable_color(CAL_COLOR_HEADER); + + if (ctl->header_hint || ctl->header_year) { + for (i = month; i; i = i->next) { + snprintf(out, sizeof(out), "%s", ctl->full_month[i->month - 1]); + center(out, ctl->week_width, i->next == NULL ? 0 : ctl->gutter_width); + } + if (!ctl->header_year) { + fputc('\n', stdout); + for (i = month; i; i = i->next) { + snprintf(out, sizeof(out), "%04d", i->year); + center(out, ctl->week_width, i->next == NULL ? 0 : ctl->gutter_width); + } + } + } else { + for (i = month; i; i = i->next) { + snprintf(out, sizeof(out), "%s %04d", ctl->full_month[i->month - 1], i->year); + center(out, ctl->week_width, i->next == NULL ? 0 : ctl->gutter_width); + } + } + fputc('\n', stdout); + for (i = month; i; i = i->next) { + if (ctl->weektype) { + if (ctl->julian) + printf("%*s%s", (int)ctl->day_width - 1, "", day_headings); + else + printf("%*s%s", (int)ctl->day_width, "", day_headings); + } else + fputs(day_headings, stdout); + if (i->next != NULL) + printf("%*s", ctl->gutter_width, ""); + } + cal_disable_color(CAL_COLOR_HEADER); + fputc('\n', stdout); +} + +static void cal_vert_output_header(struct cal_month *month, + const struct cal_control *ctl) +{ + char out[FMT_ST_CHARS]; + struct cal_month *m; + int month_width; + + month_width = ctl->day_width * (MAXDAYS / DAYS_IN_WEEK); + + /* Padding for the weekdays */ + printf("%*s", (int)ctl->day_width + 1, ""); + + if (ctl->header_hint || ctl->header_year) { + for (m = month; m; m = m->next) { + snprintf(out, sizeof(out), "%s", ctl->full_month[m->month - 1]); + left(out, month_width, ctl->gutter_width); + } + if (!ctl->header_year) { + fputc('\n', stdout); + /* Padding for the weekdays */ + printf("%*s", (int)ctl->day_width + 1, ""); + + for (m = month; m; m = m->next) { + snprintf(out, sizeof(out), "%04d", m->year); + left(out, month_width, ctl->gutter_width); + } + } + } else { + for (m = month; m; m = m->next) { + snprintf(out, sizeof(out), "%s %04d", ctl->full_month[m->month - 1], m->year); + left(out, month_width, ctl->gutter_width); + } + } + fputc('\n', stdout); +} + +#define fput_seq(_s) do { if ((_s) && *(_s)) fputs((_s), stdout); } while(0) + +static void cal_output_months(struct cal_month *month, const struct cal_control *ctl) +{ + int reqday, week_line, d; + int skip; + struct cal_month *i; + int firstwork = ctl->weekstart == SUNDAY ? 1 : 0; /* first workday in week */ + + /* Let's keep sequence cached rather than search it for each day */ + const char *seq_wo_start = cal_get_color_sequence(CAL_COLOR_WORKDAY); + const char *seq_wo_end = cal_get_color_disable_sequence(CAL_COLOR_WORKDAY); + const char *seq_we_start = cal_get_color_sequence(CAL_COLOR_WEEKEND); + const char *seq_we_end = cal_get_color_disable_sequence(CAL_COLOR_WEEKEND); + + for (week_line = 0; week_line < MAXDAYS / DAYS_IN_WEEK; week_line++) { + for (i = month; i; i = i->next) { + /* Determine the day that should be highlighted. */ + reqday = 0; + if (i->month == ctl->req.month && i->year == ctl->req.year) { + if (ctl->julian) + reqday = ctl->req.day; + else + reqday = ctl->req.day + 1 - + day_in_year(ctl, 1, i->month, + i->year); + } + + if (ctl->weektype) { + if (0 < i->weeks[week_line]) { + if ((ctl->weektype & WEEK_NUM_MASK) == i->weeks[week_line]) + printf("%s%2d%s", + cal_get_color_sequence(CAL_COLOR_WEEKNUMBER), + i->weeks[week_line], + cal_get_color_disable_sequence(CAL_COLOR_WEEKNUMBER)); + else + printf("%2d", i->weeks[week_line]); + } else + printf("%2s", ""); + skip = ctl->day_width; + } else + /* First day of the week is one char narrower than the other days, + * unless week number is printed. */ + skip = ctl->day_width - 1; + + for (d = DAYS_IN_WEEK * week_line; + d < DAYS_IN_WEEK * week_line + DAYS_IN_WEEK; d++) { + + int workday = d >= DAYS_IN_WEEK * week_line + firstwork && + d <= DAYS_IN_WEEK * week_line + firstwork + 4; + + if (0 < i->days[d]) { + fput_seq(workday ? seq_wo_start : seq_we_start); + + if (reqday == i->days[d]) + printf("%*s%s%*d%s", + skip - (ctl->julian ? 3 : 2), + "", cal_get_color_sequence(CAL_COLOR_TODAY), (ctl->julian ? 3 : 2), + i->days[d], cal_get_color_disable_sequence(CAL_COLOR_TODAY)); + else + printf("%*d", skip, i->days[d]); + + fput_seq(workday ? seq_wo_end : seq_we_end); + } else + printf("%*s", skip, ""); + + if (skip < (int)ctl->day_width) + skip++; + } + if (i->next != NULL) + printf("%*s", ctl->gutter_width, ""); + } + if (i == NULL) + fputc('\n', stdout); + } +} + +static void +cal_vert_output_months(struct cal_month *month, const struct cal_control *ctl) +{ + int i, reqday, week, d; + int skip; + struct cal_month *m; + + skip = ctl->day_width; + for (i = 0; i < DAYS_IN_WEEK; i++) { + left(ctl->weekdays[i], ctl->day_width - 1, 0); + for (m = month; m; m = m->next) { + reqday = 0; + if (m->month == ctl->req.month && m->year == ctl->req.year) { + if (ctl->julian) { + reqday = ctl->req.day; + } else { + reqday = ctl->req.day + 1 - + day_in_year(ctl, 1, m->month, m->year); + } + } + for (week = 0; week < MAXDAYS / DAYS_IN_WEEK; week++) { + d = i + DAYS_IN_WEEK * week; + if (0 < m->days[d]) { + if (reqday == m->days[d]) { + printf("%*s%s%*d%s", + skip - (ctl->julian ? 3 : 2), + "", + cal_get_color_sequence(CAL_COLOR_TODAY), + (ctl->julian ? 3 : 2), + m->days[d], + cal_get_color_disable_sequence(CAL_COLOR_TODAY)); + } else { + printf("%*d", skip, m->days[d]); + } + } else { + printf("%*s", skip, ""); + } + skip = ctl->day_width; + } + if (m->next != NULL) + printf("%*s", ctl->gutter_width, ""); + } + fputc('\n', stdout); + } + if (!ctl->weektype) + return; + + printf("%*s", (int)ctl->day_width - 1, ""); + for (m = month; m; m = m->next) { + for (week = 0; week < MAXDAYS / DAYS_IN_WEEK; week++) { + if (0 < m->weeks[week]) { + if ((ctl->weektype & WEEK_NUM_MASK) == m->weeks[week]) + printf("%s%*d%s", + cal_get_color_sequence(CAL_COLOR_WEEKNUMBER), + skip - (ctl->julian ? 3 : 2), + m->weeks[week], + cal_get_color_disable_sequence(CAL_COLOR_WEEKNUMBER)); + else + printf("%*d", skip, m->weeks[week]); + } else + printf("%*s", skip, ""); + } + if (m->next != NULL) + printf("%*s", ctl->gutter_width, ""); + } + fputc('\n', stdout); +} + + +static void monthly(const struct cal_control *ctl) +{ + struct cal_month *m, *ms; + int i, rows, month = ctl->req.start_month ? ctl->req.start_month : ctl->req.month; + int32_t year = ctl->req.year; + + /* cal -3, cal -Y --span, etc. */ + if (ctl->span_months) { + int new_month = month - ctl->num_months / 2; + if (new_month < 1) { + new_month *= -1; + year -= (new_month / MONTHS_IN_YEAR) + 1; + + if (new_month > MONTHS_IN_YEAR) + new_month %= MONTHS_IN_YEAR; + month = MONTHS_IN_YEAR - new_month; + } else + month = new_month; + } + + ms = xcalloc(ctl->months_in_row, sizeof(*ms)); + + for (i = 0; i < ctl->months_in_row - 1; i++) + ms[i].next = &ms[i + 1]; + + rows = (ctl->num_months - 1) / ctl->months_in_row; + for (i = 0; i < rows + 1 ; i++){ + if (i == rows && ctl->num_months % ctl->months_in_row > 0) + for (int n = (ctl->num_months % ctl->months_in_row) - 1; n < ctl->months_in_row; n++) + ms[n].next = NULL; + + for (m = ms; m; m = m->next){ + m->month = month++; + m->year = year; + if (MONTHS_IN_YEAR < month) { + year++; + month = 1; + } + cal_fill_month(m, ctl); + } + if (ctl->vertical) { + if (i > 0) + fputc('\n', stdout); /* Add a line between row */ + + cal_vert_output_header(ms, ctl); + cal_vert_output_months(ms, ctl); + } else { + cal_output_header(ms, ctl); + cal_output_months(ms, ctl); + } + } + free(ms); +} + +static void yearly(const struct cal_control *ctl) +{ + size_t year_width; + + year_width = (size_t) ctl->months_in_row * ctl->week_width + + ((size_t) ctl->months_in_row - 1) * ctl->gutter_width; + + if (ctl->header_year) { + char out[FMT_ST_CHARS]; + + snprintf(out, sizeof(out), "%04d", ctl->req.year); + center(out, year_width, 0); + fputs("\n\n", stdout); + } + monthly(ctl); +} + +/* + * day_in_year -- + * return the 1 based day number within the year + */ +static int day_in_year(const struct cal_control *ctl, + int day, int month, int32_t year) +{ + int i, leap; + + leap = leap_year(ctl, year); + for (i = 1; i < month; i++) + day += days_in_month[leap][i]; + return day; +} + +/* + * day_in_week + * return the 0 based day number for any date from 1 Jan. 1 to + * 31 Dec. 9999. Assumes the Gregorian reformation eliminates + * 3 Sep. 1752 through 13 Sep. 1752, and returns invalid weekday + * during the period of 11 days. + */ +static int day_in_week(const struct cal_control *ctl, int day, + int month, int32_t year) +{ + /* + * The magic constants in the reform[] array are, in a simplified + * sense, the remaining days after slicing into one week periods the total + * days from the beginning of the year to the target month. That is, + * weeks + reform[] days gets us to the target month. The exception is, + * that for the months past February 'DOY - 1' must be used. + * + * DoY (Day of Year): total days to the target month + * + * Month 1 2 3 4 5 6 7 8 9 10 11 12 + * DoY 0 31 59 90 120 151 181 212 243 273 304 334 + * DoY % 7 0 3 + * DoY - 1 % 7 - -- 2 5 0 3 5 1 4 6 2 4 + * reform[] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 }; + * + * Note: these calculations are for non leap years. + */ + static const int reform[] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 }; + static const int old[] = { 5, 1, 0, 3, 5, 1, 3, 6, 2, 4, 0, 2 }; + + if (year != ctl->reform_year + 1) + year -= month < MARCH; + else + year -= (month < MARCH) + 14; + if (ctl->reform_year < year + || (year == ctl->reform_year && REFORMATION_MONTH < month) + || (year == ctl->reform_year + && month == REFORMATION_MONTH && 13 < day)) { + return ((int64_t) year + (year / 4) + - (year / 100) + (year / 400) + + reform[month - 1] + day) % DAYS_IN_WEEK; + } + if (year < ctl->reform_year + || (year == ctl->reform_year && month < REFORMATION_MONTH) + || (year == ctl->reform_year && month == REFORMATION_MONTH && day < 3)) + return ((int64_t) year + year / 4 + old[month - 1] + day) + % DAYS_IN_WEEK; + return NONEDAY; +} + +/* + * week_number + * return the week number of a given date, 1..54. + * Supports ISO-8601 and North American modes. + * Day may be given as Julian day of the year mode, in which + * case the month is disregarded entirely. + */ +static int week_number(int day, int month, int32_t year, const struct cal_control *ctl) +{ + int fday = 0, yday; + const int wday = day_in_week(ctl, 1, JANUARY, year); + + if (ctl->weektype & WEEK_NUM_ISO) + fday = wday + (wday >= FRIDAY ? -2 : 5); + else { + /* WEEK_NUM_US: Jan 1 is always First week, that may + * begin previous year. That means there is very seldom + * more than 52 weeks, */ + fday = wday + 6; + } + /* For julian dates the month can be set to 1, the global julian + * variable cannot be relied upon here, because we may recurse + * internally for 31.12. which would not work. */ + if (day > DAYS_IN_MONTH) + month = JANUARY; + + yday = day_in_year(ctl, day, month, year); + if (year == ctl->reform_year && yday >= YDAY_AFTER_MISSING) + fday -= NUMBER_MISSING_DAYS; + + /* Last year is last year */ + if (yday + fday < DAYS_IN_WEEK) + return week_number(31, DECEMBER, year - 1, ctl); + + /* Or it could be part of the next year. The reformation year had less + * days than 365 making this check invalid, but reformation year ended + * on Sunday and in week 51, so it's ok here. */ + if (ctl->weektype == WEEK_NUM_ISO && yday >= 363 + && day_in_week(ctl, day, month, year) >= MONDAY + && day_in_week(ctl, day, month, year) <= WEDNESDAY + && day_in_week(ctl, 31, DECEMBER, year) >= MONDAY + && day_in_week(ctl, 31, DECEMBER, year) <= WEDNESDAY) + return week_number(1, JANUARY, year + 1, ctl); + + return (yday + fday) / DAYS_IN_WEEK; +} + +/* + * week_to_day + * return the yday of the first day in a given week inside + * the given year. This may be something other than Monday + * for ISO-8601 modes. For North American numbering this + * always returns a Sunday. + */ +static int week_to_day(const struct cal_control *ctl) +{ + int yday, wday; + + wday = day_in_week(ctl, 1, JANUARY, ctl->req.year); + yday = ctl->req.week * DAYS_IN_WEEK - wday; + + if (ctl->req.year == ctl->reform_year && yday >= YDAY_AFTER_MISSING) + yday += NUMBER_MISSING_DAYS; + + if (ctl->weektype & WEEK_NUM_ISO) + yday -= (wday >= FRIDAY ? -2 : 5); + else + yday -= 6; /* WEEK_NUM_US */ + if (yday <= 0) + return 1; + + return yday; +} + +/* + * Center string, handling multibyte characters appropriately. + * In addition if the string is too large for the width it's truncated. + * The number of trailing spaces may be 1 less than the number of leading spaces. + */ +static int center_str(const char* src, char* dest, + size_t dest_size, size_t width) +{ + return mbsalign(src, dest, dest_size, &width, + MBS_ALIGN_CENTER, MBA_UNIBYTE_FALLBACK); +} + +static void center(const char *str, size_t len, int separate) +{ + char lineout[FMT_ST_CHARS]; + + center_str(str, lineout, ARRAY_SIZE(lineout), len); + fputs(lineout, stdout); + + if (separate) + printf("%*s", separate, ""); +} +static int left_str(const char* src, char* dest, + size_t dest_size, size_t width) +{ + return mbsalign(src, dest, dest_size, &width, + MBS_ALIGN_LEFT, MBA_UNIBYTE_FALLBACK); +} + +static void left(const char *str, size_t len, int separate) +{ + char lineout[FMT_ST_CHARS]; + + left_str(str, lineout, sizeof(lineout), len); + fputs(lineout, stdout); + + if (separate) + printf("%*s", separate, ""); +} + +static int parse_reform_year(const char *reform_year) +{ + size_t i; + + struct reform { + char *name; + int val; + }; + + struct reform years[] = { + {"gregorian", GREGORIAN}, + {"iso", ISO}, + {"1752", GB1752}, + {"julian", JULIAN}, + }; + + for (i = 0; i < ARRAY_SIZE(years); i++) { + if (strcasecmp(reform_year, years[i].name) == 0) + return years[i].val; + } + errx(EXIT_FAILURE, "invalid --reform value: '%s'", reform_year); +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] [[[day] month] year]\n"), program_invocation_short_name); + fprintf(out, _(" %s [options] <timestamp|monthname>\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Display a calendar, or some part of it.\n"), out); + fputs(_("Without any arguments, display the current month.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -1, --one show only a single month (default)\n"), out); + fputs(_(" -3, --three show three months spanning the date\n"), out); + fputs(_(" -n, --months <num> show num months starting with date's month\n"), out); + fputs(_(" -S, --span span the date when displaying multiple months\n"), out); + fputs(_(" -s, --sunday Sunday as first day of week\n"), out); + fputs(_(" -m, --monday Monday as first day of week\n"), out); + fputs(_(" -j, --julian use day-of-year for all calendars\n"), out); + fputs(_(" --reform <val> Gregorian reform date (1752|gregorian|iso|julian)\n"), out); + fputs(_(" --iso alias for --reform=iso\n"), out); + fputs(_(" -y, --year show the whole year\n"), out); + fputs(_(" -Y, --twelve show the next twelve months\n"), out); + fputs(_(" -w, --week[=<num>] show US or ISO-8601 week numbers\n"), out); + fputs(_(" -v, --vertical show day vertically instead of line\n"), out); + fputs(_(" -c, --columns <width> amount of columns to use\n"), out); + fprintf(out, + _(" --color[=<when>] colorize messages (%s, %s or %s)\n"), "auto", "always", "never"); + fprintf(out, + " %s\n", USAGE_COLORS_DEFAULT); + + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(23)); + printf(USAGE_MAN_TAIL("cal(1)")); + + exit(EXIT_SUCCESS); +} diff --git a/misc-utils/fadvise.1 b/misc-utils/fadvise.1 new file mode 100644 index 0000000..9d26d41 --- /dev/null +++ b/misc-utils/fadvise.1 @@ -0,0 +1,102 @@ +'\" t +.\" Title: fadvise +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-11-21 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "FADVISE" "1" "2023-11-21" "util\-linux 2.39.3" "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" +fadvise \- utility to use the posix_fadvise system call +.SH "SYNOPSIS" +.sp +\fBfadvise\fP [\fB\-a\fP \fIadvice\fP] [\fB\-o\fP \fIoffset\fP] [\fB\-l\fP \fIlength\fP] \fIfilename\fP +.sp +\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 +that is for predeclaring an access pattern for file data. +.SH "OPTIONS" +.sp +\fB\-d\fP, \fB\-\-fd\fP \fIfile\-descriptor\fP +.RS 4 +Apply the advice to the file specified with the file descriptor instead +of open a file specified with a file name. +.RE +.sp +\fB\-a\fP, \fB\-\-advice\fP \fIadvice\fP +.RS 4 +See the command output with \fB\-\-help\fP option for available values for +advice. If this option is omitted, "dontneed" is used as default advice. +.RE +.sp +\fB\-o\fP, \fB\-\-offset\fP \fIoffset\fP +.RS 4 +Specifies the beginning offset of the range, in bytes. +If this option is omitted, 0 is used as default advice. +.RE +.sp +\fB\-l\fP, \fB\-\-length\fP \fIlength\fP +.RS 4 +Specifies the length of the range, in bytes. +If this option is omitted, 0 is used as default advice. +.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 +\fBfadvise\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 +\fBposix_fadvise\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 \fBfadvise\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/fadvise.1.adoc b/misc-utils/fadvise.1.adoc new file mode 100644 index 0000000..dffbeaa --- /dev/null +++ b/misc-utils/fadvise.1.adoc @@ -0,0 +1,68 @@ +//po4a: entry man manual += fadvise(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: fadvise + +== NAME + +fadvise - utility to use the posix_fadvise system call + +== SYNOPSIS + +*fadvise* [*-a* _advice_] [*-o* _offset_] [*-l* _length_] _filename_ + +*fadvise* [*-a* _advice_] [*-o* _offset_] [*-l* _length_] -d _file-descriptor_ + +== DESCRIPTION + +*fadvise* is a simple command wrapping posix_fadvise system call +that is for predeclaring an access pattern for file data. + +== OPTIONS + +*-d*, *--fd* _file-descriptor_:: +Apply the advice to the file specified with the file descriptor instead +of open a file specified with a file name. + +*-a*, *--advice* _advice_:: +See the command output with *--help* option for available values for +advice. If this option is omitted, "dontneed" is used as default advice. + +*-o*, *--offset* _offset_:: +Specifies the beginning offset of the range, in bytes. +If this option is omitted, 0 is used as default advice. + +*-l*, *--length* _length_:: +Specifies the length of the range, in bytes. +If this option is omitted, 0 is used as default advice. + +include::man-common/help-version.adoc[] + +== EXIT STATUS + +*fadvise* has the following exit status values: + +*0*:: +success +*1*:: +unspecified failure + +== AUTHORS + +mailto:yamato@redhat.com[Masatake YAMATO] + +== SEE ALSO + +*posix_fadvise*(2) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] + diff --git a/misc-utils/fadvise.c b/misc-utils/fadvise.c new file mode 100644 index 0000000..9606fb4 --- /dev/null +++ b/misc-utils/fadvise.c @@ -0,0 +1,162 @@ +/* + * fadvise - utility to use the posix_fadvise(2) + * + * Copyright (C) 2022 Red Hat, Inc. All rights reserved. + * Written by Masatake YAMATO <yamato@redhat.com> + * + * 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 <fcntl.h> +#include <getopt.h> +#include <stdbool.h> +#include <stdio.h> + +#include "c.h" +#include "nls.h" +#include "strutils.h" + +static const struct advice { + const char *name; + int num; +} advices [] = { + { "normal", POSIX_FADV_NORMAL, }, + { "sequential", POSIX_FADV_SEQUENTIAL, }, + { "random", POSIX_FADV_RANDOM, }, + { "noreuse", POSIX_FADV_NOREUSE, }, + { "willneeded", POSIX_FADV_WILLNEED, }, + { "dontneed", POSIX_FADV_DONTNEED, }, +}; + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + size_t i; + + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] file\n"), program_invocation_short_name); + fprintf(out, _(" %s [options] --fd|-d file-descriptor\n"), program_invocation_short_name); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -a, --advice <advice> applying advice to the file (default: \"dontneed\")\n"), out); + fputs(_(" -l, --length <num> length for range operations, in bytes\n"), out); + fputs(_(" -o, --offset <num> offset for range operations, in bytes\n"), out); + + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(23)); + + fputs(_("\nAvailable values for advice:\n"), out); + for (i = 0; i < ARRAY_SIZE(advices); i++) { + fprintf(out, " %s\n", + advices[i].name); + } + + printf(USAGE_MAN_TAIL("fadvise(1)")); + + exit(EXIT_SUCCESS); +} + +int main(int argc, char ** argv) +{ + int c; + int rc; + bool do_close = false; + + int fd = -1; + off_t offset = 0; + off_t len = 0; + int advice = POSIX_FADV_DONTNEED; + + static const struct option longopts[] = { + { "advice", required_argument, NULL, 'a' }, + { "fd", required_argument, NULL, 'd' }, + { "length", required_argument, NULL, 'l' }, + { "offset", required_argument, NULL, 'o' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 }, + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + while ((c = getopt_long (argc, argv, "a:d:hl:o:V", longopts, NULL)) != -1) { + switch (c) { + case 'a': + advice = -1; + for (size_t i = 0; i < ARRAY_SIZE(advices); i++) { + if (strcmp(optarg, advices[i].name) == 0) { + advice = advices[i].num; + break; + } + } + if (advice == -1) + errx(EXIT_FAILURE, "invalid advice argument: '%s'", optarg); + break; + case 'd': + fd = strtos32_or_err(optarg, + _("invalid fd argument")); + break; + case 'l': + len = strtosize_or_err(optarg, + _("invalid length argument")); + break; + case 'o': + offset = strtosize_or_err(optarg, + _("invalid offset argument")); + break; + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (optind == argc && fd == -1) { + warnx(_("no file specified")); + errtryhelp(EXIT_FAILURE); + } + + if (argc - optind > 0 && fd != -1) { + warnx(_("specify either file descriptor or file name")); + errtryhelp(EXIT_FAILURE); + } + + if (argc - optind > 1) { + warnx(_("specify one file descriptor or file name")); + errtryhelp(EXIT_FAILURE); + } + + if (fd == -1) { + fd = open(argv[optind], O_RDONLY); + if (fd < 0) + err(EXIT_FAILURE, _("cannot open %s"), argv[optind]); + do_close = true; + } + + rc = posix_fadvise(fd, + offset, len, + advice); + if (rc != 0) + warnx(_("failed to advise: %s"), strerror(rc)); + + if (do_close) + close(fd); + + return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/misc-utils/fincore.1 b/misc-utils/fincore.1 new file mode 100644 index 0000000..1d168e0 --- /dev/null +++ b/misc-utils/fincore.1 @@ -0,0 +1,97 @@ +'\" t +.\" Title: fincore +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-12-01 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "FINCORE" "1" "2023-12-01" "util\-linux 2.39.3" "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" +fincore \- count pages of file contents in core +.SH "SYNOPSIS" +.sp +\fBfincore\fP [options] \fIfile\fP... +.SH "DESCRIPTION" +.sp +\fBfincore\fP counts pages of file contents being resident in memory (in core), and reports the numbers. If an error occurs during counting, then an error message is printed to the stderr and \fBfincore\fP continues processing the rest of files listed in a command line. +.sp +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. +.SH "OPTIONS" +.sp +\fB\-n\fP, \fB\-\-noheadings\fP +.RS 4 +Do not print a header line in status output. +.RE +.sp +\fB\-b\fP, \fB\-\-bytes\fP +.RS 4 +Print the sizes in bytes rather than in a human\-readable format. +.sp +By default, the unit, sizes are expressed in, is byte, and unit prefixes are in +power of 2^10 (1024). Abbreviations of symbols are exhibited truncated in order +to reach a better readability, by exhibiting alone the first letter of them; +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\-o\fP, \fB\-\-output\fP \fIlist\fP +.RS 4 +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\-r\fP, \fB\-\-raw\fP +.RS 4 +Produce output in raw format. All potentially unsafe characters are hex\-escaped (\(rsx<code>). +.RE +.sp +\fB\-J\fP, \fB\-\-json\fP +.RS 4 +Use JSON output format. +.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 "AUTHORS" +.sp +.MTO "yamato\(atredhat.com" "Masatake YAMATO" "" +.SH "SEE ALSO" +.sp +\fBmincore\fP(2), +\fBgetpagesize\fP(2), +\fBgetconf\fP(1p) +.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 \fBfincore\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/fincore.1.adoc b/misc-utils/fincore.1.adoc new file mode 100644 index 0000000..54ed236 --- /dev/null +++ b/misc-utils/fincore.1.adoc @@ -0,0 +1,65 @@ +//po4a: entry man manual +//// +Copyright 2017 Red Hat, Inc. + +This file may be copied under the terms of the GNU Public License. +//// += fincore(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: fincore +:plus: + + +== NAME + +fincore - count pages of file contents in core + +== SYNOPSIS + +*fincore* [options] _file_... + +== DESCRIPTION + +*fincore* counts pages of file contents being resident in memory (in core), and reports the numbers. If an error occurs during counting, then an error message is printed to the stderr and *fincore* continues processing the rest of files listed in a command line. + +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. + +== OPTIONS + +*-n*, *--noheadings*:: +Do not print a header line in status output. + +*-b*, *--bytes*:: +include::man-common/in-bytes.adoc[] + +*-o*, *--output* _list_:: +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. + +*-r*, *--raw*:: +Produce output in raw format. All potentially unsafe characters are hex-escaped (\x<code>). + +*-J*, *--json*:: +Use JSON output format. + +include::man-common/help-version.adoc[] + +== AUTHORS + +mailto:yamato@redhat.com[Masatake YAMATO] + +== SEE ALSO + +*mincore*(2), +*getpagesize*(2), +*getconf*(1p) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/fincore.c b/misc-utils/fincore.c new file mode 100644 index 0000000..ead6f7a --- /dev/null +++ b/misc-utils/fincore.c @@ -0,0 +1,413 @@ +/* + * fincore - count pages of file contents in core + * + * Copyright (C) 2017 Red Hat, Inc. All rights reserved. + * Written by Masatake YAMATO <yamato@redhat.com> + * + * 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 <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> +#include <getopt.h> +#include <stdio.h> +#include <string.h> + +#include "c.h" +#include "nls.h" +#include "closestream.h" +#include "xalloc.h" +#include "strutils.h" + +#include "libsmartcols.h" + +/* For large files, mmap is called in iterative way. + Window is the unit of vma prepared in each mmap + calling. + + Window size depends on page size. + e.g. 128MB on x86_64. ( = N_PAGES_IN_WINDOW * 4096 ). */ +#define N_PAGES_IN_WINDOW ((size_t)(32 * 1024)) + + +struct colinfo { + const char *name; + double whint; + int flags; + const char *help; +}; + +enum { + COL_PAGES, + COL_SIZE, + COL_FILE, + COL_RES +}; + +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 int columns[ARRAY_SIZE(infos) * 2] = {-1}; +static size_t ncolumns; + +struct fincore_control { + const size_t pagesize; + + struct libscols_table *tb; /* output */ + + unsigned int bytes : 1, + noheadings : 1, + raw : 1, + json : 1; +}; + + +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 int get_column_id(int num) +{ + assert(num >= 0); + assert((size_t) num < ncolumns); + assert(columns[num] < (int) ARRAY_SIZE(infos)); + return columns[num]; +} + +static const struct colinfo *get_column_info(int num) +{ + return &infos[ get_column_id(num) ]; +} + +static int add_output_data(struct fincore_control *ctl, + const char *name, + off_t file_size, + off_t count_incore) +{ + size_t i; + char *tmp; + struct libscols_line *ln; + + assert(ctl); + assert(ctl->tb); + + ln = scols_table_new_line(ctl->tb, NULL); + if (!ln) + err(EXIT_FAILURE, _("failed to allocate output line")); + + for (i = 0; i < ncolumns; i++) { + int rc = 0; + + switch(get_column_id(i)) { + case COL_FILE: + rc = scols_line_set_data(ln, i, 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); + else + tmp = size_to_human_string(SIZE_SUFFIX_1LETTER, file_size); + rc = scols_line_refer_data(ln, i, tmp); + break; + default: + return -EINVAL; + } + + if (rc) + err(EXIT_FAILURE, _("failed to add output data")); + } + + return 0; +} + +static int do_mincore(struct fincore_control *ctl, + void *window, const size_t len, + const char *name, + off_t *count_incore) +{ + 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); + return -errno; + } + + while (n > 0) + { + if (vec[--n] & 0x1) + { + vec[n] = 0; + (*count_incore)++; + } + } + + return 0; +} + +static int fincore_fd (struct fincore_control *ctl, + int fd, + const char *name, + off_t file_size, + off_t *count_incore) +{ + size_t window_size = N_PAGES_IN_WINDOW * ctl->pagesize; + off_t file_offset, len; + int rc = 0; + + for (file_offset = 0; file_offset < file_size; file_offset += len) { + void *window = NULL; + + len = file_size - file_offset; + if (len >= (off_t) 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); + break; + } + + rc = do_mincore(ctl, window, len, name, count_incore); + if (rc) + break; + + munmap (window, len); + } + + return rc; +} + +/* + * 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) +{ + int fd; + int rc = 0; + + if ((fd = open (name, O_RDONLY)) < 0) { + warn(_("failed to open: %s"), name); + return -errno; + } + + if (fstat (fd, sb) < 0) { + warn(_("failed to do fstat: %s"), name); + close (fd); + return -errno; + } + + if (S_ISDIR(sb->st_mode)) + rc = 1; /* ignore */ + + else if (sb->st_size) + rc = fincore_fd(ctl, fd, name, sb->st_size, count_incore); + + close (fd); + return rc; +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + size_t i; + + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] file...\n"), program_invocation_short_name); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -J, --json use JSON output format\n"), out); + 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(_(" -r, --raw use raw output format\n"), out); + + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(23)); + + fprintf(out, USAGE_COLUMNS); + + for (i = 0; i < ARRAY_SIZE(infos); i++) + fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help)); + + printf(USAGE_MAN_TAIL("fincore(1)")); + + exit(EXIT_SUCCESS); +} + +int main(int argc, char ** argv) +{ + int c; + size_t i; + int rc = EXIT_SUCCESS; + char *outarg = NULL; + + struct fincore_control ctl = { + .pagesize = getpagesize() + }; + + static const struct option longopts[] = { + { "bytes", no_argument, NULL, 'b' }, + { "noheadings", no_argument, NULL, 'n' }, + { "output", required_argument, NULL, 'o' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { "json", no_argument, NULL, 'J' }, + { "raw", no_argument, NULL, 'r' }, + { NULL, 0, NULL, 0 }, + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + while ((c = getopt_long (argc, argv, "bno:JrVh", longopts, NULL)) != -1) { + switch (c) { + case 'b': + ctl.bytes = 1; + break; + case 'n': + ctl.noheadings = 1; + break; + case 'o': + outarg = optarg; + break; + case 'J': + ctl.json = 1; + break; + case 'r': + ctl.raw = 1; + break; + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (optind == argc) { + warnx(_("no file specified")); + errtryhelp(EXIT_FAILURE); + } + + if (!ncolumns) { + columns[ncolumns++] = COL_RES; + columns[ncolumns++] = COL_PAGES; + columns[ncolumns++] = COL_SIZE; + columns[ncolumns++] = COL_FILE; + } + + if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns), + &ncolumns, column_name_to_id) < 0) + return EXIT_FAILURE; + + scols_init_debug(0); + ctl.tb = scols_new_table(); + if (!ctl.tb) + err(EXIT_FAILURE, _("failed to allocate output table")); + + scols_table_enable_noheadings(ctl.tb, ctl.noheadings); + scols_table_enable_raw(ctl.tb, ctl.raw); + scols_table_enable_json(ctl.tb, ctl.json); + if (ctl.json) + scols_table_set_name(ctl.tb, "fincore"); + + for (i = 0; i < ncolumns; i++) { + const struct colinfo *col = get_column_info(i); + struct libscols_column *cl; + + cl = scols_table_new_column(ctl.tb, col->name, col->whint, col->flags); + if (!cl) + err(EXIT_FAILURE, _("failed to allocate output column")); + + if (ctl.json) { + int id = get_column_id(i); + + switch (id) { + case COL_FILE: + scols_column_set_json_type(cl, SCOLS_JSON_STRING); + break; + case COL_SIZE: + case COL_RES: + if (!ctl.bytes) + break; + /* fallthrough */ + default: + scols_column_set_json_type(cl, SCOLS_JSON_NUMBER); + break; + } + } + } + + for(; optind < argc; optind++) { + char *name = argv[optind]; + struct stat sb; + off_t count_incore = 0; + + switch (fincore_name(&ctl, name, &sb, &count_incore)) { + case 0: + add_output_data(&ctl, name, sb.st_size, count_incore); + break; + case 1: + break; /* ignore */ + default: + rc = EXIT_FAILURE; + break; + } + } + + scols_print_table(ctl.tb); + scols_unref_table(ctl.tb); + + return rc; +} diff --git a/misc-utils/findfs.8 b/misc-utils/findfs.8 new file mode 100644 index 0000000..9875373 --- /dev/null +++ b/misc-utils/findfs.8 @@ -0,0 +1,123 @@ +'\" t +.\" Title: findfs +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "FINDFS" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.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" +findfs \- find a filesystem by label or UUID +.SH "SYNOPSIS" +.sp +\fBfindfs\fP \fBNAME\fP=\fIvalue\fP +.SH "DESCRIPTION" +.sp +\fBfindfs\fP will search the block devices in the system looking for a filesystem or partition with specified tag. The currently supported tags are: +.sp +\fBLABEL\fP=\fI<label>\fP +.RS 4 +Specifies filesystem label. +.RE +.sp +\fBUUID\fP=\fI<uuid>\fP +.RS 4 +Specifies filesystem UUID. +.RE +.sp +\fBPARTUUID\fP=\fI<uuid>\fP +.RS 4 +Specifies partition UUID. This partition identifier is supported for example for GUID Partition Table (GPT) partition tables. +.RE +.sp +\fBPARTLABEL\fP=\fI<label>\fP +.RS 4 +Specifies partition label (name). The partition labels are supported for example for GUID Partition Table (GPT) or MAC partition tables. +.RE +.sp +If the filesystem or partition is found, the device name will be printed on stdout. +.sp +The complete overview about filesystems and partitions you can get for example by +.RS 3 +.ll -.6i +.sp +\fBlsblk \-\-fs\fP +.sp +\fBpartx \-\-show <disk>\fP +.sp +\fBblkid\fP +.br +.RE +.ll +.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 +\fB0\fP +.RS 4 +success +.RE +.sp +\fB1\fP +.RS 4 +label or uuid cannot be found +.RE +.sp +\fB2\fP +.RS 4 +usage error, wrong number of arguments or unknown option +.RE +.SH "ENVIRONMENT" +.sp +LIBBLKID_DEBUG=all +.RS 4 +enables libblkid debug output. +.RE +.SH "AUTHORS" +.sp +\fBfindfs\fP was originally written by \c +.MTO "tytso\(atmit.edu" "Theodore Ts\(cqo" "" +and re\-written for the util\-linux package by +.MTO "kzak\(atredhat.com" "Karel Zak" "." +.SH "SEE ALSO" +.sp +\fBblkid\fP(8), +\fBlsblk\fP(8), +\fBpartx\fP(8) +.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 \fBfindfs\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/findfs.8.adoc b/misc-utils/findfs.8.adoc new file mode 100644 index 0000000..560a5ba --- /dev/null +++ b/misc-utils/findfs.8.adoc @@ -0,0 +1,79 @@ +//po4a: entry man manual +// Copyright 1993, 1994, 1995 by Theodore Ts'o. All Rights Reserved. +// This file may be copied under the terms of the GNU Public License. += findfs(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: findfs + +== NAME + +findfs - find a filesystem by label or UUID + +== SYNOPSIS + +*findfs* *NAME*=_value_ + +== DESCRIPTION + +*findfs* will search the block devices in the system looking for a filesystem or partition with specified tag. The currently supported tags are: + +*LABEL*=_<label>_:: +Specifies filesystem label. + +*UUID*=_<uuid>_:: +Specifies filesystem UUID. + +*PARTUUID*=_<uuid>_:: +Specifies partition UUID. This partition identifier is supported for example for GUID Partition Table (GPT) partition tables. + +*PARTLABEL*=_<label>_:: +Specifies partition label (name). The partition labels are supported for example for GUID Partition Table (GPT) or MAC partition tables. + +If the filesystem or partition is found, the device name will be printed on stdout. + +The complete overview about filesystems and partitions you can get for example by + +____ +*lsblk --fs* + +*partx --show <disk>* + +*blkid* +____ + +include::man-common/help-version.adoc[] + +== EXIT STATUS + +*0*:: +success +*1*:: +label or uuid cannot be found +*2*:: +usage error, wrong number of arguments or unknown option + +== ENVIRONMENT + +LIBBLKID_DEBUG=all:: +enables libblkid debug output. + +== AUTHORS + +*findfs* was originally written by mailto:tytso@mit.edu[Theodore Ts'o] and re-written for the util-linux package by mailto:kzak@redhat.com[Karel Zak]. + +== SEE ALSO + +*blkid*(8), +*lsblk*(8), +*partx*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/findfs.c b/misc-utils/findfs.c new file mode 100644 index 0000000..0997e1b --- /dev/null +++ b/misc-utils/findfs.c @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2009 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the GNU Public + * License. + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> + +#include <blkid.h> + +#include "nls.h" +#include "closestream.h" +#include "c.h" + +/* Exit codes used by findfs. */ +#define FINDFS_SUCCESS 0 /* no errors */ +#define FINDFS_NOT_FOUND 1 /* label or uuid cannot be found */ +#define FINDFS_USAGE_ERROR 2 /* user did something unexpected */ + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] {LABEL,UUID,PARTUUID,PARTLABEL}=<value>\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + 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)")); + exit(FINDFS_SUCCESS); +} + +int main(int argc, char **argv) +{ + char *dev; + int c; + static const struct option longopts[] = { + {"version", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + if (argc != 2) { + /* we return '2' for backward compatibility + * with version from e2fsprogs */ + warnx(_("bad usage")); + errtryhelp(FINDFS_USAGE_ERROR); + } + + while ((c = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1) + switch (c) { + case 'V': + print_version(FINDFS_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(FINDFS_USAGE_ERROR); + } + + dev = blkid_evaluate_tag(argv[1], NULL, NULL); + if (!dev) + errx(FINDFS_NOT_FOUND, _("unable to resolve '%s'"), argv[1]); + + puts(dev); + return FINDFS_SUCCESS; +} + diff --git a/misc-utils/findmnt-verify.c b/misc-utils/findmnt-verify.c new file mode 100644 index 0000000..3543c36 --- /dev/null +++ b/misc-utils/findmnt-verify.c @@ -0,0 +1,585 @@ +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <stdarg.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <libmount.h> +#include <blkid.h> +#include <sys/utsname.h> + +#include "nls.h" +#include "c.h" +#include "strutils.h" +#include "xalloc.h" +#include "pathnames.h" +#include "match.h" + +#include "findmnt.h" + +struct verify_context { + struct libmnt_fs *fs; + struct libmnt_table *tb; + + char **fs_ary; + size_t fs_num; + size_t fs_alloc; + + int nwarnings; + int nerrors; + + unsigned int target_printed : 1, + no_fsck : 1; +}; + +static void __attribute__ ((__format__ (__printf__, 3, 0))) + verify_mesg(struct verify_context *vfy, char type, const char *fmt, va_list ap) +{ + if (!vfy->target_printed && vfy->fs) { + fprintf(stdout, "%s\n", mnt_fs_get_target(vfy->fs)); + vfy->target_printed = 1; + } + + fprintf(stdout, " [%c] ", type); + vfprintf(stdout, fmt, ap); + fputc('\n', stdout); +} + +static int __attribute__ ((__format__ (__printf__, 2, 3))) + verify_warn(struct verify_context *vfy, const char *fmt, ...) +{ + va_list ap; + vfy->nwarnings++; + va_start(ap, fmt); + verify_mesg(vfy, 'W', fmt, ap); + va_end(ap); + return 0; +} + +static int __attribute__ ((__format__ (__printf__, 2, 3))) + verify_err(struct verify_context *vfy, const char *fmt, ...) +{ + va_list ap; + vfy->nerrors++; + va_start(ap, fmt); + verify_mesg(vfy, 'E', fmt, ap); + va_end(ap); + return 0; +} + +static int __attribute__ ((__format__ (__printf__, 2, 3))) + verify_ok(struct verify_context *vfy __attribute__((unused)), + const char *fmt, ...) +{ + va_list ap; + + if (!(flags & FL_VERBOSE)) + return 0; + + va_start(ap, fmt); + verify_mesg(vfy, ' ', fmt, ap); + va_end(ap); + return 0; +} + +static int verify_order(struct verify_context *vfy) +{ + struct libmnt_iter *itr = NULL; + struct libmnt_fs *next; + const char *tgt; + + tgt = mnt_fs_get_target(vfy->fs); + if (tgt && !(flags & FL_NOCACHE)) + tgt = mnt_resolve_target(tgt, cache); + else if (!tgt) + return 0; + + itr = mnt_new_iter(MNT_ITER_FORWARD); + if (!itr) { + warn(_("failed to initialize libmount iterator")); + goto done; + } + + /* set iterator position to 'fs' */ + mnt_table_set_iter(vfy->tb, itr, vfy->fs); + + if (mnt_table_next_fs(vfy->tb, itr, &next) != 0) + goto done; + + /* scan all next filesystems */ + while (mnt_table_next_fs(vfy->tb, itr, &next) == 0) { + const char *n_tgt; + size_t len; + + n_tgt = mnt_fs_get_target(next); + if (n_tgt && !(flags & FL_NOCACHE)) + n_tgt = mnt_resolve_target(n_tgt, cache); + else if (!n_tgt) + continue; + len = strlen(n_tgt); + + if (strncmp(n_tgt, tgt, len) == 0) { + if (*(tgt + len) == '\0') + verify_warn(vfy, _("target specified more than once")); + else if (*(tgt + len) == '/') + verify_err(vfy, _("wrong order: %s specified before %s"), tgt, n_tgt); + } + } +done: + mnt_free_iter(itr); + return 0; +} + +static int verify_target(struct verify_context *vfy) +{ + const char *tgt = mnt_fs_get_target(vfy->fs); + struct stat sb; + + if (!tgt) + return verify_err(vfy, _("undefined target (fs_file)")); + + if (!(flags & FL_NOCACHE)) { + const char *cn = mnt_resolve_target(tgt, cache); + if (!cn) + return -ENOMEM; + if (strcmp(cn, tgt) != 0) + verify_warn(vfy, _("non-canonical target path (real: %s)"), cn); + tgt = cn; + } + if (stat(tgt, &sb) != 0) { + if (mnt_fs_get_option(vfy->fs, "noauto", NULL, NULL) == 1) + verify_err(vfy, _("unreachable on boot required target: %m")); + else + verify_warn(vfy, _("unreachable target: %m")); + + } else if (!S_ISDIR(sb.st_mode) + && mnt_fs_get_option(vfy->fs, "bind", NULL, NULL) == 1) { + verify_err(vfy, _("target is not a directory")); + } else + verify_ok(vfy, _("target exists")); + + return 0; +} + +static char *verify_tag(struct verify_context *vfy, const char *name, + const char *value) +{ + char *src = NULL; + + if (!(flags & FL_NOCACHE)) + src = mnt_resolve_tag(name, value, cache); + + if (!src) { + if (mnt_fs_get_option(vfy->fs, "noauto", NULL, NULL) == 1) + verify_err(vfy, _("unreachable on boot required source: %s=%s"), name, value); + else + verify_warn(vfy, _("unreachable: %s=%s"), name, value); + } else + verify_ok(vfy, _("%s=%s translated to %s"), name, value, src); + + return src; +} + +/* Note that mount source is very FS specific and we should not + * interpret unreachable source as error. The exception is only + * NAME=value, this has to be convertible to device name. + */ +static int verify_source(struct verify_context *vfy) +{ + const char *src = mnt_fs_get_srcpath(vfy->fs); + char *t = NULL, *v = NULL; + struct stat sb; + int isbind, rc = 0; + + /* source is NAME=value tag */ + if (!src) { + const char *tag = NULL, *val = NULL; + + if (mnt_fs_get_tag(vfy->fs, &tag, &val) != 0) + return verify_err(vfy, _("undefined source (fs_spec)")); + + src = verify_tag(vfy, tag, val); + if (!src) + goto done; + + /* blkid is able to parse it, but libmount does not see it as a tag -- + * it means unsupported tag */ + } else if (blkid_parse_tag_string(src, &t, &v) == 0 && stat(src, &sb) != 0) { + rc = verify_err(vfy, _("unsupported source tag: %s"), src); + goto done; + } + isbind = mnt_fs_get_option(vfy->fs, "bind", NULL, NULL) == 0; + + /* source is path */ + if (mnt_fs_is_pseudofs(vfy->fs) || mnt_fs_is_netfs(vfy->fs)) + verify_ok(vfy, _("do not check %s source (pseudo/net)"), src); + + else if (stat(src, &sb) != 0) + verify_warn(vfy, _("unreachable source: %s: %m"), src); + + else if ((S_ISDIR(sb.st_mode) || S_ISREG(sb.st_mode)) && !isbind) + verify_warn(vfy, _("non-bind mount source %s is a directory or regular file"), src); + + else if (!S_ISBLK(sb.st_mode) && !isbind) + verify_warn(vfy, _("source %s is not a block device"), src); + else + verify_ok(vfy, _("source %s exists"), src); +done: + free(t); + free(v); + return rc; +} + +static int verify_options(struct verify_context *vfy) +{ + const char *opts; + + opts = mnt_fs_get_vfs_options(vfy->fs); + if (opts) + verify_ok(vfy, _("VFS options: %s"), opts); + + opts = mnt_fs_get_fs_options(vfy->fs); + if (opts) + verify_ok(vfy, _("FS options: %s"), opts); + + opts = mnt_fs_get_user_options(vfy->fs); + if (opts) + verify_ok(vfy, _("userspace options: %s"), opts); + + return 0; +} + +static int verify_swaparea(struct verify_context *vfy) +{ + char *arg; + size_t argsz = 0; + + if (mnt_fs_get_option(vfy->fs, "discard", &arg, &argsz) == 0 + && arg + && strncmp(arg, "once", argsz) != 0 + && strncmp(arg, "pages", argsz) != 0) + verify_err(vfy, _("unsupported swaparea discard policy: %s"), arg); + + if (mnt_fs_get_option(vfy->fs, "pri", &arg, &argsz) == 0 && arg) { + char *p = arg; + if (*p == '-') + p++; + for (; p < arg + argsz; p++) { + if (!isdigit((unsigned char) *p)) { + verify_err(vfy, _("failed to parse swaparea priority option")); + break; + } + } + } + + return 0; +} + +static int is_supported_filesystem(struct verify_context *vfy, const char *name) +{ + size_t n; + + if (!vfy->fs_num) + return 0; + + for (n = 0; n < vfy->fs_num; n++ ) { + if (match_fstype(vfy->fs_ary[n], name)) + return 1; + } + + return 0; +} + +static int add_filesystem(struct verify_context *vfy, const char *name) +{ + #define MYCHUNK 16 + + if (is_supported_filesystem(vfy, name)) + return 0; + + 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[vfy->fs_num] = xstrdup(name); + vfy->fs_num++; + + return 0; +} + +static int read_proc_filesystems(struct verify_context *vfy) +{ + int rc = 0; + FILE *f; + char buf[80], *cp, *t; + + f = fopen("/proc/filesystems", "r"); + if (!f) + return -errno; + + while (!feof(f)) { + if (!fgets(buf, sizeof(buf), f)) + break; + cp = buf; + if (!isspace(*cp)) { + while (*cp && !isspace(*cp)) + cp++; + } + while (*cp && isspace(*cp)) + cp++; + if ((t = strchr(cp, '\n')) != NULL) + *t = 0; + if ((t = strchr(cp, '\t')) != NULL) + *t = 0; + if ((t = strchr(cp, ' ')) != NULL) + *t = 0; + + rc = add_filesystem(vfy, cp); + if (rc) + break; + } + fclose(f); + return rc; +} + +static void free_proc_filesystems(struct verify_context *vfy) +{ + size_t n; + + if (!vfy->fs_ary) + return; + + for (n = 0; n < vfy->fs_num; n++ ) + free(vfy->fs_ary[n]); + free(vfy->fs_ary); +} + +static int read_kernel_filesystems(struct verify_context *vfy) +{ + int rc = 0; +#ifdef __linux__ + struct utsname uts; + FILE *f; + char buf[1024]; + + if (uname(&uts)) + return 0; + snprintf(buf, sizeof(buf), "/lib/modules/%s/modules.dep", uts.release); + + f = fopen(buf, "r"); + if (!f) + return 0; + + while (!feof(f)) { + char *p, *name; + + if (!fgets(buf, sizeof(buf), f)) + break; + + if (strncmp("kernel/fs/", buf, 10) != 0 || + strncmp("kernel/fs/nls/", buf, 14) == 0) + continue; + + p = strchr(buf, ':'); + if (!p) + continue; + *p = '\0'; + + name = strrchr(buf, '/'); + if (!name) + continue; + name++; + + p = strstr(name, ".ko"); + if (!p) + continue; + *p = '\0'; + + rc = add_filesystem(vfy, name); + if (rc) + break; + } + fclose(f); +#endif /* __linux__ */ + return rc; +} + +static int verify_fstype(struct verify_context *vfy) +{ + char *src = mnt_resolve_spec(mnt_fs_get_source(vfy->fs), cache); + char *realtype = NULL; + const char *type; + int ambi = 0, isauto = 0, isswap = 0; + + if (!src) + return 0; + + if (mnt_fs_is_pseudofs(vfy->fs) || mnt_fs_is_netfs(vfy->fs)) { + verify_ok(vfy, _("do not check %s FS type (pseudo/net)"), src); + goto done; + } + + type = mnt_fs_get_fstype(vfy->fs); + + if (type) { + int none = strcmp(type, "none") == 0; + + if (none + && mnt_fs_get_option(vfy->fs, "bind", NULL, NULL) == 1 + && mnt_fs_get_option(vfy->fs, "move", NULL, NULL) == 1) { + verify_warn(vfy, _("\"none\" FS type is recommended for bind or move oprations only")); + goto done; + } + + if (strcmp(type, "auto") == 0) + isauto = 1; + else if (strcmp(type, "swap") == 0) + isswap = 1; + else if (strcmp(type, "xfs") == 0 || strcmp(type, "btrfs") == 0) + vfy->no_fsck = 1; + + if (!isswap && !isauto && !none && !is_supported_filesystem(vfy, type)) + verify_warn(vfy, _("%s seems unsupported by the current kernel"), type); + } + + errno = 0; + realtype = mnt_get_fstype(src, &ambi, cache); + + if (!realtype) { + const char *reson = errno ? strerror(errno) : _("reason unknown"); + + if (isauto) + verify_err(vfy, _("cannot detect on-disk filesystem type (%s)"), reson); + else + verify_warn(vfy, _("cannot detect on-disk filesystem type (%s)"), reson); + goto done; + } + + if (realtype) { + isswap = strcmp(realtype, "swap") == 0; + vfy->no_fsck = strcmp(realtype, "xfs") == 0 + || strcmp(realtype, "btrfs") == 0; + + if (type && !isauto && strcmp(type, realtype) != 0) { + verify_warn(vfy, _("%s does not match with on-disk %s"), type, realtype); + goto done; + } + if (!isswap && !is_supported_filesystem(vfy, realtype)) { + verify_warn(vfy, _("on-disk %s seems unsupported by the current kernel"), realtype); + goto done; + } + + verify_ok(vfy, _("FS type is %s"), realtype); + } + +done: + if (!cache) { + free(src); + free(realtype); + } + return 0; +} + +static int verify_passno(struct verify_context *vfy) +{ + int passno = mnt_fs_get_passno(vfy->fs); + const char *tgt = mnt_fs_get_target(vfy->fs); + + if (tgt && strcmp("/", tgt) == 0 && passno != 1 && !vfy->no_fsck) + return verify_warn(vfy, _("recommended root FS passno is 1 (current is %d)"), passno); + + return 0; +} + +static int verify_filesystem(struct verify_context *vfy) +{ + int rc = 0; + + if (mnt_fs_is_swaparea(vfy->fs)) + rc = verify_swaparea(vfy); + else { + rc = verify_target(vfy); + if (!rc) + rc = verify_options(vfy); + } + + if (!rc) + rc = verify_source(vfy); + if (!rc) + rc = verify_fstype(vfy); + if (!rc) + rc = verify_passno(vfy); /* depends on verify_fstype() */ + + return rc; +} + +int verify_table(struct libmnt_table *tb) +{ + struct verify_context vfy = { .nerrors = 0 }; + struct libmnt_iter *itr; + int rc = 0; /* overall return code (alloc errors, etc.) */ + int check_order = is_listall_mode(); + static int has_read_fs = 0; + + itr = mnt_new_iter(MNT_ITER_FORWARD); + if (!itr) { + warn(_("failed to initialize libmount iterator")); + goto done; + } + + vfy.tb = tb; + + if (has_read_fs == 0) { + read_proc_filesystems(&vfy); + read_kernel_filesystems(&vfy); + has_read_fs = 1; + } + + while (rc == 0 && (vfy.fs = get_next_fs(tb, itr))) { + vfy.target_printed = 0; + vfy.no_fsck = 0; + + if (check_order) + rc = verify_order(&vfy); + if (!rc) + rc = verify_filesystem(&vfy); + + if (flags & FL_FIRSTONLY) + break; + flags |= FL_NOSWAPMATCH; + } + +#ifdef USE_SYSTEMD + { + struct stat a, b; + + if (stat(_PATH_SD_UNITSLOAD, &a) == 0 && + stat(_PATH_MNTTAB, &b) == 0 && + cmp_stat_mtime(&a, &b, <)) + verify_warn(&vfy, _( + "your fstab has been modified, but systemd still uses the old version;\n" + " use 'systemctl daemon-reload' to reload")); + } +#endif + +done: + mnt_free_iter(itr); + + /* summary */ + if (vfy.nerrors || parse_nerrors || vfy.nwarnings) { + fputc('\n', stderr); + fprintf(stderr, P_("%d parse error", "%d parse errors", parse_nerrors), parse_nerrors); + fprintf(stderr, P_(", %d error", ", %d errors", vfy.nerrors), vfy.nerrors); + fprintf(stderr, P_(", %d warning", ", %d warnings", vfy.nwarnings), vfy.nwarnings); + fputc('\n', stderr); + } else + fprintf(stdout, _("Success, no errors or warnings detected\n")); + + + free_proc_filesystems(&vfy); + + return rc != 0 ? rc : vfy.nerrors + parse_nerrors; +} diff --git a/misc-utils/findmnt.8 b/misc-utils/findmnt.8 new file mode 100644 index 0000000..d5c5d4f --- /dev/null +++ b/misc-utils/findmnt.8 @@ -0,0 +1,389 @@ +'\" t +.\" Title: findmnt +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-12-01 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "FINDMNT" "8" "2023-12-01" "util\-linux 2.39.3" "System Administration" +.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" +findmnt \- find a filesystem +.SH "SYNOPSIS" +.sp +\fBfindmnt\fP [options] +.sp +\fBfindmnt\fP [options] \fIdevice\fP|\fImountpoint\fP +.sp +\fBfindmnt\fP [options] [\fB\-\-source\fP] \fIdevice\fP [\fB\-\-target\fP \fIpath\fP|\fB\-\-mountpoint\fP \fImountpoint\fP] +.SH "DESCRIPTION" +.sp +\fBfindmnt\fP will list all mounted filesystems or search for a filesystem. The \fBfindmnt\fP command is able to search in \fI/etc/fstab\fP, \fI/etc/mtab\fP or \fI/proc/self/mountinfo\fP. If \fIdevice\fP or \fImountpoint\fP is not given, all filesystems are shown. +.sp +The device may be specified by device name, major:minor numbers, filesystem label or UUID, or partition label or UUID. Note that \fBfindmnt\fP follows \fBmount\fP(8) behavior where a device name may be interpreted as a mountpoint (and vice versa) if the \fB\-\-target\fP, \fB\-\-mountpoint\fP or \fB\-\-source\fP options are not specified. +.sp +The command\-line option \fB\-\-target\fP accepts any file or directory and then \fBfindmnt\fP displays the filesystem for the given path. +.sp +The command prints all mounted filesystems in the tree\-like format by default. The default output, is subject to change. So whenever possible, you should avoid using default output in your scripts. Always explicitly define expected columns by using \fB\-\-output columns\-list\fP in environments where a stable output is required. +.sp +The relationship between block devices and filesystems is not always one\-to\-one. The filesystem may use more block devices. This is why \fBfindmnt\fP provides SOURCE and SOURCES (pl.) columns. The column SOURCES displays all devices where it is possible to find the same filesystem UUID (or another tag specified in \fIfstab\fP when executed with \fB\-\-fstab\fP and \fB\-\-evaluate\fP). +.SH "OPTIONS" +.sp +\fB\-A\fP, \fB\-\-all\fP +.RS 4 +Disable all built\-in filters and print all filesystems. +.RE +.sp +\fB\-a\fP, \fB\-\-ascii\fP +.RS 4 +Use ascii characters for tree formatting. +.RE +.sp +\fB\-b\fP, \fB\-\-bytes\fP +.RS 4 +Print the sizes in bytes rather than in a human\-readable format. +.sp +By default, the unit, sizes are expressed in, is byte, and unit prefixes are in +power of 2^10 (1024). Abbreviations of symbols are exhibited truncated in order +to reach a better readability, by exhibiting alone the first letter of them; +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\-C\fP, \fB\-\-nocanonicalize\fP +.RS 4 +Do not canonicalize paths at all. This option affects the comparing of paths and the evaluation of tags (LABEL, UUID, etc.). +.RE +.sp +\fB\-c\fP, \fB\-\-canonicalize\fP +.RS 4 +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. +.RE +.sp +\fB\-d\fP, \fB\-\-direction\fP \fIword\fP +.RS 4 +The search direction, either \fBforward\fP or \fBbackward\fP. +.RE +.sp +\fB\-e\fP, \fB\-\-evaluate\fP +.RS 4 +Convert all tags (LABEL, UUID, PARTUUID, or PARTLABEL) to the corresponding device names for the SOURCE column. It\(cqs an unusual situation, but the same tag may be duplicated (used for more devices). For this purpose, there is SOURCES (pl.) column. This column displays by multi\-line cell all devices where the tag is detected by libblkid. This option makes sense for \fIfstab\fP only. +.RE +.sp +\fB\-F\fP, \fB\-\-tab\-file\fP \fIpath\fP +.RS 4 +Search in an alternative file. If used with \fB\-\-fstab\fP, \fB\-\-mtab\fP or \fB\-\-kernel\fP, then it overrides the default paths. If specified more than once, then tree\-like output is disabled (see the \fB\-\-list\fP option). +.RE +.sp +\fB\-f\fP, \fB\-\-first\-only\fP +.RS 4 +Print the first matching filesystem only. +.RE +.sp +\fB\-i\fP, \fB\-\-invert\fP +.RS 4 +Invert the sense of matching. +.RE +.sp +\fB\-J\fP, \fB\-\-json\fP +.RS 4 +Use JSON output format. +.RE +.sp +\fB\-k\fP, \fB\-\-kernel\fP +.RS 4 +Search in \fI/proc/self/mountinfo\fP. The output is in the tree\-like format. This is the default. The output contains only mount options maintained by kernel (see also \fB\-\-mtab\fP). +.RE +.sp +\fB\-l\fP, \fB\-\-list\fP +.RS 4 +Use the list output format. This output format is automatically enabled if the output is restricted by the \fB\-t\fP, \fB\-O\fP, \fB\-S\fP or \fB\-T\fP option and the option \fB\-\-submounts\fP is not used or if more that one source file (the option \fB\-F\fP) is specified. +.RE +.sp +\fB\-M\fP, \fB\-\-mountpoint\fP \fIpath\fP +.RS 4 +Explicitly define the mountpoint file or directory. See also \fB\-\-target\fP. +.RE +.sp +\fB\-m\fP, \fB\-\-mtab\fP +.RS 4 +Search in \fI/etc/mtab\fP. The output is in the list format by default (see \fB\-\-tree\fP). The output may include user space mount options. +.RE +.sp +\fB\-N\fP, \fB\-\-task\fP \fItid\fP +.RS 4 +Use alternative namespace \fI/proc/<tid>/mountinfo\fP rather than the default \fI/proc/self/mountinfo\fP. If the option is specified more than once, then tree\-like output is disabled (see the \fB\-\-list\fP option). See also the \fBunshare\fP(1) command. +.RE +.sp +\fB\-n\fP, \fB\-\-noheadings\fP +.RS 4 +Do not print a header line. +.RE +.sp +\fB\-O\fP, \fB\-\-options\fP \fIlist\fP +.RS 4 +Limit the set of printed filesystems. More than one option may be specified in a comma\-separated list. The \fB\-t\fP and \fB\-O\fP options are cumulative in effect. It is different from \fB\-t\fP in that each option is matched exactly; a leading \fIno\fP at the beginning does not have global meaning. The "no" can used for individual items in the list. The "no" prefix interpretation can be disabled by "+" prefix. +.RE +.sp +\fB\-o\fP, \fB\-\-output\fP \fIlist\fP +.RS 4 +Define output columns. See the \fB\-\-help\fP output to get a list of the currently supported columns. The \fBTARGET\fP column contains tree formatting if the \fB\-\-list\fP or \fB\-\-raw\fP options are not specified. +.sp +The default list of columns may be extended if \fIlist\fP is specified in the format \fI+list\fP (e.g., \fBfindmnt \-o +PROPAGATION\fP). +.RE +.sp +\fB\-\-output\-all\fP +.RS 4 +Output almost all available columns. The columns that require \fB\-\-poll\fP are not included. +.RE +.sp +\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. +.RE +.sp +\fB\-p\fP, \fB\-\-poll\fP[\fI=list\fP] +.RS 4 +Monitor changes in the \fI/proc/self/mountinfo\fP 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. +.sp +The time for which \fB\-\-poll\fP will block can be restricted with the \fB\-\-timeout\fP or \fB\-\-first\-only\fP options. +.sp +The standard columns always use the new version of the information from the mountinfo file, except the umount action which is based on the original information cached by \fBfindmnt\fP. The poll mode allows using extra columns: +.sp +\fBACTION\fP +.RS 4 +mount, umount, move or remount action name; this column is enabled by default +.RE +.sp +\fBOLD\-TARGET\fP +.RS 4 +available for umount and move actions +.RE +.sp +\fBOLD\-OPTIONS\fP +.RS 4 +available for umount and remount actions +.RE +.RE +.sp +\fB\-\-pseudo\fP +.RS 4 +Print only pseudo filesystems. +.RE +.sp +\fB\-\-shadow\fP +.RS 4 +Print only filesystems over\-mounted by another filesystem. +.RE +.sp +\fB\-R\fP, \fB\-\-submounts\fP +.RS 4 +Print recursively all submounts for the selected filesystems. The restrictions defined by options \fB\-t\fP, \fB\-O\fP, \fB\-S\fP, \fB\-T\fP and \fB\-\-direction\fP are not applied to submounts. All submounts are always printed in tree\-like order. The option enables the tree\-like output format by default. This option has no effect for \fB\-\-mtab\fP or \fB\-\-fstab\fP. +.RE +.sp +\fB\-r\fP, \fB\-\-raw\fP +.RS 4 +Use raw output format. All potentially unsafe characters are hex\-escaped (\(rsx<code>). +.RE +.sp +\fB\-\-real\fP +.RS 4 +Print only real filesystems. +.RE +.sp +\fB\-S\fP, \fB\-\-source\fP \fIspec\fP +.RS 4 +Explicitly define the mount source. Supported specifications are \fIdevice\fP, \fImaj\fP\fB:\fP\fImin\fP, \fBLABEL=\fP\fIlabel\fP, \fBUUID=\fP\fIuuid\fP, \fBPARTLABEL=\fP\fIlabel\fP and \fBPARTUUID=\fP\fIuuid\fP. +.RE +.sp +\fB\-s\fP, \fB\-\-fstab\fP +.RS 4 +Search in \fI/etc/fstab\fP. The output is in the list format (see \fB\-\-list\fP). +.RE +.sp +\fB\-T\fP, \fB\-\-target\fP \fIpath\fP +.RS 4 +Define the mount target. If \fIpath\fP is not a mountpoint file or directory, then \fBfindmnt\fP checks the \fIpath\fP elements in reverse order to get the mountpoint (this feature is supported only when searching in kernel files and unsupported for \fB\-\-fstab\fP). It\(cqs recommended to use the option \fB\-\-mountpoint\fP when checks of \fIpath\fP elements are unwanted and \fIpath\fP is a strictly specified mountpoint. +.RE +.sp +\fB\-t\fP, \fB\-\-types\fP \fIlist\fP +.RS 4 +Limit the set of printed filesystems. More than one type may be specified in a comma\-separated list. The list of filesystem types can be prefixed with \fBno\fP to specify the filesystem types on which no action should be taken. For more details see \fBmount\fP(8). +.RE +.sp +\fB\-\-tree\fP +.RS 4 +Enable tree\-like output if possible. The options is silently ignored for tables where is missing child\-parent relation (e.g., \fIfstab\fP). +.RE +.sp +\fB\-\-shadowed\fP +.RS 4 +Print only filesystems over\-mounted by another filesystem. +.RE +.sp +\fB\-U\fP, \fB\-\-uniq\fP +.RS 4 +Ignore filesystems with duplicate mount targets, thus effectively skipping over\-mounted mount points. +.RE +.sp +\fB\-u\fP, \fB\-\-notruncate\fP +.RS 4 +Do not truncate text in columns. The default is to not truncate the \fBTARGET\fP, \fBSOURCE\fP, \fBUUID\fP, \fBLABEL\fP, \fBPARTUUID\fP, \fBPARTLABEL\fP columns. This option disables text truncation also in all other columns. +.RE +.sp +\fB\-v\fP, \fB\-\-nofsroot\fP +.RS 4 +Do not print a [/dir] in the SOURCE column for bind mounts or btrfs subvolumes. +.RE +.sp +\fB\-w\fP, \fB\-\-timeout\fP \fImilliseconds\fP +.RS 4 +Specify an upper limit on the time for which \fB\-\-poll\fP will block, in milliseconds. +.RE +.sp +\fB\-x\fP, \fB\-\-verify\fP +.RS 4 +Check mount table content. The default is to verify \fI/etc/fstab\fP parsability and usability. It\(cqs possible to use this option also with \fB\-\-tab\-file\fP. It\(cqs possible to specify source (device) or target (mountpoint) to filter mount table. The option \fB\-\-verbose\fP forces \fBfindmnt\fP to print more details. +.RE +.sp +\fB\-\-verbose\fP +.RS 4 +Force \fBfindmnt\fP to print more information (\fB\-\-verify\fP only for now). +.RE +.sp +\fB\-\-vfs\-all\fP +.RS 4 +When used with \fBVFS\-OPTIONS\fP column, print all VFS (fs\-independent) flags. This option is designed for auditing purposes to list also default VFS kernel mount options which are normally not listed. +.RE +.sp +\fB\-y\fP, \fB\-\-shell\fP +.RS 4 +The column name will be modified to contain only characters allowed for shell variable identifiers. This is usable, for example, with \fB\-\-pairs\fP. Note that this feature has been automatically enabled for \fB\-\-pairs\fP in version 2.37, but due to compatibility issues, now it\(cqs necessary to request this behavior by \fB\-\-shell\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 +The exit value is 0 if there is something to display, or 1 on any error +(for example if no filesystem is found based on the user\(cqs filter +specification, or the device path or mountpoint does not exist). +.SH "ENVIRONMENT" +.sp +\fBLIBMOUNT_FSTAB\fP=<path> +.RS 4 +overrides the default location of the \fIfstab\fP file +.RE +.sp +\fBLIBMOUNT_MTAB\fP=<path> +.RS 4 +overrides the default location of the \fImtab\fP file +.RE +.sp +\fBLIBMOUNT_DEBUG\fP=all +.RS 4 +enables libmount debug output +.RE +.sp +\fBLIBSMARTCOLS_DEBUG\fP=all +.RS 4 +enables libsmartcols debug output +.RE +.sp +\fBLIBSMARTCOLS_DEBUG_PADDING\fP=on +.RS 4 +use visible padding characters. +.RE +.SH "EXAMPLES" +.sp +\fBfindmnt \-\-fstab \-t nfs\fP +.RS 4 +Prints all NFS filesystems defined in \fI/etc/fstab\fP. +.RE +.sp +\fBfindmnt \-\-fstab /mnt/foo\fP +.RS 4 +Prints all \fI/etc/fstab\fP filesystems where the mountpoint directory is \fI/mnt/foo\fP. It also prints bind mounts where \fI/mnt/foo\fP is a source. +.RE +.sp +\fBfindmnt \-\-fstab \-\-target /mnt/foo\fP +.RS 4 +Prints all \fI/etc/fstab\fP filesystems where the mountpoint directory is \fI/mnt/foo\fP. +.RE +.sp +\fBfindmnt \-\-fstab \-\-evaluate\fP +.RS 4 +Prints all \fI/etc/fstab\fP filesystems and converts LABEL= and UUID= tags to the real device names. +.RE +.sp +\fBfindmnt \-n \-\-raw \-\-evaluate \-\-output=target LABEL=/boot\fP +.RS 4 +Prints only the mountpoint where the filesystem with label "/boot" is mounted. +.RE +.sp +\fBfindmnt \-\-poll \-\-mountpoint /mnt/foo\fP +.RS 4 +Monitors mount, unmount, remount and move on \fI/mnt/foo\fP. +.RE +.sp +\fBfindmnt \-\-poll=umount \-\-first\-only \-\-mountpoint /mnt/foo\fP +.RS 4 +Waits for \fI/mnt/foo\fP unmount. +.RE +.sp +\fBfindmnt \-\-poll=remount \-t ext3 \-O ro\fP +.RS 4 +Monitors remounts to read\-only mode on all ext3 filesystems. +.RE +.SH "AUTHORS" +.sp +.MTO "kzak\(atredhat.com" "Karel Zak" "" +.SH "SEE ALSO" +.sp +\fBfstab\fP(5), +\fBmount\fP(8) +.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 \fBfindmnt\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/findmnt.8.adoc b/misc-utils/findmnt.8.adoc new file mode 100644 index 0000000..0cb7f0e --- /dev/null +++ b/misc-utils/findmnt.8.adoc @@ -0,0 +1,246 @@ +//po4a: entry man manual += findmnt(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: findmnt +:plus: + + +== NAME + +findmnt - find a filesystem + +== SYNOPSIS + +*findmnt* [options] + +*findmnt* [options] _device_|_mountpoint_ + +*findmnt* [options] [*--source*] _device_ [*--target* _path_|*--mountpoint* _mountpoint_] + +== DESCRIPTION + +*findmnt* will list all mounted filesystems or search for a filesystem. The *findmnt* command is able to search in _/etc/fstab_, _/etc/mtab_ or _/proc/self/mountinfo_. If _device_ or _mountpoint_ is not given, all filesystems are shown. + +The device may be specified by device name, major:minor numbers, filesystem label or UUID, or partition label or UUID. Note that *findmnt* follows *mount*(8) behavior where a device name may be interpreted as a mountpoint (and vice versa) if the *--target*, *--mountpoint* or *--source* options are not specified. + +The command-line option *--target* accepts any file or directory and then *findmnt* displays the filesystem for the given path. + +The command prints all mounted filesystems in the tree-like format by default. The default output, is subject to change. So whenever possible, you should avoid using default output in your scripts. Always explicitly define expected columns by using *--output columns-list* in environments where a stable output is required. + +The relationship between block devices and filesystems is not always one-to-one. The filesystem may use more block devices. This is why *findmnt* provides SOURCE and SOURCES (pl.) columns. The column SOURCES displays all devices where it is possible to find the same filesystem UUID (or another tag specified in _fstab_ when executed with *--fstab* and *--evaluate*). + +== OPTIONS + +*-A*, *--all*:: +Disable all built-in filters and print all filesystems. + +*-a*, *--ascii*:: +Use ascii characters for tree formatting. + +*-b*, *--bytes*:: +include::man-common/in-bytes.adoc[] + +*-C*, *--nocanonicalize*:: +Do not canonicalize paths at all. This option affects the comparing of paths and the evaluation of tags (LABEL, UUID, etc.). + +*-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. + +*-d*, *--direction* _word_:: +The search direction, either *forward* or *backward*. + +*-e*, *--evaluate*:: +Convert all tags (LABEL, UUID, PARTUUID, or PARTLABEL) to the corresponding device names for the SOURCE column. It's an unusual situation, but the same tag may be duplicated (used for more devices). For this purpose, there is SOURCES (pl.) column. This column displays by multi-line cell all devices where the tag is detected by libblkid. This option makes sense for _fstab_ only. + +*-F*, *--tab-file* _path_:: +Search in an alternative file. If used with *--fstab*, *--mtab* or *--kernel*, then it overrides the default paths. If specified more than once, then tree-like output is disabled (see the *--list* option). + +*-f*, *--first-only*:: +Print the first matching filesystem only. + +*-i*, *--invert*:: +Invert the sense of matching. + +*-J*, *--json*:: +Use JSON output format. + +*-k*, *--kernel*:: +Search in _/proc/self/mountinfo_. The output is in the tree-like format. This is the default. The output contains only mount options maintained by kernel (see also *--mtab*). + +*-l*, *--list*:: +Use the list output format. This output format is automatically enabled if the output is restricted by the *-t*, *-O*, *-S* or *-T* option and the option *--submounts* is not used or if more that one source file (the option *-F*) is specified. + +*-M*, *--mountpoint* _path_:: +Explicitly define the mountpoint file or directory. See also *--target*. + +*-m*, *--mtab*:: +Search in _/etc/mtab_. The output is in the list format by default (see *--tree*). The output may include user space mount options. + +*-N*, *--task* _tid_:: +Use alternative namespace _/proc/<tid>/mountinfo_ rather than the default _/proc/self/mountinfo_. If the option is specified more than once, then tree-like output is disabled (see the *--list* option). See also the *unshare*(1) command. + +*-n*, *--noheadings*:: +Do not print a header line. + +*-O*, *--options* _list_:: +Limit the set of printed filesystems. More than one option may be specified in a comma-separated list. The *-t* and *-O* options are cumulative in effect. It is different from *-t* in that each option is matched exactly; a leading _no_ at the beginning does not have global meaning. The "no" can used for individual items in the list. The "no" prefix interpretation can be disabled by "{plus}" prefix. +//TRANSLATORS: Keep {plus} untranslated. + +*-o*, *--output* _list_:: +Define output columns. See the *--help* output to get a list of the currently supported columns. The *TARGET* column contains tree formatting if the *--list* or *--raw* options are not specified. ++ +The default list of columns may be extended if _list_ is specified in the format _{plus}list_ (e.g., *findmnt -o {plus}PROPAGATION*). +//TRANSLATORS: Keep {plus} untranslated. + +*--output-all*:: +Output almost all available columns. The columns that require *--poll* are not included. + +*-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*. + +*-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. ++ +The time for which *--poll* will block can be restricted with the *--timeout* or *--first-only* options. ++ +The standard columns always use the new version of the information from the mountinfo file, except the umount action which is based on the original information cached by *findmnt*. The poll mode allows using extra columns: ++ +*ACTION*;; +mount, umount, move or remount action name; this column is enabled by default +*OLD-TARGET*;; +available for umount and move actions +*OLD-OPTIONS*;; +available for umount and remount actions + +*--pseudo*:: +Print only pseudo filesystems. + +*--shadow*:: +Print only filesystems over-mounted by another filesystem. + +*-R*, *--submounts*:: +Print recursively all submounts for the selected filesystems. The restrictions defined by options *-t*, *-O*, *-S*, *-T* and *--direction* are not applied to submounts. All submounts are always printed in tree-like order. The option enables the tree-like output format by default. This option has no effect for *--mtab* or *--fstab*. + +*-r*, *--raw*:: +Use raw output format. All potentially unsafe characters are hex-escaped (\x<code>). + +*--real*:: +Print only real filesystems. + +*-S*, *--source* _spec_:: +Explicitly define the mount source. Supported specifications are _device_, __maj__**:**_min_, **LABEL=**__label__, **UUID=**__uuid__, **PARTLABEL=**__label__ and **PARTUUID=**__uuid__. + +*-s*, *--fstab*:: +Search in _/etc/fstab_. The output is in the list format (see *--list*). + +*-T*, *--target* _path_:: +Define the mount target. If _path_ is not a mountpoint file or directory, then *findmnt* checks the _path_ elements in reverse order to get the mountpoint (this feature is supported only when searching in kernel files and unsupported for *--fstab*). It's recommended to use the option *--mountpoint* when checks of _path_ elements are unwanted and _path_ is a strictly specified mountpoint. + +*-t*, *--types* _list_:: +Limit the set of printed filesystems. More than one type may be specified in a comma-separated list. The list of filesystem types can be prefixed with *no* to specify the filesystem types on which no action should be taken. For more details see *mount*(8). + +*--tree*:: +Enable tree-like output if possible. The options is silently ignored for tables where is missing child-parent relation (e.g., _fstab_). + +*--shadowed*:: +Print only filesystems over-mounted by another filesystem. + +*-U*, *--uniq*:: +Ignore filesystems with duplicate mount targets, thus effectively skipping over-mounted mount points. + +*-u*, *--notruncate*:: +Do not truncate text in columns. The default is to not truncate the *TARGET*, *SOURCE*, *UUID*, *LABEL*, *PARTUUID*, *PARTLABEL* columns. This option disables text truncation also in all other columns. + +*-v*, *--nofsroot*:: +Do not print a [/dir] in the SOURCE column for bind mounts or btrfs subvolumes. + +*-w*, *--timeout* _milliseconds_:: +Specify an upper limit on the time for which *--poll* will block, in milliseconds. + +*-x*, *--verify*:: +Check mount table content. The default is to verify _/etc/fstab_ parsability and usability. It's possible to use this option also with *--tab-file*. It's possible to specify source (device) or target (mountpoint) to filter mount table. The option *--verbose* forces *findmnt* to print more details. + +*--verbose*:: +Force *findmnt* to print more information (*--verify* only for now). + +*--vfs-all*:: +When used with *VFS-OPTIONS* column, print all VFS (fs-independent) flags. This option is designed for auditing purposes to list also default VFS kernel mount options which are normally not listed. + +*-y*, *--shell*:: +The column name will be modified to contain only characters allowed for shell variable identifiers. This is usable, for example, with *--pairs*. Note that this feature has been automatically enabled for *--pairs* in version 2.37, but due to compatibility issues, now it's necessary to request this behavior by *--shell*. + +include::man-common/help-version.adoc[] + +== EXIT STATUS + +The exit value is 0 if there is something to display, or 1 on any error +(for example if no filesystem is found based on the user's filter +specification, or the device path or mountpoint does not exist). + +== ENVIRONMENT + +*LIBMOUNT_FSTAB*=<path>:: +overrides the default location of the _fstab_ file + +*LIBMOUNT_MTAB*=<path>:: +overrides the default location of the _mtab_ file + +*LIBMOUNT_DEBUG*=all:: +enables libmount debug output + +*LIBSMARTCOLS_DEBUG*=all:: +enables libsmartcols debug output + +*LIBSMARTCOLS_DEBUG_PADDING*=on:: +use visible padding characters. + +== EXAMPLES + +*findmnt --fstab -t nfs*:: +Prints all NFS filesystems defined in _/etc/fstab_. + +*findmnt --fstab /mnt/foo*:: +Prints all _/etc/fstab_ filesystems where the mountpoint directory is _/mnt/foo_. It also prints bind mounts where _/mnt/foo_ is a source. + +*findmnt --fstab --target /mnt/foo*:: +Prints all _/etc/fstab_ filesystems where the mountpoint directory is _/mnt/foo_. + +*findmnt --fstab --evaluate*:: +Prints all _/etc/fstab_ filesystems and converts LABEL= and UUID= tags to the real device names. + +*findmnt -n --raw --evaluate --output=target LABEL=/boot*:: +Prints only the mountpoint where the filesystem with label "/boot" is mounted. + +*findmnt --poll --mountpoint /mnt/foo*:: +Monitors mount, unmount, remount and move on _/mnt/foo_. + +*findmnt --poll=umount --first-only --mountpoint /mnt/foo*:: +Waits for _/mnt/foo_ unmount. + +*findmnt --poll=remount -t ext3 -O ro*:: +Monitors remounts to read-only mode on all ext3 filesystems. + +== AUTHORS + +mailto:kzak@redhat.com[Karel Zak] + +== SEE ALSO + +*fstab*(5), +*mount*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/findmnt.c b/misc-utils/findmnt.c new file mode 100644 index 0000000..733bbc1 --- /dev/null +++ b/misc-utils/findmnt.c @@ -0,0 +1,1873 @@ +/* + * findmnt(8) + * + * Copyright (C) 2010-2015 Red Hat, Inc. All rights reserved. + * Written by Karel Zak <kzak@redhat.com> + * + * 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 <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <getopt.h> +#include <string.h> +#include <termios.h> +#ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +#endif +#include <assert.h> +#include <poll.h> +#include <sys/statvfs.h> +#include <sys/types.h> +#ifdef HAVE_LIBUDEV +# include <libudev.h> +#endif +#include <blkid.h> +#include <libmount.h> +#include <libsmartcols.h> + +#include "pathnames.h" +#include "nls.h" +#include "closestream.h" +#include "c.h" +#include "strutils.h" +#include "xalloc.h" +#include "optutils.h" +#include "mangle.h" +#include "buffer.h" + +#include "findmnt.h" + +/* column IDs */ +enum { + COL_ACTION, + COL_AVAIL, + COL_FREQ, + COL_FSROOT, + COL_FSTYPE, + COL_FS_OPTIONS, + COL_ID, + COL_LABEL, + COL_MAJMIN, + COL_OLD_OPTIONS, + COL_OLD_TARGET, + COL_OPTIONS, + COL_OPT_FIELDS, + COL_PARENT, + COL_PARTLABEL, + COL_PARTUUID, + COL_PASSNO, + COL_PROPAGATION, + COL_SIZE, + COL_SOURCE, + COL_SOURCES, + COL_TARGET, + COL_TID, + COL_USED, + COL_USEPERC, + COL_UUID, + COL_VFS_OPTIONS +}; + +enum { + TABTYPE_FSTAB = 1, + TABTYPE_MTAB, + TABTYPE_KERNEL +}; + +/* column names */ +struct colinfo { + const char *name; /* header */ + double whint; /* width hint (N < 1 is in percent of termwidth) */ + int flags; /* libsmartcols flags */ + const char *help; /* column description */ + const char *match; /* pattern for match_func() */ + void *match_data; /* match specific data */ +}; + +/* 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_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_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") }, + [COL_OLD_TARGET] = { "OLD-TARGET", 0.30, 0, N_("old mountpoint saved by --poll") }, + [COL_OPTIONS] = { "OPTIONS", 0.10, SCOLS_FL_TRUNC, N_("all mount options") }, + [COL_OPT_FIELDS] = { "OPT-FIELDS", 0.10, SCOLS_FL_TRUNC, N_("optional mount fields") }, + [COL_PARENT] = { "PARENT", 2, SCOLS_FL_RIGHT, N_("mount parent ID") }, + [COL_PARTLABEL] = { "PARTLABEL", 0.10, 0, N_("partition label") }, + [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_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_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") } +}; + +/* columns[] array specifies all currently wanted output column. The columns + * are defined by infos[] array and you can specify (on command line) each + * column twice. That's enough, dynamically allocated array of the columns is + * unnecessary overkill and over-engineering in this case */ +static int columns[ARRAY_SIZE(infos) * 2]; +static size_t ncolumns; + +static inline size_t err_columns_index(size_t arysz, size_t idx) +{ + if (idx >= arysz) + errx(EXIT_FAILURE, _("too many columns specified, " + "the limit is %zu columns"), + arysz - 1); + return idx; +} + +#define add_column(ary, n, id) \ + ((ary)[ err_columns_index(ARRAY_SIZE(ary), (n)) ] = (id)) + +/* poll actions (parsed --poll=<list> */ +#define FINDMNT_NACTIONS 4 /* mount, umount, move, remount */ +static int actions[FINDMNT_NACTIONS]; +static int nactions; + +/* global (accessed from findmnt-verify.c too) */ +unsigned int flags; +int parse_nerrors; +struct libmnt_cache *cache; + +static blkid_cache blk_cache; + +#ifdef HAVE_LIBUDEV +static struct udev *udev; +#endif + +static int match_func(struct libmnt_fs *fs, void *data __attribute__ ((__unused__))); + + +static int get_column_id(int num) +{ + assert(num >= 0); + assert((size_t) num < ncolumns); + assert((size_t) columns[num] < ARRAY_SIZE(infos)); + return columns[num]; +} + +static struct colinfo *get_column_info(int num) +{ + return &infos[ get_column_id(num) ]; +} + +static const char *column_id_to_name(int id) +{ + assert((size_t) id < ARRAY_SIZE(infos)); + return infos[id].name; +} + +static const char *get_column_name(int num) +{ + return get_column_info(num)->name; +} + +static float get_column_whint(int num) +{ + return get_column_info(num)->whint; +} + +static int get_column_flags(int num) +{ + return get_column_info(num)->flags; +} + +static const char *get_match(int id) +{ + assert((size_t) id < ARRAY_SIZE(infos)); + return infos[id].match; +} + +static void *get_match_data(int id) +{ + assert((size_t) id < ARRAY_SIZE(infos)); + return infos[id].match_data; +} + +static void set_match(int id, const char *match) +{ + assert((size_t) id < ARRAY_SIZE(infos)); + infos[id].match = match; +} + +static void set_match_data(int id, void *data) +{ + assert((size_t) id < ARRAY_SIZE(infos)); + infos[id].match_data = data; +} + +/* + * source match means COL_SOURCE *or* COL_MAJMIN, depends on + * data format. + */ +static void set_source_match(const char *data) +{ + int maj, min; + + if (sscanf(data, "%d:%d", &maj, &min) == 2) { + dev_t *devno = xmalloc(sizeof(dev_t)); + + *devno = makedev(maj, min); + set_match(COL_MAJMIN, data); + set_match_data(COL_MAJMIN, (void *) devno); + flags |= FL_NOSWAPMATCH; + } else + set_match(COL_SOURCE, data); +} + +/* + * Extra functionality for --target <path>. The function mnt_table_find_mountpoint() + * also checks parents (path elements in reverse order) to get mountpoint. + * + * @tb has to be from kernel (so no fstab or so)! + */ +static void enable_extra_target_match(struct libmnt_table *tb) +{ + char *cn = NULL; + const char *tgt = NULL, *mnt = NULL; + struct libmnt_fs *fs; + + /* + * Check if match pattern is mountpoint, if not use the + * real mountpoint. + */ + if (flags & FL_NOCACHE) + tgt = get_match(COL_TARGET); + else { + tgt = cn = mnt_resolve_path(get_match(COL_TARGET), cache); + if (!cn) + return; + } + + fs = mnt_table_find_mountpoint(tb, tgt, MNT_ITER_BACKWARD); + if (fs) + mnt = mnt_fs_get_target(fs); + if (mnt && strcmp(mnt, tgt) != 0) + set_match(COL_TARGET, xstrdup(mnt)); /* replace the current setting */ + + if (!cache) + free(cn); +} + + +static int is_tabdiff_column(int id) +{ + assert((size_t) id < ARRAY_SIZE(infos)); + + switch(id) { + case COL_ACTION: + case COL_OLD_TARGET: + case COL_OLD_OPTIONS: + return 1; + default: + break; + } + return 0; +} + +/* + * "findmnt" without any filter + */ +int is_listall_mode(void) +{ + if ((flags & FL_DF || flags & FL_REAL || flags & FL_PSEUDO) && !(flags & FL_ALL)) + return 0; + + return (!get_match(COL_SOURCE) && + !get_match(COL_TARGET) && + !get_match(COL_FSTYPE) && + !get_match(COL_OPTIONS) && + !get_match(COL_MAJMIN)); +} + +/* + * Returns 1 if the @act is in the --poll=<list> + */ +static int has_poll_action(int act) +{ + int i; + + if (!nactions) + return 1; /* all actions enabled */ + for (i = 0; i < nactions; i++) + if (actions[i] == act) + return 1; + return 0; +} + +static int poll_action_name_to_id(const char *name, size_t namesz) +{ + int id = -1; + + if (strncasecmp(name, "move", namesz) == 0 && namesz == 4) + id = MNT_TABDIFF_MOVE; + else if (strncasecmp(name, "mount", namesz) == 0 && namesz == 5) + id = MNT_TABDIFF_MOUNT; + else if (strncasecmp(name, "umount", namesz) == 0 && namesz == 6) + id = MNT_TABDIFF_UMOUNT; + else if (strncasecmp(name, "remount", namesz) == 0 && namesz == 7) + id = MNT_TABDIFF_REMOUNT; + else + warnx(_("unknown action: %s"), name); + + return id; +} + +/* + * findmnt --first-only <devname|TAG=|mountpoint> + * + * ... it works like "mount <devname|TAG=|mountpoint>" + */ +static int is_mount_compatible_mode(void) +{ + if (!get_match(COL_SOURCE)) + return 0; /* <devname|TAG=|mountpoint> is required */ + if (get_match(COL_FSTYPE) || get_match(COL_OPTIONS)) + return 0; /* cannot be restricted by -t or -O */ + if (!(flags & FL_FIRSTONLY)) + return 0; /* we have to return the first entry only */ + + return 1; /* ok */ +} + +static void disable_columns_truncate(void) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(infos); i++) + infos[i].flags &= ~SCOLS_FL_TRUNC; +} + +/* + * converts @name to column ID + */ +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 = column_id_to_name(i); + + if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) + return i; + } + warnx(_("unknown column: %s"), name); + return -1; +} + + +#ifdef HAVE_LIBUDEV +static char *get_tag_from_udev(const char *devname, int col) +{ + struct udev_device *dev; + const char *data = NULL; + char *res = NULL, *path; + + if (!udev) + udev = udev_new(); + if (!udev) + return NULL; + + /* libudev don't like /dev/mapper/ symlinks */ + path = realpath(devname, NULL); + if (path) + devname = path; + + if (strncmp(devname, "/dev/", 5) == 0) + devname += 5; + + dev = udev_device_new_from_subsystem_sysname(udev, "block", devname); + free(path); + + if (!dev) + return NULL; + + switch(col) { + case COL_LABEL: + data = udev_device_get_property_value(dev, "ID_FS_LABEL_ENC"); + break; + case COL_UUID: + data = udev_device_get_property_value(dev, "ID_FS_UUID_ENC"); + break; + case COL_PARTUUID: + data = udev_device_get_property_value(dev, "ID_PART_ENTRY_UUID"); + break; + case COL_PARTLABEL: + data = udev_device_get_property_value(dev, "ID_PART_ENTRY_NAME"); + break; + default: + break; + } + + if (data) { + res = xstrdup(data); + unhexmangle_string(res); + } + + udev_device_unref(dev); + return res; +} +#endif /* HAVE_LIBUDEV */ + +/* Returns LABEL or UUID */ +static char *get_tag(struct libmnt_fs *fs, const char *tagname, int col +#ifndef HAVE_LIBUDEV + __attribute__((__unused__)) +#endif + ) +{ + const char *t, *v; + char *res = NULL; + + if (!mnt_fs_get_tag(fs, &t, &v) && !strcmp(t, tagname)) + res = xstrdup(v); + else { + const char *dev = mnt_fs_get_source(fs); + + if (dev && !(flags & FL_NOCACHE)) + dev = mnt_resolve_spec(dev, cache); +#ifdef HAVE_LIBUDEV + if (dev) + res = get_tag_from_udev(dev, col); +#endif + if (!res) { + res = mnt_cache_find_tag_value(cache, dev, tagname); + if (res && cache) + /* don't return pointer to cache */ + res = xstrdup(res); + } + } + + return res; +} + +static char *get_vfs_attr(struct libmnt_fs *fs, int sizetype) +{ + struct statvfs buf; + uint64_t vfs_attr = 0; + char *sizestr; + + if (statvfs(mnt_fs_get_target(fs), &buf) != 0) + return NULL; + + switch(sizetype) { + case COL_SIZE: + vfs_attr = buf.f_frsize * buf.f_blocks; + break; + case COL_AVAIL: + vfs_attr = buf.f_frsize * buf.f_bavail; + break; + case COL_USED: + vfs_attr = buf.f_frsize * (buf.f_blocks - buf.f_bfree); + break; + case COL_USEPERC: + if (buf.f_blocks == 0) + return xstrdup("-"); + + xasprintf(&sizestr, "%.0f%%", + (double)(buf.f_blocks - buf.f_bfree) / + buf.f_blocks * 100); + return sizestr; + } + + if (!vfs_attr) + sizestr = xstrdup("0"); + else if (flags & FL_BYTES) + xasprintf(&sizestr, "%ju", vfs_attr); + else + sizestr = size_to_human_string(SIZE_SUFFIX_1LETTER, vfs_attr); + + return sizestr; +} + +/* reads sources from libmount/libblkid + */ +static char *get_data_col_sources(struct libmnt_fs *fs, int evaluate) +{ + const char *tag = NULL, *p = NULL; + int i = 0; + const char *device = NULL; + char *val = NULL; + blkid_dev_iterate iter; + blkid_dev dev; + struct ul_buffer buf = UL_INIT_BUFFER; + + /* get TAG from libmount if avalable (e.g. fstab) */ + if (mnt_fs_get_tag(fs, &tag, &p) == 0) { + + /* if device is in the form 'UUID=..' or 'LABEL=..' and evaluate==0 + * then it is ok to do not search for multiple devices + */ + if (!evaluate) + goto nothing; + val = xstrdup(p); + } + + /* or get device name */ + else if (!(device = mnt_fs_get_srcpath(fs)) + || strncmp(device, "/dev/", 5) != 0) + goto nothing; + + if (!blk_cache) { + if (blkid_get_cache(&blk_cache, NULL) != 0) + goto nothing; + blkid_probe_all(blk_cache); + } + + /* ask libblkid for the UUID */ + if (!val) { + assert(device); + + tag = "UUID"; + val = blkid_get_tag_value(blk_cache, "UUID", device); /* returns allocated string */ + if (!val) + goto nothing; + } + + assert(val); + assert(tag); + + /* scan all devices for the TAG */ + iter = blkid_dev_iterate_begin(blk_cache); + blkid_dev_set_search(iter, tag, val); + + while (blkid_dev_next(iter, &dev) == 0) { + dev = blkid_verify(blk_cache, dev); + if (!dev) + continue; + if (i != 0) + ul_buffer_append_data(&buf, "\n", 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); + +nothing: + free(val); + return NULL; +} + +/* reads FS data from libmount + */ +static char *get_data(struct libmnt_fs *fs, int num) +{ + char *str = NULL; + int col_id = get_column_id(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); + if (str) + break; + + /* fallthrough */ + case COL_SOURCE: + { + const char *root = mnt_fs_get_root(fs); + const char *spec = mnt_fs_get_srcpath(fs); + char *cn = NULL; + + if (spec && (flags & FL_CANONICALIZE)) + spec = cn = mnt_resolve_path(spec, cache); + if (!spec) { + spec = mnt_fs_get_source(fs); + + if (spec && (flags & FL_EVALUATE)) + spec = cn = mnt_resolve_spec(spec, cache); + } + if (root && spec && !(flags & FL_NOFSROOT) && strcmp(root, "/") != 0) + xasprintf(&str, "%s[%s]", spec, root); + else if (spec) + str = xstrdup(spec); + if (!cache) + free(cn); + break; + } + + case COL_TARGET: + if (mnt_fs_get_target(fs)) + str = xstrdup(mnt_fs_get_target(fs)); + break; + case COL_FSTYPE: + if (mnt_fs_get_fstype(fs)) + str = xstrdup(mnt_fs_get_fstype(fs)); + break; + case COL_OPTIONS: + if (mnt_fs_get_options(fs)) + str = xstrdup(mnt_fs_get_options(fs)); + break; + case COL_VFS_OPTIONS: + if (flags & FL_VFS_ALL) + str = mnt_fs_get_vfs_options_all(fs); + else if (mnt_fs_get_vfs_options(fs)) + str = xstrdup(mnt_fs_get_vfs_options(fs)); + break; + case COL_FS_OPTIONS: + if (mnt_fs_get_fs_options(fs)) + str = xstrdup(mnt_fs_get_fs_options(fs)); + break; + case COL_OPT_FIELDS: + if (mnt_fs_get_optional_fields(fs)) + str = xstrdup(mnt_fs_get_optional_fields(fs)); + break; + case COL_UUID: + str = get_tag(fs, "UUID", col_id); + break; + case COL_PARTUUID: + str = get_tag(fs, "PARTUUID", col_id); + break; + case COL_LABEL: + str = get_tag(fs, "LABEL", col_id); + break; + case COL_PARTLABEL: + str = get_tag(fs, "PARTLABEL", col_id); + break; + + case COL_MAJMIN: + { + dev_t devno = mnt_fs_get_devno(fs); + if (!devno) + break; + + if ((flags & FL_RAW) || (flags & FL_EXPORT) || (flags & FL_JSON)) + xasprintf(&str, "%u:%u", major(devno), minor(devno)); + else + xasprintf(&str, "%3u:%-3u", major(devno), minor(devno)); + break; + } + case COL_SIZE: + case COL_AVAIL: + case COL_USED: + case COL_USEPERC: + str = get_vfs_attr(fs, col_id); + break; + case COL_FSROOT: + if (mnt_fs_get_root(fs)) + str = xstrdup(mnt_fs_get_root(fs)); + break; + case COL_TID: + if (mnt_fs_get_tid(fs)) + xasprintf(&str, "%d", mnt_fs_get_tid(fs)); + break; + case COL_ID: + if (mnt_fs_get_id(fs)) + xasprintf(&str, "%d", mnt_fs_get_id(fs)); + break; + case COL_PARENT: + if (mnt_fs_get_parent_id(fs)) + xasprintf(&str, "%d", mnt_fs_get_parent_id(fs)); + break; + case COL_PROPAGATION: + if (mnt_fs_is_kernel(fs)) { + unsigned long fl = 0; + char *n = NULL; + + if (mnt_fs_get_propagation(fs, &fl) != 0) + break; + + n = xstrdup((fl & MS_SHARED) ? "shared" : "private"); + + if (fl & MS_SLAVE) { + xasprintf(&str, "%s,slave", n); + free(n); + n = str; + } + if (fl & MS_UNBINDABLE) { + xasprintf(&str, "%s,unbindable", n); + free(n); + n = str; + } + str = n; + } + break; + case COL_FREQ: + if (!mnt_fs_is_kernel(fs)) + xasprintf(&str, "%d", mnt_fs_get_freq(fs)); + break; + case COL_PASSNO: + if (!mnt_fs_is_kernel(fs)) + xasprintf(&str, "%d", mnt_fs_get_passno(fs)); + break; + default: + break; + } + return str; +} + +static char *get_tabdiff_data(struct libmnt_fs *old_fs, + struct libmnt_fs *new_fs, + int change, + int num) +{ + char *str = NULL; + + switch (get_column_id(num)) { + case COL_ACTION: + switch (change) { + case MNT_TABDIFF_MOUNT: + str = _("mount"); + break; + case MNT_TABDIFF_UMOUNT: + str = _("umount"); + break; + case MNT_TABDIFF_REMOUNT: + str = _("remount"); + break; + case MNT_TABDIFF_MOVE: + str = _("move"); + break; + default: + str = _("unknown"); + break; + } + str = xstrdup(str); + break; + case COL_OLD_OPTIONS: + if (old_fs && (change == MNT_TABDIFF_REMOUNT || + change == MNT_TABDIFF_UMOUNT) + && mnt_fs_get_options(old_fs)) + str = xstrdup(mnt_fs_get_options(old_fs)); + break; + case COL_OLD_TARGET: + if (old_fs && (change == MNT_TABDIFF_MOVE || + change == MNT_TABDIFF_UMOUNT) + && mnt_fs_get_target(old_fs)) + str = xstrdup(mnt_fs_get_target(old_fs)); + break; + default: + if (new_fs) + str = get_data(new_fs, num); + else + str = get_data(old_fs, num); + break; + } + return str; +} + +/* 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) +{ + size_t i; + struct libscols_line *line = scols_table_new_line(table, parent); + + if (!line) + 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")); + } + + scols_line_set_userdata(line, fs); + return line; +} + +static struct libscols_line *add_tabdiff_line(struct libscols_table *table, struct libmnt_fs *new_fs, + struct libmnt_fs *old_fs, int change) +{ + size_t i; + struct libscols_line *line = scols_table_new_line(table, NULL); + + if (!line) + 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")); + } + + return line; +} + +static int has_line(struct libscols_table *table, struct libmnt_fs *fs) +{ + struct libscols_line *ln; + struct libscols_iter *itr; + int rc = 0; + + itr = scols_new_iter(SCOLS_ITER_FORWARD); + if (!itr) + return 0; + + while (scols_table_next_line(table, itr, &ln) == 0) { + if ((struct libmnt_fs *) scols_line_get_userdata(ln) == fs) { + rc = 1; + break; + } + } + + scols_free_iter(itr); + return rc; +} + +/* reads filesystems from @tb (libmount) and fillin @table (output table) */ +static int create_treenode(struct libscols_table *table, struct libmnt_table *tb, + struct libmnt_fs *fs, struct libscols_line *parent_line) +{ + struct libmnt_fs *chld = NULL; + struct libmnt_iter *itr = NULL; + struct libscols_line *line; + int rc = -1, first = 0; + + if (!fs) { + /* first call, get root FS */ + if (mnt_table_get_root_fs(tb, &fs)) + goto leave; + parent_line = NULL; + first = 1; + + } else if ((flags & FL_SUBMOUNTS) && has_line(table, fs)) + return 0; + + itr = mnt_new_iter(MNT_ITER_FORWARD); + if (!itr) + goto leave; + + if ((flags & FL_SUBMOUNTS) || match_func(fs, NULL)) { + line = add_line(table, fs, parent_line); + if (!line) + goto leave; + } else + line = parent_line; + + /* + * add all children to the output table + */ + while (mnt_table_next_child_fs(tb, itr, fs, &chld) == 0) { + if (create_treenode(table, tb, chld, line)) + goto leave; + } + rc = 0; + + /* make sure all entries are in the tree */ + if (first && (size_t) mnt_table_get_nents(tb) > + (size_t) scols_table_get_nlines(table)) { + mnt_reset_iter(itr, MNT_ITER_FORWARD); + fs = NULL; + + while (mnt_table_next_fs(tb, itr, &fs) == 0) { + if (!has_line(table, fs) && match_func(fs, NULL)) + create_treenode(table, tb, fs, NULL); + } + } +leave: + mnt_free_iter(itr); + return rc; +} + +/* error callback */ +static int parser_errcb(struct libmnt_table *tb __attribute__ ((__unused__)), + const char *filename, int line) +{ + warnx(_("%s: parse error at line %d -- ignored"), filename, line); + ++parse_nerrors; + return 1; +} + +static char **append_tabfile(char **files, int *nfiles, char *filename) +{ + files = xrealloc(files, sizeof(char *) * (*nfiles + 1)); + files[(*nfiles)++] = filename; + return files; +} + +static char **append_pid_tabfile(char **files, int *nfiles, pid_t pid) +{ + char *path = NULL; + + xasprintf(&path, "/proc/%d/mountinfo", (int) pid); + return append_tabfile(files, nfiles, path); +} + +/* calls libmount fstab/mtab/mountinfo parser */ +static struct libmnt_table *parse_tabfiles(char **files, + int nfiles, + int tabtype) +{ + struct libmnt_table *tb; + int rc = 0; + + tb = mnt_new_table(); + if (!tb) { + warn(_("failed to initialize libmount table")); + return NULL; + } + mnt_table_set_parser_errcb(tb, parser_errcb); + + do { + /* NULL means that libmount will use default paths */ + const char *path = nfiles ? *files++ : NULL; + + switch (tabtype) { + case TABTYPE_FSTAB: + rc = mnt_table_parse_fstab(tb, path); + break; + case TABTYPE_MTAB: + rc = mnt_table_parse_mtab(tb, path); + break; + case TABTYPE_KERNEL: + if (!path) + path = access(_PATH_PROC_MOUNTINFO, R_OK) == 0 ? + _PATH_PROC_MOUNTINFO : + _PATH_PROC_MOUNTS; + + rc = mnt_table_parse_file(tb, path); + break; + } + if (rc) { + mnt_unref_table(tb); + warn(_("can't read %s"), path); + return NULL; + } + } while (--nfiles > 0); + + return tb; +} + +/* + * Parses mountinfo and calls mnt_cache_set_targets(cache, mtab). Only + * necessary if @tb in main() was read from a non-kernel source. + */ +static void cache_set_targets(struct libmnt_cache *tmp) +{ + struct libmnt_table *tb; + const char *path; + + tb = mnt_new_table(); + if (!tb) + return; + + path = access(_PATH_PROC_MOUNTINFO, R_OK) == 0 ? + _PATH_PROC_MOUNTINFO : + _PATH_PROC_MOUNTS; + + if (mnt_table_parse_file(tb, path) == 0) + mnt_cache_set_targets(tmp, tb); + + mnt_unref_table(tb); +} + +/* checks if @tb contains parent->child relations */ +static int tab_is_tree(struct libmnt_table *tb) +{ + struct libmnt_fs *fs = NULL; + struct libmnt_iter *itr; + int rc = 0; + + itr = mnt_new_iter(MNT_ITER_BACKWARD); + if (!itr) + return 0; + + rc = (mnt_table_next_fs(tb, itr, &fs) == 0 && + mnt_fs_is_kernel(fs) && + mnt_fs_get_root(fs)); + + mnt_free_iter(itr); + return rc; +} + +/* checks if all fs in @tb are from kernel */ +static int tab_is_kernel(struct libmnt_table *tb) +{ + struct libmnt_fs *fs = NULL; + struct libmnt_iter *itr; + + itr = mnt_new_iter(MNT_ITER_BACKWARD); + if (!itr) + return 0; + + while (mnt_table_next_fs(tb, itr, &fs) == 0) { + if (!mnt_fs_is_kernel(fs)) { + mnt_free_iter(itr); + return 0; + } + } + + mnt_free_iter(itr); + return 1; +} + +/* filter function for libmount (mnt_table_find_next_fs()) */ +static int match_func(struct libmnt_fs *fs, + void *data __attribute__ ((__unused__))) +{ + int rc = flags & FL_INVERT ? 1 : 0; + const char *m; + void *md; + + m = get_match(COL_FSTYPE); + if (m && !mnt_fs_match_fstype(fs, m)) + return rc; + + m = get_match(COL_OPTIONS); + if (m && !mnt_fs_match_options(fs, m)) + return rc; + + md = get_match_data(COL_MAJMIN); + if (md && mnt_fs_get_devno(fs) != *((dev_t *) md)) + return rc; + + m = get_match(COL_TARGET); + if (m && !mnt_fs_match_target(fs, m, cache)) + return rc; + + m = get_match(COL_SOURCE); + if (m && !mnt_fs_match_source(fs, m, cache)) + return rc; + + if ((flags & FL_DF) && !(flags & FL_ALL)) { + const char *type = mnt_fs_get_fstype(fs); + + if (type && strstr(type, "tmpfs")) /* tmpfs is wanted */ + return !rc; + + if (mnt_fs_is_pseudofs(fs)) + return rc; + } + + if ((flags & FL_REAL) && mnt_fs_is_pseudofs(fs)) + return rc; + + if ((flags & FL_PSEUDO) && !mnt_fs_is_pseudofs(fs)) + return rc; + + if ((flags & FL_SHADOWED)) { + struct libmnt_table *tb = NULL; + + mnt_fs_get_table(fs, &tb); + if (tb && mnt_table_over_fs(tb, fs, NULL) != 0) + return rc; + } + + return !rc; +} + +/* iterate over filesystems in @tb */ +struct libmnt_fs *get_next_fs(struct libmnt_table *tb, + struct libmnt_iter *itr) +{ + struct libmnt_fs *fs = NULL; + + if (is_listall_mode()) { + /* + * Print whole file + */ + if (mnt_table_next_fs(tb, itr, &fs) != 0) + return NULL; + + } else if (is_mount_compatible_mode()) { + /* + * Look up for FS in the same way how mount(8) searches in fstab + * + * findmnt -f <spec> + */ + fs = mnt_table_find_source(tb, get_match(COL_SOURCE), + mnt_iter_get_direction(itr)); + + if (!fs && !(flags & FL_NOSWAPMATCH)) + fs = mnt_table_find_target(tb, get_match(COL_SOURCE), + mnt_iter_get_direction(itr)); + } else { + /* + * Look up for all matching entries + * + * findmnt [-l] <source> <target> [-O <options>] [-t <types>] + * findmnt [-l] <spec> [-O <options>] [-t <types>] + */ +again: + if (mnt_table_find_next_fs(tb, itr, match_func, NULL, &fs) != 0) + fs = NULL; + + if (!fs && + !(flags & FL_NOSWAPMATCH) && + !get_match(COL_TARGET) && get_match(COL_SOURCE)) { + + /* swap 'spec' and target. */ + set_match(COL_TARGET, get_match(COL_SOURCE)); + set_match(COL_SOURCE, NULL); + mnt_reset_iter(itr, -1); + + goto again; + } + } + + return fs; +} + +/* + * Filter out unwanted lines for --list output or top level lines for + * --submounts tree output. + */ +static int add_matching_lines(struct libmnt_table *tb, + struct libscols_table *table, int direction) +{ + struct libmnt_iter *itr; + struct libmnt_fs *fs; + int nlines = 0, rc = -1; + + itr = mnt_new_iter(direction); + if (!itr) { + warn(_("failed to initialize libmount iterator")); + goto done; + } + + while((fs = get_next_fs(tb, itr))) { + if ((flags & FL_TREE) || (flags & FL_SUBMOUNTS)) + rc = create_treenode(table, tb, fs, NULL); + else + rc = !add_line(table, fs, NULL); + if (rc) + goto done; + nlines++; + if (flags & FL_FIRSTONLY) + break; + flags |= FL_NOSWAPMATCH; + } + + if (nlines) + rc = 0; +done: + mnt_free_iter(itr); + return rc; +} + +static int poll_match(struct libmnt_fs *fs) +{ + int rc = match_func(fs, NULL); + + if (rc == 0 && !(flags & FL_NOSWAPMATCH) && + get_match(COL_SOURCE) && !get_match(COL_TARGET)) { + /* + * findmnt --poll /foo + * The '/foo' maybe source as well as target. + */ + const char *str = get_match(COL_SOURCE); + + set_match(COL_TARGET, str); /* swap */ + set_match(COL_SOURCE, NULL); + + rc = match_func(fs, NULL); + + set_match(COL_TARGET, NULL); /* restore */ + set_match(COL_SOURCE, str); + + } + return rc; +} + +static int poll_table(struct libmnt_table *tb, const char *tabfile, + int timeout, struct libscols_table *table, int direction) +{ + FILE *f = NULL; + int rc = -1; + struct libmnt_iter *itr = NULL; + struct libmnt_table *tb_new; + struct libmnt_tabdiff *diff = NULL; + struct pollfd fds[1]; + + tb_new = mnt_new_table(); + if (!tb_new) { + warn(_("failed to initialize libmount table")); + goto done; + } + + itr = mnt_new_iter(direction); + if (!itr) { + warn(_("failed to initialize libmount iterator")); + goto done; + } + + diff = mnt_new_tabdiff(); + if (!diff) { + warn(_("failed to initialize libmount tabdiff")); + goto done; + } + + /* cache is unnecessary to detect changes */ + mnt_table_set_cache(tb, NULL); + mnt_table_set_cache(tb_new, NULL); + + f = fopen(tabfile, "r"); + if (!f) { + warn(_("cannot open %s"), tabfile); + goto done; + } + + mnt_table_set_parser_errcb(tb_new, parser_errcb); + + fds[0].fd = fileno(f); + fds[0].events = POLLPRI; + + while (1) { + struct libmnt_table *tmp; + struct libmnt_fs *old, *new; + int change, count; + + count = poll(fds, 1, timeout); + if (count == 0) + break; /* timeout */ + if (count < 0) { + warn(_("poll() failed")); + goto done; + } + + rewind(f); + rc = mnt_table_parse_stream(tb_new, f, tabfile); + if (!rc) + rc = mnt_diff_tables(diff, tb, tb_new); + if (rc < 0) + goto done; + + count = 0; + mnt_reset_iter(itr, direction); + while(mnt_tabdiff_next_change( + diff, itr, &old, &new, &change) == 0) { + + if (!has_poll_action(change)) + continue; + if (!poll_match(new ? new : old)) + continue; + count++; + rc = !add_tabdiff_line(table, new, old, change); + if (rc) + goto done; + if (flags & FL_FIRSTONLY) + break; + } + + if (count) { + rc = scols_table_print_range(table, NULL, NULL); + if (rc == 0) + fputc('\n', scols_table_get_stream(table)); + fflush(stdout); + if (rc) + goto done; + } + + /* swap tables */ + tmp = tb; + tb = tb_new; + tb_new = tmp; + + /* remove already printed lines to reduce memory usage */ + scols_table_remove_lines(table); + mnt_reset_table(tb_new); + + if (count && (flags & FL_FIRSTONLY)) + break; + } + + rc = 0; +done: + mnt_unref_table(tb_new); + mnt_free_tabdiff(diff); + mnt_free_iter(itr); + if (f) + fclose(f); + return rc; +} + +static int uniq_fs_target_cmp( + struct libmnt_table *tb __attribute__((__unused__)), + struct libmnt_fs *a, + struct libmnt_fs *b) +{ + return !mnt_fs_match_target(a, mnt_fs_get_target(b), cache); +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + size_t i; + + fputs(USAGE_HEADER, out); + fprintf(out, _( + " %1$s [options]\n" + " %1$s [options] <device> | <mountpoint>\n" + " %1$s [options] <device> <mountpoint>\n" + " %1$s [options] [--source <device>] [--target <path> | --mountpoint <dir>]\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Find a (mounted) filesystem.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -s, --fstab search in static table of filesystems\n"), out); + fputs(_(" -m, --mtab search in table of mounted filesystems\n" + " (includes user space mount options)\n"), out); + fputs(_(" -k, --kernel search in kernel table of mounted\n" + " filesystems (default)\n"), out); + fputc('\n', out); + fputs(_(" -p, --poll[=<list>] monitor changes in table of mounted filesystems\n"), out); + fputs(_(" -w, --timeout <num> upper limit in milliseconds that --poll will block\n"), out); + fputc('\n', out); + + fputs(_(" -A, --all disable all built-in filters, print all filesystems\n"), out); + fputs(_(" -a, --ascii use ASCII chars for tree formatting\n"), out); + fputs(_(" -b, --bytes print sizes in bytes rather than in human readable format\n"), out); + fputs(_(" -C, --nocanonicalize don't canonicalize when comparing paths\n"), out); + fputs(_(" -c, --canonicalize canonicalize printed paths\n"), out); + fputs(_(" -D, --df imitate the output of df(1)\n"), out); + fputs(_(" -d, --direction <word> direction of search, 'forward' or 'backward'\n"), out); + fputs(_(" -e, --evaluate convert tags (LABEL,UUID,PARTUUID,PARTLABEL) \n" + " 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, --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(_(" --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); + fputs(_(" --shadowed print only filesystems over-mounted by another filesystem\n"), out); + fputs(_(" -R, --submounts print all submounts for the matching filesystems\n"), out); + fputs(_(" -r, --raw use raw output format\n"), out); + fputs(_(" --real print only real filesystems\n"), out); + fputs(_(" -S, --source <string> the device to mount (by name, maj:min, \n" + " LABEL=, UUID=, PARTUUID=, PARTLABEL=)\n"), out); + fputs(_(" -T, --target <path> the path to the filesystem to use\n"), out); + fputs(_(" --tree enable tree format output if possible\n"), out); + fputs(_(" -M, --mountpoint <dir> the mountpoint directory\n"), out); + fputs(_(" -t, --types <list> limit the set of filesystems by FS types\n"), out); + fputs(_(" -U, --uniq ignore filesystems with duplicate target\n"), out); + fputs(_(" -u, --notruncate don't truncate text in columns\n"), out); + fputs(_(" -v, --nofsroot don't print [/dir] for bind or btrfs mounts\n"), out); + fputs(_(" -y, --shell use column names to be usable as shell variable identifiers\n"), out); + + fputc('\n', out); + fputs(_(" -x, --verify verify mount table content (default is fstab)\n"), out); + fputs(_(" --verbose print more details\n"), out); + fputs(_(" --vfs-all print all VFS options\n"), out); + + fputs(USAGE_SEPARATOR, out); + printf(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)); + + printf(USAGE_MAN_TAIL("findmnt(8)")); + + 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 c, rc = -1, timeout = -1; + int ntabfiles = 0, tabtype = 0; + char *outarg = NULL; + size_t i; + int force_tree = 0, istree = 0; + + struct libscols_table *table = NULL; + + enum { + FINDMNT_OPT_VERBOSE = CHAR_MAX + 1, + FINDMNT_OPT_TREE, + FINDMNT_OPT_OUTPUT_ALL, + FINDMNT_OPT_PSEUDO, + FINDMNT_OPT_REAL, + FINDMNT_OPT_VFS_ALL, + FINDMNT_OPT_SHADOWED + }; + + static const struct option longopts[] = { + { "all", no_argument, NULL, 'A' }, + { "ascii", no_argument, NULL, 'a' }, + { "bytes", no_argument, NULL, 'b' }, + { "canonicalize", no_argument, NULL, 'c' }, + { "direction", required_argument, NULL, 'd' }, + { "df", no_argument, NULL, 'D' }, + { "evaluate", no_argument, NULL, 'e' }, + { "first-only", no_argument, NULL, 'f' }, + { "fstab", no_argument, NULL, 's' }, + { "help", no_argument, NULL, 'h' }, + { "invert", no_argument, NULL, 'i' }, + { "json", no_argument, NULL, 'J' }, + { "kernel", no_argument, NULL, 'k' }, + { "list", no_argument, NULL, 'l' }, + { "mountpoint", required_argument, NULL, 'M' }, + { "mtab", no_argument, NULL, 'm' }, + { "noheadings", no_argument, NULL, 'n' }, + { "notruncate", no_argument, NULL, 'u' }, + { "options", required_argument, NULL, 'O' }, + { "output", required_argument, NULL, 'o' }, + { "output-all", no_argument, NULL, FINDMNT_OPT_OUTPUT_ALL }, + { "poll", optional_argument, NULL, 'p' }, + { "pairs", no_argument, NULL, 'P' }, + { "raw", no_argument, NULL, 'r' }, + { "types", required_argument, NULL, 't' }, + { "nocanonicalize", no_argument, NULL, 'C' }, + { "nofsroot", no_argument, NULL, 'v' }, + { "submounts", no_argument, NULL, 'R' }, + { "source", required_argument, NULL, 'S' }, + { "tab-file", required_argument, NULL, 'F' }, + { "task", required_argument, NULL, 'N' }, + { "target", required_argument, NULL, 'T' }, + { "timeout", required_argument, NULL, 'w' }, + { "uniq", no_argument, NULL, 'U' }, + { "verify", no_argument, NULL, 'x' }, + { "version", no_argument, NULL, 'V' }, + { "shell", no_argument, NULL, 'y' }, + { "verbose", no_argument, NULL, FINDMNT_OPT_VERBOSE }, + { "tree", no_argument, NULL, FINDMNT_OPT_TREE }, + { "real", no_argument, NULL, FINDMNT_OPT_REAL }, + { "pseudo", no_argument, NULL, FINDMNT_OPT_PSEUDO }, + { "vfs-all", no_argument, NULL, FINDMNT_OPT_VFS_ALL }, + { "shadowed", no_argument, NULL, FINDMNT_OPT_SHADOWED }, + { NULL, 0, NULL, 0 } + }; + + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { 'C', 'c'}, /* [no]canonicalize */ + { 'C', 'e' }, /* nocanonicalize, evaluate */ + { 'J', 'P', 'r','x' }, /* json,pairs,raw,verify */ + { 'M', 'T' }, /* mountpoint, target */ + { 'N','k','m','s' }, /* task,kernel,mtab,fstab */ + { 'P','l','r','x' }, /* pairs,list,raw,verify */ + { 'p','x' }, /* poll,verify */ + { 'm','p','s' }, /* mtab,poll,fstab */ + { FINDMNT_OPT_PSEUDO, FINDMNT_OPT_REAL }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + /* default output format */ + flags |= FL_TREE; + + while ((c = getopt_long(argc, argv, + "AabCcDd:ehiJfF:o:O:p::PklmM:nN:rst:uvRS:T:Uw:Vxy", + longopts, NULL)) != -1) { + + err_exclusive_options(c, longopts, excl, excl_st); + + switch(c) { + case 'A': + flags |= FL_ALL; + break; + case 'a': + flags |= FL_ASCII; + break; + case 'b': + flags |= FL_BYTES; + break; + case 'C': + flags |= FL_NOCACHE; + break; + case 'c': + flags |= FL_CANONICALIZE; + break; + case 'D': + flags &= ~FL_TREE; + flags |= FL_DF; + break; + case 'd': + if (!strcmp(optarg, "forward")) + direction = MNT_ITER_FORWARD; + else if (!strcmp(optarg, "backward")) + direction = MNT_ITER_BACKWARD; + else + errx(EXIT_FAILURE, + _("unknown direction '%s'"), optarg); + break; + case 'e': + flags |= FL_EVALUATE; + break; + case 'i': + flags |= FL_INVERT; + break; + case 'J': + flags |= FL_JSON; + break; + case 'f': + flags |= FL_FIRSTONLY; + break; + case 'F': + tabfiles = append_tabfile(tabfiles, &ntabfiles, optarg); + break; + case 'u': + disable_columns_truncate(); + break; + case 'o': + outarg = optarg; + break; + case FINDMNT_OPT_OUTPUT_ALL: + ncolumns = 0; + for (i = 0; i < ARRAY_SIZE(infos); i++) { + if (is_tabdiff_column(i)) + continue; + columns[ncolumns++] = i; + } + break; + case 'O': + set_match(COL_OPTIONS, optarg); + break; + case 'p': + if (optarg) { + nactions = string_to_idarray(optarg, + actions, ARRAY_SIZE(actions), + poll_action_name_to_id); + if (nactions < 0) + exit(EXIT_FAILURE); + } + flags |= FL_POLL; + flags &= ~FL_TREE; + break; + case 'P': + flags |= FL_EXPORT; + flags &= ~FL_TREE; + break; + case 'm': /* mtab */ + tabtype = TABTYPE_MTAB; + flags &= ~FL_TREE; + break; + case 's': /* fstab */ + tabtype = TABTYPE_FSTAB; + flags &= ~FL_TREE; + break; + case 'k': /* kernel (mountinfo) */ + tabtype = TABTYPE_KERNEL; + break; + case 't': + set_match(COL_FSTYPE, optarg); + break; + case 'r': + flags &= ~FL_TREE; /* disable the default */ + flags |= FL_RAW; /* enable raw */ + break; + case 'l': + flags &= ~FL_TREE; /* disable the default */ + break; + case 'n': + flags |= FL_NOHEADINGS; + break; + case 'N': + tabtype = TABTYPE_KERNEL; + tabfiles = append_pid_tabfile(tabfiles, &ntabfiles, + strtou32_or_err(optarg, + _("invalid TID argument"))); + break; + case 'v': + flags |= FL_NOFSROOT; + break; + case 'R': + flags |= FL_SUBMOUNTS; + break; + case 'S': + set_source_match(optarg); + flags |= FL_NOSWAPMATCH; + break; + case 'M': + flags |= FL_STRICTTARGET; + /* fallthrough */ + case 'T': + set_match(COL_TARGET, optarg); + flags |= FL_NOSWAPMATCH; + break; + case 'U': + flags |= FL_UNIQ; + break; + case 'w': + timeout = strtos32_or_err(optarg, _("invalid timeout argument")); + break; + case 'x': + verify = 1; + break; + case 'y': + flags |= FL_SHELLVAR; + break; + case FINDMNT_OPT_VERBOSE: + flags |= FL_VERBOSE; + break; + case FINDMNT_OPT_TREE: + force_tree = 1; + break; + case FINDMNT_OPT_PSEUDO: + flags |= FL_PSEUDO; + break; + case FINDMNT_OPT_REAL: + flags |= FL_REAL; + break; + case FINDMNT_OPT_VFS_ALL: + flags |= FL_VFS_ALL; + break; + case FINDMNT_OPT_SHADOWED: + flags |= FL_SHADOWED; + break; + case 'h': + usage(); + case 'V': + print_version(EXIT_SUCCESS); + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (!ncolumns && (flags & FL_DF)) { + add_column(columns, ncolumns++, COL_SOURCE); + add_column(columns, ncolumns++, COL_FSTYPE); + add_column(columns, ncolumns++, COL_SIZE); + add_column(columns, ncolumns++, COL_USED); + add_column(columns, ncolumns++, COL_AVAIL); + add_column(columns, ncolumns++, COL_USEPERC); + add_column(columns, ncolumns++, COL_TARGET); + } + + /* default columns */ + if (!ncolumns) { + if (flags & FL_POLL) + add_column(columns, ncolumns++, COL_ACTION); + + add_column(columns, ncolumns++, COL_TARGET); + add_column(columns, ncolumns++, COL_SOURCE); + add_column(columns, ncolumns++, COL_FSTYPE); + add_column(columns, ncolumns++, COL_OPTIONS); + } + + if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns), + &ncolumns, column_name_to_id) < 0) + return EXIT_FAILURE; + + if (!tabtype) + tabtype = verify ? TABTYPE_FSTAB : TABTYPE_KERNEL; + + if ((flags & FL_POLL) && ntabfiles > 1) + errx(EXIT_FAILURE, _("--poll accepts only one file, but more specified by --tab-file")); + + if (optind < argc && (get_match(COL_SOURCE) || get_match(COL_TARGET))) + errx(EXIT_FAILURE, _( + "options --target and --source can't be used together " + "with command line element that is not an option")); + + if (optind < argc) + set_source_match(argv[optind++]); /* dev/tag/mountpoint/maj:min */ + if (optind < argc) + set_match(COL_TARGET, argv[optind++]); /* mountpoint */ + + if ((flags & FL_SUBMOUNTS) && is_listall_mode()) + /* don't care about submounts if list all mounts */ + flags &= ~FL_SUBMOUNTS; + + if (!(flags & FL_SUBMOUNTS) && ((flags & FL_FIRSTONLY) + || get_match(COL_TARGET) + || get_match(COL_SOURCE) + || get_match(COL_MAJMIN))) + flags &= ~FL_TREE; + + if (!(flags & FL_NOSWAPMATCH) && + !get_match(COL_TARGET) && get_match(COL_SOURCE)) { + /* + * Check if we can swap source and target, it's + * not possible if the source is LABEL=/UUID= + */ + const char *x = get_match(COL_SOURCE); + + if (!strncmp(x, "LABEL=", 6) || !strncmp(x, "UUID=", 5) || + !strncmp(x, "PARTLABEL=", 10) || !strncmp(x, "PARTUUID=", 9)) + flags |= FL_NOSWAPMATCH; + } + + /* + * initialize libmount + */ + mnt_init_debug(0); + + tb = parse_tabfiles(tabfiles, ntabfiles, tabtype); + if (!tb) + goto leave; + + if (tabtype == TABTYPE_MTAB && tab_is_kernel(tb)) + tabtype = TABTYPE_KERNEL; + + istree = tab_is_tree(tb); + if (istree && force_tree) + flags |= FL_TREE; + + if ((flags & FL_TREE) && (ntabfiles > 1 || !istree)) + flags &= ~FL_TREE; + + if (!(flags & FL_NOCACHE)) { + cache = mnt_new_cache(); + if (!cache) { + warn(_("failed to initialize libmount cache")); + goto leave; + } + mnt_table_set_cache(tb, cache); + + if (tabtype != TABTYPE_KERNEL) + cache_set_targets(cache); + } + + if (flags & FL_UNIQ) + mnt_table_uniq_fs(tb, MNT_UNIQ_KEEPTREE, uniq_fs_target_cmp); + + if (verify) { + rc = verify_table(tb); + goto leave; + } + + /* + * initialize libsmartcols + */ + scols_init_debug(0); + table = scols_new_table(); + if (!table) { + warn(_("failed to allocate output table")); + goto leave; + } + scols_table_enable_raw(table, !!(flags & FL_RAW)); + scols_table_enable_export(table, !!(flags & FL_EXPORT)); + scols_table_enable_shellvar(table, !!(flags & FL_SHELLVAR)); + scols_table_enable_json(table, !!(flags & FL_JSON)); + scols_table_enable_ascii(table, !!(flags & FL_ASCII)); + scols_table_enable_noheadings(table, !!(flags & FL_NOHEADINGS)); + + if (flags & FL_JSON) + scols_table_set_name(table, "filesystems"); + + for (i = 0; i < ncolumns; i++) { + struct libscols_column *cl; + int fl = get_column_flags(i); + int id = get_column_id(i); + + if (!(flags & FL_TREE)) + fl &= ~SCOLS_FL_TREE; + + if (!(flags & FL_POLL) && is_tabdiff_column(id)) { + warnx(_("%s column is requested, but --poll " + "is not enabled"), get_column_name(i)); + goto leave; + } + cl = scols_table_new_column(table, get_column_name(i), + get_column_whint(i), fl); + if (!cl) { + warn(_("failed to allocate output column")); + goto leave; + } + /* multi-line cells (now used for SOURCES) */ + if (fl & SCOLS_FL_WRAP) { + scols_column_set_wrapfunc(cl, + scols_wrapnl_chunksize, + scols_wrapnl_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; + } + } + } + + /* + * Fill in data to the output table + */ + if (flags & FL_POLL) { + /* poll mode (accept the first tabfile only) */ + rc = poll_table(tb, tabfiles ? *tabfiles : _PATH_PROC_MOUNTINFO, timeout, table, direction); + + } else if ((flags & FL_TREE) && !(flags & FL_SUBMOUNTS)) { + /* whole tree */ + rc = create_treenode(table, tb, NULL, NULL); + } else { + /* whole list of sub-tree */ + rc = add_matching_lines(tb, table, direction); + + if (rc != 0 + && tabtype == TABTYPE_KERNEL + && (flags & FL_NOSWAPMATCH) + && !(flags & FL_STRICTTARGET) + && get_match(COL_TARGET)) { + /* + * Found nothing, maybe the --target is regular file, + * try it again with extra functionality for target + * match + */ + enable_extra_target_match(tb); + rc = add_matching_lines(tb, table, direction); + } + } + + /* + * Print the output table for non-poll modes + */ + if (!rc && !(flags & FL_POLL)) + scols_print_table(table); +leave: + scols_unref_table(table); + + mnt_unref_table(tb); + mnt_unref_cache(cache); + + free(tabfiles); +#ifdef HAVE_LIBUDEV + udev_unref(udev); +#endif + return rc ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/misc-utils/findmnt.h b/misc-utils/findmnt.h new file mode 100644 index 0000000..ce5ddaf --- /dev/null +++ b/misc-utils/findmnt.h @@ -0,0 +1,45 @@ +#ifndef UTIL_LINUX_FINDMNT_H +#define UTIL_LINUX_FINDMNT_H + +/* flags */ +enum { + FL_EVALUATE = (1 << 1), + FL_CANONICALIZE = (1 << 2), + FL_FIRSTONLY = (1 << 3), + FL_INVERT = (1 << 4), + FL_NOSWAPMATCH = (1 << 6), + FL_NOFSROOT = (1 << 7), + FL_SUBMOUNTS = (1 << 8), + FL_POLL = (1 << 9), + FL_DF = (1 << 10), + FL_ALL = (1 << 11), + FL_UNIQ = (1 << 12), + FL_BYTES = (1 << 13), + FL_NOCACHE = (1 << 14), + FL_STRICTTARGET = (1 << 15), + FL_VERBOSE = (1 << 16), + FL_PSEUDO = (1 << 17), + FL_REAL = (1 << 18), + FL_VFS_ALL = (1 << 19), + FL_SHADOWED = (1 << 20), + FL_DELETED = (1 << 21), + FL_SHELLVAR = (1 << 22), + + /* basic table settings */ + FL_ASCII = (1 << 25), + FL_RAW = (1 << 26), + FL_NOHEADINGS = (1 << 27), + FL_EXPORT = (1 << 28), + FL_TREE = (1 << 29), + FL_JSON = (1 << 30), +}; + +extern struct libmnt_cache *cache; +extern unsigned int flags; +extern int parse_nerrors; + +extern int is_listall_mode(void); +extern struct libmnt_fs *get_next_fs(struct libmnt_table *tb, struct libmnt_iter *itr); +extern int verify_table(struct libmnt_table *tb); + +#endif /* UTIL_LINUX_FINDMNT_H */ diff --git a/misc-utils/getopt-example.bash b/misc-utils/getopt-example.bash new file mode 100644 index 0000000..0870882 --- /dev/null +++ b/misc-utils/getopt-example.bash @@ -0,0 +1,75 @@ +#!/bin/bash + +# A small example script for using the getopt(1) program. +# This script will only work with bash(1). +# A similar script using the tcsh(1) language can be found +# as getopt-example.tcsh. + +# Example input and output (from the bash prompt): +# +# ./getopt-example.bash -a par1 'another arg' --c-long 'wow!*\?' -cmore -b " very long " +# Option a +# Option c, no argument +# Option c, argument 'more' +# Option b, argument ' very long ' +# Remaining arguments: +# --> 'par1' +# --> 'another arg' +# --> 'wow!*\?' + +# Note that we use "$@" to let each command-line parameter expand to a +# separate word. The quotes around "$@" are essential! +# We need TEMP as the 'eval set --' would nuke the return value of getopt. +TEMP=$(getopt -o 'ab:c::' --long 'a-long,b-long:,c-long::' -n 'example.bash' -- "$@") + +if [ $? -ne 0 ]; then + echo 'Terminating...' >&2 + exit 1 +fi + +# Note the quotes around "$TEMP": they are essential! +eval set -- "$TEMP" +unset TEMP + +while true; do + case "$1" in + '-a'|'--a-long') + echo 'Option a' + shift + continue + ;; + '-b'|'--b-long') + echo "Option b, argument '$2'" + shift 2 + continue + ;; + '-c'|'--c-long') + # c has an optional argument. As we are in quoted mode, + # an empty parameter will be generated if its optional + # argument is not found. + case "$2" in + '') + echo 'Option c, no argument' + ;; + *) + echo "Option c, argument '$2'" + ;; + esac + shift 2 + continue + ;; + '--') + shift + break + ;; + *) + echo 'Internal error!' >&2 + exit 1 + ;; + esac +done + +echo 'Remaining arguments:' +for arg; do + echo "--> '$arg'" +done diff --git a/misc-utils/getopt-example.tcsh b/misc-utils/getopt-example.tcsh new file mode 100644 index 0000000..2b82294 --- /dev/null +++ b/misc-utils/getopt-example.tcsh @@ -0,0 +1,77 @@ +#!/bin/tcsh + +# A small example script for using the getopt(1) program. +# This script will only work with tcsh(1). +# A similar script using the bash(1) language can be found +# as getopt-example.bash. + +# Example input and output (from the tcsh prompt): +# ./getopt-example.tcsh -a par1 'another arg' --c-long 'wow\!*\?' -cmore -b " very long " +# Option a +# Option c, no argument +# Option c, argument `more' +# Option b, argument ` very long ' +# Remaining arguments: +# --> `par1' +# --> `another arg' +# --> `wow!*\?' + +# Note that we had to escape the exclamation mark in the wow-argument. This +# is _not_ a problem with getopt, but with the tcsh command parsing. If you +# would give the same line from the bash prompt (ie. call ./parse.tcsh), +# you could remove the exclamation mark. + +# This is a bit tricky. We use a temp variable, to be able to check the +# return value of getopt (eval nukes it). argv contains the command arguments +# as a list. The ':q` copies that list without doing any substitutions: +# each element of argv becomes a separate argument for getopt. The braces +# are needed because the result is also a list. +set temp=(`getopt -s tcsh -o ab:c:: --long a-long,b-long:,c-long:: -- $argv:q`) +if ($? != 0) then + echo "Terminating..." >/dev/stderr + exit 1 +endif + +# Now we do the eval part. As the result is a list, we need braces. But they +# must be quoted, because they must be evaluated when the eval is called. +# The 'q` stops doing any silly substitutions. +eval set argv=\($temp:q\) + +while (1) + switch($1:q) + case -a: + case --a-long: + echo "Option a" ; shift + breaksw; + case -b: + case --b-long: + echo "Option b, argument "\`$2:q\' ; shift ; shift + breaksw + case -c: + case --c-long: + # c has an optional argument. As we are in quoted mode, + # an empty parameter will be generated if its optional + # argument is not found. + + if ($2:q == "") then + echo "Option c, no argument" + else + echo "Option c, argument "\`$2:q\' + endif + shift; shift + breaksw + case --: + shift + break + default: + echo "Internal error!" ; exit 1 + endsw +end + +echo "Remaining arguments:" +# foreach el ($argv:q) created problems for some tcsh-versions (at least +# 6.02). So we use another shift-loop here: +while ($#argv > 0) + echo '--> '\`$1:q\' + shift +end diff --git a/misc-utils/getopt.1 b/misc-utils/getopt.1 new file mode 100644 index 0000000..4921add --- /dev/null +++ b/misc-utils/getopt.1 @@ -0,0 +1,190 @@ +'\" t +.\" Title: getopt +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "GETOPT" "1" "2023-10-23" "util\-linux 2.39.3" "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" +getopt \- parse command options (enhanced) +.SH "SYNOPSIS" +.sp +\fBgetopt\fP \fIoptstring\fP \fIparameters\fP +.sp +\fBgetopt\fP [options] [\fB\-\-\fP] \fIoptstring\fP \fIparameters\fP +.sp +\fBgetopt\fP [options] \fB\-o\fP|\fB\-\-options\fP \fIoptstring\fP [options] [\fB\-\-\fP] \fIparameters\fP +.SH "DESCRIPTION" +.sp +\fBgetopt\fP is used to break up (\fIparse\fP) options in command lines for easy parsing by shell procedures, and to check for valid options. It uses the GNU \fBgetopt\fP(3) routines to do this. +.sp +The parameters \fBgetopt\fP is called with can be divided into two parts: options which modify the way \fBgetopt\fP will do the parsing (the \fIoptions\fP and the \fIoptstring\fP in the \fBSYNOPSIS\fP), and the parameters which are to be parsed (\fIparameters\fP in the \fBSYNOPSIS\fP). The second part will start at the first non\-option parameter that is not an option argument, or after the first occurrence of \*(Aq\fB\-\-\fP\*(Aq. If no \*(Aq\fB\-o\fP\*(Aq or \*(Aq\fB\-\-options\fP\*(Aq option is found in the first part, the first parameter of the second part is used as the short options string. +.sp +If the environment variable \fBGETOPT_COMPATIBLE\fP is set, or if the first \fIparameter\fP is not an option (does not start with a \*(Aq\fB\-\fP\*(Aq, the first format in the \fBSYNOPSIS\fP), \fBgetopt\fP will generate output that is compatible with that of other versions of \fBgetopt\fP(1). It will still do parameter shuffling and recognize optional arguments (see the \fBCOMPATIBILITY\fP section for more information). +.sp +Traditional implementations of \fBgetopt\fP(1) are unable to cope with whitespace and other (shell\-specific) special characters in arguments and non\-option parameters. To solve this problem, this implementation can generate quoted output which must once again be interpreted by the shell (usually by using the \fBeval\fP command). This has the effect of preserving those characters, but you must call \fBgetopt\fP in a way that is no longer compatible with other versions (the second or third format in the \fBSYNOPSIS\fP). To determine whether this enhanced version of \fBgetopt\fP(1) is installed, a special test option (\fB\-T\fP) can be used. +.SH "OPTIONS" +.sp +\fB\-a\fP, \fB\-\-alternative\fP +.RS 4 +Allow long options to start with a single \*(Aq\fB\-\fP\*(Aq. +.RE +.sp +\fB\-l\fP, \fB\-\-longoptions\fP \fIlongopts\fP +.RS 4 +The long (multi\-character) options to be recognized. More than one option name may be specified at once, by separating the names with commas. This option may be given more than once, the \fIlongopts\fP are cumulative. Each long option name in \fIlongopts\fP may be followed by one colon to indicate it has a required argument, and by two colons to indicate it has an optional argument. +.RE +.sp +\fB\-n\fP, \fB\-\-name\fP \fIprogname\fP +.RS 4 +The name that will be used by the \fBgetopt\fP(3) routines when it reports errors. Note that errors of \fBgetopt\fP(1) are still reported as coming from getopt. +.RE +.sp +\fB\-o\fP, \fB\-\-options\fP \fIshortopts\fP +.RS 4 +The short (one\-character) options to be recognized. If this option is not found, the first parameter of \fBgetopt\fP that does not start with a \*(Aq\fB\-\fP\*(Aq (and is not an option argument) is used as the short options string. Each short option character in \fIshortopts\fP may be followed by one colon to indicate it has a required argument, and by two colons to indicate it has an optional argument. The first character of shortopts may be \*(Aq\fB+\fP\*(Aq or \*(Aq\fB\-\fP\*(Aq to influence the way options are parsed and output is generated (see the \fBSCANNING MODES\fP section for details). +.RE +.sp +\fB\-q\fP, \fB\-\-quiet\fP +.RS 4 +Disable error reporting by \fBgetopt\fP(3). +.RE +.sp +\fB\-Q\fP, \fB\-\-quiet\-output\fP +.RS 4 +Do not generate normal output. Errors are still reported by \fBgetopt\fP(3), unless you also use \fB\-q\fP. +.RE +.sp +\fB\-s\fP, \fB\-\-shell\fP \fIshell\fP +.RS 4 +Set quoting conventions to those of \fIshell\fP. If the \fB\-s\fP option is not given, the \fBBASH\fP conventions are used. Valid arguments are currently \*(Aq\fBsh\fP\*(Aq, \*(Aq\fBbash\fP\*(Aq, \*(Aq\fBcsh\fP\*(Aq, and \*(Aq\fBtcsh\fP\*(Aq. +.RE +.sp +\fB\-T\fP, \fB\-\-test\fP +.RS 4 +Test if your \fBgetopt\fP(1) is this enhanced version or an old version. This generates no output, and sets the error status to 4. Other implementations of \fBgetopt\fP(1), and this version if the environment variable \fBGETOPT_COMPATIBLE\fP is set, will return \*(Aq\fB\-\-\fP\*(Aq and error status 0. +.RE +.sp +\fB\-u\fP, \fB\-\-unquoted\fP +.RS 4 +Do not quote the output. Note that whitespace and special (shell\-dependent) characters can cause havoc in this mode (like they do with other \fBgetopt\fP(1) implementations). +.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 "PARSING" +.sp +This section specifies the format of the second part of the parameters of \fBgetopt\fP (the \fIparameters\fP in the \fBSYNOPSIS\fP). The next section (\fBOUTPUT\fP) describes the output that is generated. These parameters were typically the parameters a shell function was called with. Care must be taken that each parameter the shell function was called with corresponds to exactly one parameter in the parameter list of \fBgetopt\fP (see the \fBEXAMPLES\fP). All parsing is done by the GNU \fBgetopt\fP(3) routines. +.sp +The parameters are parsed from left to right. Each parameter is classified as a short option, a long option, an argument to an option, or a non\-option parameter. +.sp +A simple short option is a \*(Aq\fB\-\fP\*(Aq followed by a short option character. If the option has a required argument, it may be written directly after the option character or as the next parameter (i.e., separated by whitespace on the command line). If the option has an optional argument, it must be written directly after the option character if present. +.sp +It is possible to specify several short options after one \*(Aq\fB\-\fP\*(Aq, as long as all (except possibly the last) do not have required or optional arguments. +.sp +A long option normally begins with \*(Aq\fB\-\-\fP\*(Aq followed by the long option name. If the option has a required argument, it may be written directly after the long option name, separated by \*(Aq\fB=\fP\*(Aq, or as the next argument (i.e., separated by whitespace on the command line). If the option has an optional argument, it must be written directly after the long option name, separated by \*(Aq\fB=\fP\*(Aq, if present (if you add the \*(Aq\fB=\fP\*(Aq but nothing behind it, it is interpreted as if no argument was present; this is a slight bug, see the \fBBUGS\fP). Long options may be abbreviated, as long as the abbreviation is not ambiguous. +.sp +Each parameter not starting with a \*(Aq\fB\-\fP\*(Aq, and not a required argument of a previous option, is a non\-option parameter. Each parameter after a \*(Aq\fB\-\-\fP\*(Aq parameter is always interpreted as a non\-option parameter. If the environment variable \fBPOSIXLY_CORRECT\fP is set, or if the short option string started with a \*(Aq\fB+\fP\*(Aq, all remaining parameters are interpreted as non\-option parameters as soon as the first non\-option parameter is found. +.SH "OUTPUT" +.sp +Output is generated for each element described in the previous section. Output is done in the same order as the elements are specified in the input, except for non\-option parameters. Output can be done in \fIcompatible\fP (\fIunquoted\fP) mode, or in such way that whitespace and other special characters within arguments and non\-option parameters are preserved (see \fBQUOTING\fP). When the output is processed in the shell script, it will seem to be composed of distinct elements that can be processed one by one (by using the shift command in most shell languages). This is imperfect in unquoted mode, as elements can be split at unexpected places if they contain whitespace or special characters. +.sp +If there are problems parsing the parameters, for example because a required argument is not found or an option is not recognized, an error will be reported on stderr, there will be no output for the offending element, and a non\-zero error status is returned. +.sp +For a short option, a single \*(Aq\fB\-\fP\*(Aq and the option character are generated as one parameter. If the option has an argument, the next parameter will be the argument. If the option takes an optional argument, but none was found, the next parameter will be generated but be empty in quoting mode, but no second parameter will be generated in unquoted (compatible) mode. Note that many other \fBgetopt\fP(1) implementations do not support optional arguments. +.sp +If several short options were specified after a single \*(Aq\fB\-\fP\*(Aq, each will be present in the output as a separate parameter. +.sp +For a long option, \*(Aq\fB\-\-\fP\*(Aq and the full option name are generated as one parameter. This is done regardless whether the option was abbreviated or specified with a single \*(Aq\fB\-\fP\*(Aq in the input. Arguments are handled as with short options. +.sp +Normally, no non\-option parameters output is generated until all options and their arguments have been generated. Then \*(Aq\fB\-\-\fP\*(Aq is generated as a single parameter, and after it the non\-option parameters in the order they were found, each as a separate parameter. Only if the first character of the short options string was a \*(Aq\fB\-\fP\*(Aq, non\-option parameter output is generated at the place they are found in the input (this is not supported if the first format of the \fBSYNOPSIS\fP is used; in that case all preceding occurrences of \*(Aq\fB\-\fP\*(Aq and \*(Aq\fB+\fP\*(Aq are ignored). +.SH "QUOTING" +.sp +In compatibility mode, whitespace or \*(Aqspecial\*(Aq characters in arguments or non\-option parameters are not handled correctly. As the output is fed to the shell script, the script does not know how it is supposed to break the output into separate parameters. To circumvent this problem, this implementation offers quoting. The idea is that output is generated with quotes around each parameter. When this output is once again fed to the shell (usually by a shell \fBeval\fP command), it is split correctly into separate parameters. +.sp +Quoting is not enabled if the environment variable \fBGETOPT_COMPATIBLE\fP is set, if the first form of the \fBSYNOPSIS\fP is used, or if the option \*(Aq\fB\-u\fP\*(Aq is found. +.sp +Different shells use different quoting conventions. You can use the \*(Aq\fB\-s\fP\*(Aq option to select the shell you are using. The following shells are currently supported: \*(Aq\fBsh\fP\*(Aq, \*(Aq\fBbash\fP\*(Aq, \*(Aq\fBcsh\fP\*(Aq and \*(Aq\fBtcsh\fP\*(Aq. Actually, only two \*(Aqflavors\*(Aq are distinguished: sh\-like quoting conventions and csh\-like quoting conventions. Chances are that if you use another shell script language, one of these flavors can still be used. +.SH "SCANNING MODES" +.sp +The first character of the short options string may be a \*(Aq\fB\-\fP\*(Aq or a \*(Aq\fB+\fP\*(Aq to indicate a special scanning mode. If the first calling form in the \fBSYNOPSIS\fP is used they are ignored; the environment variable \fBPOSIXLY_CORRECT\fP is still examined, though. +.sp +If the first character is \*(Aq\fB+\fP\*(Aq, or if the environment variable \fBPOSIXLY_CORRECT\fP is set, parsing stops as soon as the first non\-option parameter (i.e., a parameter that does not start with a \*(Aq\fB\-\fP\*(Aq) is found that is not an option argument. The remaining parameters are all interpreted as non\-option parameters. +.sp +If the first character is a \*(Aq\fB\-\fP\*(Aq, non\-option parameters are outputted at the place where they are found; in normal operation, they are all collected at the end of output after a \*(Aq\fB\-\-\fP\*(Aq parameter has been generated. Note that this \*(Aq\fB\-\-\fP\*(Aq parameter is still generated, but it will always be the last parameter in this mode. +.SH "COMPATIBILITY" +.sp +This version of \fBgetopt\fP(1) is written to be as compatible as possible to other versions. Usually you can just replace them with this version without any modifications, and with some advantages. +.sp +If the first character of the first parameter of getopt is not a \*(Aq\fB\-\fP\*(Aq, \fBgetopt\fP goes into compatibility mode. It will interpret its first parameter as the string of short options, and all other arguments will be parsed. It will still do parameter shuffling (i.e., all non\-option parameters are output at the end), unless the environment variable \fBPOSIXLY_CORRECT\fP is set, in which case, \fBgetopt\fP will prepend a \*(Aq\fB+\fP\*(Aq before short options automatically. +.sp +The environment variable \fBGETOPT_COMPATIBLE\fP forces \fBgetopt\fP into compatibility mode. Setting both this environment variable and \fBPOSIXLY_CORRECT\fP offers 100% compatibility for \*(Aqdifficult\*(Aq programs. Usually, though, neither is needed. +.sp +In compatibility mode, leading \*(Aq\fB\-\fP\*(Aq and \*(Aq\fB+\fP\*(Aq characters in the short options string are ignored. +.SH "RETURN CODES" +.sp +\fBgetopt\fP returns error code \fB0\fP for successful parsing, \fB1\fP if \fBgetopt\fP(3) returns errors, \fB2\fP if it does not understand its own parameters, \fB3\fP if an internal error occurs like out\-of\-memory, and \fB4\fP if it is called with \fB\-T\fP. +.SH "EXAMPLES" +.sp +Example scripts for (ba)sh and (t)csh are provided with the \fBgetopt\fP(1) distribution, and are installed in \fI/usr/share/doc/util\-linux\fP directory. +.SH "ENVIRONMENT" +.sp +\fBPOSIXLY_CORRECT\fP +.RS 4 +This environment variable is examined by the \fBgetopt\fP(3) routines. If it is set, parsing stops as soon as a parameter is found that is not an option or an option argument. All remaining parameters are also interpreted as non\-option parameters, regardless whether they start with a \*(Aq\fB\-\fP\*(Aq. +.RE +.sp +\fBGETOPT_COMPATIBLE\fP +.RS 4 +Forces \fBgetopt\fP to use the first calling format as specified in the \fBSYNOPSIS\fP. +.RE +.SH "BUGS" +.sp +\fBgetopt\fP(3) can parse long options with optional arguments that are given an empty optional argument (but cannot do this for short options). This \fBgetopt\fP(1) treats optional arguments that are empty as if they were not present. +.sp +The syntax if you do not want any short option variables at all is not very intuitive (you have to set them explicitly to the empty string). +.SH "AUTHOR" +.sp +.MTO "frodo\(atfrodo.looijaard.name" "Frodo Looijaard" "" +.SH "SEE ALSO" +.sp +\fBbash\fP(1), +\fBtcsh\fP(1), +\fBgetopt\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 \fBgetopt\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/getopt.1.adoc b/misc-utils/getopt.1.adoc new file mode 100644 index 0000000..2630205 --- /dev/null +++ b/misc-utils/getopt.1.adoc @@ -0,0 +1,163 @@ +//po4a: entry man manual += getopt(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: getopt +:plus: + + +== NAME + +getopt - parse command options (enhanced) + +== SYNOPSIS + +*getopt* _optstring_ _parameters_ + +*getopt* [options] [*--*] _optstring_ _parameters_ + +*getopt* [options] *-o*|*--options* _optstring_ [options] [*--*] _parameters_ + +== DESCRIPTION + +*getopt* is used to break up (_parse_) options in command lines for easy parsing by shell procedures, and to check for valid options. It uses the GNU *getopt*(3) routines to do this. + +The parameters *getopt* is called with can be divided into two parts: options which modify the way *getopt* will do the parsing (the _options_ and the _optstring_ in the *SYNOPSIS*), and the parameters which are to be parsed (_parameters_ in the *SYNOPSIS*). The second part will start at the first non-option parameter that is not an option argument, or after the first occurrence of '*--*'. If no '*-o*' or '*--options*' option is found in the first part, the first parameter of the second part is used as the short options string. + +If the environment variable *GETOPT_COMPATIBLE* is set, or if the first _parameter_ is not an option (does not start with a '*-*', the first format in the *SYNOPSIS*), *getopt* will generate output that is compatible with that of other versions of *getopt*(1). It will still do parameter shuffling and recognize optional arguments (see the *COMPATIBILITY* section for more information). + +Traditional implementations of *getopt*(1) are unable to cope with whitespace and other (shell-specific) special characters in arguments and non-option parameters. To solve this problem, this implementation can generate quoted output which must once again be interpreted by the shell (usually by using the *eval* command). This has the effect of preserving those characters, but you must call *getopt* in a way that is no longer compatible with other versions (the second or third format in the *SYNOPSIS*). To determine whether this enhanced version of *getopt*(1) is installed, a special test option (*-T*) can be used. + +== OPTIONS + +*-a*, *--alternative*:: +Allow long options to start with a single '*-*'. + +*-l*, *--longoptions* _longopts_:: +The long (multi-character) options to be recognized. More than one option name may be specified at once, by separating the names with commas. This option may be given more than once, the _longopts_ are cumulative. Each long option name in _longopts_ may be followed by one colon to indicate it has a required argument, and by two colons to indicate it has an optional argument. + +*-n*, *--name* _progname_:: +The name that will be used by the *getopt*(3) routines when it reports errors. Note that errors of *getopt*(1) are still reported as coming from getopt. + +*-o*, *--options* _shortopts_:: +The short (one-character) options to be recognized. If this option is not found, the first parameter of *getopt* that does not start with a '*-*' (and is not an option argument) is used as the short options string. Each short option character in _shortopts_ may be followed by one colon to indicate it has a required argument, and by two colons to indicate it has an optional argument. The first character of shortopts may be '*{plus}*' or '*-*' to influence the way options are parsed and output is generated (see the *SCANNING MODES* section for details). +//TRANSLATORS: Keep {plus} untranslated. + +*-q*, *--quiet*:: +Disable error reporting by *getopt*(3). + +*-Q*, *--quiet-output*:: +Do not generate normal output. Errors are still reported by *getopt*(3), unless you also use *-q*. + +*-s*, *--shell* _shell_:: +Set quoting conventions to those of _shell_. If the *-s* option is not given, the *BASH* conventions are used. Valid arguments are currently '*sh*', '*bash*', '*csh*', and '*tcsh*'. + +*-T*, *--test*:: +Test if your *getopt*(1) is this enhanced version or an old version. This generates no output, and sets the error status to 4. Other implementations of *getopt*(1), and this version if the environment variable *GETOPT_COMPATIBLE* is set, will return '*--*' and error status 0. + +*-u*, *--unquoted*:: +Do not quote the output. Note that whitespace and special (shell-dependent) characters can cause havoc in this mode (like they do with other *getopt*(1) implementations). + +include::man-common/help-version.adoc[] + +== PARSING + +This section specifies the format of the second part of the parameters of *getopt* (the _parameters_ in the *SYNOPSIS*). The next section (*OUTPUT*) describes the output that is generated. These parameters were typically the parameters a shell function was called with. Care must be taken that each parameter the shell function was called with corresponds to exactly one parameter in the parameter list of *getopt* (see the *EXAMPLES*). All parsing is done by the GNU *getopt*(3) routines. + +The parameters are parsed from left to right. Each parameter is classified as a short option, a long option, an argument to an option, or a non-option parameter. + +A simple short option is a '*-*' followed by a short option character. If the option has a required argument, it may be written directly after the option character or as the next parameter (i.e., separated by whitespace on the command line). If the option has an optional argument, it must be written directly after the option character if present. + +It is possible to specify several short options after one '*-*', as long as all (except possibly the last) do not have required or optional arguments. + +A long option normally begins with '*--*' followed by the long option name. If the option has a required argument, it may be written directly after the long option name, separated by '*=*', or as the next argument (i.e., separated by whitespace on the command line). If the option has an optional argument, it must be written directly after the long option name, separated by '*=*', if present (if you add the '*=*' but nothing behind it, it is interpreted as if no argument was present; this is a slight bug, see the *BUGS*). Long options may be abbreviated, as long as the abbreviation is not ambiguous. + +Each parameter not starting with a '*-*', and not a required argument of a previous option, is a non-option parameter. Each parameter after a '*--*' parameter is always interpreted as a non-option parameter. If the environment variable *POSIXLY_CORRECT* is set, or if the short option string started with a '*{plus}*', all remaining parameters are interpreted as non-option parameters as soon as the first non-option parameter is found. +//TRANSLATORS: Keep {plus} untranslated. + +== OUTPUT + +Output is generated for each element described in the previous section. Output is done in the same order as the elements are specified in the input, except for non-option parameters. Output can be done in _compatible_ (_unquoted_) mode, or in such way that whitespace and other special characters within arguments and non-option parameters are preserved (see *QUOTING*). When the output is processed in the shell script, it will seem to be composed of distinct elements that can be processed one by one (by using the shift command in most shell languages). This is imperfect in unquoted mode, as elements can be split at unexpected places if they contain whitespace or special characters. + +If there are problems parsing the parameters, for example because a required argument is not found or an option is not recognized, an error will be reported on stderr, there will be no output for the offending element, and a non-zero error status is returned. + +For a short option, a single '*-*' and the option character are generated as one parameter. If the option has an argument, the next parameter will be the argument. If the option takes an optional argument, but none was found, the next parameter will be generated but be empty in quoting mode, but no second parameter will be generated in unquoted (compatible) mode. Note that many other *getopt*(1) implementations do not support optional arguments. + +If several short options were specified after a single '*-*', each will be present in the output as a separate parameter. + +For a long option, '*--*' and the full option name are generated as one parameter. This is done regardless whether the option was abbreviated or specified with a single '*-*' in the input. Arguments are handled as with short options. + +Normally, no non-option parameters output is generated until all options and their arguments have been generated. Then '*--*' is generated as a single parameter, and after it the non-option parameters in the order they were found, each as a separate parameter. Only if the first character of the short options string was a '*-*', non-option parameter output is generated at the place they are found in the input (this is not supported if the first format of the *SYNOPSIS* is used; in that case all preceding occurrences of '*-*' and '*{plus}*' are ignored). +//TRANSLATORS: Keep {plus} untranslated. + +== QUOTING + +In compatibility mode, whitespace or 'special' characters in arguments or non-option parameters are not handled correctly. As the output is fed to the shell script, the script does not know how it is supposed to break the output into separate parameters. To circumvent this problem, this implementation offers quoting. The idea is that output is generated with quotes around each parameter. When this output is once again fed to the shell (usually by a shell *eval* command), it is split correctly into separate parameters. + +Quoting is not enabled if the environment variable *GETOPT_COMPATIBLE* is set, if the first form of the *SYNOPSIS* is used, or if the option '*-u*' is found. + +Different shells use different quoting conventions. You can use the '*-s*' option to select the shell you are using. The following shells are currently supported: '*sh*', '*bash*', '*csh*' and '*tcsh*'. Actually, only two 'flavors' are distinguished: sh-like quoting conventions and csh-like quoting conventions. Chances are that if you use another shell script language, one of these flavors can still be used. + +== SCANNING MODES + +The first character of the short options string may be a '*-*' or a '*{plus}*' to indicate a special scanning mode. If the first calling form in the *SYNOPSIS* is used they are ignored; the environment variable *POSIXLY_CORRECT* is still examined, though. +//TRANSLATORS: Keep {plus} untranslated. + +If the first character is '*{plus}*', or if the environment variable *POSIXLY_CORRECT* is set, parsing stops as soon as the first non-option parameter (i.e., a parameter that does not start with a '*-*') is found that is not an option argument. The remaining parameters are all interpreted as non-option parameters. +//TRANSLATORS: Keep {plus} untranslated. + +If the first character is a '*-*', non-option parameters are outputted at the place where they are found; in normal operation, they are all collected at the end of output after a '*--*' parameter has been generated. Note that this '*--*' parameter is still generated, but it will always be the last parameter in this mode. + +== COMPATIBILITY + +This version of *getopt*(1) is written to be as compatible as possible to other versions. Usually you can just replace them with this version without any modifications, and with some advantages. + +If the first character of the first parameter of getopt is not a '*-*', *getopt* goes into compatibility mode. It will interpret its first parameter as the string of short options, and all other arguments will be parsed. It will still do parameter shuffling (i.e., all non-option parameters are output at the end), unless the environment variable *POSIXLY_CORRECT* is set, in which case, *getopt* will prepend a '*{plus}*' before short options automatically. +//TRANSLATORS: Keep {plus} untranslated. + +The environment variable *GETOPT_COMPATIBLE* forces *getopt* into compatibility mode. Setting both this environment variable and *POSIXLY_CORRECT* offers 100% compatibility for 'difficult' programs. Usually, though, neither is needed. + +In compatibility mode, leading '*-*' and '*{plus}*' characters in the short options string are ignored. +//TRANSLATORS: Keep {plus} untranslated. + +== RETURN CODES + +*getopt* returns error code *0* for successful parsing, *1* if *getopt*(3) returns errors, *2* if it does not understand its own parameters, *3* if an internal error occurs like out-of-memory, and *4* if it is called with *-T*. + +== EXAMPLES + +// TRANSLATORS: Don't translate _{package-docdir}_. +Example scripts for (ba)sh and (t)csh are provided with the *getopt*(1) distribution, and are installed in _{package-docdir}_ directory. + +== ENVIRONMENT + +*POSIXLY_CORRECT*:: +This environment variable is examined by the *getopt*(3) routines. If it is set, parsing stops as soon as a parameter is found that is not an option or an option argument. All remaining parameters are also interpreted as non-option parameters, regardless whether they start with a '*-*'. + +*GETOPT_COMPATIBLE*:: +Forces *getopt* to use the first calling format as specified in the *SYNOPSIS*. + +== BUGS + +*getopt*(3) can parse long options with optional arguments that are given an empty optional argument (but cannot do this for short options). This *getopt*(1) treats optional arguments that are empty as if they were not present. + +The syntax if you do not want any short option variables at all is not very intuitive (you have to set them explicitly to the empty string). + +== AUTHOR + +mailto:frodo@frodo.looijaard.name[Frodo Looijaard] + +== SEE ALSO + +*bash*(1), +*tcsh*(1), +*getopt*(3) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/getopt.c b/misc-utils/getopt.c new file mode 100644 index 0000000..977b725 --- /dev/null +++ b/misc-utils/getopt.c @@ -0,0 +1,484 @@ +/* + * getopt.c - Enhanced implementation of BSD getopt(1) + * Copyright (c) 1997-2014 Frodo Looijaard <frodo@frodo.looijaard.name> + * + * 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 will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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. + */ + +/* + * Version 1.0-b4: Tue Sep 23 1997. First public release. + * Version 1.0: Wed Nov 19 1997. + * Bumped up the version number to 1.0 + * Fixed minor typo (CSH instead of TCSH) + * Version 1.0.1: Tue Jun 3 1998 + * Fixed sizeof instead of strlen bug + * Bumped up the version number to 1.0.1 + * Version 1.0.2: Thu Jun 11 1998 (not present) + * Fixed gcc-2.8.1 warnings + * Fixed --version/-V option (not present) + * Version 1.0.5: Tue Jun 22 1999 + * Make -u option work (not present) + * Version 1.0.6: Tue Jun 27 2000 + * No important changes + * Version 1.1.0: Tue Jun 30 2000 + * Added NLS support (partly written by Arkadiusz Miśkiewicz + * <misiek@pld.org.pl>) + * Version 1.1.4: Mon Nov 7 2005 + * Fixed a few type's in the manpage + * Version 1.1.5: Sun Aug 12 2012 + * Sync with util-linux-2.21, fixed build problems, many new translations + * Version 1.1.6: Mon Nov 24 2014 + * Sync with util-linux git 20141120, detect ambiguous long options, fix + * backslash problem in tcsh + */ + +/* Exit codes: + * 0) No errors, successful operation. + * 1) getopt(3) returned an error. + * 2) A problem with parameter parsing for getopt(1). + * 3) Internal error, out of memory + * 4) Returned for -T + */ +#define GETOPT_EXIT_CODE 1 +#define PARAMETER_EXIT_CODE 2 +#define XALLOC_EXIT_CODE 3 +#define CLOSE_EXIT_CODE XALLOC_EXIT_CODE +#define TEST_EXIT_CODE 4 + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <getopt.h> +#ifdef HAVE_SYS_PARAM_H +# include <sys/param.h> /* BSD */ +#endif + +#include "closestream.h" +#include "nls.h" +#include "strutils.h" +#include "xalloc.h" + +/* NON_OPT is the code that is returned getopt(3) when a non-option is + * found in 'char optstring[]="-abc...";', e.g., it begins by '-' */ +#define NON_OPT 1 +/* LONG_OPT is the code that is returned when a long option is found. */ +#define LONG_OPT 0 + +/* The shells recognized. */ +typedef enum { BASH, TCSH } shell_t; + +struct getopt_control { + shell_t shell; /* the shell we generate output for */ + char *optstr; /* getopt(3) optstring */ + char *name; + struct option *long_options; /* long options */ + int long_options_length; /* length of options array */ + int long_options_nr; /* number of used elements in array */ + unsigned int + compatible:1, /* compatibility mode for 'difficult' programs */ + quiet_errors:1, /* print errors */ + quiet_output:1, /* print output */ + quote:1; /* quote output */ +}; + +enum { REALLOC_INCREMENT = 8 }; + +/* Allow changing which getopt is in use with function pointer. */ +static int (*getopt_long_fp) (int argc, char *const *argv, const char *optstr, + const struct option * longopts, int *longindex); + +/* + * This function 'normalizes' a single argument: it puts single quotes + * around it and escapes other special characters. If quote is false, it + * just returns its argument. + * + * Bash only needs special treatment for single quotes; tcsh also recognizes + * exclamation marks within single quotes, and nukes whitespace. This + * function returns a pointer to a buffer that is overwritten by each call. + */ +static void print_normalized(const struct getopt_control *ctl, const char *arg) +{ + char *buf; + const char *argptr = arg; + char *bufptr; + + if (!ctl->quote) { + printf(" %s", arg); + return; + } + + /* + * Each character in arg may take up to four characters in the + * result: For a quote we need a closing quote, a backslash, a quote + * and an opening quote! We need also the global opening and closing + * quote, and one extra character for '\0'. + */ + buf = xmalloc(strlen(arg) * 4 + 3); + bufptr = buf; + + for (*bufptr++ = '\''; *argptr; argptr++) { + if (ctl->shell == TCSH) { + switch (*argptr) { + case '\\': + /* Backslash: replace it with: '\\' */ + *bufptr++ = '\\'; + *bufptr++ = '\\'; + continue; + case '!': + /* Exclamation mark: replace it with: \! */ + *bufptr++ = '\''; + *bufptr++ = '\\'; + *bufptr++ = '!'; + *bufptr++ = '\''; + continue; + case '\n': + /* Newline: replace it with: \n */ + *bufptr++ = '\\'; + *bufptr++ = 'n'; + continue; + } + if (isspace(*argptr)) { + /* Non-newline whitespace: replace it with \<ws> */ + *bufptr++ = '\''; + *bufptr++ = '\\'; + *bufptr++ = *argptr; + *bufptr++ = '\''; + continue; + } + } + if (*argptr == '\'') { + /* Quote: replace it with: '\'' */ + *bufptr++ = '\''; + *bufptr++ = '\\'; + *bufptr++ = '\''; + *bufptr++ = '\''; + } else + /* Just copy */ + *bufptr++ = *argptr; + } + + *bufptr++ = '\''; + *bufptr++ = '\0'; + printf(" %s", buf); + free(buf); +} + +/* + * Generate the output. argv[0] is the program name (used for reporting errors). + * argv[1..] contains the options to be parsed. argc must be the number of + * elements in argv (ie. 1 if there are no options, only the program name), + * optstr must contain the short options, and longopts the long options. + * Other settings are found in global variables. + */ +static int generate_output(struct getopt_control *ctl, char *argv[], int argc) +{ + int exit_code = EXIT_SUCCESS; /* Assume everything will be OK */ + int opt; + int longindex; + const char *charptr; + + if (ctl->quiet_errors) + /* No error reporting from getopt(3) */ + opterr = 0; + /* Reset getopt(3) */ + optind = 0; + + while ((opt = + (getopt_long_fp + (argc, argv, ctl->optstr, + (const struct option *)ctl->long_options, &longindex))) + != EOF) { + if (opt == '?' || opt == ':') + exit_code = GETOPT_EXIT_CODE; + else if (!ctl->quiet_output) { + switch (opt) { + case LONG_OPT: + printf(" --%s", ctl->long_options[longindex].name); + if (ctl->long_options[longindex].has_arg) + print_normalized(ctl, optarg ? optarg : ""); + break; + case NON_OPT: + print_normalized(ctl, optarg ? optarg : ""); + break; + default: + printf(" -%c", opt); + charptr = strchr(ctl->optstr, opt); + if (charptr != NULL && *++charptr == ':') + print_normalized(ctl, optarg ? optarg : ""); + } + } + } + if (!ctl->quiet_output) { + printf(" --"); + while (optind < argc) + print_normalized(ctl, argv[optind++]); + printf("\n"); + } + for (longindex = 0; longindex < ctl->long_options_nr; longindex++) + free((char *)ctl->long_options[longindex].name); + free(ctl->long_options); + free(ctl->optstr); + free(ctl->name); + return exit_code; +} + +/* + * Report an error when parsing getopt's own arguments. If message is NULL, + * we already sent a message, we just exit with a helpful hint. + */ +static void __attribute__ ((__noreturn__)) parse_error(const char *message) +{ + if (message) + warnx("%s", message); + errtryhelp(PARAMETER_EXIT_CODE); +} + + +/* Register a long option. The contents of name is copied. */ +static void add_longopt(struct getopt_control *ctl, const char *name, int has_arg) +{ + static int flag; + int nr = ctl->long_options_nr; + + 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); + } + if (name) { + /* Not for init! */ + ctl->long_options[nr].has_arg = has_arg; + ctl->long_options[nr].flag = &flag; + ctl->long_options[nr].val = ctl->long_options_nr; + ctl->long_options[nr].name = xstrdup(name); + } else { + /* lets use add_longopt(ct, NULL, 0) to terminate the array */ + ctl->long_options[nr].name = NULL; + ctl->long_options[nr].has_arg = 0; + ctl->long_options[nr].flag = NULL; + ctl->long_options[nr].val = 0; + } +} + + +static void add_short_options(struct getopt_control *ctl, char *options) +{ + free(ctl->optstr); + if (*options != '+' && getenv("POSIXLY_CORRECT")) + ctl->optstr = strconcat("+", options); + else + ctl->optstr = xstrdup(options); + if (!ctl->optstr) + err_oom(); +} + + +/* + * Register several long options. options is a string of long options, + * separated by commas or whitespace. This nukes options! + */ +static void add_long_options(struct getopt_control *ctl, char *options) +{ + int arg_opt; + char *tokptr = strtok(options, ", \t\n"); + + while (tokptr) { + size_t len = strlen(tokptr); + + arg_opt = no_argument; + if (len > 0) { + if (tokptr[len - 1] == ':') { + if (tokptr[len - 2] == ':') { + tokptr[len - 2] = '\0'; + arg_opt = optional_argument; + } else { + tokptr[len - 1] = '\0'; + arg_opt = required_argument; + } + if (!*tokptr) + parse_error(_ + ("empty long option after " + "-l or --long argument")); + } + add_longopt(ctl, tokptr, arg_opt); + ctl->long_options_nr++; + } + tokptr = strtok(NULL, ", \t\n"); + } + add_longopt(ctl, NULL, 0); /* ensure long_options[] is not full */ +} + +static shell_t shell_type(const char *new_shell) +{ + if (!strcmp(new_shell, "bash")) + return BASH; + if (!strcmp(new_shell, "sh")) + return BASH; + if (!strcmp(new_shell, "tcsh")) + return TCSH; + if (!strcmp(new_shell, "csh")) + return TCSH; + parse_error(_("unknown shell after -s or --shell argument")); +} + +static void __attribute__((__noreturn__)) usage(void) +{ + fputs(USAGE_HEADER, stdout); + printf(_( + " %1$s <optstring> <parameters>\n" + " %1$s [options] [--] <optstring> <parameters>\n" + " %1$s [options] -o|--options <optstring> [options] [--] <parameters>\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, stdout); + fputs(_("Parse command options.\n"), stdout); + + fputs(USAGE_OPTIONS, stdout); + fputs(_(" -a, --alternative allow long options starting with single -\n"), stdout); + fputs(_(" -l, --longoptions <longopts> the long options to be recognized\n"), stdout); + fputs(_(" -n, --name <progname> the name under which errors are reported\n"), stdout); + fputs(_(" -o, --options <optstring> the short options to be recognized\n"), stdout); + fputs(_(" -q, --quiet disable error reporting by getopt(3)\n"), stdout); + fputs(_(" -Q, --quiet-output no normal output\n"), stdout); + fputs(_(" -s, --shell <shell> set quoting conventions to those of <shell>\n"), stdout); + 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)")); + exit(EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + struct getopt_control ctl = { + .shell = BASH, + .quote = 1 + }; + int opt; + + /* Stop scanning as soon as a non-option argument is found! */ + static const char *shortopts = "+ao:l:n:qQs:TuhV"; + static const struct option longopts[] = { + {"options", required_argument, NULL, 'o'}, + {"longoptions", required_argument, NULL, 'l'}, + {"quiet", no_argument, NULL, 'q'}, + {"quiet-output", no_argument, NULL, 'Q'}, + {"shell", required_argument, NULL, 's'}, + {"test", no_argument, NULL, 'T'}, + {"unquoted", no_argument, NULL, 'u'}, + {"help", no_argument, NULL, 'h'}, + {"alternative", no_argument, NULL, 'a'}, + {"name", required_argument, NULL, 'n'}, + {"version", no_argument, NULL, 'V'}, + {NULL, 0, NULL, 0} + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + if (getenv("GETOPT_COMPATIBLE")) + ctl.compatible = 1; + + if (argc == 1) { + if (ctl.compatible) { + /* + * For some reason, the original getopt gave no + * error when there were no arguments. + */ + printf(" --\n"); + return EXIT_SUCCESS; + } + parse_error(_("missing optstring argument")); + } + + add_longopt(&ctl, NULL, 0); /* init */ + getopt_long_fp = getopt_long; + + if (argv[1][0] != '-' || ctl.compatible) { + ctl.quote = 0; + ctl.optstr = xmalloc(strlen(argv[1]) + 1); + strcpy(ctl.optstr, argv[1] + strspn(argv[1], "-+")); + argv[1] = argv[0]; + return generate_output(&ctl, argv + 1, argc - 1); + } + + while ((opt = + getopt_long(argc, argv, shortopts, longopts, NULL)) != EOF) + switch (opt) { + case 'a': + getopt_long_fp = getopt_long_only; + break; + case 'o': + add_short_options(&ctl, optarg); + break; + case 'l': + add_long_options(&ctl, optarg); + break; + case 'n': + free(ctl.name); + ctl.name = xstrdup(optarg); + break; + case 'q': + ctl.quiet_errors = 1; + break; + case 'Q': + ctl.quiet_output = 1; + break; + case 's': + ctl.shell = shell_type(optarg); + break; + case 'T': + free(ctl.long_options); + return TEST_EXIT_CODE; + case 'u': + ctl.quote = 0; + break; + + case 'V': + print_version(EXIT_SUCCESS); + case '?': + case ':': + parse_error(NULL); + case 'h': + usage(); + default: + parse_error(_("internal error, contact the author.")); + } + + if (!ctl.optstr) { + if (optind >= argc) + parse_error(_("missing optstring argument")); + else { + add_short_options(&ctl, argv[optind]); + optind++; + } + } + + if (ctl.name) { + argv[optind - 1] = ctl.name; +#if defined (HAVE_SETPROGNAME) && !defined (__linux__) + setprogname(ctl.name); +#endif + } else + argv[optind - 1] = argv[0]; + + return generate_output(&ctl, argv + optind - 1, argc - optind + 1); +} diff --git a/misc-utils/hardlink.1 b/misc-utils/hardlink.1 new file mode 100644 index 0000000..4aa0737 --- /dev/null +++ b/misc-utils/hardlink.1 @@ -0,0 +1,213 @@ +'\" t +.\" Title: hardlink +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-11-21 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "HARDLINK" "1" "2023-11-21" "util\-linux 2.39.3" "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" +hardlink \- link multiple copies of a file +.SH "SYNOPSIS" +.sp +\fBhardlink\fP [options] [\fIdirectory\fP|\fIfile\fP]... +.SH "DESCRIPTION" +.sp +\fBhardlink\fP is a tool that replaces copies of a file with either hardlinks +or copy\-on\-write clones, thus saving space. +.sp +\fBhardlink\fP first creates a binary tree of file sizes and then compares +the content of files that have the same size. There are two basic content +comparison methods. The \fBmemcmp\fP method directly reads data blocks from +files and compares them. The other method is based on checksums (like SHA256); +in this case for each data block a checksum is calculated by the Linux kernel +crypto API, and this checksum is stored in userspace and used for file +comparisons. +.sp +For each file also an "intro" buffer (32 bytes) is cached. This buffer is used +independently from the comparison method and requested cache\-size and io\-size. +The "intro" buffer dramatically reduces operations with data content as files +are very often different from the beginning. +.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 +.sp +\fB\-c\fP, \fB\-\-content\fP +.RS 4 +Consider only file content, not attributes, when determining whether two files are equal. Same as \fB\-pot\fP. +.RE +.sp +\fB\-b\fP, \fB\-\-io\-size\fP \fIsize\fP +.RS 4 +The size of the \fBread\fP(2) or \fBsendfile\fP(2) buffer used when comparing file contents. +The \fIsize\fP argument may be followed by the multiplicative suffixes KiB, MiB, +etc. The "iB" is optional, e.g., "K" has the same meaning as "KiB". The +default is 8KiB for memcmp method and 1MiB for the other methods. The only +memcmp method uses process memory for the buffer, other methods use zero\-copy +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. +.RE +.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. +.RE +.sp +\fB\-f\fP, \fB\-\-respect\-name\fP +.RS 4 +Only try to link files with the same (base)name. It\(cqs strongly recommended to use long options rather than \fB\-f\fP which is interpreted in a different way by other \fBhardlink\fP implementations. +.RE +.sp +\fB\-i\fP, \fB\-\-include\fP \fIregex\fP +.RS 4 +A regular expression to include files. If the option \fB\-\-exclude\fP has been given, this option re\-includes files which would otherwise be excluded. If the option is used without \fB\-\-exclude\fP, only files matched by the pattern are included. +.RE +.sp +\fB\-m\fP, \fB\-\-maximize\fP +.RS 4 +Among equal files, keep the file with the highest link count. +.RE +.sp +\fB\-M\fP, \fB\-\-minimize\fP +.RS 4 +Among equal files, keep the file with the lowest link count. +.RE +.sp +\fB\-n\fP, \fB\-\-dry\-run\fP +.RS 4 +Do not act, just print what would happen. +.RE +.sp +\fB\-o\fP, \fB\-\-ignore\-owner\fP +.RS 4 +Link and compare files even if their owner information (user and group) differs. Results may be unpredictable. +.RE +.sp +\fB\-O\fP, \fB\-\-keep\-oldest\fP +.RS 4 +Among equal files, keep the oldest file (least recent modification time). By default, the newest file is kept. If \fB\-\-maximize\fP or \fB\-\-minimize\fP is specified, the link count has a higher precedence than the time of modification. +.RE +.sp +\fB\-p\fP, \fB\-\-ignore\-mode\fP +.RS 4 +Link and compare files even if their mode is different. Results may be slightly unpredictable. +.RE +.sp +\fB\-q\fP, \fB\-\-quiet\fP +.RS 4 +Quiet mode, don\(cqt print anything. +.RE +.sp +\fB\-r\fP, \fB\-\-cache\-size\fP \fIsize\fP +.RS 4 +The size of the cache for content checksums. All non\-memcmp methods calculate checksum for each +file content block (see \fB\-\-io\-size\fP), these checksums are cached for the next comparison. The +size is important for large files or a large sets of files of the same size. The default is +10MiB. +.RE +.sp +\fB\-s\fP, \fB\-\-minimum\-size\fP \fIsize\fP +.RS 4 +The minimum size to consider. By default this is 1, so empty files will not be linked. The \fIsize\fP argument may be followed by the multiplicative suffixes KiB (=1024), MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB and YiB (the "iB" is optional, e.g., "K" has the same meaning as "KiB"). +.RE +.sp +\fB\-S\fP, \fB\-\-maximum\-size\fP \fIsize\fP +.RS 4 +The maximum size to consider. By default this is 0 and 0 has the special meaning of unlimited. The \fIsize\fP argument may be followed by the multiplicative suffixes KiB (=1024), MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB and YiB (the "iB" is optional, e.g., "K" has the same meaning as "KiB"). +.RE +.sp +\fB\-t\fP, \fB\-\-ignore\-time\fP +.RS 4 +Link and compare files even if their time of modification is different. This is usually a good choice. +.RE +.sp +\fB\-v\fP, \fB\-\-verbose\fP +.RS 4 +Verbose output, explain to the user what is being done. If specified once, every hardlinked file is displayed. If specified twice, it also shows every comparison. +.RE +.sp +\fB\-x\fP, \fB\-\-exclude\fP \fIregex\fP +.RS 4 +A regular expression which excludes files from being compared and linked. +.RE +.sp +\fB\-X\fP, \fB\-\-respect\-xattrs\fP +.RS 4 +Only try to link files with the same extended attributes. +.RE +.sp +\fB\-y\fP, \fB\-\-method\fP \fIname\fP +.RS 4 +Set the file content comparison method. The currently supported methods are +sha256, sha1, crc32c and memcmp. The default is sha256, or memcmp if Linux +Crypto API is not available. The methods based on checksums are implemented in +zero\-copy way, in this case file contents are not copied to the userspace and all +calculation is done in kernel. +.RE +.sp +\fB\-\-reflink\fP[=\fIwhen\fP] +.RS 4 +Create copy\-on\-write clones (aka reflinks) rather than hardlinks. The reflinked files +share only on\-disk data, but the file mode and owner can be different. It\(cqs recommended +to use it with \fB\-\-ignore\-owner\fP and \fB\-\-ignore\-mode\fP options. This option implies +\fB\-\-skip\-reflinks\fP to ignore already cloned files. +.sp +The optional argument \fIwhen\fP can be \fBnever\fP, \fBalways\fP, or \fBauto\fP. If the \fIwhen\fP argument +is omitted, it defaults to \fBauto\fP, in this case, \fBhardlink\fP checks filesystem type and +uses reflinks on BTRFS and XFS only, and fallback to hardlinks when creating reflink is impossible. +The argument \fBalways\fP disables filesystem type detection and fallback to hardlinks, in this case, +only reflinks are allowed. +.RE +.sp +\fB\-\-skip\-reflinks\fP +.RS 4 +Ignore already cloned files. This option may be used without \fB\-\-reflink\fP when creating classic hardlinks. +.RE +.SH "ARGUMENTS" +.sp +\fBhardlink\fP takes one or more directories which will be searched for files to be linked. +.SH "BUGS" +.sp +The original \fBhardlink\fP implementation uses the option \fB\-f\fP to force hardlinks creation between filesystem. This very rarely usable feature is no more supported by the current \fBhardlink\fP. +.sp +\fBhardlink\fP assumes that the trees it operates on do not change during operation. If a tree does change, the result is undefined and potentially dangerous. For example, if a regular file is replaced by a device, \fBhardlink\fP may start reading from the device. If a component of a path is replaced by a symbolic link or file permissions change, security may be compromised. Do not run \fBhardlink\fP on a changing tree or on a tree controlled by another user. +.SH "AUTHOR" +.sp +There are multiple \fBhardlink\fP implementations. The very first implementation is from Jakub Jelinek for Fedora distribution, this implementation has been used in util\-linux between versions v2.34 to v2.36. The current implementations is based on Debian version from Julian Andres Klode. +.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 \fBhardlink\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/hardlink.1.adoc b/misc-utils/hardlink.1.adoc new file mode 100644 index 0000000..91d9867 --- /dev/null +++ b/misc-utils/hardlink.1.adoc @@ -0,0 +1,154 @@ +//po4a: entry man manual +//// +SPDX-License-Identifier: MIT + +Copyright (C) 2008 - 2012 Julian Andres Klode. See hardlink.c for license. +Copyright (C) 2021 Karel Zak <kzak@redhat.com> +//// += hardlink(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: hardlink + +== NAME + +hardlink - link multiple copies of a file + +== SYNOPSIS + +*hardlink* [options] [_directory_|_file_]... + +== DESCRIPTION + +*hardlink* is a tool that replaces copies of a file with either hardlinks +or copy-on-write clones, thus saving space. + +*hardlink* first creates a binary tree of file sizes and then compares +the content of files that have the same size. There are two basic content +comparison methods. The *memcmp* method directly reads data blocks from +files and compares them. The other method is based on checksums (like SHA256); +in this case for each data block a checksum is calculated by the Linux kernel +crypto API, and this checksum is stored in userspace and used for file +comparisons. + +For each file also an "intro" buffer (32 bytes) is cached. This buffer is used +independently from the comparison method and requested cache-size and io-size. +The "intro" buffer dramatically reduces operations with data content as files +are very often different from the beginning. + +== OPTIONS + +include::man-common/help-version.adoc[] + +*-c*, *--content*:: +Consider only file content, not attributes, when determining whether two files are equal. Same as *-pot*. + +*-b*, *--io-size* _size_:: +The size of the *read*(2) or *sendfile*(2) buffer used when comparing file contents. +The _size_ argument may be followed by the multiplicative suffixes KiB, MiB, +etc. The "iB" is optional, e.g., "K" has the same meaning as "KiB". The +default is 8KiB for memcmp method and 1MiB for the other methods. The only +memcmp method uses process memory for the buffer, other methods use zero-copy +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. + +*-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. + +*-i*, *--include* _regex_:: +A regular expression to include files. If the option *--exclude* has been given, this option re-includes files which would otherwise be excluded. If the option is used without *--exclude*, only files matched by the pattern are included. + +*-m*, *--maximize*:: +Among equal files, keep the file with the highest link count. + +*-M*, *--minimize*:: +Among equal files, keep the file with the lowest link count. + +*-n*, *--dry-run*:: +Do not act, just print what would happen. + +*-o*, *--ignore-owner*:: +Link and compare files even if their owner information (user and group) differs. Results may be unpredictable. + +*-O*, *--keep-oldest*:: +Among equal files, keep the oldest file (least recent modification time). By default, the newest file is kept. If *--maximize* or *--minimize* is specified, the link count has a higher precedence than the time of modification. + +*-p*, *--ignore-mode*:: +Link and compare files even if their mode is different. Results may be slightly unpredictable. + +*-q*, *--quiet*:: +Quiet mode, don't print anything. + +*-r*, *--cache-size* _size_:: +The size of the cache for content checksums. All non-memcmp methods calculate checksum for each +file content block (see *--io-size*), these checksums are cached for the next comparison. The +size is important for large files or a large sets of files of the same size. The default is +10MiB. + +*-s*, *--minimum-size* _size_:: +The minimum size to consider. By default this is 1, so empty files will not be linked. The _size_ argument may be followed by the multiplicative suffixes KiB (=1024), MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB and YiB (the "iB" is optional, e.g., "K" has the same meaning as "KiB"). + +*-S*, *--maximum-size* _size_:: +The maximum size to consider. By default this is 0 and 0 has the special meaning of unlimited. The _size_ argument may be followed by the multiplicative suffixes KiB (=1024), MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB and YiB (the "iB" is optional, e.g., "K" has the same meaning as "KiB"). + +*-t*, *--ignore-time*:: +Link and compare files even if their time of modification is different. This is usually a good choice. + +*-v*, *--verbose*:: +Verbose output, explain to the user what is being done. If specified once, every hardlinked file is displayed. If specified twice, it also shows every comparison. + +*-x*, *--exclude* _regex_:: +A regular expression which excludes files from being compared and linked. + +*-X*, *--respect-xattrs*:: +Only try to link files with the same extended attributes. + +*-y*, *--method* _name_:: +Set the file content comparison method. The currently supported methods are +sha256, sha1, crc32c and memcmp. The default is sha256, or memcmp if Linux +Crypto API is not available. The methods based on checksums are implemented in +zero-copy way, in this case file contents are not copied to the userspace and all +calculation is done in kernel. + +*--reflink*[=_when_]:: +Create copy-on-write clones (aka reflinks) rather than hardlinks. The reflinked files +share only on-disk data, but the file mode and owner can be different. It's recommended +to use it with *--ignore-owner* and *--ignore-mode* options. This option implies +*--skip-reflinks* to ignore already cloned files. ++ +The optional argument _when_ can be *never*, *always*, or *auto*. If the _when_ argument +is omitted, it defaults to *auto*, in this case, *hardlink* checks filesystem type and +uses reflinks on BTRFS and XFS only, and fallback to hardlinks when creating reflink is impossible. +The argument *always* disables filesystem type detection and fallback to hardlinks, in this case, +only reflinks are allowed. + +*--skip-reflinks*:: +Ignore already cloned files. This option may be used without *--reflink* when creating classic hardlinks. + + +== ARGUMENTS + +*hardlink* takes one or more directories which will be searched for files to be linked. + +== BUGS + +The original *hardlink* implementation uses the option *-f* to force hardlinks creation between filesystem. This very rarely usable feature is no more supported by the current *hardlink*. + +*hardlink* assumes that the trees it operates on do not change during operation. If a tree does change, the result is undefined and potentially dangerous. For example, if a regular file is replaced by a device, *hardlink* may start reading from the device. If a component of a path is replaced by a symbolic link or file permissions change, security may be compromised. Do not run *hardlink* on a changing tree or on a tree controlled by another user. + +== AUTHOR + +There are multiple *hardlink* implementations. The very first implementation is from Jakub Jelinek for Fedora distribution, this implementation has been used in util-linux between versions v2.34 to v2.36. The current implementations is based on Debian version from Julian Andres Klode. + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c new file mode 100644 index 0000000..7e66dfd --- /dev/null +++ b/misc-utils/hardlink.c @@ -0,0 +1,1454 @@ +/* hardlink.c - Link multiple identical files together + * + * Copyright (C) 2008 - 2014 Julian Andres Klode <jak@jak-linux.org> + * Copyright (C) 2021 Karel Zak <kzak@redhat.com> + * + * SPDX-License-Identifier: MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#define _POSIX_C_SOURCE 200112L /* POSIX functions */ +#define _XOPEN_SOURCE 600 /* nftw() */ + +#include <sys/types.h> /* stat */ +#include <sys/stat.h> /* stat */ +#include <sys/time.h> /* getrlimit, getrusage */ +#include <sys/resource.h> /* getrlimit, getrusage */ +#include <fcntl.h> /* posix_fadvise */ +#include <ftw.h> /* ftw */ +#include <search.h> /* tsearch() and friends */ +#include <signal.h> /* SIG*, sigaction */ +#include <getopt.h> /* getopt_long() */ +#include <ctype.h> /* tolower() */ +#include <sys/ioctl.h> + +#if defined(HAVE_LINUX_FIEMAP_H) && defined(HAVE_SYS_VFS_H) +# include <linux/fs.h> +# include <linux/fiemap.h> +# ifdef FICLONE +# define USE_REFLINK 1 +# endif +#endif + +#include "nls.h" +#include "c.h" +#include "xalloc.h" +#include "strutils.h" +#include "monotonic.h" +#include "optutils.h" +#include "fileeq.h" + +#ifdef USE_REFLINK +# include "statfs_magic.h" +#endif + +#include <regex.h> /* regcomp(), regexec() */ + +#if defined(HAVE_SYS_XATTR_H) && defined(HAVE_LLISTXATTR) && defined(HAVE_LGETXATTR) +# include <sys/xattr.h> +# define USE_XATTR 1 +#endif + +static int quiet; /* don't print anything */ +static int rootbasesz; /* size of the directory for nftw() */ + +#ifdef USE_REFLINK +enum { + REFLINK_NEVER = 0, + REFLINK_AUTO, + REFLINK_ALWAYS +}; +static int reflink_mode = REFLINK_NEVER; +static int reflinks_skip; +#endif + +static struct ul_fileeq fileeq; + +/** + * struct file - Information about a file + * @st: The stat buffer associated with the file + * @next: Next file with the same size + * @basename: The offset off the basename in the filename + * @path: The path of the file + * + * This contains all information we need about a file. + */ +struct file { + struct stat st; + struct ul_fileeq_data data; + + struct file *next; + struct link { + struct link *next; + int basename; + int dirname; +#if __STDC_VERSION__ >= 199901L + char path[]; +#elif __GNUC__ + char path[0]; +#else + char path[1]; +#endif + } *links; +}; + +/** + * enum log_level - Logging levels + * @JLOG_SUMMARY: Default log level + * @JLOG_INFO: Verbose logging (verbose == 1) + * @JLOG_VERBOSE1: Verbosity 2 + * @JLOG_VERBOSE2: Verbosity 3 + */ +enum log_level { + JLOG_SUMMARY, + JLOG_INFO, + JLOG_VERBOSE1, + JLOG_VERBOSE2 +}; + +/** + * struct statistic - Statistics about the file + * @started: Whether we are post command-line processing + * @files: The number of files worked on + * @linked: The number of files replaced by a hardlink to a master + * @xattr_comparisons: The number of extended attribute comparisons + * @comparisons: The number of comparisons + * @saved: The (exaggerated) amount of space saved + * @start_time: The time we started at + */ +static struct statistics { + int started; + size_t files; + size_t linked; + size_t xattr_comparisons; + size_t comparisons; + size_t ignored_reflinks; + double saved; + struct timeval start_time; +} stats; + + +struct hdl_regex { + regex_t re; /* POSIX compatible regex handler */ + + struct hdl_regex *next; +}; + +/** + * struct options - Processed command-line options + * @include: A linked list of regular expressions for the --include option + * @exclude: A linked list of regular expressions for the --exclude option + * @verbosity: The verbosity. Should be one of #enum log_level + * @respect_mode: Whether to respect file modes (default = TRUE) + * @respect_owner: Whether to respect file owners (uid, gid; default = TRUE) + * @respect_name: Whether to respect file names (default = FALSE) + * @respect_time: Whether to respect file modification times (default = TRUE) + * @respect_xattrs: Whether to respect extended attributes (default = FALSE) + * @maximise: Chose the file with the highest link count as master + * @minimise: Chose the file with the lowest link count as master + * @keep_oldest: Choose the file with oldest timestamp as master (default = FALSE) + * @dry_run: Specifies whether hardlink should not link files (default = FALSE) + * @min_size: Minimum size of files to consider. (default = 1 byte) + * @max_size: Maximum size of files to consider, 0 means umlimited. (default = 0 byte) + */ +static struct options { + struct hdl_regex *include; + struct hdl_regex *exclude; + + const char *method; + signed int verbosity; + unsigned int respect_mode:1; + unsigned int respect_owner:1; + unsigned int respect_name:1; + unsigned int respect_dir:1; + unsigned int respect_time:1; + unsigned int respect_xattrs:1; + unsigned int maximise:1; + unsigned int minimise:1; + unsigned int keep_oldest:1; + unsigned int dry_run:1; + uintmax_t min_size; + uintmax_t max_size; + size_t io_size; + size_t cache_size; +} opts = { + /* default setting */ +#ifdef USE_FILEEQ_CRYPTOAPI + .method = "sha256", +#else + .method = "memcmp", +#endif + .respect_mode = TRUE, + .respect_owner = TRUE, + .respect_time = TRUE, + .respect_xattrs = FALSE, + .keep_oldest = FALSE, + .min_size = 1, + .cache_size = 10*1024*1024 +}; + +/* + * files + * + * A binary tree of files, managed using tsearch(). To see which nodes + * are considered equal, see compare_nodes() + */ +static void *files; +static void *files_by_ino; + +/* + * last_signal + * + * The last signal we received. We store the signal here in order to be able + * to break out of loops gracefully and to return from our nftw() handler. + */ +static volatile sig_atomic_t last_signal; + + +#define is_log_enabled(_level) (quiet == 0 && (_level) <= (unsigned int)opts.verbosity) + +/** + * jlog - Logging for hardlink + * @level: The log level + * @format: A format string for printf() + */ +__attribute__((format(printf, 2, 3))) +static void jlog(enum log_level level, const char *format, ...) +{ + va_list args; + + if (!is_log_enabled(level)) + return; + + va_start(args, format); + vfprintf(stdout, format, args); + va_end(args); + fputc('\n', stdout); +} + +/** + * CMP - Compare two numerical values, return 1, 0, or -1 + * @a: First value + * @b: Second value + * + * Used to compare two integers of any size while avoiding overflow. + */ +#define CMP(a, b) ((a) > (b) ? 1 : ((a) < (b) ? -1 : 0)) + +/** + * register_regex - Compile and insert a regular expression into list + * @pregs: Pointer to a linked list of regular expressions + * @regex: String containing the regular expression to be compiled + */ +static void register_regex(struct hdl_regex **pregs, const char *regex) +{ + struct hdl_regex *link; + int err; + + link = xmalloc(sizeof(*link)); + + if ((err = regcomp(&link->re, regex, REG_NOSUB | REG_EXTENDED)) != 0) { + size_t size = regerror(err, &link->re, NULL, 0); + char *buf = xmalloc(size + 1); + + regerror(err, &link->re, buf, size); + + errx(EXIT_FAILURE, _("could not compile regular expression %s: %s"), + regex, buf); + } + link->next = *pregs; *pregs = link; +} + +/** + * match_any_regex - Match against multiple regular expressions + * @pregs: A linked list of regular expressions + * @what: The string to match against + * + * Checks whether any of the regular expressions in the list matches the + * string. + */ +static int match_any_regex(struct hdl_regex *pregs, const char *what) +{ + for (; pregs != NULL; pregs = pregs->next) { + if (regexec(&pregs->re, what, 0, NULL, 0) == 0) + return TRUE; + } + return FALSE; +} + +/** + * compare_nodes - Node comparison function + * @_a: The first node (a #struct file) + * @_b: The second node (a #struct file) + * + * Compare the two nodes for the binary tree. + */ +static int compare_nodes(const void *_a, const void *_b) +{ + const struct file *a = _a; + const struct file *b = _b; + int diff = 0; + + if (diff == 0) + diff = CMP(a->st.st_dev, b->st.st_dev); + if (diff == 0) + diff = CMP(a->st.st_size, b->st.st_size); + + return diff; +} + +/* Compare only filenames */ +static inline int filename_strcmp(const struct file *a, const struct file *b) +{ + return strcmp( a->links->path + a->links->basename, + b->links->path + b->links->basename); +} + +/** + * Compare only directory names (ignores root directory and basename (filename)) + * + * The complete path conrains three fragments: + * + * <rootdir> is specified on hardlink command line + * <dirname> is all betweehn rootdir and filename + * <filename> is last component (aka basename) + */ +static inline int dirname_strcmp(const struct file *a, const struct file *b) +{ + int diff = 0; + int asz = a->links->basename - a->links->dirname, + bsz = b->links->basename - b->links->dirname; + + diff = CMP(asz, bsz); + + if (diff == 0) { + const char *a_start, *b_start; + + a_start = a->links->path + a->links->dirname; + b_start = b->links->path + b->links->dirname; + + diff = strncmp(a_start, b_start, asz); + } + return diff; +} + +/** + * compare_nodes_ino - Node comparison function + * @_a: The first node (a #struct file) + * @_b: The second node (a #struct file) + * + * Compare the two nodes for the binary tree. + */ +static int compare_nodes_ino(const void *_a, const void *_b) +{ + const struct file *a = _a; + const struct file *b = _b; + int diff = 0; + + if (diff == 0) + diff = CMP(a->st.st_dev, b->st.st_dev); + if (diff == 0) + diff = CMP(a->st.st_ino, b->st.st_ino); + + /* If opts.respect_name is used, we will restrict a struct file to + * contain only links with the same basename to keep the rest simple. + */ + if (diff == 0 && opts.respect_name) + diff = filename_strcmp(a, b); + if (diff == 0 && opts.respect_dir) + diff = dirname_strcmp(a, b); + + return diff; +} + +/** + * print_stats - Print statistics to stdout + */ +static void print_stats(void) +{ + struct timeval end = { 0, 0 }, delta = { 0, 0 }; + char *ssz; + + gettime_monotonic(&end); + timersub(&end, &stats.start_time, &delta); + + jlog(JLOG_SUMMARY, "%-25s %s", _("Mode:"), + opts.dry_run ? _("dry-run") : _("real")); + jlog(JLOG_SUMMARY, "%-25s %s", _("Method:"), opts.method); + jlog(JLOG_SUMMARY, "%-25s %zu", _("Files:"), stats.files); + jlog(JLOG_SUMMARY, _("%-25s %zu files"), _("Linked:"), stats.linked); + +#ifdef USE_XATTR + jlog(JLOG_SUMMARY, _("%-25s %zu xattrs"), _("Compared:"), + stats.xattr_comparisons); +#endif + jlog(JLOG_SUMMARY, _("%-25s %zu files"), _("Compared:"), + stats.comparisons); +#ifdef USE_REFLINK + if (reflinks_skip) + jlog(JLOG_SUMMARY, _("%-25s %zu files"), _("Skipped reflinks:"), + stats.ignored_reflinks); +#endif + ssz = size_to_human_string(SIZE_SUFFIX_3LETTER | + SIZE_SUFFIX_SPACE | + SIZE_DECIMAL_2DIGITS, stats.saved); + + jlog(JLOG_SUMMARY, "%-25s %s", _("Saved:"), ssz); + free(ssz); + + jlog(JLOG_SUMMARY, _("%-25s %"PRId64".%06"PRId64" seconds"), _("Duration:"), + (int64_t)delta.tv_sec, (int64_t)delta.tv_usec); +} + +/** + * handle_interrupt - Handle a signal + * + * Returns: %TRUE on SIGINT, SIGTERM; %FALSE on all other signals. + */ +static int handle_interrupt(void) +{ + switch (last_signal) { + case SIGINT: + case SIGTERM: + return TRUE; + case SIGUSR1: + print_stats(); + putchar('\n'); + break; + } + + last_signal = 0; + return FALSE; +} + +#ifdef USE_XATTR + +/** + * llistxattr_or_die - Wrapper for llistxattr() + * + * This does the same thing as llistxattr() except that it aborts if any error + * other than "not supported" is detected. + */ +static ssize_t llistxattr_or_die(const char *path, char *list, size_t size) +{ + ssize_t len = llistxattr(path, list, size); + + if (len < 0 && errno != ENOTSUP) + err(EXIT_FAILURE, _("cannot get xattr names for %s"), path); + + return len; +} + +/** + * lgetxattr_or_die - Wrapper for lgetxattr() + * + * This does the same thing as lgetxattr() except that it aborts upon error. + */ +static ssize_t lgetxattr_or_die(const char *path, + const char *name, void *value, size_t size) +{ + ssize_t len = lgetxattr(path, name, value, size); + + if (len < 0) + err(EXIT_FAILURE, _("cannot get xattr value of %s for %s"), + name, path); + + return len; +} + +/** + * get_xattr_name_count - Count the number of xattr names + * @names: a non-empty table of concatenated, null-terminated xattr names + * @len: the total length of the table + * + * @Returns the number of xattr names + */ +static int get_xattr_name_count(const char *const names, ssize_t len) +{ + int count = 0; + const char *name; + + for (name = names; name < (names + len); name += strlen(name) + 1) + count++; + + return count; +} + +/** + * cmp_xattr_name_ptrs - Compare two pointers to xattr names by comparing + * the names they point to. + */ +static int cmp_xattr_name_ptrs(const void *ptr1, const void *ptr2) +{ + return strcmp(*(char *const *)ptr1, *(char *const *)ptr2); +} + +/** + * get_sorted_xattr_name_table - Create a sorted table of xattr names. + * @names - table of concatenated, null-terminated xattr names + * @n - the number of names + * + * @Returns allocated table of pointers to the names, sorted alphabetically + */ +static const char **get_sorted_xattr_name_table(const char *names, int n) +{ + const char **table = xmalloc(n * sizeof(char *)); + int i; + + for (i = 0; i < n; i++) { + table[i] = names; + names += strlen(names) + 1; + } + + qsort(table, n, sizeof(char *), cmp_xattr_name_ptrs); + + return table; +} + +/** + * file_xattrs_equal - Compare the extended attributes of two files + * @a: The first file + * @b: The second file + * + * @Returns: %TRUE if and only if extended attributes are equal + */ +static int file_xattrs_equal(const struct file *a, const struct file *b) +{ + ssize_t len_a; + ssize_t len_b; + char *names_a = NULL; + char *names_b = NULL; + int n_a; + int n_b; + const char **name_ptrs_a = NULL; + const char **name_ptrs_b = NULL; + void *value_a = NULL; + void *value_b = NULL; + int ret = FALSE; + int i; + + assert(a->links != NULL); + assert(b->links != NULL); + + jlog(JLOG_VERBOSE1, _("Comparing xattrs of %s to %s"), a->links->path, + b->links->path); + + stats.xattr_comparisons++; + + len_a = llistxattr_or_die(a->links->path, NULL, 0); + len_b = llistxattr_or_die(b->links->path, NULL, 0); + + if (len_a <= 0 && len_b <= 0) + return TRUE; // xattrs not supported or neither file has any + + if (len_a != len_b) + return FALSE; // total lengths of xattr names differ + + names_a = xmalloc(len_a); + names_b = xmalloc(len_b); + + len_a = llistxattr_or_die(a->links->path, names_a, len_a); + len_b = llistxattr_or_die(b->links->path, names_b, len_b); + assert((len_a > 0) && (len_a == len_b)); + + n_a = get_xattr_name_count(names_a, len_a); + n_b = get_xattr_name_count(names_b, len_b); + + if (n_a != n_b) + goto exit; // numbers of xattrs differ + + name_ptrs_a = get_sorted_xattr_name_table(names_a, n_a); + name_ptrs_b = get_sorted_xattr_name_table(names_b, n_b); + + // We now have two sorted tables of xattr names. + + for (i = 0; i < n_a; i++) { + if (handle_interrupt()) + goto exit; // user wants to quit + + if (strcmp(name_ptrs_a[i], name_ptrs_b[i]) != 0) + goto exit; // names at same slot differ + + len_a = + lgetxattr_or_die(a->links->path, name_ptrs_a[i], NULL, 0); + len_b = + lgetxattr_or_die(b->links->path, name_ptrs_b[i], NULL, 0); + + if (len_a != len_b) + goto exit; // xattrs with same name, different value lengths + + value_a = xmalloc(len_a); + value_b = xmalloc(len_b); + + len_a = lgetxattr_or_die(a->links->path, name_ptrs_a[i], + value_a, len_a); + len_b = lgetxattr_or_die(b->links->path, name_ptrs_b[i], + value_b, len_b); + assert((len_a >= 0) && (len_a == len_b)); + + if (memcmp(value_a, value_b, len_a) != 0) + goto exit; // xattrs with same name, different values + + free(value_a); + free(value_b); + value_a = NULL; + value_b = NULL; + } + + ret = TRUE; + + exit: + free(names_a); + free(names_b); + free(name_ptrs_a); + free(name_ptrs_b); + free(value_a); + free(value_b); + return ret; +} +#else /* !USE_XATTR */ +static int file_xattrs_equal(const struct file *a, const struct file *b) +{ + return TRUE; +} +#endif /* USE_XATTR */ + +/** + * file_may_link_to - Check whether a file may replace another one + * @a: The first file + * @b: The second file + * + * Check whether the two files are considered equal attributes and can be + * linked. This function does not compare content od the files! + */ +static int file_may_link_to(const struct file *a, const struct file *b) +{ + return (a->st.st_size != 0 && + a->st.st_size == b->st.st_size && + a->links != NULL && b->links != NULL && + a->st.st_dev == b->st.st_dev && + a->st.st_ino != b->st.st_ino && + (!opts.respect_mode || a->st.st_mode == b->st.st_mode) && + (!opts.respect_owner || a->st.st_uid == b->st.st_uid) && + (!opts.respect_owner || a->st.st_gid == b->st.st_gid) && + (!opts.respect_time || a->st.st_mtime == b->st.st_mtime) && + (!opts.respect_name || filename_strcmp(a, b) == 0) && + (!opts.respect_dir || dirname_strcmp(a, b) == 0) && + (!opts.respect_xattrs || file_xattrs_equal(a, b))); +} + +/** + * file_compare - Compare two files to decide which should be master + * @a: The first file + * @b: The second file + * + * Check which of the files should be considered greater and thus serve + * as the master when linking (the master is the file that all equal files + * will be replaced with). + */ +static int file_compare(const struct file *a, const struct file *b) +{ + int res = 0; + if (a->st.st_dev == b->st.st_dev && a->st.st_ino == b->st.st_ino) + return 0; + + if (res == 0 && opts.maximise) + res = CMP(a->st.st_nlink, b->st.st_nlink); + if (res == 0 && opts.minimise) + res = CMP(b->st.st_nlink, a->st.st_nlink); + if (res == 0) + res = opts.keep_oldest ? CMP(b->st.st_mtime, a->st.st_mtime) + : CMP(a->st.st_mtime, b->st.st_mtime); + if (res == 0) + res = CMP(b->st.st_ino, a->st.st_ino); + + return res; +} + +#ifdef USE_REFLINK +static inline int do_link(struct file *a, struct file *b, + const char *new_name, int reflink) +{ + if (reflink) { + int dest = -1, src = -1; + + dest = open(new_name, O_CREAT|O_WRONLY|O_TRUNC, 0600); + if (dest < 0) + goto fallback; + if (fchmod(dest, b->st.st_mode) != 0) + goto fallback; + if (fchown(dest, b->st.st_uid, b->st.st_gid) != 0) + goto fallback; + src = open(a->links->path, O_RDONLY); + if (src < 0) + goto fallback; + if (ioctl(dest, FICLONE, src) != 0) + goto fallback; + close(dest); + close(src); + return 0; +fallback: + if (dest >= 0) { + close(dest); + unlink(new_name); + } + if (src >= 0) + close(src); + + if (reflink_mode == REFLINK_ALWAYS) + return -errno; + jlog(JLOG_VERBOSE2,_("Reflinking failed, fallback to hardlinking")); + } + + return link(a->links->path, new_name); +} +#else +static inline int do_link(struct file *a, + struct file *b __attribute__((__unused__)), + const char *new_name, + int reflink __attribute__((__unused__))) +{ + return link(a->links->path, new_name); +} +#endif /* USE_REFLINK */ + +/** + * file_link - Replace b with a link to a + * @a: The first file + * @b: The second file + * + * Link the file, replacing @b with the current one. The file is first + * linked to a temporary name, and then renamed to the name of @b, making + * the replace atomic (@b will always exist). + */ +static int file_link(struct file *a, struct file *b, int reflink) +{ + + file_link: + assert(a->links != NULL); + assert(b->links != NULL); + + if (is_log_enabled(JLOG_INFO)) { + char *ssz = size_to_human_string(SIZE_SUFFIX_3LETTER | + SIZE_SUFFIX_SPACE | + SIZE_DECIMAL_2DIGITS, a->st.st_size); + jlog(JLOG_INFO, _("%s%sLinking %s to %s (-%s)"), + opts.dry_run ? _("[DryRun] ") : "", + reflink ? "Ref" : "", + a->links->path, b->links->path, + ssz); + free(ssz); + } + + if (!opts.dry_run) { + char *new_path; + int failed = 1; + + xasprintf(&new_path, "%s.hardlink-temporary", b->links->path); + + if (do_link(a, b, new_path, reflink) != 0) + warn(_("cannot link %s to %s"), a->links->path, new_path); + + else if (rename(new_path, b->links->path) != 0) { + warn(_("cannot rename %s to %s"), a->links->path, new_path); + unlink(new_path); + } else + failed = 0; + + free(new_path); + if (failed) + return FALSE; + } + + /* Update statistics */ + stats.linked++; + + /* Increase the link count of this file, and set stat() of other file */ + a->st.st_nlink++; + b->st.st_nlink--; + + if (b->st.st_nlink == 0) + stats.saved += a->st.st_size; + + /* Move the link from file b to a */ + { + struct link *new_link = b->links; + + b->links = b->links->next; + new_link->next = a->links->next; + a->links->next = new_link; + } + + /* Do it again */ + if (b->links) + goto file_link; + + return TRUE; +} + +static int has_fpath(struct file *node, const char *path) +{ + struct link *l; + + for (l = node->links; l; l = l->next) { + if (strcmp(l->path, path) == 0) + return 1; + } + + return 0; +} + + +/** + * inserter - Callback function for nftw() + * @fpath: The path of the file being visited + * @sb: The stat information of the file + * @typeflag: The type flag + * @ftwbuf: Contains current level of nesting and offset of basename + * + * Called by nftw() for the files. See the manual page for nftw() for + * further information. + */ +static int inserter(const char *fpath, const struct stat *sb, + int typeflag, struct FTW *ftwbuf) +{ + struct file *fil; + struct file **node; + size_t pathlen; + int included; + int excluded; + + if (handle_interrupt()) + return 1; + if (typeflag == FTW_DNR || typeflag == FTW_NS) + warn(_("cannot read %s"), fpath); + if (typeflag != FTW_F || !S_ISREG(sb->st_mode)) + return 0; + + included = match_any_regex(opts.include, fpath); + excluded = match_any_regex(opts.exclude, fpath); + + if ((opts.exclude && excluded && !included) || + (!opts.exclude && opts.include && !included)) + return 0; + + stats.files++; + + if ((uintmax_t) sb->st_size < opts.min_size) { + jlog(JLOG_VERBOSE1, + _("Skipped %s (smaller than configured size)"), fpath); + return 0; + } + + jlog(JLOG_VERBOSE2, " %5zu: [%" PRIu64 "/%" PRIu64 "/%zu] %s", + stats.files, sb->st_dev, sb->st_ino, + (size_t) sb->st_nlink, fpath); + + if ((opts.max_size > 0) && ((uintmax_t) sb->st_size > opts.max_size)) { + jlog(JLOG_VERBOSE1, + _("Skipped %s (greater than configured size)"), fpath); + return 0; + } + + pathlen = strlen(fpath) + 1; + + fil = xcalloc(1, sizeof(*fil)); + fil->links = xcalloc(1, sizeof(struct link) + pathlen); + + fil->st = *sb; + fil->links->basename = ftwbuf->base; + fil->links->dirname = rootbasesz; + fil->links->next = NULL; + + memcpy(fil->links->path, fpath, pathlen); + + node = tsearch(fil, &files_by_ino, compare_nodes_ino); + + if (node == NULL) + goto fail; + + if (*node != fil) { + /* Already known inode, add link to inode information */ + assert((*node)->st.st_dev == sb->st_dev); + assert((*node)->st.st_ino == sb->st_ino); + + if (has_fpath(*node, fpath)) { + jlog(JLOG_VERBOSE1, + _("Skipped %s (specified more than once)"), fpath); + free(fil->links); + } else { + fil->links->next = (*node)->links; + (*node)->links = fil->links; + } + + free(fil); + } else { + /* New inode, insert into by-size table */ + node = tsearch(fil, &files, compare_nodes); + + if (node == NULL) + goto fail; + + if (*node != fil) { + struct file *l; + + if (file_compare(fil, *node) >= 0) { + fil->next = *node; + *node = fil; + } else { + for (l = *node; l != NULL; l = l->next) { + if (l->next != NULL + && file_compare(fil, l->next) < 0) + continue; + + fil->next = l->next; + l->next = fil; + + break; + } + } + } + } + + return 0; + + fail: + warn(_("cannot continue")); /* probably ENOMEM */ + return 0; +} + +#ifdef USE_REFLINK +static int is_reflink_compatible(dev_t devno, const char *filename) +{ + static dev_t last_dev = 0; + static int last_status = 0; + + if (last_dev != devno) { + struct statfs vfs; + + if (statfs(filename, &vfs) != 0) + return 0; + + last_dev = devno; + switch (vfs.f_type) { + case STATFS_BTRFS_MAGIC: + case STATFS_XFS_MAGIC: + last_status = 1; + break; + default: + last_status = 0; + break; + } + } + + return last_status; +} + +static int is_reflink(struct file *xa, struct file *xb) +{ + int last = 0, rc = 0; + char abuf[BUFSIZ] = { 0 }, + bbuf[BUFSIZ] = { 0 }; + + struct fiemap *amap = (struct fiemap *) abuf, + *bmap = (struct fiemap *) bbuf; + + int af = open(xa->links->path, O_RDONLY), + bf = open(xb->links->path, O_RDONLY); + + if (af < 0 || bf < 0) + goto done; + + do { + size_t i; + + amap->fm_length = ~0ULL; + amap->fm_flags = FIEMAP_FLAG_SYNC; + amap->fm_extent_count = (sizeof(abuf) - sizeof(*amap)) / sizeof(struct fiemap_extent); + + bmap->fm_length = ~0ULL; + bmap->fm_flags = FIEMAP_FLAG_SYNC; + bmap->fm_extent_count = (sizeof(bbuf) - sizeof(*bmap)) / sizeof(struct fiemap_extent); + + if (ioctl(af, FS_IOC_FIEMAP, (unsigned long) amap) < 0) + goto done; + if (ioctl(bf, FS_IOC_FIEMAP, (unsigned long) bmap) < 0) + goto done; + + if (amap->fm_mapped_extents != bmap->fm_mapped_extents) + goto done; + + for (i = 0; i < amap->fm_mapped_extents; i++) { + struct fiemap_extent *a = &amap->fm_extents[i]; + struct fiemap_extent *b = &bmap->fm_extents[i]; + + if (a->fe_logical != b->fe_logical || + a->fe_length != b->fe_length || + a->fe_physical != b->fe_physical) + goto done; + if (!(a->fe_flags & FIEMAP_EXTENT_SHARED) || + !(b->fe_flags & FIEMAP_EXTENT_SHARED)) + goto done; + if (a->fe_flags & FIEMAP_EXTENT_LAST) + last = 1; + } + + bmap->fm_start = amap->fm_start = + amap->fm_extents[amap->fm_mapped_extents - 1].fe_logical + + amap->fm_extents[amap->fm_mapped_extents - 1].fe_length; + } while (last == 0); + + rc = 1; +done: + if (af >= 0) + close(af); + if (bf >= 0) + close(bf); + return rc; +} +#endif /* USE_REFLINK */ + +static inline size_t count_nodes(struct file *x) +{ + size_t ct = 0; + + for ( ; x != NULL; x = x->next) + ct++; + + return ct; +} + +/** + * visitor - Callback for twalk() + * @nodep: Pointer to a pointer to a #struct file + * @which: At which point this visit is (preorder, postorder, endorder) + * @depth: The depth of the node in the tree + * + * Visit the nodes in the binary tree. For each node, call hardlinker() + * on each #struct file in the linked list of #struct file instances located + * at that node. + */ +static void visitor(const void *nodep, const VISIT which, const int depth) +{ + struct file *master = *(struct file **)nodep; + struct file *begin = master; + struct file *other; + + (void)depth; + + if (which != leaf && which != endorder) + return; + + for (; master != NULL; master = master->next) { + size_t nnodes, memsiz; + int may_reflink = 0; + + if (handle_interrupt()) + exit(EXIT_FAILURE); + if (master->links == NULL) + continue; + + /* calculate per file max memory use */ + nnodes = count_nodes(master); + if (!nnodes) + continue; + + /* per-file cache size */ + memsiz = opts.cache_size / nnodes; + /* filesiz, readsiz, memsiz */ + ul_fileeq_set_size(&fileeq, master->st.st_size, opts.io_size, memsiz); + +#ifdef USE_REFLINK + if (reflink_mode || reflinks_skip) { + may_reflink = + reflink_mode == REFLINK_ALWAYS ? 1 : + is_reflink_compatible(master->st.st_dev, + master->links->path); + } +#endif + for (other = master->next; other != NULL; other = other->next) { + int eq; + + if (handle_interrupt()) + exit(EXIT_FAILURE); + + assert(other != other->next); + assert(other->st.st_size == master->st.st_size); + + if (!other->links) + continue; + + /* check file attributes, etc. */ + if (!file_may_link_to(master, other)) { + jlog(JLOG_VERBOSE2, + _("Skipped (attributes mismatch) %s"), other->links->path); + continue; + } +#ifdef USE_REFLINK + if (may_reflink && reflinks_skip && is_reflink(master, other)) { + jlog(JLOG_VERBOSE2, + _("Skipped (already reflink) %s"), other->links->path); + stats.ignored_reflinks++; + continue; + } +#endif + /* initialize content comparison */ + if (!ul_fileeq_data_associated(&master->data)) + ul_fileeq_data_set_file(&master->data, master->links->path); + if (!ul_fileeq_data_associated(&other->data)) + ul_fileeq_data_set_file(&other->data, other->links->path); + + /* compare files */ + eq = ul_fileeq(&fileeq, &master->data, &other->data); + + /* reduce number of open files, keep only master open */ + ul_fileeq_data_close_file(&other->data); + + stats.comparisons++; + + if (!eq) { + jlog(JLOG_VERBOSE2, + _("Skipped (content mismatch) %s"), other->links->path); + continue; + } + + /* link files */ + if (!file_link(master, other, may_reflink) && errno == EMLINK) { + ul_fileeq_data_deinit(&master->data); + master = other; + } + } + + /* don't keep master data in memory */ + ul_fileeq_data_deinit(&master->data); + } + + /* final cleanup */ + for (other = begin; other != NULL; other = other->next) { + if (ul_fileeq_data_associated(&other->data)) + ul_fileeq_data_deinit(&other->data); + } +} + +/** + * usage - Print the program help and exit + */ +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] <directory>|<file> ...\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Consolidate duplicate files using hardlinks.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -c, --content compare only file contents, same as -pot\n"), out); + fputs(_(" -b, --io-size <size> I/O buffer size for file reading\n" + " (speedup, using more RAM)\n"), out); + fputs(_(" -d, --respect-dir directory names have to be identical\n"), out); + fputs(_(" -f, --respect-name filenames have to be identical\n"), out); + fputs(_(" -i, --include <regex> regular expression to include files/dirs\n"), out); + fputs(_(" -m, --maximize maximize the hardlink count, remove the file with\n" + " lowest hardlink count\n"), out); + fputs(_(" -M, --minimize reverse the meaning of -m\n"), out); + fputs(_(" -n, --dry-run don't actually link anything\n"), out); + fputs(_(" -o, --ignore-owner ignore owner changes\n"), out); + fputs(_(" -O, --keep-oldest keep the oldest file of multiple equal files\n" + " (lower precedence than minimize/maximize)\n"), out); + fputs(_(" -p, --ignore-mode ignore changes of file mode\n"), out); + fputs(_(" -q, --quiet quiet mode - don't print anything\n"), out); + fputs(_(" -r, --cache-size <size> memory limit for cached file content data\n"), out); + fputs(_(" -s, --minimum-size <size> minimum size for files.\n"), out); + fputs(_(" -S, --maximum-size <size> maximum size for files.\n"), out); + fputs(_(" -t, --ignore-time ignore timestamps (when testing for equality)\n"), out); + fputs(_(" -v, --verbose verbose output (repeat for more verbosity)\n"), out); + fputs(_(" -x, --exclude <regex> regular expression to exclude files\n"), out); +#ifdef USE_XATTR + fputs(_(" -X, --respect-xattrs respect extended attributes\n"), out); +#endif + fputs(_(" -y, --method <name> file content comparison method\n"), out); + +#ifdef USE_REFLINK + fputs(_(" --reflink[=<when>] create clone/CoW copies (auto, always, never)\n"), out); + 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)")); + + exit(EXIT_SUCCESS); +} + +/** + * parse_options - Parse the command line options + * @argc: Number of options + * @argv: Array of options + */ +static int parse_options(int argc, char *argv[]) +{ + enum { + OPT_REFLINK = CHAR_MAX + 1, + OPT_SKIP_RELINKS + }; + static const char optstr[] = "VhvndfpotXcmMOx:y:i:r:S:s:b:q"; + static const struct option long_options[] = { + {"version", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {"verbose", no_argument, NULL, 'v'}, + {"dry-run", no_argument, NULL, 'n'}, + {"respect-name", no_argument, NULL, 'f'}, + {"respect-dir", no_argument, NULL, 'd'}, + {"ignore-mode", no_argument, NULL, 'p'}, + {"ignore-owner", no_argument, NULL, 'o'}, + {"ignore-time", no_argument, NULL, 't'}, + {"respect-xattrs", no_argument, NULL, 'X'}, + {"maximize", no_argument, NULL, 'm'}, + {"minimize", no_argument, NULL, 'M'}, + {"keep-oldest", no_argument, NULL, 'O'}, + {"exclude", required_argument, NULL, 'x'}, + {"include", required_argument, NULL, 'i'}, + {"method", required_argument, NULL, 'y' }, + {"minimum-size", required_argument, NULL, 's'}, + {"maximum-size", required_argument, NULL, 'S'}, +#ifdef USE_REFLINK + {"reflink", optional_argument, NULL, OPT_REFLINK }, + {"skip-reflinks", no_argument, NULL, OPT_SKIP_RELINKS }, +#endif + {"io-size", required_argument, NULL, 'b'}, + {"content", no_argument, NULL, 'c'}, + {"quiet", no_argument, NULL, 'q'}, + {"cache-size", required_argument, NULL, 'r'}, + {NULL, 0, NULL, 0} + }; + static const ul_excl_t excl[] = { + {'q', 'v'}, + {0} + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + int c, content_only = 0; + + while ((c = getopt_long(argc, argv, optstr, long_options, NULL)) != -1) { + + err_exclusive_options(c, long_options, excl, excl_st); + + switch (c) { + case 'p': + opts.respect_mode = FALSE; + break; + case 'o': + opts.respect_owner = FALSE; + break; + case 't': + opts.respect_time = FALSE; + break; + case 'X': + opts.respect_xattrs = TRUE; + break; + case 'm': + opts.maximise = TRUE; + break; + case 'M': + opts.minimise = TRUE; + break; + case 'O': + opts.keep_oldest = TRUE; + break; + case 'f': + opts.respect_name = TRUE; + break; + case 'd': + opts.respect_dir = TRUE; + break; + case 'v': + opts.verbosity++; + break; + case 'q': + quiet = TRUE; + break; + case 'c': + content_only = 1; + break; + case 'n': + opts.dry_run = 1; + break; + case 'x': + register_regex(&opts.exclude, optarg); + break; + case 'y': + opts.method = optarg; + break; + case 'i': + register_regex(&opts.include, optarg); + break; + case 's': + opts.min_size = strtosize_or_err(optarg, _("failed to parse minimum size")); + break; + case 'S': + opts.max_size = strtosize_or_err(optarg, _("failed to parse maximum size")); + break; + case 'r': + opts.cache_size = strtosize_or_err(optarg, _("failed to parse cache size")); + break; + case 'b': + opts.io_size = strtosize_or_err(optarg, _("failed to parse I/O size")); + break; +#ifdef USE_REFLINK + case OPT_REFLINK: + reflink_mode = REFLINK_AUTO; + if (optarg) { + if (strcmp(optarg, "auto") == 0) + reflink_mode = REFLINK_AUTO; + else if (strcmp(optarg, "always") == 0) + reflink_mode = REFLINK_ALWAYS; + else if (strcmp(optarg, "never") == 0) + reflink_mode = REFLINK_NEVER; + else + errx(EXIT_FAILURE, _("unsupported reflink mode; %s"), optarg); + } + if (reflink_mode != REFLINK_NEVER) + reflinks_skip = 1; + break; + case OPT_SKIP_RELINKS: + reflinks_skip = 1; + break; +#endif + case 'h': + usage(); + case 'V': + { + static const char *features[] = { +#ifdef USE_REFLINK + "reflink", +#endif +#ifdef USE_FILEEQ_CRYPTOAPI + "cryptoapi", +#endif + NULL + }; + print_version_with_features(EXIT_SUCCESS, features); + } + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (content_only) { + opts.respect_mode = FALSE; + opts.respect_name = FALSE; + opts.respect_dir = FALSE; + opts.respect_owner = FALSE; + opts.respect_time = FALSE; + opts.respect_xattrs = FALSE; + } + return 0; +} + +/** +* to_be_called_atexit - Cleanup handler, also prints statistics. +*/ +static void to_be_called_atexit(void) +{ + if (stats.started) + print_stats(); +} + +/** +* sighandler - Signal handler, sets the global last_signal variable +* @i: The signal number +*/ +static void sighandler(int i) +{ + if (last_signal != SIGINT) + last_signal = i; + if (i == SIGINT) + /* can't use stdio on signal handler */ + ignore_result(write(STDOUT_FILENO, "\n", sizeof("\n")-1)); +} + +int main(int argc, char *argv[]) +{ + struct sigaction sa; + int rc; + + sa.sa_handler = sighandler; + sa.sa_flags = SA_RESTART; + sigfillset(&sa.sa_mask); + + /* If we receive a SIGINT, end the processing */ + sigaction(SIGINT, &sa, NULL); + sigaction(SIGUSR1, &sa, NULL); + + /* Localize messages, number formatting, and anything else. */ + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + if (atexit(to_be_called_atexit) != 0) + err(EXIT_FAILURE, _("cannot register exit handler")); + + parse_options(argc, argv); + + if (optind == argc) + errx(EXIT_FAILURE, _("no directory or file specified")); + + gettime_monotonic(&stats.start_time); + + rc = ul_fileeq_init(&fileeq, opts.method); + if (rc != 0 && strcmp(opts.method, "memcmp") != 0) { + jlog(JLOG_INFO, _("cannot initialize %s method, use 'memcmp' fallback"), opts.method); + opts.method = "memcmp"; + rc = ul_fileeq_init(&fileeq, opts.method); + } + if (rc < 0) + err(EXIT_FAILURE, _("failed to initialize files comparior")); + + /* defautl I/O size */ + if (!opts.io_size) { + if (strcmp(opts.method, "memcmp") == 0) + opts.io_size = 8*1024; + else + opts.io_size = 1024*1024; + } + + stats.started = TRUE; + + jlog(JLOG_VERBOSE2, _("Scanning [device/inode/links]:")); + for (; optind < argc; optind++) { + char *path = realpath(argv[optind], NULL); + + if (!path) { + warn(_("cannot get realpath: %s"), argv[optind]); + continue; + } + if (opts.respect_dir) + rootbasesz = strlen(path); + if (nftw(path, inserter, 20, FTW_PHYS) == -1) + warn(_("cannot process %s"), path); + free(path); + rootbasesz = 0; + } + + twalk(files, visitor); + + ul_fileeq_deinit(&fileeq); + return 0; +} diff --git a/misc-utils/kill.1 b/misc-utils/kill.1 new file mode 100644 index 0000000..46d7e9d --- /dev/null +++ b/misc-utils/kill.1 @@ -0,0 +1,184 @@ +'\" t +.\" Title: kill +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-11-21 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "KILL" "1" "2023-11-21" "util\-linux 2.39.3" "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" +kill \- terminate a process +.SH "SYNOPSIS" +.sp +\fBkill\fP [\fB\-\fP\fIsignal\fP|\fB\-s\fP \fIsignal\fP|\fB\-p\fP] [\fB\-q\fP \fIvalue\fP] [\fB\-a\fP] [\fB\-\-timeout\fP \fImilliseconds\fP \fIsignal\fP] [\fB\-\-\fP] \fIpid\fP|\fIname\fP... +.sp +\fBkill\fP \fB\-l\fP [\fInumber\fP] | \fB\-L\fP +.SH "DESCRIPTION" +.sp +The command \fBkill\fP sends the specified \fIsignal\fP to the specified processes or process groups. +.sp +If no signal is specified, the \fBTERM\fP signal is sent. The default action for this signal is to terminate the process. This signal should be used in preference to the \fBKILL\fP signal (number 9), since a process may install a handler for the TERM signal in order to perform clean\-up steps before terminating in an orderly fashion. If a process does not terminate after a \fBTERM\fP signal has been sent, then the \fBKILL\fP signal may be used; be aware that the latter signal cannot be caught, and so does not give the target process the opportunity to perform any clean\-up before terminating. +.sp +Most modern shells have a builtin \fBkill\fP command, with a usage rather similar to that of the command described here. The \fB\-\-all\fP, \fB\-\-pid\fP, and \fB\-\-queue\fP options, and the possibility to specify processes by command name, are local extensions. +.sp +If \fIsignal\fP is 0, then no actual signal is sent, but error checking is still performed. +.SH "ARGUMENTS" +.sp +The list of processes to be signaled can be a mixture of names and PIDs. +.sp +\fIpid\fP +.RS 4 +Each \fIpid\fP can be expressed in one of the following ways: +.sp +\fIn\fP +.RS 4 +where \fIn\fP is larger than 0. The process with PID \fIn\fP is signaled. +.RE +.sp +\fB0\fP +.RS 4 +All processes in the current process group are signaled. +.RE +.sp +\fB\-1\fP +.RS 4 +All processes with a PID larger than 1 are signaled. +.RE +.sp +\fB\-\fP\fIn\fP +.RS 4 +where \fIn\fP is larger than 1. All processes in process group \fIn\fP are signaled. When an argument of the form \*(Aq\-n\*(Aq is given, and it is meant to denote a process group, either a signal must be specified first, or the argument must be preceded by a \*(Aq\-\-\*(Aq option, otherwise it will be taken as the signal to send. +.RE +.RE +.sp +\fIname\fP +.RS 4 +All processes invoked using this \fIname\fP will be signaled. +.RE +.SH "OPTIONS" +.sp +\fB\-s\fP, \fB\-\-signal\fP \fIsignal\fP +.RS 4 +The signal to send. It may be given as a name or a number. +.RE +.sp +\fB\-l\fP, \fB\-\-list\fP [\fInumber\fP] +.RS 4 +Print a list of signal names, or convert the given signal number to a name. The signals can be found in \fI/usr/include/linux/signal.h\fP. +.RE +.sp +\fB\-L\fP, \fB\-\-table\fP +.RS 4 +Similar to \fB\-l\fP, but it will print signal names and their corresponding numbers. +.RE +.sp +\fB\-a\fP, \fB\-\-all\fP +.RS 4 +Do not restrict the command\-name\-to\-PID conversion to processes with the same UID as the present process. +.RE +.sp +\fB\-p\fP, \fB\-\-pid\fP +.RS 4 +Only print the process ID (PID) of the named processes, do not send any signals. +.RE +.sp +\fB\-r\fP, \fB\-\-require\-handler\fP +.RS 4 +Do not send the signal if it is not caught in userspace by the signalled process. +.RE +.sp +\fB\-\-verbose\fP +.RS 4 +Print PID(s) that will be signaled with \fBkill\fP along with the signal. +.RE +.sp +\fB\-q\fP, \fB\-\-queue\fP \fIvalue\fP +.RS 4 +Send the signal using \fBsigqueue\fP(3) rather than \fBkill\fP(2). The \fIvalue\fP argument is an integer that is sent along with the signal. If the receiving process has installed a handler for this signal using the \fBSA_SIGINFO\fP flag to \fBsigaction\fP(2), then it can obtain this data via the \fIsi_sigval\fP field of the \fIsiginfo_t\fP structure. +.RE +.sp +\fB\-\-timeout\fP \fImilliseconds signal\fP +.RS 4 +Send a signal defined in the usual way to a process, followed by an additional signal after a specified delay. The \fB\-\-timeout\fP option causes \fBkill\fP to wait for a period defined in \fImilliseconds\fP before sending a follow\-up \fIsignal\fP to the process. This feature is implemented using the Linux kernel PID file descriptor feature in order to guarantee that the follow\-up signal is sent to the same process or not sent if the process no longer exists. +.sp +Note that the operating system may re\-use PIDs and implementing an equivalent feature in a shell using \fBkill\fP and \fBsleep\fP would be subject to races whereby the follow\-up signal might be sent to a different process that used a recycled PID. +.sp +The \fB\-\-timeout\fP option can be specified multiple times: the signals are sent sequentially with the specified timeouts. The \fB\-\-timeout\fP option can be combined with the \fB\-\-queue\fP option. +.sp +As an example, the following command sends the signals \fBQUIT\fP, \fBTERM\fP and \fBKILL\fP in sequence and waits for 1000 milliseconds between sending the signals: +.sp +.if n .RS 4 +.nf +.fam C +kill \-\-verbose \-\-timeout 1000 TERM \-\-timeout 1000 KILL \(rs + \-\-signal QUIT 12345 +.fam +.fi +.if n .RE +.RE +.SH "EXIT STATUS" +.sp +\fBkill\fP has the following exit status values: +.sp +\fB0\fP +.RS 4 +success +.RE +.sp +\fB1\fP +.RS 4 +failure +.RE +.sp +\fB64\fP +.RS 4 +partial success (when more than one process specified) +.RE +.SH "NOTES" +.sp +Although it is possible to specify the TID (thread ID, see \fBgettid\fP(2)) of one of the threads in a multithreaded process as the argument of \fBkill\fP, the signal is nevertheless directed to the process (i.e., the entire thread group). In other words, it is not possible to send a signal to an explicitly selected thread in a multithreaded process. The signal will be delivered to an arbitrarily selected thread in the target process that is not blocking the signal. For more details, see \fBsignal\fP(7) and the description of \fBCLONE_THREAD\fP in \fBclone\fP(2). +.sp +Various shells provide a builtin \fBkill\fP command that is preferred in relation to the \fBkill\fP(1) executable described by this manual. The easiest way to ensure one is executing the command described in this page is to use the full path when calling the command, for example: \fB/bin/kill \-\-version\fP +.SH "AUTHORS" +.sp +.MTO "svalente\(atmit.edu" "Salvatore Valente" "," +.MTO "kzak\(atredhat.com" "Karel Zak" "" +.sp +The original version was taken from BSD 4.4. +.SH "SEE ALSO" +.sp +\fBbash\fP(1), +\fBtcsh\fP(1), +\fBsigaction\fP(2), +\fBkill\fP(2), +\fBsigqueue\fP(3), +\fBsignal\fP(7) +.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 \fBkill\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/kill.1.adoc b/misc-utils/kill.1.adoc new file mode 100644 index 0000000..40ab024 --- /dev/null +++ b/misc-utils/kill.1.adoc @@ -0,0 +1,124 @@ +//po4a: entry man manual +//// +Copyright 1994 Salvatore Valente (svalente@mit.edu) +Copyright 1992 Rickard E. Faith (faith@cs.unc.edu) +May be distributed under the GNU General Public License +//// += kill(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: kill + +== NAME + +kill - terminate a process + +== SYNOPSIS + +*kill* [**-**_signal_|*-s* _signal_|*-p*] [*-q* _value_] [*-a*] [*--timeout* _milliseconds_ _signal_] [*--*] _pid_|_name_... + +*kill* *-l* [_number_] | *-L* + + +== DESCRIPTION + +The command *kill* sends the specified _signal_ to the specified processes or process groups. + +If no signal is specified, the *TERM* signal is sent. The default action for this signal is to terminate the process. This signal should be used in preference to the *KILL* signal (number 9), since a process may install a handler for the TERM signal in order to perform clean-up steps before terminating in an orderly fashion. If a process does not terminate after a *TERM* signal has been sent, then the *KILL* signal may be used; be aware that the latter signal cannot be caught, and so does not give the target process the opportunity to perform any clean-up before terminating. + +Most modern shells have a builtin *kill* command, with a usage rather similar to that of the command described here. The *--all*, *--pid*, and *--queue* options, and the possibility to specify processes by command name, are local extensions. + +If _signal_ is 0, then no actual signal is sent, but error checking is still performed. + +== ARGUMENTS + +The list of processes to be signaled can be a mixture of names and PIDs. + +_pid_:: +Each _pid_ can be expressed in one of the following ways: +_n_;; +where _n_ is larger than 0. The process with PID _n_ is signaled. +*0*;; +All processes in the current process group are signaled. +*-1*;; +All processes with a PID larger than 1 are signaled. +**-**__n__;; +where _n_ is larger than 1. All processes in process group _n_ are signaled. When an argument of the form '-n' is given, and it is meant to denote a process group, either a signal must be specified first, or the argument must be preceded by a '--' option, otherwise it will be taken as the signal to send. + +_name_:: +All processes invoked using this _name_ will be signaled. + +== OPTIONS + +*-s*, *--signal* _signal_:: +The signal to send. It may be given as a name or a number. +*-l*, *--list* [_number_]:: +Print a list of signal names, or convert the given signal number to a name. The signals can be found in _/usr/include/linux/signal.h_. +*-L*, *--table*:: +Similar to *-l*, but it will print signal names and their corresponding numbers. +*-a*, *--all*:: +Do not restrict the command-name-to-PID conversion to processes with the same UID as the present process. +*-p*, *--pid*:: +Only print the process ID (PID) of the named processes, do not send any signals. +*-r*, *--require-handler*:: +Do not send the signal if it is not caught in userspace by the signalled process. +*--verbose*:: +Print PID(s) that will be signaled with *kill* along with the signal. +*-q*, *--queue* _value_:: +Send the signal using *sigqueue*(3) rather than *kill*(2). The _value_ argument is an integer that is sent along with the signal. If the receiving process has installed a handler for this signal using the *SA_SIGINFO* flag to *sigaction*(2), then it can obtain this data via the _si_sigval_ field of the _siginfo_t_ structure. +*--timeout* _milliseconds signal_:: +Send a signal defined in the usual way to a process, followed by an additional signal after a specified delay. The *--timeout* option causes *kill* to wait for a period defined in _milliseconds_ before sending a follow-up _signal_ to the process. This feature is implemented using the Linux kernel PID file descriptor feature in order to guarantee that the follow-up signal is sent to the same process or not sent if the process no longer exists. ++ +Note that the operating system may re-use PIDs and implementing an equivalent feature in a shell using *kill* and *sleep* would be subject to races whereby the follow-up signal might be sent to a different process that used a recycled PID. ++ +The *--timeout* option can be specified multiple times: the signals are sent sequentially with the specified timeouts. The *--timeout* option can be combined with the *--queue* option. ++ +As an example, the following command sends the signals *QUIT*, *TERM* and *KILL* in sequence and waits for 1000 milliseconds between sending the signals: ++ +.... +kill --verbose --timeout 1000 TERM --timeout 1000 KILL \ + --signal QUIT 12345 +.... + +== EXIT STATUS + +*kill* has the following exit status values: + +*0*:: +success +*1*:: +failure +*64*:: +partial success (when more than one process specified) + +== NOTES + +Although it is possible to specify the TID (thread ID, see *gettid*(2)) of one of the threads in a multithreaded process as the argument of *kill*, the signal is nevertheless directed to the process (i.e., the entire thread group). In other words, it is not possible to send a signal to an explicitly selected thread in a multithreaded process. The signal will be delivered to an arbitrarily selected thread in the target process that is not blocking the signal. For more details, see *signal*(7) and the description of *CLONE_THREAD* in *clone*(2). + +Various shells provide a builtin *kill* command that is preferred in relation to the *kill*(1) executable described by this manual. The easiest way to ensure one is executing the command described in this page is to use the full path when calling the command, for example: */bin/kill --version* + +== AUTHORS + +mailto:svalente@mit.edu[Salvatore Valente], +mailto:kzak@redhat.com[Karel Zak] + +The original version was taken from BSD 4.4. + +== SEE ALSO + +*bash*(1), +*tcsh*(1), +*sigaction*(2), +*kill*(2), +*sigqueue*(3), +*signal*(7) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/kill.c b/misc-utils/kill.c new file mode 100644 index 0000000..c469074 --- /dev/null +++ b/misc-utils/kill.c @@ -0,0 +1,560 @@ +/* + * Copyright (c) 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + */ +/* + * oct 5 1994 -- almost entirely re-written to allow for process names. + * modifications (c) salvatore valente <svalente@mit.edu> + * may be used / modified / distributed under the same terms as the original. + * + * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + * + * 1999-11-13 aeb Accept signal numbers 128+s. + * + * Copyright (C) 2014 Sami Kerola <kerolasa@iki.fi> + * Copyright (C) 2014 Karel Zak <kzak@redhat.com> + */ + +#include <ctype.h> /* for isdigit() */ +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "c.h" +#include "closestream.h" +#include "nls.h" +#include "pidfd-utils.h" +#include "procfs.h" +#include "pathnames.h" +#include "signames.h" +#include "strutils.h" +#include "ttyutils.h" +#include "xalloc.h" +#include "fileutils.h" + +/* partial success, otherwise we return regular EXIT_{SUCCESS,FAILURE} */ +#define KILL_EXIT_SOMEOK 64 + +enum { + KILL_FIELD_WIDTH = 11, + KILL_OUTPUT_WIDTH = 72 +}; + +#ifdef UL_HAVE_PIDFD +# include <poll.h> +# include "list.h" +struct timeouts { + int period; + int sig; + struct list_head follow_ups; +}; +#endif + +struct kill_control { + char *arg; + pid_t pid; + int numsig; +#ifdef HAVE_SIGQUEUE + union sigval sigdata; +#endif +#ifdef UL_HAVE_PIDFD + struct list_head follow_ups; +#endif + unsigned int + check_all:1, + do_kill:1, + do_pid:1, + require_handler:1, + use_sigval:1, +#ifdef UL_HAVE_PIDFD + timeout:1, +#endif + verbose:1; +}; + +static void print_signal_name(int signum) +{ + const char *name = signum_to_signame(signum); + + if (name) { + printf("%s\n", name); + return; + } +#ifdef SIGRTMIN + if (SIGRTMIN <= signum && signum <= SIGRTMAX) { + printf("RT%d\n", signum - SIGRTMIN); + return; + } +#endif + printf("%d\n", signum); +} + +static void pretty_print_signal(FILE *fp, size_t term_width, size_t *lpos, + int signum, const char *name) +{ + if (term_width < (*lpos + KILL_FIELD_WIDTH)) { + fputc('\n', fp); + *lpos = 0; + } + *lpos += KILL_FIELD_WIDTH; + fprintf(fp, "%2d %-8s", signum, name); +} + +static void print_all_signals(FILE *fp, int pretty) +{ + size_t n, lth, lpos = 0, width; + const char *signame = NULL; + int signum = 0; + + if (!pretty) { + for (n = 0; get_signame_by_idx(n, &signame, NULL) == 0; n++) { + lth = 1 + strlen(signame); + if (KILL_OUTPUT_WIDTH < lpos + lth) { + fputc('\n', fp); + lpos = 0; + } else if (lpos) + fputc(' ', fp); + lpos += lth; + fputs(signame, fp); + } +#ifdef SIGRTMIN + fputs(" RT<N> RTMIN+<N> RTMAX-<N>", fp); +#endif + fputc('\n', fp); + return; + } + + /* pretty print */ + width = get_terminal_width(KILL_OUTPUT_WIDTH + 1) - 1; + for (n = 0; get_signame_by_idx(n, &signame, &signum) == 0; n++) + pretty_print_signal(fp, width, &lpos, signum, signame); +#ifdef SIGRTMIN + pretty_print_signal(fp, width, &lpos, SIGRTMIN, "RTMIN"); + pretty_print_signal(fp, width, &lpos, SIGRTMAX, "RTMAX"); +#endif + fputc('\n', fp); +} + +static void err_nosig(char *name) +{ + warnx(_("unknown signal %s; valid signals:"), name); + print_all_signals(stderr, 1); + exit(EXIT_FAILURE); +} + +static int arg_to_signum(char *arg, int maskbit) +{ + int numsig; + char *ep; + + if (isdigit(*arg)) { + errno = 0; + numsig = strtol(arg, &ep, 10); + if (NSIG <= numsig && maskbit && (numsig & 128) != 0) + numsig -= 128; + if (errno || *ep != 0 || numsig < 0 || NSIG <= numsig) + return -1; + return numsig; + } + return signame_to_signum(arg); +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] <pid>|<name>...\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Forcibly terminate a process.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -a, --all do not restrict the name-to-pid conversion to processes\n" + " with the same uid as the present process\n"), out); + fputs(_(" -s, --signal <signal> send this <signal> instead of SIGTERM\n"), out); +#ifdef HAVE_SIGQUEUE + fputs(_(" -q, --queue <value> use sigqueue(2), not kill(2), and pass <value> as data\n"), out); +#endif +#ifdef UL_HAVE_PIDFD + fputs(_(" --timeout <milliseconds> <follow-up signal>\n" + " wait up to timeout and send follow-up signal\n"), out); +#endif + fputs(_(" -p, --pid print pids without signaling them\n"), out); + fputs(_(" -l, --list[=<signal>] list signal names, or convert a signal number to a name\n"), out); + fputs(_(" -L, --table list signal names and numbers\n"), out); + fputs(_(" -r, --require-handler do not send signal if signal handler is not present\n"), out); + 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)")); + + exit(EXIT_SUCCESS); +} + +static void __attribute__((__noreturn__)) print_kill_version(void) +{ + static const char *features[] = { +#ifdef HAVE_SIGQUEUE + "sigqueue", +#endif +#ifdef UL_HAVE_PIDFD + "pidfd", +#endif + }; + + printf(_("%s from %s"), program_invocation_short_name, PACKAGE_STRING); + + if (ARRAY_SIZE(features)) { + size_t i; + fputs(_(" (with: "), stdout); + for (i = 0; i < ARRAY_SIZE(features); i++) { + fputs(features[i], stdout); + if (i + 1 < ARRAY_SIZE(features)) + fputs(", ", stdout); + } + fputs(")\n", stdout); + } + exit(EXIT_SUCCESS); +} + +static char **parse_arguments(int argc, char **argv, struct kill_control *ctl) +{ + char *arg; + + /* Loop through the arguments. Actually, -a is the only option + * can be used with other options. The 'kill' is basically a + * one-option-at-most program. */ + for (argc--, argv++; 0 < argc; argc--, argv++) { + arg = *argv; + if (*arg != '-') + break; + if (!strcmp(arg, "--")) { + argc--, argv++; + break; + } + if (!strcmp(arg, "-v") || !strcmp(arg, "-V") || + !strcmp(arg, "--version")) + print_kill_version(); + if (!strcmp(arg, "-h") || !strcmp(arg, "--help")) + usage(); + if (!strcmp(arg, "--verbose")) { + ctl->verbose = 1; + continue; + } + if (!strcmp(arg, "-a") || !strcmp(arg, "--all")) { + ctl->check_all = 1; + continue; + } + if (!strcmp(arg, "-l") || !strcmp(arg, "--list")) { + if (argc < 2) { + print_all_signals(stdout, 0); + exit(EXIT_SUCCESS); + } + if (2 < argc) + errx(EXIT_FAILURE, _("too many arguments")); + /* argc == 2, accept "kill -l $?" */ + arg = argv[1]; + if ((ctl->numsig = arg_to_signum(arg, 1)) < 0) + errx(EXIT_FAILURE, _("unknown signal: %s"), + arg); + print_signal_name(ctl->numsig); + exit(EXIT_SUCCESS); + } + /* for compatibility with procps kill(1) */ + if (!strncmp(arg, "--list=", 7) || !strncmp(arg, "-l=", 3)) { + char *p = strchr(arg, '=') + 1; + if ((ctl->numsig = arg_to_signum(p, 1)) < 0) + errx(EXIT_FAILURE, _("unknown signal: %s"), p); + print_signal_name(ctl->numsig); + exit(EXIT_SUCCESS); + } + if (!strcmp(arg, "-L") || !strcmp(arg, "--table")) { + print_all_signals(stdout, 1); + exit(EXIT_SUCCESS); + } + if (!strcmp(arg, "-r") || !strcmp(arg, "--require-handler")) { + ctl->require_handler = 1; + continue; + } + if (!strcmp(arg, "-p") || !strcmp(arg, "--pid")) { + ctl->do_pid = 1; + if (ctl->do_kill) + errx(EXIT_FAILURE, _("%s and %s are mutually exclusive"), "--pid", "--signal"); +#ifdef HAVE_SIGQUEUE + if (ctl->use_sigval) + errx(EXIT_FAILURE, _("%s and %s are mutually exclusive"), "--pid", "--queue"); +#endif + continue; + } + if (!strcmp(arg, "-s") || !strcmp(arg, "--signal")) { + if (argc < 2) + errx(EXIT_FAILURE, _("not enough arguments")); + ctl->do_kill = 1; + if (ctl->do_pid) + errx(EXIT_FAILURE, _("%s and %s are mutually exclusive"), "--pid", "--signal"); + argc--, argv++; + arg = *argv; + if ((ctl->numsig = arg_to_signum(arg, 0)) < 0) + err_nosig(arg); + continue; + } +#ifdef HAVE_SIGQUEUE + if (!strcmp(arg, "-q") || !strcmp(arg, "--queue")) { + if (argc < 2) + errx(EXIT_FAILURE, _("option '%s' requires an argument"), arg); + if (ctl->do_pid) + errx(EXIT_FAILURE, _("%s and %s are mutually exclusive"), "--pid", "--queue"); + argc--, argv++; + arg = *argv; + ctl->sigdata.sival_int = strtos32_or_err(arg, _("argument error")); + ctl->use_sigval = 1; + continue; + } +#endif +#ifdef UL_HAVE_PIDFD + if (!strcmp(arg, "--timeout")) { + struct timeouts *next; + + ctl->timeout = 1; + if (argc < 2) + errx(EXIT_FAILURE, _("option '%s' requires an argument"), arg); + argc--, argv++; + arg = *argv; + next = xcalloc(1, sizeof(*next)); + next->period = strtos32_or_err(arg, _("argument error")); + INIT_LIST_HEAD(&next->follow_ups); + argc--, argv++; + arg = *argv; + if ((next->sig = arg_to_signum(arg, 0)) < 0) + err_nosig(arg); + list_add_tail(&next->follow_ups, &ctl->follow_ups); + continue; + } +#endif + /* 'arg' begins with a dash but is not a known option. + * So it's probably something like -HUP, or -1/-n try to + * deal with it. + * + * -n could be either signal n or pid -n (a process group + * number). In case of doubt, POSIX tells us to assume a + * signal. But if a signal has already been parsed, then + * assume it is a process group, so stop parsing options. */ + if (ctl->do_kill) + break; + arg++; + if ((ctl->numsig = arg_to_signum(arg, 0)) < 0) + errx(EXIT_FAILURE, _("invalid signal name or number: %s"), arg); + ctl->do_kill = 1; + if (ctl->do_pid) + errx(EXIT_FAILURE, _("%s and %s are mutually exclusive"), "--pid", "--signal"); + } + if (!*argv) + errx(EXIT_FAILURE, _("not enough arguments")); + return argv; +} + +#ifdef UL_HAVE_PIDFD +static int kill_with_timeout(const struct kill_control *ctl) +{ + int pfd, n; + struct pollfd p = { 0 }; + siginfo_t info = { 0 }; + struct list_head *entry; + + info.si_code = SI_QUEUE; + info.si_signo = ctl->numsig; + info.si_uid = getuid(); + info.si_pid = getpid(); + info.si_value.sival_int = + ctl->use_sigval != 0 ? ctl->use_sigval : ctl->numsig; + + if ((pfd = pidfd_open(ctl->pid, 0)) < 0) + err(EXIT_FAILURE, _("pidfd_open() failed: %d"), ctl->pid); + p.fd = pfd; + p.events = POLLIN; + + if (pidfd_send_signal(pfd, ctl->numsig, &info, 0) < 0) + err(EXIT_FAILURE, _("pidfd_send_signal() failed")); + list_for_each(entry, &ctl->follow_ups) { + struct timeouts *timeout; + + timeout = list_entry(entry, struct timeouts, follow_ups); + n = poll(&p, 1, timeout->period); + if (n < 0) + err(EXIT_FAILURE, _("poll() failed")); + if (n == 0) { + info.si_signo = timeout->sig; + if (ctl->verbose) + printf(_("timeout, sending signal %d to pid %d\n"), + timeout->sig, ctl->pid); + if (pidfd_send_signal(pfd, timeout->sig, &info, 0) < 0) + err(EXIT_FAILURE, _("pidfd_send_signal() failed")); + } + } + return 0; +} +#endif + +static int kill_verbose(const struct kill_control *ctl) +{ + int rc = 0; + + if (ctl->verbose) + printf(_("sending signal %d to pid %d\n"), ctl->numsig, ctl->pid); + if (ctl->do_pid) { + printf("%ld\n", (long) ctl->pid); + return 0; + } +#ifdef UL_HAVE_PIDFD + if (ctl->timeout) { + rc = kill_with_timeout(ctl); + } else +#endif +#ifdef HAVE_SIGQUEUE + if (ctl->use_sigval) + rc = sigqueue(ctl->pid, ctl->numsig, ctl->sigdata); + else +#endif + rc = kill(ctl->pid, ctl->numsig); + + if (rc < 0) + warn(_("sending signal to %s failed"), ctl->arg); + return rc; +} + +static int check_signal_handler(const struct kill_control *ctl) +{ + uintmax_t sigcgt = 0; + int rc = 0, has_hnd = 0; + struct path_cxt *pc; + + if (!ctl->require_handler) + return 1; + + pc = ul_new_procfs_path(ctl->pid, NULL); + if (!pc) + return -ENOMEM; + + rc = procfs_process_get_stat_nth(pc, 34, &sigcgt); + if (rc) + return -EINVAL; + + ul_unref_path(pc); + + has_hnd = ((1UL << (ctl->numsig - 1)) & sigcgt) != 0; + if (ctl->verbose && !has_hnd) + printf(_("not signalling pid %d, it has no userspace handler for signal %d\n"), ctl->pid, ctl->numsig); + + return has_hnd; +} + +int main(int argc, char **argv) +{ + struct kill_control ctl = { .numsig = SIGTERM }; + int nerrs = 0, ct = 0; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + +#ifdef UL_HAVE_PIDFD + INIT_LIST_HEAD(&ctl.follow_ups); +#endif + argv = parse_arguments(argc, argv, &ctl); + + /* The rest of the arguments should be process ids and names. */ + for ( ; (ctl.arg = *argv) != NULL; argv++) { + char *ep = NULL; + + errno = 0; + ctl.pid = strtol(ctl.arg, &ep, 10); + if (errno == 0 && ep && *ep == '\0' && ctl.arg < ep) { + if (check_signal_handler(&ctl) <= 0) + continue; + if (kill_verbose(&ctl) != 0) + nerrs++; + ct++; + } else { + int found = 0; + struct dirent *d; + DIR *dir = opendir(_PATH_PROC); + uid_t uid = !ctl.check_all ? getuid() : 0; + + if (!dir) + continue; + + while ((d = xreaddir(dir))) { + if (!ctl.check_all && + !procfs_dirent_match_uid(dir, d, uid)) + continue; + if (ctl.arg && + !procfs_dirent_match_name(dir, d, ctl.arg)) + continue; + if (procfs_dirent_get_pid(d, &ctl.pid) != 0) + continue; + if (check_signal_handler(&ctl) <= 0) + continue; + + if (kill_verbose(&ctl) != 0) + nerrs++; + ct++; + found = 1; + } + + closedir(dir); + if (!found) { + nerrs++, ct++; + warnx(_("cannot find process \"%s\""), ctl.arg); + } + } + } + +#ifdef UL_HAVE_PIDFD + while (!list_empty(&ctl.follow_ups)) { + struct timeouts *x = list_entry(ctl.follow_ups.next, + struct timeouts, follow_ups); + list_del(&x->follow_ups); + free(x); + } +#endif + if (ct && nerrs == 0) + return EXIT_SUCCESS; /* full success */ + if (ct == nerrs) + return EXIT_FAILURE; /* all failed */ + + return KILL_EXIT_SOMEOK; /* partial success */ +} + diff --git a/misc-utils/logger.1 b/misc-utils/logger.1 new file mode 100644 index 0000000..232e1bd --- /dev/null +++ b/misc-utils/logger.1 @@ -0,0 +1,353 @@ +'\" t +.\" Title: logger +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-11-21 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "LOGGER" "1" "2023-11-21" "util\-linux 2.39.3" "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" +logger \- enter messages into the system log +.SH "SYNOPSIS" +.sp +\fBlogger\fP [options] \fImessage\fP +.SH "DESCRIPTION" +.sp +\fBlogger\fP makes entries in the system log. +.sp +When the optional \fImessage\fP argument is present, it is written to the log. If it is not present, and the \fB\-f\fP option is not given either, then standard input is logged. +.SH "OPTIONS" +.sp +\fB\-d\fP, \fB\-\-udp\fP +.RS 4 +Use datagrams (UDP) only. By default the connection is tried to the syslog port defined in \fI/etc/services\fP, which is often 514. +.sp +See also \fB\-\-server\fP and \fB\-\-socket\fP to specify where to connect. +.RE +.sp +\fB\-e\fP, \fB\-\-skip\-empty\fP +.RS 4 +Ignore empty lines when processing files. An empty line is defined to be a line without any characters. Thus a line consisting only of whitespace is NOT considered empty. Note that when the \fB\-\-prio\-prefix\fP option is specified, the priority is not part of the line. Thus an empty line in this mode is a line that does not have any characters after the priority prefix (e.g., \fB<13>\fP). +.RE +.sp +\fB\-f\fP, \fB\-\-file\fP \fIfile\fP +.RS 4 +Log the contents of the specified \fIfile\fP. This option cannot be combined with a command\-line message. +.RE +.sp +\fB\-i\fP +.RS 4 +Log the PID of the \fBlogger\fP process with each line. +.RE +.sp +\fB\-\-id\fP[\fB=\fP\fIid\fP] +.RS 4 +Log the PID of the \fBlogger\fP process with each line. When the optional argument \fIid\fP is specified, then it is used instead of the \fBlogger\fP command\(cqs PID. The use of \fB\-\-id=$$\fP (PPID) is recommended in scripts that send several messages. +.sp +Note that the system logging infrastructure (for example \fBsystemd\fP when listening on \fI/dev/log\fP) may follow local socket credentials to overwrite the PID specified in the message. \fBlogger\fP(1) is able to set those socket credentials to the given \fIid\fP, but only if you have root permissions and a process with the specified PID exists, otherwise the socket credentials are not modified and the problem is silently ignored. +.RE +.sp +\fB\-\-journald\fP[\fB=\fP\fIfile\fP] +.RS 4 +Write a \fBsystemd\fP journal entry. The entry is read from the given \fIfile\fP, when specified, otherwise from standard input. Each line must begin with a field that is accepted by \fBjournald\fP; see \fBsystemd.journal\-fields\fP(7) for details. The use of a MESSAGE_ID field is generally a good idea, as it makes finding entries easy. Examples: +.RS 3 +.ll -.6i +.sp +.if n .RS 4 +.nf +.fam C +logger \-\-journald <<end +MESSAGE_ID=67feb6ffbaf24c5cbec13c008dd72309 +MESSAGE=The dogs bark, but the caravan goes on. +DOGS=bark +CARAVAN=goes on +end +.fam +.fi +.if n .RE +.sp +.if n .RS 4 +.nf +.fam C +logger \-\-journald=entry.txt +.fam +.fi +.if n .RE +.br +.RE +.ll +.sp +Notice that \fB\-\-journald\fP will ignore values of other options, such as priority. If priority is needed it must be within input, and use PRIORITY field. The simple execution of \fBjournalctl\fP(1) will display MESSAGE field. Use \fBjournalctl \-\-output json\-pretty\fP to see rest of the fields. +.sp +To include newlines in MESSAGE, specify MESSAGE several times. This is handled as a special case, other fields will be stored as an array in the journal if they appear multiple times. +.RE +.sp +\fB\-\-msgid\fP \fImsgid\fP +.RS 4 +Sets the \c +.URL "https://tools.ietf.org/html/rfc5424" "RFC 5424" "" +MSGID field. Note that the space character is not permitted inside of \fImsgid\fP. This option is only used if \fB\-\-rfc5424\fP is specified as well; otherwise, it is silently ignored. +.RE +.sp +\fB\-n\fP, \fB\-\-server\fP \fIserver\fP +.RS 4 +Write to the specified remote syslog \fIserver\fP instead of to the system log socket. Unless \fB\-\-udp\fP or \fB\-\-tcp\fP is specified, \fBlogger\fP will first try to use UDP, but if this fails a TCP connection is attempted. +.RE +.sp +\fB\-\-no\-act\fP +.RS 4 +Causes everything to be done except for writing the log message to the system log, and removing the connection to the journal. This option can be used together with \fB\-\-stderr\fP for testing purposes. +.RE +.sp +\fB\-\-octet\-count\fP +.RS 4 +Use the \c +.URL "https://tools.ietf.org/html/rfc6587" "RFC 6587" "" +octet counting framing method for sending messages. When this option is not used, the default is no framing on UDP, and RFC6587 non\-transparent framing (also known as octet stuffing) on TCP. +.RE +.sp +\fB\-P\fP, \fB\-\-port\fP \fIport\fP +.RS 4 +Use the specified \fIport\fP. When this option is not specified, the port defaults to \fBsyslog\fP for udp and to \fBsyslog\-conn\fP for tcp connections. +.RE +.sp +\fB\-p\fP, \fB\-\-priority\fP \fIpriority\fP +.RS 4 +Enter the message into the log with the specified \fIpriority\fP. The priority may be specified numerically or as a \fIfacility\fP.\fIlevel\fP pair. For example, \fB\-p local3.info\fP logs the message as informational in the local3 facility. The default is \fBuser.notice\fP. +.RE +.sp +\fB\-\-prio\-prefix\fP +.RS 4 +Look for a syslog prefix on every line read from standard input. This prefix is a decimal number within angle brackets that encodes both the facility and the level. The number is constructed by multiplying the facility by 8 and then adding the level. For example, \fBlocal0.info\fP, meaning facility=16 and level=6, becomes \fB<134>\fP. +.sp +If the prefix contains no facility, the facility defaults to what is specified by the \fB\-p\fP option. Similarly, if no prefix is provided, the line is logged using the \fIpriority\fP given with \fB\-p\fP. +.sp +This option doesn\(cqt affect a command\-line message. +.RE +.sp +\fB\-\-rfc3164\fP +.RS 4 +Use the \c +.URL "https://tools.ietf.org/html/rfc3164" "RFC 3164" "" +BSD syslog protocol to submit messages to a remote server. +.RE +.sp +\fB\-\-rfc5424\fP[\fB=\fP\fIwithout\fP] +.RS 4 +Use the \c +.URL "https://tools.ietf.org/html/rfc5424" "RFC 5424" "" +syslog protocol to submit messages to a remote server. The optional \fIwithout\fP argument can be a comma\-separated list of the following values: \fBnotq\fP, \fBnotime\fP, \fBnohost\fP. +.sp +The \fBnotq\fP value suppresses the time\-quality structured data from the submitted message. The time\-quality information shows whether the local clock was synchronized plus the maximum number of microseconds the timestamp might be off. The time quality is also automatically suppressed when \fB\-\-sd\-id timeQuality\fP is specified. +.sp +The \fBnotime\fP value (which implies \fBnotq\fP) suppresses the complete sender timestamp that is in ISO\-8601 format, including microseconds and timezone. +.sp +The \fBnohost\fP value suppresses \fBgethostname\fP(2) information from the message header. +.sp +The RFC 5424 protocol has been the default for \fBlogger\fP since version 2.26. +.RE +.sp +\fB\-s\fP, \fB\-\-stderr\fP +.RS 4 +Output the message to standard error as well as to the system log. +.RE +.sp +\fB\-\-sd\-id\fP \fIname\fP[\fB@\fP\fIdigits\fP] +.RS 4 +Specifies a structured data element ID for an RFC 5424 message header. The option has to be used before \fB\-\-sd\-param\fP to introduce a new element. The number of structured data elements is unlimited. The ID (\fIname\fP plus possibly \fB@\fP\fIdigits\fP) is case\-sensitive and uniquely identifies the type and purpose of the element. The same ID must not exist more than once in a message. The \fB@\fP\fIdigits\fP part is required for user\-defined non\-standardized IDs. +.sp +\fBlogger\fP currently generates the \fBtimeQuality\fP standardized element only. RFC 5424 also describes the elements \fBorigin\fP (with parameters \fBip\fP, \fBenterpriseId\fP, \fBsoftware\fP and \fBswVersion\fP) and \fBmeta\fP (with parameters \fBsequenceId\fP, \fBsysUpTime\fP and \fBlanguage\fP). These element IDs may be specified without the \fB@\fP\fIdigits\fP suffix. +.RE +.sp +\fB\-\-sd\-param\fP \fIname\fP=\fIvalue\fP +.RS 4 +Specifies a structured data element parameter, a name and value pair. The option has to be used after \fB\-\-sd\-id\fP and may be specified more than once for the same element. Note that the quotation marks around \fIvalue\fP are required and must be escaped on the command line. +.sp +.if n .RS 4 +.nf +.fam C + logger \-\-rfc5424 \-\-sd\-id zoo@123 \(rs + \-\-sd\-param tiger="hungry" \(rs + \-\-sd\-param zebra="running" \(rs + \-\-sd\-id manager@123 \(rs + \-\-sd\-param onMeeting="yes" \(rs + "this is message" +.fam +.fi +.if n .RE +.sp +produces: +.sp +\fB<13>1 2015\-10\-01T14:07:59.168662+02:00 ws kzak \- \- [timeQuality tzKnown="1" isSynced="1" syncAccuracy="218616"][zoo@123 tiger="hungry" zebra="running"][manager@123 onMeeting="yes"] this is message\fP +.RE +.sp +\fB\-S\fP, \fB\-\-size\fP \fIsize\fP +.RS 4 +Sets the maximum permitted message size to \fIsize\fP. The default is 1KiB characters, which is the limit traditionally used and specified in RFC 3164. With RFC 5424, this limit has become flexible. A good assumption is that RFC 5424 receivers can at least process 4KiB messages. +.sp +Most receivers accept messages larger than 1KiB over any type of syslog protocol. As such, the \fB\-\-size\fP option affects \fBlogger\fP in all cases (not only when \fB\-\-rfc5424\fP was used). +.sp +Note: the message\-size limit limits the overall message size, including the syslog header. Header sizes vary depending on the selected options and the hostname length. As a rule of thumb, headers are usually not longer than 50 to 80 characters. When selecting a maximum message size, it is important to ensure that the receiver supports the max size as well, otherwise messages may become truncated. Again, as a rule of thumb two to four KiB message size should generally be OK, whereas anything larger should be verified to work. +.RE +.sp +\fB\-\-socket\-errors\fP[\fB=\fP\fImode\fP] +.RS 4 +Print errors about Unix socket connections. The \fImode\fP can be a value of \fBoff\fP, \fBon\fP, or \fBauto\fP. When the mode is \fBauto\fP, then \fBlogger\fP will detect if the init process is \fBsystemd\fP(1), and if so assumption is made \fI/dev/log\fP can be used early at boot. Other init systems lack of \fI/dev/log\fP will not cause errors that is identical with messaging using \fBopenlog\fP(3) system call. The \fBlogger\fP(1) before version 2.26 used \fBopenlog\fP(3), and hence was unable to detected loss of messages sent to Unix sockets. +.sp +The default mode is \fBauto\fP. When errors are not enabled lost messages are not communicated and will result to successful exit status of \fBlogger\fP(1) invocation. +.RE +.sp +\fB\-T\fP, \fB\-\-tcp\fP +.RS 4 +Use stream (TCP) only. By default the connection is tried to the \fIsyslog\-conn\fP port defined in \fI/etc/services\fP, which is often \fI601\fP. +.sp +See also \fB\-\-server\fP and \fB\-\-socket\fP to specify where to connect. +.RE +.sp +\fB\-t\fP, \fB\-\-tag\fP \fItag\fP +.RS 4 +Mark every line to be logged with the specified \fItag\fP. The default tag is the name of the user logged in on the terminal (or a user name based on effective user ID). +.RE +.sp +\fB\-u\fP, \fB\-\-socket\fP \fIsocket\fP +.RS 4 +Write to the specified \fIsocket\fP instead of to the system log socket. +.RE +.sp +\fB\-\-\fP +.RS 4 +End the argument list. This allows the \fImessage\fP to start with a hyphen (\-). +.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 +The \fBlogger\fP utility exits 0 on success, and >0 if an error occurs. +.SH "FACILITIES AND LEVELS" +.sp +Valid facility names are: +.sp +\fBauth\fP +.br +\fBauthpriv\fP for security information of a sensitive nature +.br +\fBcron\fP +.br +.sp +\fBdaemon\fP +.br +\fBftp\fP +.br +\fBkern\fP cannot be generated from userspace process, automatically converted to \fBuser\fP +.br +.sp +\fBlpr\fP +.br +\fBmail\fP +.br +\fBnews\fP +.br +\fBsyslog\fP +.br +\fBuser\fP +.br +\fBuucp\fP +.br +\fBlocal0\fP +.br +to +.br +\fBlocal7\fP +.br +\fBsecurity\fP deprecated synonym for \fBauth\fP +.sp +Valid level names are: +.sp +\fBemerg\fP +.br +\fBalert\fP +.br +\fBcrit\fP +.br +\fBerr\fP +.br +\fBwarning\fP +.br +\fBnotice\fP +.br +\fBinfo\fP +.br +\fBdebug\fP +.br +\fBpanic\fP deprecated synonym for \fBemerg\fP +.br +\fBerror\fP deprecated synonym for \fBerr\fP +.br +\fBwarn\fP deprecated synonym for \fBwarning\fP +.br +.sp +For the priority order and intended purposes of these facilities and levels, see \fBsyslog\fP(3). +.SH "CONFORMING TO" +.sp +The \fBlogger\fP command is expected to be IEEE Std 1003.2 ("POSIX.2") compatible. +.SH "EXAMPLES" +.RS 3 +.ll -.6i +.sp +logger System rebooted +.sp +logger \-p local0.notice \-t HOSTIDM \-f /dev/idmc +.sp +logger \-n loghost.example.com System rebooted +.br +.RE +.ll +.SH "AUTHORS" +.sp +The \fBlogger\fP command was originally written by University of California in 1983\-1993 and later rewritten by \c +.MTO "kzak\(atredhat.com" "Karel Zak" "," +.MTO "rgerhards\(atadiscon.com" "Rainer Gerhards" "," +and +.MTO "kerolasa\(atiki.fi" "Sami Kerola" "." +.SH "SEE ALSO" +.sp +\fBjournalctl\fP(1), +\fBsyslog\fP(3), +\fBsystemd.journal\-fields\fP(7) +.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 \fBlogger\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/logger.1.adoc b/misc-utils/logger.1.adoc new file mode 100644 index 0000000..7037fcb --- /dev/null +++ b/misc-utils/logger.1.adoc @@ -0,0 +1,258 @@ +//po4a: entry man manual +//// +Copyright (c) 1983, 1990, 1993 + The Regents of the University of California. All rights reserved. + +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. +3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by the University of + California, Berkeley and its contributors. +4. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + + @(#)logger.1 8.1 (Berkeley) 6/6/93 +//// += logger(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: logger + +== NAME + +logger - enter messages into the system log + +== SYNOPSIS + +*logger* [options] _message_ + +== DESCRIPTION + +*logger* makes entries in the system log. + +When the optional _message_ argument is present, it is written to the log. If it is not present, and the *-f* option is not given either, then standard input is logged. + +== OPTIONS + +*-d*, *--udp*:: +Use datagrams (UDP) only. By default the connection is tried to the syslog port defined in _/etc/services_, which is often 514. ++ +See also *--server* and *--socket* to specify where to connect. + +*-e*, *--skip-empty*:: +Ignore empty lines when processing files. An empty line is defined to be a line without any characters. Thus a line consisting only of whitespace is NOT considered empty. Note that when the *--prio-prefix* option is specified, the priority is not part of the line. Thus an empty line in this mode is a line that does not have any characters after the priority prefix (e.g., *<13>*). + +*-f*, *--file* _file_:: +Log the contents of the specified _file_. This option cannot be combined with a command-line message. + +*-i*:: +Log the PID of the *logger* process with each line. + +*--id*[**=**__id__]:: +Log the PID of the *logger* process with each line. When the optional argument _id_ is specified, then it is used instead of the *logger* command's PID. The use of *--id=$$* (PPID) is recommended in scripts that send several messages. ++ +Note that the system logging infrastructure (for example *systemd* when listening on _/dev/log_) may follow local socket credentials to overwrite the PID specified in the message. *logger*(1) is able to set those socket credentials to the given _id_, but only if you have root permissions and a process with the specified PID exists, otherwise the socket credentials are not modified and the problem is silently ignored. + +*--journald*[**=**__file__]:: +Write a *systemd* journal entry. The entry is read from the given _file_, when specified, otherwise from standard input. Each line must begin with a field that is accepted by *journald*; see *systemd.journal-fields*(7) for details. The use of a MESSAGE_ID field is generally a good idea, as it makes finding entries easy. Examples: ++ +____ + logger --journald <<end + MESSAGE_ID=67feb6ffbaf24c5cbec13c008dd72309 + MESSAGE=The dogs bark, but the caravan goes on. + DOGS=bark + CARAVAN=goes on + end + + logger --journald=entry.txt +____ ++ +Notice that *--journald* will ignore values of other options, such as priority. If priority is needed it must be within input, and use PRIORITY field. The simple execution of *journalctl*(1) will display MESSAGE field. Use *journalctl --output json-pretty* to see rest of the fields. ++ +To include newlines in MESSAGE, specify MESSAGE several times. This is handled as a special case, other fields will be stored as an array in the journal if they appear multiple times. + +*--msgid* _msgid_:: +Sets the link:https://tools.ietf.org/html/rfc5424[RFC 5424] MSGID field. Note that the space character is not permitted inside of _msgid_. This option is only used if *--rfc5424* is specified as well; otherwise, it is silently ignored. + +*-n*, *--server* _server_:: +Write to the specified remote syslog _server_ instead of to the system log socket. Unless *--udp* or *--tcp* is specified, *logger* will first try to use UDP, but if this fails a TCP connection is attempted. + +*--no-act*:: +Causes everything to be done except for writing the log message to the system log, and removing the connection to the journal. This option can be used together with *--stderr* for testing purposes. + +*--octet-count*:: +Use the link:https://tools.ietf.org/html/rfc6587[RFC 6587] octet counting framing method for sending messages. When this option is not used, the default is no framing on UDP, and RFC6587 non-transparent framing (also known as octet stuffing) on TCP. + +*-P*, *--port* _port_:: +Use the specified _port_. When this option is not specified, the port defaults to *syslog* for udp and to *syslog-conn* for tcp connections. + +*-p*, *--priority* _priority_:: +Enter the message into the log with the specified _priority_. The priority may be specified numerically or as a _facility_._level_ pair. For example, *-p local3.info* logs the message as informational in the local3 facility. The default is *user.notice*. + +*--prio-prefix*:: +Look for a syslog prefix on every line read from standard input. This prefix is a decimal number within angle brackets that encodes both the facility and the level. The number is constructed by multiplying the facility by 8 and then adding the level. For example, *local0.info*, meaning facility=16 and level=6, becomes *<134>*. ++ +If the prefix contains no facility, the facility defaults to what is specified by the *-p* option. Similarly, if no prefix is provided, the line is logged using the _priority_ given with *-p*. ++ +This option doesn't affect a command-line message. + +*--rfc3164*:: +Use the link:https://tools.ietf.org/html/rfc3164[RFC 3164] BSD syslog protocol to submit messages to a remote server. + +*--rfc5424*[**=**__without__]:: +Use the link:https://tools.ietf.org/html/rfc5424[RFC 5424] syslog protocol to submit messages to a remote server. The optional _without_ argument can be a comma-separated list of the following values: *notq*, *notime*, *nohost*. ++ +The *notq* value suppresses the time-quality structured data from the submitted message. The time-quality information shows whether the local clock was synchronized plus the maximum number of microseconds the timestamp might be off. The time quality is also automatically suppressed when *--sd-id timeQuality* is specified. ++ +The *notime* value (which implies *notq*) suppresses the complete sender timestamp that is in ISO-8601 format, including microseconds and timezone. ++ +The *nohost* value suppresses *gethostname*(2) information from the message header. ++ +The RFC 5424 protocol has been the default for *logger* since version 2.26. + +*-s*, *--stderr*:: +Output the message to standard error as well as to the system log. + +*--sd-id* _name_[**@**__digits__]:: +Specifies a structured data element ID for an RFC 5424 message header. The option has to be used before *--sd-param* to introduce a new element. The number of structured data elements is unlimited. The ID (_name_ plus possibly **@**__digits__) is case-sensitive and uniquely identifies the type and purpose of the element. The same ID must not exist more than once in a message. The **@**__digits__ part is required for user-defined non-standardized IDs. ++ +*logger* currently generates the *timeQuality* standardized element only. RFC 5424 also describes the elements *origin* (with parameters *ip*, *enterpriseId*, *software* and *swVersion*) and *meta* (with parameters *sequenceId*, *sysUpTime* and *language*). These element IDs may be specified without the **@**__digits__ suffix. + +*--sd-param* _name_=_value_:: +Specifies a structured data element parameter, a name and value pair. The option has to be used after *--sd-id* and may be specified more than once for the same element. Note that the quotation marks around _value_ are required and must be escaped on the command line. ++ +.... + logger --rfc5424 --sd-id zoo@123 \ + --sd-param tiger="hungry" \ + --sd-param zebra="running" \ + --sd-id manager@123 \ + --sd-param onMeeting="yes" \ + "this is message" +.... +produces: ++ +*<13>1 2015-10-01T14:07:59.168662+02:00 ws kzak - - [timeQuality tzKnown="1" isSynced="1" syncAccuracy="218616"][zoo@123 tiger="hungry" zebra="running"][manager@123 onMeeting="yes"] this is message* + +*-S*, *--size* _size_:: +Sets the maximum permitted message size to _size_. The default is 1KiB characters, which is the limit traditionally used and specified in RFC 3164. With RFC 5424, this limit has become flexible. A good assumption is that RFC 5424 receivers can at least process 4KiB messages. ++ +Most receivers accept messages larger than 1KiB over any type of syslog protocol. As such, the *--size* option affects *logger* in all cases (not only when *--rfc5424* was used). ++ +Note: the message-size limit limits the overall message size, including the syslog header. Header sizes vary depending on the selected options and the hostname length. As a rule of thumb, headers are usually not longer than 50 to 80 characters. When selecting a maximum message size, it is important to ensure that the receiver supports the max size as well, otherwise messages may become truncated. Again, as a rule of thumb two to four KiB message size should generally be OK, whereas anything larger should be verified to work. + +*--socket-errors*[**=**__mode__]:: +Print errors about Unix socket connections. The _mode_ can be a value of *off*, *on*, or *auto*. When the mode is *auto*, then *logger* will detect if the init process is *systemd*(1), and if so assumption is made _/dev/log_ can be used early at boot. Other init systems lack of _/dev/log_ will not cause errors that is identical with messaging using *openlog*(3) system call. The *logger*(1) before version 2.26 used *openlog*(3), and hence was unable to detected loss of messages sent to Unix sockets. ++ +The default mode is *auto*. When errors are not enabled lost messages are not communicated and will result to successful exit status of *logger*(1) invocation. + +*-T*, *--tcp*:: +Use stream (TCP) only. By default the connection is tried to the _syslog-conn_ port defined in _/etc/services_, which is often _601_. ++ +See also *--server* and *--socket* to specify where to connect. + +*-t*, *--tag* _tag_:: +Mark every line to be logged with the specified _tag_. The default tag is the name of the user logged in on the terminal (or a user name based on effective user ID). + +*-u*, *--socket* _socket_:: +Write to the specified _socket_ instead of to the system log socket. + +*--*:: +End the argument list. This allows the _message_ to start with a hyphen (-). + +include::man-common/help-version.adoc[] + +== EXIT STATUS + +The *logger* utility exits 0 on success, and >0 if an error occurs. + +== FACILITIES AND LEVELS + +Valid facility names are: + +*auth* + +*authpriv* for security information of a sensitive nature + +*cron* + + +*daemon* + +*ftp* + +*kern* cannot be generated from userspace process, automatically converted to *user* + + +*lpr* + +*mail* + +*news* + +*syslog* + +*user* + +*uucp* + +*local0* + + to + +*local7* + +*security* deprecated synonym for *auth* + +Valid level names are: + +*emerg* + +*alert* + +*crit* + +*err* + +*warning* + +*notice* + +*info* + +*debug* + +*panic* deprecated synonym for *emerg* + +*error* deprecated synonym for *err* + +*warn* deprecated synonym for *warning* + + +For the priority order and intended purposes of these facilities and levels, see *syslog*(3). + +== CONFORMING TO + +The *logger* command is expected to be IEEE Std 1003.2 ("POSIX.2") compatible. + +== EXAMPLES +____ +logger System rebooted + +logger -p local0.notice -t HOSTIDM -f /dev/idmc + +logger -n loghost.example.com System rebooted +____ + +== AUTHORS + +The *logger* command was originally written by University of California in 1983-1993 and later rewritten by mailto:kzak@redhat.com[Karel Zak], mailto:rgerhards@adiscon.com[Rainer Gerhards], and mailto:kerolasa@iki.fi[Sami Kerola]. + +== SEE ALSO + +*journalctl*(1), +*syslog*(3), +*systemd.journal-fields*(7) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/logger.c b/misc-utils/logger.c new file mode 100644 index 0000000..8174d55 --- /dev/null +++ b/misc-utils/logger.c @@ -0,0 +1,1324 @@ +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + * Sun Mar 21 1999 - Arnaldo Carvalho de Melo <acme@conectiva.com.br> + * - fixed strerr(errno) in gettext calls + */ + +#include <errno.h> +#include <limits.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/time.h> +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <getopt.h> +#include <pwd.h> +#include <signal.h> +#include <sys/uio.h> + +#include "all-io.h" +#include "c.h" +#include "closestream.h" +#include "nls.h" +#include "pathnames.h" +#include "strutils.h" +#include "xalloc.h" +#include "strv.h" +#include "list.h" +#include "pwdutils.h" + +#define SYSLOG_NAMES +#include <syslog.h> + +#ifdef HAVE_LIBSYSTEMD +# define SD_JOURNAL_SUPPRESS_LOCATION +# include <systemd/sd-daemon.h> +# include <systemd/sd-journal.h> +#endif + +#ifdef HAVE_SYS_TIMEX_H +# include <sys/timex.h> +#endif + +enum { + TYPE_UDP = (1 << 1), + TYPE_TCP = (1 << 2), + ALL_TYPES = TYPE_UDP | TYPE_TCP +}; + +enum { + AF_UNIX_ERRORS_OFF = 0, + AF_UNIX_ERRORS_ON, + AF_UNIX_ERRORS_AUTO +}; + +enum { + OPT_PRIO_PREFIX = CHAR_MAX + 1, + OPT_JOURNALD, + OPT_RFC3164, + OPT_RFC5424, + OPT_SOCKET_ERRORS, + OPT_MSGID, + OPT_NOACT, + OPT_ID, + OPT_STRUCTURED_DATA_ID, + OPT_STRUCTURED_DATA_PARAM, + OPT_OCTET_COUNT +}; + +/* rfc5424 structured data */ +struct structured_data { + char *id; /* SD-ID */ + char **params; /* array with SD-PARAMs */ + + struct list_head sds; +}; + +struct logger_ctl { + int fd; + int pri; + pid_t pid; /* zero when unwanted */ + char *hdr; /* the syslog header (based on protocol) */ + char const *tag; + char *login; + char *msgid; + char *unix_socket; /* -u <path> or default to _PATH_DEVLOG */ + char *server; + char *port; + int socket_type; + size_t max_message_size; + struct list_head user_sds; /* user defined rfc5424 structured data */ + struct list_head reserved_sds; /* standard rfc5424 structured data */ + + void (*syslogfp)(struct logger_ctl *ctl); + + unsigned int + unix_socket_errors:1, /* whether to report or not errors */ + noact:1, /* do not write to sockets */ + prio_prefix:1, /* read priority from input */ + stderr_printout:1, /* output message to stderr */ + rfc5424_time:1, /* include time stamp */ + rfc5424_tq:1, /* include time quality markup */ + rfc5424_host:1, /* include hostname */ + skip_empty_lines:1, /* do not send empty lines when processing files */ + octet_count:1; /* use RFC6587 octet counting */ +}; + +#define is_connected(_ctl) ((_ctl)->fd >= 0) +static void logger_reopen(struct logger_ctl *ctl); + +/* + * For tests we want to be able to control datetime outputs + */ +#ifdef TEST_LOGGER +static inline int logger_gettimeofday(struct timeval *tv, struct timezone *tz) +{ + char *str = getenv("LOGGER_TEST_TIMEOFDAY"); + uintmax_t sec, usec; + + if (str && sscanf(str, "%ju.%ju", &sec, &usec) == 2) { + tv->tv_sec = sec; + tv->tv_usec = usec; + return tv->tv_sec >= 0 && tv->tv_usec >= 0 ? 0 : -EINVAL; + } + + return gettimeofday(tv, tz); +} + +static inline char *logger_xgethostname(void) +{ + char *str = getenv("LOGGER_TEST_HOSTNAME"); + return str ? xstrdup(str) : xgethostname(); +} + +static inline pid_t logger_getpid(void) +{ + char *str = getenv("LOGGER_TEST_GETPID"); + unsigned int pid; + + if (str && sscanf(str, "%u", &pid) == 1) + return pid; + return getpid(); +} + + +#undef HAVE_NTP_GETTIME /* force to default non-NTP */ + +#else /* !TEST_LOGGER */ +# define logger_gettimeofday(x, y) gettimeofday(x, y) +# define logger_xgethostname xgethostname +# define logger_getpid getpid +#endif + + +static int decode(const char *name, const CODE *codetab) +{ + register const CODE *c; + + if (name == NULL || *name == '\0') + return -1; + if (isdigit(*name)) { + int num; + char *end = NULL; + + errno = 0; + num = strtol(name, &end, 10); + if (errno || name == end || (end && *end)) + return -1; + for (c = codetab; c->c_name; c++) + if (num == c->c_val) + return num; + return -1; + } + for (c = codetab; c->c_name; c++) + if (!strcasecmp(name, c->c_name)) + return (c->c_val); + + return -1; +} + +static int pencode(char *s) +{ + int facility, level; + char *separator; + + assert(s); + + separator = strchr(s, '.'); + if (separator) { + *separator = '\0'; + facility = decode(s, facilitynames); + if (facility < 0) + errx(EXIT_FAILURE, _("unknown facility name: %s"), s); + s = ++separator; + } else + facility = LOG_USER; + level = decode(s, prioritynames); + if (level < 0) + errx(EXIT_FAILURE, _("unknown priority name: %s"), s); + if (facility == LOG_KERN) + facility = LOG_USER; /* kern is forbidden */ + return ((level & LOG_PRIMASK) | (facility & LOG_FACMASK)); +} + +static int unix_socket(struct logger_ctl *ctl, const char *path, int *socket_type) +{ + int fd = -1, i, type = -1; + static struct sockaddr_un s_addr; /* AF_UNIX address of local logger */ + + if (strlen(path) >= sizeof(s_addr.sun_path)) + errx(EXIT_FAILURE, _("openlog %s: pathname too long"), path); + + s_addr.sun_family = AF_UNIX; + strcpy(s_addr.sun_path, path); + + for (i = 2; i; i--) { + int st = -1; + + if (i == 2 && *socket_type & TYPE_UDP) { + st = SOCK_DGRAM; + type = TYPE_UDP; + } + if (i == 1 && *socket_type & TYPE_TCP) { + st = SOCK_STREAM; + type = TYPE_TCP; + } + if (st == -1 || (fd = socket(AF_UNIX, st, 0)) == -1) + continue; + if (connect(fd, (struct sockaddr *)&s_addr, sizeof(s_addr)) == -1) { + close(fd); + continue; + } + break; + } + + if (i == 0) { + if (ctl->unix_socket_errors) + err(EXIT_FAILURE, _("socket %s"), path); + + /* write_output() will try to reconnect */ + return -1; + } + + /* replace ALL_TYPES with the real TYPE_* */ + if (type > 0 && type != *socket_type) + *socket_type = type; + return fd; +} + +static int inet_socket(const char *servername, const char *port, int *socket_type) +{ + int fd, errcode, i, type = -1; + struct addrinfo hints, *res; + const char *p = port; + + for (i = 2; i; i--) { + memset(&hints, 0, sizeof(hints)); + if (i == 2 && *socket_type & TYPE_UDP) { + hints.ai_socktype = SOCK_DGRAM; + type = TYPE_UDP; + if (port == NULL) + p = "syslog"; + } + if (i == 1 && *socket_type & TYPE_TCP) { + hints.ai_socktype = SOCK_STREAM; + type = TYPE_TCP; + if (port == NULL) + p = "syslog-conn"; + } + if (hints.ai_socktype == 0) + continue; + hints.ai_family = AF_UNSPEC; + errcode = getaddrinfo(servername, p, &hints, &res); + if (errcode != 0) + errx(EXIT_FAILURE, _("failed to resolve name %s port %s: %s"), + servername, p, gai_strerror(errcode)); + if ((fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) { + freeaddrinfo(res); + continue; + } + if (connect(fd, res->ai_addr, res->ai_addrlen) == -1) { + freeaddrinfo(res); + close(fd); + continue; + } + + freeaddrinfo(res); + break; + } + + if (i == 0) + errx(EXIT_FAILURE, _("failed to connect to %s port %s"), servername, p); + + /* replace ALL_TYPES with the real TYPE_* */ + if (type > 0 && type != *socket_type) + *socket_type = type; + return fd; +} + +#ifdef HAVE_LIBSYSTEMD +static int journald_entry(struct logger_ctl *ctl, FILE *fp) +{ + struct iovec *iovec; + char *buf = NULL; + ssize_t sz; + int n, lines = 0, vectors = 8, ret = 0, msgline = -1; + size_t dummy = 0; + + iovec = xmalloc(vectors * sizeof(struct iovec)); + while (1) { + buf = NULL; + sz = getline(&buf, &dummy, fp); + if (sz == -1 || + (sz = rtrim_whitespace((unsigned char *) buf)) == 0) { + free(buf); + break; + } + + if (strncmp(buf, "MESSAGE=", 8) == 0) { + if (msgline == -1) + msgline = lines; /* remember the first message */ + else { + char *p = xrealloc(iovec[msgline].iov_base, + iovec[msgline].iov_len + sz - 8 + 2); + + iovec[msgline].iov_base = p; + p += iovec[msgline].iov_len; + *p++ = '\n'; + memcpy(p, buf + 8, sz - 8); + free(buf); + + iovec[msgline].iov_len += sz - 8 + 1; + continue; + } + } + + if (lines == vectors) { + vectors *= 2; + if (IOV_MAX < vectors) + errx(EXIT_FAILURE, _("maximum input lines (%d) exceeded"), IOV_MAX); + iovec = xrealloc(iovec, vectors * sizeof(struct iovec)); + } + iovec[lines].iov_base = buf; + iovec[lines].iov_len = sz; + ++lines; + } + + if (!ctl->noact) + ret = sd_journal_sendv(iovec, lines); + if (ctl->stderr_printout) { + for (n = 0; n < lines; n++) + fprintf(stderr, "%s\n", (char *) iovec[n].iov_base); + } + for (n = 0; n < lines; n++) + free(iovec[n].iov_base); + free(iovec); + return ret; +} +#endif + +/* this creates a timestamp based on current time according to the + * fine rules of RFC3164, most importantly it ensures in a portable + * way that the month day is correctly written (with a SP instead + * of a leading 0). The function uses a static buffer which is + * overwritten on the next call (just like ctime() does). + */ +static char const *rfc3164_current_time(void) +{ + static char time[32]; + struct timeval tv; + struct tm tm; + static char const * const monthnames[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", + "Sep", "Oct", "Nov", "Dec" + }; + + logger_gettimeofday(&tv, NULL); + localtime_r(&tv.tv_sec, &tm); + snprintf(time, sizeof(time),"%s %2d %2.2d:%2.2d:%2.2d", + monthnames[tm.tm_mon], tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + return time; +} + +#define next_iovec(ary, idx) __extension__ ({ \ + assert(ARRAY_SIZE(ary) > (size_t)idx); \ + assert(idx >= 0); \ + &ary[idx++]; \ +}) + +#define iovec_add_string(ary, idx, str, len) \ + do { \ + struct iovec *v = next_iovec(ary, idx); \ + v->iov_base = (void *) str; \ + v->iov_len = len ? len : strlen(str); \ + } while (0) + +#define iovec_memcmp(ary, idx, str, len) \ + memcmp((ary)[(idx) - 1].iov_base, str, len) + +/* writes generated buffer to desired destination. For TCP syslog, + * we use RFC6587 octet-stuffing (unless octet-counting is selected). + * This is not great, but doing full blown RFC5425 (TLS) looks like + * it is too much for the logger utility. If octet-counting is + * selected, we use that. + */ +static void write_output(struct logger_ctl *ctl, const char *const msg) +{ + struct iovec iov[4]; + int iovlen = 0; + char *octet = NULL; + + /* initial connect failed? */ + if (!ctl->noact && !is_connected(ctl)) + logger_reopen(ctl); + + /* 1) octen count */ + if (ctl->octet_count) { + size_t len = xasprintf(&octet, "%zu ", strlen(ctl->hdr) + strlen(msg)); + iovec_add_string(iov, iovlen, octet, len); + } + + /* 2) header */ + iovec_add_string(iov, iovlen, ctl->hdr, 0); + + /* 3) message */ + iovec_add_string(iov, iovlen, msg, 0); + + if (!ctl->noact && is_connected(ctl)) { + struct msghdr message = { 0 }; +#ifdef SCM_CREDENTIALS + struct cmsghdr *cmhp; + struct ucred *cred; + union { + struct cmsghdr cmh; + char control[CMSG_SPACE(sizeof(struct ucred))]; + } cbuf = { .control = { 0 } }; +#endif + + /* 4) add extra \n to make sure message is terminated */ + if ((ctl->socket_type == TYPE_TCP) && !ctl->octet_count) + iovec_add_string(iov, iovlen, "\n", 1); + + message.msg_iov = iov; + message.msg_iovlen = iovlen; + +#ifdef SCM_CREDENTIALS + /* syslog/journald may follow local socket credentials rather + * than in the message PID. If we use --id as root than we can + * force kernel to accept another valid PID than the real logger(1) + * PID. + */ + if (ctl->pid && !ctl->server && ctl->pid != getpid() + && geteuid() == 0 && kill(ctl->pid, 0) == 0) { + + message.msg_control = cbuf.control; + message.msg_controllen = CMSG_SPACE(sizeof(struct ucred)); + + cmhp = CMSG_FIRSTHDR(&message); + cmhp->cmsg_len = CMSG_LEN(sizeof(struct ucred)); + cmhp->cmsg_level = SOL_SOCKET; + cmhp->cmsg_type = SCM_CREDENTIALS; + cred = (struct ucred *) CMSG_DATA(cmhp); + + cred->pid = ctl->pid; + } +#endif + /* Note that logger(1) maybe executed for long time (as pipe + * reader) and connection endpoint (syslogd) may be restarted. + * + * The libc syslog() function reconnects on failed send(). + * Let's do the same to be robust. [kzak -- Oct 2017] + * + * MSG_NOSIGNAL is POSIX.1-2008 compatible, but it for example + * not supported by apple-darwin15.6.0. + */ +#ifndef MSG_NOSIGNAL +# define MSG_NOSIGNAL 0 +#endif + if (sendmsg(ctl->fd, &message, MSG_NOSIGNAL) < 0) { + logger_reopen(ctl); + if (sendmsg(ctl->fd, &message, MSG_NOSIGNAL) < 0) + warn(_("send message failed")); + } + } + + if (ctl->stderr_printout) { + /* make sure it's terminated for stderr */ + if (iovec_memcmp(iov, iovlen, "\n", 1) != 0) + iovec_add_string(iov, iovlen, "\n", 1); + + ignore_result( writev(STDERR_FILENO, iov, iovlen) ); + } + + free(octet); +} + +#define NILVALUE "-" +static void syslog_rfc3164_header(struct logger_ctl *const ctl) +{ + char pid[30], *hostname; + + *pid = '\0'; + if (ctl->pid) + snprintf(pid, sizeof(pid), "[%d]", ctl->pid); + + if ((hostname = logger_xgethostname())) { + char *dot = strchr(hostname, '.'); + if (dot) + *dot = '\0'; + } else + hostname = xstrdup(NILVALUE); + + xasprintf(&ctl->hdr, "<%d>%.15s %s %.200s%s: ", + ctl->pri, rfc3164_current_time(), hostname, ctl->tag, pid); + + free(hostname); +} + +static inline struct list_head *get_user_structured_data(struct logger_ctl *ctl) +{ + return &ctl->user_sds; +} + +static inline struct list_head *get_reserved_structured_data(struct logger_ctl *ctl) +{ + return &ctl->reserved_sds; +} + +static int has_structured_data_id(struct list_head *ls, const char *id) +{ + struct list_head *p; + + if (!ls || list_empty(ls)) + return 0; + + list_for_each(p, ls) { + struct structured_data *sd = list_entry(p, struct structured_data, sds); + if (sd->id && strcmp(sd->id, id) == 0) + return 1; + } + + return 0; +} + +static void add_structured_data_id(struct list_head *ls, const char *id) +{ + struct structured_data *sd; + + assert(id); + + if (has_structured_data_id(ls, id)) + errx(EXIT_FAILURE, _("structured data ID '%s' is not unique"), id); + + sd = xcalloc(1, sizeof(*sd)); + INIT_LIST_HEAD(&sd->sds); + sd->id = xstrdup(id); + + list_add_tail(&sd->sds, ls); +} + +static void add_structured_data_param(struct list_head *ls, const char *param) +{ + struct structured_data *sd; + + if (list_empty(ls)) + errx(EXIT_FAILURE, _("--sd-id was not specified for --sd-param %s"), param); + + assert(param); + + sd = list_last_entry(ls, struct structured_data, sds); + + if (strv_extend(&sd->params, param)) + err_oom(); +} + +static void __attribute__ ((__format__ (__printf__, 2, 3))) + add_structured_data_paramf(struct list_head *ls, const char *fmt, ...) +{ + struct structured_data *sd; + va_list ap; + int x; + + assert(!list_empty(ls)); + assert(fmt); + + sd = list_last_entry(ls, struct structured_data, sds); + va_start(ap, fmt); + x = strv_extendv(&sd->params, fmt, ap); + va_end(ap); + + if (x) + err_oom(); +} + +static char *strdup_structured_data(struct structured_data *sd) +{ + char *res, *tmp; + + if (strv_isempty(sd->params)) + return NULL; + + xasprintf(&res, "[%s %s]", sd->id, + (tmp = strv_join(sd->params, " "))); + free(tmp); + return res; +} + +static char *strdup_structured_data_list(struct list_head *ls) +{ + struct list_head *p; + char *res = NULL; + + list_for_each(p, ls) { + struct structured_data *sd = list_entry(p, struct structured_data, sds); + char *one = strdup_structured_data(sd); + char *tmp = res; + + if (!one) + continue; + res = strconcat(tmp, one); + free(tmp); + free(one); + } + + return res; +} + +static char *get_structured_data_string(struct logger_ctl *ctl) +{ + char *sys = NULL, *usr = NULL, *res; + + if (!list_empty(&ctl->reserved_sds)) + sys = strdup_structured_data_list(&ctl->reserved_sds); + if (!list_empty(&ctl->user_sds)) + usr = strdup_structured_data_list(&ctl->user_sds); + + if (sys && usr) { + res = strconcat(sys, usr); + free(sys); + free(usr); + } else + res = sys ? sys : usr; + + return res; +} + +static int valid_structured_data_param(const char *str) +{ + char *s; + char *eq = strchr(str, '='), + *qm1 = strchr(str, '"'), + *qm2 = qm1 ? ul_strchr_escaped(qm1 + 1, '"') : NULL; + + /* something is missing */ + if (!eq || !qm1 || !qm2) + return 0; + + /* ']' need to be escaped */ + for (s = qm1 + 1; s && *s; ) { + char *p = strchr(s, ']'); + if (!p) + break; + if (p > qm2 || p == ul_strchr_escaped(s, ']')) + return 0; + s = p + 1; + } + + /* '\' is allowed only before '[]"\' chars */ + for (s = qm1 + 1; s && *s; ) { + char *p = strchr(s, '\\'); + if (!p) + break; + if (!strchr("[]\"\\", *(p + 1))) + return 0; + s = p + 1; + if (*s == '\\') + s++; + } + + /* foo="bar" */ + return eq > str && eq < qm1 && eq + 1 == qm1 && qm1 < qm2 && *(qm2 + 1) == '\0'; +} + +/* SD-ID format: + * name@<private enterprise number>, e.g., "ourSDID@32473" + */ +static int valid_structured_data_id(const char *str) +{ + char *at = strchr(str, '@'); + const char *p; + + /* standardized IDs without @<digits> */ + if (!at && (strcmp(str, "timeQuality") == 0 || + strcmp(str, "origin") == 0 || + strcmp(str, "meta") == 0)) + return 1; + + if (!at || at == str || !*(at + 1)) + return 0; + + /* <digits> or <digits>.<digits>[...] */ + for (p = at + 1; p && *p; p++) { + const char *end; + + if (isdigit_strend(p, &end)) + break; /* only digits in the string */ + + if (end == NULL || end == p || + *end != '.' || *(end + 1) == '\0') + return 0; + p = end; + } + + /* check for forbidden chars in the <name> */ + for (p = str; p < at; p++) { + if (*p == '[' || *p == '=' || *p == '"' || *p == '@') + return 0; + if (isblank((unsigned char) *p) || iscntrl((unsigned char) *p)) + return 0; + } + return 1; +} + + +/* Some field mappings may be controversial, thus I give the reason + * why this specific mapping was used: + * APP-NAME <-- tag + * Some may argue that "logger" is a better fit, but we think + * this is better inline of what other implementations do. In + * rsyslog, for example, the TAG value is populated from APP-NAME. + * PROCID <-- pid + * This is a relatively straightforward interpretation from + * RFC5424, sect. 6.2.6. + * MSGID <-- msgid (from --msgid) + * One may argue that the string "logger" would be better suited + * here so that a receiver can identify the sender process. + * However, this does not sound like a good match to RFC5424, + * sect. 6.2.7. + * Note that appendix A.1 of RFC5424 does not provide clear guidance + * of how these fields should be used. This is the case because the + * IETF working group couldn't arrive at a clear agreement when we + * specified RFC5424. The rest of the field mappings should be + * pretty clear from RFC5424. -- Rainer Gerhards, 2015-03-10 + */ +static void syslog_rfc5424_header(struct logger_ctl *const ctl) +{ + char *time; + char *hostname; + char const *app_name = ctl->tag; + char *procid; + char *const msgid = xstrdup(ctl->msgid ? ctl->msgid : NILVALUE); + char *structured = NULL; + struct list_head *sd; + + if (ctl->rfc5424_time) { + struct timeval tv; + struct tm tm; + + logger_gettimeofday(&tv, NULL); + if (localtime_r(&tv.tv_sec, &tm) != NULL) { + char fmt[64]; + const size_t i = strftime(fmt, sizeof(fmt), + "%Y-%m-%dT%H:%M:%S.%%06u%z ", &tm); + /* patch TZ info to comply with RFC3339 (we left SP at end) */ + fmt[i - 1] = fmt[i - 2]; + fmt[i - 2] = fmt[i - 3]; + fmt[i - 3] = ':'; + xasprintf(&time, fmt, tv.tv_usec); + } else + err(EXIT_FAILURE, _("localtime() failed")); + } else + time = xstrdup(NILVALUE); + + if (ctl->rfc5424_host) { + if (!(hostname = logger_xgethostname())) + hostname = xstrdup(NILVALUE); + /* Arbitrary looking 'if (var < strlen()) checks originate from + * RFC 5424 - 6 Syslog Message Format definition. */ + if (255 < strlen(hostname)) + errx(EXIT_FAILURE, _("hostname '%s' is too long"), + hostname); + } else + hostname = xstrdup(NILVALUE); + + if (48 < strlen(ctl->tag)) + errx(EXIT_FAILURE, _("tag '%s' is too long"), ctl->tag); + + if (ctl->pid) + xasprintf(&procid, "%d", ctl->pid); + else + procid = xstrdup(NILVALUE); + + sd = get_reserved_structured_data(ctl); + + /* time quality structured data (maybe overwritten by --sd-id timeQuality) */ + if (ctl->rfc5424_tq && !has_structured_data_id(sd, "timeQuality")) { + + add_structured_data_id(sd, "timeQuality"); + add_structured_data_param(sd, "tzKnown=\"1\""); + +#ifdef HAVE_NTP_GETTIME + struct ntptimeval ntptv; + + if (ntp_gettime(&ntptv) == TIME_OK) { + add_structured_data_param(sd, "isSynced=\"1\""); + add_structured_data_paramf(sd, "syncAccuracy=\"%ld\"", ntptv.maxerror); + } else +#endif + add_structured_data_paramf(sd, "isSynced=\"0\""); + } + + /* convert all structured data to string */ + structured = get_structured_data_string(ctl); + if (!structured) + structured = xstrdup(NILVALUE); + + xasprintf(&ctl->hdr, "<%d>1 %s %s %s %s %s %s ", + ctl->pri, + time, + hostname, + app_name, + procid, + msgid, + structured); + + free(time); + free(hostname); + /* app_name points to ctl->tag, do NOT free! */ + free(procid); + free(msgid); + free(structured); +} + +static void parse_rfc5424_flags(struct logger_ctl *ctl, char *s) +{ + char *in, *tok; + + in = s; + while ((tok = strtok(in, ","))) { + in = NULL; + if (!strcmp(tok, "notime")) { + ctl->rfc5424_time = 0; + ctl->rfc5424_tq = 0; + } else if (!strcmp(tok, "notq")) + ctl->rfc5424_tq = 0; + else if (!strcmp(tok, "nohost")) + ctl->rfc5424_host = 0; + else + warnx(_("ignoring unknown option argument: %s"), tok); + } +} + +static int parse_unix_socket_errors_flags(char *s) +{ + if (!strcmp(s, "off")) + return AF_UNIX_ERRORS_OFF; + if (!strcmp(s, "on")) + return AF_UNIX_ERRORS_ON; + if (!strcmp(s, "auto")) + return AF_UNIX_ERRORS_AUTO; + warnx(_("invalid argument: %s: using automatic errors"), s); + return AF_UNIX_ERRORS_AUTO; +} + +static void syslog_local_header(struct logger_ctl *const ctl) +{ + char pid[32]; + + if (ctl->pid) + snprintf(pid, sizeof(pid), "[%d]", ctl->pid); + else + pid[0] = '\0'; + + xasprintf(&ctl->hdr, "<%d>%s %s%s: ", ctl->pri, rfc3164_current_time(), + ctl->tag, pid); +} + +static void generate_syslog_header(struct logger_ctl *const ctl) +{ + free(ctl->hdr); + ctl->hdr = NULL; + ctl->syslogfp(ctl); +} + +/* just open, nothing else */ +static void __logger_open(struct logger_ctl *ctl) +{ + if (ctl->server) { + ctl->fd = inet_socket(ctl->server, ctl->port, &ctl->socket_type); + } else { + if (!ctl->unix_socket) + ctl->unix_socket = _PATH_DEVLOG; + + ctl->fd = unix_socket(ctl, ctl->unix_socket, &ctl->socket_type); + } +} + +/* open and initialize relevant @ctl tuff */ +static void logger_open(struct logger_ctl *ctl) +{ + __logger_open(ctl); + + if (!ctl->syslogfp) + ctl->syslogfp = ctl->server ? syslog_rfc5424_header : + syslog_local_header; + if (!ctl->tag) + ctl->tag = ctl->login = xgetlogin(); + if (!ctl->tag) + ctl->tag = "<someone>"; +} + +/* re-open; usually after failed connection */ +static void logger_reopen(struct logger_ctl *ctl) +{ + if (ctl->fd != -1) + close(ctl->fd); + ctl->fd = -1; + + __logger_open(ctl); +} + +static void logger_command_line(struct logger_ctl *ctl, char **argv) +{ + /* note: we never re-generate the syslog header here, even if we + * generate multiple messages. If so, we think it is the right thing + * to do to report them with the same timestamp, as the user actually + * intended to send a single message. + */ + char *const buf = xmalloc(ctl->max_message_size + 1); + char *p = buf; + const char *endp = buf + ctl->max_message_size - 1; + size_t len; + + while (*argv) { + len = strlen(*argv); + if (endp < p + len && p != buf) { + write_output(ctl, buf); + p = buf; + } + if (ctl->max_message_size < len) { + (*argv)[ctl->max_message_size] = '\0'; /* truncate */ + write_output(ctl, *argv++); + continue; + } + if (p != buf) + *p++ = ' '; + memmove(p, *argv++, len); + *(p += len) = '\0'; + } + if (p != buf) + write_output(ctl, buf); + free(buf); +} + +static void logger_stdin(struct logger_ctl *ctl) +{ + /* note: we re-generate the syslog header for each log message to + * update header timestamps and to reflect possible priority changes. + */ + int default_priority = ctl->pri; + char *buf = xmalloc(ctl->max_message_size + 2 + 2); + int pri; + int c; + size_t i; + + c = getchar(); + while (c != EOF) { + i = 0; + if (ctl->prio_prefix && c == '<') { + pri = 0; + buf[i++] = c; + while (isdigit(c = getchar()) && pri <= 191) { + buf[i++] = c; + pri = pri * 10 + c - '0'; + } + if (c != EOF && c != '\n') + buf[i++] = c; + if (c == '>' && 0 <= pri && pri <= 191) { + /* valid RFC PRI values */ + i = 0; + if ((pri & LOG_FACMASK) == 0) + pri |= (default_priority & LOG_FACMASK); + ctl->pri = pri; + } else + ctl->pri = default_priority; + + if (c != EOF && c != '\n') + c = getchar(); + } + + while (c != EOF && c != '\n' && i < ctl->max_message_size) { + buf[i++] = c; + c = getchar(); + } + buf[i] = '\0'; + + if (i > 0 || !ctl->skip_empty_lines) { + generate_syslog_header(ctl); + write_output(ctl, buf); + } + + if (c == '\n') /* discard line terminator */ + c = getchar(); + } + + free(buf); +} + +static void logger_close(const struct logger_ctl *ctl) +{ + if (ctl->fd != -1 && close(ctl->fd) != 0) + err(EXIT_FAILURE, _("close failed")); + free(ctl->hdr); + free(ctl->login); +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] [<message>]\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Enter messages into the system log.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -i log the logger command's PID\n"), out); + fputs(_(" --id[=<id>] log the given <id>, or otherwise the PID\n"), out); + fputs(_(" -f, --file <file> log the contents of this file\n"), out); + fputs(_(" -e, --skip-empty do not log empty lines when processing files\n"), out); + fputs(_(" --no-act do everything except the write the log\n"), out); + fputs(_(" -p, --priority <prio> mark given message with this priority\n"), out); + fputs(_(" --octet-count use rfc6587 octet counting\n"), out); + fputs(_(" --prio-prefix look for a prefix on every line read from stdin\n"), out); + fputs(_(" -s, --stderr output message to standard error as well\n"), out); + fputs(_(" -S, --size <size> maximum size for a single message\n"), out); + fputs(_(" -t, --tag <tag> mark every line with this tag\n"), out); + fputs(_(" -n, --server <name> write to this remote syslog server\n"), out); + fputs(_(" -P, --port <port> use this port for UDP or TCP connection\n"), out); + fputs(_(" -T, --tcp use TCP only\n"), out); + fputs(_(" -d, --udp use UDP only\n"), out); + fputs(_(" --rfc3164 use the obsolete BSD syslog protocol\n"), out); + fputs(_(" --rfc5424[=<snip>] use the syslog protocol (the default for remote);\n" + " <snip> can be notime, or notq, and/or nohost\n"), out); + fputs(_(" --sd-id <id> rfc5424 structured data ID\n"), out); + fputs(_(" --sd-param <data> rfc5424 structured data name=value\n"), out); + fputs(_(" --msgid <msgid> set rfc5424 message id field\n"), out); + fputs(_(" -u, --socket <socket> write to this Unix socket\n"), out); + fputs(_(" --socket-errors[=<on|off|auto>]\n" + " print connection errors when using Unix sockets\n"), out); +#ifdef HAVE_LIBSYSTEMD + fputs(_(" --journald[=<file>] write journald entry\n"), out); +#endif + + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(26)); + printf(USAGE_MAN_TAIL("logger(1)")); + + exit(EXIT_SUCCESS); +} + +/* + * logger -- read and log utility + * + * Reads from an input and arranges to write the result on the system + * log. + */ +int main(int argc, char **argv) +{ + struct logger_ctl ctl = { + .fd = -1, + .pid = 0, + .pri = LOG_USER | LOG_NOTICE, + .prio_prefix = 0, + .tag = NULL, + .unix_socket = NULL, + .unix_socket_errors = 0, + .server = NULL, + .port = NULL, + .hdr = NULL, + .msgid = NULL, + .socket_type = ALL_TYPES, + .max_message_size = 1024, + .rfc5424_time = 1, + .rfc5424_tq = 1, + .rfc5424_host = 1, + .skip_empty_lines = 0 + }; + int ch; + int stdout_reopened = 0; + int unix_socket_errors_mode = AF_UNIX_ERRORS_AUTO; +#ifdef HAVE_LIBSYSTEMD + FILE *jfd = NULL; +#endif + static const struct option longopts[] = { + { "id", optional_argument, 0, OPT_ID }, + { "stderr", no_argument, 0, 's' }, + { "file", required_argument, 0, 'f' }, + { "no-act", no_argument, 0, OPT_NOACT, }, + { "priority", required_argument, 0, 'p' }, + { "tag", required_argument, 0, 't' }, + { "socket", required_argument, 0, 'u' }, + { "socket-errors", required_argument, 0, OPT_SOCKET_ERRORS }, + { "udp", no_argument, 0, 'd' }, + { "tcp", no_argument, 0, 'T' }, + { "server", required_argument, 0, 'n' }, + { "port", required_argument, 0, 'P' }, + { "version", no_argument, 0, 'V' }, + { "help", no_argument, 0, 'h' }, + { "octet-count", no_argument, 0, OPT_OCTET_COUNT }, + { "prio-prefix", no_argument, 0, OPT_PRIO_PREFIX }, + { "rfc3164", no_argument, 0, OPT_RFC3164 }, + { "rfc5424", optional_argument, 0, OPT_RFC5424 }, + { "size", required_argument, 0, 'S' }, + { "msgid", required_argument, 0, OPT_MSGID }, + { "skip-empty", no_argument, 0, 'e' }, + { "sd-id", required_argument, 0, OPT_STRUCTURED_DATA_ID }, + { "sd-param", required_argument, 0, OPT_STRUCTURED_DATA_PARAM }, +#ifdef HAVE_LIBSYSTEMD + { "journald", optional_argument, 0, OPT_JOURNALD }, +#endif + { NULL, 0, 0, 0 } + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + INIT_LIST_HEAD(&ctl.user_sds); + INIT_LIST_HEAD(&ctl.reserved_sds); + + while ((ch = getopt_long(argc, argv, "ef:ip:S:st:u:dTn:P:Vh", + longopts, NULL)) != -1) { + switch (ch) { + case 'f': /* file to log */ + if (freopen(optarg, "r", stdin) == NULL) + err(EXIT_FAILURE, _("file %s"), optarg); + stdout_reopened = 1; + break; + case 'e': + ctl.skip_empty_lines = 1; + break; + case 'i': /* log process id also */ + ctl.pid = logger_getpid(); + break; + case OPT_ID: + if (optarg) { + const char *p = optarg; + + if (*p == '=') + p++; + ctl.pid = strtoul_or_err(optarg, _("failed to parse id")); + } else + ctl.pid = logger_getpid(); + break; + case 'p': /* priority */ + ctl.pri = pencode(optarg); + break; + case 's': /* log to standard error */ + ctl.stderr_printout = 1; + break; + case 't': /* tag */ + ctl.tag = optarg; + break; + case 'u': /* unix socket */ + ctl.unix_socket = optarg; + break; + case 'S': /* max message size */ + ctl.max_message_size = strtosize_or_err(optarg, + _("failed to parse message size")); + break; + case 'd': + ctl.socket_type = TYPE_UDP; + break; + case 'T': + ctl.socket_type = TYPE_TCP; + break; + case 'n': + ctl.server = optarg; + break; + case 'P': + ctl.port = optarg; + break; + case OPT_OCTET_COUNT: + ctl.octet_count = 1; + break; + case OPT_PRIO_PREFIX: + ctl.prio_prefix = 1; + break; + case OPT_RFC3164: + ctl.syslogfp = syslog_rfc3164_header; + break; + case OPT_RFC5424: + ctl.syslogfp = syslog_rfc5424_header; + if (optarg) + parse_rfc5424_flags(&ctl, optarg); + break; + case OPT_MSGID: + if (strchr(optarg, ' ')) + errx(EXIT_FAILURE, _("--msgid cannot contain space")); + ctl.msgid = optarg; + break; +#ifdef HAVE_LIBSYSTEMD + case OPT_JOURNALD: + if (optarg) { + jfd = fopen(optarg, "r"); + if (!jfd) + err(EXIT_FAILURE, _("cannot open %s"), + optarg); + } else + jfd = stdin; + break; +#endif + case OPT_SOCKET_ERRORS: + unix_socket_errors_mode = parse_unix_socket_errors_flags(optarg); + break; + case OPT_NOACT: + ctl.noact = 1; + break; + case OPT_STRUCTURED_DATA_ID: + if (!valid_structured_data_id(optarg)) + errx(EXIT_FAILURE, _("invalid structured data ID: '%s'"), optarg); + add_structured_data_id(get_user_structured_data(&ctl), optarg); + break; + case OPT_STRUCTURED_DATA_PARAM: + if (!valid_structured_data_param(optarg)) + errx(EXIT_FAILURE, _("invalid structured data parameter: '%s'"), optarg); + add_structured_data_param(get_user_structured_data(&ctl), optarg); + break; + + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + argc -= optind; + argv += optind; + if (stdout_reopened && argc) + warnx(_("--file <file> and <message> are mutually exclusive, message is ignored")); +#ifdef HAVE_LIBSYSTEMD + if (jfd) { + int ret = journald_entry(&ctl, jfd); + if (stdin != jfd) + fclose(jfd); + if (ret) + errx(EXIT_FAILURE, _("journald entry could not be written")); + return EXIT_SUCCESS; + } +#endif + + /* user overwrites built-in SD-ELEMENT */ + if (has_structured_data_id(get_user_structured_data(&ctl), "timeQuality")) + ctl.rfc5424_tq = 0; + + switch (unix_socket_errors_mode) { + case AF_UNIX_ERRORS_OFF: + ctl.unix_socket_errors = 0; + break; + case AF_UNIX_ERRORS_ON: + ctl.unix_socket_errors = 1; + break; + case AF_UNIX_ERRORS_AUTO: + ctl.unix_socket_errors = ctl.noact || ctl.stderr_printout; +#ifdef HAVE_LIBSYSTEMD + ctl.unix_socket_errors |= !!sd_booted(); +#endif + break; + default: + abort(); + } + logger_open(&ctl); + if (0 < argc) { + generate_syslog_header(&ctl); + logger_command_line(&ctl, argv); + } else + /* Note. --file <arg> reopens stdin making the below + * function to be used for file inputs. */ + logger_stdin(&ctl); + + logger_close(&ctl); + return EXIT_SUCCESS; +} diff --git a/misc-utils/look.1 b/misc-utils/look.1 new file mode 100644 index 0000000..b45090d --- /dev/null +++ b/misc-utils/look.1 @@ -0,0 +1,116 @@ +'\" t +.\" Title: look +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "LOOK" "1" "2023-10-23" "util\-linux 2.39.3" "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" +look \- display lines beginning with a given string +.SH "SYNOPSIS" +.sp +\fBlook\fP [options] \fIstring\fP [\fIfile\fP] +.SH "DESCRIPTION" +.sp +The \fBlook\fP utility displays any lines in \fIfile\fP which contain \fIstring\fP as a prefix. As \fBlook\fP performs a binary search, the lines in \fIfile\fP must be sorted (where \fBsort\fP(1) was given the same options \fB\-d\fP and/or \fB\-f\fP that \fBlook\fP is invoked with). +.sp +If \fIfile\fP is not specified, the file \fI/usr/share/dict/words\fP is used, only alphanumeric characters are compared and the case of alphabetic characters is ignored. +.SH "OPTIONS" +.sp +\fB\-a\fP, \fB\-\-alternative\fP +.RS 4 +Use the alternative dictionary file. +.RE +.sp +\fB\-d\fP, \fB\-\-alphanum\fP +.RS 4 +Use normal dictionary character set and order, i.e., only blanks and alphanumeric characters are compared. This is on by default if no file is specified. +.sp +Note that blanks have been added to dictionary character set for compatibility with \fBsort \-d\fP command since version 2.28. +.RE +.sp +\fB\-f\fP, \fB\-\-ignore\-case\fP +.RS 4 +Ignore the case of alphabetic characters. This is on by default if no file is specified. +.RE +.sp +\fB\-t\fP, \fB\-\-terminate\fP \fIcharacter\fP +.RS 4 +Specify a string termination character, i.e., only the characters in \fIstring\fP up to and including the first occurrence of \fIcharacter\fP are compared. +.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 +.sp +The \fBlook\fP utility exits 0 if one or more lines were found and displayed, 1 if no lines were found, and >1 if an error occurred. +.SH "ENVIRONMENT" +.sp +\fBWORDLIST\fP +.RS 4 +Path to a dictionary file. The environment variable has greater priority than the dictionary path defined in the \fBFILES\fP segment. +.RE +.SH "FILES" +.sp +\fI/usr/share/dict/words\fP +.RS 4 +the dictionary +.RE +.sp +\fI/usr/share/dict/web2\fP +.RS 4 +the alternative dictionary +.RE +.SH "HISTORY" +.sp +The \fBlook\fP utility appeared in Version 7 AT&T Unix. +.SH "EXAMPLES" +.sp +.if n .RS 4 +.nf +.fam C +sort \-d /etc/passwd \-o /tmp/look.dict +look \-t: root:foobar /tmp/look.dict +.fam +.fi +.if n .RE +.SH "SEE ALSO" +.sp +\fBgrep\fP(1), +\fBsort\fP(1) +.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 \fBlook\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/look.1.adoc b/misc-utils/look.1.adoc new file mode 100644 index 0000000..35b8978 --- /dev/null +++ b/misc-utils/look.1.adoc @@ -0,0 +1,112 @@ +//po4a: entry man manual +//// +Copyright (c) 1990, 1993 + The Regents of the University of California. All rights reserved. + +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. +3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by the University of + California, Berkeley and its contributors. +4. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + + @(#)look.1 8.1 (Berkeley) 6/14/93 +//// += look(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: look + +== NAME + +look - display lines beginning with a given string + +== SYNOPSIS + +*look* [options] _string_ [_file_] + +== DESCRIPTION + +The *look* utility displays any lines in _file_ which contain _string_ as a prefix. As *look* performs a binary search, the lines in _file_ must be sorted (where *sort*(1) was given the same options *-d* and/or *-f* that *look* is invoked with). + +If _file_ is not specified, the file _/usr/share/dict/words_ is used, only alphanumeric characters are compared and the case of alphabetic characters is ignored. + +== OPTIONS + +*-a*, *--alternative*:: +Use the alternative dictionary file. + +*-d*, *--alphanum*:: +Use normal dictionary character set and order, i.e., only blanks and alphanumeric characters are compared. This is on by default if no file is specified. ++ +Note that blanks have been added to dictionary character set for compatibility with *sort -d* command since version 2.28. + +*-f*, *--ignore-case*:: +Ignore the case of alphabetic characters. This is on by default if no file is specified. + +*-t*, *--terminate* _character_:: +Specify a string termination character, i.e., only the characters in _string_ up to and including the first occurrence of _character_ are compared. + +include::man-common/help-version.adoc[] + +The *look* utility exits 0 if one or more lines were found and displayed, 1 if no lines were found, and >1 if an error occurred. + +== ENVIRONMENT + +*WORDLIST*:: +Path to a dictionary file. The environment variable has greater priority than the dictionary path defined in the *FILES* segment. + +== FILES + +_/usr/share/dict/words_:: +the dictionary + +_/usr/share/dict/web2_:: +the alternative dictionary + +== HISTORY + +The *look* utility appeared in Version 7 AT&T Unix. + +== EXAMPLES + +.... +sort -d /etc/passwd -o /tmp/look.dict +look -t: root:foobar /tmp/look.dict +.... + +== SEE ALSO + +*grep*(1), +*sort*(1) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/look.c b/misc-utils/look.c new file mode 100644 index 0000000..0e6f1ed --- /dev/null +++ b/misc-utils/look.c @@ -0,0 +1,373 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * David Hitz of Auspex Systems, Inc. + * + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + */ + + /* 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + */ + +/* + * look -- find lines in a sorted list. + * + * The man page said that TABs and SPACEs participate in -d comparisons. + * In fact, they were ignored. This implements historic practice, not + * the manual page. + */ + +#include <sys/mman.h> +#include <sys/stat.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <ctype.h> +#include <getopt.h> + +#include "nls.h" +#include "xalloc.h" +#include "pathnames.h" +#include "closestream.h" + +#define EQUAL 0 +#define GREATER 1 +#define LESS (-1) + +static int dflag, fflag; +/* uglified the source a bit with globals, so that we only need + to allocate comparbuf once */ +static int stringlen; +static char *string; +static char *comparbuf; + +static char *binary_search (char *, char *); +static int compare (char *, char *); +static char *linear_search (char *, char *); +static int look (char *, char *); +static void print_from (char *, char *); +static void __attribute__((__noreturn__)) usage(void); + +int +main(int argc, char *argv[]) +{ + struct stat sb; + int ch, fd, termchar; + char *back, *file, *front, *p; + + static const struct option longopts[] = { + {"alternative", no_argument, NULL, 'a'}, + {"alphanum", no_argument, NULL, 'd'}, + {"ignore-case", no_argument, NULL, 'f'}, + {"terminate", required_argument, NULL, 't'}, + {"version", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + if ((file = getenv("WORDLIST")) && !access(file, R_OK)) + /* use the WORDLIST */; + else + file = _PATH_WORDS; + + termchar = '\0'; + string = NULL; /* just for gcc */ + + while ((ch = getopt_long(argc, argv, "adft:Vh", longopts, NULL)) != -1) + switch(ch) { + case 'a': + file = _PATH_WORDS_ALT; + break; + case 'd': + dflag = 1; + break; + case 'f': + fflag = 1; + break; + case 't': + termchar = *optarg; + break; + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + argc -= optind; + argv += optind; + + switch (argc) { + case 2: /* Don't set -df for user. */ + string = *argv++; + file = *argv; + break; + case 1: /* But set -df by default. */ + dflag = fflag = 1; + string = *argv; + break; + default: + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + + if (termchar != '\0' && (p = strchr(string, termchar)) != NULL) + *++p = '\0'; + + if ((fd = open(file, O_RDONLY, 0)) < 0 || fstat(fd, &sb)) + err(EXIT_FAILURE, "%s", file); + front = mmap(NULL, (size_t) sb.st_size, PROT_READ, + MAP_SHARED, fd, (off_t) 0); + if +#ifdef MAP_FAILED + (front == MAP_FAILED) +#else + ((void *)(front) <= (void *)0) +#endif + err(EXIT_FAILURE, "%s", file); + back = front + sb.st_size; + return look(front, back); +} + +static int +look(char *front, char *back) +{ + int ch; + char *readp, *writep; + + /* Reformat string to avoid doing it multiple times later. */ + if (dflag) { + for (readp = writep = string; (ch = *readp++) != 0;) { + if (isalnum(ch) || isblank(ch)) + *(writep++) = ch; + } + *writep = '\0'; + stringlen = writep - string; + } else + stringlen = strlen(string); + + comparbuf = xmalloc(stringlen+1); + + front = binary_search(front, back); + front = linear_search(front, back); + + if (front) + print_from(front, back); + + free(comparbuf); + + return (front ? 0 : 1); +} + + +/* + * Binary search for "string" in memory between "front" and "back". + * + * This routine is expected to return a pointer to the start of a line at + * *or before* the first word matching "string". Relaxing the constraint + * this way simplifies the algorithm. + * + * Invariants: + * front points to the beginning of a line at or before the first + * matching string. + * + * back points to the beginning of a line at or after the first + * matching line. + * + * Advancing the Invariants: + * + * p = first newline after halfway point from front to back. + * + * If the string at "p" is not greater than the string to match, + * p is the new front. Otherwise it is the new back. + * + * Termination: + * + * The definition of the routine allows it return at any point, + * since front is always at or before the line to print. + * + * In fact, it returns when the chosen "p" equals "back". This + * implies that there exists a string is least half as long as + * (back - front), which in turn implies that a linear search will + * be no more expensive than the cost of simply printing a string or two. + * + * Trying to continue with binary search at this point would be + * more trouble than it's worth. + */ +#define SKIP_PAST_NEWLINE(p, back) \ + while (p < back && *p++ != '\n') + +static char * +binary_search(char *front, char *back) +{ + char *p; + + p = front + (back - front) / 2; + SKIP_PAST_NEWLINE(p, back); + + /* + * If the file changes underneath us, make sure we don't + * infinitely loop. + */ + while (p < back && back > front) { + if (compare(p, back) == GREATER) + front = p; + else + back = p; + p = front + (back - front) / 2; + SKIP_PAST_NEWLINE(p, back); + } + return (front); +} + +/* + * Find the first line that starts with string, linearly searching from front + * to back. + * + * Return NULL for no such line. + * + * This routine assumes: + * + * o front points at the first character in a line. + * o front is before or at the first line to be printed. + */ +static char * +linear_search(char *front, char *back) +{ + while (front < back) { + switch (compare(front, back)) { + case EQUAL: /* Found it. */ + return (front); + case LESS: /* No such string. */ + return (NULL); + case GREATER: /* Keep going. */ + break; + } + SKIP_PAST_NEWLINE(front, back); + } + return (NULL); +} + +/* + * Print as many lines as match string, starting at front. + */ +static void +print_from(char *front, char *back) +{ + int eol; + + while (front < back && compare(front, back) == EQUAL) { + if (compare(front, back) == EQUAL) { + eol = 0; + while (front < back && !eol) { + if (putchar(*front) == EOF) + err(EXIT_FAILURE, "stdout"); + if (*front++ == '\n') + eol = 1; + } + } else + SKIP_PAST_NEWLINE(front, back); + } +} + +/* + * Return LESS, GREATER, or EQUAL depending on how string compares with + * string2 (s1 ??? s2). + * + * o Matches up to len(s1) are EQUAL. + * o Matches up to len(s2) are GREATER. + * + * Compare understands about the -f and -d flags, and treats comparisons + * appropriately. + * + * The string "string" is null terminated. The string "s2" is '\n' terminated + * (or "s2end" terminated). + * + * We use strcasecmp etc, since it knows how to ignore case also + * in other locales. + */ +static int +compare(char *s2, char *s2end) { + int i; + char *p; + + /* copy, ignoring things that should be ignored */ + p = comparbuf; + i = stringlen; + while(s2 < s2end && *s2 != '\n' && i) { + if (!dflag || isalnum(*s2) || isblank(*s2)) + { + *p++ = *s2; + i--; + } + s2++; + } + *p = 0; + + /* and compare */ + if (fflag) + i = strncasecmp(comparbuf, string, stringlen); + else + i = strncmp(comparbuf, string, stringlen); + + return ((i > 0) ? LESS : (i < 0) ? GREATER : EQUAL); +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] <string> [<file>...]\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Display lines beginning with a specified string.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -a, --alternative use the alternative dictionary\n"), out); + fputs(_(" -d, --alphanum compare only blanks and alphanumeric characters\n"), out); + fputs(_(" -f, --ignore-case ignore case differences when comparing\n"), out); + 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)")); + + exit(EXIT_SUCCESS); +} diff --git a/misc-utils/lsblk-devtree.c b/misc-utils/lsblk-devtree.c new file mode 100644 index 0000000..5e4ff4c --- /dev/null +++ b/misc-utils/lsblk-devtree.c @@ -0,0 +1,564 @@ +/* + * These functions implement tree of block devices. The devtree struct contains + * two basic lists: + * + * 1) devtree->devices -- This is simple list without any hierarchy. We use + * reference counting here. + * + * 2) devtree->roots -- The root nodes of the trees. The code does not use + * reference counting here due to complexity and it's unnecessary. + * + * Note that the same device maybe have more parents and more children. The + * device is allocated only once and shared within the tree. The dependence + * (devdep struct) contains reference to child as well as to parent and the + * dependence is reference by ls_childs from parent device and by ls_parents + * from child. (Yes, "childs" is used for children ;-) + * + * Copyright (C) 2018 Karel Zak <kzak@redhat.com> + */ +#include "lsblk.h" +#include "sysfs.h" +#include "pathnames.h" + + +void lsblk_reset_iter(struct lsblk_iter *itr, int direction) +{ + if (direction == -1) + direction = itr->direction; + + memset(itr, 0, sizeof(*itr)); + itr->direction = direction; +} + +struct lsblk_device *lsblk_new_device(void) +{ + struct lsblk_device *dev; + + dev = calloc(1, sizeof(*dev)); + if (!dev) + return NULL; + + dev->refcount = 1; + dev->removable = -1; + dev->discard_granularity = (uint64_t) -1; + + INIT_LIST_HEAD(&dev->childs); + INIT_LIST_HEAD(&dev->parents); + INIT_LIST_HEAD(&dev->ls_roots); + INIT_LIST_HEAD(&dev->ls_devices); + + DBG(DEV, ul_debugobj(dev, "alloc")); + return dev; +} + +void lsblk_ref_device(struct lsblk_device *dev) +{ + if (dev) + dev->refcount++; +} + +/* removes dependence from child as well as from parent */ +static int remove_dependence(struct lsblk_devdep *dep) +{ + if (!dep) + return -EINVAL; + + DBG(DEP, ul_debugobj(dep, " dealloc")); + + list_del_init(&dep->ls_childs); + list_del_init(&dep->ls_parents); + + free(dep); + return 0; +} + +static int device_remove_dependences(struct lsblk_device *dev) +{ + if (!dev) + return -EINVAL; + + if (!list_empty(&dev->childs)) + DBG(DEV, ul_debugobj(dev, " %s: remove all children deps", dev->name)); + while (!list_empty(&dev->childs)) { + struct lsblk_devdep *dp = list_entry(dev->childs.next, + struct lsblk_devdep, ls_childs); + remove_dependence(dp); + } + + if (!list_empty(&dev->parents)) + DBG(DEV, ul_debugobj(dev, " %s: remove all parents deps", dev->name)); + while (!list_empty(&dev->parents)) { + struct lsblk_devdep *dp = list_entry(dev->parents.next, + struct lsblk_devdep, ls_parents); + remove_dependence(dp); + } + + return 0; +} + +void lsblk_unref_device(struct lsblk_device *dev) +{ + if (!dev) + return; + + if (--dev->refcount <= 0) { + DBG(DEV, ul_debugobj(dev, " freeing [%s] <<", dev->name)); + + device_remove_dependences(dev); + lsblk_device_free_properties(dev->properties); + lsblk_device_free_filesystems(dev); + + lsblk_unref_device(dev->wholedisk); + + free(dev->dm_name); + free(dev->filename); + free(dev->dedupkey); + + ul_unref_path(dev->sysfs); + + DBG(DEV, ul_debugobj(dev, " >> dealloc [%s]", dev->name)); + free(dev->name); + free(dev); + } +} + +int lsblk_device_has_child(struct lsblk_device *dev, struct lsblk_device *child) +{ + struct lsblk_device *x = NULL; + struct lsblk_iter itr; + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (lsblk_device_next_child(dev, &itr, &x) == 0) { + if (x == child) + return 1; + } + + return 0; +} + +int lsblk_device_new_dependence(struct lsblk_device *parent, struct lsblk_device *child) +{ + struct lsblk_devdep *dp; + + if (!parent || !child) + return -EINVAL; + + if (lsblk_device_has_child(parent, child)) + return 1; + + dp = calloc(1, sizeof(*dp)); + if (!dp) + return -ENOMEM; + + INIT_LIST_HEAD(&dp->ls_childs); + INIT_LIST_HEAD(&dp->ls_parents); + + dp->child = child; + list_add_tail(&dp->ls_childs, &parent->childs); + + dp->parent = parent; + list_add_tail(&dp->ls_parents, &child->parents); + + DBG(DEV, ul_debugobj(parent, "add dependence 0x%p [%s->%s]", dp, parent->name, child->name)); + + return 0; +} + +static int device_next_child(struct lsblk_device *dev, + struct lsblk_iter *itr, + struct lsblk_devdep **dp) +{ + int rc = 1; + + if (!dev || !itr || !dp) + return -EINVAL; + *dp = NULL; + + if (!itr->head) + LSBLK_ITER_INIT(itr, &dev->childs); + if (itr->p != itr->head) { + LSBLK_ITER_ITERATE(itr, *dp, struct lsblk_devdep, ls_childs); + rc = 0; + } + + return rc; +} + +int lsblk_device_next_child(struct lsblk_device *dev, + struct lsblk_iter *itr, + struct lsblk_device **child) +{ + struct lsblk_devdep *dp = NULL; + int rc = device_next_child(dev, itr, &dp); + + if (!child) + return -EINVAL; + + *child = rc == 0 ? dp->child : NULL; + return rc; +} + +int lsblk_device_is_last_parent(struct lsblk_device *dev, struct lsblk_device *parent) +{ + struct lsblk_devdep *dp = list_last_entry( + &dev->parents, + struct lsblk_devdep, ls_parents); + if (!dp) + return 0; + return dp->parent == parent; +} + +int lsblk_device_next_parent( + struct lsblk_device *dev, + struct lsblk_iter *itr, + struct lsblk_device **parent) +{ + int rc = 1; + + if (!dev || !itr || !parent) + return -EINVAL; + *parent = NULL; + + if (!itr->head) + LSBLK_ITER_INIT(itr, &dev->parents); + if (itr->p != itr->head) { + struct lsblk_devdep *dp = NULL; + LSBLK_ITER_ITERATE(itr, dp, struct lsblk_devdep, ls_parents); + if (dp) + *parent = dp->parent; + rc = 0; + } + + return rc; +} + +struct lsblk_devtree *lsblk_new_devtree(void) +{ + struct lsblk_devtree *tr; + + tr = calloc(1, sizeof(*tr)); + if (!tr) + return NULL; + + tr->refcount = 1; + + INIT_LIST_HEAD(&tr->roots); + INIT_LIST_HEAD(&tr->devices); + INIT_LIST_HEAD(&tr->pktcdvd_map); + + DBG(TREE, ul_debugobj(tr, "alloc")); + return tr; +} + +void lsblk_ref_devtree(struct lsblk_devtree *tr) +{ + if (tr) + tr->refcount++; +} + +void lsblk_unref_devtree(struct lsblk_devtree *tr) +{ + if (!tr) + return; + + if (--tr->refcount <= 0) { + DBG(TREE, ul_debugobj(tr, "dealloc")); + + while (!list_empty(&tr->devices)) { + struct lsblk_device *dev = list_entry(tr->devices.next, + struct lsblk_device, ls_devices); + lsblk_devtree_remove_device(tr, dev); + } + + while (!list_empty(&tr->pktcdvd_map)) { + struct lsblk_devnomap *map = list_entry(tr->pktcdvd_map.next, + struct lsblk_devnomap, ls_devnomap); + list_del(&map->ls_devnomap); + free(map); + } + + free(tr); + } +} + +static int has_root(struct lsblk_devtree *tr, struct lsblk_device *dev) +{ + struct lsblk_iter itr; + struct lsblk_device *x = NULL; + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (lsblk_devtree_next_root(tr, &itr, &x) == 0) { + if (x == dev) + return 1; + } + return 0; +} + +int lsblk_devtree_add_root(struct lsblk_devtree *tr, struct lsblk_device *dev) +{ + if (has_root(tr, dev)) + return 0; + + if (!lsblk_devtree_has_device(tr, dev)) + lsblk_devtree_add_device(tr, dev); + + /* We don't increment reference counter for tr->roots list. The primary + * reference is tr->devices */ + + DBG(TREE, ul_debugobj(tr, "add root device 0x%p [%s]", dev, dev->name)); + list_add_tail(&dev->ls_roots, &tr->roots); + return 0; +} + +int lsblk_devtree_remove_root(struct lsblk_devtree *tr __attribute__((unused)), + struct lsblk_device *dev) +{ + DBG(TREE, ul_debugobj(tr, "remove root device 0x%p [%s]", dev, dev->name)); + list_del_init(&dev->ls_roots); + + return 0; +} + +int lsblk_devtree_next_root(struct lsblk_devtree *tr, + struct lsblk_iter *itr, + struct lsblk_device **dev) +{ + int rc = 1; + + if (!tr || !itr || !dev) + return -EINVAL; + *dev = NULL; + if (!itr->head) + LSBLK_ITER_INIT(itr, &tr->roots); + if (itr->p != itr->head) { + LSBLK_ITER_ITERATE(itr, *dev, struct lsblk_device, ls_roots); + rc = 0; + } + return rc; +} + +int lsblk_devtree_add_device(struct lsblk_devtree *tr, struct lsblk_device *dev) +{ + lsblk_ref_device(dev); + + DBG(TREE, ul_debugobj(tr, "add device 0x%p [%s]", dev, dev->name)); + list_add_tail(&dev->ls_devices, &tr->devices); + return 0; +} + +int lsblk_devtree_next_device(struct lsblk_devtree *tr, + struct lsblk_iter *itr, + struct lsblk_device **dev) +{ + int rc = 1; + + if (!tr || !itr || !dev) + return -EINVAL; + *dev = NULL; + if (!itr->head) + LSBLK_ITER_INIT(itr, &tr->devices); + if (itr->p != itr->head) { + LSBLK_ITER_ITERATE(itr, *dev, struct lsblk_device, ls_devices); + rc = 0; + } + return rc; +} + +int lsblk_devtree_has_device(struct lsblk_devtree *tr, struct lsblk_device *dev) +{ + struct lsblk_device *x = NULL; + struct lsblk_iter itr; + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (lsblk_devtree_next_device(tr, &itr, &x) == 0) { + if (x == dev) + return 1; + } + + return 0; +} + +struct lsblk_device *lsblk_devtree_get_device(struct lsblk_devtree *tr, const char *name) +{ + struct lsblk_device *dev = NULL; + struct lsblk_iter itr; + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (lsblk_devtree_next_device(tr, &itr, &dev) == 0) { + if (strcmp(name, dev->name) == 0) + return dev; + } + + return NULL; +} + +int lsblk_devtree_remove_device(struct lsblk_devtree *tr, struct lsblk_device *dev) +{ + DBG(TREE, ul_debugobj(tr, "remove device 0x%p [%s]", dev, dev->name)); + + if (!lsblk_devtree_has_device(tr, dev)) + return 1; + + list_del_init(&dev->ls_roots); + list_del_init(&dev->ls_devices); + lsblk_unref_device(dev); + + return 0; +} + +static void read_pktcdvd_map(struct lsblk_devtree *tr) +{ + char buf[PATH_MAX]; + FILE *f; + + assert(tr->pktcdvd_read == 0); + + f = ul_path_fopen(NULL, "r", _PATH_SYS_CLASS "/pktcdvd/device_map"); + if (!f) + goto done; + + while (fgets(buf, sizeof(buf), f)) { + struct lsblk_devnomap *map; + int pkt_maj, pkt_min, blk_maj, blk_min; + + if (sscanf(buf, "%*s %d:%d %d:%d\n", + &pkt_maj, &pkt_min, + &blk_maj, &blk_min) != 4) + continue; + + map = malloc(sizeof(*map)); + if (!map) + break; + map->holder = makedev(pkt_maj, pkt_min); + map->slave = makedev(blk_maj, blk_min); + INIT_LIST_HEAD(&map->ls_devnomap); + list_add_tail(&map->ls_devnomap, &tr->pktcdvd_map); + } + + fclose(f); +done: + tr->pktcdvd_read = 1; +} + +/* returns opposite device of @devno for blk->pkt relation -- e.g. if devno + * is_slave (blk) then returns holder (pkt) and vice-versa */ +dev_t lsblk_devtree_pktcdvd_get_mate(struct lsblk_devtree *tr, dev_t devno, int is_slave) +{ + struct list_head *p; + + if (!tr->pktcdvd_read) + read_pktcdvd_map(tr); + if (list_empty(&tr->pktcdvd_map)) + return 0; + + list_for_each(p, &tr->pktcdvd_map) { + struct lsblk_devnomap *x = list_entry(p, struct lsblk_devnomap, ls_devnomap); + + if (is_slave && devno == x->slave) + return x->holder; + if (!is_slave && devno == x->holder) + return x->slave; + } + return 0; +} + +static int device_dedupkey_is_equal( + struct lsblk_device *dev, + struct lsblk_device *pattern) +{ + assert(pattern->dedupkey); + + if (!dev->dedupkey || dev == pattern) + return 0; + if (strcmp(dev->dedupkey, pattern->dedupkey) == 0) { + if (!device_is_partition(dev) || + !dev->wholedisk->dedupkey || + strcmp(dev->dedupkey, dev->wholedisk->dedupkey) != 0) { + DBG(DEV, ul_debugobj(dev, "%s: match deduplication pattern", dev->name)); + return 1; + } + } + return 0; +} + +static void device_dedup_dependencies( + struct lsblk_device *dev, + struct lsblk_device *pattern) +{ + struct lsblk_iter itr; + struct lsblk_devdep *dp; + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (device_next_child(dev, &itr, &dp) == 0) { + struct lsblk_device *child = dp->child; + + if (device_dedupkey_is_equal(child, pattern)) { + DBG(DEV, ul_debugobj(dev, "remove duplicate dependence: 0x%p [%s]", + dp->child, dp->child->name)); + remove_dependence(dp); + } else + device_dedup_dependencies(child, pattern); + } +} + +static void devtree_dedup(struct lsblk_devtree *tr, struct lsblk_device *pattern) +{ + struct lsblk_iter itr; + struct lsblk_device *dev = NULL; + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + DBG(TREE, ul_debugobj(tr, "de-duplicate by key: %s", pattern->dedupkey)); + + while (lsblk_devtree_next_root(tr, &itr, &dev) == 0) { + if (device_dedupkey_is_equal(dev, pattern)) { + DBG(TREE, ul_debugobj(tr, "remove duplicate device: 0x%p [%s]", + dev, dev->name)); + /* Note that root list does not use ref-counting; the + * primary reference is ls_devices */ + list_del_init(&dev->ls_roots); + } else + device_dedup_dependencies(dev, pattern); + } +} + +static int cmp_devices_devno(struct list_head *a, struct list_head *b, + __attribute__((__unused__)) void *data) +{ + struct lsblk_device *ax = list_entry(a, struct lsblk_device, ls_devices), + *bx = list_entry(b, struct lsblk_device, ls_devices); + + return cmp_numbers(makedev(ax->maj, ax->min), + makedev(bx->maj, bx->min)); +} + +/* Note that dev->dedupkey has to be already set */ +int lsblk_devtree_deduplicate_devices(struct lsblk_devtree *tr) +{ + struct lsblk_device *pattern = NULL; + struct lsblk_iter itr; + char *last = NULL; + + list_sort(&tr->devices, cmp_devices_devno, NULL); + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (lsblk_devtree_next_device(tr, &itr, &pattern) == 0) { + if (!pattern->dedupkey) + continue; + if (device_is_partition(pattern) && + pattern->wholedisk->dedupkey && + strcmp(pattern->dedupkey, pattern->wholedisk->dedupkey) == 0) + continue; + if (last && strcmp(pattern->dedupkey, last) == 0) + continue; + + devtree_dedup(tr, pattern); + last = pattern->dedupkey; + } + return 0; +} diff --git a/misc-utils/lsblk-mnt.c b/misc-utils/lsblk-mnt.c new file mode 100644 index 0000000..9f6ba0d --- /dev/null +++ b/misc-utils/lsblk-mnt.c @@ -0,0 +1,181 @@ +#include "c.h" +#include "pathnames.h" +#include "xalloc.h" +#include "nls.h" + +#include "lsblk.h" + +static struct libmnt_table *mtab, *swaps; +static struct libmnt_cache *mntcache; + +static int table_parser_errcb(struct libmnt_table *tb __attribute__((__unused__)), + const char *filename, int line) +{ + if (filename) + warnx(_("%s: parse error at line %d -- ignored"), filename, line); + return 1; +} + +static struct libmnt_fs *get_active_swap(const char *filename) +{ + assert(filename); + + if (!swaps) { + swaps = mnt_new_table(); + if (!swaps) + return 0; + if (!mntcache) + mntcache = mnt_new_cache(); + + mnt_table_set_parser_errcb(swaps, table_parser_errcb); + mnt_table_set_cache(swaps, mntcache); + + if (!lsblk->sysroot) + mnt_table_parse_swaps(swaps, NULL); + else { + char buf[PATH_MAX]; + snprintf(buf, sizeof(buf), "%s" _PATH_PROC_SWAPS, lsblk->sysroot); + mnt_table_parse_swaps(swaps, buf); + } + } + + return mnt_table_find_srcpath(swaps, filename, MNT_ITER_BACKWARD); +} + +void lsblk_device_free_filesystems(struct lsblk_device *dev) +{ + if (!dev) + return; + + free(dev->fss); + + dev->fss = NULL; + dev->nfss = 0; + dev->is_mounted = 0; + dev->is_swap = 0; +} + +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[dev->nfss] = fs; + dev->nfss++; + dev->is_mounted = 1; +} + +struct libmnt_fs **lsblk_device_get_filesystems(struct lsblk_device *dev, size_t *n) +{ + struct libmnt_fs *fs; + struct libmnt_iter *itr = NULL; + dev_t devno; + + assert(dev); + assert(dev->filename); + + if (dev->is_mounted) + goto done; + + lsblk_device_free_filesystems(dev); /* reset */ + + if (!mtab) { + mtab = mnt_new_table(); + if (!mtab) + return NULL; + if (!mntcache) + mntcache = mnt_new_cache(); + + mnt_table_set_parser_errcb(mtab, table_parser_errcb); + mnt_table_set_cache(mtab, mntcache); + + if (!lsblk->sysroot) + mnt_table_parse_mtab(mtab, NULL); + else { + char buf[PATH_MAX]; + snprintf(buf, sizeof(buf), "%s" _PATH_PROC_MOUNTINFO, lsblk->sysroot); + mnt_table_parse_mtab(mtab, buf); + } + } + + devno = makedev(dev->maj, dev->min); + + /* All mounpoint where is used devno or device name + */ + itr = mnt_new_iter(MNT_ITER_BACKWARD); + while (mnt_table_next_fs(mtab, itr, &fs) == 0) { + if (mnt_fs_get_devno(fs) != devno && + !mnt_fs_streq_srcpath(fs, dev->filename)) + continue; + add_filesystem(dev, fs); + } + + /* Try mnt_table_find_srcpath() which also canonicalizes patches, etc. + */ + if (!dev->nfss) { + fs = get_active_swap(dev->filename); + if (!fs) { + fs = mnt_table_find_srcpath(mtab, dev->filename, MNT_ITER_BACKWARD); + if (fs) + dev->is_swap = 1; + } + if (fs) + add_filesystem(dev, fs); + } + +done: + mnt_free_iter(itr); + if (n) + *n = dev->nfss; + return dev->fss; +} + +/* Returns mounpoint where the device is mounted. If the device is used for + * more filesystems (subvolumes, ...) than returns the "best" one. + */ +const char *lsblk_device_get_mountpoint(struct lsblk_device *dev) +{ + struct libmnt_fs *fs = NULL; + const char *root; + + lsblk_device_get_filesystems(dev, NULL); + if (!dev->nfss) + return NULL; + + /* lsblk_device_get_filesystems() scans mountinfo/swaps in backward + * order. It means the first in fss[] is the last mounted FS. Let's + * keep it as default */ + fs = dev->fss[0]; + root = mnt_fs_get_root(fs); + + if (root && strcmp(root, "/") != 0) { + /* FS is subvolume (or subdirectory bind-mount). Try to get + * FS with "/" root */ + size_t i; + + for (i = 1; i < dev->nfss; i++) { + root = mnt_fs_get_root(dev->fss[i]); + if (!root || strcmp(root, "/") == 0) { + fs = dev->fss[i]; + break; + } + } + } + if (mnt_fs_is_swaparea(fs)) + return "[SWAP]"; + return mnt_fs_get_target(fs); +} + +void lsblk_mnt_init(void) +{ + mnt_init_debug(0); +} + +void lsblk_mnt_deinit(void) +{ + mnt_unref_table(mtab); + mnt_unref_table(swaps); + mnt_unref_cache(mntcache); +} diff --git a/misc-utils/lsblk-properties.c b/misc-utils/lsblk-properties.c new file mode 100644 index 0000000..dc6f289 --- /dev/null +++ b/misc-utils/lsblk-properties.c @@ -0,0 +1,429 @@ + +#include <blkid.h> + +#ifdef HAVE_LIBUDEV +# include <libudev.h> +#endif + +#include "c.h" +#include "xalloc.h" +#include "mangle.h" +#include "path.h" +#include "nls.h" +#include "strutils.h" + +#include "lsblk.h" + +#ifdef HAVE_LIBUDEV +static struct udev *udev; +#endif + +void lsblk_device_free_properties(struct lsblk_devprop *p) +{ + if (!p) + return; + + free(p->fstype); + free(p->fsversion); + free(p->uuid); + free(p->ptuuid); + free(p->pttype); + free(p->label); + free(p->parttype); + free(p->partuuid); + free(p->partlabel); + free(p->partn); + free(p->wwn); + free(p->serial); + free(p->model); + free(p->partflags); + free(p->idlink); + free(p->revision); + + free(p->mode); + free(p->owner); + free(p->group); + + free(p); +} + +#ifndef HAVE_LIBUDEV +static struct lsblk_devprop *get_properties_by_udev(struct lsblk_device *dev + __attribute__((__unused__))) +{ + return NULL; +} +#else + +#define LSBLK_UDEV_BYID_PREFIX "/dev/disk/by-id/" +#define LSBLK_UDEV_BYID_PREFIXSZ (sizeof(LSBLK_UDEV_BYID_PREFIX) - 1) + +static struct lsblk_devprop *get_properties_by_udev(struct lsblk_device *ld) +{ + struct udev_device *dev; + struct udev_list_entry *le; + const char *data; + struct lsblk_devprop *prop; + size_t len; + + if (ld->udev_requested) + return ld->properties; + + if (!udev) + udev = udev_new(); /* global handler */ + if (!udev) + goto done; + + dev = udev_device_new_from_subsystem_sysname(udev, "block", ld->name); + if (!dev) + goto done; + + DBG(DEV, ul_debugobj(ld, "%s: found udev properties", ld->name)); + + if (ld->properties) + lsblk_device_free_properties(ld->properties); + prop = ld->properties = xcalloc(1, sizeof(*ld->properties)); + + if ((data = udev_device_get_property_value(dev, "ID_FS_LABEL_ENC"))) { + prop->label = xstrdup(data); + unhexmangle_string(prop->label); + } + if ((data = udev_device_get_property_value(dev, "ID_FS_UUID_ENC"))) { + prop->uuid = xstrdup(data); + unhexmangle_string(prop->uuid); + } + if ((data = udev_device_get_property_value(dev, "ID_PART_TABLE_UUID"))) + prop->ptuuid = xstrdup(data); + if ((data = udev_device_get_property_value(dev, "ID_PART_TABLE_TYPE"))) + prop->pttype = xstrdup(data); + if ((data = udev_device_get_property_value(dev, "ID_PART_ENTRY_NAME"))) { + prop->partlabel = xstrdup(data); + unhexmangle_string(prop->partlabel); + } + if ((data = udev_device_get_property_value(dev, "ID_FS_TYPE"))) + prop->fstype = xstrdup(data); + if ((data = udev_device_get_property_value(dev, "ID_FS_VERSION"))) + prop->fsversion = xstrdup(data); + if ((data = udev_device_get_property_value(dev, "ID_PART_ENTRY_TYPE"))) + prop->parttype = xstrdup(data); + if ((data = udev_device_get_property_value(dev, "ID_PART_ENTRY_UUID"))) + prop->partuuid = xstrdup(data); + if ((data = udev_device_get_property_value(dev, "ID_PART_ENTRY_NUMBER"))) + prop->partn = xstrdup(data); + if ((data = udev_device_get_property_value(dev, "ID_PART_ENTRY_FLAGS"))) + prop->partflags = xstrdup(data); + + data = udev_device_get_property_value(dev, "ID_WWN_WITH_EXTENSION"); + if (!data) + data = udev_device_get_property_value(dev, "ID_WWN"); + if (data) + prop->wwn = xstrdup(data); + + data = udev_device_get_property_value(dev, "SCSI_IDENT_SERIAL"); /* sg3_utils do not use I_D prefix */ + if (!data) + data = udev_device_get_property_value(dev, "ID_SCSI_SERIAL"); + if(!data) + data = udev_device_get_property_value(dev, "ID_SERIAL_SHORT"); + if(!data) + data = udev_device_get_property_value(dev, "ID_SERIAL"); + if (data) { + prop->serial = xstrdup(data); + normalize_whitespace((unsigned char *) prop->serial); + } + + if ((data = udev_device_get_property_value(dev, "ID_REVISION"))) + prop->revision = xstrdup(data); + + if ((data = udev_device_get_property_value(dev, "ID_MODEL_ENC"))) { + prop->model = xstrdup(data); + unhexmangle_string(prop->model); + normalize_whitespace((unsigned char *) prop->model); + } else if ((data = udev_device_get_property_value(dev, "ID_MODEL"))) { + prop->model = xstrdup(data); + normalize_whitespace((unsigned char *) prop->model); + } + + /* select the shortest udev by-id symlink */ + len = 0; + udev_list_entry_foreach(le, udev_device_get_devlinks_list_entry(dev)) { + const char *name = udev_list_entry_get_name(le); + size_t sz; + + if (!name || !startswith(name, LSBLK_UDEV_BYID_PREFIX)) + continue; + name += LSBLK_UDEV_BYID_PREFIXSZ; + if (!*name) + continue; + sz = strlen(name); + if (!len || sz < len) { + len = sz; + free(prop->idlink); + prop->idlink = xstrdup(name); + } + } + + udev_device_unref(dev); +done: + ld->udev_requested = 1; + + DBG(DEV, ul_debugobj(ld, " from udev")); + return ld->properties; +} +#endif /* HAVE_LIBUDEV */ + + +static int lookup(char *buf, char *pattern, char **value) +{ + char *p, *v; + int len; + + /* do not re-fill value */ + if (!buf || *value) + return 0; + + len = strlen(pattern); + if (strncmp(buf, pattern, len) != 0) + return 0; + + p = buf + len; + if (*p != '=') + return 0; + p++; + if (!*p || *p == '\n') + return 0; + v = p; + for (; *p && *p != '\n'; p++) ; + if (*p == '\n') + *p = '\0'; + + *value = xstrdup(v); + return 1; +} + +/* read device properties from fake text file (used on --sysroot) */ +static struct lsblk_devprop *get_properties_by_file(struct lsblk_device *ld) +{ + struct lsblk_devprop *prop; + struct path_cxt *pc; + FILE *fp = NULL; + struct stat sb; + char buf[BUFSIZ]; + + assert(lsblk->sysroot); + + if (ld->file_requested) + return ld->properties; + + if (ld->properties || ld->filename) { + lsblk_device_free_properties(ld->properties); + ld->properties = NULL; + } + + pc = ul_new_path("/"); + if (!pc) + return NULL; + if (ul_path_set_prefix(pc, lsblk->sysroot) != 0) + goto done; + if (ul_path_stat(pc, &sb, 0, ld->filename) != 0 || !S_ISREG(sb.st_mode)) + goto done; + + fp = ul_path_fopen(pc, "r", ld->filename); + if (!fp) + goto done; + + prop = ld->properties; + if (!prop) + prop = ld->properties = xcalloc(1, sizeof(*ld->properties)); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + /* udev based */ + if (lookup(buf, "ID_FS_LABEL_ENC", &prop->label)) + unhexmangle_string(prop->label); + else if (lookup(buf, "ID_FS_UUID_ENC", &prop->uuid)) + unhexmangle_string(prop->uuid); + else if (lookup(buf, "ID_PART_ENTRY_NAME", &prop->partlabel)) + unhexmangle_string(prop->partlabel); + else if (lookup(buf, "ID_PART_TABLE_UUID", &prop->ptuuid)) ; + else if (lookup(buf, "ID_PART_TABLE_TYPE", &prop->pttype)) ; + else if (lookup(buf, "ID_FS_TYPE", &prop->fstype)) ; + else if (lookup(buf, "ID_FS_VERSION", &prop->fsversion)) ; + else if (lookup(buf, "ID_PART_ENTRY_TYPE", &prop->parttype)) ; + else if (lookup(buf, "ID_PART_ENTRY_UUID", &prop->partuuid)) ; + else if (lookup(buf, "ID_PART_ENTRY_FLAGS", &prop->partflags)) ; + else if (lookup(buf, "ID_PART_ENTRY_NUMBER", &prop->partn)) ; + else if (lookup(buf, "ID_MODEL", &prop->model)) ; + else if (lookup(buf, "ID_WWN_WITH_EXTENSION", &prop->wwn)) ; + else if (lookup(buf, "ID_WWN", &prop->wwn)) ; + else if (lookup(buf, "SCSI_IDENT_SERIAL", &prop->serial)) ; /* serial from sg3_utils */ + else if (lookup(buf, "ID_SCSI_SERIAL", &prop->serial)) ; + else if (lookup(buf, "ID_SERIAL_SHORT", &prop->serial)) ; + else if (lookup(buf, "ID_SERIAL", &prop->serial)) ; + else if (lookup(buf, "ID_REVISION", &prop->revision)) ; + + /* lsblk specific */ + else if (lookup(buf, "MODE", &prop->mode)) ; + else if (lookup(buf, "OWNER", &prop->owner)) ; + else if (lookup(buf, "GROUP", &prop->group)) ; + + else + continue; + } +done: + if (fp) + fclose(fp); + ul_unref_path(pc); + ld->file_requested = 1; + + DBG(DEV, ul_debugobj(ld, " from fake-file")); + return ld->properties; +} + + +static struct lsblk_devprop *get_properties_by_blkid(struct lsblk_device *dev) +{ + blkid_probe pr = NULL; + + if (dev->blkid_requested) + return dev->properties; + + if (!dev->size) + goto done; + if (getuid() != 0) + goto done;; /* no permissions to read from the device */ + + pr = blkid_new_probe_from_filename(dev->filename); + if (!pr) + goto done; + + blkid_probe_enable_superblocks(pr, 1); + blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_LABEL | + BLKID_SUBLKS_UUID | + BLKID_SUBLKS_TYPE); + blkid_probe_enable_partitions(pr, 1); + blkid_probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS); + + if (!blkid_do_safeprobe(pr)) { + const char *data = NULL; + struct lsblk_devprop *prop; + + if (dev->properties) + lsblk_device_free_properties(dev->properties); + prop = dev->properties = xcalloc(1, sizeof(*dev->properties)); + + if (!blkid_probe_lookup_value(pr, "TYPE", &data, NULL)) + prop->fstype = xstrdup(data); + if (!blkid_probe_lookup_value(pr, "UUID", &data, NULL)) + prop->uuid = xstrdup(data); + if (!blkid_probe_lookup_value(pr, "PTUUID", &data, NULL)) + prop->ptuuid = xstrdup(data); + if (!blkid_probe_lookup_value(pr, "PTTYPE", &data, NULL)) + prop->pttype = xstrdup(data); + if (!blkid_probe_lookup_value(pr, "LABEL", &data, NULL)) + prop->label = xstrdup(data); + if (!blkid_probe_lookup_value(pr, "VERSION", &data, NULL)) + prop->fsversion = xstrdup(data); + if (!blkid_probe_lookup_value(pr, "PART_ENTRY_TYPE", &data, NULL)) + prop->parttype = xstrdup(data); + if (!blkid_probe_lookup_value(pr, "PART_ENTRY_UUID", &data, NULL)) + prop->partuuid = xstrdup(data); + if (!blkid_probe_lookup_value(pr, "PART_ENTRY_NAME", &data, NULL)) + prop->partlabel = xstrdup(data); + if (!blkid_probe_lookup_value(pr, "PART_ENTRY_FLAGS", &data, NULL)) + prop->partflags = xstrdup(data); + if (!blkid_probe_lookup_value(pr, "PART_ENTRY_NUMBER", &data, NULL)) + prop->partn = xstrdup(data); + + DBG(DEV, ul_debugobj(dev, "%s: found blkid properties", dev->name)); + } + +done: + blkid_free_probe(pr); + + DBG(DEV, ul_debugobj(dev, " from blkid")); + dev->blkid_requested = 1; + return dev->properties; +} + +struct lsblk_devprop *lsblk_device_get_properties(struct lsblk_device *dev) +{ + struct lsblk_devprop *p = NULL; + + DBG(DEV, ul_debugobj(dev, "%s: properties requested", dev->filename)); + if (lsblk->sysroot) + return get_properties_by_file(dev); + + p = get_properties_by_udev(dev); + if (!p) + p = get_properties_by_blkid(dev); + return p; +} + +void lsblk_properties_deinit(void) +{ +#ifdef HAVE_LIBUDEV + udev_unref(udev); +#endif +} + + + +/* + * Partition types + */ +struct lsblk_parttype { + unsigned int code; /* type as number or zero */ + char *name; /* description */ + char *typestr; /* type as string or NULL */ +}; + +static const struct lsblk_parttype mbr_types[] = +{ + #include "pt-mbr-partnames.h" +}; + +#define DEF_GUID(_u, _n) \ + { \ + .typestr = (_u), \ + .name = (_n), \ + } +static const struct lsblk_parttype gpt_types[] = +{ + #include "pt-gpt-partnames.h" +}; + +const char *lsblk_parttype_code_to_string(const char *code, const char *pttype) +{ + size_t i; + + if (!code || !pttype) + return NULL; + + if (strcmp(pttype, "dos") == 0 || strcmp(pttype, "mbr") == 0) { + char *end = NULL; + unsigned int xcode; + + errno = 0; + xcode = strtol(code, &end, 16); + + if (errno || *end != '\0') + return NULL; + + for (i = 0; i < ARRAY_SIZE(mbr_types); i++) { + const struct lsblk_parttype *t = &mbr_types[i]; + + if (t->name && t->code == xcode) + return t->name; + } + + } else if (strcmp(pttype, "gpt") == 0) { + for (i = 0; i < ARRAY_SIZE(gpt_types); i++) { + const struct lsblk_parttype *t = &gpt_types[i]; + + if (t->name && t->typestr && + strcasecmp(code, t->typestr) == 0) + return t->name; + } + } + + return NULL; +} diff --git a/misc-utils/lsblk.8 b/misc-utils/lsblk.8 new file mode 100644 index 0000000..f67b96c --- /dev/null +++ b/misc-utils/lsblk.8 @@ -0,0 +1,294 @@ +'\" t +.\" Title: lsblk +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-12-01 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "LSBLK" "8" "2023-12-01" "util\-linux 2.39.3" "System Administration" +.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" +lsblk \- list block devices +.SH "SYNOPSIS" +.sp +\fBlsblk\fP [options] [\fIdevice\fP...] +.SH "DESCRIPTION" +.sp +\fBlsblk\fP lists information about all available or the specified block devices. The \fBlsblk\fP command reads the \fBsysfs\fP filesystem and \fBudev db\fP to gather information. If the udev db is not available or \fBlsblk\fP is compiled without udev support, then it tries to read LABELs, UUIDs and filesystem types from the block device. In this case root permissions are necessary. +.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 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. +.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 +The relationship between block devices and filesystems is not always one\-to\-one. The filesystem may use more block devices, or the same filesystem may be accessible by more paths. This is the reason why \fBlsblk\fP provides MOUNTPOINT and MOUNTPOINTS (pl.) columns. The column MOUNTPOINT displays only one mount point (usually the last mounted instance of the filesystem), and the column MOUNTPOINTS displays by multi\-line cell all mount points associated with the device. +.SH "OPTIONS" +.sp +\fB\-A\fP, \fB\-\-noempty\fP +.RS 4 +Don\(cqt print empty devices. +.RE +.sp +\fB\-a\fP, \fB\-\-all\fP +.RS 4 +Disable all built\-in filters and list all empty devices and RAM disk devices too. +.RE +.sp +\fB\-b\fP, \fB\-\-bytes\fP +.RS 4 +Print the sizes in bytes rather than in a human\-readable format. +.sp +By default, the unit, sizes are expressed in, is byte, and unit prefixes are in +power of 2^10 (1024). Abbreviations of symbols are exhibited truncated in order +to reach a better readability, by exhibiting alone the first letter of them; +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\-D\fP, \fB\-\-discard\fP +.RS 4 +Print information about the discarding capabilities (TRIM, UNMAP) for each device. +.RE +.sp +\fB\-d\fP, \fB\-\-nodeps\fP +.RS 4 +Do not print holder devices or slaves. For example, \fBlsblk \-\-nodeps /dev/sda\fP prints information about the sda device only. +.RE +.sp +\fB\-E\fP, \fB\-\-dedup\fP \fIcolumn\fP +.RS 4 +Use \fIcolumn\fP as a de\-duplication key to de\-duplicate output tree. If the key is not available for the device, or the device is a partition and parental whole\-disk device provides the same key than the device is always printed. +.sp +The usual use case is to de\-duplicate output on system multi\-path devices, for example by \fB\-E WWN\fP. +.RE +.sp +\fB\-e\fP, \fB\-\-exclude\fP \fIlist\fP +.RS 4 +Exclude the devices specified by the comma\-separated \fIlist\fP of major device numbers. Note that RAM disks (major=1) are excluded by default if \fB\-\-all\fP is not specified. The filter is applied to the top\-level devices only. This may be confusing for \fB\-\-list\fP output format where hierarchy of the devices is not obvious. +.RE +.sp +\fB\-f\fP, \fB\-\-fs\fP +.RS 4 +Output info about filesystems. This option is equivalent to \fB\-o NAME,FSTYPE,FSVER,LABEL,UUID,FSAVAIL,FSUSE%,MOUNTPOINTS\fP. The authoritative information about filesystems and raids is provided by the \fBblkid\fP(8) command. +.RE +.sp +\fB\-I\fP, \fB\-\-include\fP \fIlist\fP +.RS 4 +Include devices specified by the comma\-separated \fIlist\fP of major device numbers. The filter is applied to the top\-level devices only. This may be confusing for \fB\-\-list\fP output format where hierarchy of the devices is not obvious. +.RE +.sp +\fB\-i\fP, \fB\-\-ascii\fP +.RS 4 +Use ASCII characters for tree formatting. +.RE +.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. +.RE +.sp +\fB\-l\fP, \fB\-\-list\fP +.RS 4 +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 \fB\-\-pairs\fP or \fB\-\-raw\fP not specified (the parsable outputs are maintained in backwardly compatible way). +.RE +.sp +\fB\-M\fP, \fB\-\-merge\fP +.RS 4 +Group parents of sub\-trees to provide more readable output for RAIDs and Multi\-path devices. The tree\-like output is required. +.RE +.sp +\fB\-m\fP, \fB\-\-perms\fP +.RS 4 +Output info about device owner, group and mode. This option is equivalent to \fB\-o NAME,SIZE,OWNER,GROUP,MODE\fP. +.RE +.sp +\fB\-N\fP, \fB\-\-nvme\fP +.RS 4 +Output info about NVMe devices only. +.RE +.sp +\fB\-v\fP, \fB\-\-virtio\fP +.RS 4 +Output info about virtio devices only. +.RE +.sp +\fB\-n\fP, \fB\-\-noheadings\fP +.RS 4 +Do not print a header line. +.RE +.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). +.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 +.sp +\fB\-O\fP, \fB\-\-output\-all\fP +.RS 4 +Output all available columns. +.RE +.sp +\fB\-P\fP, \fB\-\-pairs\fP +.RS 4 +Produce output in the form of key="value" pairs. The output lines are still ordered by dependencies. All potentially unsafe value characters are hex\-escaped (\(rsx<code>). See also option \fB\-\-shell\fP. +.RE +.sp +\fB\-p\fP, \fB\-\-paths\fP +.RS 4 +Print full device paths. +.RE +.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. +.RE +.sp +\fB\-S\fP, \fB\-\-scsi\fP +.RS 4 +Output info about SCSI devices only. All partitions, slaves and holder devices are ignored. +.RE +.sp +\fB\-s\fP, \fB\-\-inverse\fP +.RS 4 +Print dependencies in inverse order. If the \fB\-\-list\fP output is requested then the lines are still ordered by dependencies. +.RE +.sp +\fB\-T\fP, \fB\-\-tree\fP[\fB=\fP\fIcolumn\fP] +.RS 4 +Force tree\-like output format. If \fIcolumn\fP is specified, then a tree is printed in the column. The default is NAME column. +.RE +.sp +\fB\-t\fP, \fB\-\-topology\fP +.RS 4 +Output info about block\-device topology. This option is equivalent to +.sp +\fB\-o NAME,ALIGNMENT,MIN\-IO,OPT\-IO,PHY\-SEC,LOG\-SEC,ROTA,SCHED,RQ\-SIZE,RA,WSAME\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 +.sp +\fB\-w\fP, \fB\-\-width\fP \fInumber\fP +.RS 4 +Specifies output width as a number of characters. The default is the number of the terminal columns, and if not executed on a terminal, then output width is not restricted at all by default. This option also forces \fBlsblk\fP to assume that terminal control characters and unsafe characters are not allowed. The expected use\-case is for example when \fBlsblk\fP is used by the \fBwatch\fP(1) command. +.RE +.sp +\fB\-x\fP, \fB\-\-sort\fP \fIcolumn\fP +.RS 4 +Sort output lines by \fIcolumn\fP. This option enables \fB\-\-list\fP output format by default. It is possible to use the option \fB\-\-tree\fP to force tree\-like output and than the tree branches are sorted by the \fIcolumn\fP. +.RE +.sp +\fB\-y\fP, \fB\-\-shell\fP +.RS 4 +The column name will be modified to contain only characters allowed for shell variable identifiers, for example, MIN_IO and FSUSE_PCT instead of MIN\-IO and FSUSE%. This is usable, for example, with \fB\-\-pairs\fP. Note that this feature has been automatically enabled for \fB\-\-pairs\fP in version 2.37, but due to compatibility issues, now it\(cqs necessary to request this behavior by \fB\-\-shell\fP. +.RE +.sp +\fB\-z\fP, \fB\-\-zoned\fP +.RS 4 +Print the zone related information for each device. +.RE +.sp +\fB\-\-sysroot\fP \fIdirectory\fP +.RS 4 +Gather data for a Linux instance other than the instance from which the \fBlsblk\fP command is issued. The specified directory is the system root of the Linux instance to be inspected. The real device nodes in the target directory can be replaced by text files with udev attributes. +.RE +.SH "EXIT STATUS" +.sp +0 +.RS 4 +success +.RE +.sp +1 +.RS 4 +failure +.RE +.sp +32 +.RS 4 +none of specified devices found +.RE +.sp +64 +.RS 4 +some specified devices found, some not found +.RE +.SH "ENVIRONMENT" +.sp +\fBLSBLK_DEBUG\fP=all +.RS 4 +enables \fBlsblk\fP debug output. +.RE +.sp +\fBLIBBLKID_DEBUG\fP=all +.RS 4 +enables \fBlibblkid\fP debug output. +.RE +.sp +\fBLIBMOUNT_DEBUG\fP=all +.RS 4 +enables \fBlibmount\fP debug output. +.RE +.sp +\fBLIBSMARTCOLS_DEBUG\fP=all +.RS 4 +enables \fBlibsmartcols\fP debug output. +.RE +.sp +\fBLIBSMARTCOLS_DEBUG_PADDING\fP=on +.RS 4 +use visible padding characters. +.RE +.SH "NOTES" +.sp +For partitions, some information (e.g., queue attributes) is inherited from the parent device. +.sp +The \fBlsblk\fP command needs to be able to look up each block device by major:minor numbers, which is done by using \fI/sys/dev/block\fP. This sysfs block directory appeared in kernel 2.6.27 (October 2008). In case of problems with a new enough kernel, check that \fBCONFIG_SYSFS\fP was enabled at the time of the kernel build. +.SH "AUTHORS" +.sp +.MTO "gmazyland\(atgmail.com" "Milan Broz" "," +.MTO "kzak\(atredhat.com" "Karel Zak" "" +.SH "SEE ALSO" +.sp +\fBls\fP(1), +\fBblkid\fP(8), +\fBfindmnt\fP(8) +.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 \fBlsblk\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/lsblk.8.adoc b/misc-utils/lsblk.8.adoc new file mode 100644 index 0000000..d4b13f2 --- /dev/null +++ b/misc-utils/lsblk.8.adoc @@ -0,0 +1,188 @@ +//po4a: entry man manual += lsblk(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: lsblk + +== NAME + +lsblk - list block devices + +== SYNOPSIS + +*lsblk* [options] [_device_...] + +== DESCRIPTION + +*lsblk* lists information about all available or the specified block devices. The *lsblk* command reads the *sysfs* filesystem and *udev db* to gather information. If the udev db is not available or *lsblk* is compiled without udev support, then it tries to read LABELs, UUIDs and filesystem types from the block device. In this case root permissions are necessary. + +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 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. + +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. + +The relationship between block devices and filesystems is not always one-to-one. The filesystem may use more block devices, or the same filesystem may be accessible by more paths. This is the reason why *lsblk* provides MOUNTPOINT and MOUNTPOINTS (pl.) columns. The column MOUNTPOINT displays only one mount point (usually the last mounted instance of the filesystem), and the column MOUNTPOINTS displays by multi-line cell all mount points associated with the device. + +== OPTIONS + +*-A*, *--noempty*:: +Don't print empty devices. + +*-a*, *--all*:: +Disable all built-in filters and list all empty devices and RAM disk devices too. + +*-b*, *--bytes*:: +include::man-common/in-bytes.adoc[] + +*-D*, *--discard*:: +Print information about the discarding capabilities (TRIM, UNMAP) for each device. + +*-d*, *--nodeps*:: +Do not print holder devices or slaves. For example, *lsblk --nodeps /dev/sda* prints information about the sda device only. + +*-E*, *--dedup* _column_:: +Use _column_ as a de-duplication key to de-duplicate output tree. If the key is not available for the device, or the device is a partition and parental whole-disk device provides the same key than the device is always printed. ++ +The usual use case is to de-duplicate output on system multi-path devices, for example by *-E WWN*. + +*-e*, *--exclude* _list_:: +Exclude the devices specified by the comma-separated _list_ of major device numbers. Note that RAM disks (major=1) are excluded by default if *--all* is not specified. The filter is applied to the top-level devices only. This may be confusing for *--list* output format where hierarchy of the devices is not obvious. + +*-f*, *--fs*:: +Output info about filesystems. This option is equivalent to *-o NAME,FSTYPE,FSVER,LABEL,UUID,FSAVAIL,FSUSE%,MOUNTPOINTS*. The authoritative information about filesystems and raids is provided by the *blkid*(8) command. + +*-I*, *--include* _list_:: +Include devices specified by the comma-separated _list_ of major device numbers. The filter is applied to the top-level devices only. This may be confusing for *--list* output format where hierarchy of the devices is not obvious. + +*-i*, *--ascii*:: +Use ASCII characters for tree formatting. + +*-J*, *--json*:: +Use JSON output format. It's strongly recommended to use *--output* and also *--tree* if necessary. + +*-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). + +*-M*, *--merge*:: +Group parents of sub-trees to provide more readable output for RAIDs and Multi-path devices. The tree-like output is required. + +*-m*, *--perms*:: +Output info about device owner, group and mode. This option is equivalent to *-o NAME,SIZE,OWNER,GROUP,MODE*. + +*-N*, *--nvme*:: +Output info about NVMe devices only. + +*-v*, *--virtio*:: +Output info about virtio devices only. + +*-n*, *--noheadings*:: +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*). ++ +The default list of columns may be extended if _list_ is specified in the format _+list_ (e.g., *lsblk -o +UUID*). + +*-O*, *--output-all*:: +Output all available columns. + +*-P*, *--pairs*:: +Produce output in the form of key="value" pairs. The output lines are still ordered by dependencies. All potentially unsafe value characters are hex-escaped (\x<code>). See also option *--shell*. + +*-p*, *--paths*:: +Print full device paths. + +*-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. + +*-S*, *--scsi*:: +Output info about SCSI devices only. All partitions, slaves and holder devices are ignored. + +*-s*, *--inverse*:: +Print dependencies in inverse order. If the *--list* output is requested then the lines are still ordered by dependencies. + +*-T*, *--tree*[**=**__column__]:: +Force tree-like output format. If _column_ is specified, then a tree is printed in the column. The default is NAME column. + +*-t*, *--topology*:: +Output info about block-device topology. This option is equivalent to ++ +*-o NAME,ALIGNMENT,MIN-IO,OPT-IO,PHY-SEC,LOG-SEC,ROTA,SCHED,RQ-SIZE,RA,WSAME*. + +include::man-common/help-version.adoc[] + +*-w*, *--width* _number_:: +Specifies output width as a number of characters. The default is the number of the terminal columns, and if not executed on a terminal, then output width is not restricted at all by default. This option also forces *lsblk* to assume that terminal control characters and unsafe characters are not allowed. The expected use-case is for example when *lsblk* is used by the *watch*(1) command. + +*-x*, *--sort* _column_:: +Sort output lines by _column_. This option enables *--list* output format by default. It is possible to use the option *--tree* to force tree-like output and than the tree branches are sorted by the _column_. + +*-y*, *--shell*:: +The column name will be modified to contain only characters allowed for shell variable identifiers, for example, MIN_IO and FSUSE_PCT instead of MIN-IO and FSUSE%. This is usable, for example, with *--pairs*. Note that this feature has been automatically enabled for *--pairs* in version 2.37, but due to compatibility issues, now it's necessary to request this behavior by *--shell*. + +*-z*, *--zoned*:: +Print the zone related information for each device. + +*--sysroot* _directory_:: +Gather data for a Linux instance other than the instance from which the *lsblk* command is issued. The specified directory is the system root of the Linux instance to be inspected. The real device nodes in the target directory can be replaced by text files with udev attributes. + +== EXIT STATUS + +0:: +success + +1:: +failure + +32:: +none of specified devices found + +64:: +some specified devices found, some not found + +== ENVIRONMENT + +*LSBLK_DEBUG*=all:: +enables *lsblk* debug output. + +*LIBBLKID_DEBUG*=all:: +enables *libblkid* debug output. + +*LIBMOUNT_DEBUG*=all:: +enables *libmount* debug output. + +*LIBSMARTCOLS_DEBUG*=all:: +enables *libsmartcols* debug output. + +*LIBSMARTCOLS_DEBUG_PADDING*=on:: +use visible padding characters. + +== NOTES + +For partitions, some information (e.g., queue attributes) is inherited from the parent device. + +The *lsblk* command needs to be able to look up each block device by major:minor numbers, which is done by using _/sys/dev/block_. This sysfs block directory appeared in kernel 2.6.27 (October 2008). In case of problems with a new enough kernel, check that *CONFIG_SYSFS* was enabled at the time of the kernel build. + +== AUTHORS + +mailto:gmazyland@gmail.com[Milan Broz], +mailto:kzak@redhat.com[Karel Zak] + +== SEE ALSO + +*ls*(1), +*blkid*(8), +*findmnt*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/lsblk.c b/misc-utils/lsblk.c new file mode 100644 index 0000000..fae918b --- /dev/null +++ b/misc-utils/lsblk.c @@ -0,0 +1,2486 @@ +/* + * lsblk(8) - list block devices + * + * Copyright (C) 2010-2018 Red Hat, Inc. All rights reserved. + * Written by Milan Broz <gmazyland@gmail.com> + * Karel Zak <kzak@redhat.com> + * + * 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 <errno.h> +#include <getopt.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <fcntl.h> +#include <string.h> +#include <sys/ioctl.h> +#include <stdarg.h> +#include <locale.h> +#include <pwd.h> +#include <grp.h> +#include <ctype.h> +#include <assert.h> + +#include <blkid.h> + +#include "c.h" +#include "pathnames.h" +#include "blkdev.h" +#include "canonicalize.h" +#include "nls.h" +#include "xalloc.h" +#include "strutils.h" +#include "sysfs.h" +#include "closestream.h" +#include "optutils.h" +#include "fileutils.h" +#include "loopdev.h" +#include "buffer.h" + +#include "lsblk.h" + +UL_DEBUG_DEFINE_MASK(lsblk); +UL_DEBUG_DEFINE_MASKNAMES(lsblk) = UL_DEBUG_EMPTY_MASKNAMES; + +#define LSBLK_EXIT_SOMEOK 64 +#define LSBLK_EXIT_ALLFAILED 32 + +static int column_id_to_number(int id); + +/* column IDs */ +enum { + COL_ALIOFF = 0, + COL_IDLINK, + COL_ID, + COL_DALIGN, + COL_DAX, + COL_DGRAN, + COL_DISKSEQ, + COL_DMAX, + COL_DZERO, + COL_FSAVAIL, + COL_FSROOTS, + COL_FSSIZE, + COL_FSTYPE, + COL_FSUSED, + COL_FSUSEPERC, + COL_FSVERSION, + COL_GROUP, + COL_HCTL, + COL_HOTPLUG, + COL_KNAME, + COL_LABEL, + COL_LOGSEC, + COL_MAJMIN, + COL_MINIO, + COL_MODE, + COL_MODEL, + COL_MQ, + COL_NAME, + COL_OPTIO, + COL_OWNER, + COL_PARTFLAGS, + COL_PARTLABEL, + COL_PARTN, + COL_PARTTYPE, + COL_PARTTYPENAME, + COL_PARTUUID, + COL_PATH, + COL_PHYSEC, + COL_PKNAME, + COL_PTTYPE, + COL_PTUUID, + COL_RA, + COL_RAND, + COL_REV, + COL_RM, + COL_RO, + COL_ROTA, + COL_RQ_SIZE, + COL_SCHED, + COL_SERIAL, + COL_SIZE, + COL_START, + COL_STATE, + COL_SUBSYS, + COL_TARGET, + COL_TARGETS, + COL_TRANSPORT, + COL_TYPE, + COL_UUID, + COL_VENDOR, + COL_WSAME, + COL_WWN, + COL_ZONED, + COL_ZONE_SZ, + COL_ZONE_WGRAN, + COL_ZONE_APP, + COL_ZONE_NR, + COL_ZONE_OMAX, + COL_ZONE_AMAX, +}; + +/* basic table settings */ +enum { + LSBLK_ASCII = (1 << 0), + LSBLK_RAW = (1 << 1), + LSBLK_NOHEADINGS = (1 << 2), + LSBLK_EXPORT = (1 << 3), + LSBLK_TREE = (1 << 4), + LSBLK_JSON = (1 << 5), + LSBLK_SHELLVAR = (1 << 6) +}; + +/* Types used for qsort() and JSON */ +enum { + COLTYPE_STR = 0, /* default */ + COLTYPE_NUM = 1, /* always u64 number */ + COLTYPE_SORTNUM = 2, /* string on output, u64 for qsort() */ + COLTYPE_SIZE = 3, /* srring by default, number when --bytes */ + COLTYPE_BOOL = 4 /* 0 or 1 */ +}; + +/* column names */ +struct colinfo { + const char *name; /* header */ + double whint; /* width hint (N < 1 is in percent of termwidth) */ + int flags; /* SCOLS_FL_* */ + const char *help; + int type; /* COLTYPE_* */ +}; + +/* columns descriptions */ +static 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_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_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_FSROOTS] = { "FSROOTS", 0.1, SCOLS_FL_WRAP, N_("mounted filesystem roots") }, + [COL_FSSIZE] = { "FSSIZE", 5, SCOLS_FL_RIGHT, N_("filesystem size"), 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_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") }, + [COL_HCTL] = { "HCTL", 10, 0, N_("Host:Channel:Target:Lun for SCSI") }, + [COL_HOTPLUG] = { "HOTPLUG", 1, SCOLS_FL_RIGHT, N_("removable or hotplug device (usb, pcmcia, ...)"), COLTYPE_BOOL }, + [COL_KNAME] = { "KNAME", 0.3, 0, N_("internal kernel device name") }, + [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_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") }, + [COL_MQ] = { "MQ", 3, SCOLS_FL_RIGHT, N_("device queues") }, + [COL_NAME] = { "NAME", 0.25, SCOLS_FL_NOEXTREMES, N_("device name") }, + [COL_OPTIO] = { "OPT-IO", 6, SCOLS_FL_RIGHT, N_("optimal I/O size"), COLTYPE_NUM }, + [COL_OWNER] = { "OWNER", 0.1, SCOLS_FL_TRUNC, N_("user name"), }, + [COL_PARTFLAGS] = { "PARTFLAGS", 36, 0, N_("partition flags") }, + [COL_PARTLABEL] = { "PARTLABEL", 0.1, 0, N_("partition LABEL") }, + [COL_PARTN] = { "PARTN", 2, SCOLS_FL_RIGHT, N_("partition number as read from the partition table"), COLTYPE_NUM }, + [COL_PARTTYPENAME] = { "PARTTYPENAME", 0.1, 0, N_("partition type name") }, + [COL_PARTTYPE] = { "PARTTYPE", 36, 0, N_("partition type code or UUID") }, + [COL_PARTUUID] = { "PARTUUID", 36, 0, N_("partition UUID") }, + [COL_PATH] = { "PATH", 0.3, 0, N_("path to the device node") }, + [COL_PHYSEC] = { "PHY-SEC", 7, SCOLS_FL_RIGHT, N_("physical sector size"), COLTYPE_NUM }, + [COL_PKNAME] = { "PKNAME", 0.3, 0, N_("internal parent kernel device name") }, + [COL_PTTYPE] = { "PTTYPE", 0.1, 0, N_("partition table type") }, + [COL_PTUUID] = { "PTUUID", 36, 0, N_("partition table identifier (usually UUID)") }, + [COL_RAND] = { "RAND", 1, SCOLS_FL_RIGHT, N_("adds randomness"), COLTYPE_BOOL }, + [COL_RA] = { "RA", 3, SCOLS_FL_RIGHT, N_("read-ahead of the device"), COLTYPE_NUM }, + [COL_REV] = { "REV", 4, SCOLS_FL_RIGHT, N_("device revision") }, + [COL_RM] = { "RM", 1, SCOLS_FL_RIGHT, N_("removable device"), COLTYPE_BOOL }, + [COL_ROTA] = { "ROTA", 1, SCOLS_FL_RIGHT, N_("rotational device"), COLTYPE_BOOL }, + [COL_RO] = { "RO", 1, SCOLS_FL_RIGHT, N_("read-only device"), COLTYPE_BOOL }, + [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_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") }, + [COL_TARGET] = { "MOUNTPOINT", 0.10, SCOLS_FL_TRUNC | SCOLS_FL_NOEXTREMES, N_("where the device is mounted") }, + [COL_TRANSPORT] = { "TRAN", 6, 0, N_("device transport type") }, + [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_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_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 }, +}; + +struct lsblk *lsblk; /* global handler */ + +/* + * columns[] array specifies all currently wanted output column. The columns + * are defined by infos[] array and you can specify (on command line) each + * column twice. That's enough, dynamically allocated array of the columns is + * unnecessary overkill and over-engineering in this case + */ +static int columns[ARRAY_SIZE(infos) * 2]; +static size_t ncolumns; + +static inline void add_column(int id) +{ + if (ncolumns >= ARRAY_SIZE(columns)) + errx(EXIT_FAILURE, _("too many columns specified, " + "the limit is %zu columns"), + ARRAY_SIZE(columns) - 1); + columns[ ncolumns++ ] = id; +} + +static inline void add_uniq_column(int id) +{ + if (column_id_to_number(id) < 0) + add_column(id); +} + +static void lsblk_init_debug(void) +{ + __UL_INIT_DEBUG_FROM_ENV(lsblk, LSBLK_DEBUG_, 0, LSBLK_DEBUG); +} + +/* + * exclude/include devices filter based on major device numbers + */ +static int excludes[256]; +static size_t nexcludes; + +static int includes[256]; +static size_t nincludes; + +static int is_maj_excluded(int maj) +{ + size_t i; + + assert(ARRAY_SIZE(excludes) > nexcludes); + + if (!nexcludes) + return 0; /* filter not enabled, device not excluded */ + + for (i = 0; i < nexcludes; i++) { + if (excludes[i] == maj) { + DBG(FILTER, ul_debug("exclude: maj=%d", maj)); + return 1; + } + } + return 0; +} + +static int is_maj_included(int maj) +{ + size_t i; + + assert(ARRAY_SIZE(includes) > nincludes); + + if (!nincludes) + return 1; /* filter not enabled, device is included */ + + for (i = 0; i < nincludes; i++) { + if (includes[i] == maj) { + DBG(FILTER, ul_debug("include: maj=%d", maj)); + return 1; + } + } + return 0; +} + +/* Converts column sequential number to column ID (COL_*) */ +static int get_column_id(int num) +{ + assert(num >= 0); + assert((size_t) num < ncolumns); + assert(columns[num] < (int) ARRAY_SIZE(infos)); + return columns[num]; +} + +/* Returns column description for the column sequential number */ +static struct colinfo *get_column_info(int num) +{ + return &infos[ get_column_id(num) ]; +} + +/* Converts column name (as defined in the infos[] to the column ID */ +static int column_name_to_id(const char *name, size_t namesz) +{ + size_t i; + + 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; +} + +/* Converts column ID (COL_*) to column sequential number */ +static int column_id_to_number(int id) +{ + size_t i; + + for (i = 0; i < ncolumns; i++) + if (columns[i] == id) + return i; + return -1; +} + +/* Checks for DM prefix in the device name */ +static int is_dm(const char *name) +{ + return strncmp(name, "dm-", 3) ? 0 : 1; +} + +/* Returns full pat to the device node (TODO: what about sysfs_blkdev_get_path()) */ +static char *get_device_path(struct lsblk_device *dev) +{ + char path[PATH_MAX]; + + assert(dev); + assert(dev->name); + + if (is_dm(dev->name)) + return __canonicalize_dm_name(lsblk->sysroot, dev->name); + + snprintf(path, sizeof(path), "/dev/%s", dev->name); + sysfs_devname_sys_to_dev(path); + return xstrdup(path); +} + +static int is_readonly_device(struct lsblk_device *dev) +{ + int fd, ro = 0; + + if (ul_path_scanf(dev->sysfs, "ro", "%d", &ro) == 1) + return ro; + + /* fallback if "ro" attribute does not exist */ + fd = open(dev->filename, O_RDONLY); + if (fd != -1) { + if (ioctl(fd, BLKROGET, &ro) != 0) + ro = 0; + close(fd); + } + return ro; +} + +static char *get_scheduler(struct lsblk_device *dev) +{ + char buf[128]; + char *p, *res = NULL; + + if (ul_path_read_buffer(dev->sysfs, buf, sizeof(buf), "queue/scheduler") == 0) + return NULL; + p = strchr(buf, '['); + if (p) { + res = p + 1; + p = strchr(res, ']'); + if (p) { + *p = '\0'; + res = xstrdup(res); + } else + res = NULL; + } + return res; +} + +static char *get_type(struct lsblk_device *dev) +{ + char *res = NULL, *p; + + if (device_is_partition(dev)) + return xstrdup("part"); + + if (is_dm(dev->name)) { + char *dm_uuid = NULL; + + /* The DM_UUID prefix should be set to subsystem owning + * the device - LVM, CRYPT, DMRAID, MPATH, PART */ + if (ul_path_read_string(dev->sysfs, &dm_uuid, "dm/uuid") > 0 + && dm_uuid) { + char *tmp = dm_uuid; + char *dm_uuid_prefix = strsep(&tmp, "-"); + + if (dm_uuid_prefix) { + /* kpartx hack to remove partition number */ + if (strncasecmp(dm_uuid_prefix, "part", 4) == 0) + dm_uuid_prefix[4] = '\0'; + + res = xstrdup(dm_uuid_prefix); + } + } + + free(dm_uuid); + if (!res) + /* No UUID or no prefix - just mark it as DM device */ + res = xstrdup("dm"); + + } else if (!strncmp(dev->name, "loop", 4)) { + res = xstrdup("loop"); + + } else if (!strncmp(dev->name, "md", 2)) { + char *md_level = NULL; + + ul_path_read_string(dev->sysfs, &md_level, "md/level"); + res = md_level ? md_level : xstrdup("md"); + + } else { + const char *type = NULL; + int x = 0; + + if (ul_path_read_s32(dev->sysfs, &x, "device/type") == 0) + type = blkdev_scsi_type_to_name(x); + if (!type) + type = "disk"; + res = xstrdup(type); + } + + for (p = res; p && *p; p++) + *p = tolower((unsigned char) *p); + return res; +} + +/* Thanks to lsscsi code for idea of detection logic used here */ +static const char *get_transport(struct lsblk_device *dev) +{ + struct path_cxt *sysfs = dev->sysfs; + char *attr = NULL; + const char *trans = NULL; + + + /* SCSI - Serial Peripheral Interface */ + if (sysfs_blkdev_scsi_host_is(sysfs, "spi")) + trans = "spi"; + + /* FC/FCoE - Fibre Channel / Fibre Channel over Ethernet */ + else if (sysfs_blkdev_scsi_host_is(sysfs, "fc")) { + attr = sysfs_blkdev_scsi_host_strdup_attribute(sysfs, "fc", "symbolic_name"); + if (!attr) + return NULL; + trans = strstr(attr, " over ") ? "fcoe" : "fc"; + free(attr); + } + + /* SAS - Serial Attached SCSI */ + else if (sysfs_blkdev_scsi_host_is(sysfs, "sas") || + sysfs_blkdev_scsi_has_attribute(sysfs, "sas_device")) + trans = "sas"; + + + /* SBP - Serial Bus Protocol (FireWire) */ + else if (sysfs_blkdev_scsi_has_attribute(sysfs, "ieee1394_id")) + trans = "sbp"; + + /* iSCSI */ + else if (sysfs_blkdev_scsi_host_is(sysfs, "iscsi")) + trans ="iscsi"; + + /* USB - Universal Serial Bus */ + else if (sysfs_blkdev_scsi_path_contains(sysfs, "usb")) + trans = "usb"; + + /* ATA, SATA */ + else if (sysfs_blkdev_scsi_host_is(sysfs, "scsi")) { + attr = sysfs_blkdev_scsi_host_strdup_attribute(sysfs, "scsi", "proc_name"); + if (!attr) + return NULL; + if (!strncmp(attr, "ahci", 4) || !strncmp(attr, "sata", 4)) + trans = "sata"; + else if (strstr(attr, "ata")) + trans = "ata"; + free(attr); + + } else if (strncmp(dev->name, "nvme", 4) == 0) { + trans = "nvme"; + } else if (strncmp(dev->name, "vd", 2) == 0) + trans = "virtio"; + else if (strncmp(dev->name, "mmcblk", 6) == 0) + trans = "mmc"; + + return trans; +} + +static char *get_subsystems(struct lsblk_device *dev) +{ + char path[PATH_MAX]; + char *sub, *chain, *res = NULL; + size_t len = 0, last = 0; + + chain = sysfs_blkdev_get_devchain(dev->sysfs, path, sizeof(path)); + if (!chain) + return NULL; + + while (sysfs_blkdev_next_subsystem(dev->sysfs, chain, &sub) == 0) { + size_t sz; + + /* don't create "block:scsi:scsi", but "block:scsi" */ + if (len && strcmp(res + last, sub) == 0) { + free(sub); + continue; + } + + sz = strlen(sub); + res = xrealloc(res, len + sz + 2); + if (len) + res[len++] = ':'; + + memcpy(res + len, sub, sz + 1); + last = len; + len += sz; + free(sub); + } + + return res; +} + + +#define is_parsable(_l) (scols_table_is_raw((_l)->table) || \ + scols_table_is_export((_l)->table) || \ + scols_table_is_json((_l)->table)) + +static char *mk_name(const char *name) +{ + char *p; + if (!name) + return NULL; + if (lsblk->paths) + xasprintf(&p, "/dev/%s", name); + else + p = xstrdup(name); + if (p) + sysfs_devname_sys_to_dev(p); + return p; +} + +static char *mk_dm_name(const char *name) +{ + char *p; + if (!name) + return NULL; + if (lsblk->paths) + xasprintf(&p, "/dev/mapper/%s", name); + else + p = xstrdup(name); + return p; +} + +/* stores data to scols cell userdata (invisible and independent on output) + * to make the original values accessible for sort functions + */ +static void set_sortdata_u64(struct libscols_line *ln, int col, uint64_t x) +{ + struct libscols_cell *ce = scols_line_get_cell(ln, col); + uint64_t *data; + + if (!ce) + return; + data = xmalloc(sizeof(uint64_t)); + *data = x; + scols_cell_set_userdata(ce, data); +} + +/* do not modify *data on any error */ +static void str2u64(const char *str, uint64_t *data) +{ + uintmax_t num; + char *end = NULL; + + errno = 0; + if (str == NULL || *str == '\0') + return; + num = strtoumax(str, &end, 10); + + if (errno || str == end || (end && *end)) + return; + *data = num; +} + +static void unref_sortdata(struct libscols_table *tb) +{ + struct libscols_iter *itr; + struct libscols_line *ln; + + if (!tb || !lsblk->sort_col) + return; + itr = scols_new_iter(SCOLS_ITER_FORWARD); + if (!itr) + return; + while (scols_table_next_line(tb, itr, &ln) == 0) { + struct libscols_cell *ce = scols_line_get_column_cell(ln, + lsblk->sort_col); + void *data = scols_cell_get_userdata(ce); + free(data); + } + + scols_free_iter(itr); +} + +static char *get_vfs_attribute(struct lsblk_device *dev, int id) +{ + char *sizestr; + uint64_t vfs_attr = 0; + + if (!dev->fsstat.f_blocks) { + const char *mnt = lsblk_device_get_mountpoint(dev); + if (!mnt || dev->is_swap) + return NULL; + if (statvfs(mnt, &dev->fsstat) != 0) + return NULL; + } + + switch(id) { + case COL_FSSIZE: + vfs_attr = dev->fsstat.f_frsize * dev->fsstat.f_blocks; + break; + case COL_FSAVAIL: + vfs_attr = dev->fsstat.f_frsize * dev->fsstat.f_bavail; + break; + case COL_FSUSED: + vfs_attr = dev->fsstat.f_frsize * (dev->fsstat.f_blocks - dev->fsstat.f_bfree); + break; + case COL_FSUSEPERC: + if (dev->fsstat.f_blocks == 0) + return xstrdup("-"); + + xasprintf(&sizestr, "%.0f%%", + (double)(dev->fsstat.f_blocks - dev->fsstat.f_bfree) / + dev->fsstat.f_blocks * 100); + return sizestr; + } + + if (!vfs_attr) + sizestr = xstrdup("0"); + else if (lsblk->bytes) + xasprintf(&sizestr, "%ju", vfs_attr); + else + sizestr = size_to_human_string(SIZE_SUFFIX_1LETTER, vfs_attr); + + return sizestr; +} + +static struct stat *device_get_stat(struct lsblk_device *dev) +{ + if (!dev->st.st_rdev + && stat(dev->filename, &dev->st) != 0) + return NULL; + + return &dev->st; +} + +static int is_removable_device(struct lsblk_device *dev, struct lsblk_device *parent) +{ + struct path_cxt *pc; + + if (dev->removable != -1) + goto done; + + dev->removable = sysfs_blkdev_is_removable(dev->sysfs); + + if (!dev->removable && parent) { + pc = sysfs_blkdev_get_parent(dev->sysfs); + if (!pc) + goto done; + + if (pc == parent->sysfs) + /* dev is partition and parent is whole-disk */ + dev->removable = is_removable_device(parent, NULL); + else + /* parent is something else, use sysfs parent */ + dev->removable = sysfs_blkdev_is_removable(pc); + } +done: + if (dev->removable == -1) + dev->removable = 0; + return dev->removable; +} + +static uint64_t device_get_discard_granularity(struct lsblk_device *dev) +{ + if (dev->discard_granularity == (uint64_t) -1 + && ul_path_read_u64(dev->sysfs, &dev->discard_granularity, + "queue/discard_granularity") != 0) + dev->discard_granularity = 0; + + return dev->discard_granularity; +} + +static void device_read_bytes(struct lsblk_device *dev, char *path, char **str, + uint64_t *sortdata) +{ + uint64_t x; + + if (lsblk->bytes) { + ul_path_read_string(dev->sysfs, str, path); + if (sortdata) + str2u64(*str, sortdata); + return; + } + + if (ul_path_read_u64(dev->sysfs, &x, path) == 0) { + *str = size_to_human_string(SIZE_SUFFIX_1LETTER, x); + if (sortdata) + *sortdata = x; + } +} + +static void process_mq(struct lsblk_device *dev, char **str) +{ + unsigned int queues = 0; + + DBG(DEV, ul_debugobj(dev, "%s: process mq", dev->name)); + + queues = ul_path_count_dirents(dev->sysfs, "mq"); + if (!queues) { + *str = xstrdup("1"); + DBG(DEV, ul_debugobj(dev, "%s: no mq supported, use a single queue", dev->name)); + return; + } + + DBG(DEV, ul_debugobj(dev, "%s: has %d queues", dev->name, queues)); + xasprintf(str, "%3u", queues); +} + +/* + * Generates data (string) for column specified by column ID for specified device. If sortdata + * is not NULL then returns number usable to sort the column if the data are available for the + * column. + */ +static char *device_get_data( + struct lsblk_device *dev, /* device */ + struct lsblk_device *parent, /* device parent as defined in the tree */ + int id, /* column ID (COL_*) */ + uint64_t *sortdata) /* returns sort data as number */ +{ + struct lsblk_devprop *prop = NULL; + char *str = NULL; + + switch(id) { + case COL_NAME: + str = dev->dm_name ? mk_dm_name(dev->dm_name) : mk_name(dev->name); + break; + case COL_KNAME: + str = mk_name(dev->name); + break; + case COL_PKNAME: + if (parent) + str = mk_name(parent->name); + break; + case COL_PATH: + if (dev->filename) + str = xstrdup(dev->filename); + break; + case COL_OWNER: + if (lsblk->sysroot) + prop = lsblk_device_get_properties(dev); + if (prop && prop->owner) { + str = xstrdup(prop->owner); + } else { + struct stat *st = device_get_stat(dev); + struct passwd *pw = st ? getpwuid(st->st_uid) : NULL; + if (pw) + str = xstrdup(pw->pw_name); + } + break; + case COL_GROUP: + if (lsblk->sysroot) + prop = lsblk_device_get_properties(dev); + if (prop && prop->group) { + str = xstrdup(prop->group); + } else { + struct stat *st = device_get_stat(dev); + struct group *gr = st ? getgrgid(st->st_gid) : NULL; + if (gr) + str = xstrdup(gr->gr_name); + } + break; + case COL_MODE: + if (lsblk->sysroot) + prop = lsblk_device_get_properties(dev); + if (prop && prop->mode) { + str = xstrdup(prop->mode); + } else { + struct stat *st = device_get_stat(dev); + char md[11] = { '\0' }; + + if (st) + str = xstrdup(xstrmode(st->st_mode, md)); + } + break; + case COL_MAJMIN: + if (is_parsable(lsblk)) + 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); + break; + case COL_FSTYPE: + prop = lsblk_device_get_properties(dev); + if (prop && prop->fstype) + str = xstrdup(prop->fstype); + break; + case COL_FSSIZE: + case COL_FSAVAIL: + case COL_FSUSED: + case COL_FSUSEPERC: + str = get_vfs_attribute(dev, id); + break; + case COL_FSVERSION: + prop = lsblk_device_get_properties(dev); + if (prop && prop->fsversion) + str = xstrdup(prop->fsversion); + break; + case COL_TARGET: + { + const char *p = lsblk_device_get_mountpoint(dev); + if (p) + str = xstrdup(p); + break; + } + case COL_TARGETS: + { + size_t i, n = 0; + struct ul_buffer buf = UL_INIT_BUFFER; + struct libmnt_fs **fss = lsblk_device_get_filesystems(dev, &n); + + for (i = 0; i < n; i++) { + struct libmnt_fs *fs = fss[i]; + if (mnt_fs_is_swaparea(fs)) + ul_buffer_append_string(&buf, "[SWAP]"); + else + ul_buffer_append_string(&buf, mnt_fs_get_target(fs)); + if (i + 1 < n) + ul_buffer_append_data(&buf, "\n", 1); + } + str = ul_buffer_get_data(&buf, NULL, NULL); + break; + } + case COL_FSROOTS: + { + size_t i, n = 0; + struct ul_buffer buf = UL_INIT_BUFFER; + struct libmnt_fs **fss = lsblk_device_get_filesystems(dev, &n); + + for (i = 0; i < n; i++) { + struct libmnt_fs *fs = fss[i]; + const char *root = mnt_fs_get_root(fs); + if (mnt_fs_is_swaparea(fs)) + continue; + ul_buffer_append_string(&buf, root ? root : "/"); + if (i + 1 < n) + ul_buffer_append_data(&buf, "\n", 1); + } + str = ul_buffer_get_data(&buf, NULL, NULL); + break; + } + case COL_LABEL: + prop = lsblk_device_get_properties(dev); + if (prop && prop->label) + str = xstrdup(prop->label); + break; + case COL_UUID: + prop = lsblk_device_get_properties(dev); + if (prop && prop->uuid) + str = xstrdup(prop->uuid); + break; + case COL_PTUUID: + prop = lsblk_device_get_properties(dev); + if (prop && prop->ptuuid) + str = xstrdup(prop->ptuuid); + break; + case COL_PTTYPE: + prop = lsblk_device_get_properties(dev); + if (prop && prop->pttype) + str = xstrdup(prop->pttype); + break; + case COL_PARTTYPE: + prop = lsblk_device_get_properties(dev); + if (prop && prop->parttype) + str = xstrdup(prop->parttype); + break; + case COL_PARTTYPENAME: + prop = lsblk_device_get_properties(dev); + if (prop && prop->parttype && prop->pttype) { + const char *x = lsblk_parttype_code_to_string( + prop->parttype, prop->pttype); + if (x) + str = xstrdup(x); + } + break; + case COL_PARTLABEL: + prop = lsblk_device_get_properties(dev); + if (prop && prop->partlabel) + str = xstrdup(prop->partlabel); + break; + case COL_PARTUUID: + prop = lsblk_device_get_properties(dev); + if (prop && prop->partuuid) + str = xstrdup(prop->partuuid); + break; + case COL_PARTFLAGS: + prop = lsblk_device_get_properties(dev); + if (prop && prop->partflags) + str = xstrdup(prop->partflags); + break; + case COL_PARTN: + prop = lsblk_device_get_properties(dev); + if (prop && prop->partn) + str = xstrdup(prop->partn); + break; + case COL_WWN: + prop = lsblk_device_get_properties(dev); + if (prop && prop->wwn) + str = xstrdup(prop->wwn); + break; + case COL_IDLINK: + prop = lsblk_device_get_properties(dev); + if (prop && prop->idlink) + str = xstrdup(prop->idlink); + break; + case COL_ID: + prop = lsblk_device_get_properties(dev); + if (prop && prop->idlink) { + /* skip bus/subsystem prefix */ + const char *p = strchr(prop->idlink, '-'); + str = p && *(p + 1) ? xstrdup(p+1) : xstrdup(prop->idlink); + } + break; + case COL_RA: + ul_path_read_string(dev->sysfs, &str, "queue/read_ahead_kb"); + if (sortdata) + str2u64(str, sortdata); + break; + case COL_RO: + str = xstrdup(is_readonly_device(dev) ? "1" : "0"); + break; + case COL_RM: + str = xstrdup(is_removable_device(dev, parent) ? "1" : "0"); + break; + case COL_HOTPLUG: + str = sysfs_blkdev_is_hotpluggable(dev->sysfs) ? xstrdup("1") : xstrdup("0"); + break; + case COL_ROTA: + ul_path_read_string(dev->sysfs, &str, "queue/rotational"); + break; + case COL_RAND: + ul_path_read_string(dev->sysfs, &str, "queue/add_random"); + break; + case COL_MODEL: + if (!device_is_partition(dev) && dev->nslaves == 0) { + prop = lsblk_device_get_properties(dev); + if (prop && prop->model) + str = xstrdup(prop->model); + else + ul_path_read_string(dev->sysfs, &str, "device/model"); + } + break; + case COL_SERIAL: + if (!device_is_partition(dev) && dev->nslaves == 0) { + prop = lsblk_device_get_properties(dev); + if (prop && prop->serial) + str = xstrdup(prop->serial); + else + ul_path_read_string(dev->sysfs, &str, "device/serial"); + } + break; + case COL_REV: + if (!device_is_partition(dev) && dev->nslaves == 0) { + prop = lsblk_device_get_properties(dev); + if (prop && prop->revision) + str = xstrdup(prop->revision); + else + ul_path_read_string(dev->sysfs, &str, "device/rev"); + } + break; + case COL_VENDOR: + if (!device_is_partition(dev) && dev->nslaves == 0) + ul_path_read_string(dev->sysfs, &str, "device/vendor"); + break; + case COL_SIZE: + if (lsblk->bytes) + xasprintf(&str, "%ju", dev->size); + else + str = size_to_human_string(SIZE_SUFFIX_1LETTER, dev->size); + if (sortdata) + *sortdata = dev->size; + break; + case COL_START: + ul_path_read_string(dev->sysfs, &str, "start"); + if (sortdata) + str2u64(str, sortdata); + break; + case COL_STATE: + if (!device_is_partition(dev) && !dev->dm_name) + ul_path_read_string(dev->sysfs, &str, "device/state"); + else if (dev->dm_name) { + int x = 0; + if (ul_path_read_s32(dev->sysfs, &x, "dm/suspended") == 0) + str = xstrdup(x ? "suspended" : "running"); + } + break; + case COL_ALIOFF: + ul_path_read_string(dev->sysfs, &str, "alignment_offset"); + if (sortdata) + str2u64(str, sortdata); + break; + case COL_MINIO: + ul_path_read_string(dev->sysfs, &str, "queue/minimum_io_size"); + if (sortdata) + str2u64(str, sortdata); + break; + case COL_OPTIO: + ul_path_read_string(dev->sysfs, &str, "queue/optimal_io_size"); + if (sortdata) + str2u64(str, sortdata); + break; + case COL_PHYSEC: + ul_path_read_string(dev->sysfs, &str, "queue/physical_block_size"); + if (sortdata) + str2u64(str, sortdata); + break; + case COL_LOGSEC: + ul_path_read_string(dev->sysfs, &str, "queue/logical_block_size"); + if (sortdata) + str2u64(str, sortdata); + 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); + break; + case COL_TYPE: + str = get_type(dev); + break; + case COL_HCTL: + { + int h, c, t, l; + if (sysfs_blkdev_scsi_get_hctl(dev->sysfs, &h, &c, &t, &l) == 0) + xasprintf(&str, "%d:%d:%d:%d", h, c, t, l); + break; + } + case COL_TRANSPORT: + { + const char *trans = get_transport(dev); + if (trans) + str = xstrdup(trans); + break; + } + case COL_SUBSYS: + str = get_subsystems(dev); + break; + case COL_DALIGN: + if (device_get_discard_granularity(dev) > 0) + ul_path_read_string(dev->sysfs, &str, "discard_alignment"); + if (!str) + str = xstrdup("0"); + if (sortdata) + str2u64(str, sortdata); + break; + case COL_DGRAN: + if (lsblk->bytes) { + ul_path_read_string(dev->sysfs, &str, "queue/discard_granularity"); + if (sortdata) + str2u64(str, sortdata); + } else { + uint64_t x = device_get_discard_granularity(dev); + str = size_to_human_string(SIZE_SUFFIX_1LETTER, x); + if (sortdata) + *sortdata = x; + } + break; + case COL_DMAX: + device_read_bytes(dev, "queue/discard_max_bytes", &str, sortdata); + break; + case COL_DZERO: + if (device_get_discard_granularity(dev) > 0) + ul_path_read_string(dev->sysfs, &str, "queue/discard_zeroes_data"); + if (!str) + str = xstrdup("0"); + break; + case COL_WSAME: + device_read_bytes(dev, "queue/write_same_max_bytes", &str, sortdata); + if (!str) + str = xstrdup("0"); + break; + case COL_ZONED: + ul_path_read_string(dev->sysfs, &str, "queue/zoned"); + break; + case COL_ZONE_SZ: + { + uint64_t x; + + if (ul_path_read_u64(dev->sysfs, &x, "queue/chunk_sectors") == 0) { + x <<= 9; + if (lsblk->bytes) + xasprintf(&str, "%ju", x); + else + str = size_to_human_string(SIZE_SUFFIX_1LETTER, x); + if (sortdata) + *sortdata = x; + } + break; + } + case COL_ZONE_WGRAN: + device_read_bytes(dev, "queue/zone_write_granularity", &str, sortdata); + break; + case COL_ZONE_APP: + device_read_bytes(dev, "queue/zone_append_max_bytes", &str, sortdata); + break; + case COL_ZONE_NR: + ul_path_read_string(dev->sysfs, &str, "queue/nr_zones"); + if (sortdata) + str2u64(str, sortdata); + 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); + 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); + break; + case COL_DAX: + ul_path_read_string(dev->sysfs, &str, "queue/dax"); + break; + case COL_MQ: + process_mq(dev, &str); + break; + case COL_DISKSEQ: + ul_path_read_string(dev->sysfs, &str, "diskseq"); + if (sortdata) + str2u64(str, sortdata); + break; + }; + + return str; +} + +/* + * Adds data for all wanted columns about the device to the smartcols table + */ +static void device_to_scols( + struct lsblk_device *dev, + struct lsblk_device *parent, + struct libscols_table *tab, + struct libscols_line *parent_line) +{ + size_t i; + struct libscols_line *ln; + struct lsblk_iter itr; + struct lsblk_device *child = NULL; + int link_group = 0; + + + DBG(DEV, ul_debugobj(dev, "add '%s' to scols", dev->name)); + ON_DBG(DEV, if (ul_path_isopen_dirfd(dev->sysfs)) ul_debugobj(dev, " %s ---> is open!", dev->name)); + + if (!parent && dev->wholedisk) + parent = dev->wholedisk; + + /* Do not print device more than once on --list if tree order is not requested */ + if (!(lsblk->flags & LSBLK_TREE) && !lsblk->force_tree_order && dev->is_printed) + return; + + if (lsblk->merge && list_count_entries(&dev->parents) > 1) { + if (!lsblk_device_is_last_parent(dev, parent)) + return; + link_group = 1; + } + + ln = scols_table_new_line(tab, link_group ? NULL : parent_line); + if (!ln) + err(EXIT_FAILURE, _("failed to allocate output line")); + + dev->is_printed = 1; + + if (link_group) { + struct lsblk_device *p; + struct libscols_line *gr = parent_line; + + /* Merge all my parents to the one group */ + DBG(DEV, ul_debugobj(dev, " grouping parents [--merge]")); + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + while (lsblk_device_next_parent(dev, &itr, &p) == 0) { + if (!p->scols_line) { + DBG(DEV, ul_debugobj(dev, " *** ignore '%s' no scols line yet", p->name)); + continue; + } + DBG(DEV, ul_debugobj(dev, " group '%s'", p->name)); + scols_table_group_lines(tab, p->scols_line, gr, 0); + } + + /* Link the group -- this makes group->child connection */ + DBG(DEV, ul_debugobj(dev, " linking the group [--merge]")); + 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")); + } + + dev->scols_line = ln; + + if (dev->npartitions == 0) + /* For partitions we often read from parental whole-disk sysfs, + * otherwise we can close */ + ul_path_close_dirfd(dev->sysfs); + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + while (lsblk_device_next_child(dev, &itr, &child) == 0) { + DBG(DEV, ul_debugobj(dev, "%s -> continue to child", dev->name)); + device_to_scols(child, dev, tab, ln); + DBG(DEV, ul_debugobj(dev, "%s <- child done", dev->name)); + } + + /* Let's be careful with number of open files */ + ul_path_close_dirfd(dev->sysfs); +} + +/* + * Walks on tree and adds one line for each device to the smartcols table + */ +static void devtree_to_scols(struct lsblk_devtree *tr, struct libscols_table *tab) +{ + struct lsblk_iter itr; + struct lsblk_device *dev = NULL; + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (lsblk_devtree_next_root(tr, &itr, &dev) == 0) + device_to_scols(dev, NULL, tab, NULL); +} + +static int ignore_empty(struct lsblk_device *dev) +{ + /* show all non-empty devices */ + if (dev->size) + return 0; + + if (lsblk->noempty && dev->size == 0) + return 1; + + /* ignore empty loop devices without backing file */ + if (dev->maj == LOOPDEV_MAJOR && + !loopdev_has_backing_file(dev->filename)) + return 1; + + return 0; +} + +/* + * Reads very basic information about the device from sysfs into the device struct + */ +static int initialize_device(struct lsblk_device *dev, + struct lsblk_device *wholedisk, + const char *name) +{ + dev_t devno; + + DBG(DEV, ul_debugobj(dev, "initialize %s [wholedisk=%p %s]", + name, wholedisk, wholedisk ? wholedisk->name : "")); + + if (sysfs_devname_is_hidden(lsblk->sysroot, name)) { + DBG(DEV, ul_debugobj(dev, "%s: hidden, ignore", name)); + return -1; + } + + dev->name = xstrdup(name); + + if (wholedisk) { + dev->wholedisk = wholedisk; + lsblk_ref_device(wholedisk); + } + + dev->filename = get_device_path(dev); + if (!dev->filename) { + DBG(DEV, ul_debugobj(dev, "%s: failed to get device path", dev->name)); + return -1; + } + DBG(DEV, ul_debugobj(dev, "%s: filename=%s", dev->name, dev->filename)); + + devno = __sysfs_devname_to_devno(lsblk->sysroot, dev->name, wholedisk ? wholedisk->name : NULL); + if (!devno) { + DBG(DEV, ul_debugobj(dev, "%s: unknown device name", dev->name)); + return -1; + } + + dev->sysfs = ul_new_sysfs_path(devno, wholedisk ? wholedisk->sysfs : NULL, lsblk->sysroot); + if (!dev->sysfs) { + DBG(DEV, ul_debugobj(dev, "%s: failed to initialize sysfs handler", dev->name)); + return -1; + } + + dev->maj = major(devno); + dev->min = minor(devno); + dev->size = 0; + + if (ul_path_read_u64(dev->sysfs, &dev->size, "size") == 0) /* in sectors */ + dev->size <<= 9; /* in bytes */ + + /* Ignore devices of zero size */ + if (!lsblk->all_devices && ignore_empty(dev)) { + DBG(DEV, ul_debugobj(dev, "zero size device -- ignore")); + return -1; + } + if (is_dm(dev->name)) { + ul_path_read_string(dev->sysfs, &dev->dm_name, "dm/name"); + if (!dev->dm_name) { + DBG(DEV, ul_debugobj(dev, "%s: failed to get dm name", dev->name)); + return -1; + } + } + + dev->npartitions = sysfs_blkdev_count_partitions(dev->sysfs, dev->name); + dev->nholders = ul_path_count_dirents(dev->sysfs, "holders"); + dev->nslaves = ul_path_count_dirents(dev->sysfs, "slaves"); + + DBG(DEV, ul_debugobj(dev, "%s: npartitions=%d, nholders=%d, nslaves=%d", + dev->name, dev->npartitions, dev->nholders, dev->nslaves)); + + /* ignore non-SCSI devices */ + if (lsblk->scsi && sysfs_blkdev_scsi_get_hctl(dev->sysfs, NULL, NULL, NULL, NULL)) { + DBG(DEV, ul_debugobj(dev, "non-scsi device -- ignore")); + return -1; + } + + /* ignore non-NVMe devices */ + if (lsblk->nvme) { + const char *transport = get_transport(dev); + + if (!transport || strcmp(transport, "nvme")) { + DBG(DEV, ul_debugobj(dev, "non-nvme device -- ignore")); + return -1; + } + } + + /* ignore non-virtio devices */ + if (lsblk->virtio) { + const char *transport = get_transport(dev); + + if (!transport || strcmp(transport, "virtio")) { + DBG(DEV, ul_debugobj(dev, "non-virtio device -- ignore")); + return -1; + } + } + + DBG(DEV, ul_debugobj(dev, "%s: context successfully initialized", dev->name)); + return 0; +} + +static struct lsblk_device *devtree_get_device_or_new(struct lsblk_devtree *tr, + struct lsblk_device *disk, + const char *name) +{ + struct lsblk_device *dev = lsblk_devtree_get_device(tr, name); + + if (!dev) { + dev = lsblk_new_device(); + if (!dev) + err(EXIT_FAILURE, _("failed to allocate device")); + + if (initialize_device(dev, disk, name) != 0) { + lsblk_unref_device(dev); + return NULL; + } + lsblk_devtree_add_device(tr, dev); + lsblk_unref_device(dev); /* keep it referenced by devtree only */ + } else + DBG(DEV, ul_debugobj(dev, "%s: already processed", name)); + + return dev; +} + +static struct lsblk_device *devtree_pktcdvd_get_dep( + struct lsblk_devtree *tr, + struct lsblk_device *dev, + int want_slave) +{ + char buf[PATH_MAX], *name; + dev_t devno; + + devno = lsblk_devtree_pktcdvd_get_mate(tr, + makedev(dev->maj, dev->min), !want_slave); + if (!devno) + return NULL; + + name = sysfs_devno_to_devname(devno, buf, sizeof(buf)); + if (!name) + return NULL; + + return devtree_get_device_or_new(tr, NULL, name); +} + +static int process_dependencies( + struct lsblk_devtree *tr, + struct lsblk_device *dev, + int do_partitions); + +/* + * Read devices from whole-disk device into tree + */ +static int process_partitions(struct lsblk_devtree *tr, struct lsblk_device *disk) +{ + DIR *dir; + struct dirent *d; + + assert(disk); + + /* + * Do not process further if there are no partitions for + * this device or the device itself is a partition. + */ + if (!disk->npartitions || device_is_partition(disk)) + return -EINVAL; + + DBG(DEV, ul_debugobj(disk, "%s: probe whole-disk for partitions", disk->name)); + + dir = ul_path_opendir(disk->sysfs, NULL); + if (!dir) + err(EXIT_FAILURE, _("failed to open device directory in sysfs")); + + while ((d = xreaddir(dir))) { + struct lsblk_device *part; + + if (!(sysfs_blkdev_is_partition_dirent(dir, d, disk->name))) + continue; + + DBG(DEV, ul_debugobj(disk, " checking %s", d->d_name)); + + part = devtree_get_device_or_new(tr, disk, d->d_name); + if (!part) + continue; + + if (lsblk_device_new_dependence(disk, part) == 0) + process_dependencies(tr, part, 0); + + ul_path_close_dirfd(part->sysfs); + } + + /* For partitions we need parental (whole-disk) sysfs directory pretty + * often, so close it now when all is done */ + ul_path_close_dirfd(disk->sysfs); + + DBG(DEV, ul_debugobj(disk, "probe whole-disk for partitions -- done")); + closedir(dir); + return 0; +} + +static char *get_wholedisk_from_partition_dirent(DIR *dir, struct dirent *d, char *buf, size_t bufsz) +{ + char *p; + int len; + + if ((len = readlinkat(dirfd(dir), d->d_name, buf, bufsz - 1)) < 0) + return 0; + + buf[len] = '\0'; + + /* The path ends with ".../<device>/<partition>" */ + p = strrchr(buf, '/'); + if (!p) + return NULL; + *p = '\0'; + + p = strrchr(buf, '/'); + if (!p) + return NULL; + p++; + + return p; +} + +/* + * Reads slaves/holders and partitions for specified device into device tree + */ +static int process_dependencies( + struct lsblk_devtree *tr, + struct lsblk_device *dev, + int do_partitions) +{ + DIR *dir; + struct dirent *d; + const char *depname; + struct lsblk_device *dep = NULL; + + assert(dev); + + if (lsblk->nodeps) + return 0; + + /* read all or specified partition */ + if (do_partitions && dev->npartitions) + process_partitions(tr, dev); + + DBG(DEV, ul_debugobj(dev, "%s: reading dependencies", dev->name)); + + if (!(lsblk->inverse ? dev->nslaves : dev->nholders)) { + DBG(DEV, ul_debugobj(dev, " ignore (no slaves/holders)")); + goto done; + } + + depname = lsblk->inverse ? "slaves" : "holders"; + dir = ul_path_opendir(dev->sysfs, depname); + if (!dir) { + DBG(DEV, ul_debugobj(dev, " ignore (no slaves/holders directory)")); + goto done; + } + ul_path_close_dirfd(dev->sysfs); + + DBG(DEV, ul_debugobj(dev, " %s: checking for '%s' dependence", dev->name, depname)); + + while ((d = xreaddir(dir))) { + struct lsblk_device *disk = NULL; + + /* Is the dependency a partition? */ + if (sysfs_blkdev_is_partition_dirent(dir, d, NULL)) { + + char buf[PATH_MAX]; + char *diskname; + + DBG(DEV, ul_debugobj(dev, " %s: dependence is partition", d->d_name)); + + diskname = get_wholedisk_from_partition_dirent(dir, d, buf, sizeof(buf)); + if (diskname) + disk = devtree_get_device_or_new(tr, NULL, diskname); + if (!disk) { + DBG(DEV, ul_debugobj(dev, " ignore no wholedisk ???")); + goto next; + } + + dep = devtree_get_device_or_new(tr, disk, d->d_name); + if (!dep) + goto next; + + if (lsblk_device_new_dependence(dev, dep) == 0) + process_dependencies(tr, dep, 1); + + if (lsblk->inverse + && lsblk_device_new_dependence(dep, disk) == 0) + process_dependencies(tr, disk, 0); + } + /* The dependency is a whole device. */ + else { + DBG(DEV, ul_debugobj(dev, " %s: %s: dependence is whole-disk", + dev->name, d->d_name)); + + dep = devtree_get_device_or_new(tr, NULL, d->d_name); + if (!dep) + goto next; + + if (lsblk_device_new_dependence(dev, dep) == 0) + /* For inverse tree we don't want to show partitions + * if the dependence is on whole-disk */ + process_dependencies(tr, dep, lsblk->inverse ? 0 : 1); + } +next: + if (dep && dep->sysfs) + ul_path_close_dirfd(dep->sysfs); + if (disk && disk->sysfs) + ul_path_close_dirfd(disk->sysfs); + } + closedir(dir); +done: + dep = devtree_pktcdvd_get_dep(tr, dev, lsblk->inverse); + + if (dep && lsblk_device_new_dependence(dev, dep) == 0) { + lsblk_devtree_remove_root(tr, dep); + process_dependencies(tr, dep, lsblk->inverse ? 0 : 1); + } + + return 0; +} + +/* + * Defines the device as root node in the device tree and walks on all dependencies of the device. + */ +static int __process_one_device(struct lsblk_devtree *tr, char *devname, dev_t devno) +{ + struct lsblk_device *dev = NULL; + struct lsblk_device *disk = NULL; + char buf[PATH_MAX + 1], *name = NULL, *diskname = NULL; + int real_part = 0, rc = -EINVAL; + + if (devno == 0 && devname) { + struct stat st; + + DBG(DEV, ul_debug("%s: reading alone device", devname)); + + if (stat(devname, &st) || !S_ISBLK(st.st_mode)) { + warnx(_("%s: not a block device"), devname); + goto leave; + } + devno = st.st_rdev; + } else if (devno) { + DBG(DEV, ul_debug("%d:%d: reading alone device", major(devno), minor(devno))); + } else { + assert(devno || devname); + return -EINVAL; + } + + /* TODO: sysfs_devno_to_devname() internally initializes path_cxt, it + * would be better to use ul_new_sysfs_path() + sysfs_blkdev_get_name() + * and reuse path_cxt for initialize_device() + */ + name = sysfs_devno_to_devname(devno, buf, sizeof(buf)); + if (!name) { + if (devname) + warn(_("%s: failed to get sysfs name"), devname); + goto leave; + } + name = xstrdup(name); + + if (!strncmp(name, "dm-", 3)) { + /* dm mapping is never a real partition! */ + real_part = 0; + } else { + dev_t diskno = 0; + + if (blkid_devno_to_wholedisk(devno, buf, sizeof(buf), &diskno)) { + warn(_("%s: failed to get whole-disk device number"), name); + goto leave; + } + diskname = buf; + real_part = devno != diskno; + } + + if (!real_part) { + /* + * Device is not a partition. + */ + DBG(DEV, ul_debug(" non-partition")); + + dev = devtree_get_device_or_new(tr, NULL, name); + if (!dev) + goto leave; + + lsblk_devtree_add_root(tr, dev); + process_dependencies(tr, dev, !lsblk->inverse); + } else { + /* + * Partition, read sysfs name of the disk device + */ + DBG(DEV, ul_debug(" partition")); + + disk = devtree_get_device_or_new(tr, NULL, diskname); + if (!disk) + goto leave; + + dev = devtree_get_device_or_new(tr, disk, name); + if (!dev) + goto leave; + + lsblk_devtree_add_root(tr, dev); + process_dependencies(tr, dev, 1); + + if (lsblk->inverse + && lsblk_device_new_dependence(dev, disk) == 0) + process_dependencies(tr, disk, 0); + else + ul_path_close_dirfd(disk->sysfs); + } + + rc = 0; +leave: + if (dev && dev->sysfs) + ul_path_close_dirfd(dev->sysfs); + if (disk && disk->sysfs) + ul_path_close_dirfd(disk->sysfs); + free(name); + return rc; +} + +static int process_one_device(struct lsblk_devtree *tr, char *devname) +{ + assert(devname); + return __process_one_device(tr, devname, 0); +} + +/* + * The /sys/block contains only root devices, and no partitions. It seems more + * simple to scan /sys/dev/block where are all devices without exceptions to get + * top-level devices for the reverse tree. + */ +static int process_all_devices_inverse(struct lsblk_devtree *tr) +{ + DIR *dir; + struct dirent *d; + struct path_cxt *pc = ul_new_path(_PATH_SYS_DEVBLOCK); + + assert(lsblk->inverse); + + if (!pc) + err(EXIT_FAILURE, _("failed to allocate /sys handler")); + + ul_path_set_prefix(pc, lsblk->sysroot); + dir = ul_path_opendir(pc, NULL); + if (!dir) + goto done; + + DBG(DEV, ul_debug("iterate on " _PATH_SYS_DEVBLOCK)); + + while ((d = xreaddir(dir))) { + dev_t devno; + int maj, min; + + DBG(DEV, ul_debug(" %s dentry", d->d_name)); + + if (sscanf(d->d_name, "%d:%d", &maj, &min) != 2) + continue; + devno = makedev(maj, min); + + if (is_maj_excluded(maj) || !is_maj_included(maj)) + continue; + if (ul_path_countf_dirents(pc, "%s/holders", d->d_name) != 0) + continue; + if (sysfs_devno_count_partitions(devno) != 0) + continue; + __process_one_device(tr, NULL, devno); + } + + closedir(dir); +done: + ul_unref_path(pc); + DBG(DEV, ul_debug("iterate on " _PATH_SYS_DEVBLOCK " -- done")); + return 0; +} + +/* + * Reads root nodes (devices) from /sys/block into devices tree + */ +static int process_all_devices(struct lsblk_devtree *tr) +{ + DIR *dir; + struct dirent *d; + struct path_cxt *pc; + + assert(lsblk->inverse == 0); + + pc = ul_new_path(_PATH_SYS_BLOCK); + if (!pc) + err(EXIT_FAILURE, _("failed to allocate /sys handler")); + + ul_path_set_prefix(pc, lsblk->sysroot); + dir = ul_path_opendir(pc, NULL); + if (!dir) + goto done; + + DBG(DEV, ul_debug("iterate on " _PATH_SYS_BLOCK)); + + while ((d = xreaddir(dir))) { + struct lsblk_device *dev = NULL; + + DBG(DEV, ul_debug(" %s dentry", d->d_name)); + dev = devtree_get_device_or_new(tr, NULL, d->d_name); + if (!dev) + goto next; + + /* remove unwanted devices */ + if (is_maj_excluded(dev->maj) || !is_maj_included(dev->maj)) { + DBG(DEV, ul_debug(" %s: ignore (by filter)", d->d_name)); + lsblk_devtree_remove_device(tr, dev); + dev = NULL; + goto next; + } + + if (dev->nslaves) { + DBG(DEV, ul_debug(" %s: ignore (in-middle)", d->d_name)); + goto next; + } + + lsblk_devtree_add_root(tr, dev); + process_dependencies(tr, dev, 1); +next: + /* Let's be careful with number of open files */ + if (dev && dev->sysfs) + ul_path_close_dirfd(dev->sysfs); + } + + closedir(dir); +done: + ul_unref_path(pc); + DBG(DEV, ul_debug("iterate on " _PATH_SYS_BLOCK " -- done")); + return 0; +} + +/* + * Parses major numbers as specified on lsblk command line + */ +static void parse_excludes(const char *str0) +{ + const char *str = str0; + + while (str && *str) { + char *end = NULL; + unsigned long n; + + errno = 0; + n = strtoul(str, &end, 10); + + if (end == str || (end && *end && *end != ',')) + errx(EXIT_FAILURE, _("failed to parse list '%s'"), str0); + if (errno != 0 && (n == ULONG_MAX || n == 0)) + err(EXIT_FAILURE, _("failed to parse list '%s'"), str0); + excludes[nexcludes++] = n; + + if (nexcludes == ARRAY_SIZE(excludes)) + /* TRANSLATORS: The standard value for %d is 256. */ + errx(EXIT_FAILURE, _("the list of excluded devices is " + "too large (limit is %d devices)"), + (int)ARRAY_SIZE(excludes)); + + str = end && *end ? end + 1 : NULL; + } +} + +/* + * Parses major numbers as specified on lsblk command line + * (TODO: what about refactor and merge parse_excludes() and parse_includes().) + */ +static void parse_includes(const char *str0) +{ + const char *str = str0; + + while (str && *str) { + char *end = NULL; + unsigned long n; + + errno = 0; + n = strtoul(str, &end, 10); + + if (end == str || (end && *end && *end != ',')) + errx(EXIT_FAILURE, _("failed to parse list '%s'"), str0); + if (errno != 0 && (n == ULONG_MAX || n == 0)) + err(EXIT_FAILURE, _("failed to parse list '%s'"), str0); + includes[nincludes++] = n; + + if (nincludes == ARRAY_SIZE(includes)) + /* TRANSLATORS: The standard value for %d is 256. */ + errx(EXIT_FAILURE, _("the list of included devices is " + "too large (limit is %d devices)"), + (int)ARRAY_SIZE(includes)); + str = end && *end ? end + 1 : NULL; + } +} + +/* + * see set_sortdata_u64() and columns initialization in main() + */ +static int cmp_u64_cells(struct libscols_cell *a, + struct libscols_cell *b, + __attribute__((__unused__)) void *data) +{ + uint64_t *adata = (uint64_t *) scols_cell_get_userdata(a), + *bdata = (uint64_t *) scols_cell_get_userdata(b); + + if (adata == NULL && bdata == NULL) + return 0; + if (adata == NULL) + return -1; + if (bdata == NULL) + return 1; + return *adata == *bdata ? 0 : *adata >= *bdata ? 1 : -1; +} + +static void device_set_dedupkey( + struct lsblk_device *dev, + struct lsblk_device *parent, + int id) +{ + struct lsblk_iter itr; + struct lsblk_device *child = NULL; + + dev->dedupkey = device_get_data(dev, parent, id, NULL); + if (dev->dedupkey) + DBG(DEV, ul_debugobj(dev, "%s: de-duplication key: %s", dev->name, dev->dedupkey)); + + if (dev->npartitions == 0) + /* For partitions we often read from parental whole-disk sysfs, + * otherwise we can close */ + ul_path_close_dirfd(dev->sysfs); + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (lsblk_device_next_child(dev, &itr, &child) == 0) + device_set_dedupkey(child, dev, id); + + /* Let's be careful with number of open files */ + ul_path_close_dirfd(dev->sysfs); +} + +static void devtree_set_dedupkeys(struct lsblk_devtree *tr, int id) +{ + struct lsblk_iter itr; + struct lsblk_device *dev = NULL; + + lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); + + while (lsblk_devtree_next_root(tr, &itr, &dev) == 0) + device_set_dedupkey(dev, NULL, id); +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + size_t i; + + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] [<device> ...]\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("List information about block devices.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -A, --noempty don't print empty devices\n"), out); + fputs(_(" -D, --discard print discard capabilities\n"), out); + fputs(_(" -E, --dedup <column> de-duplicate output by <column>\n"), out); + fputs(_(" -I, --include <list> show only devices with specified major numbers\n"), out); + fputs(_(" -J, --json use JSON output format\n"), out); + fputs(_(" -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(_(" -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); + fputs(_(" -T, --tree[=<column>] use tree format output\n"), out); + fputs(_(" -a, --all print all devices\n"), out); + fputs(_(" -b, --bytes print SIZE in bytes rather than in human readable format\n"), out); + fputs(_(" -d, --nodeps don't print slaves or holders\n"), out); + fputs(_(" -e, --exclude <list> exclude devices by major number (default: RAM disks)\n"), out); + fputs(_(" -f, --fs output info about filesystems\n"), out); + fputs(_(" -i, --ascii use ascii characters only\n"), out); + fputs(_(" -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(_(" -p, --paths print complete device path\n"), out); + fputs(_(" -r, --raw use raw output format\n"), out); + fputs(_(" -s, --inverse inverse dependencies\n"), out); + fputs(_(" -t, --topology output info about topology\n"), out); + fputs(_(" -w, --width <num> specifies output width as number of characters\n"), out); + fputs(_(" -x, --sort <column> sort output by <column>\n"), out); + 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)); + + fprintf(out, USAGE_COLUMNS); + + for (i = 0; i < ARRAY_SIZE(infos); i++) + fprintf(out, " %12s %s\n", infos[i].name, _(infos[i].help)); + + printf(USAGE_MAN_TAIL("lsblk(8)")); + + exit(EXIT_SUCCESS); +} + +static void check_sysdevblock(void) +{ + if (access(_PATH_SYS_DEVBLOCK, R_OK) != 0) + err(EXIT_FAILURE, _("failed to access sysfs directory: %s"), + _PATH_SYS_DEVBLOCK); +} + +int main(int argc, char *argv[]) +{ + struct lsblk _ls = { + .sort_id = -1, + .dedup_id = -1, + .flags = LSBLK_TREE, + .tree_id = COL_NAME + }; + struct lsblk_devtree *tr = NULL; + int c, status = EXIT_FAILURE; + char *outarg = NULL; + size_t i; + unsigned int width = 0; + int force_tree = 0, has_tree_col = 0; + + enum { + OPT_SYSROOT = CHAR_MAX + 1 + }; + + static const struct option longopts[] = { + { "all", no_argument, NULL, 'a' }, + { "bytes", no_argument, NULL, 'b' }, + { "nodeps", no_argument, NULL, 'd' }, + { "noempty", no_argument, NULL, 'A' }, + { "discard", no_argument, NULL, 'D' }, + { "dedup", required_argument, NULL, 'E' }, + { "zoned", no_argument, NULL, 'z' }, + { "help", no_argument, NULL, 'h' }, + { "json", no_argument, NULL, 'J' }, + { "output", required_argument, NULL, 'o' }, + { "output-all", no_argument, NULL, 'O' }, + { "merge", no_argument, NULL, 'M' }, + { "perms", no_argument, NULL, 'm' }, + { "noheadings", no_argument, NULL, 'n' }, + { "list", no_argument, NULL, 'l' }, + { "ascii", no_argument, NULL, 'i' }, + { "raw", no_argument, NULL, 'r' }, + { "inverse", no_argument, NULL, 's' }, + { "fs", no_argument, NULL, 'f' }, + { "exclude", required_argument, NULL, 'e' }, + { "include", required_argument, NULL, 'I' }, + { "topology", no_argument, NULL, 't' }, + { "paths", no_argument, NULL, 'p' }, + { "pairs", no_argument, NULL, 'P' }, + { "scsi", no_argument, NULL, 'S' }, + { "nvme", no_argument, NULL, 'N' }, + { "virtio", no_argument, NULL, 'v' }, + { "sort", required_argument, NULL, 'x' }, + { "sysroot", required_argument, NULL, OPT_SYSROOT }, + { "shell", no_argument, NULL, 'y' }, + { "tree", optional_argument, NULL, 'T' }, + { "version", no_argument, NULL, 'V' }, + { "width", required_argument, NULL, 'w' }, + { NULL, 0, NULL, 0 }, + }; + + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { 'D','O' }, + { 'I','e' }, + { 'J', 'P', 'r' }, + { 'O','S' }, + { 'O','f' }, + { 'O','m' }, + { 'O','o' }, + { 'O','t' }, + { 'P','T', 'l','r' }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + lsblk = &_ls; + + lsblk_init_debug(); + + while((c = getopt_long(argc, argv, + "AabdDzE:e:fhJlNnMmo:OpPiI:rstVvST::w:x:y", + longopts, NULL)) != -1) { + + err_exclusive_options(c, longopts, excl, excl_st); + + switch(c) { + case 'A': + lsblk->noempty = 1; + break; + case 'a': + lsblk->all_devices = 1; + break; + case 'b': + lsblk->bytes = 1; + break; + case 'd': + lsblk->nodeps = 1; + break; + case 'D': + add_uniq_column(COL_NAME); + add_uniq_column(COL_DALIGN); + add_uniq_column(COL_DGRAN); + add_uniq_column(COL_DMAX); + add_uniq_column(COL_DZERO); + break; + case 'z': + add_uniq_column(COL_NAME); + add_uniq_column(COL_ZONED); + add_uniq_column(COL_ZONE_SZ); + add_uniq_column(COL_ZONE_NR); + add_uniq_column(COL_ZONE_AMAX); + add_uniq_column(COL_ZONE_OMAX); + add_uniq_column(COL_ZONE_APP); + add_uniq_column(COL_ZONE_WGRAN); + break; + case 'e': + parse_excludes(optarg); + break; + case 'J': + lsblk->flags |= LSBLK_JSON; + break; + case 'l': + lsblk->flags &= ~LSBLK_TREE; /* disable the default */ + break; + case 'M': + lsblk->merge = 1; + break; + case 'n': + lsblk->flags |= LSBLK_NOHEADINGS; + break; + case 'o': + outarg = optarg; + break; + case 'O': + for (ncolumns = 0 ; ncolumns < ARRAY_SIZE(infos); ncolumns++) + columns[ncolumns] = ncolumns; + break; + case 'p': + lsblk->paths = 1; + break; + case 'P': + lsblk->flags |= LSBLK_EXPORT; + lsblk->flags &= ~LSBLK_TREE; /* disable the default */ + break; + case 'y': + lsblk->flags |= LSBLK_SHELLVAR; + break; + case 'i': + lsblk->flags |= LSBLK_ASCII; + break; + case 'I': + parse_includes(optarg); + break; + case 'r': + lsblk->flags &= ~LSBLK_TREE; /* disable the default */ + lsblk->flags |= LSBLK_RAW; /* enable raw */ + break; + case 's': + lsblk->inverse = 1; + break; + case 'f': + add_uniq_column(COL_NAME); + add_uniq_column(COL_FSTYPE); + add_uniq_column(COL_FSVERSION); + add_uniq_column(COL_LABEL); + add_uniq_column(COL_UUID); + add_uniq_column(COL_FSAVAIL); + add_uniq_column(COL_FSUSEPERC); + add_uniq_column(COL_TARGETS); + break; + case 'm': + add_uniq_column(COL_NAME); + add_uniq_column(COL_SIZE); + add_uniq_column(COL_OWNER); + add_uniq_column(COL_GROUP); + add_uniq_column(COL_MODE); + break; + case 't': + add_uniq_column(COL_NAME); + add_uniq_column(COL_ALIOFF); + add_uniq_column(COL_MINIO); + add_uniq_column(COL_OPTIO); + add_uniq_column(COL_PHYSEC); + add_uniq_column(COL_LOGSEC); + add_uniq_column(COL_ROTA); + add_uniq_column(COL_SCHED); + add_uniq_column(COL_RQ_SIZE); + add_uniq_column(COL_RA); + add_uniq_column(COL_WSAME); + break; + case 'S': + lsblk->nodeps = 1; + lsblk->scsi = 1; + add_uniq_column(COL_NAME); + add_uniq_column(COL_HCTL); + add_uniq_column(COL_TYPE); + add_uniq_column(COL_VENDOR); + add_uniq_column(COL_MODEL); + add_uniq_column(COL_REV); + add_uniq_column(COL_SERIAL); + add_uniq_column(COL_TRANSPORT); + break; + case 'N': + lsblk->nodeps = 1; + lsblk->nvme = 1; + add_uniq_column(COL_NAME); + add_uniq_column(COL_TYPE); + add_uniq_column(COL_MODEL); + add_uniq_column(COL_SERIAL); + add_uniq_column(COL_REV); + add_uniq_column(COL_TRANSPORT); + add_uniq_column(COL_RQ_SIZE); + add_uniq_column(COL_MQ); + break; + case 'v': + lsblk->nodeps = 1; + lsblk->virtio = 1; + add_uniq_column(COL_NAME); + add_uniq_column(COL_TYPE); + add_uniq_column(COL_TRANSPORT); + add_uniq_column(COL_SIZE); + add_uniq_column(COL_RQ_SIZE); + add_uniq_column(COL_MQ); + break; + case 'T': + force_tree = 1; + if (optarg) { + if (*optarg == '=') + optarg++; + lsblk->tree_id = column_name_to_id(optarg, strlen(optarg)); + } + break; + case OPT_SYSROOT: + lsblk->sysroot = optarg; + break; + case 'E': + lsblk->dedup_id = column_name_to_id(optarg, strlen(optarg)); + if (lsblk->dedup_id >= 0) + break; + errtryhelp(EXIT_FAILURE); + break; + case 'w': + width = strtou32_or_err(optarg, _("invalid output width number argument")); + break; + case 'x': + lsblk->flags &= ~LSBLK_TREE; /* disable the default */ + lsblk->sort_id = column_name_to_id(optarg, strlen(optarg)); + if (lsblk->sort_id >= 0) + break; + errtryhelp(EXIT_FAILURE); + break; + + case 'h': + usage(); + case 'V': + print_version(EXIT_SUCCESS); + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (force_tree) + lsblk->flags |= LSBLK_TREE; + + check_sysdevblock(); + + if (!ncolumns) { + add_column(COL_NAME); + add_column(COL_MAJMIN); + add_column(COL_RM); + add_column(COL_SIZE); + add_column(COL_RO); + add_column(COL_TYPE); + add_column(COL_TARGETS); + } + + if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns), + &ncolumns, column_name_to_id) < 0) + return EXIT_FAILURE; + + if (lsblk->all_devices == 0 && nexcludes == 0 && nincludes == 0) + excludes[nexcludes++] = 1; /* default: ignore RAM disks */ + + if (lsblk->sort_id < 0) + /* Since Linux 4.8 we have sort devices by default, because + * /sys is no more sorted */ + lsblk->sort_id = COL_MAJMIN; + + /* For --{inverse,raw,pairs} --list we still follow parent->child relation */ + if (!(lsblk->flags & LSBLK_TREE) + && (lsblk->inverse || lsblk->flags & LSBLK_EXPORT || lsblk->flags & LSBLK_RAW)) + lsblk->force_tree_order = 1; + + if (lsblk->sort_id >= 0 && column_id_to_number(lsblk->sort_id) < 0) { + /* the sort column is not between output columns -- add as hidden */ + add_column(lsblk->sort_id); + lsblk->sort_hidden = 1; + } + + if (lsblk->dedup_id >= 0 && column_id_to_number(lsblk->dedup_id) < 0) { + /* the deduplication column is not between output columns -- add as hidden */ + add_column(lsblk->dedup_id); + lsblk->dedup_hidden = 1; + } + + lsblk_mnt_init(); + scols_init_debug(0); + ul_path_init_debug(); + + /* + * initialize output columns + */ + if (!(lsblk->table = scols_new_table())) + errx(EXIT_FAILURE, _("failed to allocate output table")); + scols_table_enable_raw(lsblk->table, !!(lsblk->flags & LSBLK_RAW)); + scols_table_enable_export(lsblk->table, !!(lsblk->flags & LSBLK_EXPORT)); + scols_table_enable_shellvar(lsblk->table, !!(lsblk->flags & LSBLK_SHELLVAR)); + scols_table_enable_ascii(lsblk->table, !!(lsblk->flags & LSBLK_ASCII)); + scols_table_enable_json(lsblk->table, !!(lsblk->flags & LSBLK_JSON)); + scols_table_enable_noheadings(lsblk->table, !!(lsblk->flags & LSBLK_NOHEADINGS)); + + if (lsblk->flags & LSBLK_JSON) + scols_table_set_name(lsblk->table, "blockdevices"); + if (width) { + scols_table_set_termwidth(lsblk->table, width); + scols_table_set_termforce(lsblk->table, SCOLS_TERMFORCE_ALWAYS); + } + + for (i = 0; i < ncolumns; i++) { + struct colinfo *ci = get_column_info(i); + struct libscols_column *cl; + int id = get_column_id(i), fl = ci->flags; + + if ((lsblk->flags & LSBLK_TREE) + && has_tree_col == 0 + && id == lsblk->tree_id) { + fl |= SCOLS_FL_TREE; + fl &= ~SCOLS_FL_RIGHT; + has_tree_col = 1; + } + + if (lsblk->sort_hidden && lsblk->sort_id == id) + fl |= SCOLS_FL_HIDDEN; + if (lsblk->dedup_hidden && lsblk->dedup_id == id) + fl |= SCOLS_FL_HIDDEN; + + if (force_tree + && lsblk->flags & LSBLK_JSON + && has_tree_col == 0 + && i + 1 == ncolumns) + /* The "--tree --json" specified, but no column with + * SCOLS_FL_TREE yet; force it for the last column + */ + fl |= SCOLS_FL_TREE; + + cl = scols_table_new_column(lsblk->table, ci->name, ci->whint, fl); + if (!cl) { + warn(_("failed to allocate output column")); + goto leave; + } + if (!lsblk->sort_col && lsblk->sort_id == id) { + lsblk->sort_col = cl; + scols_column_set_cmpfunc(cl, + ci->type == COLTYPE_NUM ? cmp_u64_cells : + ci->type == COLTYPE_SIZE ? cmp_u64_cells : + ci->type == COLTYPE_SORTNUM ? cmp_u64_cells : scols_cmpstr_cells, + 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 (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; + } + } + } + + tr = lsblk_new_devtree(); + if (!tr) + err(EXIT_FAILURE, _("failed to allocate device tree")); + + if (optind == argc) { + int rc = lsblk->inverse ? + process_all_devices_inverse(tr) : + process_all_devices(tr); + + status = rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE; + } else { + int cnt = 0, cnt_err = 0; + + while (optind < argc) { + if (process_one_device(tr, argv[optind++]) != 0) + cnt_err++; + cnt++; + } + status = cnt == 0 ? EXIT_FAILURE : /* nothing */ + cnt == cnt_err ? LSBLK_EXIT_ALLFAILED :/* all failed */ + cnt_err ? LSBLK_EXIT_SOMEOK : /* some ok */ + EXIT_SUCCESS; /* all success */ + } + + if (lsblk->dedup_id > -1) { + devtree_set_dedupkeys(tr, lsblk->dedup_id); + lsblk_devtree_deduplicate_devices(tr); + } + + devtree_to_scols(tr, lsblk->table); + + if (lsblk->sort_col) + scols_sort_table(lsblk->table, lsblk->sort_col); + if (lsblk->force_tree_order) + scols_sort_table_by_tree(lsblk->table); + + scols_print_table(lsblk->table); + +leave: + if (lsblk->sort_col) + unref_sortdata(lsblk->table); + + scols_unref_table(lsblk->table); + + lsblk_mnt_deinit(); + lsblk_properties_deinit(); + lsblk_unref_devtree(tr); + + return status; +} diff --git a/misc-utils/lsblk.h b/misc-utils/lsblk.h new file mode 100644 index 0000000..a437c06 --- /dev/null +++ b/misc-utils/lsblk.h @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2010-2018 Red Hat, Inc. All rights reserved. + * Written by Milan Broz <gmazyland@gmail.com> + * Karel Zak <kzak@redhat.com> + */ +#ifndef UTIL_LINUX_LSBLK_H +#define UTIL_LINUX_LSBLK_H + +#include <stdint.h> +#include <inttypes.h> +#include <sys/stat.h> +#include <sys/statvfs.h> + +#include <libsmartcols.h> +#include <libmount.h> + +#include "c.h" +#include "list.h" +#include "debug.h" + +#define LSBLK_DEBUG_INIT (1 << 1) +#define LSBLK_DEBUG_FILTER (1 << 2) +#define LSBLK_DEBUG_DEV (1 << 3) +#define LSBLK_DEBUG_TREE (1 << 4) +#define LSBLK_DEBUG_DEP (1 << 5) +#define LSBLK_DEBUG_ALL 0xFFFF + +UL_DEBUG_DECLARE_MASK(lsblk); +#define DBG(m, x) __UL_DBG(lsblk, LSBLK_DEBUG_, m, x) +#define ON_DBG(m, x) __UL_DBG_CALL(lsblk, LSBLK_DEBUG_, m, x) + +#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(lsblk) +#include "debugobj.h" + +struct lsblk { + struct libscols_table *table; /* output table */ + struct libscols_column *sort_col;/* sort output by this column */ + + int sort_id; /* id of the sort column */ + int tree_id; /* od of column used for tree */ + + int dedup_id; + + + const char *sysroot; + int flags; /* LSBLK_* */ + + unsigned int all_devices:1; /* print all devices, including empty */ + unsigned int bytes:1; /* print SIZE in bytes */ + unsigned int inverse:1; /* print inverse dependencies */ + unsigned int merge:1; /* merge sub-trees */ + unsigned int nodeps:1; /* don't print slaves/holders */ + unsigned int scsi:1; /* print only device with HCTL (SCSI) */ + unsigned int nvme:1; /* print NVMe device only */ + 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 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 */ +}; + +extern struct lsblk *lsblk; /* global handler */ + +struct lsblk_devprop { + /* udev / blkid based */ + char *fstype; /* detected fs, NULL or "?" if cannot detect */ + char *fsversion; /* filesystem version */ + char *uuid; /* filesystem UUID (or stack uuid) */ + char *ptuuid; /* partition table UUID */ + char *pttype; /* partition table type */ + char *label; /* filesystem label */ + char *parttype; /* partition type UUID */ + char *partuuid; /* partition UUID */ + char *partlabel; /* partition label */ + char *partflags; /* partition flags */ + char *partn; /* partition number */ + char *wwn; /* storage WWN */ + char *serial; /* disk serial number */ + char *model; /* disk model */ + char *idlink; /* /dev/disk/by-id/<name> */ + char *revision; /* firmware revision/version */ + + /* lsblk specific (for --sysroot only) */ + char *owner; /* user name */ + char *group; /* group name */ + char *mode; /* access mode in ls(1)-like notation */ +}; + +/* Device dependence + * + * Note that the same device may be slave/holder for more another devices. It + * means we need to allocate list member rather than use @child directly. + */ +struct lsblk_devdep { + struct list_head ls_childs; /* item in parent->childs */ + struct list_head ls_parents; /* item in child->parents */ + + struct lsblk_device *child; + struct lsblk_device *parent; +}; + +struct lsblk_device { + int refcount; + + struct list_head childs; /* list with lsblk_devdep */ + struct list_head parents; + struct list_head ls_roots; /* item in devtree->roots list */ + struct list_head ls_devices; /* item in devtree->devices list */ + + struct lsblk_device *wholedisk; /* for partitions */ + + struct libscols_line *scols_line; + + struct lsblk_devprop *properties; + struct stat st; + + char *name; /* kernel name in /sys/block */ + char *dm_name; /* DM name (dm/block) */ + + char *filename; /* path to device node */ + char *dedupkey; /* de-duplication key */ + + struct path_cxt *sysfs; + + struct libmnt_fs **fss; /* filesystems attached to the device */ + size_t nfss; /* number of items in fss[] */ + + struct statvfs fsstat; /* statvfs() result */ + + int npartitions; /* # of partitions this device has */ + int nholders; /* # of devices mapped directly to this device + * /sys/block/.../holders */ + int nslaves; /* # of devices this device maps to */ + int maj, min; /* devno */ + + uint64_t discard_granularity; /* sunknown:-1, yes:1, not:0 */ + + uint64_t size; /* device size */ + int removable; /* unknown:-1, yes:1, not:0 */ + + unsigned int is_mounted : 1, + is_swap : 1, + is_printed : 1, + udev_requested : 1, + blkid_requested : 1, + file_requested : 1; +}; + +#define device_is_partition(_x) ((_x)->wholedisk != NULL) + +/* Unfortunately, pktcdvd dependence on block device is not defined by + * slave/holder symlinks. The struct lsblk_devnomap represents one line in + * /sys/class/pktcdvd/device_map + */ +struct lsblk_devnomap { + dev_t slave; /* packet device devno */ + dev_t holder; /* block device devno */ + + struct list_head ls_devnomap; +}; + + +/* + * Note that lsblk tree uses bottom devices (devices without slaves) as root + * of the tree, and partitions are interpreted as a dependence too; it means: + * sda -> sda1 -> md0 + * + * The flag 'is_inverted' turns the tree over (root is device without holders): + * md0 -> sda1 -> sda + */ +struct lsblk_devtree { + int refcount; + + struct list_head roots; /* tree root devices */ + struct list_head devices; /* all devices */ + struct list_head pktcdvd_map; /* devnomap->ls_devnomap */ + + unsigned int is_inverse : 1, /* inverse tree */ + pktcdvd_read : 1; +}; + + +/* + * Generic iterator + */ +struct lsblk_iter { + struct list_head *p; /* current position */ + struct list_head *head; /* start position */ + int direction; /* LSBLK_ITER_{FOR,BACK}WARD */ +}; + +#define LSBLK_ITER_FORWARD 0 +#define LSBLK_ITER_BACKWARD 1 + +#define IS_ITER_FORWARD(_i) ((_i)->direction == LSBLK_ITER_FORWARD) +#define IS_ITER_BACKWARD(_i) ((_i)->direction == LSBLK_ITER_BACKWARD) + +#define LSBLK_ITER_INIT(itr, list) \ + do { \ + (itr)->p = IS_ITER_FORWARD(itr) ? \ + (list)->next : (list)->prev; \ + (itr)->head = (list); \ + } while(0) + +#define LSBLK_ITER_ITERATE(itr, res, restype, member) \ + do { \ + res = list_entry((itr)->p, restype, member); \ + (itr)->p = IS_ITER_FORWARD(itr) ? \ + (itr)->p->next : (itr)->p->prev; \ + } while(0) + + +/* lsblk-mnt.c */ +extern void lsblk_mnt_init(void); +extern void lsblk_mnt_deinit(void); + +extern void lsblk_device_free_filesystems(struct lsblk_device *dev); +extern const char *lsblk_device_get_mountpoint(struct lsblk_device *dev); +extern struct libmnt_fs **lsblk_device_get_filesystems(struct lsblk_device *dev, size_t *n); + +/* lsblk-properties.c */ +extern void lsblk_device_free_properties(struct lsblk_devprop *p); +extern struct lsblk_devprop *lsblk_device_get_properties(struct lsblk_device *dev); +extern void lsblk_properties_deinit(void); + +extern const char *lsblk_parttype_code_to_string(const char *code, const char *pttype); + +/* lsblk-devtree.c */ +void lsblk_reset_iter(struct lsblk_iter *itr, int direction); +struct lsblk_device *lsblk_new_device(void); +void lsblk_ref_device(struct lsblk_device *dev); +void lsblk_unref_device(struct lsblk_device *dev); +int lsblk_device_new_dependence(struct lsblk_device *parent, struct lsblk_device *child); +int lsblk_device_has_child(struct lsblk_device *dev, struct lsblk_device *child); +int lsblk_device_next_child(struct lsblk_device *dev, + struct lsblk_iter *itr, + struct lsblk_device **child); + +dev_t lsblk_devtree_pktcdvd_get_mate(struct lsblk_devtree *tr, dev_t devno, int is_slave); + +int lsblk_device_is_last_parent(struct lsblk_device *dev, struct lsblk_device *parent); +int lsblk_device_next_parent( + struct lsblk_device *dev, + struct lsblk_iter *itr, + struct lsblk_device **parent); + +struct lsblk_devtree *lsblk_new_devtree(void); +void lsblk_ref_devtree(struct lsblk_devtree *tr); +void lsblk_unref_devtree(struct lsblk_devtree *tr); +int lsblk_devtree_add_root(struct lsblk_devtree *tr, struct lsblk_device *dev); +int lsblk_devtree_remove_root(struct lsblk_devtree *tr, struct lsblk_device *dev); +int lsblk_devtree_next_root(struct lsblk_devtree *tr, + struct lsblk_iter *itr, + struct lsblk_device **dev); +int lsblk_devtree_add_device(struct lsblk_devtree *tr, struct lsblk_device *dev); +int lsblk_devtree_next_device(struct lsblk_devtree *tr, + struct lsblk_iter *itr, + struct lsblk_device **dev); +int lsblk_devtree_has_device(struct lsblk_devtree *tr, struct lsblk_device *dev); +struct lsblk_device *lsblk_devtree_get_device(struct lsblk_devtree *tr, const char *name); +int lsblk_devtree_remove_device(struct lsblk_devtree *tr, struct lsblk_device *dev); +int lsblk_devtree_deduplicate_devices(struct lsblk_devtree *tr); + +#endif /* UTIL_LINUX_LSBLK_H */ diff --git a/misc-utils/lsfd-bdev.c b/misc-utils/lsfd-bdev.c new file mode 100644 index 0000000..7be99db --- /dev/null +++ b/misc-utils/lsfd-bdev.c @@ -0,0 +1,163 @@ +/* + * lsfd-bdev.c - handle associations opening block devices + * + * Copyright (C) 2021 Red Hat, Inc. All rights reserved. + * Written by Masatake YAMATO <yamato@redhat.com> + * + * 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., 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; + +struct partition { + struct list_head partitions; + dev_t dev; + char *name; +}; + +static bool bdev_fill_column(struct proc *proc __attribute__((__unused__)), + struct file *file __attribute__((__unused__)), + struct libscols_line *ln, + int column_id, + size_t column_index) +{ + char *str = NULL; + const char *partition, *devdrv; + + switch(column_id) { + case COL_TYPE: + if (scols_line_set_data(ln, column_index, "BLK")) + err(EXIT_FAILURE, _("failed to add output data")); + return true; + case COL_BLKDRV: + devdrv = get_blkdrv(major(file->stat.st_rdev)); + if (devdrv) + str = xstrdup(devdrv); + else + xasprintf(&str, "%u", + major(file->stat.st_rdev)); + break; + case COL_DEVTYPE: + if (scols_line_set_data(ln, column_index, + "blk")) + err(EXIT_FAILURE, _("failed to add output data")); + return true; + case COL_SOURCE: + case COL_PARTITION: + partition = get_partition(file->stat.st_rdev); + if (partition) { + str = xstrdup(partition); + break; + } + devdrv = get_blkdrv(major(file->stat.st_rdev)); + if (devdrv) { + 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: + return false; + } + + if (!str) + err(EXIT_FAILURE, _("failed to add output data")); + if (scols_line_refer_data(ln, column_index, str)) + err(EXIT_FAILURE, _("failed to add output data")); + return true; +} + +static struct partition *new_partition(dev_t dev, const char *name) +{ + struct partition *partition = xcalloc(1, sizeof(*partition)); + + INIT_LIST_HEAD(&partition->partitions); + + partition->dev = dev; + partition->name = xstrdup(name); + + return partition; +} + +static void free_partition(struct partition *partition) +{ + free(partition->name); + free(partition); +} + +static void read_partitions(struct list_head *partitions_list, FILE *part_fp) +{ + unsigned int major, minor; + char line[256]; + char name[sizeof(line)]; + + while (fgets(line, sizeof(line), part_fp)) { + struct partition *partition; + + if (sscanf(line, "%u %u %*u %s", &major, &minor, name) != 3) + continue; + partition = new_partition(makedev(major, minor), name); + list_add_tail(&partition->partitions, partitions_list); + } +} + +static void bdev_class_initialize(void) +{ + FILE *part_fp; + + INIT_LIST_HEAD(&partitions); + + part_fp = fopen("/proc/partitions", "r"); + if (part_fp) { + read_partitions(&partitions, part_fp); + fclose(part_fp); + } +} + +static void bdev_class_finalize(void) +{ + list_free(&partitions, struct partition, partitions, free_partition); +} + +const char *get_partition(dev_t dev) +{ + struct list_head *p; + list_for_each(p, &partitions) { + struct partition *partition = list_entry(p, struct partition, partitions); + if (partition->dev == dev) + return partition->name; + } + return NULL; +} + +const struct file_class bdev_class = { + .super = &file_class, + .size = sizeof(struct file), + .initialize_class = bdev_class_initialize, + .finalize_class = bdev_class_finalize, + .fill_column = bdev_fill_column, + .free_content = NULL, +}; diff --git a/misc-utils/lsfd-cdev.c b/misc-utils/lsfd-cdev.c new file mode 100644 index 0000000..4e35f15 --- /dev/null +++ b/misc-utils/lsfd-cdev.c @@ -0,0 +1,178 @@ +/* + * lsfd-cdev.c - handle associations opening character devices + * + * Copyright (C) 2021 Red Hat, Inc. All rights reserved. + * Written by Masatake YAMATO <yamato@redhat.com> + * + * 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., 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; + +struct miscdev { + struct list_head miscdevs; + unsigned long minor; + char *name; +}; + +static bool cdev_fill_column(struct proc *proc __attribute__((__unused__)), + struct file *file __attribute__((__unused__)), + struct libscols_line *ln, + int column_id, + size_t column_index) +{ + char *str = NULL; + const char *devdrv; + const char *miscdev; + + switch(column_id) { + 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); + 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: + return false; + } + + if (!str) + err(EXIT_FAILURE, _("failed to add output data")); + if (scols_line_refer_data(ln, column_index, str)) + err(EXIT_FAILURE, _("failed to add output data")); + return true; +} + +static struct miscdev *new_miscdev(unsigned long minor, const char *name) +{ + struct miscdev *miscdev = xcalloc(1, sizeof(*miscdev)); + + INIT_LIST_HEAD(&miscdev->miscdevs); + + miscdev->minor = minor; + miscdev->name = xstrdup(name); + + return miscdev; +} + +static void free_miscdev(struct miscdev *miscdev) +{ + free(miscdev->name); + free(miscdev); +} + +static void read_misc(struct list_head *miscdevs_list, FILE *misc_fp) +{ + unsigned long minor; + char line[256]; + char name[sizeof(line)]; + + while (fgets(line, sizeof(line), misc_fp)) { + struct miscdev *miscdev; + + if (sscanf(line, "%lu %s", &minor, name) != 2) + continue; + + miscdev = new_miscdev(minor, name); + list_add_tail(&miscdev->miscdevs, miscdevs_list); + } +} + +static void cdev_class_initialize(void) +{ + FILE *misc_fp; + + INIT_LIST_HEAD(&miscdevs); + + misc_fp = fopen("/proc/misc", "r"); + if (misc_fp) { + read_misc(&miscdevs, misc_fp); + fclose(misc_fp); + } +} + +static void cdev_class_finalize(void) +{ + list_free(&miscdevs, struct miscdev, miscdevs, free_miscdev); +} + +const char *get_miscdev(unsigned long minor) +{ + struct list_head *c; + list_for_each(c, &miscdevs) { + struct miscdev *miscdev = list_entry(c, struct miscdev, miscdevs); + if (miscdev->minor == minor) + return miscdev->name; + } + return NULL; +} + +const struct file_class cdev_class = { + .super = &file_class, + .size = sizeof(struct file), + .initialize_class = cdev_class_initialize, + .finalize_class = cdev_class_finalize, + .fill_column = cdev_fill_column, + .free_content = NULL, +}; diff --git a/misc-utils/lsfd-counter.c b/misc-utils/lsfd-counter.c new file mode 100644 index 0000000..55f0fde --- /dev/null +++ b/misc-utils/lsfd-counter.c @@ -0,0 +1,56 @@ +/* + * 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 new file mode 100644 index 0000000..bac11a9 --- /dev/null +++ b/misc-utils/lsfd-counter.h @@ -0,0 +1,30 @@ +/* + * 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-decode-file-flags.c b/misc-utils/lsfd-decode-file-flags.c new file mode 100644 index 0000000..bd87c7d --- /dev/null +++ b/misc-utils/lsfd-decode-file-flags.c @@ -0,0 +1,148 @@ +/* + * lsfd(1) - list file descriptors + * + * Copyright (C) 2022 Red Hat, Inc. All rights reserved. + * Written by Masatake YAMATO <yamato@redhat.com> + * + * Very generally based on lsof(8) by Victor A. Abell <abe@purdue.edu> + * It supports multiple OSes. lsfd specializes to Linux. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* lsfd_decode_file_flags() is for decoding `flags' field of + * /proc/$pid/fdinfo/$fd. Bits of the field have name defined + * in fctl.h. + * A system on which lsfd is built may have multiple + * fctl.h files: + * + * - /usr/include/asm/fcntl.h (a part of Linux kernel) + * - /usr/include/asm-generic/fcntl.h (a part of Linux kernel) + * - /usr/include/fcntl.h (a part of glibc) + * - /usr/include/bits/fcntl.h (a part of glibc) + * + * For decoding purpose, /usr/include/asm/fcntl.h or + * /usr/include/asm-generic/fcntl.h is needed. + * + * /usr/include/bits/fcntl.h and /usr/include/fcntl.h are + * not suitable for decoding. They should not be included. + * /usr/include/fcntl.h includes /usr/include/bits/fcntl.h. + */ + +#if defined HAVE_ASM_FCNTL_H +#include <asm/fcntl.h> +#elif defined HAVE_ASM_GENERIC_FCNTL_H +#include <asm-generic/fcntl.h> +#else +#error "kernel's fcntl.h is not available" +#endif + +#include <stddef.h> /* for size_t */ +struct ul_buffer; + +void lsfd_decode_file_flags(struct ul_buffer *buf, int flags); + +/* We cannot include buffer.h because buffer.h includes + * /usr/include/fcntl.h indirectly. */ +extern int ul_buffer_is_empty(struct ul_buffer *buf); +extern int ul_buffer_append_data(struct ul_buffer *buf, const char *data, size_t sz); +extern int ul_buffer_append_string(struct ul_buffer *buf, const char *str); + +void lsfd_decode_file_flags(struct ul_buffer *buf, int flags) +{ +#define SET_FLAG_FULL(L,s) \ + do { \ + if (flags & (L)) { \ + if (!ul_buffer_is_empty(buf)) \ + ul_buffer_append_data(buf, ",", 1); \ + ul_buffer_append_string(buf, #s); \ + } \ + } while (0) + +#define SET_FLAG(L,s) SET_FLAG_FULL(O_##L,s) + +#ifdef O_WRONLY + SET_FLAG(WRONLY,wronly); +#endif + +#ifdef O_RDWR + SET_FLAG(RDWR,rdwr); +#endif + +#ifdef O_CREAT + SET_FLAG(CREAT,creat); +#endif + +#ifdef O_EXCL + SET_FLAG(EXCL,excl); +#endif + +#ifdef O_NOCTTY + SET_FLAG(NOCTTY,noctty); +#endif + +#ifdef O_APPEND + SET_FLAG(APPEND,append); +#endif + +#ifdef O_NONBLOCK + SET_FLAG(NONBLOCK,nonblock); +#endif + +#ifdef O_DSYNC + SET_FLAG(DSYNC,dsync); +#endif + +#ifdef FASYNC + SET_FLAG_FULL(FASYNC,fasync); +#endif + +#ifdef O_DIRECT + SET_FLAG(DIRECT,direct); +#endif + +#ifdef O_LARGEFILE + SET_FLAG(LARGEFILE,largefile); +#endif + +#ifdef O_DIRECTORY + SET_FLAG(DIRECTORY,directory); +#endif + +#ifdef O_NOFOLLOW + SET_FLAG(NOFOLLOW,nofollow); +#endif + +#ifdef O_NOATIME + SET_FLAG(NOATIME,noatime); +#endif + +#ifdef O_CLOEXEC + SET_FLAG(CLOEXEC,cloexec); +#endif + +#ifdef __O_SYNC + SET_FLAG_FULL(__O_SYNC,_sync); +#endif + +#ifdef O_PATH + SET_FLAG(PATH,path); +#endif + +#ifdef __O_TMPFILE + SET_FLAG_FULL(__O_TMPFILE,_tmpfile); +#endif + +} diff --git a/misc-utils/lsfd-fifo.c b/misc-utils/lsfd-fifo.c new file mode 100644 index 0000000..f736192 --- /dev/null +++ b/misc-utils/lsfd-fifo.c @@ -0,0 +1,148 @@ +/* + * lsfd-fifo.c - handle associations opening fifo objects + * + * Copyright (C) 2021 Red Hat, Inc. All rights reserved. + * Written by Masatake YAMATO <yamato@redhat.com> + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "xalloc.h" +#include "nls.h" +#include "libsmartcols.h" + +#include "lsfd.h" + +struct fifo { + struct file file; + struct ipc_endpoint endpoint; +}; + +struct fifo_ipc { + struct ipc ipc; + ino_t ino; +}; + +static inline char *fifo_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 fifo_fill_column(struct proc *proc __attribute__((__unused__)), + struct file *file, + struct libscols_line *ln, + int column_id, + size_t column_index) +{ + char *str = NULL; + + switch(column_id) { + case COL_TYPE: + if (scols_line_set_data(ln, column_index, "FIFO")) + err(EXIT_FAILURE, _("failed to add output data")); + return true; + case COL_SOURCE: + if (major(file->stat.st_dev) == 0 + && strncmp(file->name, "pipe:", 5) == 0) { + str = xstrdup("pipefs"); + break; + } + return false; + case COL_ENDPOINTS: { + struct fifo *this = (struct fifo *)file; + struct list_head *e; + char *estr; + list_for_each_backwardly(e, &this->endpoint.ipc->endpoints) { + struct fifo *other = list_entry(e, struct fifo, endpoint.endpoints); + if (this == other) + continue; + if (str) + xstrputc(&str, '\n'); + estr = fifo_xstrendpoint(&other->file); + xstrappend(&str, estr); + free(estr); + } + if (!str) + return false; + break; + } + default: + return false; + } + + if (!str) + err(EXIT_FAILURE, _("failed to add output data")); + if (scols_line_refer_data(ln, column_index, str)) + err(EXIT_FAILURE, _("failed to add output data")); + return true; +} + +static unsigned int fifo_get_hash(struct file *file) +{ + return (unsigned int)(file->stat.st_ino % UINT_MAX); +} + +static bool fifo_is_suitable_ipc(struct ipc *ipc, struct file *file) +{ + return ((struct fifo_ipc *)ipc)->ino == file->stat.st_ino; +} + +static const struct ipc_class *fifo_get_ipc_class(struct file *file __attribute__((__unused__))) +{ + static const struct ipc_class fifo_ipc_class = { + .get_hash = fifo_get_hash, + .is_suitable_ipc = fifo_is_suitable_ipc, + .free = NULL, + }; + return &fifo_ipc_class; +} + +static void fifo_initialize_content(struct file *file) +{ + struct fifo *fifo = (struct fifo *)file; + struct ipc *ipc; + unsigned int hash; + + INIT_LIST_HEAD(&fifo->endpoint.endpoints); + 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); + ((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); +} + +const struct file_class fifo_class = { + .super = &file_class, + .size = sizeof(struct fifo), + .fill_column = fifo_fill_column, + .initialize_content = fifo_initialize_content, + .free_content = NULL, + .get_ipc_class = fifo_get_ipc_class, +}; diff --git a/misc-utils/lsfd-file.c b/misc-utils/lsfd-file.c new file mode 100644 index 0000000..bdaac3f --- /dev/null +++ b/misc-utils/lsfd-file.c @@ -0,0 +1,465 @@ +/* + * lsfd(1) - list file descriptors + * + * Copyright (C) 2021 Red Hat, Inc. All rights reserved. + * Written by Masatake YAMATO <yamato@redhat.com> + * + * Very generally based on lsof(8) by Victor A. Abell <abe@purdue.edu> + * It supports multiple OSes. lsfd specializes to Linux. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <unistd.h> + +#ifdef HAVE_LINUX_NSFS_H +# include <linux/nsfs.h> +# if defined(NS_GET_NSTYPE) +# define USE_NS_GET_API 1 +# include <sys/ioctl.h> +# endif +#endif +#include <linux/sched.h> + +#include "xalloc.h" +#include "nls.h" +#include "buffer.h" +#include "idcache.h" +#include "strutils.h" + +#include "libsmartcols.h" + +#include "lsfd.h" + +static struct idcache *username_cache; + +static const char *assocstr[N_ASSOCS] = { + [ASSOC_CWD] = "cwd", + [ASSOC_EXE] = "exe", + /* "root" appears as user names, too. + * So we use "rtd" here instead of "root". */ + [ASSOC_ROOT] = "rtd", + [ASSOC_NS_CGROUP] = "cgroup", + [ASSOC_NS_IPC] = "ipc", + [ASSOC_NS_MNT] = "mnt", + [ASSOC_NS_NET] = "net", + [ASSOC_NS_PID] = "pid", + [ASSOC_NS_PID4C] = "pid4c", + [ASSOC_NS_TIME] = "time", + [ASSOC_NS_TIME4C] = "time4c", + [ASSOC_NS_USER] = "user", + [ASSOC_NS_UTS] = "uts", + [ASSOC_MEM] = "mem", + [ASSOC_SHM] = "shm", +}; + +static const char *strftype(mode_t ftype) +{ + switch (ftype) { + case S_IFBLK: + return "BLK"; + case S_IFCHR: + return "CHR"; + case S_IFDIR: + return "DIR"; + case S_IFIFO: + return "FIFO"; + case S_IFLNK: + return "LINK"; + case S_IFREG: + return "REG"; + case S_IFSOCK: + return "SOCK"; + default: + return "UNKN"; + } +} + +extern void lsfd_decode_file_flags(struct ul_buffer *buf, int flags); +static void file_fill_flags_buf(struct ul_buffer *buf, int flags) +{ + lsfd_decode_file_flags(buf, flags); +} + +#define does_file_has_fdinfo_alike(file) \ + ((file)->association >= 0 \ + || (file)->association == -ASSOC_SHM \ + || (file)->association == -ASSOC_MEM) + +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 (!pagesize) + pagesize = getpagesize(); + + res = (file->map_end - file->map_start) / pagesize; + } + + return res; +} + +static bool file_fill_column(struct proc *proc, + struct file *file, + struct libscols_line *ln, + int column_id, + size_t column_index) +{ + char *str = NULL; + mode_t ftype; + const char *partition; + + switch(column_id) { + case COL_COMMAND: + if (proc->command + && 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 + && scols_line_set_data(ln, column_index, file->name)) + err(EXIT_FAILURE, _("failed to add output data")); + return true; + case COL_STTYPE: + case COL_TYPE: + ftype = file->stat.st_mode & S_IFMT; + if (scols_line_set_data(ln, column_index, strftype(ftype))) + err(EXIT_FAILURE, _("failed to add output data")); + return true; + case COL_USER: + add_uid(username_cache, (int)proc->uid); + if (scols_line_set_data(ln, column_index, + get_id(username_cache, + (int)proc->uid)->name)) + err(EXIT_FAILURE, _("failed to add output data")); + return true; + case COL_OWNER: + add_uid(username_cache, (int)file->stat.st_uid); + if (scols_line_set_data(ln, column_index, + get_id(username_cache, + (int)file->stat.st_uid)->name)) + err(EXIT_FAILURE, _("failed to add output data")); + return true; + case COL_DEVTYPE: + if (scols_line_set_data(ln, column_index, + "nodev")) + err(EXIT_FAILURE, _("failed to add output data")); + return true; + case COL_FD: + if (!is_opened_file(file)) + return false; + /* FALL THROUGH */ + case COL_ASSOC: + if (is_opened_file(file)) + xasprintf(&str, "%d", file->association); + else { + int assoc = file->association * -1; + if (assoc >= N_ASSOCS) + return false; /* INTERNAL ERROR */ + xasprintf(&str, "%s", 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 */ + case COL_PARTITION: + partition = get_partition(file->stat.st_dev); + if (partition) { + str = xstrdup(partition); + break; + } + /* FALL THROUGH */ + case COL_DEV: + case COL_MAJMIN: + xasprintf(&str, "%u:%u", + major(file->stat.st_dev), + minor(file->stat.st_dev)); + break; + case COL_RDEV: + xasprintf(&str, "%u:%u", + major(file->stat.st_rdev), + minor(file->stat.st_rdev)); + break; + case COL_PID: + xasprintf(&str, "%d", (int)proc->leader->pid); + break; + case COL_TID: + xasprintf(&str, "%d", (int)proc->pid); + break; + case COL_UID: + xasprintf(&str, "%d", (int)proc->uid); + break; + case COL_FUID: + xasprintf(&str, "%d", (int)file->stat.st_uid); + break; + case COL_SIZE: + xasprintf(&str, "%jd", (intmax_t)file->stat.st_size); + break; + case COL_NLINK: + xasprintf(&str, "%ju", (uintmax_t)file->stat.st_nlink); + break; + case COL_DELETED: + xasprintf(&str, "%d", file->stat.st_nlink == 0); + break; + case COL_KTHREAD: + xasprintf(&str, "%u", proc->kthread); + break; + case COL_MNT_ID: + xasprintf(&str, "%d", is_opened_file(file)? file->mnt_id: 0); + break; + case COL_MODE: + if (does_file_has_fdinfo_alike(file)) + xasprintf(&str, "%c%c%c", + file->mode & S_IRUSR? 'r': '-', + file->mode & S_IWUSR? 'w': '-', + (is_mapped_file(file) + && file->mode & S_IXUSR)? 'x': '-'); + else + xasprintf(&str, "---"); + break; + case COL_POS: + xasprintf(&str, "%" PRIu64, + (does_file_has_fdinfo_alike(file))? file->pos: 0); + break; + case COL_FLAGS: { + struct ul_buffer buf = UL_INIT_BUFFER; + + if (!is_opened_file(file)) + return true; + + if (file->sys_flags == 0) + return true; + + file_fill_flags_buf(&buf, file->sys_flags); + if (ul_buffer_is_empty(&buf)) + return true; + str = ul_buffer_get_data(&buf, NULL, NULL); + break; + } + case COL_MAPLEN: + if (!is_mapped_file(file)) + return true; + xasprintf(&str, "%ju", (uintmax_t)get_map_length(file)); + break; + default: + return false; + }; + + if (!str) + err(EXIT_FAILURE, _("failed to add output data")); + if (scols_line_refer_data(ln, column_index, str)) + err(EXIT_FAILURE, _("failed to add output data")); + return true; +} + +static int file_handle_fdinfo(struct file *file, const char *key, const char* value) +{ + int rc; + + if (strcmp(key, "pos") == 0) { + rc = ul_strtou64(value, &file->pos, 10); + + } else if (strcmp(key, "flags") == 0) { + rc = ul_strtou32(value, &file->sys_flags, 8); + + } else if (strcmp(key, "mnt_id") == 0) { + rc = ul_strtou32(value, &file->mnt_id, 10); + + } else + return 0; /* ignore -- unknown item */ + + if (rc < 0) + return 0; /* ignore -- parse failed */ + + return 1; /* success */ +} + +static void file_free_content(struct file *file) +{ + free(file->name); +} + +static void file_class_initialize(void) +{ + username_cache = new_idcache(); + if (!username_cache) + err(EXIT_FAILURE, _("failed to allocate UID cache")); +} + +static void file_class_finalize(void) +{ + free_idcache(username_cache); +} + +const struct file_class file_class = { + .super = NULL, + .size = sizeof(struct file), + .initialize_class = file_class_initialize, + .finalize_class = file_class_finalize, + .fill_column = file_fill_column, + .handle_fdinfo = file_handle_fdinfo, + .free_content = file_free_content, +}; + +/* + * Regular files on NSFS + */ + +struct nsfs_file { + struct file file; + int clone_type; +}; + +static const char *get_ns_type_name(int clone_type) +{ + switch (clone_type) { +#ifdef USE_NS_GET_API + case CLONE_NEWNS: + return "mnt"; + case CLONE_NEWCGROUP: + return "cgroup"; + case CLONE_NEWUTS: + return "uts"; + case CLONE_NEWIPC: + return "ipc"; + case CLONE_NEWUSER: + return "user"; + case CLONE_NEWPID: + return "pid"; + case CLONE_NEWNET: + return "net"; +#ifdef CLONE_NEWTIME + case CLONE_NEWTIME: + return "time"; +#endif /* CLONE_NEWTIME */ +#endif /* USE_NS_GET_API */ + default: + return "unknown"; + } +} + +static void init_nsfs_file_content(struct file *file) +{ + struct nsfs_file *nsfs_file = (struct nsfs_file *)file; + nsfs_file->clone_type = -1; + +#ifdef USE_NS_GET_API + char *proc_fname = NULL; + int ns_fd; + int ns_type; + + if (is_association (file, NS_CGROUP)) + nsfs_file->clone_type = CLONE_NEWCGROUP; + else if (is_association (file, NS_IPC)) + nsfs_file->clone_type = CLONE_NEWIPC; + else if (is_association (file, NS_MNT)) + nsfs_file->clone_type = CLONE_NEWNS; + 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)) + nsfs_file->clone_type = CLONE_NEWPID; +#ifdef CLONE_NEWTIME + 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)) + nsfs_file->clone_type = CLONE_NEWUSER; + else if (is_association (file, NS_UTS)) + nsfs_file->clone_type = CLONE_NEWUTS; + + if (nsfs_file->clone_type != -1) + return; + + if (!is_opened_file(file)) + return; + + if (!file->name) + return; + + xasprintf(&proc_fname, "/proc/%d/fd/%d", + file->proc->pid, file->association); + ns_fd = open(proc_fname, O_RDONLY); + free(proc_fname); + if (ns_fd < 0) + return; + + ns_type = ioctl(ns_fd, NS_GET_NSTYPE); + close(ns_fd); + if (ns_type < 0) + return; + + nsfs_file->clone_type = ns_type; +#endif /* USE_NS_GET_API */ +} + + +static bool nsfs_file_fill_column(struct proc *proc __attribute__((__unused__)), + struct file *file, + struct libscols_line *ln, + int column_id, + size_t column_index) +{ + struct nsfs_file *nsfs_file = (struct nsfs_file *)file; + char *name = NULL; + + if (nsfs_file->clone_type == -1) + return false; + + switch (column_id) { + case COL_NS_NAME: + xasprintf(&name, "%s:[%llu]", + get_ns_type_name(nsfs_file->clone_type), + (unsigned long long)file->stat.st_ino); + break; + case COL_NS_TYPE: + if (scols_line_set_data(ln, column_index, + get_ns_type_name(nsfs_file->clone_type))) + err(EXIT_FAILURE, _("failed to add output data")); + return true; + default: + return false; + } + + if (name && scols_line_refer_data(ln, column_index, name)) + err(EXIT_FAILURE, _("failed to add output data")); + + return true; +} + +const struct file_class nsfs_file_class = { + .super = &file_class, + .size = sizeof(struct nsfs_file), + .initialize_class = NULL, + .finalize_class = NULL, + .initialize_content = init_nsfs_file_content, + .free_content = NULL, + .fill_column = nsfs_file_fill_column, + .handle_fdinfo = NULL, +}; diff --git a/misc-utils/lsfd-filter.c b/misc-utils/lsfd-filter.c new file mode 100644 index 0000000..8a4a4d8 --- /dev/null +++ b/misc-utils/lsfd-filter.c @@ -0,0 +1,1409 @@ +/* + * 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 new file mode 100644 index 0000000..db5cbd6 --- /dev/null +++ b/misc-utils/lsfd-filter.h @@ -0,0 +1,43 @@ +/* + * 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 new file mode 100644 index 0000000..61b8607 --- /dev/null +++ b/misc-utils/lsfd-sock-xinfo.c @@ -0,0 +1,2025 @@ +/* + * lsfd-sock-xinfo.c - read various information from files under /proc/net/ + * + * Copyright (C) 2022 Red Hat, Inc. All rights reserved. + * Written by Masatake YAMATO <yamato@redhat.com> + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <arpa/inet.h> /* inet_ntop */ +#include <netinet/in.h> /* in6_addr */ +#include <fcntl.h> /* open(2) */ +#include <ifaddrs.h> /* getifaddrs */ +#include <inttypes.h> /* SCNu16 */ +#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/un.h> /* UNIX_PATH_MAX */ +#include <sched.h> /* for setns(2) */ +#include <search.h> +#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_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_netlink(ino_t netns_inode); +static void load_xinfo_from_proc_packet(ino_t netns_inode); + +static int self_netns_fd = -1; +static struct stat self_netns_sb; + +static void *xinfo_tree; /* for tsearch/tfind */ +static void *netns_tree; + +struct iface { + unsigned int index; + char name[IF_NAMESIZE]; +}; + +static const char *get_iface_name(ino_t netns, unsigned int iface_index); + +struct netns { + ino_t inode; + struct iface *ifaces; +}; + +static int netns_compare(const void *a, const void *b) +{ + const struct netns *netns_a = a; + const struct netns *netns_b = b; + + return netns_a->inode - netns_b->inode; +} + +static void netns_free(void *netns) +{ + struct netns *nsobj = netns; + + free(nsobj->ifaces); + free(netns); +} + +/* + * iface index -> iface name mappings + */ +static void load_ifaces_from_getifaddrs(struct netns *nsobj) +{ + struct ifaddrs *ifa_list; + struct ifaddrs *ifa; + size_t i, count = 0; + + if (getifaddrs(&ifa_list) < 0) + return; + + for (ifa = ifa_list; ifa != NULL; ifa = ifa->ifa_next) + count++; + + nsobj->ifaces = xcalloc(count + 1, sizeof(*nsobj->ifaces)); + + for (ifa = ifa_list, i = 0; ifa != NULL; ifa = ifa->ifa_next, i++) { + unsigned int if_index = if_nametoindex(ifa->ifa_name); + + nsobj->ifaces[i].index = if_index; + strncpy(nsobj->ifaces[i].name, ifa->ifa_name, IF_NAMESIZE - 1); + /* The slot for the last byte is already filled by calloc. */ + } + /* nsobj->ifaces[count] is the sentinel value. */ + + freeifaddrs(ifa_list); + + return; +} + +static const char *get_iface_name(ino_t netns, unsigned int iface_index) +{ + struct netns key = { .inode = netns }; + struct netns **nsobj = tfind(&key, &netns_tree, netns_compare); + if (!nsobj) + return NULL; + + for (size_t i = 0; (*nsobj)->ifaces[i].index; i++) { + if ((*nsobj)->ifaces[i].index == iface_index) + return (*nsobj)->ifaces[i].name; + } + + return NULL; +} + +static bool is_sock_xinfo_loaded(ino_t netns) +{ + struct netns key = { .inode = netns }; + return tfind(&key, &netns_tree, netns_compare)? true: false; +} + +static struct netns *mark_sock_xinfo_loaded(ino_t ino) +{ + struct netns *netns = xcalloc(1, sizeof(*netns)); + ino_t **tmp; + + netns->inode = ino; + tmp = tsearch(netns, &netns_tree, netns_compare); + if (tmp == NULL) + errx(EXIT_FAILURE, _("failed to allocate memory")); + return *(struct netns **)tmp; +} + +static void load_sock_xinfo_no_nsswitch(struct netns *nsobj) +{ + ino_t netns = nsobj? nsobj->inode: 0; + + 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_netlink(netns); + load_xinfo_from_proc_packet(netns); + + if (nsobj) + load_ifaces_from_getifaddrs(nsobj); +} + +static void load_sock_xinfo_with_fd(int fd, struct netns *nsobj) +{ + if (setns(fd, CLONE_NEWNET) == 0) { + load_sock_xinfo_no_nsswitch(nsobj); + setns(self_netns_fd, CLONE_NEWNET); + } +} + +void load_sock_xinfo(struct path_cxt *pc, const char *name, ino_t netns) +{ + if (self_netns_fd == -1) + return; + + if (!is_sock_xinfo_loaded(netns)) { + int fd; + struct netns *nsobj = mark_sock_xinfo_loaded(netns); + fd = ul_path_open(pc, O_RDONLY, name); + if (fd < 0) + return; + + load_sock_xinfo_with_fd(fd, nsobj); + close(fd); + } +} + +void initialize_sock_xinfos(void) +{ + struct path_cxt *pc; + DIR *dir; + struct dirent *d; + + self_netns_fd = open("/proc/self/ns/net", O_RDONLY); + + if (self_netns_fd < 0) + load_sock_xinfo_no_nsswitch(NULL); + else { + if (fstat(self_netns_fd, &self_netns_sb) == 0) { + struct netns *nsobj = mark_sock_xinfo_loaded(self_netns_sb.st_ino); + load_sock_xinfo_no_nsswitch(nsobj); + } + } + + /* Load /proc/net/{unix,...} of the network namespace + * specified with netns files under /var/run/netns/. + * + * `ip netns' command pins a network namespace on + * /var/run/netns. + */ + pc = ul_new_path("/var/run/netns"); + if (!pc) + err(EXIT_FAILURE, _("failed to alloc path context for /var/run/netns")); + dir = ul_path_opendir(pc, NULL); + if (dir == NULL) { + ul_unref_path(pc); + return; + } + while ((d = readdir(dir))) { + struct stat sb; + int fd; + struct netns *nsobj; + if (ul_path_stat(pc, &sb, 0, d->d_name) < 0) + continue; + if (is_sock_xinfo_loaded(sb.st_ino)) + continue; + nsobj = mark_sock_xinfo_loaded(sb.st_ino); + fd = ul_path_open(pc, O_RDONLY, d->d_name); + if (fd < 0) + continue; + load_sock_xinfo_with_fd(fd, nsobj); + close(fd); + } + closedir(dir); + ul_unref_path(pc); +} + +static void free_sock_xinfo(void *node) +{ + struct sock_xinfo *xinfo = node; + if (xinfo->class->free) + xinfo->class->free(xinfo); + free(node); +} + +void finalize_sock_xinfos(void) +{ + if (self_netns_fd != -1) + close(self_netns_fd); + tdestroy(netns_tree, netns_free); + tdestroy(xinfo_tree, free_sock_xinfo); +} + +static int xinfo_compare(const void *a, const void *b) +{ + return ((struct sock_xinfo *)a)->inode - ((struct sock_xinfo *)b)->inode; +} + +static void add_sock_info(struct sock_xinfo *xinfo) +{ + struct sock_xinfo **tmp = tsearch(xinfo, &xinfo_tree, xinfo_compare); + + if (tmp == NULL) + errx(EXIT_FAILURE, _("failed to allocate memory")); +} + +struct sock_xinfo *get_sock_xinfo(ino_t netns_inode) +{ + struct sock_xinfo key = { .inode = netns_inode }; + struct sock_xinfo **xinfo = tfind(&key, &xinfo_tree, xinfo_compare); + + if (xinfo) + return *xinfo; + return NULL; +} + +bool is_nsfs_dev(dev_t dev) +{ + return dev == self_netns_sb.st_dev; +} + +static const char *sock_decode_type(uint16_t type) +{ + switch (type) { + case SOCK_STREAM: + return "stream"; + case SOCK_DGRAM: + return "dgram"; + case SOCK_RAW: + return "raw"; + case SOCK_RDM: + return "rdm"; + case SOCK_SEQPACKET: + return "seqpacket"; + case SOCK_DCCP: + return "dccp"; + case SOCK_PACKET: + return "packet"; + default: + return "unknown"; + } +} + +/* + * Protocol specific code + */ + +/* + * UNIX + */ +struct unix_xinfo { + struct sock_xinfo sock; + int acceptcon; /* flags */ + uint16_t type; + uint8_t st; + char path[ + UNIX_PATH_MAX + + 1 /* for @ */ + + 1 /* \0? */ + ]; +}; + +static const char *unix_decode_state(uint8_t st) +{ + switch (st) { + case SS_FREE: + return "free"; + case SS_UNCONNECTED: + return "unconnected"; + case SS_CONNECTING: + return "connecting"; + case SS_CONNECTED: + return "connected"; + case SS_DISCONNECTING: + return "disconnecting"; + default: + return "unknown"; + } +} + +static char *unix_get_name(struct sock_xinfo *sock_xinfo, + struct sock *sock) +{ + struct unix_xinfo *ux = (struct unix_xinfo *)sock_xinfo; + const char *state = unix_decode_state(ux->st); + char *str = NULL; + + if (sock->protoname && (strcmp(sock->protoname, "UNIX-STREAM") == 0)) + xasprintf(&str, "state=%s%s%s", + (ux->acceptcon)? "listen": state, + *(ux->path)? " path=": "", + *(ux->path)? ux->path: ""); + else + xasprintf(&str, "state=%s%s%s type=%s", + (ux->acceptcon)? "listen": state, + *(ux->path)? " path=": "", + *(ux->path)? ux->path: "", + sock_decode_type(ux->type)); + return str; +} + +static char *unix_get_type(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + const char *str; + struct unix_xinfo *ux = (struct unix_xinfo *)sock_xinfo; + + str = sock_decode_type(ux->type); + return xstrdup(str); +} + +static char *unix_get_state(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + const char *str; + struct unix_xinfo *ux = (struct unix_xinfo *)sock_xinfo; + + if (ux->acceptcon) + return xstrdup("listen"); + + str = unix_decode_state(ux->st); + return xstrdup(str); +} + +static bool unix_get_listening(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + struct unix_xinfo *ux = (struct unix_xinfo *)sock_xinfo; + + return ux->acceptcon; +} + +static bool unix_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + 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; + + switch (column_id) { + case COL_UNIX_PATH: + if (*ux->path) { + *str = xstrdup(ux->path); + return true; + } + break; + } + + return false; +} + +static const struct sock_xinfo_class unix_xinfo_class = { + .get_name = unix_get_name, + .get_type = unix_get_type, + .get_state = unix_get_state, + .get_listening = unix_get_listening, + .fill_column = unix_fill_column, + .free = NULL, +}; + +/* UNIX_LINE_LEN need at least 54 + 21 + UNIX_PATH_MAX + 1. + * + * An actual number must be used in this definition + * since UNIX_LINE_LEN is specified as an argument for + * stringify_value(). + */ +#define UNIX_LINE_LEN 256 +static void load_xinfo_from_proc_unix(ino_t netns_inode) +{ + char line[UNIX_LINE_LEN]; + FILE *unix_fp; + + unix_fp = fopen("/proc/net/unix", "r"); + if (!unix_fp) + return; + + if (fgets(line, sizeof(line), unix_fp) == NULL) + goto out; + if (!(line[0] == 'N' && line[1] == 'u' && line[2] == 'm')) + /* Unexpected line */ + goto out; + + while (fgets(line, sizeof(line), unix_fp)) { + uint64_t flags; + uint32_t type; + unsigned int st; + unsigned long inode; + struct unix_xinfo *ux; + char path[UNIX_LINE_LEN + 1] = { 0 }; + + + if (sscanf(line, "%*x: %*x %*x %" SCNx64 " %x %x %lu %" + stringify_value(UNIX_LINE_LEN) "[^\n]", + &flags, &type, &st, &inode, path) < 4) + continue; + + if (inode == 0) + continue; + + ux = xcalloc(1, sizeof(*ux)); + ux->sock.class = &unix_xinfo_class; + ux->sock.inode = (ino_t)inode; + ux->sock.netns_inode = netns_inode; + + ux->acceptcon = !!flags; + ux->type = type; + ux->st = st; + xstrncpy(ux->path, path, sizeof(ux->path)); + + add_sock_info(&ux->sock); + } + + out: + fclose(unix_fp); +} + +/* + * AF_INET + */ +struct inet_xinfo { + struct sock_xinfo sock; + struct in_addr local_addr; + struct in_addr remote_addr; +}; + +static uint32_t kernel32_to_cpu(enum sysfs_byteorder byteorder, uint32_t v) +{ + if (byteorder == SYSFS_BYTEORDER_LITTLE) + return le32_to_cpu(v); + else + return be32_to_cpu(v); +} + +/* + * AF_INET6 + */ +struct inet6_xinfo { + struct sock_xinfo sock; + struct in6_addr local_addr; + struct in6_addr remote_addr; +}; + +/* + * L4 abstract-layer for protocols stacked on IP and IP6. + */ +enum l4_state { + /* + * Taken from linux/include/net/tcp_states.h. + * (GPL-2.0-or-later) + * + * UDP and RAW sockets also uses the contents in Linux. + */ + TCP_ESTABLISHED = 1, + TCP_SYN_SENT, + TCP_SYN_RECV, + TCP_FIN_WAIT1, + TCP_FIN_WAIT2, + TCP_TIME_WAIT, + TCP_CLOSE, + TCP_CLOSE_WAIT, + TCP_LAST_ACK, + TCP_LISTEN, + TCP_CLOSING, + TCP_NEW_SYN_RECV, + + TCP_MAX_STATES /* Leave at the end! */ +}; + +static const char *l4_decode_state(enum l4_state st) +{ + const char * table [] = { + [TCP_ESTABLISHED] = "established", + [TCP_SYN_SENT] = "syn-sent", + [TCP_SYN_RECV] = "syn-recv", + [TCP_FIN_WAIT1] = "fin-wait1", + [TCP_FIN_WAIT2] = "fin-wait2", + [TCP_TIME_WAIT] = "time-wait", + [TCP_CLOSE] = "close", + [TCP_CLOSE_WAIT] = "close-wait", + [TCP_LAST_ACK] = "last-ack", + [TCP_LISTEN] = "listen", + [TCP_CLOSING] = "closing", + [TCP_NEW_SYN_RECV] = "new-syn-recv", + }; + + if (st < TCP_MAX_STATES) + return table[st]; + return "unknown"; +} + +struct l4_xinfo { + union { + struct inet_xinfo inet; + struct inet6_xinfo inet6; + }; + enum l4_state st; +}; + +enum l4_side { L4_LOCAL, L4_REMOTE }; +enum l3_decorator { L3_DECO_START, L3_DECO_END }; + +struct l4_xinfo_class { + struct sock_xinfo_class sock; + struct sock_xinfo *(*scan_line)(const struct sock_xinfo_class *, + char *, + ino_t, + enum sysfs_byteorder); + void * (*get_addr)(struct l4_xinfo *, enum l4_side); + bool (*is_any_addr)(void *); + int family; + const char *l3_decorator[2]; +}; + +#define l3_fill_column_handler(L3, SOCK_XINFO, COLUMN_ID, STR) __extension__ \ + ({ \ + struct l4_xinfo_class *class = (struct l4_xinfo_class *)SOCK_XINFO->class; \ + struct l4_xinfo *l4 = (struct l4_xinfo *)SOCK_XINFO; \ + void *n = NULL; \ + char s[BUFSIZ]; \ + bool r = false; \ + \ + switch (COLUMN_ID) { \ + case COL_##L3##_LADDR: \ + n = class->get_addr(l4, L4_LOCAL); \ + break; \ + case COL_##L3##_RADDR: \ + n = class->get_addr(l4, L4_REMOTE); \ + break; \ + default: \ + break; \ + } \ + \ + if (n && inet_ntop(class->family, n, s, sizeof(s))) { \ + *STR = xstrdup(s); \ + r = true; \ + } \ + r; \ + }) + +/* + * TCP + */ +struct tcp_xinfo { + struct l4_xinfo l4; + uint16_t local_port; + uint16_t remote_port; +}; + +static char *tcp_get_name(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + char *str = NULL; + struct tcp_xinfo *tcp = ((struct tcp_xinfo *)sock_xinfo); + struct l4_xinfo *l4 = &tcp->l4; + const char *st_str = l4_decode_state(l4->st); + struct l4_xinfo_class *class = (struct l4_xinfo_class *)sock_xinfo->class; + void *laddr = class->get_addr(l4, L4_LOCAL); + void *raddr = class->get_addr(l4, L4_REMOTE); + char local_s[BUFSIZ]; + char remote_s[BUFSIZ]; + const char *start = class->l3_decorator[L3_DECO_START]; + const char *end = class->l3_decorator[L3_DECO_END]; + + if (!inet_ntop(class->family, laddr, local_s, sizeof(local_s))) + xasprintf(&str, "state=%s", st_str); + else if (l4->st == TCP_LISTEN + || !inet_ntop(class->family, raddr, remote_s, sizeof(remote_s))) + xasprintf(&str, "state=%s laddr=%s%s%s:%"PRIu16, + st_str, + start, local_s, end, tcp->local_port); + else + xasprintf(&str, "state=%s laddr=%s%s%s:%"PRIu16" raddr=%s%s%s:%"PRIu16, + st_str, + start, local_s, end, tcp->local_port, + start, remote_s, end, tcp->remote_port); + return str; +} + +static char *tcp_get_type(struct sock_xinfo *sock_xinfo __attribute__((__unused__)), + struct sock *sock __attribute__((__unused__))) +{ + return xstrdup("stream"); +} + +static char *tcp_get_state(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + return xstrdup(l4_decode_state(((struct l4_xinfo *)sock_xinfo)->st)); +} + +static bool tcp_get_listening(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + return ((struct l4_xinfo *)sock_xinfo)->st == TCP_LISTEN; +} + +#define l4_fill_column_handler(L4, SOCK_XINFO, COLUMN_ID, STR) __extension__ \ + ({ \ + struct l4_xinfo_class *class = (struct l4_xinfo_class *)SOCK_XINFO->class; \ + struct tcp_xinfo *tcp = (struct tcp_xinfo *)SOCK_XINFO; \ + struct l4_xinfo *l4 = &tcp->l4; \ + void *n = NULL; \ + bool has_laddr = false; \ + unsigned short p; \ + bool has_lport = false; \ + char s[BUFSIZ]; \ + bool r = true; \ + \ + switch (COLUMN_ID) { \ + case COL_##L4##_LADDR: \ + n = class->get_addr(l4, L4_LOCAL); \ + has_laddr = true; \ + p = tcp->local_port; \ + /* FALL THROUGH */ \ + case COL_##L4##_RADDR: \ + if (!has_laddr) { \ + n = class->get_addr(l4, L4_REMOTE); \ + p = tcp->remote_port; \ + } \ + if (n && inet_ntop(class->family, n, s, sizeof(s))) \ + xasprintf(STR, "%s%s%s:%"PRIu16, \ + class->l3_decorator[L3_DECO_START], \ + s, \ + class->l3_decorator[L3_DECO_END], \ + p); \ + break; \ + case COL_##L4##_LPORT: \ + p = tcp->local_port; \ + has_lport = true; \ + /* FALL THROUGH */ \ + case COL_##L4##_RPORT: \ + if (!has_lport) \ + p = tcp->remote_port; \ + xasprintf(STR, "%"PRIu16, p); \ + break; \ + default: \ + r = false; \ + break; \ + } \ + r; \ + }) + +static struct sock_xinfo *tcp_xinfo_scan_line(const struct sock_xinfo_class *class, + char * line, + ino_t netns_inode, + enum sysfs_byteorder byteorder) +{ + unsigned long local_addr; + unsigned long local_port; + unsigned long remote_addr; + unsigned long remote_port; + unsigned long st; + unsigned long long inode; + struct tcp_xinfo *tcp; + struct inet_xinfo *inet; + struct sock_xinfo *sock; + + if (sscanf(line, "%*d: %lx:%lx %lx:%lx %lx %*x:%*x %*x:%*x %*x %*u %*u %lld", + &local_addr, &local_port, &remote_addr, &remote_port, + &st, &inode) != 6) + return NULL; + + if (inode == 0) + return NULL; + + tcp = xcalloc(1, sizeof(*tcp)); + inet = &tcp->l4.inet; + sock = &inet->sock; + sock->class = class; + sock->inode = (ino_t)inode; + sock->netns_inode = netns_inode; + inet->local_addr.s_addr = kernel32_to_cpu(byteorder, local_addr); + tcp->local_port = local_port; + inet->remote_addr.s_addr = kernel32_to_cpu(byteorder, remote_addr); + tcp->remote_port = remote_port; + tcp->l4.st = st; + + return sock; +} + +static void *tcp_xinfo_get_addr(struct l4_xinfo *l4, enum l4_side side) +{ + return (side == L4_LOCAL) + ? &l4->inet.local_addr + : &l4->inet.remote_addr; +} + +static bool tcp_xinfo_is_any_addr(void *addr) +{ + return ((struct in_addr *)addr)->s_addr == INADDR_ANY; +} + +static bool tcp_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + return l3_fill_column_handler(INET, sock_xinfo, column_id, str) + || l4_fill_column_handler(TCP, sock_xinfo, column_id, str); +} + +static const struct l4_xinfo_class tcp_xinfo_class = { + .sock = { + .get_name = tcp_get_name, + .get_type = tcp_get_type, + .get_state = tcp_get_state, + .get_listening = tcp_get_listening, + .fill_column = tcp_fill_column, + .free = NULL, + }, + .scan_line = tcp_xinfo_scan_line, + .get_addr = tcp_xinfo_get_addr, + .is_any_addr = tcp_xinfo_is_any_addr, + .family = AF_INET, + .l3_decorator = {"", ""}, +}; + +static bool L4_verify_initial_line(const char *line) +{ + /* At least we expect two white spaces. */ + if (strncmp(line, " ", 2) != 0) + return false; + line += 2; + + /* Skip white spaces. */ + line = skip_space(line); + + return strncmp(line, "sl", 2) == 0; +} + +#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) +{ + char line[TCP_LINE_LEN]; + FILE *tcp_fp; + + tcp_fp = fopen(proc_file, "r"); + if (!tcp_fp) + return; + + if (fgets(line, sizeof(line), tcp_fp) == NULL) + goto out; + if (!L4_verify_initial_line(line)) + /* 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) + add_sock_info(sock); + } + + out: + fclose(tcp_fp); +} + +static void load_xinfo_from_proc_tcp(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/tcp", + &tcp_xinfo_class); +} + +/* + * UDP + */ +static char *udp_get_name(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + char *str = NULL; + struct tcp_xinfo *tcp = ((struct tcp_xinfo *)sock_xinfo); + struct l4_xinfo *l4 = &tcp->l4; + unsigned int st = l4->st; + const char *st_str = l4_decode_state(st); + struct l4_xinfo_class *class = (struct l4_xinfo_class *)sock_xinfo->class; + void *laddr = class->get_addr(l4, L4_LOCAL); + void *raddr = class->get_addr(l4, L4_REMOTE); + char local_s[BUFSIZ]; + char remote_s[BUFSIZ]; + const char *start = class->l3_decorator[L3_DECO_START]; + const char *end = class->l3_decorator[L3_DECO_END]; + + if (!inet_ntop(class->family, laddr, local_s, sizeof(local_s))) + xasprintf(&str, "state=%s", st_str); + else if ((class->is_any_addr(raddr) && tcp->remote_port == 0) + || !inet_ntop(class->family, raddr, remote_s, sizeof(remote_s))) + xasprintf(&str, "state=%s laddr=%s%s%s:%"PRIu16, + st_str, + start, local_s, end, tcp->local_port); + else + xasprintf(&str, "state=%s laddr=%s%s%s:%"PRIu16" raddr=%s%s%s:%"PRIu16, + st_str, + start, local_s, end, tcp->local_port, + start, remote_s, end, tcp->remote_port); + return str; +} + +static char *udp_get_type(struct sock_xinfo *sock_xinfo __attribute__((__unused__)), + struct sock *sock __attribute__((__unused__))) +{ + return xstrdup("dgram"); +} + +static bool udp_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + return l3_fill_column_handler(INET, sock_xinfo, column_id, str) + || l4_fill_column_handler(UDP, sock_xinfo, column_id, str); +} + +static const struct l4_xinfo_class udp_xinfo_class = { + .sock = { + .get_name = udp_get_name, + .get_type = udp_get_type, + .get_state = tcp_get_state, + .get_listening = NULL, + .fill_column = udp_fill_column, + .free = NULL, + }, + .scan_line = tcp_xinfo_scan_line, + .get_addr = tcp_xinfo_get_addr, + .is_any_addr = tcp_xinfo_is_any_addr, + .family = AF_INET, + .l3_decorator = {"", ""}, +}; + +static void load_xinfo_from_proc_udp(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/udp", + &udp_xinfo_class); +} + +/* + * UDP-Lite + */ +static bool udplite_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + return l3_fill_column_handler(INET, sock_xinfo, column_id, str) + || l4_fill_column_handler(UDPLITE, sock_xinfo, column_id, str); +} + +static const struct l4_xinfo_class udplite_xinfo_class = { + .sock = { + .get_name = udp_get_name, + .get_type = udp_get_type, + .get_state = tcp_get_state, + .get_listening = NULL, + .fill_column = udplite_fill_column, + .free = NULL, + }, + .scan_line = tcp_xinfo_scan_line, + .get_addr = tcp_xinfo_get_addr, + .is_any_addr = tcp_xinfo_is_any_addr, + .family = AF_INET, + .l3_decorator = {"", ""}, +}; + +static void load_xinfo_from_proc_udplite(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/udplite", + &udplite_xinfo_class); +} + +/* + * RAW + */ +struct raw_xinfo { + struct l4_xinfo l4; + uint16_t protocol; +}; + +static char *raw_get_name_common(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + const char *port_label) +{ + char *str = NULL; + struct l4_xinfo_class *class = (struct l4_xinfo_class *)sock_xinfo->class; + struct raw_xinfo *raw = ((struct raw_xinfo *)sock_xinfo); + struct l4_xinfo *l4 = &raw->l4; + const char *st_str = l4_decode_state(l4->st); + void *laddr = class->get_addr(l4, L4_LOCAL); + void *raddr = class->get_addr(l4, L4_REMOTE); + char local_s[BUFSIZ]; + char remote_s[BUFSIZ]; + + if (!inet_ntop(class->family, laddr, local_s, sizeof(local_s))) + xasprintf(&str, "state=%s", st_str); + else if (class->is_any_addr(raddr) + || !inet_ntop(class->family, raddr, remote_s, sizeof(remote_s))) + xasprintf(&str, "state=%s %s=%"PRIu16" laddr=%s", + st_str, + port_label, + raw->protocol, local_s); + else + xasprintf(&str, "state=%s %s=%"PRIu16" laddr=%s raddr=%s", + st_str, + port_label, + raw->protocol, local_s, remote_s); + return str; +} + +static char *raw_get_name(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + return raw_get_name_common(sock_xinfo, sock, "protocol"); +} + +static char *raw_get_type(struct sock_xinfo *sock_xinfo __attribute__((__unused__)), + struct sock *sock __attribute__((__unused__))) +{ + return xstrdup("raw"); +} + +static bool raw_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + if (l3_fill_column_handler(INET, sock_xinfo, column_id, str)) + return true; + + if (column_id == COL_RAW_PROTOCOL) { + xasprintf(str, "%"PRIu16, + ((struct raw_xinfo *)sock_xinfo)->protocol); + return true; + } + + return false; +} + +static struct sock_xinfo *raw_xinfo_scan_line(const struct sock_xinfo_class *class, + char * line, + ino_t netns_inode, + enum sysfs_byteorder byteorder) +{ + unsigned long local_addr; + unsigned long protocol; + unsigned long remote_addr; + unsigned long st; + unsigned long long inode; + struct raw_xinfo *raw; + struct inet_xinfo *inet; + struct sock_xinfo *sock; + + if (sscanf(line, "%*d: %lx:%lx %lx:%*x %lx %*x:%*x %*x:%*x %*x %*u %*u %lld", + &local_addr, &protocol, &remote_addr, + &st, &inode) != 5) + return NULL; + + if (inode == 0) + return NULL; + + raw = xcalloc(1, sizeof(*raw)); + inet = &raw->l4.inet; + sock = &inet->sock; + sock->class = class; + sock->inode = (ino_t)inode; + sock->netns_inode = netns_inode; + inet->local_addr.s_addr = kernel32_to_cpu(byteorder, local_addr); + inet->remote_addr.s_addr = kernel32_to_cpu(byteorder, remote_addr); + raw->protocol = protocol; + raw->l4.st = st; + + return sock; +} + +static const struct l4_xinfo_class raw_xinfo_class = { + .sock = { + .get_name = raw_get_name, + .get_type = raw_get_type, + .get_state = tcp_get_state, + .get_listening = NULL, + .fill_column = raw_fill_column, + .free = NULL, + }, + .scan_line = raw_xinfo_scan_line, + .get_addr = tcp_xinfo_get_addr, + .is_any_addr = tcp_xinfo_is_any_addr, + .family = AF_INET, + .l3_decorator = {"", ""}, +}; + +static void load_xinfo_from_proc_raw(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/raw", + &raw_xinfo_class); +} + +/* + * PING + */ +static char *ping_get_name(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + return raw_get_name_common(sock_xinfo, sock, "id"); +} + +static char *ping_get_type(struct sock_xinfo *sock_xinfo __attribute__((__unused__)), + struct sock *sock __attribute__((__unused__))) +{ + return xstrdup("dgram"); +} + +static bool ping_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + if (l3_fill_column_handler(INET, sock_xinfo, column_id, str)) + return true; + + if (column_id == COL_PING_ID) { + xasprintf(str, "%"PRIu16, + ((struct raw_xinfo *)sock_xinfo)->protocol); + return true; + } + + return false; +} + +static const struct l4_xinfo_class ping_xinfo_class = { + .sock = { + .get_name = ping_get_name, + .get_type = ping_get_type, + .get_state = tcp_get_state, + .get_listening = NULL, + .fill_column = ping_fill_column, + .free = NULL, + }, + .scan_line = raw_xinfo_scan_line, + .get_addr = tcp_xinfo_get_addr, + .is_any_addr = tcp_xinfo_is_any_addr, + .family = AF_INET, + .l3_decorator = {"", ""}, +}; + +static void load_xinfo_from_proc_icmp(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/icmp", + &ping_xinfo_class); +} + +/* + * TCP6 + */ +static struct sock_xinfo *tcp6_xinfo_scan_line(const struct sock_xinfo_class *class, + char * line, + ino_t netns_inode, + enum sysfs_byteorder byteorder) +{ + uint32_t local_addr[4]; + unsigned int local_port; + uint32_t remote_addr[4]; + unsigned int remote_port; + unsigned int st; + unsigned long inode; + struct tcp_xinfo *tcp; + struct inet6_xinfo *inet6; + struct sock_xinfo *sock; + + if (sscanf(line, + "%*d: " + "%08x%08x%08x%08x:%04x " + "%08x%08x%08x%08x:%04x " + "%x %*x:%*x %*x:%*x %*x %*u %*d %lu ", + local_addr+0, local_addr+1, local_addr+2, local_addr+3, &local_port, + remote_addr+0, remote_addr+1, remote_addr+2, remote_addr+3, &remote_port, + &st, &inode) != 12) + return NULL; + + if (inode == 0) + return NULL; + + tcp = xmalloc(sizeof(*tcp)); + inet6 = &tcp->l4.inet6; + sock = &inet6->sock; + sock->class = class; + sock->inode = (ino_t)inode; + sock->netns_inode = netns_inode; + tcp->local_port = local_port; + for (int i = 0; i < 4; i++) { + inet6->local_addr.s6_addr32[i] = kernel32_to_cpu(byteorder, local_addr[i]); + inet6->remote_addr.s6_addr32[i] = kernel32_to_cpu(byteorder, remote_addr[i]); + } + tcp->remote_port = remote_port; + tcp->l4.st = st; + + return sock; +} + +static bool tcp6_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + return l3_fill_column_handler(INET6, sock_xinfo, column_id, str) + || l4_fill_column_handler(TCP, sock_xinfo, column_id, str); +} + +static void *tcp6_xinfo_get_addr(struct l4_xinfo * l4, enum l4_side side) +{ + return (side == L4_LOCAL) + ? &l4->inet6.local_addr + : &l4->inet6.remote_addr; +} + +static bool tcp6_xinfo_is_any_addr(void *addr) +{ + return IN6_ARE_ADDR_EQUAL(addr, &(struct in6_addr)IN6ADDR_ANY_INIT); +} + +static const struct l4_xinfo_class tcp6_xinfo_class = { + .sock = { + .get_name = tcp_get_name, + .get_type = tcp_get_type, + .get_state = tcp_get_state, + .get_listening = tcp_get_listening, + .fill_column = tcp6_fill_column, + .free = NULL, + }, + .scan_line = tcp6_xinfo_scan_line, + .get_addr = tcp6_xinfo_get_addr, + .is_any_addr = tcp6_xinfo_is_any_addr, + .family = AF_INET6, + .l3_decorator = {"[", "]"}, +}; + +static void load_xinfo_from_proc_tcp6(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/tcp6", + &tcp6_xinfo_class); +} + +/* + * UDP6 + */ +static bool udp6_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + return l3_fill_column_handler(INET6, sock_xinfo, column_id, str) + || l4_fill_column_handler(UDP, sock_xinfo, column_id, str); +} + +static const struct l4_xinfo_class udp6_xinfo_class = { + .sock = { + .get_name = udp_get_name, + .get_type = udp_get_type, + .get_state = tcp_get_state, + .get_listening = NULL, + .fill_column = udp6_fill_column, + .free = NULL, + }, + .scan_line = tcp6_xinfo_scan_line, + .get_addr = tcp6_xinfo_get_addr, + .is_any_addr = tcp6_xinfo_is_any_addr, + .family = AF_INET6, + .l3_decorator = {"[", "]"}, +}; + +static void load_xinfo_from_proc_udp6(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/udp6", + &udp6_xinfo_class); +} + +/* + * UDPLITEv6 + */ +static bool udplite6_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + return l3_fill_column_handler(INET6, sock_xinfo, column_id, str) + || l4_fill_column_handler(UDPLITE, sock_xinfo, column_id, str); +} + +static const struct l4_xinfo_class udplite6_xinfo_class = { + .sock = { + .get_name = udp_get_name, + .get_type = udp_get_type, + .get_state = tcp_get_state, + .get_listening = NULL, + .fill_column = udplite6_fill_column, + .free = NULL, + }, + .scan_line = tcp6_xinfo_scan_line, + .get_addr = tcp6_xinfo_get_addr, + .is_any_addr = tcp6_xinfo_is_any_addr, + .family = AF_INET6, + .l3_decorator = {"[", "]"}, +}; + +static void load_xinfo_from_proc_udplite6(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/udplite6", + &udplite6_xinfo_class); +} + +/* + * RAW6 + */ +static struct sock_xinfo *raw6_xinfo_scan_line(const struct sock_xinfo_class *class, + char * line, + ino_t netns_inode, + enum sysfs_byteorder byteorder) +{ + uint32_t local_addr[4]; + unsigned int protocol; + uint32_t remote_addr[4]; + unsigned int st; + unsigned long inode; + struct raw_xinfo *raw; + struct inet6_xinfo *inet6; + struct sock_xinfo *sock; + + if (sscanf(line, + "%*d: " + "%08x%08x%08x%08x:%04x " + "%08x%08x%08x%08x:0000 " + "%x %*x:%*x %*x:%*x %*x %*u %*d %lu ", + local_addr+0, local_addr+1, local_addr+2, local_addr+3, &protocol, + remote_addr+0, remote_addr+1, remote_addr+2, remote_addr+3, + &st, &inode) != 11) + return NULL; + + if (inode == 0) + return NULL; + + raw = xmalloc(sizeof(*raw)); + inet6 = &raw->l4.inet6; + sock = &inet6->sock; + sock->class = class; + sock->inode = (ino_t)inode; + sock->netns_inode = netns_inode; + for (int i = 0; i < 4; i++) { + inet6->local_addr.s6_addr32[i] = kernel32_to_cpu(byteorder, local_addr[i]); + inet6->remote_addr.s6_addr32[i] = kernel32_to_cpu(byteorder, remote_addr[i]); + } + raw->protocol = protocol; + raw->l4.st = st; + + return sock; +} + +static bool raw6_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct raw_xinfo *raw; + + if (l3_fill_column_handler(INET6, sock_xinfo, column_id, str)) + return true; + + raw = (struct raw_xinfo *)sock_xinfo; + if (column_id == COL_RAW_PROTOCOL) { + xasprintf(str, "%"PRIu16, raw->protocol); + return true; + } + + return false; +} + +static const struct l4_xinfo_class raw6_xinfo_class = { + .sock = { + .get_name = raw_get_name, + .get_type = raw_get_type, + .get_state = tcp_get_state, + .get_listening = NULL, + .fill_column = raw6_fill_column, + .free = NULL, + }, + .scan_line = raw6_xinfo_scan_line, + .get_addr = tcp6_xinfo_get_addr, + .is_any_addr = tcp6_xinfo_is_any_addr, + .family = AF_INET6, + .l3_decorator = {"[", "]"}, +}; + +static void load_xinfo_from_proc_raw6(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/raw6", + &raw6_xinfo_class); +} + +/* + * PINGv6 + */ +static bool ping6_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + if (l3_fill_column_handler(INET6, sock_xinfo, column_id, str)) + return true; + + if (column_id == COL_PING_ID) { + xasprintf(str, "%"PRIu16, + ((struct raw_xinfo *)sock_xinfo)->protocol); + return true; + } + + return false; +} + +static const struct l4_xinfo_class ping6_xinfo_class = { + .sock = { + .get_name = ping_get_name, + .get_type = ping_get_type, + .get_state = tcp_get_state, + .get_listening = NULL, + .fill_column = ping6_fill_column, + .free = NULL, + }, + .scan_line = raw6_xinfo_scan_line, + .get_addr = tcp6_xinfo_get_addr, + .is_any_addr = tcp6_xinfo_is_any_addr, + .family = AF_INET6, + .l3_decorator = {"[", "]"}, +}; + +static void load_xinfo_from_proc_icmp6(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/icmp6", + &ping6_xinfo_class); +} + +/* + * NETLINK + */ +struct netlink_xinfo { + struct sock_xinfo sock; + uint16_t protocol; + uint32_t lportid; /* netlink_diag may provide rportid. */ + uint32_t groups; +}; + +static const char *netlink_decode_protocol(uint16_t protocol) +{ + switch (protocol) { + case NETLINK_ROUTE: + return "route"; + case NETLINK_UNUSED: + return "unused"; + case NETLINK_USERSOCK: + return "usersock"; + case NETLINK_FIREWALL: + return "firewall"; + case NETLINK_SOCK_DIAG: + return "sock_diag"; + case NETLINK_NFLOG: + return "nflog"; + case NETLINK_XFRM: + return "xfrm"; + case NETLINK_SELINUX: + return "selinux"; + case NETLINK_ISCSI: + return "iscsi"; + case NETLINK_AUDIT: + return "audit"; + case NETLINK_FIB_LOOKUP: + return "fib_lookup"; + case NETLINK_CONNECTOR: + return "connector"; + case NETLINK_NETFILTER: + return "netfilter"; + case NETLINK_IP6_FW: + return "ip6_fw"; + case NETLINK_DNRTMSG: + return "dnrtmsg"; + case NETLINK_KOBJECT_UEVENT: + return "kobject_uevent"; + case NETLINK_GENERIC: + return "generic"; + case NETLINK_SCSITRANSPORT: + return "scsitransport"; + case NETLINK_ECRYPTFS: + return "ecryptfs"; + case NETLINK_RDMA: + return "rdma"; + case NETLINK_CRYPTO: + return "crypto"; +#ifdef NETLINK_SMC + case NETLINK_SMC: + return "smc"; +#endif + default: + return "unknown"; + } +} + +static char *netlink_get_name(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + struct netlink_xinfo *nl = (struct netlink_xinfo *)sock_xinfo; + char *str = NULL; + const char *protocol = netlink_decode_protocol(nl->protocol); + + if (nl->groups) + xasprintf(&str, "protocol=%s lport=%"PRIu16 " groups=%"PRIu32, + protocol, + nl->lportid, nl->groups); + else + xasprintf(&str, "protocol=%s lport=%"PRIu16, + protocol, + nl->lportid); + return str; +} + +static char *netlink_get_type(struct sock_xinfo *sock_xinfo __attribute__((__unused__)), + struct sock *sock __attribute__((__unused__))) +{ + return xstrdup("raw"); +} + +static bool netlink_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct netlink_xinfo *nl = (struct netlink_xinfo *)sock_xinfo; + + switch (column_id) { + case COL_NETLINK_GROUPS: + xasprintf(str, "%"PRIu32, nl->groups); + return true; + case COL_NETLINK_LPORT: + xasprintf(str, "%"PRIu32, nl->lportid); + return true; + case COL_NETLINK_PROTOCOL: + *str = xstrdup(netlink_decode_protocol(nl->protocol)); + return true; + } + + return false; +} + +static const struct sock_xinfo_class netlink_xinfo_class = { + .get_name = netlink_get_name, + .get_type = netlink_get_type, + .get_state = NULL, + .get_listening = NULL, + .fill_column = netlink_fill_column, + .free = NULL, +}; + +static void load_xinfo_from_proc_netlink(ino_t netns_inode) +{ + char line[BUFSIZ]; + FILE *netlink_fp; + + netlink_fp = fopen("/proc/net/netlink", "r"); + if (!netlink_fp) + return; + + if (fgets(line, sizeof(line), netlink_fp) == NULL) + goto out; + if (!(line[0] == 's' && line[1] == 'k')) + /* Unexpected line */ + goto out; + + while (fgets(line, sizeof(line), netlink_fp)) { + uint16_t protocol; + uint32_t lportid; + uint32_t groups; + unsigned long inode; + struct netlink_xinfo *nl; + + if (sscanf(line, "%*x %" SCNu16 " %" SCNu32 " %" SCNx32 " %*d %*d %*d %*d %*u %lu", + &protocol, &lportid, &groups, &inode) < 4) + continue; + + if (inode == 0) + continue; + + nl = xcalloc(1, sizeof(*nl)); + nl->sock.class = &netlink_xinfo_class; + nl->sock.inode = (ino_t)inode; + nl->sock.netns_inode = netns_inode; + + nl->protocol = protocol; + nl->lportid = lportid; + nl->groups = groups; + + add_sock_info(&nl->sock); + } + + out: + fclose(netlink_fp); +} + +/* + * PACKET + */ +struct packet_xinfo { + struct sock_xinfo sock; + uint16_t type; + uint16_t protocol; + unsigned int iface; +}; + +static const char *packet_decode_protocol(uint16_t proto) +{ + switch (proto) { + case 0: + return NULL; + case ETH_P_802_3: + return "802_3"; + case ETH_P_AX25: + return "ax25"; + case ETH_P_ALL: + return "all"; + case ETH_P_802_2: + return "802_2"; + case ETH_P_SNAP: + return "snap"; + case ETH_P_DDCMP: + return "ddcmp"; + case ETH_P_WAN_PPP: + return "wan_ppp"; + case ETH_P_PPP_MP: + return "ppp_mp"; + case ETH_P_LOCALTALK: + return "localtalk"; + case ETH_P_CAN: + return "can"; + case ETH_P_CANFD: + return "canfd"; +#ifdef ETH_P_CANXL + case ETH_P_CANXL: + return "canxl"; +#endif + case ETH_P_PPPTALK: + return "ppptalk"; + case ETH_P_TR_802_2: + return "tr_802_2"; + case ETH_P_MOBITEX: + return "mobitex"; + case ETH_P_CONTROL: + return "control"; + case ETH_P_IRDA: + return "irda"; + case ETH_P_ECONET: + return "econet"; + case ETH_P_HDLC: + return "hdlc"; + case ETH_P_ARCNET: + return "arcnet"; + case ETH_P_DSA: + return "dsa"; + case ETH_P_TRAILER: + return "trailer"; + case ETH_P_PHONET: + return "phonet"; + case ETH_P_IEEE802154: + return "ieee802154"; + case ETH_P_CAIF: + return "caif"; +#ifdef ETH_P_XDSA + case ETH_P_XDSA: + return "xdsa"; +#endif +#ifdef ETH_P_MAP + case ETH_P_MAP: + return "map"; +#endif +#ifdef ETH_P_MCTP + case ETH_P_MCTP: + return "mctp"; +#endif + case ETH_P_LOOP: + return "loop"; + case ETH_P_PUP: + return "pup"; + case ETH_P_PUPAT: + return "pupat"; +#ifdef ETH_P_TSN + case ETH_P_TSN: + return "tsn"; +#endif +#ifdef ETH_P_ERSPAN2 + case ETH_P_ERSPAN2: + return "erspan2"; +#endif + case ETH_P_IP: + return "ip"; + case ETH_P_X25: + return "x25"; + case ETH_P_ARP: + return "arp"; + case ETH_P_BPQ: + return "bpq"; + case ETH_P_IEEEPUP: + return "ieeepup"; + case ETH_P_IEEEPUPAT: + return "ieeepupat"; + case ETH_P_BATMAN: + return "batman"; + case ETH_P_DEC: + return "dec"; + case ETH_P_DNA_DL: + return "dna_dl"; + case ETH_P_DNA_RC: + return "dna_rc"; + case ETH_P_DNA_RT: + return "dna_rt"; + case ETH_P_LAT: + return "lat"; + case ETH_P_DIAG: + return "diag"; + case ETH_P_CUST: + return "cust"; + case ETH_P_SCA: + return "sca"; + case ETH_P_TEB: + return "teb"; + case ETH_P_RARP: + return "rarp"; + case ETH_P_ATALK: + return "atalk"; + case ETH_P_AARP: + return "aarp"; + case ETH_P_8021Q: + return "8021q"; +#ifdef ETH_P_ERSPAN + case ETH_P_ERSPAN: + return "erspan"; +#endif + case ETH_P_IPX: + return "ipx"; + case ETH_P_IPV6: + return "ipv6"; + case ETH_P_PAUSE: + return "pause"; + case ETH_P_SLOW: + return "slow"; + case ETH_P_WCCP: + return "wccp"; + case ETH_P_MPLS_UC: + return "mpls_uc"; + case ETH_P_MPLS_MC: + return "mpls_mc"; + case ETH_P_ATMMPOA: + return "atmmpoa"; +#ifdef ETH_P_PPP_DISC + case ETH_P_PPP_DISC: + return "ppp_disc"; +#endif +#ifdef ETH_P_PPP_SES + case ETH_P_PPP_SES: + return "ppp_ses"; +#endif + case ETH_P_LINK_CTL: + return "link_ctl"; + case ETH_P_ATMFATE: + return "atmfate"; + case ETH_P_PAE: + return "pae"; +#ifdef ETH_P_PROFINET + case ETH_P_PROFINET: + return "profinet"; +#endif +#ifdef ETH_P_REALTEK + case ETH_P_REALTEK: + return "realtek"; +#endif + case ETH_P_AOE: + return "aoe"; +#ifdef ETH_P_ETHERCAT + case ETH_P_ETHERCAT: + return "ethercat"; +#endif + case ETH_P_8021AD: + return "8021ad"; + case ETH_P_802_EX1: + return "802_ex1"; +#ifdef ETH_P_PREAUTH + case ETH_P_PREAUTH: + return "preauth"; +#endif + case ETH_P_TIPC: + return "tipc"; +#ifdef ETH_P_LLDP + case ETH_P_LLDP: + return "lldp"; +#endif +#ifdef ETH_P_MRP + case ETH_P_MRP: + return "mrp"; +#endif +#ifdef ETH_P_MACSEC + case ETH_P_MACSEC: + return "macsec"; +#endif + case ETH_P_8021AH: + return "8021ah"; +#ifdef ETH_P_MVRP + case ETH_P_MVRP: + return "mvrp"; +#endif + case ETH_P_1588: + return "1588"; +#ifdef ETH_P_NCSI + case ETH_P_NCSI: + return "ncsi"; +#endif +#ifdef ETH_P_PRP + case ETH_P_PRP: + return "prp"; +#endif +#ifdef ETH_P_CFM + case ETH_P_CFM: + return "cfm"; +#endif + case ETH_P_FCOE: + return "fcoe"; +#ifdef ETH_P_IBOE + case ETH_P_IBOE: + return "iboe"; +#endif + case ETH_P_TDLS: + return "tdls"; + case ETH_P_FIP: + return "fip"; +#ifdef ETH_P_80221 + case ETH_P_80221: + return "80221"; +#endif +#ifdef ETH_P_HSR + case ETH_P_HSR: + return "hsr"; +#endif +#ifdef ETH_P_NSH + case ETH_P_NSH: + return "nsh"; +#endif +#ifdef ETH_P_LOOPBACK + case ETH_P_LOOPBACK: + return "loopback"; +#endif + case ETH_P_QINQ1: + return "qinq1"; + case ETH_P_QINQ2: + return "qinq2"; + case ETH_P_QINQ3: + return "qinq3"; + case ETH_P_EDSA: + return "edsa"; +#ifdef ETH_P_DSA_8021Q + case ETH_P_DSA_8021Q: + return "dsa_8021q"; +#endif +#ifdef ETH_P_DSA_A5PSW + case ETH_P_DSA_A5PSW: + return "dsa_a5psw"; +#endif +#ifdef ETH_P_IFE + case ETH_P_IFE: + return "ife"; +#endif + case ETH_P_AF_IUCV: + return "af_iucv"; +#ifdef ETH_P_802_3_MIN + case ETH_P_802_3_MIN: + return "802_3_min"; +#endif + default: + return "unknown"; + } +} + +static char *packet_get_name(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + struct packet_xinfo *pkt = (struct packet_xinfo *)sock_xinfo; + char *str = NULL; + const char *type = sock_decode_type(pkt->type); + const char *proto = packet_decode_protocol(pkt->protocol); + const char *iface = get_iface_name(sock_xinfo->netns_inode, + pkt->iface); + + if (iface && proto) + xasprintf(&str, "type=%s protocol=%s iface=%s", + type, proto, iface); + else if (proto) + xasprintf(&str, "type=%s protocol=%s", + type, proto); + else if (iface) + xasprintf(&str, "type=%s iface=%s", + type, iface); + else + xasprintf(&str, "type=%s", type); + + return str; +} + +static char *packet_get_type(struct sock_xinfo *sock_xinfo __attribute__((__unused__)), + struct sock *sock __attribute__((__unused__))) +{ + const char *str; + struct packet_xinfo *pkt = (struct packet_xinfo *)sock_xinfo; + + str = sock_decode_type(pkt->type); + return xstrdup(str); +} + +static bool packet_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct packet_xinfo *pkt = (struct packet_xinfo *)sock_xinfo; + + switch (column_id) { + case COL_PACKET_IFACE: { + const char *iface; + iface = get_iface_name(sock_xinfo->netns_inode, + pkt->iface); + if (iface) { + *str = xstrdup(iface); + return true; + } + break; + } + case COL_PACKET_PROTOCOL: { + const char *proto; + proto = packet_decode_protocol(pkt->protocol); + if (proto) { + *str = xstrdup(proto); + return true; + } + break; + } + default: + break; + } + return false; +} + +static const struct sock_xinfo_class packet_xinfo_class = { + .get_name = packet_get_name, + .get_type = packet_get_type, + .get_state = NULL, + .get_listening = NULL, + .fill_column = packet_fill_column, + .free = NULL, +}; + +static void load_xinfo_from_proc_packet(ino_t netns_inode) +{ + char line[BUFSIZ]; + FILE *packet_fp; + + packet_fp = fopen("/proc/net/packet", "r"); + if (!packet_fp) + return; + + if (fgets(line, sizeof(line), packet_fp) == NULL) + goto out; + if (!(line[0] == 's' && line[1] == 'k')) + /* Unexpected line */ + goto out; + + while (fgets(line, sizeof(line), packet_fp)) { + uint16_t type; + uint16_t protocol; + unsigned int iface; + unsigned long inode; + struct packet_xinfo *pkt; + + if (sscanf(line, "%*x %*d %" SCNu16 " %" SCNu16 " %u %*d %*d %*d %lu", + &type, &protocol, &iface, &inode) < 4) + continue; + + pkt = xcalloc(1, sizeof(*pkt)); + pkt->sock.class = &packet_xinfo_class; + pkt->sock.inode = (ino_t)inode; + pkt->sock.netns_inode = netns_inode; + + pkt->type = type; + pkt->protocol = protocol; + pkt->iface = iface; + + add_sock_info(&pkt->sock); + } + + out: + fclose(packet_fp); +} diff --git a/misc-utils/lsfd-sock.c b/misc-utils/lsfd-sock.c new file mode 100644 index 0000000..3264516 --- /dev/null +++ b/misc-utils/lsfd-sock.c @@ -0,0 +1,180 @@ +/* + * lsfd-sock.c - handle associations opening socket objects + * + * Copyright (C) 2021 Red Hat, Inc. All rights reserved. + * Written by Masatake YAMATO <yamato@redhat.com> + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#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); +} + +static bool sock_fill_column(struct proc *proc __attribute__((__unused__)), + struct file *file, + struct libscols_line *ln, + int column_id, + size_t column_index) +{ + char *str = NULL; + struct sock *sock = (struct sock *)file; + switch(column_id) { + case COL_TYPE: + if (!sock->protoname) + return false; + /* FALL THROUGH */ + case COL_SOCK_PROTONAME: + if (sock->protoname) + if (scols_line_set_data(ln, column_index, sock->protoname)) + err(EXIT_FAILURE, _("failed to add output data")); + return true; + case COL_NAME: + if (sock->xinfo + && sock->xinfo->class && sock->xinfo->class->get_name) { + str = sock->xinfo->class->get_name(sock->xinfo, sock); + if (str) + break; + } + return false; + case COL_SOURCE: + if (major(file->stat.st_dev) == 0 + && strncmp(file->name, "socket:", 7) == 0) { + str = xstrdup("sockfs"); + break; + } + return false; + case COL_SOCK_NETNS: + if (sock->xinfo) { + xasprintf(&str, "%llu", + (unsigned long long)sock->xinfo->netns_inode); + break; + } + return false; + case COL_SOCK_TYPE: + if (sock->xinfo + && sock->xinfo->class && sock->xinfo->class->get_type) { + str = sock->xinfo->class->get_type(sock->xinfo, sock); + if (str) + break; + } + return false; + case COL_SOCK_STATE: + if (sock->xinfo + && sock->xinfo->class && sock->xinfo->class->get_state) { + str = sock->xinfo->class->get_state(sock->xinfo, sock); + if (str) + break; + } + return false; + case COL_SOCK_LISTENING: + str = xstrdup((sock->xinfo + && sock->xinfo->class + && sock->xinfo->class->get_listening + && sock->xinfo->class->get_listening(sock->xinfo, sock)) + ? "1" + : "0"); + 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; + } + + if (!str) + err(EXIT_FAILURE, _("failed to add output data")); + if (scols_line_refer_data(ln, column_index, str)) + err(EXIT_FAILURE, _("failed to add output data")); + return true; +} + +static void init_sock_content(struct file *file) +{ + int fd; + + 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; + + assert(file->proc); + + if (is_opened_file(file)) + sprintf(path, "/proc/%d/fd/%d", file->proc->pid, fd); + else + sprintf(path, "/proc/%d/map_files/%"PRIx64 "-%" PRIx64, + file->proc->pid, + file->map_start, + file->map_end); + + len = getxattr(path, "system.sockprotoname", buf, sizeof(buf) - 1); + if (len > 0) { + buf[len] = '\0'; + sock->protoname = xstrdup(buf); + } + } +} + +static void free_sock_content(struct file *file) +{ + struct sock *sock = (struct sock *)file; + if (sock->protoname) { + free(sock->protoname); + sock->protoname = NULL; + } +} + +static void initialize_sock_class(void) +{ + initialize_sock_xinfos(); +} + +static void finalize_sock_class(void) +{ + finalize_sock_xinfos(); +} + +const struct file_class sock_class = { + .super = &file_class, + .size = sizeof(struct sock), + .fill_column = sock_fill_column, + .attach_xinfo = attach_sock_xinfo, + .initialize_content = init_sock_content, + .free_content = free_sock_content, + .initialize_class = initialize_sock_class, + .finalize_class = finalize_sock_class, +}; diff --git a/misc-utils/lsfd-sock.h b/misc-utils/lsfd-sock.h new file mode 100644 index 0000000..d488d0f --- /dev/null +++ b/misc-utils/lsfd-sock.h @@ -0,0 +1,72 @@ +/* + * lsfd(1) - list file descriptors + * + * Copyright (C) 2022 Red Hat, Inc. All rights reserved. + * Written by Masatake YAMATO <yamato@redhat.com> + * + * Very generally based on lsof(8) by Victor A. Abell <abe@purdue.edu> + * It supports multiple OSes. lsfd specializes to Linux. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef UTIL_LINUX_LSFD_SOCK_H +#define UTIL_LINUX_LSFD_SOCK_H + +#include <stdbool.h> +#include <sys/stat.h> + +#include "libsmartcols.h" + +/* + * xinfo: eXtra inforation about sockets + */ +struct sock_xinfo { + ino_t inode; /* inode in sockfs */ + ino_t netns_inode; /* inode of netns where + the socket belongs to */ + const struct sock_xinfo_class *class; +}; + +struct sock { + struct file file; + char *protoname; + struct sock_xinfo *xinfo; +}; + +struct sock_xinfo_class { + /* Methods for filling socket related columns */ + char * (*get_name)(struct sock_xinfo *, struct sock *); + char * (*get_type)(struct sock_xinfo *, struct sock *); + char * (*get_state)(struct sock_xinfo *, struct sock *); + bool (*get_listening)(struct sock_xinfo *, struct sock *); + /* Method for class specific columns. + * Return true when the method fills the column. */ + bool (*fill_column)(struct proc *, + struct sock_xinfo *, + struct sock *, + struct libscols_line *, + int, + size_t, + char **str); + + void (*free)(struct sock_xinfo *); +}; + +void initialize_sock_xinfos(void); +void finalize_sock_xinfos(void); + +struct sock_xinfo *get_sock_xinfo(ino_t netns_inode); + +#endif /* UTIL_LINUX_LSFD_SOCK_H */ diff --git a/misc-utils/lsfd-unkn.c b/misc-utils/lsfd-unkn.c new file mode 100644 index 0000000..087e31d --- /dev/null +++ b/misc-utils/lsfd-unkn.c @@ -0,0 +1,282 @@ +/* + * lsfd-unkn.c - handle associations opening unknown objects + * + * Copyright (C) 2021 Red Hat, Inc. All rights reserved. + * Written by Masatake YAMATO <yamato@redhat.com> + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "xalloc.h" +#include "nls.h" +#include "libsmartcols.h" + +#include "lsfd.h" + +struct unkn { + struct file file; + const struct anon_ops *anon_ops; + void *anon_data; +}; + +struct anon_ops { + const char *class; + char * (*get_name)(struct unkn *); + /* Return true is handled the column. */ + bool (*fill_column)(struct proc *, + struct unkn *, + struct libscols_line *, + int, + size_t, + char **str); + void (*init)(struct unkn *); + void (*free)(struct unkn *); + int (*handle_fdinfo)(struct unkn *, const char *, const char *); +}; + +static const struct anon_ops anon_generic_ops; +static const struct anon_ops anon_pidfd_ops; + +static char * anon_get_class(struct unkn *unkn) +{ + char *name; + + if (unkn->anon_ops->class) + return xstrdup(unkn->anon_ops->class); + + /* See unkn_init_content() */ + name = ((struct file *)unkn)->name + 11; + /* Does it have the form anon_inode:[class]? */ + if (*name == '[') { + size_t len = strlen(name + 1); + if (*(name + 1 + len - 1) == ']') + return strndup(name + 1, len - 1); + } + + return xstrdup(name); +} + +static bool unkn_fill_column(struct proc *proc, + struct file *file, + struct libscols_line *ln, + int column_id, + size_t column_index) +{ + char *str = NULL; + struct unkn *unkn = (struct unkn *)file; + + switch(column_id) { + case COL_NAME: + if (unkn->anon_ops && unkn->anon_ops->get_name) { + str = unkn->anon_ops->get_name(unkn); + if (str) + break; + } + return false; + case COL_TYPE: + if (!unkn->anon_ops) + return false; + /* FALL THROUGH */ + case COL_AINODECLASS: + if (unkn->anon_ops) { + str = anon_get_class(unkn); + break; + } + return false; + case COL_SOURCE: + if (unkn->anon_ops) { + str = xstrdup("anon_inodefs"); + break; + } + return false; + default: + if (unkn->anon_ops && unkn->anon_ops->fill_column) { + if (unkn->anon_ops->fill_column(proc, unkn, ln, + column_id, column_index, &str)) + break; + } + return false; + } + + if (!str) + err(EXIT_FAILURE, _("failed to add output data")); + if (scols_line_refer_data(ln, column_index, str)) + err(EXIT_FAILURE, _("failed to add output data")); + return true; +} + +static void unkn_init_content(struct file *file) +{ + struct unkn *unkn = (struct unkn *)file; + + assert(file); + unkn->anon_ops = NULL; + unkn->anon_data = NULL; + + if (major(file->stat.st_dev) == 0 + && 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; + + if (unkn->anon_ops->init) + unkn->anon_ops->init(unkn); + } +} + +static void unkn_content_free(struct file *file) +{ + struct unkn *unkn = (struct unkn *)file; + + assert(file); + if (unkn->anon_ops && unkn->anon_ops->free) + unkn->anon_ops->free((struct unkn *)file); +} + +static int unkn_handle_fdinfo(struct file *file, const char *key, const char *value) +{ + struct unkn *unkn = (struct unkn *)file; + + assert(file); + if (unkn->anon_ops && unkn->anon_ops->handle_fdinfo) + return unkn->anon_ops->handle_fdinfo(unkn, key, value); + return 0; /* Should be handled in parents */ +} + +/* + * pidfd + */ +struct anon_pidfd_data { + pid_t pid; + char *nspid; +}; + +static char *anon_pidfd_get_name(struct unkn *unkn) +{ + char *str = NULL; + struct anon_pidfd_data *data = (struct anon_pidfd_data *)unkn->anon_data; + + char *comm = NULL; + struct proc *proc = get_proc(data->pid); + if (proc) + comm = proc->command; + + xasprintf(&str, "pid=%d comm=%s nspid=%s", + data->pid, + comm? comm: "", + data->nspid? data->nspid: ""); + return str; +} + +static void anon_pidfd_init(struct unkn *unkn) +{ + unkn->anon_data = xcalloc(1, sizeof(struct anon_pidfd_data)); +} + +static void anon_pidfd_free(struct unkn *unkn) +{ + struct anon_pidfd_data *data = (struct anon_pidfd_data *)unkn->anon_data; + + if (data->nspid) + free(data->nspid); + free(data); +} + +static int anon_pidfd_handle_fdinfo(struct unkn *unkn, const char *key, const char *value) +{ + if (strcmp(key, "Pid") == 0) { + uint64_t pid; + + int rc = ul_strtou64(value, &pid, 10); + if (rc < 0) + return 0; /* ignore -- parse failed */ + ((struct anon_pidfd_data *)unkn->anon_data)->pid = (pid_t)pid; + return 1; + } + else if (strcmp(key, "NSpid") == 0) { + ((struct anon_pidfd_data *)unkn->anon_data)->nspid = xstrdup(value); + return 1; + + } + return 0; +} + +static bool anon_pidfd_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_pidfd_data *data = (struct anon_pidfd_data *)unkn->anon_data; + + switch(column_id) { + case COL_PIDFD_COMM: { + struct proc *pidfd_proc = get_proc(data->pid); + char *pidfd_comm = NULL; + if (pidfd_proc) + pidfd_comm = pidfd_proc->command; + if (pidfd_comm) { + *str = xstrdup(pidfd_comm); + return true; + } + break; + } + case COL_PIDFD_NSPID: + if (data->nspid) { + *str = xstrdup(data->nspid); + return true; + } + break; + case COL_PIDFD_PID: + xasprintf(str, "%d", (int)data->pid); + return true; + } + + return false; +} + +static const struct anon_ops anon_pidfd_ops = { + .class = "pidfd", + .get_name = anon_pidfd_get_name, + .fill_column = anon_pidfd_fill_column, + .init = anon_pidfd_init, + .free = anon_pidfd_free, + .handle_fdinfo = anon_pidfd_handle_fdinfo, +}; + +/* + * generic (fallback implementation) + */ +static const struct anon_ops anon_generic_ops = { + .class = NULL, + .get_name = NULL, + .fill_column = NULL, + .init = NULL, + .free = NULL, + .handle_fdinfo = NULL, +}; + +const struct file_class unkn_class = { + .super = &file_class, + .size = sizeof(struct unkn), + .fill_column = unkn_fill_column, + .initialize_content = unkn_init_content, + .free_content = unkn_content_free, + .handle_fdinfo = unkn_handle_fdinfo, +}; diff --git a/misc-utils/lsfd.1 b/misc-utils/lsfd.1 new file mode 100644 index 0000000..9baaa42 --- /dev/null +++ b/misc-utils/lsfd.1 @@ -0,0 +1,1110 @@ +'\" t +.\" Title: lsfd +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-12-01 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "LSFD" "1" "2023-12-01" "util\-linux 2.39.3" "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" +lsfd \- list file descriptors +.SH "SYNOPSIS" +.sp +\fBlsfd\fP [option] +.SH "DESCRIPTION" +.sp +\fBlsfd\fP is intended to be a modern replacement for \fBlsof\fP(8) on Linux systems. +Unlike \fBlsof\fP, \fBlsfd\fP is specialized to Linux kernel; it supports Linux +specific features like namespaces with simpler code. \fBlsfd\fP is not a +drop\-in replacement for \fBlsof\fP; they are different in the command line +interface and output formats. +.sp +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 +\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 +to get a list of all available columns. +.SH "OPTIONS" +.sp +\fB\-l\fP, \fB\-\-threads\fP +.RS 4 +List in threads level. +.RE +.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. +.sp +The default list of columns may be extended if \fIlist\fP is specified in +the format +\fIlist\fP (e.g., \fBlsfd \-o +DELETED\fP). +.RE +.sp +\fB\-r\fP, \fB\-\-raw\fP +.RS 4 +Use raw output format. +.RE +.sp +\fB\-\-notruncate\fP +.RS 4 +Don\(cqt truncate text in columns. +.RE +.sp +\fB\-p\fP, \fB\-\-pid\fP \fIpids\fP +.RS 4 +Collect information only for specified processes. +\fIpids\fP is a list of pids. A comma or whitespaces can be used as separators. +You can use this option with \fBpidof\fP(1). See \fBFILTER EXAMPLES\fP. +.sp +Both \fB\-Q\fP option with an expression including PID, e.g. \-Q (PID == 1), +and \fB\-p\fP option, e.g. \-p 1, may print the same output but using \fB\-p\fP +option is much more efficient because \fB\-p\fP option works at a much earlier +stage of processing than the \fB\-Q\fP option. +.RE +.sp +\fB\-i\fP[4|6], \fB\-\-inet\fP[=4|=6] +.RS 4 +List only IPv4 sockets and/or IPv6 sockets. +.RE +.sp +\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. +.RE +.sp +\fB\-C\fP, \fB\-\-counter\fP \fIlabel\fP:\fIfilter_expr\fP +.RS 4 +Define a custom counter used in \fB\-\-summary\fP output. \fBlsfd\fP makes a +counter named \fIlabel\fP. During collect information, \fBlsfd\fP counts files +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. +\fIlabel\fP should not include \f(CR{\fP nor \f(CR:\fP. You can define multiple +counters by specifying this option multiple times. +.sp +See also \fBCOUNTER EXAMPLES\fP. +.RE +.sp +\fB\-\-summary\fP[=\fIwhen\fP] +.RS 4 +This option controls summary lines output. The optional argument \fIwhen\fP +can be \fBonly\fP, \fBappend\fP or \fBnever\fP. If the \fIwhen\fP argument is omitted, +it defaults to \fBonly\fP. +.sp +The summary reports counters. A counter consists of a label and an +integer value. \fB\-\-counter\fP is the option for defining a counter. If +a user defines no counter, \fBlsfd\fP uses the definitions of pre\-defined +built\-in counters (default counters) to make the summary output. +.sp +CAUTION: Using \fB\-\-summary\fP and \fB\-\-json\fP may make the output broken. Only combining \fB\-\-summary\fP=\fBonly\fP and \fB\-\-json\fP is valid. +.RE +.sp +\fB\-\-debug\-filter\fP +.RS 4 +Dump the internal data structure for the filter and exit. This is useful +only for \fBlsfd\fP developers. +.RE +.sp +\fB\-\-dump\-counters\fP +.RS 4 +Dump the definition of counters used in \fB\-\-summary\fP output. +.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 +CAUTION: The names and types of columns are not stable yet. +They may be changed in the future releases. +.sp +AINODECLASS <\f(CRstring\fP> +.RS 4 +Class of anonymous inode. +.RE +.sp +ASSOC <\f(CRstring\fP> +.RS 4 +Association between file and process. +.RE +.sp +BLKDRV <\f(CRstring\fP> +.RS 4 +Block device driver name resolved by \f(CR/proc/devices\fP. +.RE +.sp +CHRDRV <\f(CRstring\fP> +.RS 4 +Character device driver name resolved by \f(CR/proc/devices\fP. +.RE +.sp +COMMAND <\f(CRstring\fP> +.RS 4 +Command of the process opening the file. +.RE +.sp +DELETED <\f(CRboolean\fP> +.RS 4 +Reachability from the file system. +.RE +.sp +DEV <\f(CRstring\fP> +.RS 4 +ID of the device containing the file. +.RE +.sp +DEVTYPE <\f(CRstring\fP> +.RS 4 +Device type (\f(CRblk\fP, \f(CRchar\fP, or \f(CRnodev\fP). +.RE +.sp +ENDPOINT <\f(CRstring\fP> +.RS 4 +IPC endpoints information communicated with the fd. +The format of the column depends on the object associated +with the fd: +.sp +FIFO type +.RS 4 +\fIPID\fP,\fICOMMAND\fP,\fIASSOC\fP[\-r][\-w] +.sp +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. +.RE +.sp +FD <\f(CRnumber\fP> +.RS 4 +File descriptor for the file. +.RE +.sp +FLAGS <\f(CRstring\fP> +.RS 4 +Flags specified when opening the file. +.RE +.sp +FUID <\f(CRnumber\fP> +.RS 4 +User ID number of the file\(cqs owner. +.RE +.sp +INET.LADDR <\f(CRstring\fP> +.RS 4 +Local IP address. +.RE +.sp +INET.RADDR <\f(CRstring\fP> +.RS 4 +Remote IP address. +.RE +.sp +INET6.LADDR <\f(CRstring\fP> +.RS 4 +Local IP6 address. +.RE +.sp +INET6.RADDR <\f(CRstring\fP> +.RS 4 +Remote IP6 address. +.RE +.sp +INODE <\f(CRnumber\fP> +.RS 4 +Inode number. +.RE +.sp +KNAME <\f(CRstring\fP> +.RS 4 +Raw file name extracted from +from \f(CR/proc/\fP\fIpid\fP\f(CR/fd/\fP\fIfd\fP or \f(CR/proc/\fP\fIpid\fP\f(CR/map_files/\fP\fIregion\fP. +.RE +.sp +KTHREAD <\f(CRboolean\fP> +.RS 4 +Whether the process is a kernel thread or not. +.RE +.sp +MAJ:MIN <\f(CRstring\fP> +.RS 4 +Device ID for special, or ID of device containing file. +.RE +.sp +MAPLEN <\f(CRnumber\fP> +.RS 4 +Length of file mapping (in page). +.RE +.sp +MISCDEV <\f(CRstring\fP> +.RS 4 +Misc character device name resolved by \f(CR/proc/misc\fP. +.RE +.sp +MNTID <\f(CRnumber\fP> +.RS 4 +Mount ID. +.RE +.sp +MODE <\f(CRstring\fP> +.RS 4 +Access mode (rwx). +.RE +.sp +NAME <\f(CRstring\fP> +.RS 4 +Cooked version of KNAME. It is mostly same as KNAME. +.sp +Some files have special formats and information sources: +.sp +NETLINK +.RS 4 +protocol=\fINETLINK.PROTOCOL\fP[ lport=\fINETLINK.LPORT\fP[ group=\fINETLINK.GROUPS\fP]] +.RE +.sp +PACKET +.RS 4 +type=\fISOCK.TYPE\fP[ protocol=\fIPACKET.PROTOCOL\fP][ iface=\fIPACKET.IFACE\fP] +.RE +.sp +pidfd +.RS 4 +pid=\fITARGET\-PID\fP comm=\fITARGET\-COMMAND\fP nspid=\fITARGET\-NSPIDS\fP +.sp +\fBlsfd\fP extracts \fITARGET\-PID\fP and \fITARGET\-NSPIDS\fP from +\f(CR/proc/\fP\fIpid\fP\f(CR/fdinfo/\fP\fIfd\fP. +.RE +.sp +PING +.RS 4 +state=\fISOCK.STATE\fP[ id=\fIPING.ID\fP][ laddr=\fIINET.LADDR\fP [ raddr=\fIINET.RADDR\fP]] +.RE +.sp +PINGv6 +.RS 4 +state=\fISOCK.STATE\fP[ id=\fIPING.ID\fP][ laddr=\fIINET6.LADDR\fP [ raddr=\fIINET6.RADDR\fP]] +.RE +.sp +RAW +.RS 4 +state=\fISOCK.STATE\fP[ protocol=\fIRAW.PROTOCOL\fP [ laddr=\fIINET.LADDR\fP [ raddr=\fIINET.RADDR\fP]]] +.RE +.sp +RAWv6 +.RS 4 +state=\fISOCK.STATE\fP[ protocol=\fIRAW.PROTOCOL\fP [ laddr=\fIINET6.LADDR\fP [ raddr=\fIINET6.RADDR\fP]]] +.RE +.sp +TCP, TCPv6 +.RS 4 +state=\fISOCK.STATE\fP[ laddr=\fITCP.LADDR\fP [ raddr=\fITCP.RADDR\fP]] +.RE +.sp +UDP, UDPv6 +.RS 4 +state=\fISOCK.STATE\fP[ laddr=\fIUDP.LADDR\fP [ raddr=\fIUDP.RADDR\fP]] +.sp +\fBlsfd\fP hides \f(CRraddr=\fP if \fIUDP.RADDR\fP is \f(CR0.0.0.0\fP and \fIUDP.RPORT\fP is 0. +.RE +.sp +UDP\-LITE, UDPLITEv6 +.RS 4 +state=\fISOCK.STATE\fP[ laddr=\fIUDPLITE.LADDR\fP [ raddr=\fIUDPLITE.RADDR\fP]] +.RE +.sp +UNIX\-STREAM +.RS 4 +state=\fISOCK.STATE\fP[ path=\fIUNIX.PATH\fP] +.RE +.sp +UNIX +.RS 4 +state=\fISOCK.STATE\fP[ path=\fIUNIX.PATH\fP] type=\fISOCK.TYPE\fP +.RE +.RE +.sp +NETLINK.GROUPS <\f(CRnumber\fP>> +.RS 4 +Netlink multicast groups. +.RE +.sp +NETLINK.LPORT <\f(CRnumber\fP>> +.RS 4 +Netlink local port id. +.RE +.sp +NETLINK.PROTOCOL <\f(CRstring\fP>> +.RS 4 +Netlink protocol. +.RE +.sp +NLINK <\f(CRnumber\fP> +.RS 4 +Link count. +.RE +.sp +NS.NAME <\f(CRstring\fP> +.RS 4 +Name (\fINS.TYPE\fP:[\fIINODE\fP]) of the namespace specified with the file. +.RE +.sp +NS.TYPE <\f(CRstring\fP> +.RS 4 +Type of the namespace specified with the file. +The type is \f(CRmnt\fP, \f(CRcgroup\fP, \f(CRuts\fP, \f(CRipc\fP, \f(CRuser\fP, \f(CRpid\fP, \f(CRnet\fP, +\f(CRtime\fP, or \f(CRunknown\fP. +.RE +.sp +OWNER <\f(CRstring\fP> +.RS 4 +Owner of the file. +.RE +.sp +PACKET.IFACE <\f(CRstring\fP> +.RS 4 +Interface name associated with the packet socket. +.RE +.sp +PACKET.PROTOCOL <\f(CRstring\fP> +.RS 4 +L3 protocol associated with the packet socket. +.RE +.sp +PARTITION <\f(CRstring\fP> +.RS 4 +Block device name resolved by \f(CR/proc/partition\fP. +.RE +.sp +PID <\f(CRnumber\fP> +.RS 4 +PID of the process opening the file. +.RE +.sp +PIDFD.COMM <\f(CRstring\fP> +.RS 4 +Command of the process targeted by the pidfd. +.RE +.sp +PIDFD.NSPID <\f(CRstring\fP> +.RS 4 +Value of NSpid field in \f(CR/proc/\fP\fIpid\fP\f(CR/fdinfo/\fP\fIfd\fP of the pidfd. +.sp +Quoted from kernel/fork.c of Linux source tree: +.RS 3 +.ll -.6i +.sp +If pid namespaces are supported then this function will also print +the pid of a given pidfd refers to for all descendant pid namespaces +starting from the current pid namespace of the instance, i.e. the +Pid field and the first entry in the NSpid field will be identical. +.sp +Note that this differs from the Pid and NSpid fields in +/proc/<pid>/status where Pid and NSpid are always shown relative to +the pid namespace of the procfs instance. +.br +.RE +.ll +.RE +.sp +PIDFD.PID <\f(CRnumber\fP> +.RS 4 +PID of the process targeted by the pidfd. +.RE +.sp +PING.ID <`number`> +.RS 4 +ICMP echo request id used on the PING socket. +.RE +.sp +POS <\f(CRnumber\fP> +.RS 4 +File position. +.RE +.sp +RAW.PROTOCOL <\f(CRnumber\fP> +.RS 4 +Protocol number of the raw socket. +.RE +.sp +RDEV <\f(CRstring\fP> +.RS 4 +Device ID (if special file). +.RE +.sp +SIZE <\f(CRnumber\fP> +.RS 4 +File size. +.RE +.sp +SOCK.LISTENING <\f(CRboolean\fP> +.RS 4 +Listening socket. +.RE +.sp +SOCK.NETS <\f(CRnumber\fP> +.RS 4 +Inode identifying network namespace where the socket belongs to. +.RE +.sp +SOCK.PROTONAME <\f(CRstring\fP> +.RS 4 +Protocol name. +.RE +.sp +SOCK.STATE <\f(CRstring\fP> +.RS 4 +State of socket. +.RE +.sp +SOCK.TYPE <\f(CRstring\fP> +.RS 4 +Type of socket. Here type means the second parameter of +socket system call: +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +stream +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +dgram +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +raw +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +rdm +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +seqpacket +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +dccp +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +packet +.RE +.RE +.sp +SOURCE <\f(CRstring\fP> +.RS 4 +File system, partition, or device containing the file. +.RE +.sp +STTYPE <\f(CRstring\fP> +.RS 4 +Raw file types returned from \fBstat\fP(2): BLK, CHR, DIR, FIFO, LINK, REG, SOCK, or UNKN. +.RE +.sp +TCP.LADDR <\f(CRstring\fP> +.RS 4 +Local L3 (INET.LADDR or INET6.LADDR) address and local TCP port. +.RE +.sp +TCP.LPORT <\f(CRinteger\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. +.RE +.sp +TCP.RPORT <\f(CRinteger\fP> +.RS 4 +Remote TCP port. +.RE +.sp +TID <\f(CRnumber\fP> +.RS 4 +Thread ID of the process opening the file. +.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. +.RE +.sp +UDP.LADDR <\f(CRstring\fP> +.RS 4 +Local IP address and local UDP port. +.RE +.sp +UDP.LPORT <\f(CRinteger\fP> +.RS 4 +Local UDP port. +.RE +.sp +UDP.RADDR <\f(CRstring\fP> +.RS 4 +Remote IP address and remote UDP port. +.RE +.sp +UDP.RPORT <\f(CRinteger\fP> +.RS 4 +Remote UDP port. +.RE +.sp +UDPLITE.LADDR <\f(CRstring\fP> +.RS 4 +Local IP address and local UDPLite port. +.RE +.sp +UDPLITE.LPORT <\f(CRinteger\fP> +.RS 4 +Local UDP port. +.RE +.sp +UDPLITE.RADDR <\f(CRstring\fP> +.RS 4 +Remote IP address and remote UDPLite port. +.RE +.sp +UDPLITE.RPORT <\f(CRinteger\fP> +.RS 4 +Remote UDP port. +.RE +.sp +UID <\f(CRnumber\fP> +.RS 4 +User ID number. +.RE +.sp +UNIX.PATH <\f(CRstring\fP> +.RS 4 +Filesystem pathname for UNIX domain socket. +.RE +.sp +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 +.RS 4 +STREXP OP2 STREXP | NUMEXP OP2 NUMEXP | BOOLEXP0 OP2 BOOLEXP0 +.RE +.sp +OP2 +.RS 4 +\fI==\fP | \fIeq\fP | \fI!=\fP | \fIne\fP +.RE +.sp +BOOLOP2BL +.RS 4 +BOOLEXP0 OP2BL BOOLEXP0 +.RE +.sp +OP2BL +.RS 4 +\fI&&\fP | \fIand\fP | \fI||\fP | \fIor\fP +.RE +.sp +BOOLOP2CMP +.RS 4 +NUMEXP OP2CMP NUMEXP +.RE +.sp +OP2CMP +.RS 4 +\fI<\fP | \fIlt\fP | \fI<=\fP | \fIle\fP | \fI>\fP | \fIgt\fP | \fI>=\fP | \fIge\fP +.RE +.sp +BOOLOP2REG +.RS 4 +STREXP OP2REG STRLIT +.RE +.sp +OP2REG +.RS 4 +\fI=~\fP | \fI!~\fP +.RE +.SH "FILTER EXAMPLES" +.sp +\fBlsfd\fP has few options for filtering. In most of cases, what you should +know is \fB\-Q\fP (or \fB\-\-filter\fP) option. Combined with \fB\-o\fP (or +\fB\-\-output\fP) option, you can customize the output as you want. +.sp +List files associated with PID 1 and PID 2 processes: +.RS 4 +.RE +.sp +.if n .RS 4 +.nf +.fam C +# lsfd \-Q \*(Aq(PID == 1) or (PID == 2)\*(Aq +.fam +.fi +.if n .RE +.sp +Do the same in an alternative way: +.RS 4 +.RE +.sp +.if n .RS 4 +.nf +.fam C +# lsfd \-Q \*(Aq(PID == 1) || (PID == 2)\*(Aq +.fam +.fi +.if n .RE +.sp +Do the same in a more efficient way: +.RS 4 +.RE +.sp +.if n .RS 4 +.nf +.fam C +# lsfd \-\-pid 1,2 +.fam +.fi +.if n .RE +.sp +Whitescapes can be used instead of a comma: +.RS 4 +.RE +.sp +.if n .RS 4 +.nf +.fam C +# lsfd \-\-pid \*(Aq1 2\*(Aq +.fam +.fi +.if n .RE +.sp +Utilize \fBpidof\fP(1) for list the files associated with "firefox": +.RS 4 +.RE +.sp +.if n .RS 4 +.nf +.fam C +# lsfd \-\-pid "$(pidof firefox)" +.fam +.fi +.if n .RE +.sp +List the 1st file descriptor opened by PID 1 process: +.RS 4 +.RE +.sp +.if n .RS 4 +.nf +.fam C +# lsfd \-Q \*(Aq(PID == 1) and (FD == 1)\*(Aq +.fam +.fi +.if n .RE +.sp +Do the same in an alternative way: +.RS 4 +.RE +.sp +.if n .RS 4 +.nf +.fam C +# lsfd \-Q \*(Aq(PID == 1) && (FD == 1)\*(Aq +.fam +.fi +.if n .RE +.sp +List all running executables: +.RS 4 +.RE +.sp +.if n .RS 4 +.nf +.fam C +# lsfd \-Q \*(AqASSOC == "exe"\*(Aq +.fam +.fi +.if n .RE +.sp +Do the same in an alternative way: +.RS 4 +.RE +.sp +.if n .RS 4 +.nf +.fam C +# lsfd \-Q \*(AqASSOC eq "exe"\*(Aq +.fam +.fi +.if n .RE +.sp +Do the same but print only file names: +.RS 4 +.RE +.sp +.if n .RS 4 +.nf +.fam C +# lsfd \-o NAME \-Q \*(AqASSOC eq "exe"\*(Aq | sort \-u +.fam +.fi +.if n .RE +.sp +List deleted files associated to processes: +.RS 4 +.RE +.sp +.if n .RS 4 +.nf +.fam C +# lsfd \-Q \*(AqDELETED\*(Aq +.fam +.fi +.if n .RE +.sp +List non\-regular files: +.RS 4 +.RE +.sp +.if n .RS 4 +.nf +.fam C +# lsfd \-Q \*(AqTYPE != "REG"\*(Aq +.fam +.fi +.if n .RE +.sp +List block devices: +.RS 4 +.RE +.sp +.if n .RS 4 +.nf +.fam C +# lsfd \-Q \*(AqDEVTYPE == "blk"\*(Aq +.fam +.fi +.if n .RE +.sp +Do the same with TYPE column: +.RS 4 +.RE +.sp +.if n .RS 4 +.nf +.fam C +# lsfd \-Q \*(AqTYPE == "BLK"\*(Aq +.fam +.fi +.if n .RE +.sp +List files including "dconf" directory in their names: +.RS 4 +.RE +.sp +.if n .RS 4 +.nf +.fam C +# lsfd \-Q \*(AqNAME =~ ".\(rs*/dconf/.*"\*(Aq +.fam +.fi +.if n .RE +.sp +List files opened in a QEMU virtual machine: +.RS 4 +.RE +.sp +.if n .RS 4 +.nf +.fam C +# lsfd \-Q \*(Aq(COMMAND =~ ".\(rs*qemu.*") and (FD >= 0)\*(Aq +.fam +.fi +.if n .RE +.sp +Hide files associated to kernel threads: +.RS 4 +.RE +.sp +.if n .RS 4 +.nf +.fam C +# lsfd \-Q \*(Aq!KTHREAD\*(Aq +.fam +.fi +.if n .RE +.SH "COUNTER EXAMPLES" +.sp +Report the numbers of netlink socket descriptors and unix socket descriptors: +.RS 4 +.RE +.sp +.if n .RS 4 +.nf +.fam C +# lsfd \-\-summary=only \(rs + \-C \*(Aqnetlink sockets\*(Aq:\*(Aq(NAME =~ "NETLINK:.*")\*(Aq \(rs + \-C \*(Aqunix sockets\*(Aq:\*(Aq(NAME =~ "UNIX:.*")\*(Aq +VALUE COUNTER + 57 netlink sockets + 1552 unix sockets +.fam +.fi +.if n .RE +.sp +Do the same but print in JSON format: +.RS 4 +.RE +.sp +.if n .RS 4 +.nf +.fam C +# lsfd \-\-summary=only \-\-json \(rs + \-C \*(Aqnetlink sockets\*(Aq:\*(Aq(NAME =~ "NETLINK:.*")\*(Aq \(rs + \-C \*(Aqunix sockets\*(Aq:\*(Aq(NAME =~ "UNIX:.*")\*(Aq +{ + "lsfd\-summary": [ + { + "value": 15, + "counter": "netlink sockets" + },{ + "value": 798, + "counter": "unix sockets" + } + ] +} +.fam +.fi +.if n .RE +.SH "HISTORY" +.sp +The \fBlsfd\fP command is part of the util\-linux package since v2.38. +.SH "AUTHORS" +.sp +.MTO "yamato\(atredhat.com" "Masatake YAMATO" "," +.MTO "kzak\(atredhat.com" "Karel Zak" "" +.SH "SEE ALSO" +.sp +\fBlsof\fP(8) +\fBpidof\fP(1) +\fBproc\fP(5) +\fBsocket\fP(2) +\fBstat\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 \fBlsfd\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/lsfd.1.adoc b/misc-utils/lsfd.1.adoc new file mode 100644 index 0000000..23eee28 --- /dev/null +++ b/misc-utils/lsfd.1.adoc @@ -0,0 +1,667 @@ +//po4a: entry man manual +//// +Copyright 2021 Red Hat, Inc. + +This file may be copied under the terms of the GNU Public License. +//// += lsfd(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: lsfd +:colon: : + +== NAME + +lsfd - list file descriptors + +== SYNOPSIS + +*lsfd* [option] + +== DESCRIPTION + +*lsfd* is intended to be a modern replacement for *lsof*(8) on Linux systems. +Unlike *lsof*, *lsfd* is specialized to Linux kernel; it supports Linux +specific features like namespaces with simpler code. *lsfd* is not a +drop-in replacement for *lsof*; they are different in the command line +interface and output formats. + +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. + +*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* +to get a list of all available columns. + +== OPTIONS + +*-l*, *--threads*:: +List in threads level. + +*-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. ++ +The default list of columns may be extended if _list_ is specified in +the format +_list_ (e.g., *lsfd -o +DELETED*). + +*-r*, *--raw*:: +Use raw output format. + +*--notruncate*:: +Don't truncate text in columns. + +*-p*, *--pid* _pids_:: +Collect information only for specified processes. +_pids_ is a list of pids. A comma or whitespaces can be used as separators. +You can use this option with *pidof*(1). See *FILTER EXAMPLES*. ++ +Both *-Q* option with an expression including PID, e.g. -Q (PID == 1), +and *-p* option, e.g. -p 1, may print the same output but using *-p* +option is much more efficient because *-p* option works at a much earlier +stage of processing than the *-Q* option. + +*-i*[4|6], *--inet*[=4|=6]:: +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*. + +*-C*, *--counter* __label__:__filter_expr__:: +Define a custom counter used in *--summary* output. *lsfd* makes a +counter named _label_. During collect information, *lsfd* counts files +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_. +_label_ should not include `{` nor `:`. You can define multiple +counters by specifying this option multiple times. ++ +See also *COUNTER EXAMPLES*. + +*--summary*[=_when_]:: +This option controls summary lines output. The optional argument _when_ +can be *only*, *append* or *never*. If the _when_ argument is omitted, +it defaults to *only*. ++ +The summary reports counters. A counter consists of a label and an +integer value. *--counter* is the option for defining a counter. If +a user defines no counter, *lsfd* uses the definitions of pre-defined +built-in counters (default counters) to make the summary output. ++ +CAUTION{colon} Using *--summary* and *--json* may make the output broken. Only combining *--summary*=*only* and *--json* is valid. +//TRANSLATORS: Keep {colon} untranslated. + +*--debug-filter*:: +Dump the internal data structure for the filter and exit. This is useful +only for *lsfd* developers. + +*--dump-counters*:: +Dump the definition of counters used in *--summary* output. + +include::man-common/help-version.adoc[] + +== OUTPUT COLUMNS + +Each column has a type. Types are surround by < and >. + +//TRANSLATORS: Keep {colon} untranslated. +CAUTION{colon} The names and types of columns are not stable yet. +They may be changed in the future releases. + +AINODECLASS <``string``>:: +Class of anonymous inode. + +ASSOC <``string``>:: +Association between file and process. + +BLKDRV <``string``>:: +Block device driver name resolved by `/proc/devices`. + +CHRDRV <``string``>:: +Character device driver name resolved by `/proc/devices`. + +COMMAND <``string``>:: +Command of the process opening the file. + +DELETED <``boolean``>:: +Reachability from the file system. + +DEV <``string``>:: +ID of the device containing the file. + +DEVTYPE <``string``>:: +Device type (`blk`, `char`, or `nodev`). + +ENDPOINT <``string``>:: +IPC endpoints information communicated with the fd. +The format of the column depends on the object associated +with the fd: ++ +FIFO type::: +_PID_,_COMMAND_,_ASSOC_[-r][-w] ++ +The last characters ([-r][-w]) represents the read and/or +write mode of the endpoint. + ++ +*lsfd* collects endpoints within the processes that +*lsfd* scans; *lsfd* may miss some endpoints +if you limits the processes with *-p* option. + +FD <``number``>:: +File descriptor for the file. + +FLAGS <``string``>:: +Flags specified when opening the file. + +FUID <``number``>:: +User ID number of the file's owner. + +INET.LADDR <``string``>:: +Local IP address. + +INET.RADDR <``string``>:: +Remote IP address. + +INET6.LADDR <``string``>:: +Local IP6 address. + +INET6.RADDR <``string``>:: +Remote IP6 address. + +INODE <``number``>:: +Inode number. + +KNAME <``string``>:: +// +// It seems that the manpage backend of asciidoctor has limitations +// about emitting text with nested face specifications like: +// +// `_u_` p +// +// Not only u but also p is decorated with underline. +// +Raw file name extracted from +from ``/proc/``_pid_``/fd/``_fd_ or ``/proc/``_pid_``/map_files/``_region_. + +KTHREAD <``boolean``>:: +Whether the process is a kernel thread or not. + +MAJ:MIN <``string``>:: +Device ID for special, or ID of device containing file. + +MAPLEN <``number``>:: +Length of file mapping (in page). + +MISCDEV <``string``>:: +Misc character device name resolved by `/proc/misc`. + +MNTID <``number``>:: +Mount ID. + +MODE <``string``>:: +Access mode (rwx). + +NAME <``string``>:: +Cooked version of KNAME. It is mostly same as KNAME. ++ +Some files have special formats and information sources: ++ +NETLINK::: +protocol=_NETLINK.PROTOCOL_[ lport=_NETLINK.LPORT_[ group=_NETLINK.GROUPS_]] ++ +PACKET::: +type=_SOCK.TYPE_[ protocol=_PACKET.PROTOCOL_][ iface=_PACKET.IFACE_] ++ +pidfd::: +pid=_TARGET-PID_ comm=_TARGET-COMMAND_ nspid=_TARGET-NSPIDS_ ++ +*lsfd* extracts _TARGET-PID_ and _TARGET-NSPIDS_ from +``/proc/``_pid_``/fdinfo/``_fd_. ++ +PING::: +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_]] ++ +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_]]] ++ +TCP::: +TCPv6::: +state=_SOCK.STATE_[ laddr=_TCP.LADDR_ [ raddr=_TCP.RADDR_]] ++ +UDP::: +UDPv6::: +state=_SOCK.STATE_[ laddr=_UDP.LADDR_ [ raddr=_UDP.RADDR_]] ++ +*lsfd* hides ``raddr=`` if _UDP.RADDR_ is ``0.0.0.0`` and _UDP.RPORT_ is 0. ++ +UDP-LITE::: +UDPLITEv6::: +state=_SOCK.STATE_[ laddr=_UDPLITE.LADDR_ [ raddr=_UDPLITE.RADDR_]] ++ +UNIX-STREAM::: +state=_SOCK.STATE_[ path=_UNIX.PATH_] ++ +UNIX::: +state=_SOCK.STATE_[ path=_UNIX.PATH_] type=_SOCK.TYPE_ + +NETLINK.GROUPS <``number``>>:: +Netlink multicast groups. + +NETLINK.LPORT <``number``>>:: +Netlink local port id. + +NETLINK.PROTOCOL <``string``>>:: +Netlink protocol. + +NLINK <``number``>:: +Link count. + +NS.NAME <``string``>:: +Name (_NS.TYPE_:[_INODE_]) of the namespace specified with the file. + +NS.TYPE <``string``>:: +Type of the namespace specified with the file. +The type is `mnt`, `cgroup`, `uts`, `ipc`, `user`, `pid`, `net`, +`time`, or `unknown`. + +OWNER <``string``>:: +Owner of the file. + +PACKET.IFACE <``string``>:: +Interface name associated with the packet socket. + +PACKET.PROTOCOL <``string``>:: +L3 protocol associated with the packet socket. + +PARTITION <``string``>:: +Block device name resolved by `/proc/partition`. + +PID <``number``>:: +PID of the process opening the file. + +PIDFD.COMM <``string``>:: +Command of the process targeted by the pidfd. + +PIDFD.NSPID <``string``>:: +Value of NSpid field in ``/proc/``_pid_``/fdinfo/``_fd_ of the pidfd. ++ +Quoted from kernel/fork.c of Linux source tree: ++ +____ +If pid namespaces are supported then this function will also print +the pid of a given pidfd refers to for all descendant pid namespaces +starting from the current pid namespace of the instance, i.e. the +Pid field and the first entry in the NSpid field will be identical. + +Note that this differs from the Pid and NSpid fields in +/proc/<pid>/status where Pid and NSpid are always shown relative to +the pid namespace of the procfs instance. +____ + +PIDFD.PID <``number``>:: +PID of the process targeted by the pidfd. + +PING.ID <`number`>:: +ICMP echo request id used on the PING socket. + +POS <``number``>:: +File position. + +RAW.PROTOCOL <``number``>:: +Protocol number of the raw socket. + +RDEV <``string``>:: +Device ID (if special file). + +SIZE <``number``>:: +File size. + +SOCK.LISTENING <``boolean``>:: +Listening socket. + +SOCK.NETS <``number``>:: +Inode identifying network namespace where the socket belongs to. + +SOCK.PROTONAME <``string``>:: +Protocol name. + +SOCK.STATE <``string``>:: +State of socket. + +SOCK.TYPE <``string``>:: +Type of socket. Here type means the second parameter of +socket system call: ++ +* stream +* dgram +* raw +* rdm +* seqpacket +* dccp +* packet + +SOURCE <``string``>:: +File system, partition, or device containing the file. + +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. + +TCP.LPORT <``integer``>:: +Local TCP port. + +TCP.RADDR <``string``>:: +Remote L3 (INET.RADDR or INET6.RADDR) address and remote TCP port. + +TCP.RPORT <``integer``>:: +Remote TCP port. + +TID <``number``>:: +Thread ID of the process opening the file. + +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. + +UDP.LADDR <``string``>:: +Local IP address and local UDP port. + +UDP.LPORT <``integer``>:: +Local UDP port. + +UDP.RADDR <``string``>:: +Remote IP address and remote UDP port. + +UDP.RPORT <``integer``>:: +Remote UDP port. + +UDPLITE.LADDR <``string``>:: +Local IP address and local UDPLite port. + +UDPLITE.LPORT <``integer``>:: +Local UDP port. + +UDPLITE.RADDR <``string``>:: +Remote IP address and remote UDPLite port. + +UDPLITE.RPORT <``integer``>:: +Remote UDP port. + +UID <``number``>:: +User ID number. + +UNIX.PATH <``string``>:: +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 :: _=~_ | _!~_ + +== FILTER EXAMPLES + +*lsfd* has few options for filtering. In most of cases, what you should +know is *-Q* (or *--filter*) option. Combined with *-o* (or +*--output*) option, you can customize the output as you want. + +//TRANSLATORS: In the following messages, don't forget to add whitespace at the end! +List files associated with PID 1 and PID 2 processes: :: +.... +# lsfd -Q '(PID == 1) or (PID == 2)' +.... + +Do the same in an alternative way: :: +.... +# lsfd -Q '(PID == 1) || (PID == 2)' +.... + +Do the same in a more efficient way: :: +.... +# lsfd --pid 1,2 +.... + +Whitescapes can be used instead of a comma: :: +.... +# lsfd --pid '1 2' +.... + +Utilize *pidof*(1) for list the files associated with "firefox": :: +.... +# lsfd --pid "$(pidof firefox)" +.... + +List the 1st file descriptor opened by PID 1 process: :: +.... +# lsfd -Q '(PID == 1) and (FD == 1)' +.... + +Do the same in an alternative way: :: +.... +# lsfd -Q '(PID == 1) && (FD == 1)' +.... + +List all running executables: :: +.... +# lsfd -Q 'ASSOC == "exe"' +.... + +Do the same in an alternative way: :: +.... +# lsfd -Q 'ASSOC eq "exe"' +.... + +Do the same but print only file names: :: +.... +# lsfd -o NAME -Q 'ASSOC eq "exe"' | sort -u +.... + +List deleted files associated to processes: :: +.... +# lsfd -Q 'DELETED' +.... + +List non-regular files: :: +.... +# lsfd -Q 'TYPE != "REG"' +.... + +List block devices: :: +.... +# lsfd -Q 'DEVTYPE == "blk"' +.... + +Do the same with TYPE column: :: +.... +# lsfd -Q 'TYPE == "BLK"' +.... + +List files including "dconf" directory in their names: :: +.... +# lsfd -Q 'NAME =~ ".\*/dconf/.*"' +.... + +List files opened in a QEMU virtual machine: :: +.... +# lsfd -Q '(COMMAND =~ ".\*qemu.*") and (FD >= 0)' +.... + +Hide files associated to kernel threads: :: +.... +# lsfd -Q '!KTHREAD' +.... + +== COUNTER EXAMPLES + +Report the numbers of netlink socket descriptors and unix socket descriptors: :: +.... +# lsfd --summary=only \ + -C 'netlink sockets':'(NAME =~ "NETLINK:.*")' \ + -C 'unix sockets':'(NAME =~ "UNIX:.*")' +VALUE COUNTER + 57 netlink sockets + 1552 unix sockets +.... + +Do the same but print in JSON format: :: +.... +# lsfd --summary=only --json \ + -C 'netlink sockets':'(NAME =~ "NETLINK:.*")' \ + -C 'unix sockets':'(NAME =~ "UNIX:.*")' +{ + "lsfd-summary": [ + { + "value": 15, + "counter": "netlink sockets" + },{ + "value": 798, + "counter": "unix sockets" + } + ] +} +.... + + +== HISTORY + +The *lsfd* command is part of the util-linux package since v2.38. + +== AUTHORS + +mailto:yamato@redhat.com[Masatake YAMATO], +mailto:kzak@redhat.com[Karel Zak] + +== SEE ALSO + +*lsof*(8) +*pidof*(1) +*proc*(5) +*socket*(2) +*stat*(2) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/lsfd.c b/misc-utils/lsfd.c new file mode 100644 index 0000000..f8537a7 --- /dev/null +++ b/misc-utils/lsfd.c @@ -0,0 +1,2054 @@ +/* + * lsfd(1) - list file descriptors + * + * Copyright (C) 2021 Red Hat, Inc. All rights reserved. + * Written by Masatake YAMATO <yamato@redhat.com> + * Karel Zak <kzak@redhat.com> + * + * Very generally based on lsof(8) by Victor A. Abell <abe@purdue.edu> + * It supports multiple OSes. lsfd specializes to Linux. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <stdio.h> +#include <sys/types.h> +#include <inttypes.h> +#include <sys/stat.h> +#include <unistd.h> +#include <getopt.h> +#include <ctype.h> +#include <search.h> + +#include <linux/sched.h> +#include <sys/syscall.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); +} + +/* 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 "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 + */ +struct nodev { + struct list_head nodevs; + unsigned long minor; + char *filesystem; +}; + +struct nodev_table { +#define NODEV_TABLE_SIZE 97 + struct list_head tables[NODEV_TABLE_SIZE]; +}; +static struct nodev_table nodev_table; + +struct name_manager { + struct idcache *cache; + unsigned long next_id; +}; + +/* + * /proc/devices entries + */ +struct devdrv { + struct list_head devdrvs; + unsigned long major; + char *name; +}; + +static struct list_head chrdrvs; +static struct list_head blkdrvs; + +/* + * IPC table + */ + +#define IPC_TABLE_SIZE 997 +struct ipc_table { + struct list_head tables[IPC_TABLE_SIZE]; +}; + +static struct ipc_table ipc_table; + +/* + * Column related stuffs + */ + +/* column names */ +struct colinfo { + const char *name; + double whint; + int flags; + int json_type; + const char *help; +}; + +/* columns descriptions */ +static const struct colinfo infos[] = { + [COL_AINODECLASS] = { "AINODECLASS", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("class of anonymous inode") }, + [COL_ASSOC] = { "ASSOC", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("association between file and process") }, + [COL_BLKDRV] = { "BLKDRV", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("block device driver name resolved by /proc/devices") }, + [COL_CHRDRV] = { "CHRDRV", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("character device driver name resolved by /proc/devices") }, + [COL_COMMAND] = { "COMMAND", + 0.3, SCOLS_FL_TRUNC, SCOLS_JSON_STRING, + N_("command of the process opening the file") }, + [COL_DELETED] = { "DELETED", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_BOOLEAN, + N_("reachability from the file system") }, + [COL_DEV] = { "DEV", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("ID of device containing file") }, + [COL_DEVTYPE] = { "DEVTYPE", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("device type (blk, char, or nodev)") }, + [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_FD] = { "FD", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("file descriptor for 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") }, + [COL_INET_RADDR] = { "INET.RADDR", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("remote IP address") }, + [COL_INET6_LADDR] = { "INET6.LADDR", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("local IPv6 address") }, + [COL_INET6_RADDR] = { "INET6.RADDR", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("remote IPv6 address") }, + [COL_KNAME] = { "KNAME", + 0.4, SCOLS_FL_TRUNC, SCOLS_JSON_STRING, + N_("name of the file (raw)") }, + [COL_KTHREAD] = { "KTHREAD", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_BOOLEAN, + N_("opened by a kernel thread") }, + [COL_MAJMIN] = { "MAJ:MIN", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("device ID for special, or ID of device containing file") }, + [COL_MAPLEN] = { "MAPLEN", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("length of file mapping (in page)") }, + [COL_MISCDEV] = { "MISCDEV", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("misc character device name resolved by /proc/misc") }, + [COL_MNT_ID] = { "MNTID", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("mount id") }, + [COL_MODE] = { "MODE", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("access mode (rwx)") }, + [COL_NAME] = { "NAME", + 0.4, SCOLS_FL_TRUNC, SCOLS_JSON_STRING, + N_("name of the file (cooked)") }, + [COL_NETLINK_GROUPS] = { "NETLINK.GROUPS", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("netlink multicast groups") }, + [COL_NETLINK_LPORT] = { "NETLINK.LPORT", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("netlink local port id") }, + [COL_NETLINK_PROTOCOL] = { "NETLINK.PROTOCOL", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("netlink protocol") }, + [COL_NLINK] = { "NLINK", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("link count") }, + [COL_NS_NAME] = { "NS.NAME", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("name of the namespace (NS.TYPE:[INODE])") }, + [COL_NS_TYPE] = { "NS.TYPE", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("type of the namespace") }, + [COL_OWNER] = { "OWNER", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("owner of the file") }, + [COL_PACKET_IFACE] = { "PACKET.IFACE", + 0, SCOLS_FL_RIGHT,SCOLS_JSON_STRING, + N_("net interface associated with the packet socket") }, + [COL_PACKET_PROTOCOL] = { "PACKET.PROTOCOL", + 0, SCOLS_FL_RIGHT,SCOLS_JSON_STRING, + N_("L3 protocol associated with the packet socket") }, + [COL_PARTITION] = { "PARTITION", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("block device name resolved by /proc/partition") }, + [COL_PID] = { "PID", + 5, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("PID of the process opening the file") }, + [COL_PIDFD_COMM] = { "PIDFD.COMM", + 0.2, SCOLS_FL_TRUNC, SCOLS_JSON_STRING, + N_("command of the process targeted by the pidfd") }, + [COL_PIDFD_NSPID] = { "PIDFD.NSPID", + 0.2, SCOLS_FL_TRUNC, SCOLS_JSON_STRING, + N_("NSpid field in fdinfo of the pidfd") }, + [COL_PIDFD_PID] = { "PIDFD.PID", + 5, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("PID of the process targeted by the pidfd") }, + [COL_PING_ID] = { "PING.ID", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("ICMP echo request ID") }, + [COL_POS] = { "POS", + 5, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("file position") }, + [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_SIZE] = { "SIZE", + 4, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("file size"), }, + [COL_SOCK_LISTENING] = { "SOCK.LISTENING", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_BOOLEAN, + N_("listening socket") }, + [COL_SOCK_NETNS] = { "SOCK.NETNS", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("inode identifying network namespace where the socket belongs to") }, + [COL_SOCK_PROTONAME] = { "SOCK.PROTONAME", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("protocol name") }, + [COL_SOCK_STATE] = { "SOCK.STATE", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("State of socket") }, + [COL_SOCK_TYPE] = { "SOCK.TYPE", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("Type of socket") }, + [COL_SOURCE] = { "SOURCE", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("file system, partition, or device containing file") }, + [COL_STTYPE] = { "STTYPE", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("file type (raw)") }, + [COL_TCP_LADDR] = { "TCP.LADDR", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("local TCP address (INET address:TCP port)") }, + [COL_TCP_RADDR] = { "TCP.RADDR", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("remote TCP address (INET address:TCP port)") }, + [COL_TCP_LPORT] = { "TCP.LPORT", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("local TCP port") }, + [COL_TCP_RPORT] = { "TCP.RPORT", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("remote TCP port") }, + [COL_TID] = { "TID", + 5, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("thread ID of the process opening the file") }, + [COL_TYPE] = { "TYPE", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("file type (cooked)") }, + [COL_UDP_LADDR] = { "UDP.LADDR", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("local UDP address (INET address:UDP port)") }, + [COL_UDP_RADDR] = { "UDP.RADDR", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("remote UDP address (INET address:UDP port)") }, + [COL_UDP_LPORT] = { "UDP.LPORT", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("local UDP port") }, + [COL_UDP_RPORT] = { "UDP.RPORT", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("remote UDP port") }, + [COL_UDPLITE_LADDR] = { "UDPLITE.LADDR", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("local UDPLite address (INET address:UDPLite port)") }, + [COL_UDPLITE_RADDR] = { "UDPLITE.RADDR", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("remote UDPLite address (INET address:UDPLite port)") }, + [COL_UDPLITE_LPORT] = { "UDPLITE.LPORT", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("local UDPLite port") }, + [COL_UDPLITE_RPORT] = { "UDPLITE.RPORT", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("remote UDPLite port") }, + [COL_UID] = { "UID", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("user ID number of the process") }, + [COL_UNIX_PATH] = { "UNIX.PATH", + 0.4, SCOLS_FL_TRUNC, SCOLS_JSON_STRING, + N_("filesystem pathname for UNIX domain socket") }, + [COL_USER] = { "USER", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("user of the process") }, +}; + +static const int default_columns[] = { + COL_COMMAND, + COL_PID, + COL_USER, + COL_ASSOC, + COL_MODE, + COL_TYPE, + COL_SOURCE, + COL_MNT_ID, + COL_INODE, + COL_NAME, +}; + +static const int default_threads_columns[] = { + COL_COMMAND, + COL_PID, + COL_TID, + COL_USER, + COL_ASSOC, + COL_MODE, + COL_TYPE, + COL_SOURCE, + COL_MNT_ID, + COL_INODE, + COL_NAME, +}; + +static int columns[ARRAY_SIZE(infos) * 2] = {-1}; +static size_t ncolumns; + +static ino_t *mnt_namespaces; +static size_t nspaces; + +struct counter_spec { + struct list_head specs; + const char *name; + const char *expr; +}; + +static const struct counter_spec default_counter_specs[] = { + { + .name = N_("processes"), + .expr = "ASSOC == 'cwd'", + }, + { + .name = N_("root owned processes"), + .expr = "(ASSOC == 'cwd') && (UID == 0)", + }, + { + .name = N_("kernel threads"), + .expr = "(ASSOC == 'cwd') && KTHREAD", + }, + { + .name = N_("open files"), + .expr = "FD >= 0", + }, + { + .name = N_("RO open files"), + .expr = "(FD >= 0) and (MODE == 'r--')", + }, + { + .name = N_("WO open files"), + .expr = "(FD >= 0) and (MODE == '-w-')", + }, + { + .name = N_("shared mappings"), + .expr = "ASSOC == 'shm'", + }, + { + .name = N_("RO shared mappings"), + .expr = "(ASSOC == 'shm') and (MODE == 'r--')", + }, + { + .name = N_("WO shared mappings"), + .expr = "(ASSOC == 'shm') and (MODE == '-w-')", + }, + { + .name = N_("regular files"), + .expr = "(FD >= 0) && (STTYPE == 'REG')", + }, + { + .name = N_("directories"), + .expr = "(FD >= 0) && (STTYPE == 'DIR')", + }, + { + .name = N_("sockets"), + .expr = "(FD >= 0) && (STTYPE == 'SOCK')", + }, + { + .name = N_("fifos/pipes"), + .expr = "(FD >= 0) && (STTYPE == 'FIFO')", + }, + { + .name = N_("character devices"), + .expr = "(FD >= 0) && (STTYPE == 'CHR')", + }, + { + .name = N_("block devices"), + .expr = "(FD >= 0) && (STTYPE == 'BLK')", + }, + { + .name = N_("unknown types"), + .expr = "(FD >= 0) && (STTYPE == 'UNKN')", + } +}; + +struct lsfd_control { + struct libscols_table *tb; /* output */ + struct list_head procs; /* list of all processes */ + + unsigned int noheadings : 1, + raw : 1, + json : 1, + notrunc : 1, + threads : 1, + show_main : 1, /* print main table */ + show_summary : 1, /* print summary/counters */ + sockets_only : 1; /* display only SOCKETS */ + + struct lsfd_filter *filter; + struct lsfd_counter **counters; /* NULL terminated array. */ +}; + +static void *proc_tree; /* for tsearch/tfind */ + +static int proc_tree_compare(const void *a, const void *b) +{ + return ((struct proc *)a)->pid - ((struct proc *)b)->pid; +} + +struct proc *get_proc(pid_t pid) +{ + struct proc key = { .pid = pid }; + struct proc **node = tfind(&key, &proc_tree, proc_tree_compare); + if (node) + return *node; + return NULL; +} + +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 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)); +} + +static int get_column_id(int num) +{ + assert(num >= 0); + assert((size_t) num < ncolumns); + assert(columns[num] < (int) ARRAY_SIZE(infos)); + + return columns[num]; +} + +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) +{ + struct libscols_column *cl; + int flags = col->flags; + + cl = scols_table_new_column(tb, col->name, col->whint, flags); + if (cl) { + scols_column_set_json_type(cl, col->json_type); + if (col->flags & SCOLS_FL_WRAP) { + scols_column_set_wrapfunc(cl, + scols_wrapnl_chunksize, + scols_wrapnl_nextchunk, + NULL); + scols_column_set_safechars(cl, "\n"); + } + } + + return cl; +} + +static struct libscols_column *add_column_by_id_cb(struct libscols_table *tb, int colid, void *data) +{ + struct libscols_column *cl; + + if (ncolumns >= ARRAY_SIZE(columns)) + errx(EXIT_FAILURE, _("too many columns are added via filter expression")); + + assert(colid < LSFD_N_COLS); + + cl = add_column(tb, infos + colid); + if (!cl) + err(EXIT_FAILURE, _("failed to allocate output column")); + columns[ncolumns++] = colid; + + if (colid == COL_TID) { + struct lsfd_control *ctl = data; + ctl->threads = 1; + } + + return cl; +} + +static int has_mnt_ns(ino_t id) +{ + size_t i; + + for (i = 0; i < nspaces; i++) { + if (mnt_namespaces[i] == id) + return 1; + } + return 0; +} + +static void add_mnt_ns(ino_t id) +{ + size_t nmax = 0; + + if (nspaces) + nmax = (nspaces + 16) / 16 * 16; + if (nmax <= nspaces + 1) { + nmax += 16; + mnt_namespaces = xrealloc(mnt_namespaces, + sizeof(ino_t) * nmax); + } + mnt_namespaces[nspaces++] = id; +} + +static const struct file_class *stat2class(struct stat *sb) +{ + dev_t dev; + + assert(sb); + + switch (sb->st_mode & S_IFMT) { + case S_IFCHR: + return &cdev_class; + case S_IFBLK: + return &bdev_class; + case S_IFSOCK: + return &sock_class; + case S_IFIFO: + return &fifo_class; + case S_IFLNK: + case S_IFDIR: + return &file_class; + case S_IFREG: + dev = sb->st_dev; + if (major(dev) != 0) + return &file_class; + + if (is_nsfs_dev(dev)) + return &nsfs_file_class; + + return &file_class; + default: + break; + } + + return &unkn_class; +} + +static struct file *new_file(struct proc *proc, const struct file_class *class) +{ + struct file *file; + + file = xcalloc(1, class->size); + file->proc = proc; + + INIT_LIST_HEAD(&file->files); + list_add_tail(&file->files, &proc->files); + + return file; +} + +static struct file *copy_file(struct file *old) +{ + struct file *file = xcalloc(1, old->class->size); + + INIT_LIST_HEAD(&file->files); + file->proc = old->proc; + list_add_tail(&file->files, &old->proc->files); + + file->class = old->class; + file->association = old->association; + file->name = xstrdup(old->name); + file->stat = old->stat; + + return file; +} + +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; +} + +static void file_init_content(struct file *file) +{ + if (file->class && file->class->initialize_content) + file->class->initialize_content(file); +} + +static void free_file(struct file *file) +{ + const struct file_class *class = file->class; + + while (class) { + if (class->free_content) + class->free_content(file); + class = class->super; + } + free(file); +} + + +static struct proc *new_process(pid_t pid, struct proc *leader) +{ + struct proc *proc = xcalloc(1, sizeof(*proc)); + + proc->pid = pid; + proc->leader = leader? leader: proc; + proc->command = NULL; + + INIT_LIST_HEAD(&proc->files); + INIT_LIST_HEAD(&proc->procs); + + proc->kthread = 0; + return proc; +} + +static void free_proc(struct proc *proc) +{ + list_free(&proc->files, struct file, files, free_file); + + free(proc->command); + free(proc); +} + +static void read_fdinfo(struct file *file, FILE *fdinfo) +{ + char buf[1024]; + + while (fgets(buf, sizeof(buf), fdinfo)) { + const struct file_class *class; + char *val = strchr(buf, ':'); + + if (!val) + continue; + *val++ = '\0'; /* terminate key */ + + val = (char *) skip_space(val); + rtrim_whitespace((unsigned char *) val); + + class = file->class; + while (class) { + if (class->handle_fdinfo + && class->handle_fdinfo(file, buf, val)) + break; + class = class->super; + } + } +} + +static struct file *collect_file_symlink(struct path_cxt *pc, + struct proc *proc, + const char *name, + int assoc, + bool sockets_only) +{ + char sym[PATH_MAX] = { '\0' }; + struct stat sb; + struct file *f, *prev; + + if (ul_path_readlink(pc, sym, sizeof(sym), name) < 0) + return NULL; + + /* The /proc/#/{fd,ns} often contains the same file (e.g. /dev/tty) + * more than once. Let's try to reuse the previous file if the real + * path is the same to save stat() call. + */ + prev = list_last_entry(&proc->files, struct file, files); + if (prev && prev->name && strcmp(prev->name, sym) == 0) { + f = copy_file(prev); + f->association = assoc; + } else { + const struct file_class *class; + + if (ul_path_stat(pc, &sb, 0, name) < 0) + return NULL; + + 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. + */ + && (class != &sock_class)&& (class != &nsfs_file_class)) + return NULL; + f = new_file(proc, class); + file_set_path(f, &sb, sym, assoc); + } + + file_init_content(f); + + if (is_association(f, NS_MNT)) + proc->ns_mnt = f->stat.st_ino; + else if (is_association(f, NS_NET)) + load_sock_xinfo(pc, name, f->stat.st_ino); + + else if (assoc >= 0) { + /* file-descriptor based association */ + FILE *fdinfo; + + if (ul_path_stat(pc, &sb, AT_SYMLINK_NOFOLLOW, name) == 0) + f->mode = sb.st_mode; + + if (is_nsfs_dev(f->stat.st_dev)) + load_sock_xinfo(pc, name, f->stat.st_ino); + + fdinfo = ul_path_fopenf(pc, "r", "fdinfo/%d", assoc); + if (fdinfo) { + read_fdinfo(f, fdinfo); + fclose(fdinfo); + } + } + + return f; +} + +/* read symlinks from /proc/#/fd + */ +static void collect_fd_files(struct path_cxt *pc, struct proc *proc, + bool sockets_only) +{ + DIR *sub = NULL; + struct dirent *d = NULL; + char path[sizeof("fd/") + sizeof(stringify_value(UINT64_MAX))]; + + while (ul_path_next_dirent(pc, &sub, "fd", &d) == 0) { + uint64_t num; + + if (ul_strtou64(d->d_name, &num, 10) != 0) /* only numbers */ + continue; + + snprintf(path, sizeof(path), "fd/%ju", (uintmax_t) num); + collect_file_symlink(pc, proc, path, num, sockets_only); + } +} + +static void parse_maps_line(struct path_cxt *pc, char *buf, struct proc *proc) +{ + uint64_t start, end, offset, ino; + unsigned long major, minor; + enum association assoc = ASSOC_MEM; + struct stat sb; + struct file *f, *prev; + char *path, modestr[5]; + dev_t devno; + + /* read rest of the map */ + if (sscanf(buf, "%"SCNx64 /* start */ + "-%"SCNx64 /* end */ + " %4[^ ]" /* mode */ + " %"SCNx64 /* offset */ + " %lx:%lx" /* maj:min */ + " %"SCNu64, /* inode */ + + &start, &end, modestr, &offset, + &major, &minor, &ino) != 7) + return; + + /* Skip private anonymous mappings. */ + if (major == 0 && minor == 0 && ino == 0) + return; + + devno = makedev(major, minor); + + if (modestr[3] == 's') + assoc = ASSOC_SHM; + + /* The map usually contains the same file more than once, try to reuse + * the previous file (if devno and ino are the same) to save stat() call. + */ + prev = list_last_entry(&proc->files, struct file, files); + + if (prev && prev->stat.st_dev == devno && prev->stat.st_ino == ino) { + f = copy_file(prev); + f->association = -assoc; + } else if ((path = strchr(buf, '/'))) { + rtrim_whitespace((unsigned char *) path); + if (stat(path, &sb) < 0) + /* If a file is mapped but deleted from the file system, + * "stat by the file name" may not work. In that case, + */ + 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. */ + char map_file[sizeof("map_files/0000000000000000-ffffffffffffffff")]; + char sym[PATH_MAX] = { '\0' }; + + try_map_files: + snprintf(map_file, sizeof(map_file), "map_files/%"PRIx64"-%"PRIx64, start, end); + if (ul_path_stat(pc, &sb, 0, map_file) < 0) + return; + 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); + } + + if (modestr[0] == 'r') + f->mode |= S_IRUSR; + if (modestr[1] == 'w') + f->mode |= S_IWUSR; + if (modestr[2] == 'x') + f->mode |= S_IXUSR; + + f->map_start = start; + f->map_end = end; + f->pos = offset; + + file_init_content(f); +} + +static void collect_mem_files(struct path_cxt *pc, struct proc *proc) +{ + FILE *fp; + char buf[BUFSIZ]; + + fp = ul_path_fopen(pc, "r", "maps"); + if (!fp) + return; + + while (fgets(buf, sizeof(buf), fp)) + parse_maps_line(pc, buf, proc); + + fclose(fp); +} + +static void collect_outofbox_files(struct path_cxt *pc, + struct proc *proc, + enum association assocs[], + const char *names[], + size_t count, + bool sockets_only) +{ + size_t i; + + for (i = 0; i < count; i++) + collect_file_symlink(pc, proc, names[assocs[i]], assocs[i] * -1, + sockets_only); +} + +static void collect_execve_file(struct path_cxt *pc, struct proc *proc, + bool sockets_only) +{ + enum association assocs[] = { ASSOC_EXE }; + const char *names[] = { + [ASSOC_EXE] = "exe", + }; + collect_outofbox_files(pc, proc, assocs, names, ARRAY_SIZE(assocs), + sockets_only); +} + +static void collect_fs_files(struct path_cxt *pc, struct proc *proc, + bool sockets_only) +{ + enum association assocs[] = { ASSOC_EXE, ASSOC_CWD, ASSOC_ROOT }; + const char *names[] = { + [ASSOC_CWD] = "cwd", + [ASSOC_ROOT] = "root", + }; + collect_outofbox_files(pc, proc, assocs, names, ARRAY_SIZE(assocs), + sockets_only); +} + +static void collect_namespace_files(struct path_cxt *pc, struct proc *proc) +{ + enum association assocs[] = { + ASSOC_NS_CGROUP, + ASSOC_NS_IPC, + ASSOC_NS_MNT, + ASSOC_NS_NET, + ASSOC_NS_PID, + ASSOC_NS_PID4C, + ASSOC_NS_TIME, + ASSOC_NS_TIME4C, + ASSOC_NS_USER, + 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", + [ASSOC_NS_TIME] = "ns/time", + [ASSOC_NS_TIME4C] = "ns/time_for_children", + [ASSOC_NS_USER] = "ns/user", + [ASSOC_NS_UTS] = "ns/uts", + }; + collect_outofbox_files(pc, proc, assocs, names, ARRAY_SIZE(assocs), + /* Namespace information is alwasys needed. */ + false); +} + +static struct nodev *new_nodev(unsigned long minor, const char *filesystem) +{ + struct nodev *nodev = xcalloc(1, sizeof(*nodev)); + + INIT_LIST_HEAD(&nodev->nodevs); + nodev->minor = minor; + nodev->filesystem = xstrdup(filesystem); + + return nodev; +} + +static void free_nodev(struct nodev *nodev) +{ + free(nodev->filesystem); + free(nodev); +} + +static void initialize_nodevs(void) +{ + int i; + + for (i = 0; i < NODEV_TABLE_SIZE; i++) + INIT_LIST_HEAD(&nodev_table.tables[i]); +} + +static void finalize_nodevs(void) +{ + int i; + + for (i = 0; i < NODEV_TABLE_SIZE; i++) + list_free(&nodev_table.tables[i], struct nodev, nodevs, free_nodev); + + free(mnt_namespaces); +} + +const char *get_nodev_filesystem(unsigned long minor) +{ + struct list_head *n; + int slot = minor % NODEV_TABLE_SIZE; + + list_for_each (n, &nodev_table.tables[slot]) { + struct nodev *nodev = list_entry(n, struct nodev, nodevs); + if (nodev->minor == minor) + return nodev->filesystem; + } + return NULL; +} + +static void add_nodevs(FILE *mnt) +{ + /* This can be very long. A line in mountinfo can have more than 3 + * paths. */ + char line[PATH_MAX * 3 + 256]; + + 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]", + &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]", + &major, &minor, filesystem) != 3) + continue; + + if (major != 0) + continue; + 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]); + } +} + +static void initialize_ipc_table(void) +{ + for (int i = 0; i < IPC_TABLE_SIZE; i++) + INIT_LIST_HEAD(ipc_table.tables + i); +} + +static void free_ipc(struct ipc *ipc) +{ + if (ipc->class->free) + ipc->class->free(ipc); + free(ipc); +} + +static void finalize_ipc_table(void) +{ + for (int i = 0; i < IPC_TABLE_SIZE; i++) + list_free(&ipc_table.tables[i], struct ipc, ipcs, free_ipc); +} + +struct ipc *get_ipc(struct file *file) +{ + int slot; + struct list_head *e; + const struct ipc_class *ipc_class; + + if (!file->class->get_ipc_class) + return NULL; + + ipc_class = file->class->get_ipc_class(file); + if (!ipc_class) + return NULL; + + slot = ipc_class->get_hash(file) % IPC_TABLE_SIZE; + list_for_each (e, &ipc_table.tables[slot]) { + struct ipc *ipc = list_entry(e, struct ipc, ipcs); + if (ipc->class != ipc_class) + continue; + if (ipc_class->is_suitable_ipc(ipc, file)) + return ipc; + } + return NULL; +} + +void add_ipc(struct ipc *ipc, unsigned int hash) +{ + int slot = hash % IPC_TABLE_SIZE; + list_add(&ipc->ipcs, &ipc_table.tables[slot]); +} + +static void fill_column(struct proc *proc, + struct file *file, + struct libscols_line *ln, + int column_id, + size_t column_index) +{ + const struct file_class *class = file->class; + + while (class) { + if (class->fill_column + && class->fill_column(proc, file, ln, + column_id, column_index)) + break; + class = class->super; + } +} + +static void convert_file(struct proc *proc, + struct file *file, + struct libscols_line *ln) + +{ + size_t i; + + for (i = 0; i < ncolumns; i++) + fill_column(proc, file, ln, get_column_id(i), i); +} + +static void convert(struct list_head *procs, struct lsfd_control *ctl) +{ + 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); + struct libscols_line *ln = scols_table_new_line(ctl->tb, NULL); + struct lsfd_counter **counter = NULL; + + if (!ln) + err(EXIT_FAILURE, _("failed to allocate output line")); + + convert_file(proc, file, ln); + + if (!lsfd_filter_apply(ctl->filter, ln)) { + scols_table_remove_line(ctl->tb, ln); + continue; + } + + if (!ctl->counters) + continue; + + for (counter = ctl->counters; *counter; counter++) + lsfd_counter_accumulate(*counter, ln); + } + } +} + +static void delete(struct list_head *procs, struct lsfd_control *ctl) +{ + struct list_head *p; + + list_for_each (p, procs) { + struct proc *proc = list_entry(p, struct proc, procs); + tdelete(proc, &proc_tree, proc_tree_compare); + } + 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); + } +} + +static void emit(struct lsfd_control *ctl) +{ + scols_print_table(ctl->tb); +} + + +static void initialize_class(const struct file_class *class) +{ + if (class->initialize_class) + class->initialize_class(); +} + +static void initialize_classes(void) +{ + initialize_class(&file_class); + initialize_class(&cdev_class); + initialize_class(&bdev_class); + initialize_class(&sock_class); + initialize_class(&unkn_class); +} + +static void finalize_class(const struct file_class *class) +{ + if (class->finalize_class) + class->finalize_class(); +} + +static void finalize_classes(void) +{ + finalize_class(&file_class); + finalize_class(&cdev_class); + finalize_class(&bdev_class); + finalize_class(&sock_class); + finalize_class(&unkn_class); +} + +static struct devdrv *new_devdrv(unsigned long major, const char *name) +{ + struct devdrv *devdrv = xcalloc(1, sizeof(*devdrv)); + + INIT_LIST_HEAD(&devdrv->devdrvs); + + devdrv->major = major; + devdrv->name = xstrdup(name); + + return devdrv; +} + +static void free_devdrv(struct devdrv *devdrv) +{ + free(devdrv->name); + free(devdrv); +} + +#define READ_DEVICES_LINE_LEN 256 +static struct devdrv *read_devdrv(const char *line) +{ + unsigned long major; + char name[READ_DEVICES_LINE_LEN]; + + if (sscanf(line, "%lu %s", &major, name) != 2) + return NULL; + + return new_devdrv(major, name); +} + +static void read_devices(struct list_head *chrdrvs_list, + struct list_head *blkdrvs_list, FILE *devices_fp) +{ + char line[READ_DEVICES_LINE_LEN]; + + /* Skip to the line "Character devices:". */ + while (fgets(line, sizeof(line), devices_fp)) { + if (line[0] == 'C') + break; + continue; + } + + while (fgets(line, sizeof(line), devices_fp)) { + /* Find the blank line before "Block devices:" line. */ + if (line[0] == '\n') + break; + + /* Read the character device drivers */ + struct devdrv *devdrv = read_devdrv(line); + if (devdrv) + list_add_tail(&devdrv->devdrvs, chrdrvs_list); + } + + /* Skip to the line "Block devices:". */ + while (fgets(line, sizeof(line), devices_fp)) { + if (line[0] == 'B') + break; + continue; + } + + /* Read the block device drivers */ + while (fgets(line, sizeof(line), devices_fp)) { + struct devdrv *devdrv = read_devdrv(line); + if (devdrv) + list_add_tail(&devdrv->devdrvs, blkdrvs_list); + } +} + +static void initialize_devdrvs(void) +{ + FILE *devices_fp; + + INIT_LIST_HEAD(&chrdrvs); + INIT_LIST_HEAD(&blkdrvs); + + devices_fp = fopen("/proc/devices", "r"); + if (devices_fp) { + read_devices(&chrdrvs, &blkdrvs, devices_fp); + fclose(devices_fp); + } +} + +static void finalize_devdrvs(void) +{ + list_free(&blkdrvs, struct devdrv, devdrvs, free_devdrv); + list_free(&chrdrvs, struct devdrv, devdrvs, free_devdrv); +} + +static const char *get_devdrv(struct list_head *devdrvs_list, unsigned long major) +{ + struct list_head *c; + list_for_each(c, devdrvs_list) { + struct devdrv *devdrv = list_entry(c, struct devdrv, devdrvs); + if (devdrv->major == major) + return devdrv->name; + } + return NULL; +} + +const char *get_chrdrv(unsigned long major) +{ + return get_devdrv(&chrdrvs, major); +} + +const char *get_blkdrv(unsigned long major) +{ + return get_devdrv(&blkdrvs, major); +} + +struct name_manager *new_name_manager(void) +{ + struct name_manager *nm = xcalloc(1, sizeof(struct name_manager)); + + nm->cache = new_idcache(); + if (!nm->cache) + err(EXIT_FAILURE, _("failed to allocate an idcache")); + + nm->next_id = 1; /* 0 is never issued as id. */ + return nm; +} + +void free_name_manager(struct name_manager *nm) +{ + free_idcache(nm->cache); + free(nm); +} + +const char *get_name(struct name_manager *nm, unsigned long id) +{ + struct identry *e; + + e = get_id(nm->cache, id); + + return e? e->name: NULL; +} + +unsigned long add_name(struct name_manager *nm, const char *name) +{ + struct identry *e = NULL, *tmp; + + for (tmp = nm->cache->ent; tmp; tmp = tmp->next) { + if (strcmp(tmp->name, name) == 0) { + e = tmp; + break; + } + } + + if (e) + return e->id; + + e = xmalloc(sizeof(struct identry)); + e->name = xstrdup(name); + e->id = nm->next_id++; + e->next = nm->cache->ent; + nm->cache->ent = e; + + return e->id; +} + +static void read_process(struct lsfd_control *ctl, struct path_cxt *pc, + pid_t pid, struct proc *leader) +{ + char buf[BUFSIZ]; + struct proc *proc; + + if (procfs_process_init_path(pc, pid) != 0) + return; + + proc = new_process(pid, leader); + proc->command = procfs_process_get_cmdname(pc, buf, sizeof(buf)) > 0 ? + xstrdup(buf) : xstrdup(_("(unknown)")); + procfs_process_get_uid(pc, &proc->uid); + + if (procfs_process_get_stat(pc, buf, sizeof(buf)) > 0) { + char *p; + unsigned int flags; + char *pat = NULL; + + /* See proc(5) about the column in the line. */ + xstrappend(&pat, "%*d ("); + for (p = proc->command; *p != '\0'; p++) { + if (*p == '%') + xstrappend(&pat, "%%"); + else + xstrputc(&pat, *p); + } + xstrappend(&pat, ") %*c %*d %*d %*d %*d %*d %u %*[^\n]"); + if (sscanf(buf, pat, &flags) == 1) + proc->kthread = !!(flags & PF_KTHREAD); + free(pat); + } + + collect_execve_file(pc, proc, ctl->sockets_only); + + if (proc->pid == proc->leader->pid + || kcmp(proc->leader->pid, proc->pid, KCMP_FS, 0, 0) != 0) + collect_fs_files(pc, proc, ctl->sockets_only); + + if (proc->ns_mnt == 0 || !has_mnt_ns(proc->ns_mnt)) { + FILE *mnt = ul_path_fopen(pc, "r", "mountinfo"); + if (mnt) { + add_nodevs(mnt); + if (proc->ns_mnt) + add_mnt_ns(proc->ns_mnt); + fclose(mnt); + } + } + + collect_namespace_files(pc, proc); + + /* If kcmp is not available, + * there is no way to no whether threads share resources. + * In such cases, we must pay the costs: call collect_mem_files() + * and collect_fd_files(). + */ + if ((!ctl->sockets_only) + && (proc->pid == proc->leader->pid + || kcmp(proc->leader->pid, proc->pid, KCMP_VM, 0, 0) != 0)) + collect_mem_files(pc, proc); + + if (proc->pid == proc->leader->pid + || kcmp(proc->leader->pid, proc->pid, KCMP_FILES, 0, 0) != 0) + collect_fd_files(pc, proc, ctl->sockets_only); + + list_add_tail(&proc->procs, &ctl->procs); + if (tsearch(proc, &proc_tree, proc_tree_compare) == NULL) + errx(EXIT_FAILURE, _("failed to allocate memory")); + + /* 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); + } + } + + /* Let's be careful with number of open files */ + ul_path_close_dirfd(pc); +} + +static void parse_pids(const char *str, pid_t **pids, int *count) +{ + long v; + char *next = NULL; + + if (*str == '\0') + return; + + errno = 0; + v = strtol(str, &next, 10); + if (errno) + err(EXIT_FAILURE, _("unexpected value for pid specification: %s"), str); + if (next == str) + errx(EXIT_FAILURE, _("garbage at the end of pid specification: %s"), str); + if (v < 0) + errx(EXIT_FAILURE, _("out of range value for pid specification: %ld"), v); + + (*count)++; + *pids = xrealloc(*pids, (*count) * sizeof(**pids)); + (*pids)[*count - 1] = (pid_t)v; + + while (next && *next != '\0' + && (isspace((unsigned char)*next) || *next == ',')) + next++; + if (*next != '\0') + parse_pids(next, pids, count); +} + +static int pidcmp(const void *a, const void *b) +{ + pid_t pa = *(pid_t *)a; + pid_t pb = *(pid_t *)b; + + if (pa < pb) + return -1; + else if (pa == pb) + return 0; + else + return 1; +} + +static void sort_pids(pid_t pids[], const int count) +{ + qsort(pids, count, sizeof(pid_t), pidcmp); +} + +static bool member_pids(const pid_t pid, const pid_t pids[], const int count) +{ + return bsearch(&pid, pids, count, sizeof(pid_t), pidcmp)? true: false; +} + +static void collect_processes(struct lsfd_control *ctl, const pid_t pids[], int n_pids) +{ + DIR *dir; + struct dirent *d; + struct path_cxt *pc = NULL; + + 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; + + if (procfs_dirent_get_pid(d, &pid) != 0) + continue; + if (n_pids == 0 || member_pids(pid, pids, n_pids)) + read_process(ctl, pc, pid, 0); + } + + closedir(dir); + ul_unref_path(pc); +} + +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(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(30)); + + fprintf(out, USAGE_COLUMNS); + + 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)); + + printf(USAGE_MAN_TAIL("lsfd(1)")); + + exit(EXIT_SUCCESS); +} + +static void append_filter_expr(char **a, const char *b, bool and) +{ + if (*a == NULL) { + *a = xstrdup(b); + return; + } + + char *tmp = *a; + *a = NULL; + + xstrappend(a, "("); + xstrappend(a, tmp); + xstrappend(a, ")"); + if (and) + xstrappend(a, "and("); + else + xstrappend(a, "or("); + xstrappend(a, b); + xstrappend(a, ")"); + + free(tmp); +} + +static struct lsfd_filter *new_filter(const char *expr, bool debug, const char *err_prefix, struct lsfd_control *ctl) +{ + struct lsfd_filter *filter; + const char *errmsg; + + 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); + } + + return filter; +} + +static struct counter_spec *new_counter_spec(const char *spec_str) +{ + char *sep; + struct counter_spec *spec; + + if (spec_str[0] == '\0') + errx(EXIT_FAILURE, + _("too short counter specification: -C/--counter %s"), + spec_str); + if (spec_str[0] == ':') + errx(EXIT_FAILURE, + _("no name for counter: -C/--counter %s"), + spec_str); + + sep = strchr(spec_str, ':'); + if (sep == NULL) + errx(EXIT_FAILURE, + _("no name for counter: -C/--counter %s"), + spec_str); + if (sep[1] == '\0') + errx(EXIT_FAILURE, + _("empty counter expression given: -C/--counter %s"), + spec_str); + + /* Split the spec_str in to name and expr. */ + *sep = '\0'; + + if (strchr(spec_str, '{')) + errx(EXIT_FAILURE, + _("don't use `{' in the name of a counter: %s"), + spec_str); + + spec = xmalloc(sizeof(struct counter_spec)); + INIT_LIST_HEAD(&spec->specs); + spec->name = spec_str; + spec->expr = sep + 1; + + return spec; +} + +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) +{ + struct lsfd_filter *filter; + + filter = new_filter(spec->expr, false, + _("failed in making filter for a counter: "), + ctl); + return lsfd_counter_new(spec->name, filter); +} + +static struct lsfd_counter **new_counters(struct list_head *specs, struct lsfd_control *ctl) +{ + struct lsfd_counter **counters; + size_t len = list_count_entries(specs); + size_t i = 0; + struct list_head *s; + + counters = xcalloc(len + 1, sizeof(struct lsfd_counter *)); + list_for_each(s, specs) { + struct counter_spec *spec = list_entry(s, struct counter_spec, specs); + counters[i++] = new_counter(spec, ctl); + } + assert(counters[len] == NULL); + + return counters; +} + +static struct lsfd_counter **new_default_counters(struct lsfd_control *ctl) +{ + struct lsfd_counter **counters; + size_t len = ARRAY_SIZE(default_counter_specs); + size_t i; + + counters = xcalloc(len + 1, sizeof(struct lsfd_counter *)); + for (i = 0; i < len; i++) { + const struct counter_spec *spec = default_counter_specs + i; + counters[i] = new_counter(spec, ctl); + } + assert(counters[len] == NULL); + + return counters; +} + +static void dump_default_counter_specs(void) +{ + size_t len = ARRAY_SIZE(default_counter_specs); + size_t i; + + puts("default counter specs:"); + for (i = 0; i < len; i++) { + const struct counter_spec *spec = default_counter_specs + i; + printf("\t%s:%s\n", spec->name, spec->expr); + } +} + +static void dump_counter_specs(struct list_head *specs) +{ + struct list_head *s; + + puts("custom counter specs:"); + list_for_each(s, specs) { + struct counter_spec *spec = list_entry(s, struct counter_spec, specs); + printf("\t%s:%s\n", spec->name, spec->expr); + } +} + +static struct libscols_table *new_summary_table(struct lsfd_control *ctl) +{ + struct libscols_table *tb = scols_new_table(); + + struct libscols_column *name_cl, *value_cl; + + if (!tb) + err(EXIT_FAILURE, _("failed to allocate summary table")); + + scols_table_enable_noheadings(tb, ctl->noheadings); + scols_table_enable_raw(tb, ctl->raw); + scols_table_enable_json(tb, ctl->json); + + if(ctl->json) + scols_table_set_name(tb, "lsfd-summary"); + + + value_cl = scols_table_new_column(tb, _("VALUE"), 0, SCOLS_FL_RIGHT); + if (!value_cl) + err(EXIT_FAILURE, _("failed to allocate summary column")); + if (ctl->json) + scols_column_set_json_type(value_cl, SCOLS_JSON_NUMBER); + + name_cl = scols_table_new_column(tb, _("COUNTER"), 0, 0); + if (!name_cl) + err(EXIT_FAILURE, _("failed to allocate summary column")); + if (ctl->json) + scols_column_set_json_type(name_cl, SCOLS_JSON_STRING); + + return tb; +} + +static void fill_summary_line(struct libscols_line *ln, struct lsfd_counter *counter) +{ + char *str = NULL; + + 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")); + + if (scols_line_set_data(ln, 1, lsfd_counter_name(counter))) + err(EXIT_FAILURE, _("failed to add summary data")); +} + +static void emit_summary(struct lsfd_control *ctl, struct lsfd_counter **counter) +{ + struct libscols_table *tb = new_summary_table(ctl); + + for (; *counter; counter++) { + struct libscols_line *ln = scols_table_new_line(tb, NULL); + fill_summary_line(ln, *counter); + } + scols_print_table(tb); + + scols_unref_table(tb); +} + +static void attach_xinfos(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 (file->class->attach_xinfo) + file->class->attach_xinfo(file); + } + } +} + +/* Filter expressions for implementing -i option. + * + * To list up the protocol names, use the following command line + * + * cd linux/net; + * find . -type f -exec grep -A 1 --color=auto -nH --null -e 'struct proto .*{' \{\} + + * + */ +#define INET_SUBEXP_BEGIN "(SOCK.PROTONAME =~ \"^(" +#define INET4_REG "TCP|UDP|RAW|PING|UDP-Lite|SCTP|DCCP|L2TP/IP|SMC" +#define INET6_REG "TCPv6|UDPv6|RAWv6|PINGv6|UDPLITEv6|SCTPv6|DCCPv6|L2TP/IPv6|SMC6" +#define INET_SUBEXP_END ")$\")" + +static const char *inet4_subexpr = INET_SUBEXP_BEGIN + INET4_REG + INET_SUBEXP_END; +static const char *inet6_subexpr = INET_SUBEXP_BEGIN + INET6_REG + INET_SUBEXP_END; +static const char *inet46_subexpr = INET_SUBEXP_BEGIN + INET4_REG "|" INET6_REG + INET_SUBEXP_END; + +int main(int argc, char *argv[]) +{ + int c; + size_t i; + char *outarg = NULL; + char *filter_expr = NULL; + bool debug_filter = false; + bool dump_counters = false; + pid_t *pids = NULL; + int n_pids = 0; + struct list_head counter_specs; + + struct lsfd_control ctl = { + .show_main = 1 + }; + + INIT_LIST_HEAD(&counter_specs); + + enum { + OPT_DEBUG_FILTER = CHAR_MAX + 1, + OPT_SUMMARY, + OPT_DUMP_COUNTERS, + }; + static const struct option longopts[] = { + { "noheadings", no_argument, NULL, 'n' }, + { "output", required_argument, NULL, 'o' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { "json", no_argument, NULL, 'J' }, + { "raw", no_argument, NULL, 'r' }, + { "threads", no_argument, NULL, 'l' }, + { "notruncate", no_argument, NULL, 'u' }, + { "pid", required_argument, NULL, 'p' }, + { "inet", optional_argument, NULL, 'i' }, + { "filter", required_argument, NULL, 'Q' }, + { "debug-filter",no_argument, NULL, OPT_DEBUG_FILTER }, + { "summary", optional_argument, NULL, OPT_SUMMARY }, + { "counter", required_argument, NULL, 'C' }, + { "dump-counters",no_argument, NULL, OPT_DUMP_COUNTERS }, + { NULL, 0, NULL, 0 }, + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + while ((c = getopt_long(argc, argv, "no:JrVhluQ:p:i::C:s", longopts, NULL)) != -1) { + switch (c) { + case 'n': + ctl.noheadings = 1; + break; + case 'o': + outarg = optarg; + break; + case 'J': + ctl.json = 1; + break; + case 'r': + ctl.raw = 1; + break; + case 'l': + ctl.threads = 1; + break; + case 'u': + ctl.notrunc = 1; + break; + case 'p': + parse_pids(optarg, &pids, &n_pids); + break; + case 'i': { + const char *subexpr = NULL; + + ctl.sockets_only = 1; + if (optarg == NULL) + subexpr = inet46_subexpr; + else if (strcmp(optarg, "4") == 0) + subexpr = inet4_subexpr; + else if (strcmp(optarg, "6") == 0) + subexpr = inet6_subexpr; + else + errx(EXIT_FAILURE, + _("unknown -i/--inet argument: %s"), + optarg); + + append_filter_expr(&filter_expr, subexpr, true); + break; + } + case 'Q': + append_filter_expr(&filter_expr, optarg, true); + break; + case 'C': { + struct counter_spec *c = new_counter_spec(optarg); + list_add_tail(&c->specs, &counter_specs); + break; + } + case OPT_DEBUG_FILTER: + debug_filter = true; + break; + case OPT_SUMMARY: + if (optarg) { + if (strcmp(optarg, "never") == 0) + ctl.show_summary = 0, ctl.show_main = 1; + else if (strcmp(optarg, "only") == 0) + ctl.show_summary = 1, ctl.show_main = 0; + else if (strcmp(optarg, "append") == 0) + ctl.show_summary = 1, ctl.show_main = 1; + else + errx(EXIT_FAILURE, _("unsupported --summary argument")); + } else + ctl.show_summary = 1, ctl.show_main = 0; + break; + case OPT_DUMP_COUNTERS: + dump_counters = true; + break; + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + if (argv[optind]) + errtryhelp(EXIT_FAILURE); + +#define INITIALIZE_COLUMNS(COLUMN_SPEC) \ + for (i = 0; i < ARRAY_SIZE(COLUMN_SPEC); i++) \ + columns[ncolumns++] = COLUMN_SPEC[i] + if (!ncolumns) { + if (ctl.threads) + INITIALIZE_COLUMNS(default_threads_columns); + else + INITIALIZE_COLUMNS(default_columns); + } + + if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns), + &ncolumns, column_name_to_id) < 0) + return EXIT_FAILURE; + + scols_init_debug(0); + + INIT_LIST_HEAD(&ctl.procs); + + /* inilialize scols table */ + ctl.tb = scols_new_table(); + if (!ctl.tb) + err(EXIT_FAILURE, _("failed to allocate output table")); + + scols_table_enable_noheadings(ctl.tb, ctl.noheadings); + scols_table_enable_raw(ctl.tb, ctl.raw); + scols_table_enable_json(ctl.tb, ctl.json); + if (ctl.json) + scols_table_set_name(ctl.tb, "lsfd"); + + /* 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); + + if (!cl) + err(EXIT_FAILURE, _("failed to allocate output column")); + + if (ctl.notrunc) { + int flags = scols_column_get_flags(cl); + flags &= ~SCOLS_FL_TRUNC; + scols_column_set_flags(cl, flags); + } + } + + /* make fitler */ + if (filter_expr) { + ctl.filter = new_filter(filter_expr, debug_filter, "", &ctl); + free(filter_expr); + } + + if (dump_counters) { + if (list_empty(&counter_specs)) + dump_default_counter_specs(); + else + dump_counter_specs(&counter_specs); + return 0; + } + + /* make counters */ + if (ctl.show_summary) { + if (list_empty(&counter_specs)) + ctl.counters = new_default_counters(&ctl); + else { + ctl.counters = new_counters(&counter_specs, &ctl); + list_free(&counter_specs, struct counter_spec, specs, + free_counter_spec); + } + } + + if (n_pids > 0) + sort_pids(pids, n_pids); + + /* collect data */ + initialize_nodevs(); + initialize_classes(); + initialize_devdrvs(); + initialize_ipc_table(); + + collect_processes(&ctl, pids, n_pids); + free(pids); + + attach_xinfos(&ctl.procs); + + convert(&ctl.procs, &ctl); + + /* print */ + if (ctl.show_main) + emit(&ctl); + + if (ctl.show_summary && ctl.counters) + emit_summary(&ctl, ctl.counters); + + /* cleanup */ + delete(&ctl.procs, &ctl); + + finalize_ipc_table(); + finalize_devdrvs(); + finalize_classes(); + finalize_nodevs(); + + return 0; +} diff --git a/misc-utils/lsfd.h b/misc-utils/lsfd.h new file mode 100644 index 0000000..ea1c342 --- /dev/null +++ b/misc-utils/lsfd.h @@ -0,0 +1,247 @@ +/* + * lsfd(1) - list file descriptors + * + * Copyright (C) 2021 Red Hat, Inc. All rights reserved. + * Written by Masatake YAMATO <yamato@redhat.com> + * + * Very generally based on lsof(8) by Victor A. Abell <abe@purdue.edu> + * It supports multiple OSes. lsfd specializes to Linux. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef UTIL_LINUX_LSFD_H +#define UTIL_LINUX_LSFD_H + +#include <stdbool.h> +#include <sys/stat.h> +#include <dirent.h> +#include <inttypes.h> + +#include "list.h" +#include "path.h" +#include "strutils.h" + +/* + * column IDs + */ +enum { + COL_AINODECLASS, + COL_ASSOC, + COL_BLKDRV, + COL_CHRDRV, + COL_COMMAND, + COL_DELETED, + COL_DEV, + COL_DEVTYPE, + COL_ENDPOINTS, + COL_FD, + COL_FLAGS, + COL_INODE, + COL_INET_LADDR, + COL_INET_RADDR, + COL_INET6_LADDR, + COL_INET6_RADDR, + COL_KNAME, + COL_KTHREAD, + COL_MAJMIN, + COL_MAPLEN, + COL_MISCDEV, + COL_MNT_ID, + COL_MODE, + COL_NAME, + COL_NETLINK_GROUPS, + COL_NETLINK_LPORT, + COL_NETLINK_PROTOCOL, + COL_NLINK, + COL_NS_NAME, + COL_NS_TYPE, + COL_PACKET_IFACE, + COL_PACKET_PROTOCOL, + COL_PARTITION, + COL_PID, + COL_PIDFD_COMM, + COL_PIDFD_NSPID, + COL_PIDFD_PID, + COL_PING_ID, + COL_POS, + COL_RAW_PROTOCOL, + COL_RDEV, + COL_SIZE, + COL_SOCK_LISTENING, + COL_SOCK_NETNS, + COL_SOCK_PROTONAME, + COL_SOCK_STATE, + COL_SOCK_TYPE, + COL_SOURCE, + COL_STTYPE, + COL_TCP_LADDR, + COL_TCP_RADDR, + COL_TCP_LPORT, + COL_TCP_RPORT, + COL_TID, + COL_TYPE, + COL_UDP_LADDR, + COL_UDP_RADDR, + COL_UDP_LPORT, + COL_UDP_RPORT, + COL_UDPLITE_LADDR, + COL_UDPLITE_RADDR, + COL_UDPLITE_LPORT, + COL_UDPLITE_RPORT, + COL_UID, /* process */ + COL_UNIX_PATH, + COL_USER, /* process */ + COL_FUID, /* file */ + COL_OWNER, /* file */ + LSFD_N_COLS /* This must be at last. */ +}; + +/* + * Process structure + */ +enum association { + ASSOC_EXE = 1, + ASSOC_CWD, + ASSOC_ROOT, + ASSOC_NS_CGROUP, + ASSOC_NS_IPC, + ASSOC_NS_MNT, + ASSOC_NS_NET, + ASSOC_NS_PID, + ASSOC_NS_PID4C, + ASSOC_NS_TIME, + ASSOC_NS_TIME4C, + ASSOC_NS_USER, + ASSOC_NS_UTS, + ASSOC_MEM, /* private file mapping */ + ASSOC_SHM, /* shared file mapping */ + N_ASSOCS, +}; + +struct proc { + pid_t pid; + struct proc * leader; + char *command; + uid_t uid; + ino_t ns_mnt; + struct list_head procs; + struct list_head files; + unsigned int kthread: 1; +}; + +struct proc *get_proc(pid_t pid); + +/* + * File class + */ +struct file { + struct list_head files; + const struct file_class *class; + int association; + char *name; + struct stat stat; + mode_t mode; + struct proc *proc; + + uint64_t pos; + uint64_t map_start; + uint64_t map_end; + + unsigned int sys_flags; + unsigned int mnt_id; +}; + +#define is_opened_file(_f) ((_f)->association >= 0) +#define is_mapped_file(_f) (is_association((_f), SHM) || is_association((_f), MEM)) +#define is_association(_f, a) ((_f)->association < 0 && (_f)->association == -ASSOC_ ## a) + +struct file_class { + const struct file_class *super; + size_t size; + void (*initialize_class)(void); + void (*finalize_class)(void); + bool (*fill_column)(struct proc *proc, + struct file *file, + struct libscols_line *ln, + int column_id, + size_t column_index); + int (*handle_fdinfo)(struct file *file, const char *key, const char* value); + void (*attach_xinfo)(struct file *file); + void (*initialize_content)(struct file *file); + void (*free_content)(struct file *file); + const struct ipc_class *(*get_ipc_class)(struct file *file); +}; + +extern const struct file_class file_class, cdev_class, bdev_class, sock_class, unkn_class, fifo_class, + nsfs_file_class; + +/* + * IPC + */ +struct ipc { + const struct ipc_class *class; + struct list_head endpoints; + struct list_head ipcs; +}; + +struct ipc_endpoint { + struct ipc *ipc; + struct list_head endpoints; +}; + +struct ipc_class { + unsigned int (*get_hash)(struct file *file); + bool (*is_suitable_ipc)(struct ipc *ipc, struct file *file); + void (*free)(struct ipc *ipc); +}; + +struct ipc *get_ipc(struct file *file); +void add_ipc(struct ipc *ipc, unsigned int hash); + +/* + * Name managing + */ +struct name_manager; + +struct name_manager *new_name_manager(void); +void free_name_manager(struct name_manager *nm); +const char *get_name(struct name_manager *nm, unsigned long id); +unsigned long add_name(struct name_manager *nm, const char *name); + +const char *get_partition(dev_t dev); +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); +} + +/* + * Net namespace + */ +void load_sock_xinfo(struct path_cxt *pc, const char *name, ino_t netns); +bool is_nsfs_dev(dev_t dev); + +#endif /* UTIL_LINUX_LSFD_H */ diff --git a/misc-utils/lslocks.8 b/misc-utils/lslocks.8 new file mode 100644 index 0000000..255742d --- /dev/null +++ b/misc-utils/lslocks.8 @@ -0,0 +1,175 @@ +'\" t +.\" Title: lslocks +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-12-01 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "LSLOCKS" "8" "2023-12-01" "util\-linux 2.39.3" "System Administration" +.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" +lslocks \- list local system locks +.SH "SYNOPSIS" +.sp +\fBlslocks\fP [options] +.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 +.RS 4 +Print the sizes in bytes rather than in a human\-readable format. +.sp +By default, the unit, sizes are expressed in, is byte, and unit prefixes are in +power of 2^10 (1024). Abbreviations of symbols are exhibited truncated in order +to reach a better readability, by exhibiting alone the first letter of them; +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\-i\fP, \fB\-\-noinaccessible\fP +.RS 4 +Ignore lock files which are inaccessible for the current user. +.RE +.sp +\fB\-J\fP, \fB\-\-json\fP +.RS 4 +Use JSON output format. +.RE +.sp +\fB\-n\fP, \fB\-\-noheadings\fP +.RS 4 +Do not print a header line. +.RE +.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. +.sp +The default list of columns may be extended if \fIlist\fP is specified in the format \fI+list\fP (e.g., \fBlslocks \-o +BLOCKER\fP). +.RE +.sp +\fB\-\-output\-all\fP +.RS 4 +Output all available columns. +.RE +.sp +\fB\-p\fP, \fB\-\-pid\fP \fIpid\fP +.RS 4 +Display only the locks held by the process with this \fIpid\fP. +.RE +.sp +\fB\-r\fP, \fB\-\-raw\fP +.RS 4 +Use the raw output format. +.RE +.sp +\fB\-u\fP, \fB\-\-notruncate\fP +.RS 4 +Do not truncate text in columns. +.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" +.sp +COMMAND +.RS 4 +The command name of the process holding the lock. +.RE +.sp +PID +.RS 4 +The process ID of the process which holds the lock or \-1 for OFDLCK. +.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)). +.RE +.sp +SIZE +.RS 4 +Size of the locked file. +.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). +.RE +.sp +M +.RS 4 +Whether the lock is mandatory; 0 means no (meaning the lock is only advisory), 1 means yes. (See \fBfcntl\fP(2).) +.RE +.sp +START +.RS 4 +Relative byte offset of the lock. +.RE +.sp +END +.RS 4 +Ending offset of the lock. +.RE +.sp +PATH +.RS 4 +Full path of the lock. If none is found, or there are no permissions to read the path, it will fall back to the device\(cqs mountpoint and "..." is appended to the path. The path might be truncated; use \fB\-\-notruncate\fP to get the full path. +.RE +.sp +BLOCKER +.RS 4 +The PID of the process which blocks the lock. +.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. +.SH "AUTHORS" +.sp +.MTO "dave\(atgnu.org" "Davidlohr Bueso" "" +.SH "SEE ALSO" +.sp +\fBflock\fP(1), +\fBfcntl\fP(2), +\fBlockf\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 \fBlslocks\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/lslocks.8.adoc b/misc-utils/lslocks.8.adoc new file mode 100644 index 0000000..21ad643 --- /dev/null +++ b/misc-utils/lslocks.8.adoc @@ -0,0 +1,120 @@ +//po4a: entry man manual +//// +Man page for the lslocks command. +Copyright 2012 Davidlohr Bueso <dave@gnu.org> +May be distributed under the GNU General Public License +//// +//// +Copyright 1994 Salvatore Valente (svalente@mit.edu) +Copyright 1992 Rickard E. Faith (faith@cs.unc.edu) +May be distributed under the GNU General Public License +//// += lslocks(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: lslocks +:plus: + + +== NAME + +lslocks - list local system locks + +== SYNOPSIS + +*lslocks* [options] + +== DESCRIPTION + +*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[] + +*-i*, *--noinaccessible*:: +Ignore lock files which are inaccessible for the current user. + +*-J*, *--json*:: +Use JSON output format. + +*-n*, *--noheadings*:: +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 default list of columns may be extended if _list_ is specified in the format _{plus}list_ (e.g., *lslocks -o {plus}BLOCKER*). +//TRANSLATORS: Keep {plus} untranslated. + +*--output-all*:: +Output all available columns. + +*-p*, *--pid* _pid_:: +Display only the locks held by the process with this _pid_. + +*-r*, *--raw*:: +Use the raw output format. + +*-u*, *--notruncate*:: +Do not truncate text in columns. + +include::man-common/help-version.adoc[] + +== OUTPUT + +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. + +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)). + +SIZE:: +Size of the locked file. + +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). + +M:: +Whether the lock is mandatory; 0 means no (meaning the lock is only advisory), 1 means yes. (See *fcntl*(2).) + +START:: +Relative byte offset of the lock. + +END:: +Ending offset of the lock. + +PATH:: +Full path of the lock. If none is found, or there are no permissions to read the path, it will fall back to the device's mountpoint and "..." is appended to the path. The path might be truncated; use *--notruncate* to get the full path. + +BLOCKER:: +The PID of the process which blocks the lock. + +== 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. + +== AUTHORS + +mailto:dave@gnu.org[Davidlohr Bueso] + +== SEE ALSO + +*flock*(1), +*fcntl*(2), +*lockf*(3) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/lslocks.c b/misc-utils/lslocks.c new file mode 100644 index 0000000..caca13f --- /dev/null +++ b/misc-utils/lslocks.c @@ -0,0 +1,679 @@ +/* + * lslocks(8) - list local system locks + * + * Copyright (C) 2012 Davidlohr Bueso <dave@gnu.org> + * + * Very generally based on lslk(8) by Victor A. Abell <abe@purdue.edu> + * Since it stopped being maintained over a decade ago, this + * program should be considered its replacement. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <stdio.h> +#include <string.h> +#include <getopt.h> +#include <stdlib.h> +#include <assert.h> +#include <dirent.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <libmount.h> +#include <libsmartcols.h> + +#include "pathnames.h" +#include "canonicalize.h" +#include "nls.h" +#include "xalloc.h" +#include "strutils.h" +#include "c.h" +#include "list.h" +#include "closestream.h" +#include "optutils.h" +#include "procfs.h" + +/* column IDs */ +enum { + COL_SRC = 0, + COL_PID, + COL_TYPE, + COL_SIZE, + COL_INODE, + COL_MAJMIN, + COL_MODE, + COL_M, + COL_START, + COL_END, + COL_PATH, + COL_BLOCKER +}; + +/* 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; +}; + +/* columns descriptions */ +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_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") }, + [COL_M] = { "M", 1, 0, N_("mandatory state of the lock: 0 (none), 1 (set)")}, + [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") } +}; + +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 */ +static int no_headings; +static int no_inaccessible; +static int raw; +static int json; +static int bytes; + +struct lock { + struct list_head locks; + + char *cmdname; + pid_t pid; + char *path; + char *type; + char *mode; + off_t start; + off_t end; + ino_t inode; + dev_t dev; + unsigned int mandatory :1, + blocked :1; + uint64_t size; + int id; +}; + +static void rem_lock(struct lock *lock) +{ + if (!lock) + return; + + free(lock->path); + free(lock->mode); + free(lock->cmdname); + free(lock->type); + list_del(&lock->locks); + free(lock); +} + +static void disable_columns_truncate(void) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(infos); i++) + infos[i].flags &= ~SCOLS_FL_TRUNC; +} + +/* + * Associate the device's mountpoint for a filename + */ +static char *get_fallback_filename(dev_t dev) +{ + struct libmnt_fs *fs; + char *res = NULL; + + if (!tab) { + tab = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO); + if (!tab) + return NULL; + } + + fs = mnt_table_find_devno(tab, dev, MNT_ITER_BACKWARD); + if (!fs) + return NULL; + + xasprintf(&res, "%s...", mnt_fs_get_target(fs)); + return res; +} + +/* + * Return the absolute path of a file from + * a given inode number (and its size) + */ +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; + int fd; + char path[PATH_MAX], sym[PATH_MAX], *ret = NULL; + + *size = 0; + memset(path, 0, sizeof(path)); + memset(sym, 0, sizeof(sym)); + + /* + * We know the pid so we don't have to + * iterate the *entire* filesystem searching + * for the damn file. + */ + snprintf(path, sizeof(path), "/proc/%d/fd/", lock_pid); + if (!(dirp = opendir(path))) + return NULL; + + if ((len = 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; + + errno = 0; + + /* care only for numerical descriptors */ + if (!strtol(dp->d_name, (char **) NULL, 10) || errno) + continue; + + if (!fstatat(fd, dp->d_name, &sb, 0) + && inode != sb.st_ino) + continue; + + if ((len = readlinkat(fd, dp->d_name, sym, sizeof(sym) - 1)) < 1) + goto out; + + *size = sb.st_size; + sym[len] = '\0'; + + ret = xstrdup(sym); + break; + } +out: + closedir(dirp); + return ret; +} + +/* + * Return the inode number from a string + */ +static ino_t get_dev_inode(char *str, dev_t *dev) +{ + unsigned int maj = 0, min = 0; + ino_t inum = 0; + + if (sscanf(str, "%x:%x:%ju", &maj, &min, &inum) != 3) + errx(EXIT_FAILURE, _("failed to parse '%s'"), str); + + *dev = (dev_t) makedev(maj, min); + return inum; +} + +static int get_local_locks(struct list_head *locks) +{ + int i; + FILE *fp; + char buf[PATH_MAX], *tok = NULL; + size_t sz; + struct lock *l; + + if (!(fp = fopen(_PATH_PROC_LOCKS, "r"))) + return -1; + + while (fgets(buf, sizeof(buf), fp)) { + + l = xcalloc(1, sizeof(*l)); + INIT_LIST_HEAD(&l->locks); + + 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: */ + 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; + + 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. + */ + 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)")); + } else + l->cmdname = xstrdup(_("(undefined)")); + 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 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; + } + } + + l->path = get_filename_sz(l->inode, l->pid, &sz); + + /* no permissions -- ignore */ + if (!l->path && no_inaccessible) { + rem_lock(l); + continue; + } + + 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; + + list_add(&l->locks, locks); + } + + fclose(fp); + return 0; +} + +static int column_name_to_id(const char *name, size_t namesz) +{ + size_t i; + + assert(name); + + 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 inline int get_column_id(int num) +{ + assert(num >= 0); + assert((size_t) num < ncolumns); + assert(columns[num] < (int) ARRAY_SIZE(infos)); + + return columns[num]; +} + + +static inline struct colinfo *get_column_info(unsigned num) +{ + return &infos[ get_column_id(num) ]; +} + +static pid_t get_blocker(int id, struct list_head *locks) +{ + struct list_head *p; + + list_for_each(p, locks) { + struct lock *l = list_entry(p, struct lock, locks); + + if (l->id == id && !l->blocked) + return l->pid; + } + + return 0; +} + +static void add_scols_line(struct libscols_table *table, struct lock *l, struct list_head *locks) +{ + size_t i; + struct libscols_line *line; + /* + * Whenever cmdname or filename is NULL it is most + * likely because there's no read permissions + * for the specified process. + */ + const char *notfnd = ""; + + assert(l); + assert(table); + + line = scols_table_new_line(table, NULL); + if (!line) + err(EXIT_FAILURE, _("failed to allocate output line")); + + for (i = 0; i < ncolumns; i++) { + char *str = NULL; + + switch (get_column_id(i)) { + case COL_SRC: + xasprintf(&str, "%s", l->cmdname ? l->cmdname : notfnd); + break; + case COL_PID: + xasprintf(&str, "%d", l->pid); + break; + case COL_TYPE: + xasprintf(&str, "%s", l->type); + break; + case COL_INODE: + xasprintf(&str, "%ju", (uintmax_t) l->inode); + break; + case COL_MAJMIN: + if (json || raw) + xasprintf(&str, "%u:%u", major(l->dev), minor(l->dev)); + else + xasprintf(&str, "%3u:%-3u", major(l->dev), minor(l->dev)); + break; + case COL_SIZE: + if (!l->size) + break; + if (bytes) + xasprintf(&str, "%ju", l->size); + else + str = size_to_human_string(SIZE_SUFFIX_1LETTER, l->size); + break; + case COL_MODE: + xasprintf(&str, "%s%s", l->mode, l->blocked ? "*" : ""); + break; + case COL_M: + xasprintf(&str, "%d", l->mandatory ? 1 : 0); + break; + case COL_START: + xasprintf(&str, "%jd", l->start); + break; + case COL_END: + xasprintf(&str, "%jd", l->end); + break; + case COL_PATH: + xasprintf(&str, "%s", l->path ? l->path : notfnd); + break; + case COL_BLOCKER: + { + pid_t bl = l->blocked && l->id ? + get_blocker(l->id, locks) : 0; + if (bl) + xasprintf(&str, "%d", (int) bl); + } + default: + break; + } + + if (str && scols_line_refer_data(line, i, str)) + err(EXIT_FAILURE, _("failed to add output data")); + } +} + +static int show_locks(struct list_head *locks) +{ + int rc = 0; + size_t i; + struct list_head *p, *pnext; + struct libscols_table *table; + + table = scols_new_table(); + if (!table) + err(EXIT_FAILURE, _("failed to allocate output table")); + + scols_table_enable_raw(table, raw); + scols_table_enable_json(table, json); + scols_table_enable_noheadings(table, no_headings); + + if (json) + scols_table_set_name(table, "locks"); + + for (i = 0; i < ncolumns; i++) { + struct libscols_column *cl; + 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 (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; + } + } + + } + + /* prepare data for output */ + list_for_each(p, locks) { + struct lock *l = list_entry(p, struct lock, locks); + + if (pid && 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); + } + + scols_print_table(table); + scols_unref_table(table); + return rc; +} + + +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_SEPARATOR, out); + fputs(_("List local system locks.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -b, --bytes print SIZE in bytes rather than in human readable format\n"), out); + 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(_(" --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(USAGE_COLUMNS, out); + + for (i = 0; i < ARRAY_SIZE(infos); i++) + fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help)); + + printf(USAGE_MAN_TAIL("lslocks(8)")); + + exit(EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + int c, rc = 0; + struct list_head locks; + char *outarg = NULL; + enum { + OPT_OUTPUT_ALL = CHAR_MAX + 1 + }; + static const struct option long_opts[] = { + { "bytes", no_argument, NULL, 'b' }, + { "json", no_argument, NULL, 'J' }, + { "pid", required_argument, NULL, 'p' }, + { "help", no_argument, NULL, 'h' }, + { "output", required_argument, NULL, 'o' }, + { "output-all", no_argument, NULL, OPT_OUTPUT_ALL }, + { "notruncate", no_argument, NULL, 'u' }, + { "version", no_argument, NULL, 'V' }, + { "noheadings", no_argument, NULL, 'n' }, + { "raw", no_argument, NULL, 'r' }, + { "noinaccessible", no_argument, NULL, 'i' }, + { NULL, 0, NULL, 0 } + }; + + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { 'J','r' }, + { 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, + "biJp:o:nruhV", long_opts, NULL)) != -1) { + + err_exclusive_options(c, long_opts, excl, excl_st); + + switch(c) { + case 'b': + bytes = 1; + break; + case 'i': + no_inaccessible = 1; + break; + case 'J': + json = 1; + break; + case 'p': + pid = strtos32_or_err(optarg, _("invalid PID argument")); + break; + case 'o': + outarg = optarg; + break; + case OPT_OUTPUT_ALL: + for (ncolumns = 0; ncolumns < ARRAY_SIZE(infos); ncolumns++) + columns[ncolumns] = ncolumns; + break; + case 'n': + no_headings = 1; + break; + case 'r': + raw = 1; + break; + case 'u': + disable_columns_truncate(); + break; + + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + INIT_LIST_HEAD(&locks); + + if (!ncolumns) { + /* default columns */ + columns[ncolumns++] = COL_SRC; + columns[ncolumns++] = COL_PID; + columns[ncolumns++] = COL_TYPE; + columns[ncolumns++] = COL_SIZE; + columns[ncolumns++] = COL_MODE; + columns[ncolumns++] = COL_M; + columns[ncolumns++] = COL_START; + columns[ncolumns++] = COL_END; + columns[ncolumns++] = COL_PATH; + } + + if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns), + &ncolumns, column_name_to_id) < 0) + return EXIT_FAILURE; + + scols_init_debug(0); + + rc = get_local_locks(&locks); + + if (!rc && !list_empty(&locks)) + rc = show_locks(&locks); + + mnt_unref_table(tab); + return rc; +} diff --git a/misc-utils/mcookie.1 b/misc-utils/mcookie.1 new file mode 100644 index 0000000..0062930 --- /dev/null +++ b/misc-utils/mcookie.1 @@ -0,0 +1,96 @@ +'\" t +.\" Title: mcookie +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "MCOOKIE" "1" "2023-10-23" "util\-linux 2.39.3" "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" +mcookie \- generate magic cookies for xauth +.SH "SYNOPSIS" +.sp +\fBmcookie\fP [options] +.SH "DESCRIPTION" +.sp +\fBmcookie\fP generates a 128\-bit random hexadecimal number for use with the X authority system. Typical usage: +.RS 3 +.ll -.6i +.sp +\fBxauth add :0 . \f(CRmcookie\fP\fP +.br +.RE +.ll +.sp +The "random" number generated is actually the MD5 message digest of random information coming from one of the sources \fBgetrandom\fP(2) system call, \fI/dev/urandom\fP, \fI/dev/random\fP, or the \fIlibc pseudo\-random functions\fP, in this preference order. See also the option \fB\-\-file\fP. +.SH "OPTIONS" +.sp +\fB\-f\fP, \fB\-\-file\fP \fIfile\fP +.RS 4 +Use this \fIfile\fP as an additional source of randomness (for example \fI/dev/urandom\fP). When \fIfile\fP is \*(Aq\-\*(Aq, characters are read from standard input. +.RE +.sp +\fB\-m\fP, \fB\-\-max\-size\fP \fInumber\fP +.RS 4 +Read from \fIfile\fP only this \fInumber\fP of bytes. This option is meant to be used when reading additional randomness from a file or device. +.sp +The \fInumber\fP argument may be followed by the multiplicative suffixes KiB=1024, MiB=1024*1024, and so on for GiB, TiB, PiB, EiB, ZiB and YiB (the "iB" is optional, e.g., "K" has the same meaning as "KiB") or the suffixes KB=1000, MB=1000*1000, and so on for GB, TB, PB, EB, ZB and YB. +.RE +.sp +\fB\-v\fP, \fB\-\-verbose\fP +.RS 4 +Inform where randomness originated, with amount of entropy read from each source. +.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 "FILES" +.sp +\fI/dev/urandom\fP +.sp +\fI/dev/random\fP +.SH "BUGS" +.sp +It is assumed that none of the randomness sources will block. +.SH "SEE ALSO" +.sp +\fBmd5sum\fP(1), +\fBX\fP(7), +\fBxauth\fP(1), +\fBrand\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 \fBmcookie\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/mcookie.1.adoc b/misc-utils/mcookie.1.adoc new file mode 100644 index 0000000..9a77e42 --- /dev/null +++ b/misc-utils/mcookie.1.adoc @@ -0,0 +1,67 @@ +//po4a: entry man manual +// mcookie.1 -- +// Public Domain 1995 Rickard E. Faith (faith@cs.unc.edu) += mcookie(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: mcookie + +== NAME + +mcookie - generate magic cookies for xauth + +== SYNOPSIS + +*mcookie* [options] + +== DESCRIPTION + +*mcookie* generates a 128-bit random hexadecimal number for use with the X authority system. Typical usage: + +____ +*xauth add :0 . `mcookie`* +____ + +The "random" number generated is actually the MD5 message digest of random information coming from one of the sources *getrandom*(2) system call, _/dev/urandom_, _/dev/random_, or the _libc pseudo-random functions_, in this preference order. See also the option *--file*. + +== OPTIONS + +*-f*, *--file* _file_:: +Use this _file_ as an additional source of randomness (for example _/dev/urandom_). When _file_ is '-', characters are read from standard input. + +*-m*, *--max-size* _number_:: +Read from _file_ only this _number_ of bytes. This option is meant to be used when reading additional randomness from a file or device. ++ +The _number_ argument may be followed by the multiplicative suffixes KiB=1024, MiB=1024*1024, and so on for GiB, TiB, PiB, EiB, ZiB and YiB (the "iB" is optional, e.g., "K" has the same meaning as "KiB") or the suffixes KB=1000, MB=1000*1000, and so on for GB, TB, PB, EB, ZB and YB. + +*-v*, *--verbose*:: +Inform where randomness originated, with amount of entropy read from each source. + +include::man-common/help-version.adoc[] + +== FILES + +_/dev/urandom_ + +_/dev/random_ + +== BUGS + +It is assumed that none of the randomness sources will block. + +== SEE ALSO + +*md5sum*(1), +*X*(7), +*xauth*(1), +*rand*(3) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/mcookie.c b/misc-utils/mcookie.c new file mode 100644 index 0000000..be5c34a --- /dev/null +++ b/misc-utils/mcookie.c @@ -0,0 +1,200 @@ +/* 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) + * This program comes with ABSOLUTELY NO WARRANTY. + * + * This program gathers some random bits of data and used the MD5 + * message-digest algorithm to generate a 128-bit hexadecimal number for + * use with xauth(1). + * + * NOTE: Unless /dev/random is available, this program does not actually + * gather 128 bits of random information, so the magic cookie generated + * will be considerably easier to guess than one might expect. + * + * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + * 1999-03-21 aeb: Added some fragments of code from Colin Plumb. + * + */ + +#include "c.h" +#include "md5.h" +#include "nls.h" +#include "closestream.h" +#include "randutils.h" +#include "strutils.h" +#include "xalloc.h" +#include "all-io.h" + +#include <fcntl.h> +#include <getopt.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/time.h> +#include <unistd.h> + +enum { + BUFFERSIZE = 4096, + RAND_BYTES = 128 +}; + +struct mcookie_control { + struct UL_MD5Context ctx; + char **files; + size_t nfiles; + uint64_t maxsz; + + unsigned int verbose:1; +}; + +/* The basic function to hash a file */ +static uint64_t hash_file(struct mcookie_control *ctl, int fd) +{ + unsigned char buf[BUFFERSIZE]; + uint64_t wanted, count; + + wanted = ctl->maxsz ? ctl->maxsz : sizeof(buf); + + for (count = 0; count < wanted; ) { + size_t rdsz = sizeof(buf); + ssize_t r; + + if (wanted - count < rdsz) + rdsz = wanted - count; + + r = read_all(fd, (char *) buf, rdsz); + if (r <= 0) + break; + ul_MD5Update(&ctl->ctx, buf, r); + count += r; + } + /* Separate files with a null byte */ + buf[0] = '\0'; + ul_MD5Update(&ctl->ctx, buf, 1); + return count; +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options]\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Generate magic cookies for xauth.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -f, --file <file> use file as a cookie seed\n"), out); + fputs(_(" -m, --max-size <num> limit how much is read from seed files\n"), out); + fputs(_(" -v, --verbose explain what is being done\n"), out); + + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(23)); + + fputs(USAGE_ARGUMENTS, out); + printf(USAGE_ARG_SIZE(_("<num>"))); + + printf(USAGE_MAN_TAIL("mcookie(1)")); + + exit(EXIT_SUCCESS); +} + +static void randomness_from_files(struct mcookie_control *ctl) +{ + size_t i; + + for (i = 0; i < ctl->nfiles; i++) { + const char *fname = ctl->files[i]; + size_t count; + int fd; + + if (*fname == '-' && !*(fname + 1)) + fd = STDIN_FILENO; + else + fd = open(fname, O_RDONLY); + + if (fd < 0) { + warn(_("cannot open %s"), fname); + } else { + count = hash_file(ctl, fd); + if (ctl->verbose) + fprintf(stderr, + P_("Got %zu byte from %s\n", + "Got %zu bytes from %s\n", count), + count, fname); + + if (fd != STDIN_FILENO && close(fd)) + err(EXIT_FAILURE, _("closing %s failed"), fname); + } + } +} + +int main(int argc, char **argv) +{ + struct mcookie_control ctl = { .verbose = 0 }; + size_t i; + unsigned char digest[UL_MD5LENGTH]; + unsigned char buf[RAND_BYTES]; + int c; + + static const struct option longopts[] = { + {"file", required_argument, NULL, 'f'}, + {"max-size", required_argument, NULL, 'm'}, + {"verbose", no_argument, NULL, 'v'}, + {"version", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + while ((c = getopt_long(argc, argv, "f:m:vVh", longopts, NULL)) != -1) { + switch (c) { + case 'v': + ctl.verbose = 1; + break; + case 'f': + if (!ctl.files) + ctl.files = xmalloc(sizeof(char *) * argc); + ctl.files[ctl.nfiles++] = optarg; + break; + case 'm': + ctl.maxsz = strtosize_or_err(optarg, + _("failed to parse length")); + break; + + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (ctl.maxsz && ctl.nfiles == 0) + warnx(_("--max-size ignored when used without --file")); + + ul_MD5Init(&ctl.ctx); + randomness_from_files(&ctl); + free(ctl.files); + + ul_random_get_bytes(&buf, RAND_BYTES); + ul_MD5Update(&ctl.ctx, buf, RAND_BYTES); + if (ctl.verbose) + fprintf(stderr, P_("Got %d byte from %s\n", + "Got %d bytes from %s\n", RAND_BYTES), + RAND_BYTES, random_tell_source()); + + ul_MD5Final(digest, &ctl.ctx); + for (i = 0; i < UL_MD5LENGTH; i++) + printf("%02x", digest[i]); + putchar('\n'); + + return EXIT_SUCCESS; +} diff --git a/misc-utils/meson.build b/misc-utils/meson.build new file mode 100644 index 0000000..7d21d02 --- /dev/null +++ b/misc-utils/meson.build @@ -0,0 +1,167 @@ +cal_sources = files( + 'cal.c', +) + +logger_sources = files( + 'logger.c', +) + \ + strutils_c + \ + strv_c + +look_sources = files( + 'look.c', +) + +mcookie_sources = files( + 'mcookie.c', +) + \ + md5_c + +namei_sources = files( + 'namei.c', +) + \ + strutils_c + \ + idcache_c + +whereis_sources = files( + 'whereis.c', +) + +lslocks_sources = files( + 'lslocks.c', +) + +lsblk_sources = files( + 'lsblk.c', + 'lsblk-mnt.c', + 'lsblk-properties.c', + 'lsblk-devtree.c', + 'lsblk.h', +) + +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', + 'lsfd-bdev.c', + 'lsfd-sock.c', + 'lsfd-sock.h', + 'lsfd-sock-xinfo.c', + 'lsfd-unkn.c', + 'lsfd-fifo.c', +) + +uuidgen_sources = files( + 'uuidgen.c', +) + +uuidparse_sources = files( + 'uuidparse.c', +) + +uuidd_sources = files( + 'uuidd.c', +) + \ + monotonic_c + \ + timer_c + +test_uuidd_sources = files( + 'test_uuidd.c', +) + +if build_uuidd and systemd.found() + uuidd_service = configure_file( + input : 'uuidd.service.in', + output : 'uuidd.service', + configuration : conf) + install_data( + uuidd_service, + install_dir : systemdsystemunitdir) + + uuidd_socket = configure_file( + input : 'uuidd.socket.in', + output : 'uuidd.socket', + configuration : conf) + install_data( + uuidd_socket, + install_dir : systemdsystemunitdir) +endif +if build_uuidd and sysvinit + uuidd_rc = configure_file( + input : 'uuidd.rc.in', + output : 'uuidd.rc', + configuration : conf) + install_data( + uuidd_rc, + install_mode : 'rwxr-xr-x', + install_dir : sysvinitrcdir) +endif + +blkid_sources = files( + 'blkid.c', +) + \ + ismounted_c + +findfs_sources = files( + 'findfs.c', +) + +wipefs_sources = files( + 'wipefs.c', +) + +findmnt_sources = files( + 'findmnt.c', + 'findmnt-verify.c', + 'findmnt.h', +) + +kill_sources = files( + 'kill.c', +) + +rename_sources = files( + 'rename.c', +) + +getopt_sources = files( + 'getopt.c', +) + +install_data( + 'getopt-example.bash', + 'getopt-example.tcsh', + install_dir : docdir, + install_mode: 'rwxr-xr-x') + +fincore_sources = files( + 'fincore.c', +) + +hardlink_sources = files( + 'hardlink.c', +) + \ + monotonic_c + \ + fileeq_c + +cal_sources = files( + 'cal.c', +) + +pipesz_sources = files( + 'pipesz.c', +) + +fadvise_sources = files( + 'fadvise.c', +) + +waitpid_sources = files( + 'waitpid.c', +) diff --git a/misc-utils/namei.1 b/misc-utils/namei.1 new file mode 100644 index 0000000..ad35f1d --- /dev/null +++ b/misc-utils/namei.1 @@ -0,0 +1,129 @@ +'\" t +.\" Title: namei +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-11-21 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "NAMEI" "1" "2023-11-21" "util\-linux 2.39.3" "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" +namei \- follow a pathname until a terminal point is found +.SH "SYNOPSIS" +.sp +\fBnamei\fP [options] \fIpathname\fP... +.SH "DESCRIPTION" +.sp +\fBnamei\fP interprets its arguments as pathnames to any type of Unix file (symlinks, files, directories, and so forth). \fBnamei\fP then follows each pathname until an endpoint is found (a file, a directory, a device node, etc). If it finds a symbolic link, it shows the link, and starts following it, indenting the output to show the context. +.sp +This program is useful for finding "too many levels of symbolic links" problems. +.sp +For each line of output, \fBnamei\fP uses the following characters to identify the file type found: +.sp +.if n .RS 4 +.nf +.fam C + f: = the pathname currently being resolved + d = directory + l = symbolic link (both the link and its contents are output) + s = socket + b = block device + c = character device + p = FIFO (named pipe) + \- = regular file + ? = an error of some kind +.fam +.fi +.if n .RE +.sp +\fBnamei\fP prints an informative message when the maximum number of symbolic links this system can have has been exceeded. +.SH "OPTIONS" +.sp +\fB\-l\fP, \fB\-\-long\fP +.RS 4 +Use the long listing format (same as \fB\-m \-o \-v\fP). +.RE +.sp +\fB\-m\fP, \fB\-\-modes\fP +.RS 4 +Show the mode bits of each file type in the style of \fBls\fP(1), for example \*(Aqrwxr\-xr\-x\*(Aq. +.RE +.sp +\fB\-n\fP, \fB\-\-nosymlinks\fP +.RS 4 +Don\(cqt follow symlinks. +.RE +.sp +\fB\-o\fP, \fB\-\-owners\fP +.RS 4 +Show owner and group name of each file. +.RE +.sp +\fB\-v\fP, \fB\-\-vertical\fP +.RS 4 +Vertically align the modes and owners. +.RE +.sp +\fB\-x\fP, \fB\-\-mountpoints\fP +.RS 4 +Show mountpoint directories with a \*(AqD\*(Aq rather than a \*(Aqd\*(Aq. +.RE +.sp +\fB\-Z\fP, \fB\-\-context\fP +.RS 4 +Show security context of the file or "?" if not available. +The support for security contexts is optional and does not have to be compiled to the \fBnamei\fP binary. +.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 "BUGS" +.sp +To be discovered. +.SH "AUTHORS" +.sp +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 +.MTO "kzak\(atredhat.com" "Karel Zak" "." +.SH "SEE ALSO" +.sp +\fBls\fP(1), +\fBstat\fP(1), +\fBsymlink\fP(7) +.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 \fBnamei\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/namei.1.adoc b/misc-utils/namei.1.adoc new file mode 100644 index 0000000..2cfeac1 --- /dev/null +++ b/misc-utils/namei.1.adoc @@ -0,0 +1,87 @@ +//po4a: entry man manual += namei(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: namei + +== NAME + +namei - follow a pathname until a terminal point is found + +== SYNOPSIS + +*namei* [options] _pathname_... + +== DESCRIPTION + +*namei* interprets its arguments as pathnames to any type of Unix file (symlinks, files, directories, and so forth). *namei* then follows each pathname until an endpoint is found (a file, a directory, a device node, etc). If it finds a symbolic link, it shows the link, and starts following it, indenting the output to show the context. + +This program is useful for finding "too many levels of symbolic links" problems. + +For each line of output, *namei* uses the following characters to identify the file type found: + +.... + f: = the pathname currently being resolved + d = directory + l = symbolic link (both the link and its contents are output) + s = socket + b = block device + c = character device + p = FIFO (named pipe) + - = regular file + ? = an error of some kind +.... + +*namei* prints an informative message when the maximum number of symbolic links this system can have has been exceeded. + +== OPTIONS + +*-l*, *--long*:: +Use the long listing format (same as *-m -o -v*). + +*-m*, *--modes*:: +Show the mode bits of each file type in the style of *ls*(1), for example 'rwxr-xr-x'. + +*-n*, *--nosymlinks*:: +Don't follow symlinks. + +*-o*, *--owners*:: +Show owner and group name of each file. + +*-v*, *--vertical*:: +Vertically align the modes and owners. + +*-x*, *--mountpoints*:: +Show mountpoint directories with a 'D' rather than a 'd'. + +*-Z*, *--context*:: +Show security context of the file or "?" if not available. +The support for security contexts is optional and does not have to be compiled to the *namei* binary. + +include::man-common/help-version.adoc[] + +== BUGS + +To be discovered. + +== AUTHORS + +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]. + +== SEE ALSO + +*ls*(1), +*stat*(1), +*symlink*(7) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/namei.c b/misc-utils/namei.c new file mode 100644 index 0000000..3d41407 --- /dev/null +++ b/misc-utils/namei.c @@ -0,0 +1,491 @@ +/* + * Copyright (C) 2008 Karel Zak <kzak@redhat.com> + * + * This file is part of util-linux. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * The original namei(1) was written by: + * Roger S. Southwick (May 2, 1990) + * Steve Tell (March 28, 1991) + * Arkadiusz Miśkiewicz (1999-02-22) + * Li Zefan (2007-09-10). + */ +#include <stdio.h> +#include <unistd.h> +#include <getopt.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <pwd.h> +#include <grp.h> + +#ifdef HAVE_LIBSELINUX +# include <selinux/selinux.h> +#endif + +#include "c.h" +#include "xalloc.h" +#include "nls.h" +#include "widechar.h" +#include "strutils.h" +#include "closestream.h" +#include "idcache.h" + +#ifndef MAXSYMLINKS +#define MAXSYMLINKS 256 +#endif + +#define NAMEI_NOLINKS (1 << 1) +#define NAMEI_MODES (1 << 2) +#define NAMEI_MNTS (1 << 3) +#define NAMEI_OWNERS (1 << 4) +#define NAMEI_VERTICAL (1 << 5) +#define NAMEI_CONTEXT (1 << 6) + + +struct namei { + struct stat st; /* item lstat() */ + char *name; /* item name */ + char *abslink; /* absolute symlink path */ + int relstart; /* offset of relative path in 'abslink' */ + struct namei *next; /* next item */ + int level; + int mountpoint; /* is mount point */ + int noent; /* this item not existing (stores errno from stat()) */ +#ifdef HAVE_LIBSELINUX + int context_len; /* length of selinux contexts, as returned by lgetfilecon(3) */ + char *context; /* selinux contexts, as set by lgetfilecon(3) */ +#endif +}; + +static int flags; +static struct idcache *gcache; /* groupnames */ +static struct idcache *ucache; /* usernames */ + +static void +free_namei(struct namei *nm) +{ + while (nm) { + struct namei *next = nm->next; +#ifdef HAVE_LIBSELINUX + free(nm->context); +#endif + free(nm->name); + free(nm->abslink); + free(nm); + nm = next; + } +} + +static void +readlink_to_namei(struct namei *nm, const char *path) +{ + char sym[PATH_MAX]; + ssize_t sz; + int isrel = 0; + + sz = readlink(path, sym, sizeof(sym)); + if (sz < 1) + err(EXIT_FAILURE, _("failed to read symlink: %s"), path); + if (*sym != '/') { + char *p = strrchr(path, '/'); + + if (p) { + isrel = 1; + nm->relstart = p - path; + sz += nm->relstart + 1; + } + } + nm->abslink = xmalloc(sz + 1); + + if (isrel) { + /* create the absolute path from the relative symlink */ + memcpy(nm->abslink, path, nm->relstart); + *(nm->abslink + nm->relstart) = '/'; + nm->relstart++; + memcpy(nm->abslink + nm->relstart, sym, sz - nm->relstart); + } else + /* - absolute link (foo -> /path/bar) + * - or link without any subdir (foo -> bar) + */ + memcpy(nm->abslink, sym, sz); + + nm->abslink[sz] = '\0'; +} + +static struct stat * +dotdot_stat(const char *dirname, struct stat *st) +{ + char *path; + size_t len; + +#define DOTDOTDIR "/.." + + if (!dirname) + return NULL; + + len = strlen(dirname); + path = xmalloc(len + sizeof(DOTDOTDIR)); + + memcpy(path, dirname, len); + memcpy(path + len, DOTDOTDIR, sizeof(DOTDOTDIR)); + + if (stat(path, st)) + err(EXIT_FAILURE, _("stat of %s failed"), path); + free(path); + return st; +} + +static struct namei * +new_namei(struct namei *parent, const char *path, const char *fname, int lev) +{ + struct namei *nm; + + if (!fname) + return NULL; + nm = xcalloc(1, sizeof(*nm)); + if (parent) + parent->next = nm; + + nm->level = lev; + nm->name = xstrdup(fname); + +#ifdef HAVE_LIBSELINUX + /* Don't use is_selinux_enabled() here. We need info about a context + * also on systems where SELinux is (temporary) disabled */ + nm->context_len = lgetfilecon(path, &nm->context); +#endif + if (lstat(path, &nm->st) != 0) { + nm->noent = errno; + return nm; + } + + if (S_ISLNK(nm->st.st_mode)) + readlink_to_namei(nm, path); + if (flags & NAMEI_OWNERS) { + add_uid(ucache, nm->st.st_uid); + add_gid(gcache, nm->st.st_gid); + } + + if ((flags & NAMEI_MNTS) && S_ISDIR(nm->st.st_mode)) { + struct stat stbuf, *sb = NULL; + + if (parent && S_ISDIR(parent->st.st_mode)) + sb = &parent->st; + else if (!parent || S_ISLNK(parent->st.st_mode)) + sb = dotdot_stat(path, &stbuf); + + if (sb && (sb->st_dev != nm->st.st_dev || /* different device */ + sb->st_ino == nm->st.st_ino)) /* root directory */ + nm->mountpoint = 1; + } + + return nm; +} + +static struct namei * +add_namei(struct namei *parent, const char *orgpath, int start, struct namei **last) +{ + struct namei *nm = NULL, *first = NULL; + char *fname, *end, *path; + int level = 0; + + if (!orgpath) + return NULL; + if (parent) { + nm = parent; + level = parent->level + 1; + } + path = xstrdup(orgpath); + fname = path + start; + + /* root directory */ + if (*fname == '/') { + while (*fname == '/') + fname++; /* eat extra '/' */ + first = nm = new_namei(nm, "/", "/", level); + } + + for (end = fname; fname && end; ) { + /* set end of filename */ + if (*fname) { + end = strchr(fname, '/'); + if (end) + *end = '\0'; + + /* create a new entry */ + nm = new_namei(nm, path, fname, level); + } else + end = NULL; + if (!first) + first = nm; + /* set begin of the next filename */ + if (end) { + *end++ = '/'; + while (*end == '/') + end++; /* eat extra '/' */ + } + fname = end; + } + + if (last) + *last = nm; + + free(path); + + return first; +} + +static int +follow_symlinks(struct namei *nm) +{ + int symcount = 0; + + for (; nm; nm = nm->next) { + struct namei *next, *last; + + if (nm->noent) + continue; + if (!S_ISLNK(nm->st.st_mode)) + continue; + if (++symcount > MAXSYMLINKS) { + /* drop the rest of the list */ + free_namei(nm->next); + nm->next = NULL; + return -1; + } + next = nm->next; + nm->next = add_namei(nm, nm->abslink, nm->relstart, &last); + if (last) + last->next = next; + else + nm->next = next; + } + return 0; +} + +static int +print_namei(struct namei *nm, char *path) +{ + int i; + + if (path) + printf("f: %s\n", path); + + for (; nm; nm = nm->next) { + char md[11]; + + if (nm->noent) { + int blanks = 1; + if (flags & NAMEI_MODES) + blanks += 9; + if (flags & NAMEI_OWNERS) + blanks += ucache->width + gcache->width + 2; + if (!(flags & NAMEI_VERTICAL)) + blanks += 1; + if (!(flags & NAMEI_CONTEXT)) + blanks += 1; + blanks += nm->level * 2; + printf("%*s ", blanks, ""); + printf("%s - %s\n", nm->name, strerror(nm->noent)); + return -1; + } + + xstrmode(nm->st.st_mode, md); + + if (nm->mountpoint) + md[0] = 'D'; + + if (!(flags & NAMEI_VERTICAL)) { + for (i = 0; i < nm->level; i++) + fputs(" ", stdout); + fputc(' ', stdout); + } + + if (flags & NAMEI_MODES) + printf("%s", md); + else + printf("%c", md[0]); + + if (flags & NAMEI_OWNERS) { + printf(" %-*s", ucache->width, + get_id(ucache, nm->st.st_uid)->name); + printf(" %-*s", gcache->width, + get_id(gcache, nm->st.st_gid)->name); + } +#ifdef HAVE_LIBSELINUX + if (flags & NAMEI_CONTEXT) { + if (nm->context) + printf(" %-*s", nm->context_len, nm->context); + else + printf(" ?"); + } +#endif + if (flags & NAMEI_VERTICAL) + for (i = 0; i < nm->level; i++) + fputs(" ", stdout); + + if (S_ISLNK(nm->st.st_mode)) + printf(" %s -> %s\n", nm->name, + nm->abslink + nm->relstart); + else + printf(" %s\n", nm->name); + } + return 0; +} + +static void __attribute__((__noreturn__)) usage(void) +{ + const char *p = program_invocation_short_name; + FILE *out = stdout; + + if (!*p) + p = "namei"; + + fputs(USAGE_HEADER, out); + fprintf(out, + _(" %s [options] <pathname>...\n"), p); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Follow a pathname until a terminal point is found.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_( + " -x, --mountpoints show mount point directories with a 'D'\n" + " -m, --modes show the mode bits of each file\n" + " -o, --owners show owner and group name of each file\n" + " -l, --long use a long listing format (-m -o -v) \n" + " -n, --nosymlinks don't follow symlinks\n" + " -v, --vertical vertical align of modes and owners\n"), out); +#ifdef HAVE_LIBSELINUX + fputs(_( " -Z, --context print any security context of each file \n"), out); +#endif + + printf(USAGE_HELP_OPTIONS(21)); + + printf(USAGE_MAN_TAIL("namei(1)")); + exit(EXIT_SUCCESS); +} + +static const struct option longopts[] = +{ + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "mountpoints", no_argument, NULL, 'x' }, + { "modes", no_argument, NULL, 'm' }, + { "owners", no_argument, NULL, 'o' }, + { "long", no_argument, NULL, 'l' }, + { "nolinks", no_argument, NULL, 'n' }, + { "vertical", no_argument, NULL, 'v' }, +#ifdef HAVE_LIBSELINUX + { "context", no_argument, NULL, 'Z' }, +#endif + { NULL, 0, NULL, 0 }, +}; + +int +main(int argc, char **argv) +{ + int c; + int rc = EXIT_SUCCESS; + static const char *shortopts = +#ifdef HAVE_LIBSELINUX + "Z" +#endif + "hVlmnovx"; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) { + switch(c) { + case 'l': + flags |= (NAMEI_OWNERS | NAMEI_MODES | NAMEI_VERTICAL); + break; + case 'm': + flags |= NAMEI_MODES; + break; + case 'n': + flags |= NAMEI_NOLINKS; + break; + case 'o': + flags |= NAMEI_OWNERS; + break; + case 'x': + flags |= NAMEI_MNTS; + break; + case 'v': + flags |= NAMEI_VERTICAL; + break; +#ifdef HAVE_LIBSELINUX + case 'Z': + flags |= NAMEI_CONTEXT; + break; +#endif + case 'h': + usage(); + case 'V': + print_version(EXIT_SUCCESS); + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (optind == argc) { + warnx(_("pathname argument is missing")); + errtryhelp(EXIT_FAILURE); + } + + ucache = new_idcache(); + if (!ucache) + err(EXIT_FAILURE, _("failed to allocate UID cache")); + gcache = new_idcache(); + if (!gcache) + err(EXIT_FAILURE, _("failed to allocate GID cache")); + + for(; optind < argc; optind++) { + char *path = argv[optind]; + struct namei *nm = NULL; + struct stat st; + + if (stat(path, &st) != 0) + rc = EXIT_FAILURE; + + nm = add_namei(NULL, path, 0, NULL); + if (nm) { + int sml = 0; + if (!(flags & NAMEI_NOLINKS)) + sml = follow_symlinks(nm); + if (print_namei(nm, path)) { + rc = EXIT_FAILURE; + continue; + } + free_namei(nm); + if (sml == -1) { + rc = EXIT_FAILURE; + warnx(_("%s: exceeded limit of symlinks"), path); + continue; + } + } + } + + free_idcache(ucache); + free_idcache(gcache); + + return rc; +} + diff --git a/misc-utils/pipesz.1 b/misc-utils/pipesz.1 new file mode 100644 index 0000000..b0da040 --- /dev/null +++ b/misc-utils/pipesz.1 @@ -0,0 +1,157 @@ +'\" t +.\" Title: pipesz +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-11-21 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "PIPESZ" "1" "2023-11-21" "util\-linux 2.39.3" "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" +pipesz \- set or examine pipe and FIFO buffer sizes +.SH "SYNOPSIS" +.sp +\fBpipesz\fP [options] [\-\-set \fIsize\fP] [\-\-] [\fIcommand\fP [argument] ...] +.sp +\fBpipesz\fP [options] \-\-get +.SH "DESCRIPTION" +.sp +Pipes and FIFOs maintain an internal buffer used to transfer data between the read end and the write end. In some cases, the default size of this internal buffer may not be appropriate. This program provides facilities to set and examine the size of these buffers. +.sp +The \fB\-\-set\fP operation sets pipe buffer sizes. If it is specified, it must be specified with an explicit \fIsize\fP. Otherwise, it is implied and the size is read from \fB/proc/sys/fs/pipe\-max\-size\fP. The kernel may adjust \fIsize\fP as described in \fBfcntl\fP(2). To determine the actual buffer sizes set, use the \fB\-\-verbose\fP option. If neither \fB\-\-file\fP nor \fB\-\-fd\fP are specified, \fB\-\-set\fP acts on standard output. +.sp +The \fB\-\-set\fP operation permits an optional \fIcommand\fP to execute after setting the pipe buffer sizes. This command is executed with the adjusted pipes. +.sp +The \fB\-\-get\fP operation outputs data in a tabular format. The first column is the name of the pipe as passed to \fBpipesz\fP. File descriptors are named as "fd \fIN\fP". The second column is the size, in bytes, of the pipe\(cqs internal buffer. The third column is the number of unread bytes currently in the pipe. The columns are separated by tabs (\*(Aq\(rst\*(Aq, ASCII 09h). If \fB\-\-verbose\fP is specified, a descriptive header is also emitted. If neither \fB\-\-file\fP nor \fB\-\-fd\fP are specified, \fB\-\-get\fP acts on standard input. +.sp +Unless the \fB\-\-check\fP option is specified, \fBpipesz\fP does \fInot\fP exit if it encounters an error while manipulating a file or file descriptor. This allows \fBpipesz\fP to be used generically without fear of disrupting the execution of pipelines should the type of certain files be later changed. For minimal disruption, the \fB\-\-quiet\fP option prevents warnings from being emitted in these cases. +.sp +The kernel imposes limits on the amount of pipe buffer space unprivileged processes can use, though see \fBBUGS\fP below. The kernel will also refuse to shrink a pipe buffer if this would cause a loss of buffered data. See \fBpipe\fP(7) for additional details. +.sp +\fBpipesz\fP supports specifying multiple short options consecutively, in the usual \fBgetopt\fP(3) fashion. The first non\-option argument is interpreted as \fIcommand\fP. If \fIcommand\fP might begin with \*(Aq\-\*(Aq, use \*(Aq\-\-\*(Aq to separate it from arguments to \fBpipesz\fP. In shell scripts, it is good practice to use \*(Aq\-\-\*(Aq when parameter expansion is involved. \fBpipesz\fP itself does not read from standard input and does not write to standard output unless \fB\-\-get\fP, \fB\-\-help\fP, or \fB\-\-version\fP are specified. +.SH "OPTIONS" +.sp +\fB\-g\fP, \fB\-\-get\fP +.RS 4 +Report the size of pipe buffers to standard output and exit. As a special behavior, if neither \fB\-\-file\fP nor \fB\-\-fd\fP are specified, standard input is examined. It is an error to specify this option in combination with \fB\-\-set\fP. +.RE +.sp +\fB\-s\fP, \fB\-\-set\fP \fIsize\fP +.RS 4 +Set the size of the pipe buffers, in bytes. This option may be suffixed by \fIK\fP, \fIM\fP, \fIG\fP, \fIKiB\fP, \fIMiB\fP, or \fIGiB\fP to indicate multiples of 1024. Fractional values are supported in this case. Additional suffixes are supported but are unlikely to be useful. If this option is not specified, a default value is used, as described above. If this option is specified multiple times, a warning is emitted and only the last\-specified \fIsize\fP is used. As a special behavior, if neither \fB\-\-file\fP nor \fB\-\-fd\fP are specified, standard output is adjusted. It is an error to specify this option in combination with \fB\-\-get\fP. +.RE +.sp +\fB\-f\fP, \fB\-\-file\fP \fIpath\fP +.RS 4 +Set the buffer size of the FIFO or pipe at \fIpath\fP, relative to the current working directory. You may specify this option multiple times to affect different files, and you may do so in combination with \fB\-\-fd\fP. Generally, this option is used with FIFOs, but it will also operate on anonymous pipes such as those found in \fB/proc/PID/fd\fP. Changes to the buffer size of FIFOs are not preserved across system restarts. +.RE +.sp +\fB\-n\fP, \fB\-\-fd\fP \fIfd\fP +.RS 4 +Set the buffer size of the pipe or FIFO passed to \fBpipesz\fP as the specified file descriptor number. You may specify this option multiple times to affect different file descriptors, and you may do so in combination with \fB\-\-file\fP. Shorthand options are provided for the common cases of fd 0 (stdin), fd 1 (stdout), and fd 2 (stderr). These should suffice in most cases. +.RE +.sp +\fB\-i\fP, \fB\-\-stdin\fP +.RS 4 +Shorthand for \fB\-\-fd 0\fP. +.RE +.sp +\fB\-o\fP, \fB\-\-stdout\fP +.RS 4 +Shorthand for \fB\-\-fd 1\fP. +.RE +.sp +\fB\-e\fP, \fB\-\-stderr\fP +.RS 4 +Shorthand for \fB\-\-fd 2\fP. +.RE +.sp +\fB\-c\fP, \fB\-\-check\fP +.RS 4 +Exit, without executing \fIcommand\fP, in case of any error while manipulating a file or file descriptor. The default behavior if this is not specified is to emit a warning to standard error and continue. +.RE +.sp +\fB\-q\fP, \fB\-\-quiet\fP +.RS 4 +Do not diagnose non\-fatal errors to standard error. This option does not affect the normal output of \fB\-\-get\fP, \fB\-\-verbose\fP, \fB\-\-help\fP, or \fB\-\-version\fP. +.RE +.sp +\fB\-v\fP, \fB\-\-verbose\fP +.RS 4 +If specified with \fB\-\-get\fP, \fBpipesz\fP will emit a descriptive header above the table. Otherwise, if specified, \fBpipesz\fP will print the actual buffer sizes set by the kernel to standard error. +.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 "EXAMPLES" +.sp +\fBpipesz\fP \fBdd\fP if=\fIfile\fP bs=1M | ... +.RS 4 +Runs \fBdd\fP(1) with an expanded standard output pipe, allowing it to avoid context switches when piping around large blocks. +.RE +.sp +\fBpipesz\fP \-s1M \-cf \fI/run/my\-service.fifo\fP +.RS 4 +Sets the pipe buffer size of a service FIFO to 1,048,576 bytes. If the buffer size could not be set, \fBpipesz\fP exits with an error. +.RE +.sp +\fBecho\fP hello | \fBpipesz\fP \-g +.RS 4 +Prints the size of pipe used by the shell to pass input to \fBpipesz\fP. Since \fBpipesz\fP does not read standard input, it may also report 6 unread bytes in the pipe, depending on relative timings. +.RE +.sp +\fBfind\fP /proc/\fIPID\fP/fd \-exec \fBpipesz\fP \-gqf \*(Aq{}\*(Aq \*(Aq;\*(Aq +.RS 4 +Prints the size and number of unread bytes of all pipes in use by \fIPID\fP. If some pipes are routinely full, \fBpipesz\fP might be able to mitigate a processing bottleneck. +.RE +.SH "NOTES" +.sp +Linux supports adjusting the size of pipe buffers since kernel 2.6.35. This release also introduced \fB/proc/sys/fs/pipe\-max\-size\fP. +.sp +This program uses \fBfcntl\fP(2) \fBF_GETPIPE_SZ\fP/\fBF_SETPIPE_SZ\fP to get and set pipe buffer sizes. +.sp +This program uses \fBioctl\fP(2) \fBFIONREAD\fP to report the amount of unread data in pipes. If for some reason this fails, the amount of unread data is reported as 0. +.SH "BUGS" +.sp +Before Linux 4.9, some bugs affect how certain resource limits are enforced when setting pipe buffer sizes. See \fBpipe\fP(7) for details. +.SH "AUTHORS" +.sp +.MTO "nwsharp\(atlive.com" "Nathan Sharp" "" +.SH "SEE ALSO" +.sp +\fBpipe\fP(7) +.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 \fBpipesz\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/pipesz.1.adoc b/misc-utils/pipesz.1.adoc new file mode 100644 index 0000000..b51de58 --- /dev/null +++ b/misc-utils/pipesz.1.adoc @@ -0,0 +1,109 @@ +//po4a: entry man manual += pipesz(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: pipesz + +== NAME + +pipesz - set or examine pipe and FIFO buffer sizes + +== SYNOPSIS + +*pipesz* [options] [--set _size_] [--] [_command_ [argument] ...] + +*pipesz* [options] --get + +== DESCRIPTION + +Pipes and FIFOs maintain an internal buffer used to transfer data between the read end and the write end. In some cases, the default size of this internal buffer may not be appropriate. This program provides facilities to set and examine the size of these buffers. + +The *--set* operation sets pipe buffer sizes. If it is specified, it must be specified with an explicit _size_. Otherwise, it is implied and the size is read from */proc/sys/fs/pipe-max-size*. The kernel may adjust _size_ as described in *fcntl*(2). To determine the actual buffer sizes set, use the *--verbose* option. If neither *--file* nor *--fd* are specified, *--set* acts on standard output. + +The *--set* operation permits an optional _command_ to execute after setting the pipe buffer sizes. This command is executed with the adjusted pipes. + +The *--get* operation outputs data in a tabular format. The first column is the name of the pipe as passed to *pipesz*. File descriptors are named as "fd _N_". The second column is the size, in bytes, of the pipe's internal buffer. The third column is the number of unread bytes currently in the pipe. The columns are separated by tabs ('\t', ASCII 09h). If *--verbose* is specified, a descriptive header is also emitted. If neither *--file* nor *--fd* are specified, *--get* acts on standard input. + +Unless the *--check* option is specified, *pipesz* does _not_ exit if it encounters an error while manipulating a file or file descriptor. This allows *pipesz* to be used generically without fear of disrupting the execution of pipelines should the type of certain files be later changed. For minimal disruption, the *--quiet* option prevents warnings from being emitted in these cases. + +The kernel imposes limits on the amount of pipe buffer space unprivileged processes can use, though see *BUGS* below. The kernel will also refuse to shrink a pipe buffer if this would cause a loss of buffered data. See *pipe*(7) for additional details. + +*pipesz* supports specifying multiple short options consecutively, in the usual *getopt*(3) fashion. The first non-option argument is interpreted as _command_. If _command_ might begin with '-', use '--' to separate it from arguments to *pipesz*. In shell scripts, it is good practice to use '--' when parameter expansion is involved. *pipesz* itself does not read from standard input and does not write to standard output unless *--get*, *--help*, or *--version* are specified. + +== OPTIONS + +*-g*, *--get*:: +Report the size of pipe buffers to standard output and exit. As a special behavior, if neither *--file* nor *--fd* are specified, standard input is examined. It is an error to specify this option in combination with *--set*. + +*-s*, *--set* _size_:: +Set the size of the pipe buffers, in bytes. This option may be suffixed by _K_, _M_, _G_, _KiB_, _MiB_, or _GiB_ to indicate multiples of 1024. Fractional values are supported in this case. Additional suffixes are supported but are unlikely to be useful. If this option is not specified, a default value is used, as described above. If this option is specified multiple times, a warning is emitted and only the last-specified _size_ is used. As a special behavior, if neither *--file* nor *--fd* are specified, standard output is adjusted. It is an error to specify this option in combination with *--get*. + +*-f*, *--file* _path_:: +Set the buffer size of the FIFO or pipe at _path_, relative to the current working directory. You may specify this option multiple times to affect different files, and you may do so in combination with *--fd*. Generally, this option is used with FIFOs, but it will also operate on anonymous pipes such as those found in */proc/PID/fd*. Changes to the buffer size of FIFOs are not preserved across system restarts. + +*-n*, *--fd* _fd_:: +Set the buffer size of the pipe or FIFO passed to *pipesz* as the specified file descriptor number. You may specify this option multiple times to affect different file descriptors, and you may do so in combination with *--file*. Shorthand options are provided for the common cases of fd 0 (stdin), fd 1 (stdout), and fd 2 (stderr). These should suffice in most cases. + +*-i*, *--stdin*:: +Shorthand for *--fd 0*. + +*-o*, *--stdout*:: +Shorthand for *--fd 1*. + +*-e*, *--stderr*:: +Shorthand for *--fd 2*. + +*-c*, *--check*:: +Exit, without executing _command_, in case of any error while manipulating a file or file descriptor. The default behavior if this is not specified is to emit a warning to standard error and continue. + +*-q*, *--quiet*:: +Do not diagnose non-fatal errors to standard error. This option does not affect the normal output of *--get*, *--verbose*, *--help*, or *--version*. + +*-v*, *--verbose*:: +If specified with *--get*, *pipesz* will emit a descriptive header above the table. Otherwise, if specified, *pipesz* will print the actual buffer sizes set by the kernel to standard error. + +include::man-common/help-version.adoc[] + +== EXAMPLES + +*pipesz* *dd* if=_file_ bs=1M | ...:: +Runs *dd*(1) with an expanded standard output pipe, allowing it to avoid context switches when piping around large blocks. + +*pipesz* -s1M -cf _/run/my-service.fifo_:: +Sets the pipe buffer size of a service FIFO to 1,048,576 bytes. If the buffer size could not be set, *pipesz* exits with an error. + +*echo* hello | *pipesz* -g:: +Prints the size of pipe used by the shell to pass input to *pipesz*. Since *pipesz* does not read standard input, it may also report 6 unread bytes in the pipe, depending on relative timings. + +*find* /proc/_PID_/fd -exec *pipesz* -gqf '{}' ';':: +Prints the size and number of unread bytes of all pipes in use by _PID_. If some pipes are routinely full, *pipesz* might be able to mitigate a processing bottleneck. + +== NOTES + +Linux supports adjusting the size of pipe buffers since kernel 2.6.35. This release also introduced */proc/sys/fs/pipe-max-size*. + +This program uses *fcntl*(2) *F_GETPIPE_SZ*/*F_SETPIPE_SZ* to get and set pipe buffer sizes. + +This program uses *ioctl*(2) *FIONREAD* to report the amount of unread data in pipes. If for some reason this fails, the amount of unread data is reported as 0. + +== BUGS + +Before Linux 4.9, some bugs affect how certain resource limits are enforced when setting pipe buffer sizes. See *pipe*(7) for details. + +== AUTHORS + +mailto:nwsharp@live.com[Nathan Sharp] + +== SEE ALSO + +*pipe*(7) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/pipesz.c b/misc-utils/pipesz.c new file mode 100644 index 0000000..f586acb --- /dev/null +++ b/misc-utils/pipesz.c @@ -0,0 +1,350 @@ +/* + * pipesz(1) - Set or examine pipe buffer sizes. + * + * Copyright (c) 2022 Nathan Sharp + * Written by Nathan Sharp <nwsharp@live.com> + * + * 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. + * + * 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 <getopt.h> +#include <sys/ioctl.h> /* FIONREAD */ +#include <fcntl.h> /* F_GETPIPE_SZ F_SETPIPE_SZ */ + +#include "c.h" +#include "nls.h" + +#include "closestream.h" /* close_stdout_atexit */ +#include "optutils.h" /* err_exclusive_options */ +#include "path.h" /* ul_path_read_s32 */ +#include "pathnames.h" /* _PATH_PROC_PIPE_MAX_SIZE */ +#include "strutils.h" /* strtos32_or_err strtosize_or_err */ + +static char opt_check = 0; /* --check */ +static char opt_get = 0; /* --get */ +static char opt_quiet = 0; /* --quiet */ +static int opt_size = -1; /* --set <size> */ +static char opt_verbose = 0; /* --verbose */ + +/* fallback file for default size */ +#ifndef PIPESZ_DEFAULT_SIZE_FILE +#define PIPESZ_DEFAULT_SIZE_FILE _PATH_PROC_PIPE_MAX_SIZE +#endif + +/* convenience macros, since pipesz is by default very lenient */ +#define check(FMT...) do { \ + if (opt_check) { \ + err(EXIT_FAILURE, FMT); \ + } else if (!opt_quiet) { \ + warn(FMT); \ + } \ +} while (0) + +#define checkx(FMT...) do { \ + if (opt_check) { \ + errx(EXIT_FAILURE, FMT); \ + } else if (!opt_quiet) { \ + warnx(FMT); \ + } \ +} while (0) + +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); + + fputs(USAGE_SEPARATOR, stdout); + /* TRANSLATORS: 'command' refers to a program argument */ + puts(_("Set or examine pipe buffer sizes and optionally execute command.")); + + fputs(USAGE_OPTIONS, stdout); + puts(_(" -g, --get examine pipe buffers")); + /* TRANSLATORS: '%s' refers to a system file */ + printf( + _(" -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")); + + 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")); + + fputs(USAGE_SEPARATOR, stdout); + printf(USAGE_HELP_OPTIONS(20)); + + printf(USAGE_MAN_TAIL("pipesz(1)")); + + exit(EXIT_SUCCESS); +} + +/* + * performs F_GETPIPE_SZ and FIONREAD + * outputs a table row + */ +static void do_get(int fd, const char *name) +{ + int sz, used; + + sz = fcntl(fd, F_GETPIPE_SZ); + if (sz < 0) { + /* TRANSLATORS: '%s' refers to a file */ + check(_("cannot get pipe buffer size of %s"), name); + return; + } + + if (ioctl(fd, FIONREAD, &used)) + used = 0; + + printf("%s\t%d\t%d\n", name, sz, used); +} + +/* + * performs F_SETPIPE_SZ + */ +static void do_set(int fd, const char *name) +{ + int sz; + + sz = fcntl(fd, F_SETPIPE_SZ, opt_size); + if (sz < 0) + /* TRANSLATORS: '%s' refers to a file */ + check(_("cannot set pipe buffer size of %s"), name); + else if (opt_verbose) + /* TRANSLATORS: '%s' refers to a file, '%d' to a buffer size in bytes */ + warnx(_("%s pipe buffer size set to %d"), name, sz); +} + +/* + * does the requested operation on an fd + */ +static void do_fd(int fd) +{ + char name[sizeof(stringify(INT_MIN)) + 3]; + + sprintf(name, "fd %d", fd); + + if (opt_get) + do_get(fd, name); + else + do_set(fd, name); +} + +/* + * does the requested operation on a file + */ +static void do_file(const char *path) +{ + int fd; + + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + /* TRANSLATORS: '%s' refers to a file */ + check(_("cannot open %s"), path); + return; + } + + if (opt_get) + do_get(fd, path); + else + do_set(fd, path); + + close(fd); +} + +/* + * if necessary, determines a default buffer size and places it in opt_size + * returns FALSE if this could not be done + */ +static char set_size_default(void) +{ + if (opt_size >= 0) + return TRUE; + + if (ul_path_read_s32(NULL, &opt_size, PIPESZ_DEFAULT_SIZE_FILE)) { + /* TRANSLATORS: '%s' refers to a system file */ + check(_("cannot parse %s"), PIPESZ_DEFAULT_SIZE_FILE); + return FALSE; + } + + if (opt_size < 0) { + /* TRANSLATORS: '%s' refers to a system file */ + checkx(_("cannot parse %s"), PIPESZ_DEFAULT_SIZE_FILE); + return FALSE; + } + + return TRUE; +} + +int main(int argc, char **argv) +{ + static const char shortopts[] = "+cef:ghin:oqs:vV"; + static const struct option longopts[] = { + { "check", no_argument, NULL, 'c' }, + { "fd", required_argument, NULL, 'n' }, + { "file", required_argument, NULL, 'f' }, + { "get", no_argument, NULL, 'g' }, + { "help", no_argument, NULL, 'h' }, + { "quiet", no_argument, NULL, 'q' }, + { "set", required_argument, NULL, 's' }, + { "stdin", no_argument, NULL, 'i' }, + { "stdout", no_argument, NULL, 'o' }, + { "stderr", no_argument, NULL, 'e' }, + { "verbose", no_argument, NULL, 'v' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 } + }; + static const ul_excl_t excl[] = { + { 'g', 's' }, + { 0 } + }; + + int c, fd, n_opt_pipe = 0, n_opt_size = 0; + uintmax_t sz; + + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + /* check for --help or --version */ + while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) { + switch (c) { + case 'h': + usage(); + case 'V': + print_version(EXIT_SUCCESS); + } + } + + /* gather normal options */ + optind = 1; + while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) { + err_exclusive_options(c, longopts, excl, excl_st); + + switch (c) { + case 'c': + opt_check = TRUE; + break; + case 'e': + ++n_opt_pipe; + break; + case 'f': + ++n_opt_pipe; + break; + case 'g': + opt_get = TRUE; + break; + case 'i': + ++n_opt_pipe; + break; + case 'n': + fd = strtos32_or_err(optarg, _("invalid fd argument")); + ++n_opt_pipe; + break; + case 'o': + ++n_opt_pipe; + break; + case 'q': + opt_quiet = TRUE; + break; + case 's': + sz = strtosize_or_err(optarg, _("invalid size argument")); + opt_size = sz >= INT_MAX ? INT_MAX : (int)sz; + ++n_opt_size; + break; + case 'v': + opt_verbose = TRUE; + break; + default: + errtryhelp(EXIT_FAILURE); + } + } + + /* check arguments */ + if (opt_get) { + if (argv[optind]) + errx(EXIT_FAILURE, _("cannot specify a command with --get")); + + /* print column headers, if requested */ + if (opt_verbose) + printf("%s\t%s\t%s\n", +/* TRANSLATORS: a column that contains the names of files that are unix pipes */ + _("pipe"), +/* TRANSLATORS: a column that contains buffer sizes in bytes */ + _("size"), +/* TRANSLATORS: a column that contains an amount of data which has not been used by a program */ + _("unread") + ); + + /* special behavior for --get */ + if (!n_opt_pipe) { + do_fd(STDIN_FILENO); + return EXIT_SUCCESS; + } + } else { + if (!set_size_default()) + goto execute_command; + + if (!opt_quiet && n_opt_size > 1) + warnx(_("using last specified size")); + + /* special behavior for --set */ + if (!n_opt_pipe) { + do_fd(STDOUT_FILENO); + goto execute_command; + } + } + + /* go through the arguments again and do the requested operations */ + optind = 1; + while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) + switch (c) { + case 'e': + do_fd(STDERR_FILENO); + break; + case 'f': + do_file(optarg); + break; + case 'i': + do_fd(STDIN_FILENO); + break; + case 'n': + /* optarg was checked before, but it's best to be safe */ + fd = strtos32_or_err(optarg, _("invalid fd argument")); + do_fd(fd); + break; + case 'o': + do_fd(STDOUT_FILENO); + break; + } + +execute_command: + /* exec the command, if it's present */ + if (!argv[optind]) + return EXIT_SUCCESS; + + execvp(argv[optind], &argv[optind]); + errexec(argv[optind]); +} diff --git a/misc-utils/rename.1 b/misc-utils/rename.1 new file mode 100644 index 0000000..073415e --- /dev/null +++ b/misc-utils/rename.1 @@ -0,0 +1,173 @@ +'\" t +.\" Title: rename +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-11-21 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "RENAME" "1" "2023-11-21" "util\-linux 2.39.3" "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" +rename \- rename files +.SH "SYNOPSIS" +.sp +\fBrename\fP [options] \fIexpression replacement file\fP... +.SH "DESCRIPTION" +.sp +\fBrename\fP will rename the specified files by replacing the first occurrence of \fIexpression\fP in their name by \fIreplacement\fP. +.SH "OPTIONS" +.sp +\fB\-s\fP, \fB\-\-symlink\fP +.RS 4 +Do not rename a symlink but change where it points. +.RE +.sp +\fB\-v\fP, \fB\-\-verbose\fP +.RS 4 +Show which files were renamed, if any. +.RE +.sp +\fB\-n\fP, \fB\-\-no\-act\fP +.RS 4 +Do not make any changes; add \fB\-\-verbose\fP to see what would be made. +.RE +.sp +\fB\-a\fP, \fB\-\-all\fP +.RS 4 +Replace all occurrences of \fIexpression\fP rather than only the first one. +.RE +.sp +\fB\-l\fP, \fB\-\-last\fP +.RS 4 +Replace the last occurrence of \fIexpression\fP rather than the first one. +.RE +.sp +\fB\-o\fP, \fB\-\-no\-overwrite\fP +.RS 4 +Do not overwrite existing files. When \fB\-\-symlink\fP is active, do not overwrite symlinks pointing to existing targets. +.RE +.sp +\fB\-i\fP, \fB\-\-interactive\fP +.RS 4 +Ask before overwriting existing files. +.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 "WARNING" +.sp +The renaming has no safeguards by default or without any one of the options \fB\-\-no\-overwrite\fP, \fB\-\-interactive\fP or \fB\-\-no\-act\fP. If the user has permission to rewrite file names, the command will perform the action without any questions. For example, the result can be quite drastic when the command is run as root in the \fI/lib\fP directory. Always make a backup before running the command, unless you truly know what you are doing. +.SH "EDGE CASES" +.sp +If the \fIexpression\fP is empty, then by default \fIreplacement\fP will be added to the start of the filename. With \fB\-\-all\fP, \fIreplacement\fP will be inserted in between every two characters of the filename, as well as at the start and end. +.sp +Normally, only the final path component of a filename is updated. (Or with \fB\-\-symlink\fP, only the final path component of the link.) But if either \fIexpression\fP or \fIreplacement\fP contains a \fI/\fP, the full path is updated. This can cause a file to be moved between folders. Creating folders, and moving files between filesystems, is not supported. +.SH "INTERACTIVE MODE" +.sp +As most standard utilities rename can be used with a terminal device (tty in short) in canonical mode, where the line is buffered by the tty and you press ENTER to validate the user input. If you put your tty in cbreak mode however, rename requires only a single key press to answer the prompt. To set cbreak mode, run for example: +.sp +.if n .RS 4 +.nf +.fam C +sh \-c \*(Aqstty \-icanon min 1; "$0" "$@"; stty icanon\*(Aq rename \-i from to files +.fam +.fi +.if n .RE +.SH "EXIT STATUS" +.sp +\fB0\fP +.RS 4 +all requested rename operations were successful +.RE +.sp +\fB1\fP +.RS 4 +all rename operations failed +.RE +.sp +\fB2\fP +.RS 4 +some rename operations failed +.RE +.sp +\fB4\fP +.RS 4 +nothing was renamed +.RE +.sp +\fB64\fP +.RS 4 +unanticipated error occurred +.RE +.SH "EXAMPLES" +.sp +Given the files \fIfoo1\fP, ..., \fIfoo9\fP, \fIfoo10\fP, ..., \fIfoo278\fP, the commands +.sp +.if n .RS 4 +.nf +.fam C +rename foo foo00 foo? +rename foo foo0 foo?? +.fam +.fi +.if n .RE +.sp +will turn them into \fIfoo001\fP, ..., \fIfoo009\fP, \fIfoo010\fP, ..., \fIfoo278\fP. And +.sp +.if n .RS 4 +.nf +.fam C +rename .htm .html *.htm +.fam +.fi +.if n .RE +.sp +will fix the extension of your html files. Provide an empty string for shortening: +.sp +.if n .RS 4 +.nf +.fam C +rename \*(Aq_with_long_name\*(Aq \*(Aq\*(Aq file_with_long_name.* +.fam +.fi +.if n .RE +.sp +will remove the substring in the filenames. +.SH "SEE ALSO" +.sp +\fBmv\fP(1) +.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 \fBrename\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/rename.1.adoc b/misc-utils/rename.1.adoc new file mode 100644 index 0000000..1f6225f --- /dev/null +++ b/misc-utils/rename.1.adoc @@ -0,0 +1,116 @@ +//po4a: entry man manual +// Written by Andries E. Brouwer (aeb@cwi.nl) +// Placed in the public domain += rename(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: rename + +== NAME + +rename - rename files + +== SYNOPSIS + +*rename* [options] _expression replacement file_... + +== DESCRIPTION + +*rename* will rename the specified files by replacing the first occurrence of _expression_ in their name by _replacement_. + +== OPTIONS + +*-s*, *--symlink*:: +Do not rename a symlink but change where it points. + +*-v*, *--verbose*:: +Show which files were renamed, if any. + +*-n*, *--no-act*:: +Do not make any changes; add *--verbose* to see what would be made. + +*-a*, *--all*:: +Replace all occurrences of _expression_ rather than only the first one. + +*-l*, *--last*:: +Replace the last occurrence of _expression_ rather than the first one. + +*-o*, *--no-overwrite*:: +Do not overwrite existing files. When *--symlink* is active, do not overwrite symlinks pointing to existing targets. + +*-i*, *--interactive*:: +Ask before overwriting existing files. + +include::man-common/help-version.adoc[] + +== WARNING + +The renaming has no safeguards by default or without any one of the options *--no-overwrite*, *--interactive* or *--no-act*. If the user has permission to rewrite file names, the command will perform the action without any questions. For example, the result can be quite drastic when the command is run as root in the _/lib_ directory. Always make a backup before running the command, unless you truly know what you are doing. + +== EDGE CASES + +If the _expression_ is empty, then by default _replacement_ will be added to the start of the filename. With *--all*, _replacement_ will be inserted in between every two characters of the filename, as well as at the start and end. + +Normally, only the final path component of a filename is updated. (Or with *--symlink*, only the final path component of the link.) But if either _expression_ or _replacement_ contains a _/_, the full path is updated. This can cause a file to be moved between folders. Creating folders, and moving files between filesystems, is not supported. + +== INTERACTIVE MODE + +As most standard utilities rename can be used with a terminal device (tty in short) in canonical mode, where the line is buffered by the tty and you press ENTER to validate the user input. If you put your tty in cbreak mode however, rename requires only a single key press to answer the prompt. To set cbreak mode, run for example: + +.... +sh -c 'stty -icanon min 1; "$0" "$@"; stty icanon' rename -i from to files +.... + +== EXIT STATUS + +*0*:: +all requested rename operations were successful + +*1*:: +all rename operations failed + +*2*:: +some rename operations failed + +*4*:: +nothing was renamed + +*64*:: +unanticipated error occurred + +== EXAMPLES + +Given the files _foo1_, ..., _foo9_, _foo10_, ..., _foo278_, the commands + +.... +rename foo foo00 foo? +rename foo foo0 foo?? +.... + +will turn them into _foo001_, ..., _foo009_, _foo010_, ..., _foo278_. And + +.... +rename .htm .html *.htm +.... + +will fix the extension of your html files. Provide an empty string for shortening: + +.... +rename '_with_long_name' '' file_with_long_name.* +.... + +will remove the substring in the filenames. + +== SEE ALSO + +*mv*(1) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/rename.c b/misc-utils/rename.c new file mode 100644 index 0000000..04e61ed --- /dev/null +++ b/misc-utils/rename.c @@ -0,0 +1,381 @@ +/* + * rename.c - aeb 2000-01-01 + * +-------------------------------------------------------------- +#!/bin/sh +if [ $# -le 2 ]; then echo call: rename from to files; exit; fi +FROM="$1" +TO="$2" +shift +shift +for i in $@; do N=`echo "$i" | sed "s/$FROM/$TO/g"`; mv "$i" "$N"; done +-------------------------------------------------------------- + * This shell script will do renames of files, but may fail + * in cases involving special characters. Here a C version. + */ +#include <stdio.h> +#ifdef HAVE_STDIO_EXT_H +# include <stdio_ext.h> +#endif +#ifndef HAVE___FPURGE +# ifdef HAVE_FPURGE +# define HAVE___FPURGE 1 +# define __fpurge fpurge +# endif +#endif +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <getopt.h> +#include <fcntl.h> +#include <unistd.h> +#include <termios.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "nls.h" +#include "xalloc.h" +#include "c.h" +#include "closestream.h" +#include "optutils.h" +#include "rpmatch.h" + +#define RENAME_EXIT_SOMEOK 2 +#define RENAME_EXIT_NOTHING 4 +#define RENAME_EXIT_UNEXPLAINED 64 + +static int tty_cbreak = 0; +static int all = 0; +static int last = 0; + +/* Find the first place in `orig` where we'll perform a replacement. NULL if + there are no replacements to do. */ +static char *find_initial_replace(char *from, char *to, char *orig) +{ + char *search_start = orig; + + if (strchr(from, '/') == NULL && strchr(to, '/') == NULL) { + /* 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++; + } + + return strstr(search_start, from); +} + +static int string_replace(char *from, char *to, char *orig, char **newname) +{ + char *p, *q, *where; + size_t count = 0, fromlen = strlen(from); + + p = where = find_initial_replace(from, to, orig); + if (where == NULL) + return 1; + count++; + while ((all || last) && p && *p) { + p = strstr(p + (last ? 1 : max(fromlen, (size_t) 1)), from); + if (p) { + if (all) + count++; + if (last) + where = p; + } + } + p = orig; + *newname = xmalloc(strlen(orig) - count * fromlen + count * strlen(to) + 1); + q = *newname; + while (count--) { + while (p < where) + *q++ = *p++; + p = to; + while (*p) + *q++ = *p++; + if (fromlen > 0) { + p = where + fromlen; + where = strstr(p, from); + } else { + p = where; + where += 1; + } + } + while (*p) + *q++ = *p++; + *q = 0; + return 0; +} + +static int ask(char *name) +{ + int c; + char buf[2]; + printf(_("%s: overwrite `%s'? "), program_invocation_short_name, name); + fflush(stdout); + if ((c = fgetc(stdin)) == EOF) { + buf[0] = 'n'; + printf("n\n"); + } + else { + buf[0] = c; + if (c != '\n' && tty_cbreak) { +#ifdef HAVE___FPURGE + /* Possibly purge a multi-byte character; or do a + required purge of the rest of the line (including + the newline) if the tty has been put back in + canonical mode (for example by a shell after a + SIGTSTP signal). */ + __fpurge(stdin); +#endif + printf("\n"); + } + else if (c != '\n') + while ((c = fgetc(stdin)) != '\n' && c != EOF); + } + buf[1] = '\0'; + if (rpmatch(buf) == RPMATCH_YES) + return 0; + + return 1; +} + +static int do_symlink(char *from, char *to, char *s, int verbose, int noact, + int nooverwrite, int interactive) +{ + char *newname = NULL, *target = NULL; + int ret = 1; + ssize_t ssz; + struct stat sb; + + if ( faccessat(AT_FDCWD, s, F_OK, AT_SYMLINK_NOFOLLOW) != 0 && + errno != EINVAL ) + /* Skip if AT_SYMLINK_NOFOLLOW is not supported; lstat() below will + detect the access error */ + { + warn(_("%s: not accessible"), s); + return 2; + } + + if (lstat(s, &sb) == -1) { + warn(_("stat of %s failed"), s); + return 2; + } + if (!S_ISLNK(sb.st_mode)) { + warnx(_("%s: not a symbolic link"), s); + return 2; + } + target = xmalloc(sb.st_size + 1); + + ssz = readlink(s, target, sb.st_size + 1); + if (ssz < 0) { + warn(_("%s: readlink failed"), s); + free(target); + return 2; + } + target[ssz] = '\0'; + + if (string_replace(from, to, target, &newname) != 0) + ret = 0; + + if (ret == 1 && (nooverwrite || interactive) && lstat(newname, &sb) != 0) + nooverwrite = interactive = 0; + + if ( ret == 1 && + (nooverwrite || (interactive && (noact || ask(newname) != 0))) ) + { + if (verbose) + printf(_("Skipping existing link: `%s' -> `%s'\n"), s, target); + ret = 0; + } + + if (ret == 1) { + if (!noact && 0 > unlink(s)) { + warn(_("%s: unlink failed"), s); + ret = 2; + } + else if (!noact && symlink(newname, s) != 0) { + warn(_("%s: symlinking to %s failed"), s, newname); + ret = 2; + } + } + if (verbose && (noact || ret == 1)) + printf("%s: `%s' -> `%s'\n", s, target, newname); + free(newname); + free(target); + return ret; +} + +static int do_file(char *from, char *to, char *s, int verbose, int noact, + int nooverwrite, int interactive) +{ + char *newname = NULL; + int ret = 1; + struct stat sb; + + if ( faccessat(AT_FDCWD, s, F_OK, AT_SYMLINK_NOFOLLOW) != 0 && + errno != EINVAL ) + /* Skip if AT_SYMLINK_NOFOLLOW is not supported; lstat() below will + detect the access error */ + { + warn(_("%s: not accessible"), s); + return 2; + } + + if (lstat(s, &sb) == -1) { + warn(_("stat of %s failed"), s); + return 2; + } + if (string_replace(from, to, s, &newname) != 0) + return 0; + + if ((nooverwrite || interactive) && access(newname, F_OK) != 0) + nooverwrite = interactive = 0; + + if (nooverwrite || (interactive && (noact || ask(newname) != 0))) { + if (verbose) + printf(_("Skipping existing file: `%s'\n"), newname); + ret = 0; + } + else if (!noact && rename(s, newname) != 0) { + warn(_("%s: rename to %s failed"), s, newname); + ret = 2; + } + if (verbose && (noact || ret == 1)) + printf("`%s' -> `%s'\n", s, newname); + free(newname); + return ret; +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, + _(" %s [options] <expression> <replacement> <file>...\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Rename files.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -v, --verbose explain what is being done\n"), out); + fputs(_(" -s, --symlink act on the target of symlinks\n"), out); + fputs(_(" -n, --no-act do not make any changes\n"), out); + fputs(_(" -a, --all replace all occurrences\n"), out); + fputs(_(" -l, --last replace only the last occurrence\n"), out); + 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)")); + exit(EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + char *from, *to; + int i, c, ret = 0, verbose = 0, noact = 0, nooverwrite = 0, interactive = 0; + struct termios tio; + int (*do_rename)(char *from, char *to, char *s, int verbose, int noact, + int nooverwrite, int interactive) = do_file; + + static const struct option longopts[] = { + {"verbose", no_argument, NULL, 'v'}, + {"version", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {"all", no_argument, NULL, 'a'}, + {"last", no_argument, NULL, 'l'}, + {"no-act", no_argument, NULL, 'n'}, + {"no-overwrite", no_argument, NULL, 'o'}, + {"interactive", no_argument, NULL, 'i'}, + {"symlink", no_argument, NULL, 's'}, + {NULL, 0, NULL, 0} + }; + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { 'a','l' }, + { 'i','o' }, + { 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, "vsVhnaloi", longopts, NULL)) != -1) { + err_exclusive_options(c, longopts, excl, excl_st); + switch (c) { + case 'n': + noact = 1; + break; + case 'a': + all = 1; + break; + case 'l': + last = 1; + break; + case 'v': + verbose = 1; + break; + case 'o': + nooverwrite = 1; + break; + case 'i': + interactive = 1; + break; + case 's': + do_rename = do_symlink; + break; + + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + argc -= optind; + argv += optind; + + if (argc < 3) { + warnx(_("not enough arguments")); + errtryhelp(EXIT_FAILURE); + } + + from = argv[0]; + to = argv[1]; + + if (!strcmp(from, to)) + return RENAME_EXIT_NOTHING; + + tty_cbreak = 0; + if (interactive && isatty(STDIN_FILENO) != 0) { + if (tcgetattr(STDIN_FILENO, &tio) != 0) + warn(_("failed to get terminal attributes")); + else if (!(tio.c_lflag & ICANON) && tio.c_cc[VMIN] == 1) + tty_cbreak = 1; + } + + for (i = 2; i < argc; i++) + ret |= do_rename(from, to, argv[i], verbose, noact, nooverwrite, interactive); + + switch (ret) { + case 0: + return RENAME_EXIT_NOTHING; + case 1: + return EXIT_SUCCESS; + case 2: + return EXIT_FAILURE; + case 3: + return RENAME_EXIT_SOMEOK; + default: + return RENAME_EXIT_UNEXPLAINED; + } +} diff --git a/misc-utils/test_uuidd.c b/misc-utils/test_uuidd.c new file mode 100644 index 0000000..f012a2c --- /dev/null +++ b/misc-utils/test_uuidd.c @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2006 Hewlett-Packard Development Company, L.P. + * Huschaam Hussain <Huschaam.Hussain@hp.com> + * TSG Solution Alliances Engineering + * SAP Technology Group + * + * Copyright (C) 2015 Karel Zak <kzak@redhat.com> + * + * + * The test heavily uses shared memory, to enlarge maximal size of shared + * segment use: + * + * echo "4294967295" > /proc/sys/kernel/shmm + * + * The test is compiled against in-tree libuuid, if you want to test uuidd + * installed to the system then make sure that libuuid uses the same socket + * like the running uuidd. You can start the uuidd manually, for example: + * + * uuidd --debug --no-fork --no-pid --socket /run/uuidd/request + * + * if the $runstatedir (as defined by build-system) is /run. If you want + * to overwrite the built-in default then use: + * + * make uuidd uuidgen runstatedir=/var/run + */ +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/shm.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "uuid.h" +#include "c.h" +#include "xalloc.h" +#include "strutils.h" +#include "nls.h" + +#define LOG(level,args) if (loglev >= level) { fprintf args; } + +static size_t nprocesses = 4; +static size_t nthreads = 4; +static size_t nobjects = 4096; +static size_t loglev = 1; + +struct processentry { + pid_t pid; + int status; +}; +typedef struct processentry process_t; + +struct threadentry { + process_t *proc; + pthread_t tid; /* pthread_self() / phtread_create() */ + pthread_attr_t thread_attr; + size_t index; /* index in object[] */ + int retval; /* pthread exit() */ +}; +typedef struct threadentry thread_t; + +/* this is in shared memory, keep it as small as possible */ +struct objectentry { + uuid_t uuid; + pthread_t tid; + pid_t pid; + size_t idx; +}; +typedef struct objectentry object_t; + +static int shmem_id; +static object_t *objects; + + +static void __attribute__((__noreturn__)) usage(void) +{ + printf("\n %s [options]\n", program_invocation_short_name); + + printf(" -p <num> number of nprocesses (default:%zu)\n", nprocesses); + printf(" -t <num> number of nthreads (default:%zu)\n", nthreads); + printf(" -o <num> number of nobjects (default:%zu)\n", nobjects); + printf(" -l <level> log level (default:%zu)\n", loglev); + printf(" -h display help\n"); + + exit(EXIT_SUCCESS); +} + +static void allocate_segment(int *id, void **address, size_t number, size_t size) +{ + *id = shmget(IPC_PRIVATE, number * size, IPC_CREAT | 0600); + if (*id == -1) + err(EXIT_FAILURE, "shmget failed to create %zu bytes shared memory", number * size); + + *address = shmat(*id, NULL, 0); + if (*address == (void *)-1) + err(EXIT_FAILURE, "shmat failed"); + + LOG(2, (stderr, + "allocate shared memory segment [id=%d,address=0x%p]\n", + *id, *address)); + + memset(*address, 0, number * size); +} + +static void remove_segment(int id, void *address) +{ + if (shmdt(address) == -1) + err(EXIT_FAILURE, "shmdt failed"); + if (shmctl(id, IPC_RMID, NULL) == -1) + err(EXIT_FAILURE, "shmctl failed"); + LOG(2, + (stderr, + "remove shared memory segment [id=%d,address=0x%p]\n", + id, address)); +} + +static void object_uuid_create(object_t * object) +{ + uuid_generate_time(object->uuid); +} + +static void object_uuid_to_string(object_t * object, char **string_uuid) +{ + uuid_unparse(object->uuid, *string_uuid); +} + +static int object_uuid_compare(const void *object1, const void *object2) +{ + uuid_t *uuid1 = &((object_t *) object1)->uuid, + *uuid2 = &((object_t *) object2)->uuid; + + return uuid_compare(*uuid1, *uuid2); +} + +static void *create_uuids(thread_t *th) +{ + size_t i; + + for (i = th->index; i < th->index + nobjects; i++) { + object_t *obj = &objects[i]; + + object_uuid_create(obj); + obj->tid = th->tid; + obj->pid = th->proc->pid; + obj->idx = th->index + i; + } + return NULL; +} + +static void *thread_body(void *arg) +{ + thread_t *th = (thread_t *) arg; + + return create_uuids(th); +} + +static void create_nthreads(process_t *proc, size_t index) +{ + thread_t *threads; + size_t i, ncreated = 0; + int rc; + + threads = xcalloc(nthreads, sizeof(thread_t)); + + for (i = 0; i < nthreads; i++) { + thread_t *th = &threads[i]; + + rc = pthread_attr_init(&th->thread_attr); + if (rc) { + errno = rc; + warn("%d: pthread_attr_init failed", proc->pid); + break; + } + + th->index = index; + th->proc = proc; + rc = pthread_create(&th->tid, &th->thread_attr, &thread_body, th); + + if (rc) { + errno = rc; + warn("%d: pthread_create failed", proc->pid); + break; + } + + LOG(2, (stderr, "%d: started thread [tid=%jd,index=%zu]\n", + proc->pid, (intmax_t) (intptr_t) th->tid, th->index)); + index += nobjects; + ncreated++; + } + + if (ncreated != nthreads) + fprintf(stderr, "%d: %zu threads not created and ~%zu objects will be ignored\n", + proc->pid, nthreads - ncreated, + (nthreads - ncreated) * nobjects); + + for (i = 0; i < ncreated; i++) { + thread_t *th = &threads[i]; + + rc = pthread_join(th->tid, (void *) &th->retval); + if (rc) { + errno = rc; + err(EXIT_FAILURE, "pthread_join failed"); + } + + LOG(2, (stderr, "%d: thread exited [tid=%jd,return=%d]\n", + proc->pid, (intmax_t) (intptr_t) th->tid, th->retval)); + } + + free(threads); +} + +static void create_nprocesses(void) +{ + process_t *process; + size_t i; + + process = xcalloc(nprocesses, sizeof(process_t)); + + for (i = 0; i < nprocesses; i++) { + process_t *proc = &process[i]; + + proc->pid = fork(); + switch (proc->pid) { + case -1: /* error */ + err(EXIT_FAILURE, "fork failed"); + break; + case 0: /* child */ + proc->pid = getpid(); + create_nthreads(proc, i * nthreads * nobjects); + exit(EXIT_SUCCESS); + break; + default: /* parent */ + LOG(2, (stderr, "started process [pid=%d]\n", proc->pid)); + break; + } + } + + for (i = 0; i < nprocesses; i++) { + process_t *proc = &process[i]; + + if (waitpid(proc->pid, &proc->status, 0) == (pid_t) - 1) + err(EXIT_FAILURE, "waitpid failed"); + LOG(2, + (stderr, "process exited [pid=%d,status=%d]\n", + proc->pid, proc->status)); + } + + free(process); +} + +static void object_dump(size_t idx, object_t *obj) +{ + char uuid_string[UUID_STR_LEN], *p; + + p = uuid_string; + object_uuid_to_string(obj, &p); + + fprintf(stderr, "object[%zu]: {\n", idx); + fprintf(stderr, " uuid: <%s>\n", p); + fprintf(stderr, " idx: %zu\n", obj->idx); + fprintf(stderr, " process: %d\n", (int) obj->pid); + fprintf(stderr, " thread: %jd\n", (intmax_t) (intptr_t) obj->tid); + fprintf(stderr, "}\n"); +} + +#define MSG_TRY_HELP "Try '-h' for help." + +int main(int argc, char *argv[]) +{ + size_t i, nfailed = 0, nignored = 0; + int c; + + while (((c = getopt(argc, argv, "p:t:o:l:h")) != -1)) { + switch (c) { + case 'p': + nprocesses = strtou32_or_err(optarg, "invalid nprocesses number argument"); + break; + case 't': + nthreads = strtou32_or_err(optarg, "invalid nthreads number argument"); + break; + case 'o': + nobjects = strtou32_or_err(optarg, "invalid nobjects number argument"); + break; + case 'l': + loglev = strtou32_or_err(optarg, "invalid log level argument"); + break; + case 'h': + usage(); + break; + default: + fprintf(stderr, MSG_TRY_HELP); + exit(EXIT_FAILURE); + } + } + + if (optind != argc) + errx(EXIT_FAILURE, "bad usage\n" MSG_TRY_HELP); + + if (loglev == 1) + fprintf(stderr, "requested: %zu processes, %zu threads, %zu objects per thread (%zu objects = %zu bytes)\n", + nprocesses, nthreads, nobjects, + nprocesses * nthreads * nobjects, + nprocesses * nthreads * nobjects * sizeof(object_t)); + + allocate_segment(&shmem_id, (void **)&objects, + nprocesses * nthreads * nobjects, sizeof(object_t)); + + create_nprocesses(); + + if (loglev >= 3) { + for (i = 0; i < nprocesses * nthreads * nobjects; i++) + object_dump(i, &objects[i]); + } + + qsort(objects, nprocesses * nthreads * nobjects, sizeof(object_t), + object_uuid_compare); + + for (i = 0; i < nprocesses * nthreads * nobjects - 1; i++) { + object_t *obj1 = &objects[i], + *obj2 = &objects[i + 1]; + + if (!obj1->tid) { + LOG(3, (stderr, "ignore unused object #%zu\n", i)); + nignored++; + continue; + } + + if (object_uuid_compare(obj1, obj2) == 0) { + if (loglev >= 1) + fprintf(stderr, "nobjects #%zu and #%zu have duplicate UUIDs\n", + i, i + 1); + object_dump(i, obj1), + object_dump(i + 1, obj2); + nfailed++; + } + } + + remove_segment(shmem_id, objects); + if (nignored) + printf("%zu objects ignored\n", nignored); + if (!nfailed) + printf("test successful (no duplicate UUIDs found)\n"); + else + printf("test failed (found %zu duplicate UUIDs)\n", nfailed); + + return nfailed ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/misc-utils/uuidd.8 b/misc-utils/uuidd.8 new file mode 100644 index 0000000..0e209de --- /dev/null +++ b/misc-utils/uuidd.8 @@ -0,0 +1,146 @@ +'\" t +.\" Title: uuidd +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-11-21 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "UUIDD" "8" "2023-11-21" "util\-linux 2.39.3" "System Administration" +.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" +uuidd \- UUID generation daemon +.SH "SYNOPSIS" +.sp +\fBuuidd\fP [options] +.SH "DESCRIPTION" +.sp +The \fBuuidd\fP daemon is used by the UUID library to generate universally unique identifiers (UUIDs), especially time\-based UUIDs, in a secure and guaranteed\-unique fashion, even in the face of large numbers of threads running on different CPUs trying to grab UUIDs. +.SH "OPTIONS" +.sp +\fB\-C\fP, \fB\-\-cont\-clock\fP[=\fItime\fP] +.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. +.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. +.RE +.sp +\fB\-d\fP, \fB\-\-debug\fP +.RS 4 +Run \fBuuidd\fP in debugging mode. This prevents \fBuuidd\fP from running as a daemon. +.RE +.sp +\fB\-F\fP, \fB\-\-no\-fork\fP +.RS 4 +Do not daemonize using a double\-fork. +.RE +.sp +\fB\-k\fP, \fB\-\-kill\fP +.RS 4 +If currently a uuidd daemon is running, kill it. +.RE +.sp +\fB\-n\fP, \fB\-\-uuids\fP \fInumber\fP +.RS 4 +When issuing a test request to a running \fBuuidd\fP, request a bulk response of \fInumber\fP UUIDs. +.RE +.sp +\fB\-P\fP, \fB\-\-no\-pid\fP +.RS 4 +Do not create a pid file. +.RE +.sp +\fB\-p\fP, \fB\-\-pid\fP \fIpath\fP +.RS 4 +Specify the pathname where the pid file should be written. By default, the pid file is written to \fI{runstatedir}/uuidd/uuidd.pid\fP. +.RE +.sp +\fB\-q\fP, \fB\-\-quiet\fP +.RS 4 +Suppress some failure messages. +.RE +.sp +\fB\-r\fP, \fB\-\-random\fP +.RS 4 +Test uuidd by trying to connect to a running uuidd daemon and request it to return a random\-based UUID. +.RE +.sp +\fB\-S\fP, \fB\-\-socket\-activation\fP +.RS 4 +Do not create a socket but instead expect it to be provided by the calling process. This implies \fB\-\-no\-fork\fP and \fB\-\-no\-pid\fP. This option is intended to be used only with \fBsystemd\fP(1). It needs to be enabled with a configure option. +.RE +.sp +\fB\-s\fP, \fB\-\-socket\fP \fIpath\fP +.RS 4 +Make uuidd use this pathname for the unix\-domain socket. By default, the pathname used is \fI{runstatedir}/uuidd/request\fP. This option is primarily for debugging purposes, since the pathname is hard\-coded in the \fBlibuuid\fP library. +.RE +.sp +\fB\-T\fP, \fB\-\-timeout\fP \fInumber\fP +.RS 4 +Make \fBuuidd\fP exit after \fInumber\fP seconds of inactivity. +.RE +.sp +\fB\-t\fP, \fB\-\-time\fP +.RS 4 +Test \fBuuidd\fP by trying to connect to a running uuidd daemon and request it to return a time\-based UUID. +.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 "EXAMPLE" +.sp +Start up a daemon, print 42 random keys, and then stop the daemon: +.sp +.if n .RS 4 +.nf +.fam C +uuidd \-p /tmp/uuidd.pid \-s /tmp/uuidd.socket +uuidd \-d \-r \-n 42 \-s /tmp/uuidd.socket +uuidd \-d \-k \-s /tmp/uuidd.socket +.fam +.fi +.if n .RE +.SH "AUTHOR" +.sp +The \fBuuidd\fP daemon was written by \c +.MTO "tytso\(atmit.edu" "Theodore Ts\(cqo" "." +.SH "SEE ALSO" +.sp +\fBuuid\fP(3), +\fBuuidgen\fP(1) +.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 \fBuuidd\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/uuidd.8.adoc b/misc-utils/uuidd.8.adoc new file mode 100644 index 0000000..b0f4850 --- /dev/null +++ b/misc-utils/uuidd.8.adoc @@ -0,0 +1,99 @@ +//po4a: entry man manual +//// +Copyright 2007 by Theodore Ts'o. All Rights Reserved. +This file may be copied under the terms of the GNU Public License. +//// += uuidd(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: uuidd + +== NAME + +uuidd - UUID generation daemon + +== SYNOPSIS + +*uuidd* [options] + +== DESCRIPTION + +The *uuidd* daemon is used by the UUID library to generate universally unique identifiers (UUIDs), especially time-based UUIDs, in a secure and guaranteed-unique fashion, even in the face of large numbers of threads running on different CPUs trying to grab UUIDs. + +== OPTIONS + +*-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<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. + +*-F*, *--no-fork*:: +Do not daemonize using a double-fork. + +*-k*, *--kill*:: +If currently a uuidd daemon is running, kill it. + +*-n*, *--uuids* _number_:: +When issuing a test request to a running *uuidd*, request a bulk response of _number_ UUIDs. + +*-P*, *--no-pid*:: +Do not create a pid file. + +*-p*, *--pid* _path_:: +Specify the pathname where the pid file should be written. By default, the pid file is written to _{runstatedir}/uuidd/uuidd.pid_. +// TRANSLATORS: Don't translate _{runstatedir}_. + +*-q*, *--quiet*:: +Suppress some failure messages. + +*-r*, *--random*:: +Test uuidd by trying to connect to a running uuidd daemon and request it to return a random-based UUID. + +*-S*, *--socket-activation*:: +Do not create a socket but instead expect it to be provided by the calling process. This implies *--no-fork* and *--no-pid*. This option is intended to be used only with *systemd*(1). It needs to be enabled with a configure option. + +*-s*, *--socket* _path_:: +Make uuidd use this pathname for the unix-domain socket. By default, the pathname used is _{runstatedir}/uuidd/request_. This option is primarily for debugging purposes, since the pathname is hard-coded in the *libuuid* library. +// TRANSLATORS: Don't translate _{runstatedir}_. + +*-T*, *--timeout* _number_:: +Make *uuidd* exit after _number_ seconds of inactivity. + +*-t*, *--time*:: +Test *uuidd* by trying to connect to a running uuidd daemon and request it to return a time-based UUID. + +include::man-common/help-version.adoc[] + +== EXAMPLE + +Start up a daemon, print 42 random keys, and then stop the daemon: + +.... +uuidd -p /tmp/uuidd.pid -s /tmp/uuidd.socket +uuidd -d -r -n 42 -s /tmp/uuidd.socket +uuidd -d -k -s /tmp/uuidd.socket +.... + +== AUTHOR + +The *uuidd* daemon was written by mailto:tytso@mit.edu[Theodore Ts'o]. + +== SEE ALSO + +*uuid*(3), +*uuidgen*(1) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/uuidd.c b/misc-utils/uuidd.c new file mode 100644 index 0000000..db8b0c7 --- /dev/null +++ b/misc-utils/uuidd.c @@ -0,0 +1,816 @@ +/* + * uuidd.c --- UUID-generation daemon + * + * Copyright (C) 2007 Theodore Ts'o + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +/* + * The uuidd protocol. + * + * Client: + * | operation type (1 byte) | number of uuids (if bulk request, 4 bytes) | + * + * Server: + * | reply length (4 bytes) | uuid reply (16 bytes) | + * or + * | reply length (4 bytes) | uuid reply (16 bytes) multiply by number when random bulk request | + * or + * | reply length (4 bytes) | uuid reply (16 bytes) | number (4 bytes) time bulk | + * or + * | reply length (4 bytes) | pid or maxop number string length in ascii (up to 7 bytes) | + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> +#include <errno.h> +#include <err.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <fcntl.h> +#include <signal.h> +#include <string.h> +#include <getopt.h> +#include <sys/signalfd.h> +#include <poll.h> + +#include "uuid.h" +#include "uuidd.h" +#include "all-io.h" +#include "c.h" +#include "closestream.h" +#include "strutils.h" +#include "optutils.h" +#include "monotonic.h" +#include "timer.h" + +#ifdef HAVE_LIBSYSTEMD +# include <systemd/sd-daemon.h> +#endif + +#include "nls.h" + +/* Protocol segment lengths */ +typedef uint8_t uuidd_prot_op_t; /* client operation field */ +typedef int32_t uuidd_prot_num_t; /* number of requested uuids */ + +enum { + /* client - server buffer size */ + UUIDD_PROT_BUFSZ = ((sizeof(uuidd_prot_num_t)) + (sizeof(uuid_t) * 63)) +}; + +/* server loop control structure */ +struct uuidd_cxt_t { + const char *cleanup_pidfile; + const char *cleanup_socket; + uint32_t timeout; + uint32_t cont_clock_offset; + + unsigned int debug: 1, + quiet: 1, + no_fork: 1, + no_sock: 1; +}; + +struct uuidd_options_t { + const char *pidfile_path; + const char *socket_path; + uuidd_prot_num_t num; + uuidd_prot_op_t do_type; + unsigned int do_kill:1, + no_pid:1, + s_flag:1; +}; + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options]\n"), program_invocation_short_name); + fputs(USAGE_SEPARATOR, out); + fputs(_("A daemon for generating UUIDs.\n"), out); + fputs(USAGE_OPTIONS, out); + fputs(_(" -p, --pid <path> path to pid file\n"), out); + fputs(_(" -s, --socket <path> path to socket\n"), out); + fputs(_(" -T, --timeout <sec> specify inactivity timeout\n"), out); + fputs(_(" -k, --kill kill running daemon\n"), out); + fputs(_(" -r, --random test random-based generation\n"), out); + fputs(_(" -t, --time test time-based generation\n"), out); + fputs(_(" -n, --uuids <num> request number of uuids\n"), out); + fputs(_(" -P, --no-pid do not create pid file\n"), out); + fputs(_(" -F, --no-fork do not daemonize using double-fork\n"), out); + fputs(_(" -S, --socket-activation do not create listening socket\n"), out); + fputs(_(" -C, --cont-clock[=<NUM>[hd]]\n"), out); + fputs(_(" activate continuous clock handling\n"), out); + 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)")); + exit(EXIT_SUCCESS); +} + +static void create_daemon(void) +{ + uid_t euid; + + if (daemon(0, 0)) + err(EXIT_FAILURE, "daemon"); + + euid = geteuid(); + if (setreuid(euid, euid) < 0) + err(EXIT_FAILURE, "setreuid"); +} + +static int call_daemon(const char *socket_path, uuidd_prot_op_t op, char *buf, + size_t buflen, uuidd_prot_num_t *num, const char **err_context) +{ + char op_buf[sizeof(op) + sizeof(*num)]; + size_t op_len; + int s; + ssize_t ret; + int32_t reply_len = 0; + struct sockaddr_un srv_addr; + + if (((op == UUIDD_OP_BULK_TIME_UUID) || + (op == UUIDD_OP_BULK_RANDOM_UUID)) && !num) { + if (err_context) + *err_context = _("bad arguments"); + errno = EINVAL; + return -1; + } + + if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + if (err_context) + *err_context = _("socket"); + return -1; + } + + srv_addr.sun_family = AF_UNIX; + assert(strlen(socket_path) < sizeof(srv_addr.sun_path)); + xstrncpy(srv_addr.sun_path, socket_path, sizeof(srv_addr.sun_path)); + + if (connect(s, (const struct sockaddr *) &srv_addr, + sizeof(struct sockaddr_un)) < 0) { + if (err_context) + *err_context = _("connect"); + close(s); + return -1; + } + + if (op == UUIDD_OP_BULK_RANDOM_UUID) { + if ((buflen - sizeof(*num)) < (size_t)((*num) * sizeof(uuid_t))) + *num = (buflen - sizeof(*num)) / sizeof(uuid_t); + } + op_buf[0] = op; + op_len = sizeof(op); + if ((op == UUIDD_OP_BULK_TIME_UUID) || + (op == UUIDD_OP_BULK_RANDOM_UUID)) { + memcpy(op_buf + sizeof(op), num, sizeof(*num)); + op_len += sizeof(*num); + } + + ret = write_all(s, op_buf, op_len); + if (ret < 0) { + if (err_context) + *err_context = _("write"); + close(s); + return -1; + } + + ret = read_all(s, (char *) &reply_len, sizeof(reply_len)); + if (ret < 0) { + if (err_context) + *err_context = _("read count"); + close(s); + return -1; + } + if (reply_len < 0 || (size_t) reply_len > buflen) { + if (err_context) + *err_context = _("bad response length"); + close(s); + return -1; + } + ret = read_all(s, (char *) buf, reply_len); + + if ((ret > 0) && (op == UUIDD_OP_BULK_TIME_UUID)) { + if ((sizeof(uuid_t) + sizeof(*num)) <= (size_t) reply_len) + memcpy(buf + sizeof(uuid_t), num, sizeof(*num)); + else + *num = -1; + } + if ((ret > 0) && (op == UUIDD_OP_BULK_RANDOM_UUID)) { + if (sizeof(*num) <= (size_t) reply_len) + memcpy(buf, num, sizeof(*num)); + else + *num = -1; + } + + close(s); + + return ret; +} + +/* + * Exclusively create and open a pid file with path @pidfile_path + * + * Return file descriptor of the created pid_file. + */ +static int create_pidfile(struct uuidd_cxt_t *cxt, const char *pidfile_path) +{ + int fd_pidfile; + struct flock fl; + + fd_pidfile = open(pidfile_path, O_CREAT | O_RDWR, 0664); + if (fd_pidfile < 0) { + if (!cxt->quiet) + warn(_("cannot open %s"), pidfile_path); + exit(EXIT_FAILURE); + } + cxt->cleanup_pidfile = pidfile_path; + + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + fl.l_pid = 0; + while (fcntl(fd_pidfile, F_SETLKW, &fl) < 0) { + if ((errno == EAGAIN) || (errno == EINTR)) + continue; + if (!cxt->quiet) + warn(_("cannot lock %s"), pidfile_path); + exit(EXIT_FAILURE); + } + + return fd_pidfile; +} + +/* + * Create AF_UNIX, SOCK_STREAM socket and bind to @socket_path + * + * If @will_fork is true, then make sure the descriptor + * of the socket is >2, so that it won't be later closed + * during create_daemon(). + * + * Return file descriptor corresponding to created socket. + */ +static int create_socket(struct uuidd_cxt_t *uuidd_cxt, + const char *socket_path, int will_fork) +{ + struct sockaddr_un my_addr; + mode_t save_umask; + int s; + + if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + if (!uuidd_cxt->quiet) + warn(_("couldn't create unix stream socket")); + exit(EXIT_FAILURE); + } + + /* + * Make sure the socket isn't using fd numbers 0-2 to avoid it + * getting closed by create_daemon() + */ + while (will_fork && s <= 2) { + s = dup(s); + if (s < 0) + err(EXIT_FAILURE, "dup"); + } + + /* + * Create the address we will be binding to. + */ + my_addr.sun_family = AF_UNIX; + assert(strlen(socket_path) < sizeof(my_addr.sun_path)); + xstrncpy(my_addr.sun_path, socket_path, sizeof(my_addr.sun_path)); + unlink(socket_path); + save_umask = umask(0); + if (bind(s, (const struct sockaddr *) &my_addr, + sizeof(struct sockaddr_un)) < 0) { + if (!uuidd_cxt->quiet) + warn(_("couldn't bind unix socket %s"), socket_path); + exit(EXIT_FAILURE); + } + umask(save_umask); + uuidd_cxt->cleanup_socket = socket_path; + + return s; +} + +static void __attribute__((__noreturn__)) all_done(const struct uuidd_cxt_t *uuidd_cxt, int ret) +{ + if (uuidd_cxt->cleanup_pidfile) + unlink(uuidd_cxt->cleanup_pidfile); + if (uuidd_cxt->cleanup_socket) + unlink(uuidd_cxt->cleanup_socket); + exit(ret); +} + +static void handle_signal(const struct uuidd_cxt_t *uuidd_cxt, int fd) +{ + struct signalfd_siginfo info; + ssize_t bytes; + + bytes = read(fd, &info, sizeof(info)); + if (bytes != sizeof(info)) { + if (errno == EAGAIN) + return; + warn(_("receiving signal failed")); + info.ssi_signo = 0; + } + if (info.ssi_signo == SIGPIPE) + return; /* ignored */ + all_done(uuidd_cxt, EXIT_SUCCESS); +} + +static void timeout_handler(int sig __attribute__((__unused__)), + siginfo_t * info, + void *context __attribute__((__unused__))) +{ +#ifdef HAVE_TIMER_CREATE + if (info->si_code == SI_TIMER) +#endif + errx(EXIT_FAILURE, _("timed out")); +} + +static void server_loop(const char *socket_path, const char *pidfile_path, + struct uuidd_cxt_t *uuidd_cxt) +{ + struct sockaddr_un from_addr; + socklen_t fromlen; + int32_t reply_len = 0; + uuid_t uu; + char reply_buf[UUIDD_PROT_BUFSZ], *cp; + uuidd_prot_op_t op; + char str[UUID_STR_LEN]; + int i, ns, len; + uuidd_prot_num_t num; /* intentionally uninitialized */ + int s = 0; + int fd_pidfile = -1; + int ret; + struct pollfd pfd[2]; + sigset_t sigmask; + int sigfd; + enum { + POLLFD_SIGNAL = 0, + POLLFD_SOCKET + }; + +#ifdef HAVE_LIBSYSTEMD + if (!uuidd_cxt->no_sock) /* no_sock implies no_fork and no_pid */ +#endif + { + struct ul_timer timer; + struct itimerval timeout; + + memset(&timeout, 0, sizeof timeout); + timeout.it_value.tv_sec = 30; + if (setup_timer(&timer, &timeout, &timeout_handler)) + err(EXIT_FAILURE, _("cannot set up timer")); + if (pidfile_path) + fd_pidfile = create_pidfile(uuidd_cxt, pidfile_path); + ret = call_daemon(socket_path, UUIDD_OP_GETPID, reply_buf, + sizeof(reply_buf), 0, NULL); + cancel_timer(&timer); + if (ret > 0) { + if (!uuidd_cxt->quiet) + warnx(_("uuidd daemon is already running at pid %s"), + reply_buf); + exit(EXIT_FAILURE); + } + + s = create_socket(uuidd_cxt, socket_path, + (!uuidd_cxt->debug || !uuidd_cxt->no_fork)); + if (listen(s, SOMAXCONN) < 0) { + if (!uuidd_cxt->quiet) + warn(_("couldn't listen on unix socket %s"), socket_path); + exit(EXIT_FAILURE); + } + + if (!uuidd_cxt->debug && !uuidd_cxt->no_fork) + create_daemon(); + + if (pidfile_path) { + snprintf(reply_buf, sizeof(reply_buf), "%8d\n", getpid()); + if (ftruncate(fd_pidfile, 0)) + err(EXIT_FAILURE, _("could not truncate file: %s"), pidfile_path); + write_all(fd_pidfile, reply_buf, strlen(reply_buf)); + if (fd_pidfile > 1 && close_fd(fd_pidfile) != 0) + err(EXIT_FAILURE, _("write failed: %s"), pidfile_path); + } + + } + +#ifdef HAVE_LIBSYSTEMD + if (uuidd_cxt->no_sock) { + const int r = sd_listen_fds(0); + + if (r < 0) { + errno = r * -1; + err(EXIT_FAILURE, _("sd_listen_fds() failed")); + } else if (r == 0) + errx(EXIT_FAILURE, + _("no file descriptors received, check systemctl status uuidd.socket")); + else if (1 < r) + errx(EXIT_FAILURE, + _("too many file descriptors received, check uuidd.socket")); + s = SD_LISTEN_FDS_START + 0; + } +#endif + + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGHUP); + sigaddset(&sigmask, SIGINT); + sigaddset(&sigmask, SIGTERM); + sigaddset(&sigmask, SIGALRM); + sigaddset(&sigmask, SIGPIPE); + /* Block signals so that they aren't handled according to their + * default dispositions */ + sigprocmask(SIG_BLOCK, &sigmask, NULL); + if ((sigfd = signalfd(-1, &sigmask, 0)) < 0) + err(EXIT_FAILURE, _("cannot set signal handler")); + + pfd[POLLFD_SIGNAL].fd = sigfd; + 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 ? + (int) uuidd_cxt->timeout * 1000 : -1); + if (ret < 0) { + if (errno == EAGAIN) + continue; + warn(_("poll failed")); + all_done(uuidd_cxt, EXIT_FAILURE); + } + if (ret == 0) { /* true when poll() times out */ + if (uuidd_cxt->debug) + fprintf(stderr, _("timeout [%d sec]\n"), uuidd_cxt->timeout); + all_done(uuidd_cxt, EXIT_SUCCESS); + } + if (pfd[POLLFD_SIGNAL].revents != 0) + handle_signal(uuidd_cxt, sigfd); + if (pfd[POLLFD_SOCKET].revents == 0) + continue; + fromlen = sizeof(from_addr); + ns = accept(s, (struct sockaddr *) &from_addr, &fromlen); + if (ns < 0) { + if ((errno == EAGAIN) || (errno == EINTR)) + continue; + err(EXIT_FAILURE, "accept"); + } + len = read(ns, &op, sizeof(op)); + if (len != sizeof(op)) { + if (len < 0) + warn(_("read failed")); + else + warnx(_("error reading from client, len = %d"), + len); + goto shutdown_socket; + } + if ((op == UUIDD_OP_BULK_TIME_UUID) || + (op == UUIDD_OP_BULK_RANDOM_UUID)) { + if (read_all(ns, (char *) &num, sizeof(num)) != sizeof(num)) + goto shutdown_socket; + if (uuidd_cxt->debug) + fprintf(stderr, _("operation %d, incoming num = %d\n"), + op, num); + } else if (uuidd_cxt->debug) + fprintf(stderr, _("operation %d\n"), op); + + switch (op) { + case UUIDD_OP_GETPID: + snprintf(reply_buf, sizeof(reply_buf), "%d", getpid()); + reply_len = strlen(reply_buf) + 1; + break; + case UUIDD_OP_GET_MAXOP: + snprintf(reply_buf, sizeof(reply_buf), "%d", UUIDD_MAX_OP); + reply_len = strlen(reply_buf) + 1; + break; + case UUIDD_OP_TIME_UUID: + num = 1; + ret = __uuid_generate_time_cont(uu, &num, uuidd_cxt->cont_clock_offset); + if (ret < 0 && !uuidd_cxt->quiet) + warnx(_("failed to open/lock clock counter")); + if (uuidd_cxt->debug) { + uuid_unparse(uu, str); + fprintf(stderr, _("Generated time UUID: %s\n"), str); + } + memcpy(reply_buf, uu, sizeof(uu)); + reply_len = sizeof(uu); + break; + case UUIDD_OP_RANDOM_UUID: + num = 1; + __uuid_generate_random(uu, &num); + if (uuidd_cxt->debug) { + uuid_unparse(uu, str); + fprintf(stderr, _("Generated random UUID: %s\n"), str); + } + memcpy(reply_buf, uu, sizeof(uu)); + reply_len = sizeof(uu); + break; + case UUIDD_OP_BULK_TIME_UUID: + ret = __uuid_generate_time_cont(uu, &num, uuidd_cxt->cont_clock_offset); + if (ret < 0 && !uuidd_cxt->quiet) + warnx(_("failed to open/lock clock counter")); + if (uuidd_cxt->debug) { + uuid_unparse(uu, str); + fprintf(stderr, P_("Generated time UUID %s " + "and %d following\n", + "Generated time UUID %s " + "and %d following\n", num - 1), + str, num - 1); + } + memcpy(reply_buf, uu, sizeof(uu)); + reply_len = sizeof(uu); + memcpy(reply_buf + reply_len, &num, sizeof(num)); + reply_len += sizeof(num); + break; + case UUIDD_OP_BULK_RANDOM_UUID: + if (num < 0) + num = 1; + if ((sizeof(reply_buf) - sizeof(num)) < (size_t) (sizeof(uu) * num)) + num = (sizeof(reply_buf) - sizeof(num)) / sizeof(uu); + __uuid_generate_random((unsigned char *) reply_buf + + sizeof(num), &num); + reply_len = sizeof(num) + (sizeof(uu) * num); + memcpy(reply_buf, &num, sizeof(num)); + if (uuidd_cxt->debug) { + fprintf(stderr, P_("Generated %d UUID:\n", + "Generated %d UUIDs:\n", num), num); + cp = reply_buf + sizeof(num); + for (i = 0; i < num; i++) { + uuid_unparse((unsigned char *)cp, str); + fprintf(stderr, "\t%s\n", str); + cp += sizeof(uu); + } + } + break; + default: + if (uuidd_cxt->debug) + fprintf(stderr, _("Invalid operation %d\n"), op); + goto shutdown_socket; + } + write_all(ns, (char *) &reply_len, sizeof(num)); + write_all(ns, reply_buf, reply_len); + shutdown_socket: + close(ns); + } +} + +static void __attribute__ ((__noreturn__)) unexpected_size(int size) +{ + errx(EXIT_FAILURE, _("Unexpected reply length from server %d"), size); +} + +static uint32_t parse_cont_clock(char *arg) +{ + uint32_t min_val = 60, + max_val = (3600 * 24 * 365), + factor = 1; + char *p = &arg[strlen(arg)-1]; + + if ('h' == *p) { + *p = '\0'; + factor = 3600; + min_val = 1; + } + if ('d' == *p) { + *p = '\0'; + factor = 24 * 3600; + min_val = 1; + } + return factor * str2num_or_err(optarg, 10, _("failed to parse --cont-clock/-C"), + min_val, max_val / factor); +} + +static void parse_options(int argc, char **argv, struct uuidd_cxt_t *uuidd_cxt, + struct uuidd_options_t *uuidd_opts) +{ + const struct option longopts[] = { + {"pid", required_argument, NULL, 'p'}, + {"socket", required_argument, NULL, 's'}, + {"timeout", required_argument, NULL, 'T'}, + {"kill", no_argument, NULL, 'k'}, + {"random", no_argument, NULL, 'r'}, + {"time", no_argument, NULL, 't'}, + {"uuids", required_argument, NULL, 'n'}, + {"no-pid", no_argument, NULL, 'P'}, + {"no-fork", no_argument, NULL, 'F'}, + {"socket-activation", no_argument, NULL, 'S'}, + {"cont-clock", optional_argument, NULL, 'C'}, + {"debug", no_argument, NULL, 'd'}, + {"quiet", no_argument, NULL, 'q'}, + {"version", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + const ul_excl_t excl[] = { + { 'P', 'p' }, + { 'd', 'q' }, + { 'r', 't' }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + int c; + + while ((c = getopt_long(argc, argv, "p:s:T:krtn:PFSC::dqVh", longopts, NULL)) != -1) { + err_exclusive_options(c, longopts, excl, excl_st); + switch (c) { + case 'C': + if (optarg != NULL) + uuidd_cxt->cont_clock_offset = parse_cont_clock(optarg); + else + uuidd_cxt->cont_clock_offset = 7200; /* default 2h */ + break; + case 'd': + uuidd_cxt->debug = 1; + break; + case 'k': + uuidd_opts->do_kill = 1; + break; + case 'n': + uuidd_opts->num = (uuidd_prot_num_t) strtou16_or_err(optarg, + _("failed to parse --uuids")); + break; + case 'p': + uuidd_opts->pidfile_path = optarg; + break; + case 'P': + uuidd_opts->no_pid = 1; + break; + case 'F': + uuidd_cxt->no_fork = 1; + break; + case 'S': +#ifdef HAVE_LIBSYSTEMD + uuidd_cxt->no_sock = 1; + uuidd_cxt->no_fork = 1; + uuidd_opts->no_pid = 1; +#else + errx(EXIT_FAILURE, _("uuidd has been built without " + "support for socket activation")); +#endif + break; + case 'q': + uuidd_cxt->quiet = 1; + break; + case 'r': + uuidd_opts->do_type = UUIDD_OP_RANDOM_UUID; + break; + case 's': + uuidd_opts->socket_path = optarg; + uuidd_opts->s_flag = 1; + break; + case 't': + uuidd_opts->do_type = UUIDD_OP_TIME_UUID; + break; + case 'T': + uuidd_cxt->timeout = strtou32_or_err(optarg, + _("failed to parse --timeout")); + break; + + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (0 < uuidd_opts->num) { + switch (uuidd_opts->do_type) { + case UUIDD_OP_RANDOM_UUID: + uuidd_opts->do_type = UUIDD_OP_BULK_RANDOM_UUID; + break; + case UUIDD_OP_TIME_UUID: + uuidd_opts->do_type = UUIDD_OP_BULK_TIME_UUID; + break; + } + } +} + +int main(int argc, char **argv) +{ + const char *err_context = NULL; + char *cp; + int ret; + + struct uuidd_cxt_t uuidd_cxt = { .timeout = 0, .cont_clock_offset = 0 }; + struct uuidd_options_t uuidd_opts = { .socket_path = UUIDD_SOCKET_PATH }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + parse_options(argc, argv, &uuidd_cxt, &uuidd_opts); + + if (strlen(uuidd_opts.socket_path) >= sizeof_member(struct sockaddr_un, sun_path)) + errx(EXIT_FAILURE, _("socket name too long: %s"), uuidd_opts.socket_path); + + if (!uuidd_opts.no_pid && !uuidd_opts.pidfile_path) + uuidd_opts.pidfile_path = UUIDD_PIDFILE_PATH; + + /* custom socket path and socket-activation make no sense */ + if (uuidd_opts.s_flag && uuidd_cxt.no_sock && !uuidd_cxt.quiet) + warnx(_("Both --socket-activation and --socket specified. " + "Ignoring --socket.")); + + if (uuidd_opts.num && uuidd_opts.do_type) { + char buf[UUIDD_PROT_BUFSZ]; + char str[UUID_STR_LEN]; + + ret = call_daemon(uuidd_opts.socket_path, uuidd_opts.do_type, buf, + sizeof(buf), &uuidd_opts.num, &err_context); + + if (ret < 0) + err(EXIT_FAILURE, _("error calling uuidd daemon (%s)"), + err_context ? : _("unexpected error")); + + if (uuidd_opts.do_type == UUIDD_OP_BULK_TIME_UUID) { + if (ret != sizeof(uuid_t) + sizeof(uuidd_opts.num)) + unexpected_size(ret); + + uuid_unparse((unsigned char *) buf, str); + + printf(P_("%s and %d subsequent UUID\n", + "%s and %d subsequent UUIDs\n", uuidd_opts.num - 1), + str, uuidd_opts.num - 1); + } else { + int i; + + printf(_("List of UUIDs:\n")); + cp = buf + sizeof(uuidd_opts.num); + if (ret != (int) (sizeof(uuidd_opts.num) + uuidd_opts.num * sizeof(uuid_t))) + unexpected_size(ret); + for (i = 0; i < uuidd_opts.num; i++, cp += sizeof(uuid_t)) { + uuid_unparse((unsigned char *) cp, str); + printf("\t%s\n", str); + } + } + return EXIT_SUCCESS; + } + + if (uuidd_opts.do_type) { + uuid_t uu; + char str[UUID_STR_LEN]; + + ret = call_daemon(uuidd_opts.socket_path, uuidd_opts.do_type, (char *) &uu, + sizeof(uu), 0, &err_context); + + if (ret < 0) + err(EXIT_FAILURE, _("error calling uuidd daemon (%s)"), + err_context ? : _("unexpected error")); + if (ret != sizeof(uu)) + unexpected_size(ret); + + uuid_unparse(uu, str); + + printf("%s\n", str); + return EXIT_SUCCESS; + } + + if (uuidd_opts.do_kill) { + char buf[16]; + + ret = call_daemon(uuidd_opts.socket_path, UUIDD_OP_GETPID, buf, sizeof(buf), 0, NULL); + if (0 < ret) { + pid_t pid; + + pid = (pid_t)strtou32_or_err(buf, _("failed to parse pid")); + ret = kill(pid, SIGTERM); + if (ret < 0) { + if (!uuidd_cxt.quiet) + warn(_("couldn't kill uuidd running " + "at pid %d"), pid); + return EXIT_FAILURE; + } + if (!uuidd_cxt.quiet) + printf(_("Killed uuidd running at pid %d.\n"), pid); + } + return EXIT_SUCCESS; + } + + server_loop(uuidd_opts.socket_path, uuidd_opts.pidfile_path, &uuidd_cxt); + return EXIT_SUCCESS; +} diff --git a/misc-utils/uuidd.rc.in b/misc-utils/uuidd.rc.in new file mode 100644 index 0000000..7943945 --- /dev/null +++ b/misc-utils/uuidd.rc.in @@ -0,0 +1,62 @@ +#! /bin/sh -e +### BEGIN INIT INFO +# Provides: uuidd +# Required-Start: $time $local_fs $remote_fs +# Required-Stop: $time $local_fs $remote_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: uuidd daemon +# Description: Init script for the uuid generation daemon +### END INIT INFO +# +# Author: "Theodore Ts'o" <tytso@mit.edu> +# +set -e + +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 + +test -x $DAEMON || exit 0 + +. /lib/lsb/init-functions + +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 + fi + start_daemon -p $PIDFILE $DAEMON + log_end_msg $? + ;; + stop) + log_daemon_msg "Stopping uuid generator" "uuidd" + killproc -p $PIDFILE $DAEMON + log_end_msg $? + ;; + status) + if pidofproc -p $PIDFILE $DAEMON >/dev/null 2>&1; then + echo "$DAEMON is running"; + exit 0; + else + echo "$DAEMON is NOT running"; + if test -f $PIDFILE; then exit 2; fi + exit 3; + fi + ;; + force-reload|restart) + $0 stop + $0 start + ;; + *) + echo "Usage: /etc/init.d/uuidd {start|stop|restart|force-reload}" + exit 1 + ;; +esac + +exit 0 diff --git a/misc-utils/uuidd.service.in b/misc-utils/uuidd.service.in new file mode 100644 index 0000000..4ad6d97 --- /dev/null +++ b/misc-utils/uuidd.service.in @@ -0,0 +1,23 @@ +[Unit] +Description=Daemon for generating UUIDs +Documentation=man:uuidd(8) +Requires=uuidd.socket + +[Service] +ExecStart=@usrsbin_execdir@/uuidd --socket-activation +Restart=no +User=uuidd +Group=uuidd +ProtectSystem=strict +ProtectHome=yes +PrivateDevices=yes +PrivateUsers=yes +ProtectKernelTunables=yes +ProtectKernelModules=yes +ProtectControlGroups=yes +MemoryDenyWriteExecute=yes +ReadWritePaths=/var/lib/libuuid/ +SystemCallFilter=@default @file-system @basic-io @system-service @signal @io-event @network-io + +[Install] +Also=uuidd.socket diff --git a/misc-utils/uuidd.socket.in b/misc-utils/uuidd.socket.in new file mode 100644 index 0000000..8ddcac2 --- /dev/null +++ b/misc-utils/uuidd.socket.in @@ -0,0 +1,8 @@ +[Unit] +Description=UUID daemon activation socket + +[Socket] +ListenStream=@runstatedir@/uuidd/request + +[Install] +WantedBy=sockets.target diff --git a/misc-utils/uuidgen.1 b/misc-utils/uuidgen.1 new file mode 100644 index 0000000..da396f7 --- /dev/null +++ b/misc-utils/uuidgen.1 @@ -0,0 +1,109 @@ +'\" t +.\" Title: uuidgen +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-12-01 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "UUIDGEN" "1" "2023-12-01" "util\-linux 2.39.3" "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" +uuidgen \- create a new UUID value +.SH "SYNOPSIS" +.sp +\fBuuidgen\fP [options] +.SH "DESCRIPTION" +.sp +The \fBuuidgen\fP program creates (and prints) a new universally unique identifier (UUID) using the \fBlibuuid\fP(3) library. The new UUID can reasonably be considered unique among all UUIDs created on the local system, and among UUIDs created on other systems in the past and in the future. +.sp +There are three types of UUIDs which \fBuuidgen\fP can generate: time\-based UUIDs, random\-based UUIDs, and hash\-based UUIDs. By default \fBuuidgen\fP will generate a random\-based UUID if a high\-quality random number generator is present. Otherwise, it will choose a time\-based UUID. It is possible to force the generation of one of these first two UUID types by using the \fB\-\-random\fP or \fB\-\-time\fP options. +.sp +The third type of UUID is generated with the \fB\-\-md5\fP or \fB\-\-sha1\fP options, followed by \fB\-\-namespace\fP \fInamespace\fP and \fB\-\-name\fP \fIname\fP. The \fInamespace\fP may either be a well\-known UUID, or else an alias to one of the well\-known UUIDs defined in RFC 4122, that is \fB@dns\fP, \fB@url\fP, \fB@oid\fP, or \fB@x500\fP. The \fIname\fP is an arbitrary string value. The generated UUID is the digest of the concatenation of the namespace UUID and the name value, hashed with the MD5 or SHA1 algorithms. It is, therefore, a predictable value which may be useful when UUIDs are being used as handles or nonces for more complex values or values which shouldn\(cqt be disclosed directly. See the RFC for more information. +.SH "OPTIONS" +.sp +\fB\-r\fP, \fB\-\-random\fP +.RS 4 +Generate a random\-based UUID. This method creates a UUID consisting mostly of random bits. It requires that the operating system has a high quality random number generator, such as \fI/dev/random\fP. +.RE +.sp +\fB\-t\fP, \fB\-\-time\fP +.RS 4 +Generate a time\-based UUID. This method creates a UUID based on the system clock plus the system\(cqs ethernet hardware address, if present. +.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 +.sp +\fB\-m\fP, \fB\-\-md5\fP +.RS 4 +Use MD5 as the hash algorithm. +.RE +.sp +\fB\-s\fP, \fB\-\-sha1\fP +.RS 4 +Use SHA1 as the hash algorithm. +.RE +.sp +\fB\-n\fP, \fB\-\-namespace\fP \fInamespace\fP +.RS 4 +Generate the hash with the \fInamespace\fP prefix. The \fInamespace\fP is UUID, or \*(Aq@ns\*(Aq where "ns" is well\-known predefined UUID addressed by namespace name (see above). +.RE +.sp +\fB\-N\fP, \fB\-\-name\fP \fIname\fP +.RS 4 +Generate the hash of the \fIname\fP. +.RE +.sp +\fB\-x\fP, \fB\-\-hex\fP +.RS 4 +Interpret name \fIname\fP as a hexadecimal string. +.RE +.SH "CONFORMING TO" +.sp +OSF DCE 1.1 +.SH "EXAMPLES" +.sp +uuidgen \-\-sha1 \-\-namespace @dns \-\-name "www.example.com" +.SH "AUTHORS" +.sp +\fBuuidgen\fP was written by Andreas Dilger for \fBlibuuid\fP(3). +.SH "SEE ALSO" +.sp +\fBuuidparse\fP(1), +\fBlibuuid\fP(3), +.URL "https://tools.ietf.org/html/rfc4122" "RFC 4122" "" +.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 \fBuuidgen\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/uuidgen.1.adoc b/misc-utils/uuidgen.1.adoc new file mode 100644 index 0000000..3f71aa9 --- /dev/null +++ b/misc-utils/uuidgen.1.adoc @@ -0,0 +1,78 @@ +//po4a: entry man manual +//// +Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca) +This file may be copied under the terms of the GNU Public License. +//// += uuidgen(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: uuidgen + +== NAME + +uuidgen - create a new UUID value + +== SYNOPSIS + +*uuidgen* [options] + +== DESCRIPTION + +The *uuidgen* program creates (and prints) a new universally unique identifier (UUID) using the *libuuid*(3) library. The new UUID can reasonably be considered unique among all UUIDs created on the local system, and among UUIDs created on other systems in the past and in the future. + +There are three types of UUIDs which *uuidgen* can generate: time-based UUIDs, random-based UUIDs, and hash-based UUIDs. By default *uuidgen* will generate a random-based UUID if a high-quality random number generator is present. Otherwise, it will choose a time-based UUID. It is possible to force the generation of one of these first two UUID types by using the *--random* or *--time* options. + +The third type of UUID is generated with the *--md5* or *--sha1* options, followed by *--namespace* _namespace_ and *--name* _name_. The _namespace_ may either be a well-known UUID, or else an alias to one of the well-known UUIDs defined in RFC 4122, that is *@dns*, *@url*, *@oid*, or *@x500*. The _name_ is an arbitrary string value. The generated UUID is the digest of the concatenation of the namespace UUID and the name value, hashed with the MD5 or SHA1 algorithms. It is, therefore, a predictable value which may be useful when UUIDs are being used as handles or nonces for more complex values or values which shouldn't be disclosed directly. See the RFC for more information. + +== OPTIONS + +*-r*, *--random*:: +Generate a random-based UUID. This method creates a UUID consisting mostly of random bits. It requires that the operating system has a high quality random number generator, such as _/dev/random_. + +*-t*, *--time*:: +Generate a time-based UUID. This method creates a UUID based on the system clock plus the system's ethernet hardware address, if present. + +include::man-common/help-version.adoc[] + +*-m*, *--md5*:: +Use MD5 as the hash algorithm. + +*-s*, *--sha1*:: +Use SHA1 as the hash algorithm. + +*-n*, *--namespace* _namespace_:: +Generate the hash with the _namespace_ prefix. The _namespace_ is UUID, or '@ns' where "ns" is well-known predefined UUID addressed by namespace name (see above). + +*-N*, *--name* _name_:: +Generate the hash of the _name_. + +*-x*, *--hex*:: +Interpret name _name_ as a hexadecimal string. + +== CONFORMING TO + +OSF DCE 1.1 + +== EXAMPLES + +uuidgen --sha1 --namespace @dns --name "www.example.com" + +== AUTHORS + +*uuidgen* was written by Andreas Dilger for *libuuid*(3). + +== SEE ALSO + +*uuidparse*(1), +*libuuid*(3), +link:https://tools.ietf.org/html/rfc4122[RFC 4122] + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/uuidgen.c b/misc-utils/uuidgen.c new file mode 100644 index 0000000..7dcd1f4 --- /dev/null +++ b/misc-utils/uuidgen.c @@ -0,0 +1,210 @@ +/* + * gen_uuid.c --- generate a DCE-compatible uuid + * + * Copyright (C) 1999, Andreas Dilger and Theodore Ts'o + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> + +#include "uuid.h" +#include "nls.h" +#include "c.h" +#include "closestream.h" + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, + _(" %s [options]\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("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(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(21)); + printf(USAGE_MAN_TAIL("uuidgen(1)")); + exit(EXIT_SUCCESS); +} + +static char *unhex(const char *value, size_t *valuelen) +{ + char c, *value2; + unsigned n, x; + + if (*valuelen % 2 != 0) { +badstring: + warnx(_("not a valid hex string")); + errtryhelp(EXIT_FAILURE); + } + + value2 = malloc(*valuelen / 2 + 1); + + for (x = n = 0; n < *valuelen; n++) { + c = value[n]; + if ('0' <= c && c <= '9') + x += c - '0'; + else if (('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')) + x += (c - 'A' + 10) & 0xf; + else + goto badstring; + + if (n % 2 == 0) + x *= 16; + else { + value2[n / 2] = x; + x = 0; + } + } + value2[n / 2] = '\0'; + + *valuelen = (n / 2); + + return value2; +} + +int +main (int argc, char *argv[]) +{ + int c; + int do_type = 0, is_hex = 0; + char str[UUID_STR_LEN]; + char *namespace = NULL, *name = NULL; + size_t namelen = 0; + uuid_t ns, uu; + + static const struct option longopts[] = { + {"random", no_argument, NULL, 'r'}, + {"time", no_argument, NULL, 't'}, + {"version", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {"namespace", required_argument, NULL, 'n'}, + {"name", required_argument, NULL, 'N'}, + {"md5", no_argument, NULL, 'm'}, + {"sha1", no_argument, NULL, 's'}, + {"hex", no_argument, NULL, 'x'}, + {NULL, 0, NULL, 0} + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + while ((c = getopt_long(argc, argv, "rtVhn:N:msx", longopts, NULL)) != -1) + switch (c) { + case 't': + do_type = UUID_TYPE_DCE_TIME; + break; + case 'r': + do_type = UUID_TYPE_DCE_RANDOM; + break; + case 'n': + namespace = optarg; + break; + case 'N': + name = optarg; + break; + case 'm': + do_type = UUID_TYPE_DCE_MD5; + break; + case 's': + do_type = UUID_TYPE_DCE_SHA1; + break; + case 'x': + is_hex = 1; + break; + + case 'h': + usage(); + case 'V': + print_version(EXIT_SUCCESS); + default: + errtryhelp(EXIT_FAILURE); + } + + if (namespace) { + if (!name) { + warnx(_("--namespace requires --name argument")); + errtryhelp(EXIT_FAILURE); + } + if (do_type != UUID_TYPE_DCE_MD5 && do_type != UUID_TYPE_DCE_SHA1) { + warnx(_("--namespace requires --md5 or --sha1")); + errtryhelp(EXIT_FAILURE); + } + } else { + if (name) { + warnx(_("--name requires --namespace argument")); + errtryhelp(EXIT_FAILURE); + } + if (do_type == UUID_TYPE_DCE_MD5 || do_type == UUID_TYPE_DCE_SHA1) { + warnx(_("--md5 or --sha1 requires --namespace argument")); + errtryhelp(EXIT_FAILURE); + } + } + + if (name) { + namelen = strlen(name); + if (is_hex) + 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); + } + } + 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); + + printf("%s\n", str); + + if (is_hex) + free(name); + + return EXIT_SUCCESS; +} diff --git a/misc-utils/uuidparse.1 b/misc-utils/uuidparse.1 new file mode 100644 index 0000000..2109361 --- /dev/null +++ b/misc-utils/uuidparse.1 @@ -0,0 +1,174 @@ +'\" t +.\" Title: uuidparse +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "UUIDPARSE" "1" "2023-10-23" "util\-linux 2.39.3" "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" +uuidparse \- a utility to parse unique identifiers +.SH "SYNOPSIS" +.sp +\fBuuidparse\fP [options] \fIuuid\fP +.SH "DESCRIPTION" +.sp +This command will parse unique identifier inputs from either command line arguments or standard input. The inputs are white\-space separated. +.SH "OUTPUT" +.SS "Variants" +.TS +allbox tab(:); +lt lt. +T{ +.sp +NCS +T}:T{ +.sp +Network Computing System identifier. These were the original UUIDs. +T} +T{ +.sp +DCE +T}:T{ +.sp +The Open Software Foundation\(cqs (OSF) Distributed Computing Environment UUIDs. +T} +T{ +.sp +Microsoft +T}:T{ +.sp +Microsoft Windows platform globally unique identifier (GUID). +T} +T{ +.sp +other +T}:T{ +.sp +Unknown variant. Usually invalid input data. +T} +.TE +.sp +.SS "Types" +.TS +allbox tab(:); +lt lt. +T{ +.sp +nil +T}:T{ +.sp +Special type for zero in type file. +T} +T{ +.sp +time\-based +T}:T{ +.sp +The DCE time based. +T} +T{ +.sp +DCE +T}:T{ +.sp +The DCE time and MAC Address. +T} +T{ +.sp +name\-based +T}:T{ +.sp +RFC 4122 md5sum hash. +T} +T{ +.sp +random +T}:T{ +.sp +RFC 4122 random. +T} +T{ +.sp +sha1\-based +T}:T{ +.sp +RFC 4122 sha\-1 hash. +T} +T{ +.sp +unknown +T}:T{ +.sp +Unknown type. Usually invalid input data. +T} +.TE +.sp +.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 +Do not print a header line. +.RE +.sp +\fB\-o\fP, \fB\-\-output\fP +.RS 4 +Specify which output columns to print. Use \fB\-\-help\fP to get a list of all supported columns. +.RE +.sp +\fB\-r\fP, \fB\-\-raw\fP +.RS 4 +Use the raw output format. +.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 "AUTHORS" +.sp +.MTO "kerolasa\(atiki.fi" "Sami Kerola" "" +.SH "SEE ALSO" +.sp +\fBuuidgen\fP(1), +\fBlibuuid\fP(3), +.URL "https://tools.ietf.org/html/rfc4122" "RFC 4122" "" +.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 \fBuuidparse\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/uuidparse.1.adoc b/misc-utils/uuidparse.1.adoc new file mode 100644 index 0000000..d517c19 --- /dev/null +++ b/misc-utils/uuidparse.1.adoc @@ -0,0 +1,80 @@ +//po4a: entry man manual +// Copyright (c) 2017 Sami Kerola +// The 3-Clause BSD License += uuidparse(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: uuidparse + +== NAME + +uuidparse - a utility to parse unique identifiers + +== SYNOPSIS + +*uuidparse* [options] _uuid_ + +== DESCRIPTION + +This command will parse unique identifier inputs from either command line arguments or standard input. The inputs are white-space separated. + +== OUTPUT + +=== Variants + +[cols=",",] +|=== +|NCS |Network Computing System identifier. These were the original UUIDs. +|DCE |The Open Software Foundation's (OSF) Distributed Computing Environment UUIDs. +|Microsoft |Microsoft Windows platform globally unique identifier (GUID). +|other |Unknown variant. Usually invalid input data. +|=== + +=== Types + +[cols=",",] +|=== +|nil |Special type for zero in type file. +|time-based |The DCE time based. +|DCE |The DCE time and MAC Address. +|name-based |RFC 4122 md5sum hash. +|random |RFC 4122 random. +|sha1-based |RFC 4122 sha-1 hash. +|unknown |Unknown type. Usually invalid input data. +|=== + +== OPTIONS + +*-J*, *--json*:: +Use JSON output format. + +*-n*, *--noheadings*:: +Do not print a header line. + +*-o*, *--output*:: +Specify which output columns to print. Use *--help* to get a list of all supported columns. + +*-r*, *--raw*:: +Use the raw output format. + +include::man-common/help-version.adoc[] + +== AUTHORS + +mailto:kerolasa@iki.fi[Sami Kerola] + +== SEE ALSO + +*uuidgen*(1), +*libuuid*(3), +https://tools.ietf.org/html/rfc4122[RFC 4122] + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/uuidparse.c b/misc-utils/uuidparse.c new file mode 100644 index 0000000..2dc6dfb --- /dev/null +++ b/misc-utils/uuidparse.c @@ -0,0 +1,349 @@ +/* + * uuidparse.c --- Interpret uuid encoded information. This program + * violates the UUID abstraction barrier by reaching into the + * guts of a UUID. + * + * Based on libuuid/src/uuid_time.c + * Copyright (C) 1998, 1999 Theodore Ts'o. + * + * All alterations (C) 2017 Sami Kerola + * The 3-Clause BSD License + * + * 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, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 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. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR 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 NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +#include <assert.h> +#include <getopt.h> +#include <libsmartcols.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <uuid.h> + +#include "c.h" +#include "closestream.h" +#include "nls.h" +#include "optutils.h" +#include "strutils.h" +#include "timeutils.h" +#include "xalloc.h" + +/* column IDs */ +enum { + COL_UUID = 0, + COL_VARIANT, + COL_TYPE, + COL_TIME +}; + +/* 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; +}; + +/* columns descriptions */ +static const struct colinfo infos[] = { + [COL_UUID] = {"UUID", UUID_STR_LEN, 0, N_("unique identifier")}, + [COL_VARIANT] = {"VARIANT", 9, 0, N_("variant name")}, + [COL_TYPE] = {"TYPE", 10, 0, N_("type name")}, + [COL_TIME] = {"TIME", 31, 0, N_("timestamp")} +}; + +static int columns[ARRAY_SIZE(infos) * 2]; +static size_t ncolumns; + +struct control { + unsigned int + json:1, + no_headings:1, + raw:1; +}; + +static void __attribute__((__noreturn__)) usage(void) +{ + size_t i; + + fputs(USAGE_HEADER, stdout); + 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)); + + 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)")); + exit(EXIT_SUCCESS); +} + +static int column_name_to_id(const char *name, size_t namesz) +{ + size_t i; + + assert(name); + + 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 int get_column_id(size_t num) +{ + assert(num < ncolumns); + assert(columns[num] < (int)ARRAY_SIZE(infos)); + return columns[num]; +} + +static const struct colinfo *get_column_info(int num) +{ + return &infos[get_column_id(num)]; +} + +static void fill_table_row(struct libscols_table *tb, char const *const uuid) +{ + static struct libscols_line *ln; + size_t i; + uuid_t buf; + int invalid = 0; + int variant = -1, type = -1; + + assert(tb); + assert(uuid); + + ln = scols_table_new_line(tb, NULL); + if (!ln) + errx(EXIT_FAILURE, _("failed to allocate output line")); + + if (uuid_parse(uuid, buf)) + invalid = 1; + else { + variant = uuid_variant(buf); + type = uuid_type(buf); + } + + for (i = 0; i < ncolumns; i++) { + char *str = NULL; + + switch (get_column_id(i)) { + case COL_UUID: + str = xstrdup(uuid); + break; + case COL_VARIANT: + if (invalid) { + str = xstrdup(_("invalid")); + break; + } + switch (variant) { + case UUID_VARIANT_NCS: + str = xstrdup("NCS"); + break; + case UUID_VARIANT_DCE: + str = xstrdup("DCE"); + break; + case UUID_VARIANT_MICROSOFT: + str = xstrdup("Microsoft"); + break; + default: + str = xstrdup(_("other")); + } + break; + case COL_TYPE: + if (invalid) { + str = xstrdup(_("invalid")); + break; + } + switch (type) { + case UUID_TYPE_DCE_NIL: + if (uuid_is_null(buf)) + str = xstrdup(_("nil")); + else + str = xstrdup(_("unknown")); + break; + case UUID_TYPE_DCE_TIME: + str = xstrdup(_("time-based")); + break; + case UUID_TYPE_DCE_SECURITY: + str = xstrdup("DCE"); + break; + case UUID_TYPE_DCE_MD5: + str = xstrdup(_("name-based")); + break; + case UUID_TYPE_DCE_RANDOM: + str = xstrdup(_("random")); + break; + case UUID_TYPE_DCE_SHA1: + str = xstrdup(_("sha1-based")); + break; + default: + str = xstrdup(_("unknown")); + } + break; + case COL_TIME: + if (invalid) { + str = xstrdup(_("invalid")); + break; + } + if (variant == UUID_VARIANT_DCE && type == UUID_TYPE_DCE_TIME) { + struct timeval tv; + char date_buf[ISO_BUFSIZ]; + + uuid_time(buf, &tv); + strtimeval_iso(&tv, ISO_TIMESTAMP_COMMA, + date_buf, sizeof(date_buf)); + str = xstrdup(date_buf); + } + break; + default: + abort(); + } + if (str && scols_line_refer_data(ln, i, str)) + errx(EXIT_FAILURE, _("failed to add output data")); + } +} + +static void print_output(struct control const *const ctrl, int argc, + char **argv) +{ + struct libscols_table *tb; + size_t i; + + scols_init_debug(0); + tb = scols_new_table(); + if (!tb) + err(EXIT_FAILURE, _("failed to allocate output table")); + + if (ctrl->json) { + scols_table_enable_json(tb, 1); + scols_table_set_name(tb, "uuids"); + } + scols_table_enable_noheadings(tb, ctrl->no_headings); + scols_table_enable_raw(tb, ctrl->raw); + + for (i = 0; i < ncolumns; i++) { + const struct colinfo *col = get_column_info(i); + + if (!scols_table_new_column(tb, col->name, col->whint, + col->flags)) + err(EXIT_FAILURE, + _("failed to initialize output column")); + } + + for (i = 0; i < (size_t) argc; i++) + fill_table_row(tb, argv[i]); + + if (i == 0) { + char uuid[UUID_STR_LEN]; + + while (scanf(" %36[^ \t\n]%*c", uuid) && !feof(stdin)) + fill_table_row(tb, uuid); + } + scols_print_table(tb); + scols_unref_table(tb); +} + +int main(int argc, char **argv) +{ + struct control ctrl = { 0 }; + char *outarg = NULL; + int c; + + static const struct option longopts[] = { + {"json", no_argument, NULL, 'J'}, + {"noheadings", no_argument, NULL, 'n'}, + {"output", required_argument, NULL, 'o'}, + {"raw", no_argument, NULL, 'r'}, + {"version", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + static const ul_excl_t excl[] = { + {'J', 'r'}, + {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, "Jno:rVh", longopts, NULL)) != -1) { + err_exclusive_options(c, longopts, excl, excl_st); + switch (c) { + case 'J': + ctrl.json = 1; + break; + case 'n': + ctrl.no_headings = 1; + break; + case 'o': + outarg = optarg; + break; + case 'r': + ctrl.raw = 1; + break; + + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + argc -= optind; + argv += optind; + + columns[ncolumns++] = COL_UUID; + columns[ncolumns++] = COL_VARIANT; + columns[ncolumns++] = COL_TYPE; + columns[ncolumns++] = COL_TIME; + + if (outarg + && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns), + &ncolumns, column_name_to_id) < 0) + return EXIT_FAILURE; + + print_output(&ctrl, argc, argv); + + return EXIT_SUCCESS; +} diff --git a/misc-utils/waitpid.1 b/misc-utils/waitpid.1 new file mode 100644 index 0000000..98ca155 --- /dev/null +++ b/misc-utils/waitpid.1 @@ -0,0 +1,108 @@ +'\" t +.\" Title: waitpid +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-11-21 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "WAITPID" "1" "2023-11-21" "util\-linux 2.39.3" "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" +waitpid \- utility to wait for arbitrary processes +.SH "SYNOPSIS" +.sp +\fBwaitpid\fP [\-v] [\fB\-\-timeout\fP|\fB\-t\fP \fIseconds\fP] pid... +.SH "DESCRIPTION" +.sp +\fBwaitpid\fP is a simple command to wait for arbitrary non\-child processes. +.sp +It exits after all processes whose PIDs have been passed as arguments have +exited. +.SH "OPTIONS" +.sp +\fB\-v\fP, \fB\-\-verbose\fP +.RS 4 +Be more verbose. +.RE +.sp +\fB\-t\fP, \fB\-\-timeout\fP \fIseconds\fP +.RS 4 +Maximum wait time. +.RE +.sp +\fB\-e\fP, \fB\-\-exited\fP +.RS 4 +Don\(cqt error on already exited PIDs. +.RE +.sp +\fB\-c\fP, \fB\-\-count\fP \fIcount\fP +.RS 4 +Number of process exits to wait for. +.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 +\fBwaitpid\fP has the following exit status values: +.sp +\fB0\fP +.RS 4 +success +.RE +.sp +\fB1\fP +.RS 4 +unspecified failure +.RE +.sp +\fB2\fP +.RS 4 +system does not provide necessary functionality +.RE +.sp +\fB3\fP +.RS 4 +timeout expired +.RE +.SH "AUTHORS" +.sp +.MTO "thomas\(att\-8ch.de" "Thomas Weißschuh" "" +.SH "SEE ALSO" +.sp +\fBwaitpid\fP(2) \fBwait\fP(1P) +.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 \fBwaitpid\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/waitpid.1.adoc b/misc-utils/waitpid.1.adoc new file mode 100644 index 0000000..e0dae51 --- /dev/null +++ b/misc-utils/waitpid.1.adoc @@ -0,0 +1,67 @@ +//po4a: entry man manual += waitpid(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: waitpid + +== NAME + +waitpid - utility to wait for arbitrary processes + +== SYNOPSIS + +*waitpid* [-v] [*--timeout*|*-t* _seconds_] pid... + +== DESCRIPTION + +*waitpid* is a simple command to wait for arbitrary non-child processes. + +It exits after all processes whose PIDs have been passed as arguments have +exited. + +== OPTIONS + +*-v*, *--verbose*:: +Be more verbose. + +*-t*, *--timeout* _seconds_:: +Maximum wait time. + +*-e*, *--exited*:: +Don't error on already exited PIDs. + +*-c*, *--count* _count_:: +Number of process exits to wait for. + +include::man-common/help-version.adoc[] + +== EXIT STATUS + +*waitpid* has the following exit status values: + +*0*:: +success +*1*:: +unspecified failure +*2*:: +system does not provide necessary functionality +*3*:: +timeout expired + +== AUTHORS + +mailto:thomas@t-8ch.de[Thomas Weißschuh] + +== SEE ALSO + +*waitpid*(2) *wait*(1P) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/waitpid.c b/misc-utils/waitpid.c new file mode 100644 index 0000000..b01a2f0 --- /dev/null +++ b/misc-utils/waitpid.c @@ -0,0 +1,255 @@ +/* + * waitpid(1) - wait for process termination + * + * Copyright (C) 2022 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 <sys/epoll.h> +#include <sys/timerfd.h> +#include <assert.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <stdbool.h> +#include <getopt.h> + +#include "pidfd-utils.h" +#include "c.h" +#include "nls.h" +#include "xalloc.h" +#include "strutils.h" +#include "exitcodes.h" +#include "timeutils.h" +#include "optutils.h" + +#define EXIT_TIMEOUT_EXPIRED 3 + +#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; +static size_t count; + +static pid_t *parse_pids(size_t n_strings, char * const *strings) +{ + pid_t *pids = xcalloc(n_strings, sizeof(*pids)); + + for (size_t i = 0; i < n_strings; i++) + pids[i] = strtopid_or_err(strings[i], _("failed to parse pid")); + + return pids; +} + +static int *open_pidfds(size_t n_pids, pid_t *pids) +{ + int *pidfds = xcalloc(n_pids, sizeof(*pidfds)); + + for (size_t i = 0; i < n_pids; i++) { + pidfds[i] = pidfd_open(pids[i], 0); + if (pidfds[i] == -1) { + if (allow_exited && errno == ESRCH) { + warnx(_("PID %d has exited, skipping"), pids[i]); + continue; + } + err_nosys(EXIT_FAILURE, _("could not open pid %u"), pids[i]); + } + } + + return pidfds; +} + +static int open_timeoutfd(void) +{ + int fd; + struct itimerspec timer = {}; + + if (!timeout.tv_sec && !timeout.tv_nsec) + return -1; + + timer.it_value = timeout; + + fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); + if (fd == -1) + err_nosys(EXIT_FAILURE, _("could not create timerfd")); + + if (timerfd_settime(fd, 0, &timer, NULL)) + err_nosys(EXIT_FAILURE, _("could not set timer")); + + return fd; + +} + +static size_t add_listeners(int epll, size_t n_pids, int * const pidfds, int timeoutfd) +{ + size_t skipped = 0; + struct epoll_event evt = { + .events = EPOLLIN, + }; + + if (timeoutfd != -1) { + evt.data.u64 = TIMEOUT_SOCKET_IDX; + if (epoll_ctl(epll, EPOLL_CTL_ADD, timeoutfd, &evt)) + err_nosys(EXIT_FAILURE, _("could not add timerfd")); + } + + for (size_t i = 0; i < n_pids; i++) { + if (pidfds[i] == -1) { + skipped++; + continue; + } + evt.data.u64 = i; + if (epoll_ctl(epll, EPOLL_CTL_ADD, pidfds[i], &evt)) + err_nosys(EXIT_FAILURE, _("could not add listener")); + } + + return n_pids - skipped; +} + +static void wait_for_exits(int epll, size_t active_pids, pid_t * const pids, + int * const pidfds) +{ + while (active_pids) { + struct epoll_event evt; + int ret, fd; + + ret = epoll_wait(epll, &evt, 1, -1); + if (ret == -1) { + if (errno == EINTR) + continue; + else + err_nosys(EXIT_FAILURE, _("failure during wait")); + } + if (evt.data.u64 == TIMEOUT_SOCKET_IDX) { + if (verbose) + printf(_("Timeout expired\n")); + exit(EXIT_TIMEOUT_EXPIRED); + } + if (verbose) + printf(_("PID %d finished\n"), pids[evt.data.u64]); + assert((size_t) ret <= active_pids); + fd = pidfds[evt.data.u64]; + epoll_ctl(epll, EPOLL_CTL_DEL, fd, NULL); + active_pids -= ret; + } +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] pid...\n"), program_invocation_short_name); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -v, --verbose be more verbose\n"), out); + fputs(_(" -t, --timeout=<timeout> wait at most timeout seconds\n"), out); + fputs(_(" -e, --exited allow exited PIDs\n"), out); + fputs(_(" -c, --count=<count> number of process exits to wait for\n"), out); + + fputs(USAGE_SEPARATOR, out); + fprintf(out, USAGE_HELP_OPTIONS(25)); + + fprintf(out, USAGE_MAN_TAIL("waitpid(1)")); + + exit(EXIT_SUCCESS); +} + +static int parse_options(int argc, char **argv) +{ + int c; + static const struct option longopts[] = { + { "verbose", no_argument, NULL, 'v' }, + { "timeout", required_argument, NULL, 't' }, + { "exited", no_argument, NULL, 'e' }, + { "count", required_argument, NULL, 'c' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { 0 } + }; + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { 'c', 'e' }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + while ((c = getopt_long (argc, argv, "vVht:c:e", longopts, NULL)) != -1) { + + err_exclusive_options(c, longopts, excl, excl_st); + + switch (c) { + case 'v': + verbose = true; + break; + case 't': + strtotimespec_or_err(optarg, &timeout, + _("Could not parse timeout")); + break; + case 'e': + allow_exited = true; + break; + case 'c': + count = str2num_or_err(optarg, 10, _("Invalid count"), + 1, INT64_MAX); + break; + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + return optind; +} + +int main(int argc, char **argv) +{ + int pid_idx, epoll, timeoutfd, *pidfds; + size_t n_pids, active_pids; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + pid_idx = parse_options(argc, argv); + n_pids = argc - pid_idx; + if (!n_pids) + errx(EXIT_FAILURE, _("no PIDs specified")); + + if (count && count > n_pids) + errx(EXIT_FAILURE, + _("can't want for %zu of %zu PIDs"), count, n_pids); + + pid_t *pids = parse_pids(argc - pid_idx, argv + pid_idx); + + pidfds = open_pidfds(n_pids, pids); + timeoutfd = open_timeoutfd(); + epoll = epoll_create(n_pids); + if (epoll == -1) + err_nosys(EXIT_FAILURE, _("could not create epoll")); + + active_pids = add_listeners(epoll, n_pids, pidfds, timeoutfd); + if (count) + active_pids = min(active_pids, count); + wait_for_exits(epoll, active_pids, pids, pidfds); +} diff --git a/misc-utils/whereis.1 b/misc-utils/whereis.1 new file mode 100644 index 0000000..c982144 --- /dev/null +++ b/misc-utils/whereis.1 @@ -0,0 +1,162 @@ +'\" t +.\" Title: whereis +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-11-21 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "WHEREIS" "1" "2023-11-21" "util\-linux 2.39.3" "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" +whereis \- locate the binary, source, and manual page files for a command +.SH "SYNOPSIS" +.sp +\fBwhereis\fP [options] [\fB\-BMS\fP \fIdirectory\fP... \fB\-f\fP] \fIname\fP... +.SH "DESCRIPTION" +.sp +\fBwhereis\fP locates the binary, source and manual files for the specified command names. The supplied names are \fBfirst stripped of leading pathname components\fP. Prefixes of \fBs.\fP resulting from use of source code control are also dealt with. \fBwhereis\fP then attempts to locate the desired program in the standard Linux places, and in the places specified by \fB$PATH\fP and \fB$MANPATH\fP. +.sp +The search restrictions (options \fB\-b\fP, \fB\-m\fP and \fB\-s\fP) are cumulative and apply to the subsequent \fIname\fP patterns on the command line. Any new search restriction resets the search mask. For example, +.RS 3 +.ll -.6i +.sp +\fBwhereis \-bm ls tr \-m gcc\fP +.br +.RE +.ll +.sp +searches for "ls" and "tr" binaries and man pages, and for "gcc" man pages only. +.sp +The options \fB\-B\fP, \fB\-M\fP and \fB\-S\fP reset search paths for the subsequent \fIname\fP patterns. For example, +.RS 3 +.ll -.6i +.sp +\fBwhereis \-m ls \-M /usr/share/man/man1 \-f cal\fP +.br +.RE +.ll +.sp +searches for "\fBls\fP" man pages in all default paths, but for "cal" in the \fI/usr/share/man/man1\fP directory only. +.SH "OPTIONS" +.sp +\fB\-b\fP +.RS 4 +Search for binaries. +.RE +.sp +\fB\-m\fP +.RS 4 +Search for manuals. +.RE +.sp +\fB\-s\fP +.RS 4 +Search for sources. +.RE +.sp +\fB\-u\fP +.RS 4 +Only show the command names that have unusual entries. A command is said to be unusual if it does not have just one entry of each explicitly requested type. Thus \*(Aq\fBwhereis \-m \-u *\fP\*(Aq asks for those files in the current directory which have no documentation file, or more than one. +.RE +.sp +\fB\-B\fP \fIlist\fP +.RS 4 +Limit the places where \fBwhereis\fP searches for binaries, by a whitespace\-separated list of directories. +.RE +.sp +\fB\-M\fP \fIlist\fP +.RS 4 +Limit the places where \fBwhereis\fP searches for manuals and documentation in Info format, by a whitespace\-separated list of directories. +.RE +.sp +\fB\-S\fP \fIlist\fP +.RS 4 +Limit the places where \fBwhereis\fP searches for sources, by a whitespace\-separated list of directories. +.RE +.sp +\fB\-f\fP +.RS 4 +Terminates the directory list and signals the start of filenames. It \fImust\fP be used when any of the \fB\-B\fP, \fB\-M\fP, or \fB\-S\fP options is used. +.RE +.sp +\fB\-l\fP +.RS 4 +Output the list of effective lookup paths that \fBwhereis\fP is using. When none of \fB\-B\fP, \fB\-M\fP, or \fB\-S\fP is specified, the option will output the hard\-coded paths that the command was able to find on the system. +.RE +.sp +\fB\-g\fP +.RS 4 +Interpret the next names as a \fBglob(7)\fP patterns. \fBwhereis\fP always compares only filenames (aka basename) and never complete path. Using directory names in the pattern has no effect. Don’t forget that the shell interprets the pattern when specified on the command line without quotes. It’s necessary to use quotes for the \fIname\fP, for example: +.RE +.RS 3 +.ll -.6i +.sp +.if n .RS 4 +.nf +.fam C +whereis \-g \*(Aqfind*\*(Aq +.fam +.fi +.if n .RE +.br +.RE +.ll +.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 "FILE SEARCH PATHS" +.sp +By default \fBwhereis\fP tries to find files from hard\-coded paths, which are defined with glob patterns. The command attempts to use the contents of \fB$PATH\fP and \fB$MANPATH\fP environment variables as default search path. The easiest way to know what paths are in use is to add the \fB\-l\fP listing option. Effects of the \fB\-B\fP, \fB\-M\fP, and \fB\-S\fP are displayed with \fB\-l\fP. +.SH "ENVIRONMENT" +.sp +\fBWHEREIS_DEBUG\fP=all +.RS 4 +enables debug output. +.RE +.SH "EXAMPLES" +.sp +To find all files in \fI/usr/bin\fP which are not documented in \fI/usr/man/man1\fP or have no source in \fI/usr/src\fP: +.RS 3 +.ll -.6i +.sp +\fBcd /usr/bin\fP +.br +\fBwhereis \-u \-ms \-M /usr/man/man1 \-S /usr/src \-f *\fP +.br +.RE +.ll +.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 \fBwhereis\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/whereis.1.adoc b/misc-utils/whereis.1.adoc new file mode 100644 index 0000000..6a85e6c --- /dev/null +++ b/misc-utils/whereis.1.adoc @@ -0,0 +1,130 @@ +//po4a: entry man manual +//// +Copyright (c) 1980, 1990 The Regents of the University of California. +All rights reserved. + +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. +3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by the University of + California, Berkeley and its contributors. +4. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + +@(#)whereis.1 from UCB 4.2 +//// += whereis(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: whereis + +== NAME + +whereis - locate the binary, source, and manual page files for a command + +== SYNOPSIS + +*whereis* [options] [*-BMS* _directory_... *-f*] _name_... + +== DESCRIPTION + +*whereis* locates the binary, source and manual files for the specified command names. The supplied names are *first stripped of leading pathname components*. Prefixes of *s.* resulting from use of source code control are also dealt with. *whereis* then attempts to locate the desired program in the standard Linux places, and in the places specified by *$PATH* and *$MANPATH*. + +The search restrictions (options *-b*, *-m* and *-s*) are cumulative and apply to the subsequent _name_ patterns on the command line. Any new search restriction resets the search mask. For example, + +____ +*whereis -bm ls tr -m gcc* +____ + +searches for "ls" and "tr" binaries and man pages, and for "gcc" man pages only. + +The options *-B*, *-M* and *-S* reset search paths for the subsequent _name_ patterns. For example, + +____ +*whereis -m ls -M /usr/share/man/man1 -f cal* +____ + +searches for "*ls*" man pages in all default paths, but for "cal" in the _/usr/share/man/man1_ directory only. + +== OPTIONS + +*-b*:: +Search for binaries. + +*-m*:: +Search for manuals. + +*-s*:: +Search for sources. + +*-u*:: +Only show the command names that have unusual entries. A command is said to be unusual if it does not have just one entry of each explicitly requested type. Thus '*whereis -m -u **' asks for those files in the current directory which have no documentation file, or more than one. + +*-B* _list_:: +Limit the places where *whereis* searches for binaries, by a whitespace-separated list of directories. + +*-M* _list_:: +Limit the places where *whereis* searches for manuals and documentation in Info format, by a whitespace-separated list of directories. + +*-S* _list_:: +Limit the places where *whereis* searches for sources, by a whitespace-separated list of directories. + +*-f*:: +Terminates the directory list and signals the start of filenames. It _must_ be used when any of the *-B*, *-M*, or *-S* options is used. + +*-l*:: +Output the list of effective lookup paths that *whereis* is using. When none of *-B*, *-M*, or *-S* is specified, the option will output the hard-coded paths that the command was able to find on the system. + +*-g*:: +Interpret the next names as a *glob(7)* patterns. *whereis* always compares only filenames (aka basename) and never complete path. Using directory names in the pattern has no effect. Don’t forget that the shell interprets the pattern when specified on the command line without quotes. It’s necessary to use quotes for the _name_, for example: +____ + whereis -g 'find*' +____ + +include::man-common/help-version.adoc[] + +== FILE SEARCH PATHS + +By default *whereis* tries to find files from hard-coded paths, which are defined with glob patterns. The command attempts to use the contents of *$PATH* and *$MANPATH* environment variables as default search path. The easiest way to know what paths are in use is to add the *-l* listing option. Effects of the *-B*, *-M*, and *-S* are displayed with *-l*. + +== ENVIRONMENT + +*WHEREIS_DEBUG*=all:: +enables debug output. + +== EXAMPLES + +To find all files in _/usr/bin_ which are not documented in _/usr/man/man1_ or have no source in _/usr/src_: +____ +*cd /usr/bin* + +*whereis -u -ms -M /usr/man/man1 -S /usr/src -f ** +____ +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/whereis.c b/misc-utils/whereis.c new file mode 100644 index 0000000..d21b434 --- /dev/null +++ b/misc-utils/whereis.c @@ -0,0 +1,672 @@ +/*- + * Copyright (c) 1980 The Regents of the University of California. + * All rights reserved. + * + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + * 2011-08-12 Davidlohr Bueso <dave@gnu.org> + * - added $PATH lookup + * + * Copyright (C) 2013 Karel Zak <kzak@redhat.com> + * 2013 Sami Kerola <kerolasa@iki.fi> + */ + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#ifdef HAVE_FNMATCH +# include <fnmatch.h> +#endif + +#include "xalloc.h" +#include "nls.h" +#include "c.h" +#include "closestream.h" +#include "canonicalize.h" + +#include "debug.h" + +static UL_DEBUG_DEFINE_MASK(whereis); +UL_DEBUG_DEFINE_MASKNAMES(whereis) = UL_DEBUG_EMPTY_MASKNAMES; + +#define WHEREIS_DEBUG_INIT (1 << 1) +#define WHEREIS_DEBUG_PATH (1 << 2) +#define WHEREIS_DEBUG_ENV (1 << 3) +#define WHEREIS_DEBUG_ARGV (1 << 4) +#define WHEREIS_DEBUG_SEARCH (1 << 5) +#define WHEREIS_DEBUG_STATIC (1 << 6) +#define WHEREIS_DEBUG_LIST (1 << 7) +#define WHEREIS_DEBUG_ALL 0xFFFF + +#define DBG(m, x) __UL_DBG(whereis, WHEREIS_DEBUG_, m, x) +#define ON_DBG(m, x) __UL_DBG_CALL(whereis, WHEREIS_DEBUG_, m, x) + +#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(whereis) +#include "debugobj.h" + +static char uflag; +static char use_glob; + +/* supported types */ +enum { + BIN_DIR = (1 << 1), + MAN_DIR = (1 << 2), + SRC_DIR = (1 << 3), + + ALL_DIRS = BIN_DIR | MAN_DIR | SRC_DIR +}; + +/* directories */ +struct wh_dirlist { + int type; + dev_t st_dev; + ino_t st_ino; + char *path; + + struct wh_dirlist *next; +}; + +static const char *bindirs[] = { + "/usr/bin", + "/usr/sbin", + "/bin", + "/sbin", +#if defined(MULTIARCHTRIPLET) + "/lib/" MULTIARCHTRIPLET, + "/usr/lib/" MULTIARCHTRIPLET, + "/usr/local/lib/" MULTIARCHTRIPLET, +#endif + "/usr/lib", + "/usr/lib32", + "/usr/lib64", + "/etc", + "/usr/etc", + "/lib", + "/lib32", + "/lib64", + "/usr/games", + "/usr/games/bin", + "/usr/games/lib", + "/usr/emacs/etc", + "/usr/lib/emacs/*/etc", + "/usr/TeX/bin", + "/usr/tex/bin", + "/usr/interviews/bin/LINUX", + + "/usr/X11R6/bin", + "/usr/X386/bin", + "/usr/bin/X11", + "/usr/X11/bin", + "/usr/X11R5/bin", + + "/usr/local/bin", + "/usr/local/sbin", + "/usr/local/etc", + "/usr/local/lib", + "/usr/local/games", + "/usr/local/games/bin", + "/usr/local/emacs/etc", + "/usr/local/TeX/bin", + "/usr/local/tex/bin", + "/usr/local/bin/X11", + + "/usr/contrib", + "/usr/hosts", + "/usr/include", + + "/usr/g++-include", + + "/usr/ucb", + "/usr/old", + "/usr/new", + "/usr/local", + "/usr/libexec", + "/usr/share", + + "/opt/*/bin", + NULL +}; + +static const char *mandirs[] = { + "/usr/man/*", + "/usr/share/man/*", + "/usr/X386/man/*", + "/usr/X11/man/*", + "/usr/TeX/man/*", + "/usr/interviews/man/mann", + "/usr/share/info", + NULL +}; + +static const char *srcdirs[] = { + "/usr/src/*", + "/usr/src/lib/libc/*", + "/usr/src/lib/libc/net/*", + "/usr/src/ucb/pascal", + "/usr/src/ucb/pascal/utilities", + "/usr/src/undoc", + NULL +}; + +static void whereis_init_debug(void) +{ + __UL_INIT_DEBUG_FROM_ENV(whereis, WHEREIS_DEBUG_, 0, WHEREIS_DEBUG); +} + +static const char *whereis_type_to_name(int type) +{ + switch (type) { + case BIN_DIR: return "bin"; + case MAN_DIR: return "man"; + case SRC_DIR: return "src"; + default: return "???"; + } +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] [-BMS <dir>... -f] <name>\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Locate the binary, source, and manual-page files for a command.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -b search only for binaries\n"), out); + fputs(_(" -B <dirs> define binaries lookup path\n"), out); + fputs(_(" -m search only for manuals and infos\n"), out); + fputs(_(" -M <dirs> define man and info lookup path\n"), out); + fputs(_(" -s search only for sources\n"), out); + fputs(_(" -S <dirs> define sources lookup path\n"), out); + fputs(_(" -f terminate <dirs> argument list\n"), out); + fputs(_(" -u search for unusual entries\n"), out); + fputs(_(" -g interpret name as glob (pathnames pattern)\n"), out); + fputs(_(" -l output effective lookup paths\n"), out); + + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(16)); + printf(USAGE_MAN_TAIL("whereis(1)")); + exit(EXIT_SUCCESS); +} + +static void dirlist_add_dir(struct wh_dirlist **ls0, int type, const char *dir) +{ + struct stat st; + struct wh_dirlist *prev = NULL, *ls = *ls0; + + if (access(dir, R_OK) != 0) + return; + if (stat(dir, &st) != 0 || !S_ISDIR(st.st_mode)) + return; + + while (ls) { + if (ls->st_ino == st.st_ino && + ls->st_dev == st.st_dev && + ls->type == type) { + DBG(LIST, ul_debugobj(*ls0, " ignore (already in list): %s", dir)); + return; + } + prev = ls; + ls = ls->next; + } + + + ls = xcalloc(1, sizeof(*ls)); + ls->st_ino = st.st_ino; + ls->st_dev = st.st_dev; + ls->type = type; + ls->path = canonicalize_path(dir); + + if (!*ls0) + *ls0 = ls; /* first in the list */ + else { + assert(prev); + prev->next = ls; /* add to the end of the list */ + } + + DBG(LIST, ul_debugobj(*ls0, " add dir: %s", ls->path)); +} + +/* special case for '*' in the paths */ +static void dirlist_add_subdir(struct wh_dirlist **ls, int type, const char *dir) +{ + char buf[PATH_MAX], *d; + DIR *dirp; + struct dirent *dp; + char *postfix; + size_t len; + + postfix = strchr(dir, '*'); + if (!postfix) + goto ignore; + + /* copy begin of the path to the buffer (part before '*') */ + len = (postfix - dir) + 1; + xstrncpy(buf, dir, len); + + /* remember place where to append subdirs */ + d = buf + len - 1; + + /* skip '*' */ + postfix++; + if (!*postfix) + postfix = NULL; + + /* open parental dir t scan */ + dirp = opendir(buf); + if (!dirp) + goto ignore; + + DBG(LIST, ul_debugobj(*ls, " scanning subdirs: %s [%s<subdir>%s]", + dir, buf, postfix ? postfix : "")); + + while ((dp = readdir(dirp)) != NULL) { + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) + continue; + if (postfix) + snprintf(d, PATH_MAX - len, "%s%s", dp->d_name, postfix); + else + snprintf(d, PATH_MAX - len, "%s", dp->d_name); + + dirlist_add_dir(ls, type, buf); + } + closedir(dirp); + return; +ignore: + DBG(LIST, ul_debugobj(*ls, " ignore path: %s", dir)); +} + +static void construct_dirlist_from_env(const char *env, + struct wh_dirlist **ls, + int type) +{ + char *key = NULL, *tok = NULL, *pathcp, *path = getenv(env); + + if (!path) + return; + pathcp = xstrdup(path); + + DBG(ENV, ul_debugobj(*ls, "construct %s dirlist from: %s", + whereis_type_to_name(type), path)); + + for (tok = strtok_r(pathcp, ":", &key); tok; + tok = strtok_r(NULL, ":", &key)) + dirlist_add_dir(ls, type, tok); + + free(pathcp); +} + +static void construct_dirlist_from_argv(struct wh_dirlist **ls, + int *idx, + int argc, + char *argv[], + int type) +{ + int i; + + DBG(ARGV, ul_debugobj(*ls, "construct %s dirlist from argv[%d..]", + whereis_type_to_name(type), *idx)); + + for (i = *idx; i < argc; i++) { + if (*argv[i] == '-') /* end of the list */ + break; + + DBG(ARGV, ul_debugobj(*ls, " using argv[%d]: %s", *idx, argv[*idx])); + dirlist_add_dir(ls, type, argv[i]); + *idx = i; + } +} + +static void construct_dirlist(struct wh_dirlist **ls, + int type, + const char **paths) +{ + size_t i; + + DBG(STATIC, ul_debugobj(*ls, "construct %s dirlist from static array", + whereis_type_to_name(type))); + + for (i = 0; paths[i]; i++) { + if (!strchr(paths[i], '*')) + dirlist_add_dir(ls, type, paths[i]); + else + dirlist_add_subdir(ls, type, paths[i]); + } +} + +static void free_dirlist(struct wh_dirlist **ls0, int type) +{ + struct wh_dirlist *prev = NULL, *next, *ls = *ls0; + + *ls0 = NULL; + + DBG(LIST, ul_debugobj(*ls0, "free dirlist")); + + while (ls) { + if (ls->type & type) { + next = ls->next; + DBG(LIST, ul_debugobj(*ls0, " free: %s", ls->path)); + free(ls->path); + free(ls); + ls = next; + if (prev) + prev->next = ls; + } else { + if (!prev) + *ls0 = ls; /* first unremoved */ + prev = ls; + ls = ls->next; + } + } +} + + +static int filename_equal(const char *cp, const char *dp, int type) +{ + size_t i; + + DBG(SEARCH, ul_debug("compare '%s' and '%s'", cp, dp)); + +#ifdef HAVE_FNMATCH + if (use_glob) + return fnmatch(cp, dp, 0) == 0; +#endif + if (type & SRC_DIR && + dp[0] == 's' && dp[1] == '.' && filename_equal(cp, dp + 2, type)) + return 1; + + i = strlen(dp); + + if (type & MAN_DIR) { + if (i > 1 && !strcmp(dp + i - 2, ".Z")) + i -= 2; + else if (i > 2 && !strcmp(dp + i - 3, ".gz")) + i -= 3; + else if (i > 2 && !strcmp(dp + i - 3, ".xz")) + i -= 3; + else if (i > 3 && !strcmp(dp + i - 4, ".bz2")) + i -= 4; + else if (i > 3 && !strcmp(dp + i - 4, ".zst")) + i -= 4; + } + while (*cp && *dp && *cp == *dp) + cp++, dp++, i--; + if (*cp == 0 && *dp == 0) + return 1; + if (!(type & BIN_DIR) && *cp == 0 && *dp++ == '.') { + --i; + while (i > 0 && *dp) + if (--i, *dp++ == '.') + return (*dp++ == 'C' && *dp++ == 0); + return 1; + } + return 0; +} + +static void findin(const char *dir, const char *pattern, int *count, + char **wait, int type) +{ + DIR *dirp; + struct dirent *dp; + + dirp = opendir(dir); + if (dirp == NULL) + return; + + DBG(SEARCH, ul_debug("find '%s' in '%s'", pattern, dir)); + + while ((dp = readdir(dirp)) != NULL) { + if (!filename_equal(pattern, dp->d_name, type)) + continue; + + if (uflag && *count == 0) + xasprintf(wait, "%s/%s", dir, dp->d_name); + + else if (uflag && *count == 1 && *wait) { + printf("%s: %s %s/%s", pattern, *wait, dir, dp->d_name); + free(*wait); + *wait = NULL; + } else + printf(" %s/%s", dir, dp->d_name); + ++(*count); + } + closedir(dirp); +} + +static void lookup(const char *pattern, struct wh_dirlist *ls, int want) +{ + char patbuf[PATH_MAX]; + int count = 0; + char *wait = NULL, *p; + + /* canonicalize pattern -- remove path suffix etc. */ + p = strrchr(pattern, '/'); + p = p ? p + 1 : (char *) pattern; + xstrncpy(patbuf, p, PATH_MAX); + + DBG(SEARCH, ul_debug("lookup dirs for '%s' (%s), want: %s %s %s", + patbuf, pattern, + want & BIN_DIR ? "bin" : "", + want & MAN_DIR ? "man" : "", + want & SRC_DIR ? "src" : "")); + + if (!uflag) + /* if -u not specified then we always print the pattern */ + printf("%s:", patbuf); + + for (; ls; ls = ls->next) { + if ((ls->type & want) && ls->path) + findin(ls->path, patbuf, &count, &wait, ls->type); + } + + free(wait); + + if (!uflag || count > 1) + putchar('\n'); +} + +static void list_dirlist(struct wh_dirlist *ls) +{ + while (ls) { + if (ls->path) { + switch (ls->type) { + case BIN_DIR: + printf("bin: "); + break; + case MAN_DIR: + printf("man: "); + break; + case SRC_DIR: + printf("src: "); + break; + default: + abort(); + } + printf("%s\n", ls->path); + } + ls = ls->next; + } +} + +int main(int argc, char **argv) +{ + struct wh_dirlist *ls = NULL; + int want = ALL_DIRS; + int i, want_resetable = 0, opt_f_missing = 0; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + if (argc <= 1) { + warnx(_("not enough arguments")); + errtryhelp(EXIT_FAILURE); + } else { + /* first arg may be one of our standard longopts */ + if (!strcmp(argv[1], "--help")) + usage(); + if (!strcmp(argv[1], "--version")) + print_version(EXIT_SUCCESS); + } + + whereis_init_debug(); + + construct_dirlist(&ls, BIN_DIR, bindirs); + construct_dirlist_from_env("PATH", &ls, BIN_DIR); + + construct_dirlist(&ls, MAN_DIR, mandirs); + construct_dirlist_from_env("MANPATH", &ls, MAN_DIR); + + construct_dirlist(&ls, SRC_DIR, srcdirs); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + int arg_i = i; + + DBG(ARGV, ul_debug("argv[%d]: %s", i, arg)); + + if (*arg != '-') { + lookup(arg, ls, want); + /* + * The lookup mask ("want") is cumulative and it's + * resettable only when it has been already used. + * + * whereis -b -m foo :'foo' mask=BIN|MAN + * whereis -b foo bar :'foo' and 'bar' mask=BIN|MAN + * whereis -b foo -m bar :'foo' mask=BIN; 'bar' mask=MAN + */ + want_resetable = 1; + continue; + } + + for (++arg; arg && *arg; arg++) { + DBG(ARGV, ul_debug(" arg: %s", arg)); + + switch (*arg) { + case 'f': + opt_f_missing = 0; + break; + case 'u': + uflag = 1; + opt_f_missing = 0; + break; + case 'B': + if (*(arg + 1)) { + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + i++; + free_dirlist(&ls, BIN_DIR); + construct_dirlist_from_argv( + &ls, &i, argc, argv, BIN_DIR); + opt_f_missing = 1; + break; + case 'M': + if (*(arg + 1)) { + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + i++; + free_dirlist(&ls, MAN_DIR); + construct_dirlist_from_argv( + &ls, &i, argc, argv, MAN_DIR); + opt_f_missing = 1; + break; + case 'S': + if (*(arg + 1)) { + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + i++; + free_dirlist(&ls, SRC_DIR); + construct_dirlist_from_argv( + &ls, &i, argc, argv, SRC_DIR); + opt_f_missing = 1; + break; + case 'b': + if (want_resetable) { + want = ALL_DIRS; + want_resetable = 0; + } + want = want == ALL_DIRS ? BIN_DIR : want | BIN_DIR; + opt_f_missing = 0; + break; + case 'm': + if (want_resetable) { + want = ALL_DIRS; + want_resetable = 0; + } + want = want == ALL_DIRS ? MAN_DIR : want | MAN_DIR; + opt_f_missing = 0; + break; + case 's': + if (want_resetable) { + want = ALL_DIRS; + want_resetable = 0; + } + want = want == ALL_DIRS ? SRC_DIR : want | SRC_DIR; + opt_f_missing = 0; + break; + case 'l': + list_dirlist(ls); + break; + case 'g': + use_glob = 1; + break; + + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + + if (arg_i < i) /* moved to the next argv[] item */ + break; + } + } + + free_dirlist(&ls, ALL_DIRS); + if (opt_f_missing) + errx(EXIT_FAILURE, _("option -f is missing")); + return EXIT_SUCCESS; +} diff --git a/misc-utils/wipefs.8 b/misc-utils/wipefs.8 new file mode 100644 index 0000000..6323872 --- /dev/null +++ b/misc-utils/wipefs.8 @@ -0,0 +1,166 @@ +'\" t +.\" Title: wipefs +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "WIPEFS" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.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" +wipefs \- wipe a signature from a device +.SH "SYNOPSIS" +.sp +\fBwipefs\fP [options] \fIdevice\fP... +.sp +\fBwipefs\fP [\fB\-\-backup\fP] \fB\-o\fP \fIoffset device\fP... +.sp +\fBwipefs\fP [\fB\-\-backup\fP] \fB\-a\fP \fIdevice\fP... +.SH "DESCRIPTION" +.sp +\fBwipefs\fP can erase filesystem, raid or partition\-table signatures (magic strings) from the specified \fIdevice\fP to make the signatures invisible for libblkid. \fBwipefs\fP does not erase the filesystem itself nor any other data from the 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. +.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. +.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 +Note that by default \fBwipefs\fP does not erase nested partition tables on non\-whole disk devices. For this the option \fB\-\-force\fP is required. +.SH "OPTIONS" +.sp +\fB\-a\fP, \fB\-\-all\fP +.RS 4 +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 +.RS 4 +Create a signature backup to the file \fI$HOME/wipefs\-<devname>\-<offset>.bak\fP. For more details see the \fBEXAMPLE\fP section. +.RE +.sp +\fB\-f\fP, \fB\-\-force\fP +.RS 4 +Force erasure, even if the filesystem is mounted. This is required in order to erase a partition\-table signature on a block device. +.RE +.sp +\fB\-J\fP, \fB\-\-json\fP +.RS 4 +Use JSON output format. +.RE +.sp +\fB\-\-lock\fP[=\fImode\fP] +.RS 4 +Use exclusive BSD lock for device or file it operates. The optional argument \fImode\fP can be \fByes\fP, \fBno\fP (or 1 and 0) or \fBnonblock\fP. If the \fImode\fP argument is omitted, it defaults to \fB"yes"\fP. This option overwrites environment variable \fB$LOCK_BLOCK_DEVICE\fP. The default is not to use any lock at all, but it\(cqs recommended to avoid collisions with udevd or other tools. +.RE +.sp +\fB\-i\fP, \fB\-\-noheadings\fP +.RS 4 +Do not print a header line. +.RE +.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. +.RE +.sp +\fB\-n\fP, \fB\-\-no\-act\fP +.RS 4 +Causes everything to be done except for the \fBwrite\fP(2) call. +.RE +.sp +\fB\-o\fP, \fB\-\-offset\fP \fIoffset\fP +.RS 4 +Specify the location (in bytes) of the signature which should be erased from the device. The \fIoffset\fP number may include a "0x" prefix; then the number will be interpreted as a hex value. It is possible to specify multiple \fB\-o\fP options. +.sp +The \fIoffset\fP argument may be followed by the multiplicative suffixes KiB (=1024), MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB and YiB (the "iB" is optional, e.g., "K" has the same meaning as "KiB"), or the suffixes KB (=1000), MB (=1000*1000), and so on for GB, TB, PB, EB, ZB and YB. +.RE +.sp +\fB\-p\fP, \fB\-\-parsable\fP +.RS 4 +Print out in parsable instead of printable format. Encode all potentially unsafe characters of a string to the corresponding hex value prefixed by \*(Aq\(rsx\*(Aq. +.RE +.sp +\fB\-q\fP, \fB\-\-quiet\fP +.RS 4 +Suppress any messages after a successful signature wipe. +.RE +.sp +\fB\-t\fP, \fB\-\-types\fP \fIlist\fP +.RS 4 +Limit the set of printed or erased signatures. More than one type may be specified in a comma\-separated list. The list or individual types can be prefixed with \*(Aqno\*(Aq to specify the types on which no action should be taken. For more details see \fBmount\fP(8). +.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 "ENVIRONMENT" +.sp +LIBBLKID_DEBUG=all +.RS 4 +enables \fBlibblkid\fP(3) debug output. +.RE +.sp +LOCK_BLOCK_DEVICE=<mode> +.RS 4 +use exclusive BSD lock. The mode is "1" or "0". See \fB\-\-lock\fP for more details. +.RE +.SH "EXAMPLES" +.sp +\fBwipefs /dev/sda\fP* +.RS 4 +Prints information about sda and all partitions on sda. +.RE +.sp +\fBwipefs \-\-all \-\-backup /dev/sdb\fP +.RS 4 +Erases all signatures from the device \fI/dev/sdb\fP and creates a signature backup file \fI~/wipefs\-sdb\-<offset>.bak\fP for each signature. +.RE +.sp +\fBdd if=~/wipefs\-sdb\-0x00000438.bak of=/dev/sdb seek=$((0x00000438)) bs=1 conv=notrunc\fP +.RS 4 +Restores an ext2 signature from the backup file \fI~/wipefs\-sdb\-0x00000438.bak\fP. +.RE +.SH "AUTHORS" +.sp +.MTO "kzak\(atredhat.com" "Karel Zak" "" +.SH "SEE ALSO" +.sp +\fBblkid\fP(8), +\fBfindfs\fP(8) +.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 \fBwipefs\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/wipefs.8.adoc b/misc-utils/wipefs.8.adoc new file mode 100644 index 0000000..d53b768 --- /dev/null +++ b/misc-utils/wipefs.8.adoc @@ -0,0 +1,114 @@ +//po4a: entry man manual +// Copyright 2009 by Karel Zak. All Rights Reserved. +// This file may be copied under the terms of the GNU Public License. += wipefs(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: wipefs + +== NAME + +wipefs - wipe a signature from a device + +== SYNOPSIS + +*wipefs* [options] _device_... + +*wipefs* [*--backup*] *-o* _offset device_... + +*wipefs* [*--backup*] *-a* _device_... + + +== DESCRIPTION + +*wipefs* can erase filesystem, raid or partition-table signatures (magic strings) from the specified _device_ to make the signatures invisible for libblkid. *wipefs* does not erase the filesystem itself nor any other data from the 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*. + +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. + +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. + +Note that by default *wipefs* does not erase nested partition tables on non-whole disk devices. For this the option *--force* is required. + +== OPTIONS + +*-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. + +*-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. + +*-J*, *--json*:: +Use JSON output format. + +*--lock*[=_mode_]:: +Use exclusive BSD lock for device or file it operates. The optional argument _mode_ can be *yes*, *no* (or 1 and 0) or *nonblock*. If the _mode_ argument is omitted, it defaults to *"yes"*. This option overwrites environment variable *$LOCK_BLOCK_DEVICE*. The default is not to use any lock at all, but it's recommended to avoid collisions with udevd or other tools. + +*-i*, *--noheadings*:: +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. + +*-n*, *--no-act*:: +Causes everything to be done except for the *write*(2) call. + +*-o*, *--offset* _offset_:: +Specify the location (in bytes) of the signature which should be erased from the device. The _offset_ number may include a "0x" prefix; then the number will be interpreted as a hex value. It is possible to specify multiple *-o* options. ++ +The _offset_ argument may be followed by the multiplicative suffixes KiB (=1024), MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB and YiB (the "iB" is optional, e.g., "K" has the same meaning as "KiB"), or the suffixes KB (=1000), MB (=1000*1000), and so on for GB, TB, PB, EB, ZB and YB. + +*-p*, *--parsable*:: +Print out in parsable instead of printable format. Encode all potentially unsafe characters of a string to the corresponding hex value prefixed by '\x'. + +*-q*, *--quiet*:: +Suppress any messages after a successful signature wipe. + +*-t*, *--types* _list_:: +Limit the set of printed or erased signatures. More than one type may be specified in a comma-separated list. The list or individual types can be prefixed with 'no' to specify the types on which no action should be taken. For more details see *mount*(8). + +include::man-common/help-version.adoc[] + +== ENVIRONMENT + +LIBBLKID_DEBUG=all:: +enables *libblkid*(3) debug output. + +LOCK_BLOCK_DEVICE=<mode>:: +use exclusive BSD lock. The mode is "1" or "0". See *--lock* for more details. + +== EXAMPLES + +*wipefs /dev/sda**:: +Prints information about sda and all partitions on sda. + +*wipefs --all --backup /dev/sdb*:: +Erases all signatures from the device _/dev/sdb_ and creates a signature backup file _~/wipefs-sdb-<offset>.bak_ for each signature. + +*dd if=~/wipefs-sdb-0x00000438.bak of=/dev/sdb seek=$\((0x00000438)) bs=1 conv=notrunc*:: +Restores an ext2 signature from the backup file _~/wipefs-sdb-0x00000438.bak_. + +== AUTHORS + +mailto:kzak@redhat.com[Karel Zak] + +== SEE ALSO + +*blkid*(8), +*findfs*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/wipefs.c b/misc-utils/wipefs.c new file mode 100644 index 0000000..6be470b --- /dev/null +++ b/misc-utils/wipefs.c @@ -0,0 +1,837 @@ +/* + * wipefs - utility to wipe filesystems from device + * + * Copyright (C) 2009 Red Hat, Inc. All rights reserved. + * Written by Karel Zak <kzak@redhat.com> + * + * 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 <sys/stat.h> +#include <sys/types.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <string.h> +#include <limits.h> +#include <libgen.h> + +#include <blkid.h> +#include <libsmartcols.h> + +#include "nls.h" +#include "xalloc.h" +#include "strutils.h" +#include "all-io.h" +#include "match.h" +#include "c.h" +#include "closestream.h" +#include "optutils.h" +#include "blkdev.h" + +struct wipe_desc { + loff_t offset; /* magic string offset */ + size_t len; /* length of magic string */ + unsigned char *magic; /* magic string */ + + char *usage; /* raid, filesystem, ... */ + char *type; /* FS type */ + char *label; /* FS label */ + char *uuid; /* FS uuid */ + + struct wipe_desc *next; + + unsigned int on_disk : 1, + is_parttable : 1; + +}; + +struct wipe_control { + char *devname; + const char *type_pattern; /* -t <pattern> */ + const char *lockmode; + + struct libscols_table *outtab; + struct wipe_desc *offsets; /* -o <offset> -o <offset> ... */ + + size_t ndevs; /* number of devices to probe */ + + char **reread; /* devices to BLKRRPART */ + size_t nrereads; /* size of reread */ + + unsigned int noact : 1, + all : 1, + quiet : 1, + backup : 1, + force : 1, + json : 1, + no_headings : 1, + parsable : 1; +}; + + +/* column IDs */ +enum { + COL_UUID = 0, + COL_LABEL, + COL_LEN, + COL_TYPE, + COL_OFFSET, + COL_USAGE, + COL_DEVICE +}; + +/* 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; +}; + +/* columns descriptions */ +static const struct colinfo infos[] = { + [COL_UUID] = {"UUID", 4, 0, N_("partition/filesystem UUID")}, + [COL_LABEL] = {"LABEL", 5, 0, N_("filesystem LABEL")}, + [COL_LEN] = {"LENGTH", 6, 0, N_("magic string length")}, + [COL_TYPE] = {"TYPE", 4, 0, N_("superblok type")}, + [COL_OFFSET] = {"OFFSET", 5, 0, N_("magic string offset")}, + [COL_USAGE] = {"USAGE", 5, 0, N_("type description")}, + [COL_DEVICE] = {"DEVICE", 5, 0, N_("block device name")} +}; + +static int columns[ARRAY_SIZE(infos) * 2]; +static size_t ncolumns; + +static int column_name_to_id(const char *name, size_t namesz) +{ + size_t i; + + assert(name); + + 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 int get_column_id(size_t num) +{ + assert(num < ncolumns); + assert(columns[num] < (int)ARRAY_SIZE(infos)); + return columns[num]; +} + +static const struct colinfo *get_column_info(int num) +{ + return &infos[get_column_id(num)]; +} + + +static void init_output(struct wipe_control *ctl) +{ + struct libscols_table *tb; + size_t i; + + scols_init_debug(0); + tb = scols_new_table(); + if (!tb) + err(EXIT_FAILURE, _("failed to allocate output table")); + + if (ctl->json) { + scols_table_enable_json(tb, 1); + scols_table_set_name(tb, "signatures"); + } + scols_table_enable_noheadings(tb, ctl->no_headings); + + if (ctl->parsable) { + scols_table_enable_raw(tb, 1); + scols_table_set_column_separator(tb, ","); + } + + for (i = 0; i < ncolumns; i++) { + const struct colinfo *col = get_column_info(i); + struct libscols_column *cl; + + cl = scols_table_new_column(tb, col->name, col->whint, + col->flags); + if (!cl) + err(EXIT_FAILURE, + _("failed to initialize output column")); + if (ctl->json) { + int id = get_column_id(i); + + if (id == COL_LEN) + scols_column_set_json_type(cl, SCOLS_JSON_NUMBER); + } + } + ctl->outtab = tb; +} + +static void finalize_output(struct wipe_control *ctl) +{ + scols_print_table(ctl->outtab); + scols_unref_table(ctl->outtab); +} + +static void fill_table_row(struct wipe_control *ctl, struct wipe_desc *wp) +{ + static struct libscols_line *ln; + size_t i; + + ln = scols_table_new_line(ctl->outtab, NULL); + if (!ln) + errx(EXIT_FAILURE, _("failed to allocate output line")); + + for (i = 0; i < ncolumns; i++) { + char *str = NULL; + + switch (get_column_id(i)) { + case COL_UUID: + if (wp->uuid) + str = xstrdup(wp->uuid); + break; + case COL_LABEL: + if (wp->label) + str = xstrdup(wp->label); + break; + case COL_OFFSET: + xasprintf(&str, "0x%jx", (intmax_t)wp->offset); + break; + case COL_LEN: + xasprintf(&str, "%zu", wp->len); + break; + case COL_USAGE: + if (wp->usage) + str = xstrdup(wp->usage); + break; + case COL_TYPE: + if (wp->type) + str = xstrdup(wp->type); + break; + case COL_DEVICE: + if (ctl->devname) { + char *dev = xstrdup(ctl->devname); + str = xstrdup(basename(dev)); + free(dev); + } + break; + default: + abort(); + } + + if (str && scols_line_refer_data(ln, i, str)) + errx(EXIT_FAILURE, _("failed to add output data")); + } +} + +static void add_to_output(struct wipe_control *ctl, struct wipe_desc *wp) +{ + for (/*nothing*/; wp; wp = wp->next) + fill_table_row(ctl, wp); +} + +/* Allocates a new wipe_desc and add to the wp0 if not NULL */ +static struct wipe_desc *add_offset(struct wipe_desc **wp0, loff_t offset) +{ + struct wipe_desc *wp, *last = NULL; + + if (wp0) { + /* check if already exists */ + for (wp = *wp0; wp; wp = wp->next) { + if (wp->offset == offset) + return wp; + last = wp; + } + } + + wp = xcalloc(1, sizeof(struct wipe_desc)); + wp->offset = offset; + wp->next = NULL; + + if (last) + last->next = wp; + if (wp0 && !*wp0) + *wp0 = wp; + return wp; +} + +/* Read data from libblkid and if detected type pass -t and -o filters than: + * - allocates a new wipe_desc + * - add the new wipe_desc to wp0 list (if not NULL) + * + * The function always returns offset and len if libblkid detected something. + */ +static struct wipe_desc *get_desc_for_probe(struct wipe_control *ctl, + struct wipe_desc **wp0, + blkid_probe pr, + loff_t *offset, + size_t *len) +{ + const char *off, *type, *mag, *p, *use = NULL; + struct wipe_desc *wp; + int rc, ispt = 0; + + *len = 0; + + /* superblocks */ + if (blkid_probe_lookup_value(pr, "TYPE", &type, NULL) == 0) { + rc = blkid_probe_lookup_value(pr, "SBMAGIC_OFFSET", &off, NULL); + if (!rc) + rc = blkid_probe_lookup_value(pr, "SBMAGIC", &mag, len); + if (rc) + return NULL; + + /* partitions */ + } else if (blkid_probe_lookup_value(pr, "PTTYPE", &type, NULL) == 0) { + rc = blkid_probe_lookup_value(pr, "PTMAGIC_OFFSET", &off, NULL); + if (!rc) + rc = blkid_probe_lookup_value(pr, "PTMAGIC", &mag, len); + if (rc) + return NULL; + use = N_("partition-table"); + ispt = 1; + } else + return NULL; + + errno = 0; + *offset = strtoll(off, NULL, 10); + if (errno) + return NULL; + + /* Filter out by -t <type> */ + if (ctl->type_pattern && !match_fstype(type, ctl->type_pattern)) + return NULL; + + /* Filter out by -o <offset> */ + if (ctl->offsets) { + struct wipe_desc *w = NULL; + + for (w = ctl->offsets; w; w = w->next) { + if (w->offset == *offset) + break; + } + if (!w) + return NULL; + + w->on_disk = 1; /* mark as "found" */ + } + + wp = add_offset(wp0, *offset); + if (!wp) + return NULL; + + if (use || blkid_probe_lookup_value(pr, "USAGE", &use, NULL) == 0) + wp->usage = xstrdup(use); + + wp->type = xstrdup(type); + wp->on_disk = 1; + wp->is_parttable = ispt ? 1 : 0; + + wp->magic = xmalloc(*len); + memcpy(wp->magic, mag, *len); + wp->len = *len; + + if (blkid_probe_lookup_value(pr, "LABEL", &p, NULL) == 0) + wp->label = xstrdup(p); + + if (blkid_probe_lookup_value(pr, "UUID", &p, NULL) == 0) + wp->uuid = xstrdup(p); + + return wp; +} + +static blkid_probe +new_probe(const char *devname, int mode) +{ + blkid_probe pr = NULL; + + if (!devname) + return NULL; + + if (mode) { + int fd = open(devname, mode | O_NONBLOCK); + if (fd < 0) + goto error; + + pr = blkid_new_probe(); + if (!pr || blkid_probe_set_device(pr, fd, 0, 0) != 0) { + close(fd); + goto error; + } + } else + pr = blkid_new_probe_from_filename(devname); + + if (!pr) + goto error; + + blkid_probe_enable_superblocks(pr, 1); + blkid_probe_set_superblocks_flags(pr, + BLKID_SUBLKS_MAGIC | /* return magic string and offset */ + BLKID_SUBLKS_TYPE | /* return superblock type */ + BLKID_SUBLKS_USAGE | /* return USAGE= */ + BLKID_SUBLKS_LABEL | /* return LABEL= */ + BLKID_SUBLKS_UUID | /* return UUID= */ + BLKID_SUBLKS_BADCSUM); /* accept bad checksums */ + + blkid_probe_enable_partitions(pr, 1); + blkid_probe_set_partitions_flags(pr, BLKID_PARTS_MAGIC | + BLKID_PARTS_FORCE_GPT); + return pr; +error: + blkid_free_probe(pr); + err(EXIT_FAILURE, _("error: %s: probing initialization failed"), devname); +} + +static struct wipe_desc *read_offsets(struct wipe_control *ctl) +{ + blkid_probe pr = new_probe(ctl->devname, 0); + struct wipe_desc *wp0 = NULL; + + if (!pr) + return NULL; + + while (blkid_do_probe(pr) == 0) { + size_t len = 0; + loff_t offset = 0; + + /* add a new offset to wp0 */ + get_desc_for_probe(ctl, &wp0, pr, &offset, &len); + + /* hide last detected signature and scan again */ + if (len) { + blkid_probe_hide_range(pr, offset, len); + blkid_probe_step_back(pr); + } + } + + blkid_free_probe(pr); + return wp0; +} + +static void free_wipe(struct wipe_desc *wp) +{ + while (wp) { + struct wipe_desc *next = wp->next; + + free(wp->usage); + free(wp->type); + free(wp->magic); + free(wp->label); + free(wp->uuid); + free(wp); + + wp = next; + } +} + +static void do_wipe_real(struct wipe_control *ctl, blkid_probe pr, + struct wipe_desc *w) +{ + size_t i; + + if (blkid_do_wipe(pr, ctl->noact) != 0) + err(EXIT_FAILURE, _("%s: failed to erase %s magic string at offset 0x%08jx"), + ctl->devname, w->type, (intmax_t)w->offset); + + if (ctl->quiet) + return; + + printf(P_("%s: %zd byte was erased at offset 0x%08jx (%s): ", + "%s: %zd bytes were erased at offset 0x%08jx (%s): ", + w->len), + ctl->devname, w->len, (intmax_t)w->offset, w->type); + + for (i = 0; i < w->len; i++) { + printf("%02x", w->magic[i]); + if (i + 1 < w->len) + fputc(' ', stdout); + } + putchar('\n'); +} + +static void do_backup(struct wipe_desc *wp, const char *base) +{ + char *fname = NULL; + int fd; + + xasprintf(&fname, "%s0x%08jx.bak", base, (intmax_t)wp->offset); + + fd = open(fname, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); + if (fd < 0) + goto err; + if (write_all(fd, wp->magic, wp->len) != 0) + goto err; + close(fd); + free(fname); + return; +err: + err(EXIT_FAILURE, _("%s: failed to create a signature backup"), fname); +} + +#ifdef BLKRRPART +static void rereadpt(int fd, const char *devname) +{ + struct stat st; + int try = 0; + + if (fstat(fd, &st) || !S_ISBLK(st.st_mode)) + return; + + do { + /* + * Unfortunately, it's pretty common that the first re-read + * without delay is uncuccesful. The reason is probably kernel + * and/or udevd. Let's wait a moment and try more attempts. + */ + xusleep(250000); + + errno = 0; + ioctl(fd, BLKRRPART); + if (errno != EBUSY) + break; + } while (try++ < 4); + + printf(_("%s: calling ioctl to re-read partition table: %m\n"), devname); +} +#endif + +static int do_wipe(struct wipe_control *ctl) +{ + int mode = O_RDWR, reread = 0, need_force = 0; + blkid_probe pr; + char *backup = NULL; + struct wipe_desc *w; + + if (!ctl->force) + mode |= O_EXCL; + + pr = new_probe(ctl->devname, mode); + if (!pr) + return -errno; + + if (blkdev_lock(blkid_probe_get_fd(pr), + ctl->devname, ctl->lockmode) != 0) { + blkid_free_probe(pr); + return -1; + } + + 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)); + free(tmp); + } + + while (blkid_do_probe(pr) == 0) { + int wiped = 0; + size_t len = 0; + loff_t offset = 0; + struct wipe_desc *wp; + + wp = get_desc_for_probe(ctl, NULL, pr, &offset, &len); + if (!wp) + goto done; + + if (!ctl->force + && wp->is_parttable + && !blkid_probe_is_wholedisk(pr)) { + warnx(_("%s: ignoring nested \"%s\" partition table " + "on non-whole disk device"), ctl->devname, wp->type); + need_force = 1; + goto done; + } + + if (backup) + do_backup(wp, backup); + do_wipe_real(ctl, pr, wp); + if (wp->is_parttable) + reread = 1; + wiped = 1; + done: + if (!wiped && len) { + /* if the offset has not been wiped (probably because + * filtered out by -t or -o) we need to hide it for + * libblkid to try another magic string for the same + * superblock, otherwise libblkid will continue with + * another superblock. Don't forget that the same + * superblock could be detected by more magic strings + * */ + blkid_probe_hide_range(pr, offset, len); + blkid_probe_step_back(pr); + } + free_wipe(wp); + } + + for (w = ctl->offsets; w; w = w->next) { + if (!w->on_disk && !ctl->quiet) + warnx(_("%s: offset 0x%jx not found"), + ctl->devname, (uintmax_t)w->offset); + } + + if (need_force) + warnx(_("Use the --force option to force erase.")); + + if (fsync(blkid_probe_get_fd(pr)) != 0) + err(EXIT_FAILURE, _("%s: cannot flush modified buffers"), + ctl->devname); + +#ifdef BLKRRPART + if (reread && (mode & O_EXCL)) { + if (ctl->ndevs > 1) { + /* + * We're going to probe more device, let's postpone + * re-read PT ioctl until all is erased to avoid + * situation we erase PT on /dev/sda before /dev/sdaN + * devices are processed. + */ + if (!ctl->reread) + ctl->reread = xcalloc(ctl->ndevs, sizeof(char *)); + + ctl->reread[ctl->nrereads++] = ctl->devname; + } else + rereadpt(blkid_probe_get_fd(pr), ctl->devname); + } +#endif + + if (close(blkid_probe_get_fd(pr)) != 0) + err(EXIT_FAILURE, _("%s: close device failed"), ctl->devname); + + blkid_free_probe(pr); + free(backup); + return 0; +} + + +static void __attribute__((__noreturn__)) +usage(void) +{ + size_t i; + + fputs(USAGE_HEADER, stdout); + printf(_(" %s [options] <device>\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, stdout); + puts(_("Wipe signatures from a device.")); + + 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( + _(" --lock[=<mode>] use exclusive device lock (%s, %s or %s)\n"), "yes", "no", "nonblock"); + + printf(USAGE_HELP_OPTIONS(21)); + + fputs(USAGE_ARGUMENTS, stdout); + printf(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)")); + exit(EXIT_SUCCESS); +} + + +int +main(int argc, char **argv) +{ + struct wipe_control ctl = { .devname = NULL }; + int c; + size_t i; + char *outarg = NULL; + enum { + OPT_LOCK = CHAR_MAX + 1, + }; + static const struct option longopts[] = { + { "all", no_argument, NULL, 'a' }, + { "backup", no_argument, NULL, 'b' }, + { "force", no_argument, NULL, 'f' }, + { "help", no_argument, NULL, 'h' }, + { "lock", optional_argument, NULL, OPT_LOCK }, + { "no-act", no_argument, NULL, 'n' }, + { "offset", required_argument, NULL, 'o' }, + { "parsable", no_argument, NULL, 'p' }, + { "quiet", no_argument, NULL, 'q' }, + { "types", required_argument, NULL, 't' }, + { "version", no_argument, NULL, 'V' }, + { "json", no_argument, NULL, 'J'}, + { "noheadings",no_argument, NULL, 'i'}, + { "output", required_argument, NULL, 'O'}, + { NULL, 0, NULL, 0 } + }; + + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { 'O','a','o' }, + { 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, "abfhiJnO:o:pqt:V", longopts, NULL)) != -1) { + + err_exclusive_options(c, longopts, excl, excl_st); + + switch(c) { + case 'a': + ctl.all = 1; + break; + case 'b': + ctl.backup = 1; + break; + case 'f': + ctl.force = 1; + break; + case 'J': + ctl.json = 1; + break; + case 'i': + ctl.no_headings = 1; + break; + case 'O': + outarg = optarg; + break; + case 'n': + ctl.noact = 1; + break; + case 'o': + add_offset(&ctl.offsets, strtosize_or_err(optarg, + _("invalid offset argument"))); + break; + case 'p': + ctl.parsable = 1; + ctl.no_headings = 1; + break; + case 'q': + ctl.quiet = 1; + break; + case 't': + ctl.type_pattern = optarg; + break; + case OPT_LOCK: + ctl.lockmode = "1"; + if (optarg) { + if (*optarg == '=') + optarg++; + ctl.lockmode = optarg; + } + break; + case 'h': + usage(); + case 'V': + print_version(EXIT_SUCCESS); + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (optind == argc) { + warnx(_("no device specified")); + errtryhelp(EXIT_FAILURE); + + } + + if (ctl.backup && !(ctl.all || ctl.offsets)) + warnx(_("The --backup option is meaningless in this context")); + + if (!ctl.all && !ctl.offsets) { + /* + * Print only + */ + if (ctl.parsable) { + /* keep it backward compatible */ + columns[ncolumns++] = COL_OFFSET; + columns[ncolumns++] = COL_UUID; + columns[ncolumns++] = COL_LABEL; + columns[ncolumns++] = COL_TYPE; + } else { + /* default, may be modified by -O <list> */ + columns[ncolumns++] = COL_DEVICE; + columns[ncolumns++] = COL_OFFSET; + columns[ncolumns++] = COL_TYPE; + columns[ncolumns++] = COL_UUID; + columns[ncolumns++] = COL_LABEL; + } + + if (outarg + && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns), + &ncolumns, column_name_to_id) < 0) + return EXIT_FAILURE; + + init_output(&ctl); + + while (optind < argc) { + struct wipe_desc *wp; + + ctl.devname = argv[optind++]; + wp = read_offsets(&ctl); + if (wp) + add_to_output(&ctl, wp); + free_wipe(wp); + } + finalize_output(&ctl); + } else { + /* + * Erase + */ + ctl.ndevs = argc - optind; + + while (optind < argc) { + ctl.devname = argv[optind++]; + do_wipe(&ctl); + ctl.ndevs--; + } + +#ifdef BLKRRPART + /* Re-read partition tables on whole-disk devices. This is + * postponed until all is done to avoid conflicts. + */ + for (i = 0; i < ctl.nrereads; i++) { + char *devname = ctl.reread[i]; + int fd = open(devname, O_RDONLY); + + if (fd >= 0) { + rereadpt(fd, devname); + close(fd); + } + } + free(ctl.reread); +#endif + } + return EXIT_SUCCESS; +} |