summaryrefslogtreecommitdiffstats
path: root/misc-utils
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 13:14:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 13:14:44 +0000
commit30ff6afe596eddafacf22b1a5b2d1a3d6254ea15 (patch)
tree9b788335f92174baf7ee18f03ca8330b8c19ce2b /misc-utils
parentInitial commit. (diff)
downloadutil-linux-upstream.tar.xz
util-linux-upstream.zip
Adding upstream version 2.36.1.upstream/2.36.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'misc-utils')
-rw-r--r--misc-utils/Makemodule.am232
-rw-r--r--misc-utils/blkid.8343
-rw-r--r--misc-utils/blkid.c963
-rw-r--r--misc-utils/cal.1233
-rw-r--r--misc-utils/cal.c1314
-rw-r--r--misc-utils/fincore.162
-rw-r--r--misc-utils/fincore.c412
-rw-r--r--misc-utils/findfs.879
-rw-r--r--misc-utils/findfs.c79
-rw-r--r--misc-utils/findmnt-verify.c527
-rw-r--r--misc-utils/findmnt.8308
-rw-r--r--misc-utils/findmnt.c1732
-rw-r--r--misc-utils/findmnt.h41
-rw-r--r--misc-utils/getopt-parse.bash75
-rw-r--r--misc-utils/getopt-parse.tcsh77
-rw-r--r--misc-utils/getopt.1460
-rw-r--r--misc-utils/getopt.1.in460
-rw-r--r--misc-utils/getopt.c472
-rw-r--r--misc-utils/hardlink.169
-rw-r--r--misc-utils/hardlink.c531
-rw-r--r--misc-utils/kill.1240
-rw-r--r--misc-utils/kill.c513
-rw-r--r--misc-utils/logger.1383
-rw-r--r--misc-utils/logger.c1316
-rw-r--r--misc-utils/look.1123
-rw-r--r--misc-utils/look.c375
-rw-r--r--misc-utils/lsblk-devtree.c472
-rw-r--r--misc-utils/lsblk-mnt.c127
-rw-r--r--misc-utils/lsblk-properties.c381
-rw-r--r--misc-utils/lsblk.8215
-rw-r--r--misc-utils/lsblk.c2202
-rw-r--r--misc-utils/lsblk.h237
-rw-r--r--misc-utils/lslocks.8108
-rw-r--r--misc-utils/lslocks.c662
-rw-r--r--misc-utils/mcookie.169
-rw-r--r--misc-utils/mcookie.c200
-rw-r--r--misc-utils/namei.180
-rw-r--r--misc-utils/namei.c450
-rw-r--r--misc-utils/rename.1122
-rw-r--r--misc-utils/rename.c322
-rw-r--r--misc-utils/test_uuidd.c348
-rw-r--r--misc-utils/uuidd.894
-rw-r--r--misc-utils/uuidd.8.in94
-rw-r--r--misc-utils/uuidd.c714
-rw-r--r--misc-utils/uuidd.rc.in62
-rw-r--r--misc-utils/uuidd.service.in24
-rw-r--r--misc-utils/uuidd.socket.in8
-rw-r--r--misc-utils/uuidgen.1102
-rw-r--r--misc-utils/uuidgen.c209
-rw-r--r--misc-utils/uuidparse.178
-rw-r--r--misc-utils/uuidparse.c349
-rw-r--r--misc-utils/whereis.1170
-rw-r--r--misc-utils/whereis.c656
-rw-r--r--misc-utils/wipefs.8148
-rw-r--r--misc-utils/wipefs.c851
55 files changed, 20973 insertions, 0 deletions
diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am
new file mode 100644
index 0000000..56f9a41
--- /dev/null
+++ b/misc-utils/Makemodule.am
@@ -0,0 +1,232 @@
+if BUILD_CAL
+usrbin_exec_PROGRAMS += cal
+dist_man_MANS += misc-utils/cal.1
+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
+dist_man_MANS += misc-utils/logger.1
+logger_SOURCES = misc-utils/logger.c lib/strutils.c lib/strv.c
+logger_LDADD = $(LDADD)
+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
+dist_man_MANS += misc-utils/look.1
+look_SOURCES = misc-utils/look.c
+endif
+
+if BUILD_MCOOKIE
+usrbin_exec_PROGRAMS += mcookie
+dist_man_MANS += misc-utils/mcookie.1
+mcookie_SOURCES = misc-utils/mcookie.c lib/md5.c
+mcookie_LDADD = $(LDADD) libcommon.la
+endif
+
+if BUILD_NAMEI
+usrbin_exec_PROGRAMS += namei
+dist_man_MANS += misc-utils/namei.1
+namei_SOURCES = misc-utils/namei.c lib/strutils.c lib/idcache.c
+endif
+
+if BUILD_WHEREIS
+usrbin_exec_PROGRAMS += whereis
+dist_man_MANS += misc-utils/whereis.1
+whereis_SOURCES = misc-utils/whereis.c
+whereis_LDADD = $(LDADD) libcommon.la
+endif
+
+if BUILD_LSLOCKS
+usrbin_exec_PROGRAMS += lslocks
+dist_man_MANS += misc-utils/lslocks.8
+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
+dist_man_MANS += misc-utils/lsblk.8
+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
+dist_man_MANS += misc-utils/uuidgen.1
+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
+dist_man_MANS += misc-utils/uuidparse.1
+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
+dist_man_MANS += misc-utils/uuidd.8
+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.8 \
+ misc-utils/uuidd.rc \
+ misc-utils/uuidd.service \
+ misc-utils/uuidd.socket
+
+if BUILD_BLKID
+sbin_PROGRAMS += blkid
+dist_man_MANS += misc-utils/blkid.8
+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
+dist_man_MANS += misc-utils/findfs.8
+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
+dist_man_MANS += misc-utils/wipefs.8
+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
+dist_man_MANS += misc-utils/findmnt.8
+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
+kill_SOURCES = misc-utils/kill.c
+kill_LDADD = $(LDADD) libcommon.la
+dist_man_MANS += misc-utils/kill.1
+endif
+
+if BUILD_RENAME
+usrbin_exec_PROGRAMS += rename
+dist_man_MANS += misc-utils/rename.1
+rename_SOURCES = misc-utils/rename.c
+endif
+
+if BUILD_GETOPT
+usrbin_exec_PROGRAMS += getopt
+dist_man_MANS += misc-utils/getopt.1
+PATHFILES += misc-utils/getopt.1
+getopt_SOURCES = misc-utils/getopt.c
+getoptexampledir = $(docdir)/getopt/
+dist_getoptexample_SCRIPTS = \
+ misc-utils/getopt-parse.bash \
+ misc-utils/getopt-parse.tcsh
+endif
+
+if BUILD_FINCORE
+usrbin_exec_PROGRAMS += fincore
+dist_man_MANS += misc-utils/fincore.1
+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
+hardlink_SOURCES = misc-utils/hardlink.c
+hardlink_LDADD = $(LDADD) libcommon.la
+hardlink_CFLAGS = $(AM_CFLAGS)
+if HAVE_PCRE
+hardlink_LDADD += $(PCRE_LIBS)
+hardlink_CFLAGS += $(PCRE_CFLAGS)
+endif
+dist_man_MANS += misc-utils/hardlink.1
+endif
diff --git a/misc-utils/blkid.8 b/misc-utils/blkid.8
new file mode 100644
index 0000000..57a3b75
--- /dev/null
+++ b/misc-utils/blkid.8
@@ -0,0 +1,343 @@
+.\" Copyright 2000 Andreas Dilger (adilger@turbolinux.com)
+.\"
+.\" This file may be copied under the terms of the GNU Public License.
+.TH BLKID 8 "March 2013" "util-linux" "System Administration"
+.SH NAME
+blkid \- locate/print block device attributes
+.SH SYNOPSIS
+.IP \fBblkid\fR
+.BI \-\-label " label"
+|
+.BI \-\-uuid " uuid"
+
+.IP \fBblkid\fR
+.RB [ \-\-no\-encoding
+.B \-\-garbage\-collect \-\-list\-one \-\-cache\-file
+.IR file ]
+.RB [ \-\-output
+.IR format ]
+.RB [ \-\-match\-tag
+.IR tag ]
+.RB [ \-\-match\-token
+.IR NAME=value ]
+.RI [ device " ...]"
+
+.IP \fBblkid\fR
+.BR \-\-probe " [" \-\-offset
+.IR offset ]
+.RB [ \-\-output
+.IR format ]
+.RB [ \-\-size
+.IR size ]
+.RB [ \-\-match\-tag
+.IR tag ]
+.RB [ \-\-match\-types
+.IR list ]
+.RB [ \-\-usages
+.IR list ]
+.RB [ \-\-no\-part\-details ]
+.IR device " ..."
+
+.IP \fBblkid\fR
+.BR \-\-info " [" \-\-output
+.IR format ]
+.RB [ \-\-match\-tag
+.IR tag ]
+.IR device " ..."
+
+.SH DESCRIPTION
+The
+.B blkid
+program is the command-line interface to working with the
+.BR 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).
+.PP
+.B It is recommended to use
+.BR lsblk (8)
+.B command to get information about block devices, or lsblk \-\-fs to get an overview of filesystems, or
+.BR findmnt (8)
+.B to search in already mounted filesystems.
+.PP
+.RS
+.BR 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.
+.B blkid
+reads information directly from devices and for non-root users
+it returns cached unverified information.
+.B blkid
+is mostly designed for system services and to test libblkid functionality.
+.RE
+
+.PP
+When
+.I device
+is specified, tokens from only this device are displayed.
+It is possible to specify multiple
+.I device
+arguments on the command line.
+If none is given, all partitions or unpartitioned devices which appear in
+.I /proc/partitions
+are shown, if they are recognized.
+.PP
+.B 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
+.B blkid
+silently ignores all devices where the probing result is ambivalent (multiple
+colliding filesystems are detected). The low-level probing mode (\fB\-p\fR)
+provides more information and extra exit status in this case.
+It's recommended to use
+.BR wipefs (8)
+to get a detailed overview and to erase obsolete stuff (magic strings) from the device.
+
+.SH OPTIONS
+The \fIsize\fR and \fIoffset\fR 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.
+.TP
+\fB\-c\fR, \fB\-\-cache\-file\fR \fIcachefile\fR
+Read from
+.I 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
+.IR /dev/null .
+.TP
+\fB\-d\fR, \fB\-\-no\-encoding\fR
+Don't encode non-printing characters. The non-printing characters are encoded
+by ^ and M- notation by default. Note that the \fB\-\-output udev\fR output format uses
+a different encoding which cannot be disabled.
+.TP
+\fB\-D\fR, \fB\-\-no\-part\-details\fR
+Don't print information (PART_ENTRY_* tags) from partition table in low-level probing mode.
+.TP
+\fB\-g\fR, \fB\-\-garbage\-collect\fR
+Perform a garbage collection pass on the blkid cache to remove
+devices which no longer exist.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display a usage message and exit.
+.TP
+\fB\-i\fR, \fB\-\-info\fR
+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 \fB\-\-probe\fR option.
+.TP
+\fB\-k\fR, \fB\-\-list\-filesystems\fR
+List all known filesystems and RAIDs and exit.
+.TP
+\fB\-l\fR, \fB\-\-list\-one\fR
+Look up only one device that matches the search parameter specified with the \fB\-\-match\-token\fR
+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,
+.B blkid
+will print all of the devices that match the search parameter.
+.sp
+This option forces
+.B blkid
+to use udev when used for LABEL or UUID tokens in \fB\-\-match\-token\fR. The
+goal is to provide output consistent with other utils (like mount, etc.)
+on systems where the same tag is used for multiple devices.
+.TP
+\fB\-L\fR, \fB\-\-label\fR \fIlabel\fR
+Look up the device that uses this filesystem \fIlabel\fR; this is equal to
+.BR "\-\-list-one \-\-output device \-\-match-token LABEL=\fIlabel\fR" .
+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 \fB-\-label\fR option works on systems with and without udev.
+
+Unfortunately, the original
+.BR blkid (8)
+from e2fsprogs uses the \fB\-L\fR option as a
+synonym for \fB\-o list\fR. For better portability, use \fB\-l \-o device
+\-t LABEL=\fIlabel\fR and \fB\-o list\fR in your scripts rather than the \fB\-L\fR option.
+.TP
+\fB\-n\fR, \fB\-\-match\-types\fR \fIlist\fR
+Restrict the probing functions to the specified (comma-separated) \fIlist\fR of
+superblock types (names).
+The list items may be prefixed with "no" to specify the types which should be ignored.
+For example:
+.sp
+ blkid \-\-probe \-\-match-types vfat,ext3,ext4 /dev/sda1
+.sp
+probes for vfat, ext3 and ext4 filesystems, and
+.sp
+ blkid \-\-probe \-\-match-types nominix /dev/sda1
+.sp
+probes for all supported formats except minix filesystems.
+This option is only useful together with \fB\-\-probe\fR.
+.TP
+\fB\-o\fR, \fB\-\-output\fR \fIformat\fR
+Use the specified output format. Note that the order of variables and
+devices is not fixed. See also option \fB\-s\fR. The
+.I format
+parameter may be:
+.RS
+.TP
+.B full
+print all tags (the default)
+.TP
+.B value
+print the value of the tags
+.TP
+.B list
+print the devices in a user-friendly format; this output format is unsupported
+for low-level probing (\fB\-\-probe\fR or \fB\-\-info\fR).
+
+This output format is \fBDEPRECATED\fR in favour of the
+.BR lsblk (8)
+command.
+.TP
+.B device
+print the device name only; this output format is always enabled for the \fB\-\-label\fR
+and \fB\-\-uuid\fR options
+.TP
+.B udev
+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 '_'. 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 \fBDEPRECATED\fR.
+.TP
+.B export
+print key=value pairs for easy import into the environment; this output format
+is automatically enabled when I/O Limits (\fB\-\-info\fR option) are requested.
+
+The non-printing characters are encoded by ^ and M- notation and all
+potentially unsafe characters are escaped.
+.RE
+.TP
+\fB\-O\fR, \fB\-\-offset\fR \fIoffset\fR
+Probe at the given \fIoffset\fR (only useful with \fB\-\-probe\fR). This option can be
+used together with the \fB\-\-info\fR option.
+.TP
+\fB\-p\fR, \fB\-\-probe\fR
+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 \fB\-\-probe\fR (for example PART_ENTRY_UUID= vs
+PARTUUID=). See also \fB\-\-no\-part\-details\fR.
+.TP
+\fB\-s\fR, \fB\-\-match\-tag\fR \fItag\fR
+For each (specified) device, show only the tags that match
+.IR tag .
+It is possible to specify multiple
+.B \-\-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
+.B "\-\-match\-tag none"
+with no other options.
+.TP
+\fB\-S\fR, \fB\-\-size\fR \fIsize\fR
+Override the size of device/file (only useful with \fB\-\-probe\fR).
+.TP
+\fB\-t\fR, \fB\-\-match\-token\fR \fINAME=value\fR
+Search for block devices with tokens named
+.I NAME
+that have the value
+.IR value ,
+and display any devices which are found.
+Common values for
+.I NAME
+include
+.BR TYPE ,
+.BR LABEL ,
+and
+.BR UUID .
+If there are no devices specified on the command line, all block devices
+will be searched; otherwise only the specified devices are searched.
+.TP
+\fB\-u\fR, \fB\-\-usages\fR \fIlist\fR
+Restrict the probing functions to the specified (comma-separated) \fIlist\fR 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
+ blkid \-\-probe \-\-usages filesystem,other /dev/sda1
+.sp
+probes for all filesystem and other (e.g., swap) formats, and
+.sp
+ blkid \-\-probe \-\-usages noraid /dev/sda1
+.sp
+probes for all supported formats except RAIDs.
+This option is only useful together with \fB\-\-probe\fR.
+.TP
+\fB\-U\fR, \fB\-\-uuid\fR \fIuuid\fR
+Look up the device that uses this filesystem \fIuuid\fR. For more details see the
+\fB\-\-label\fR option.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version number and exit.
+.SH EXIT STATUS
+If the specified device or device addressed by specified token (option
+\fB\-\-match\-token\fR) was found and it's possible to gather any information about the
+device, an exit status 0 is returned. Note the option \fB\-\-match\-tag\fR 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 (\fB\-p\fR), an exit status of 8 is
+returned.
+.SH CONFIGURATION FILE
+The standard location of the
+.I /etc/blkid.conf
+config file can be overridden by the environment variable BLKID_CONF.
+The following options control the libblkid library:
+.TP
+.I SEND_UEVENT=<yes|not>
+Sends uevent when
+.I /dev/disk/by-{label,uuid,partuuid,partlabel}/
+symlink does not match with LABEL, UUID, PARTUUID or PARTLABEL on the device. Default is "yes".
+.TP
+.I CACHE_FILE=<path>
+Overrides the standard location of the cache file. This setting can be
+overridden by the environment variable BLKID_FILE. Default is
+.IR /run/blkid/blkid.tab ,
+or
+.I /etc/blkid.tab
+on systems without a /run directory.
+.TP
+.I 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
+.I /dev/disk/by-*
+symlinks and the "scan" method scans all block devices from the
+.I /proc/partitions
+file.
+.SH ENVIRONMENT
+.IP "Setting LIBBLKID_DEBUG=all enables debug output."
+.SH AUTHORS
+.B blkid
+was written by Andreas Dilger for libblkid and improved by Theodore Ts'o
+and Karel Zak.
+.SH SEE ALSO
+.BR libblkid (3),
+.BR findfs (8),
+.BR lsblk (8),
+.BR wipefs (8)
+.SH AVAILABILITY
+The blkid command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/misc-utils/blkid.c b/misc-utils/blkid.c
new file mode 100644
index 0000000..dba3bb7
--- /dev/null
+++ b/misc-utils/blkid.c
@@ -0,0 +1,963 @@
+/*
+ * 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"
+
+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(_( " -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")) {
+ 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) {
+ memcpy(str, a, asz);
+ str += asz;
+ }
+ if (b) {
+ memcpy(str, b, bsz);
+ str += 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;
+ 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' },
+ { "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:DdghilL:n:ko:O:ps:S:t:u:U:w:Vv", longopts, NULL)) != -1) {
+
+ err_exclusive_options(c, NULL, 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 '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)
+ devices[numdev++] = argv[optind++];
+ }
+
+ /* 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 (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);
+
+
+ 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..2115e9a
--- /dev/null
+++ b/misc-utils/cal.1
@@ -0,0 +1,233 @@
+.\" 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
+.\"
+.TH CAL 1 "January 2018" "util-linux" "User Commands"
+.SH NAME
+cal \- display a calendar
+.SH SYNOPSIS
+.B cal
+[options]
+.RI [[[ day ] " month" ] " year" ]
+.br
+.B cal
+[options]
+.RI [ "timestamp" | "monthname" ]
+.SH DESCRIPTION
+.B cal
+displays a simple calendar. If no arguments are specified, the current
+month is displayed.
+.sp
+The \fImonth\fR 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
+.B 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).
+.sp
+Optionally, either the proleptic Gregorian calendar or the Julian calendar may
+be used exclusively.
+.RB See\ \-\-reform\ below.
+.SH OPTIONS
+.TP
+\fB\-1\fR, \fB\-\-one\fR
+Display single month output.
+(This is the default.)
+.TP
+\fB\-3\fR, \fB\-\-three\fR
+Display three months spanning the date.
+.TP
+\fB\-n , \-\-months\fR \fInumber\fR
+Display \fInumber\fR of months, starting from the month containing the date.
+.TP
+\fB\-S, \fB\-\-span\fR
+Display months spanning the date.
+.TP
+\fB\-s\fR, \fB\-\-sunday\fR
+Display Sunday as the first day of the week.
+.TP
+\fB\-m\fR, \fB\-\-monday\fR
+Display Monday as the first day of the week.
+.TP
+\fB\-v\fR, \fB\-\-vertical\fR
+Display using a vertical layout (aka ncal mode).
+.TP
+.B \-\-iso
+Display the proleptic Gregorian calendar exclusively. This option does not affect
+week numbers and the first day of the week.
+.RB See\ \-\-reform\ below.
+.TP
+\fB\-j\fR, \fB\-\-julian\fR
+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
+.BR \-\-reform\ 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
+.B cal
+also uses the Julian calendar system.
+.RB See\ DESCRIPTION\ above.
+.TP
+.BI \-\-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
+.I val
+can be:
+.RS
+.IP \(bu 2
+.I 1752
+- sets 3 September 1752 as the reform date (default).
+This is when the Gregorian calendar reform was adopted by the British Empire.
+.IP \(bu 2
+.I gregorian
+- display Gregorian calendars exclusively. This special placeholder sets the
+reform date below the smallest year that
+.B 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.
+.IP \(bu 2
+.I iso
+- alias of
+.IR gregorian .
+The ISO 8601 standard for the representation of dates and times in information
+interchange requires using the proleptic Gregorian calendar.
+.IP \(bu 2
+.I julian
+- display Julian calendars exclusively. This special placeholder sets the reform date above the largest year that
+.B cal
+can use; meaning all
+calendar output uses the Julian calendar system.
+.PP
+.RB See\ \%DESCRIPTION\ above.
+.RE
+.TP
+\fB\-y\fR, \fB\-\-year\fR
+Display a calendar for the whole year.
+.TP
+\fB\-Y, \fB\-\-twelve\fR
+Display a calendar for the next twelve months.
+.TP
+\fB\-w\fR, \fB\-\-week\fR[=\fInumber\fR]
+Display week numbers in the calendar (US or ISO-8601). See NOTES section
+for more details.
+.TP
+\fB\-\-color\fR[=\fIwhen\fR]
+Colorize the output. The optional argument \fIwhen\fP
+can be \fBauto\fR, \fBnever\fR or \fBalways\fR. If the \fIwhen\fR argument is omitted,
+it defaults to \fBauto\fR. The colors can be disabled; for the current built-in default
+see the \fB\-\-help\fR output. See also the \fBCOLORS\fR section.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display help text and exit.
+.SH PARAMETERS
+.TP
+\fBSingle digits-only parameter (e.g., 'cal 2020')\fR
+Specifies the \fIyear\fR to be displayed; note the year must be fully specified:
+.B "cal 89"
+will not display a calendar for 1989.
+.TP
+\fBSingle string parameter (e.g., 'cal tomorrow' or 'cal August')\fR
+Specifies \fItimestamp\fR or a \fImonth name\fR (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 '+2days'. Instead of prefixing the time span
+with "+" or "-", it may also be suffixed with a space and the word "left" or
+"ago" (for example '1 week ago').
+.TP
+\fBTwo parameters (e.g., 'cal 11 2020')\fR
+Denote the \fImonth\fR (1 - 12) and \fIyear\fR.
+.TP
+\fBThree parameters (e.g., 'cal 25 11 2020')\fR
+Denote the \fIday\fR (1-31), \fImonth and \fIyear\fR, 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.
+.SH NOTES
+A year starts on January 1. The first day of the week is determined by the
+locale or the
+.BR \-\-sunday \ and \ \-\-monday \ options.
+.PP
+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\fR) then the ISO 8601 standard week
+numbering is used, where the first Thursday is in week number 1.
+.SH COLORS
+Implicit coloring can be disabled as follows:
+.RS
+
+.B touch /etc/terminal-colors.d/cal.disable
+
+.RE
+See
+.BR terminal-colors.d (5)
+for more details about colorization configuration.
+.SH HISTORY
+A cal command appeared in Version 6 AT&T UNIX.
+.SH BUGS
+The default
+.B 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.
+.PP
+Alternative calendars, such as the Umm al-Qura, the Solar Hijri, the Ge'ez,
+or the lunisolar Hindu, are not supported.
+.SH AVAILABILITY
+The cal command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/misc-utils/cal.c b/misc-utils/cal.c
new file mode 100644
index 0000000..0834be2
--- /dev/null
+++ b/misc-utils/cal.c
@@ -0,0 +1,1314 @@
+/*
+ * 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"
+
+#define DOY_MONTH_WIDTH 27 /* -j month width */
+#define DOM_MONTH_WIDTH 20 /* month width */
+
+static int has_term = 0;
+static const char *Senter = "", *Sexit = ""; /* enter and exit standout mode */
+
+#if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
+# if defined(HAVE_NCURSESW_TERM_H)
+# include <ncursesw/term.h>
+# elif defined(HAVE_NCURSES_TERM_H)
+# include <ncurses/term.h>
+# elif defined(HAVE_TERM_H)
+# include <term.h>
+# endif
+#endif
+
+static int setup_terminal(char *term
+#if !defined(HAVE_LIBNCURSES) && !defined(HAVE_LIBNCURSESW)
+ __attribute__((__unused__))
+#endif
+ )
+{
+#if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
+ int ret;
+
+ if (setupterm(term, STDOUT_FILENO, &ret) != 0 || ret != 1)
+ return -1;
+#endif
+ return 0;
+}
+
+static void my_putstring(const char *s)
+{
+#if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
+ if (has_term)
+ putp(s);
+ else
+#endif
+ fputs(s, stdout);
+}
+
+static const char *my_tgetstr(char *ss
+#if !defined(HAVE_LIBNCURSES) && !defined(HAVE_LIBNCURSESW)
+ __attribute__((__unused__))
+#endif
+ )
+{
+ const char *ret = NULL;
+
+#if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
+ if (has_term)
+ ret = tigetstr(ss);
+#endif
+ if (!ret || ret == (char *)-1)
+ return "";
+ return ret;
+}
+
+#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,
+};
+
+/* 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;
+ char *term;
+ time_t now;
+ int ch = 0, yflag = 0, Yflag = 0;
+
+ 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'},
+ {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();
+
+ term = getenv("TERM");
+ if (term) {
+ has_term = setup_terminal(term) == 0;
+ if (has_term) {
+ Senter = my_tgetstr("smso");
+ Sexit = my_tgetstr("rmso");
+ }
+ }
+
+/*
+ * 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:sSywYvVh", 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 '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) {
+ /*
+ * If standout mode available (Senter and Sexit are set) and
+ * user or terminal-colors.d do not disable colors than
+ * ignore colors_init().
+ */
+ if (*Senter && *Sexit && colors_mode() != UL_COLORMODE_NEVER) {
+ /* let use standout mode */
+ ;
+ } else {
+ /* disable */
+ Senter = ""; Sexit = "";
+ 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 (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;
+
+ if (new_n < MONTHS_IN_YEAR_ROW)
+ ctl.months_in_row = new_n > 0 ? new_n : 1;
+ }
+ } 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;
+ for (int 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;
+
+ 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) {
+ my_putstring("\n");
+ 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);
+ }
+ }
+ my_putstring("\n");
+ for (i = month; i; i = i->next) {
+ if (ctl->weektype) {
+ if (ctl->julian)
+ snprintf(out, sizeof(out), "%*s%s", (int)ctl->day_width - 1, "", day_headings);
+ else
+ snprintf(out, sizeof(out), "%*s%s", (int)ctl->day_width, "", day_headings);
+ my_putstring(out);
+ } else
+ my_putstring(day_headings);
+ if (i->next != NULL) {
+ snprintf(out, sizeof(out), "%*s", ctl->gutter_width, "");
+ my_putstring(out);
+ }
+ }
+ my_putstring("\n");
+}
+
+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 */
+ snprintf(out, sizeof(out), "%*s", (int)ctl->day_width + 1, "");
+ my_putstring(out);
+ 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) {
+ my_putstring("\n");
+ /* Padding for the weekdays */
+ snprintf(out, sizeof(out), "%*s", (int)ctl->day_width + 1, "");
+ my_putstring(out);
+ 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);
+ }
+ }
+ my_putstring("\n");
+}
+
+static void cal_output_months(struct cal_month *month, const struct cal_control *ctl)
+{
+ char out[FMT_ST_CHARS];
+ int reqday, week_line, d;
+ int skip;
+ struct cal_month *i;
+
+ 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])
+ snprintf(out, sizeof(out), "%s%2d%s",
+ Senter, i->weeks[week_line],
+ Sexit);
+ else
+ snprintf(out, sizeof(out), "%2d", i->weeks[week_line]);
+ } else
+ snprintf(out, sizeof(out), "%2s", "");
+ my_putstring(out);
+ 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++) {
+ if (0 < i->days[d]) {
+ if (reqday == i->days[d])
+ snprintf(out, sizeof(out), "%*s%s%*d%s",
+ skip - (ctl->julian ? 3 : 2),
+ "", Senter, (ctl->julian ? 3 : 2),
+ i->days[d], Sexit);
+ else
+ snprintf(out, sizeof(out), "%*d", skip, i->days[d]);
+ } else
+ snprintf(out, sizeof(out), "%*s", skip, "");
+ my_putstring(out);
+ if (skip < (int)ctl->day_width)
+ skip++;
+ }
+ if (i->next != NULL) {
+ snprintf(out, sizeof(out), "%*s", ctl->gutter_width, "");
+ my_putstring(out);
+ }
+ }
+ if (i == NULL)
+ my_putstring("\n");
+ }
+}
+
+static void
+cal_vert_output_months(struct cal_month *month, const struct cal_control *ctl)
+{
+ char out[FMT_ST_CHARS];
+ 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]) {
+ snprintf(out, sizeof(out), "%*s%s%*d%s",
+ skip - (ctl->julian ? 3 : 2),
+ "", Senter, (ctl->julian ? 3 : 2),
+ m->days[d], Sexit);
+ } else {
+ snprintf(out, sizeof(out), "%*d",
+ skip, m->days[d]);
+ }
+ } else {
+ snprintf(out, sizeof(out), "%*s",
+ skip, "");
+ }
+ my_putstring(out);
+ skip = ctl->day_width;
+ }
+ if (m->next != NULL) {
+ snprintf(out, sizeof(out), "%*s", ctl->gutter_width, "");
+ my_putstring(out);
+ }
+ }
+ my_putstring("\n");
+ }
+ if (!ctl->weektype)
+ return;
+
+ snprintf(out, sizeof(out), "%*s", (int)ctl->day_width - 1, "");
+ my_putstring(out);
+ 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])
+ {
+ snprintf(out, sizeof(out), "%s%*d%s",
+ Senter,
+ skip - (ctl->julian ? 3 : 2),
+ m->weeks[week], Sexit);
+ } else {
+ snprintf(out, sizeof(out), "%*d",
+ skip, m->weeks[week]);
+ }
+ } else {
+ snprintf(out, sizeof(out), "%*s", skip, "");
+ }
+ my_putstring(out);
+ }
+ if (m->next != NULL) {
+ snprintf(out, sizeof(out), "%*s", ctl->gutter_width, "");
+ my_putstring(out);
+ }
+ }
+ my_putstring("\n");
+}
+
+
+static void monthly(const struct cal_control *ctl)
+{
+ struct cal_month m1,m2,m3, *m;
+ 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;
+ }
+
+ m1.next = (ctl->months_in_row > 1) ? &m2 : NULL;
+ m2.next = (ctl->months_in_row > 2) ? &m3 : NULL;
+ m3.next = NULL;
+
+ rows = (ctl->num_months - 1) / ctl->months_in_row;
+ for (i = 0; i < rows + 1 ; i++){
+ if (i == rows){
+ switch (ctl->num_months % ctl->months_in_row){
+ case 1:
+ m1.next = NULL;
+ /* fallthrough */
+ case 2:
+ m2.next = NULL;
+ /* fallthrough */
+ }
+ }
+ for (m = &m1; 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) {
+ /* Add a line between row */
+ my_putstring("\n");
+ }
+ cal_vert_output_header(&m1, ctl);
+ cal_vert_output_months(&m1, ctl);
+ } else {
+ cal_output_header(&m1, ctl);
+ cal_output_months(&m1, ctl);
+ }
+ }
+}
+
+static void yearly(const struct cal_control *ctl)
+{
+ char out[FMT_ST_CHARS];
+ 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) {
+ snprintf(out, sizeof(out), "%04d", ctl->req.year);
+ center(out, year_width, 0);
+ my_putstring("\n\n");
+ }
+ 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);
+ my_putstring(lineout);
+
+ if (separate) {
+ snprintf(lineout, sizeof(lineout), "%*s", separate, "");
+ my_putstring(lineout);
+ }
+}
+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);
+ my_putstring(lineout);
+
+ if (separate) {
+ snprintf(lineout, sizeof(lineout), "%*s", separate, "");
+ my_putstring(lineout);
+ }
+}
+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);
+ 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/fincore.1 b/misc-utils/fincore.1
new file mode 100644
index 0000000..c01b5de
--- /dev/null
+++ b/misc-utils/fincore.1
@@ -0,0 +1,62 @@
+.\" Copyright 2017 Red Hat, Inc.
+.\"
+.\" This file may be copied under the terms of the GNU Public License.
+.TH FINCORE 1 "March 2017" "util-linux" "User Commands"
+.SH NAME
+fincore \- count pages of file contents in core
+.SH SYNOPSIS
+.B fincore
+[options]
+.I file ...
+.SH DESCRIPTION
+.B 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
+.B 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
+.B \-\-output
+.I columns-list
+in environments where a stable output is required.
+.SH OPTIONS
+.TP
+.BR \-n , " \-\-noheadings"
+Do not print a header line in status output.
+.TP
+.BR \-b , " \-\-bytes"
+Print the SIZE column in bytes rather than in a human-readable format.
+.TP
+.BR \-o , " \-\-output \fIlist\fP"
+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.
+.TP
+.BR \-r , " \-\-raw"
+Produce output in raw format. All potentially unsafe characters are hex-escaped
+(\\x<code>).
+.TP
+.BR \-J , " \-\-json"
+Use JSON output format.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display help text and exit.
+.SH AUTHORS
+.MT yamato@\:redhat.com
+Masatake YAMATO
+.ME
+.SH SEE ALSO
+.BR mincore (2),
+.BR getpagesize (2),
+.BR getconf (1p)
+.SH AVAILABILITY
+The fincore command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/misc-utils/fincore.c b/misc-utils/fincore.c
new file mode 100644
index 0000000..09f64ed
--- /dev/null
+++ b/misc-utils/fincore.c
@@ -0,0 +1,412 @@
+/*
+ * 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;
+
+ window = mmap(window, len, PROT_NONE, 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..59466c1
--- /dev/null
+++ b/misc-utils/findfs.8
@@ -0,0 +1,79 @@
+.\" Copyright 1993, 1994, 1995 by Theodore Ts'o. All Rights Reserved.
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH FINDFS 8 "March 2014" "util-linux" "System Administration"
+.SH NAME
+findfs \- find a filesystem by label or UUID
+.SH SYNOPSIS
+.B findfs
+.BI NAME= value
+.SH DESCRIPTION
+.B findfs
+will search the block devices in the system looking for a filesystem or
+partition with specified tag. The currently supported tags are:
+.TP
+.B LABEL=<label>
+Specifies filesystem label.
+.TP
+.B UUID=<uuid>
+Specifies filesystem UUID.
+.TP
+.B PARTUUID=<uuid>
+Specifies partition UUID. This partition identifier is supported for example for
+GUID Partition Table (GPT) partition tables.
+.TP
+.B PARTLABEL=<label>
+Specifies partition label (name). The partition labels are supported for example for
+GUID Partition Table (GPT) or MAC partition tables.
+.PP
+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
+.RS
+
+.B lsblk \-\-fs
+
+.B partx --show <disk>
+
+.B blkid
+
+.RE
+
+.SH EXIT STATUS
+.RS
+.PD 0
+.TP
+.B 0
+success
+.TP
+.B 1
+label or uuid cannot be found
+.TP
+.B 2
+usage error, wrong number of arguments or unknown option
+.PD
+.RE
+.SH ENVIRONMENT
+.IP LIBBLKID_DEBUG=all
+enables libblkid debug output.
+.SH AUTHORS
+.B findfs
+was originally written by
+.MT tytso@mit.edu
+Theodore Ts'o
+.ME
+and re-written for the util-linux package by
+.MT kzak@redhat.com
+Karel Zak
+.ME .
+.SH SEE ALSO
+.BR blkid (8),
+.BR lsblk (8),
+.BR partx (8)
+.SH AVAILABILITY
+The findfs command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
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..1d25542
--- /dev/null
+++ b/misc-utils/findmnt-verify.c
@@ -0,0 +1,527 @@
+#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 "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 verify_mesg(struct verify_context *vfy, char type, const char *fmt, va_list ap)
+{
+ if (!vfy->target_printed) {
+ 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 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 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 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 = 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 (strcmp(vfy->fs_ary[n], name) == 0)
+ 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 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)
+{
+ const char *src = mnt_resolve_spec(mnt_fs_get_source(vfy->fs), cache);
+ const char *type, *realtype;
+ int ambi = 0, isauto = 0, isswap = 0;
+
+ if (!src)
+ return 0;
+ if (mnt_fs_is_pseudofs(vfy->fs) || mnt_fs_is_netfs(vfy->fs))
+ return verify_ok(vfy, _("do not check %s FS type (pseudo/net)"), src);
+
+ 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)
+ return verify_warn(vfy, _("\"none\" FS type is recommended for bind or move oprations only"));
+
+ if (strcmp(type, "auto") == 0)
+ isauto = 1;
+ else if (strcmp(type, "swap") == 0)
+ isswap = 1;
+ else if (strcmp(type, "xfs") == 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);
+ }
+ realtype = mnt_get_fstype(src, &ambi, cache);
+
+ if (!realtype) {
+ if (isauto)
+ return verify_err(vfy, _("cannot detect on-disk filesystem type"));
+ return verify_warn(vfy, _("cannot detect on-disk filesystem type"));
+ }
+
+ if (realtype) {
+ isswap = strcmp(realtype, "swap") == 0;
+ vfy->no_fsck = strcmp(realtype, "xfs") == 0;
+
+ if (type && !isauto && strcmp(type, realtype) != 0)
+ return verify_err(vfy, _("%s does not match with on-disk %s"), type, realtype);
+
+ if (!isswap && !is_supported_filesystem(vfy, realtype))
+ return verify_err(vfy, _("on-disk %s seems unsupported by the current kernel"), realtype);
+
+ verify_ok(vfy, _("FS type is %s"), 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;
+ }
+
+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"));
+
+ 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..e411c30
--- /dev/null
+++ b/misc-utils/findmnt.8
@@ -0,0 +1,308 @@
+.TH FINDMNT 8 "May 2018" "util-linux" "System Administration"
+.SH NAME
+findmnt \- find a filesystem
+.SH SYNOPSIS
+.B findmnt
+[options]
+.sp
+.B findmnt
+[options]
+.IR device | mountpoint
+.sp
+.B findmnt
+[options]
+.RB [ \-\-source ]
+.I device
+.RB [ \-\-target | \-\-mountpoint ]
+.I mountpoint
+.SH DESCRIPTION
+.B findmnt
+will list all mounted filesystems or search for a filesystem. The
+.B \%findmnt
+command is able to search in
+.IR /etc/fstab ,
+.I /etc/mtab
+or
+.IR /proc/self/mountinfo .
+If
+.I device
+or
+.I mountpoint
+is not given, all filesystems are shown.
+.PP
+The device may be specified by device name, major:minor numbers,
+filesystem label or UUID, or partition label or UUID. Note that
+.B \%findmnt
+follows
+.BR mount (8)
+behavior where a device name may be interpreted
+as a mountpoint (and vice versa) if the \fB\-\-target\fR, \fB\-\-mountpoint\fR or
+\fB\-\-source\fR options are not specified.
+.PP
+The command prints all mounted filesystems in the tree-like format by default.
+.SH OPTIONS
+.TP
+.BR \-A , " \-\-all"
+Disable all built-in filters and print all filesystems.
+.TP
+.BR \-a , " \-\-ascii"
+Use ascii characters for tree formatting.
+.TP
+.BR \-b , " \-\-bytes"
+Print the SIZE, USED and AVAIL columns in bytes rather than in a human-readable format.
+.TP
+.BR \-C , " \-\-nocanonicalize"
+Do not canonicalize paths at all. This option affects the comparing of paths
+and the evaluation of tags (LABEL, UUID, etc.).
+.TP
+.BR \-c , " \-\-canonicalize"
+Canonicalize all printed paths.
+.TP
+.BR \-D , " \-\-df"
+Imitate the output of
+.BR df (1).
+This option is equivalent to
+.B \-o\ SOURCE,FSTYPE,SIZE,USED,AVAIL,USE%,TARGET
+but excludes all pseudo filesystems.
+Use \fB\-\-all\fP to print all filesystems.
+.TP
+.BR \-d , " \-\-direction \fIword\fP"
+The search direction, either
+.B forward
+or
+.BR backward .
+.TP
+.BR \-e , " \-\-evaluate"
+Convert all tags (LABEL, UUID, PARTUUID or PARTLABEL) to the corresponding device names.
+.TP
+.BR \-F , " \-\-tab\-file \fIpath\fP"
+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).
+.TP
+.BR \-f , " \-\-first\-only"
+Print the first matching filesystem only.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.TP
+.BR \-i , " \-\-invert"
+Invert the sense of matching.
+.TP
+.BR \-J , " \-\-json"
+Use JSON output format.
+.TP
+.BR \-k , " \-\-kernel"
+Search in
+.IR /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 \fB\-\-mtab)\fP.
+.TP
+.BR \-l , " \-\-list"
+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.
+.TP
+.BR \-M , " \-\-mountpoint \fIpath\fP"
+Explicitly define the mountpoint file or directory. See also \fB\-\-target\fP.
+.TP
+.BR \-m , " \-\-mtab"
+Search in
+.IR /etc/mtab .
+The output is in the list format by default (see \fB\-\-tree\fP). The output may include user
+space mount options.
+.TP
+.BR \-N , " \-\-task \fItid\fP"
+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 \fB\-\-list\fP option). See also the
+.BR unshare (1)
+command.
+.TP
+.BR \-n , " \-\-noheadings"
+Do not print a header line.
+.TP
+.BR \-O , " \-\-options \fIlist\fP"
+Limit the set of printed filesystems. More than one option
+may be specified in a comma-separated list. The
+.B \-t
+and
+.B \-O
+options are cumulative in effect. It is different from
+.B \-t
+in that each option is matched exactly; a leading
+.I 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 "+" prefix.
+.TP
+.BR \-o , " \-\-output \fIlist\fP"
+Define output columns. See the \fB\-\-help\fP output to get a list of the
+currently supported columns. The
+.B TARGET
+column contains tree formatting if the
+.B \-\-list
+or
+.B \-\-raw
+options are not specified.
+
+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).
+.TP
+.B \-\-output\-all
+Output almost all available columns. The columns that require
+.B \-\-poll
+are not included.
+.TP
+.BR \-P , " \-\-pairs"
+Use key="value" output format. All potentially unsafe characters are hex-escaped (\\x<code>).
+.TP
+.BR \-p , " \-\-poll\fR[\fI=list\fR]"
+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 \fB\-\-poll\fR will block can be restricted with the \fB\-\-timeout\fP
+or \fB\-\-first\-only\fP 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
+.BR findmnt (8).
+The poll mode allows using extra columns:
+.RS
+.TP
+.B ACTION
+mount, umount, move or remount action name; this column is enabled by default
+.TP
+.B OLD-TARGET
+available for umount and move actions
+.TP
+.B OLD-OPTIONS
+available for umount and remount actions
+.RE
+.TP
+.B \-\-pseudo
+Print only pseudo filesystems.
+.TP
+.BR \-R , " \-\-submounts"
+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.
+.TP
+.BR \-r , " \-\-raw"
+Use raw output format. All potentially unsafe characters are hex-escaped (\\x<code>).
+.TP
+.B \-\-real
+Print only real filesystems.
+.TP
+.BR \-S , " \-\-source \fIspec\fP"
+Explicitly define the mount source. Supported specifications are \fIdevice\fR,
+\fImaj\fB:\fImin\fR, \fBLABEL=\fIlabel\fR, \fBUUID=\fIuuid\fR,
+\fBPARTLABEL=\fIlabel\fR and \fBPARTUUID=\fIuuid\fR.
+.TP
+.BR \-s , " \-\-fstab"
+Search in
+.IR /etc/fstab .
+The output is in the list format (see \fB\-\-list\fR).
+.TP
+.BR \-T , " \-\-target \fIpath\fP"
+Define the mount target. If \fIpath\fR
+is not a mountpoint file or directory, then
+.B findmnt
+checks the \fIpath\fR 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's
+recommended to use the option \fB\-\-mountpoint\fR when checks of \fIpath\fR elements are
+unwanted and \fIpath\fR is a strictly specified mountpoint.
+.TP
+.BR \-t , " \-\-types \fIlist\fP"
+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
+.B no
+to specify the filesystem types on which no action should be taken. For
+more details see
+.BR mount (8).
+.TP
+.B \-\-tree
+Enable tree-like output if possible. The options is silently ignored for
+tables where is missing child-parent relation (e.g., fstab).
+.TP
+.BR \-U , " \-\-uniq"
+Ignore filesystems with duplicate mount targets, thus effectively skipping
+over-mounted mount points.
+.TP
+.BR \-u , " \-\-notruncate"
+Do not truncate text in columns. The default is to not truncate the
+.BR TARGET ,
+.BR SOURCE ,
+.BR UUID ,
+.BR LABEL ,
+.BR PARTUUID ,
+.B PARTLABEL
+columns. This option disables text truncation also in all other columns.
+.TP
+.BR \-v , " \-\-nofsroot"
+Do not print a [/dir] in the SOURCE column for bind mounts or btrfs subvolumes.
+.TP
+.BR \-w , " \-\-timeout \fImilliseconds\fP"
+Specify an upper limit on the time for which \fB\-\-poll\fR will block, in milliseconds.
+.TP
+.BR \-x , " \-\-verify"
+Check mount table content. The default is to verify
+.I /etc/fstab
+parsability and usability. It's possible to use this option also with \fB\-\-tab\-file\fP.
+It's possible to specify source (device) or target (mountpoint) to filter mount table. The option
+\fB\-\-verbose\fP forces findmnt to print more details.
+.TP
+.B \-\-verbose
+Force findmnt to print more information (\fB\-\-verify\fP only for now).
+.SH ENVIRONMENT
+.IP LIBMOUNT_FSTAB=<path>
+overrides the default location of the fstab file
+.IP LIBMOUNT_MTAB=<path>
+overrides the default location of the mtab file
+.IP LIBMOUNT_DEBUG=all
+enables libmount debug output
+.IP LIBSMARTCOLS_DEBUG=all
+enables libsmartcols debug output
+.IP LIBSMARTCOLS_DEBUG_PADDING=on
+use visible padding characters. Requires enabled LIBSMARTCOLS_DEBUG.
+.SH EXAMPLE
+.IP "\fBfindmnt \-\-fstab \-t nfs\fP"
+Prints all NFS filesystems defined in
+.IR /etc/fstab .
+.IP "\fBfindmnt \-\-fstab /mnt/foo\fP"
+Prints all
+.I /etc/fstab
+filesystems where the mountpoint directory is /mnt/foo. It also prints bind mounts where /mnt/foo
+is a source.
+.IP "\fBfindmnt \-\-fstab \-\-target /mnt/foo\fP"
+Prints all
+.I /etc/fstab
+filesystems where the mountpoint directory is /mnt/foo.
+.IP "\fBfindmnt \-\-fstab \-\-evaluate\fP"
+Prints all
+.I /etc/fstab
+filesystems and converts LABEL= and UUID= tags to the real device names.
+.IP "\fBfindmnt \-n \-\-raw \-\-evaluate \-\-output=target LABEL=/boot\fP"
+Prints only the mountpoint where the filesystem with label "/boot" is mounted.
+.IP "\fBfindmnt \-\-poll \-\-mountpoint /mnt/foo\fP"
+Monitors mount, unmount, remount and move on /mnt/foo.
+.IP "\fBfindmnt \-\-poll=umount \-\-first-only \-\-mountpoint /mnt/foo\fP"
+Waits for /mnt/foo unmount.
+.IP "\fBfindmnt \-\-poll=remount \-t ext3 \-O ro\fP"
+Monitors remounts to read-only mode on all ext3 filesystems.
+.SH AUTHORS
+.nf
+Karel Zak <kzak@redhat.com>
+.fi
+.SH SEE ALSO
+.BR fstab (5),
+.BR mount (8)
+.SH AVAILABILITY
+The findmnt command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/misc-utils/findmnt.c b/misc-utils/findmnt.c
new file mode 100644
index 0000000..43b4dc7
--- /dev/null
+++ b/misc-utils/findmnt.c
@@ -0,0 +1,1732 @@
+/*
+ * 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 <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 "findmnt.h"
+
+/* column IDs */
+enum {
+ COL_SOURCE,
+ COL_TARGET,
+ COL_FSTYPE,
+ COL_OPTIONS,
+ COL_VFS_OPTIONS,
+ COL_FS_OPTIONS,
+ COL_LABEL,
+ COL_UUID,
+ COL_PARTLABEL,
+ COL_PARTUUID,
+ COL_MAJMIN,
+ COL_ACTION,
+ COL_OLD_TARGET,
+ COL_OLD_OPTIONS,
+ COL_SIZE,
+ COL_AVAIL,
+ COL_USED,
+ COL_USEPERC,
+ COL_FSROOT,
+ COL_TID,
+ COL_ID,
+ COL_OPT_FIELDS,
+ COL_PROPAGATION,
+ COL_FREQ,
+ COL_PASSNO
+};
+
+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_SOURCE] = { "SOURCE", 0.25, SCOLS_FL_NOEXTREMES, N_("source device") },
+ [COL_TARGET] = { "TARGET", 0.30, SCOLS_FL_TREE| SCOLS_FL_NOEXTREMES, N_("mountpoint") },
+ [COL_FSTYPE] = { "FSTYPE", 0.10, SCOLS_FL_TRUNC, N_("filesystem type") },
+ [COL_OPTIONS] = { "OPTIONS", 0.10, SCOLS_FL_TRUNC, N_("all mount options") },
+ [COL_VFS_OPTIONS] = { "VFS-OPTIONS", 0.20, SCOLS_FL_TRUNC, N_("VFS specific mount options") },
+ [COL_FS_OPTIONS] = { "FS-OPTIONS", 0.10, SCOLS_FL_TRUNC, N_("FS specific mount options") },
+ [COL_LABEL] = { "LABEL", 0.10, 0, N_("filesystem label") },
+ [COL_UUID] = { "UUID", 36, 0, N_("filesystem UUID") },
+ [COL_PARTLABEL] = { "PARTLABEL", 0.10, 0, N_("partition label") },
+ [COL_PARTUUID] = { "PARTUUID", 36, 0, N_("partition UUID") },
+ [COL_MAJMIN] = { "MAJ:MIN", 6, 0, N_("major:minor device number") },
+ [COL_ACTION] = { "ACTION", 10, SCOLS_FL_STRICTWIDTH, N_("action detected by --poll") },
+ [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_SIZE] = { "SIZE", 5, SCOLS_FL_RIGHT, N_("filesystem size") },
+ [COL_AVAIL] = { "AVAIL", 5, SCOLS_FL_RIGHT, N_("filesystem size available") },
+ [COL_USED] = { "USED", 5, SCOLS_FL_RIGHT, N_("filesystem size used") },
+ [COL_USEPERC] = { "USE%", 3, SCOLS_FL_RIGHT, N_("filesystem use percentage") },
+ [COL_FSROOT] = { "FSROOT", 0.25, SCOLS_FL_NOEXTREMES, N_("filesystem root") },
+ [COL_TID] = { "TID", 4, SCOLS_FL_RIGHT, N_("task ID") },
+ [COL_ID] = { "ID", 2, SCOLS_FL_RIGHT, N_("mount ID") },
+ [COL_OPT_FIELDS] = { "OPT-FIELDS", 0.10, SCOLS_FL_TRUNC, N_("optional mount fields") },
+ [COL_PROPAGATION] = { "PROPAGATION", 0.10, 0, N_("VFS propagation flags") },
+ [COL_FREQ] = { "FREQ", 1, SCOLS_FL_RIGHT, N_("dump(8) period in days [fstab only]") },
+ [COL_PASSNO] = { "PASSNO", 1, SCOLS_FL_RIGHT, N_("pass number on parallel fsck(8) [fstab only]") }
+};
+
+/* 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) */
+int flags;
+int parse_nerrors;
+struct libmnt_cache *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 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_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 (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_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;
+
+ if (!fs) {
+ /* first call, get root FS */
+ if (mnt_table_get_root_fs(tb, &fs))
+ goto leave;
+ parent_line = NULL;
+
+ } 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;
+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;
+
+ 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(_(" -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 is 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);
+
+ fputc('\n', out);
+ fputs(_(" -x, --verify verify mount table content (default is fstab)\n"), out);
+ fputs(_(" --verbose print more details\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
+ };
+
+ 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' },
+ { "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 },
+ { 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:Vx",
+ 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:
+ for (ncolumns = 0; ncolumns < ARRAY_SIZE(infos); ncolumns++) {
+ if (is_tabdiff_column(ncolumns))
+ continue;
+ columns[ncolumns] = ncolumns;
+ }
+ 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 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 '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_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;
+ }
+
+ 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_FREQ:
+ case COL_PASSNO:
+ case COL_TID:
+ scols_column_set_json_type(cl, SCOLS_JSON_NUMBER);
+ break;
+ default:
+ 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..6388837
--- /dev/null
+++ b/misc-utils/findmnt.h
@@ -0,0 +1,41 @@
+#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),
+
+ /* basic table settings */
+ FL_ASCII = (1 << 20),
+ FL_RAW = (1 << 21),
+ FL_NOHEADINGS = (1 << 22),
+ FL_EXPORT = (1 << 23),
+ FL_TREE = (1 << 24),
+ FL_JSON = (1 << 25),
+};
+
+extern struct libmnt_cache *cache;
+extern 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-parse.bash b/misc-utils/getopt-parse.bash
new file mode 100644
index 0000000..db8bf6b
--- /dev/null
+++ b/misc-utils/getopt-parse.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-parse.tcsh.
+
+# Example input and output (from the bash prompt):
+#
+# ./getopt-parse.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-parse.tcsh b/misc-utils/getopt-parse.tcsh
new file mode 100644
index 0000000..032d7ac
--- /dev/null
+++ b/misc-utils/getopt-parse.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-parse.bash.
+
+# Example input and output (from the tcsh prompt):
+# ./parse.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..356e16a
--- /dev/null
+++ b/misc-utils/getopt.1
@@ -0,0 +1,460 @@
+.TH GETOPT "1" "December 2014" "util-linux" "User Commands"
+.SH NAME
+getopt \- parse command options (enhanced)
+.SH SYNOPSIS
+.B getopt
+.I optstring parameters
+.br
+.B getopt
+[options]
+.RB [ \-\- ]
+.I optstring parameters
+.br
+.B getopt
+[options]
+.BR \-o | \-\-options
+.I optstring
+[options]
+.RB [ \-\- ]
+.I parameters
+.SH DESCRIPTION
+.B getopt
+is used to break up
+.RI ( parse )
+options in command lines for easy parsing by shell procedures, and to
+check for valid options. It uses the
+.SM GNU
+.BR getopt (3)
+routines to do this.
+.PP
+The parameters
+.B getopt
+is called with can be divided into two parts: options which modify
+the way
+.B getopt
+will do the parsing
+.RI "(the " options
+and the
+.I optstring
+in the
+.BR SYNOPSIS ),
+and the parameters which are to be parsed
+.RI ( parameters
+in the
+.BR SYNOPSIS ).
+The second part will start at the first non\-option parameter that is
+not an option argument, or after the first occurrence of
+.RB ' \-\- '.
+If no
+.RB ' \-o '
+or
+.RB ' \-\-options '
+option is found in the first part, the first parameter of the second
+part is used as the short options string.
+.PP
+If the environment variable
+.B GETOPT_COMPATIBLE
+is set, or if the first
+.I parameter
+is not an option (does not start with a
+.RB ' \- ',
+the first format in the
+.BR SYNOPSIS ),
+.B getopt
+will generate output that is compatible with that of other versions of
+.BR getopt (1).
+It will still do parameter shuffling and recognize optional arguments
+(see section
+.B COMPATIBILITY
+for more information).
+.PP
+Traditional implementations of
+.BR 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
+.B eval
+command). This has the effect of preserving those characters, but
+you must call
+.B getopt
+in a way that is no longer compatible with other versions (the second
+or third format in the
+.BR SYNOPSIS ).
+To determine whether this enhanced version of
+.BR getopt (1)
+is installed, a special test option
+.RB ( \-T )
+can be used.
+.SH OPTIONS
+.TP
+.BR \-a , " \-\-alternative"
+Allow long options to start with a single
+.RB ' \- '.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit. No other output is generated.
+.TP
+.BR \-l , " \-\-longoptions \fIlongopts\fP"
+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
+.I longopts
+are cumulative. Each long option name in
+.I 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.
+.TP
+.BR \-n , " \-\-name \fIprogname\fP"
+The name that will be used by the
+.BR getopt (3)
+routines when it reports errors. Note that errors of
+.BR getopt (1)
+are still reported as coming from getopt.
+.TP
+.BR \-o , " \-\-options \fIshortopts\fP"
+The short (one\-character) options to be recognized. If this option
+is not found, the first parameter of
+.B getopt
+that does not start with a
+.RB ' \- '
+(and is not an option argument) is used as the short options string.
+Each short option character in
+.I 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
+.RB ' + '
+or
+.RB ' \- '
+to influence the way options are parsed and output is generated (see
+section
+.B SCANNING MODES
+for details).
+.TP
+.BR \-q , " \-\-quiet"
+Disable error reporting by getopt(3).
+.TP
+.BR \-Q , " \-\-quiet\-output"
+Do not generate normal output. Errors are still reported by
+.BR getopt (3),
+unless you also use
+.BR \-q .
+.TP
+.BR \-s , " \-\-shell \fIshell\fP"
+Set quoting conventions to those of
+.IR shell .
+If the \fB\-s\fR option is not given, the
+.SM BASH
+conventions are used. Valid arguments are currently
+.RB ' sh '
+.RB ' bash ',
+.RB ' csh ',
+and
+.RB ' tcsh '.
+.TP
+.BR \-T , " \-\-test"
+Test if your
+.BR getopt (1)
+is this enhanced version or an old version. This generates no
+output, and sets the error status to 4. Other implementations of
+.BR getopt (1),
+and this version if the environment variable
+.B GETOPT_COMPATIBLE
+is set, will return
+.RB ' \-\- '
+and error status 0.
+.TP
+.BR \-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
+.BR getopt (1)
+implementations).
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit. No other output is generated.
+.SH PARSING
+This section specifies the format of the second part of the
+parameters of
+.B getopt
+(the
+.I parameters
+in the
+.BR SYNOPSIS ).
+The next section
+.RB ( 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
+.B getopt
+(see the
+.BR EXAMPLES ).
+All parsing is done by the GNU
+.BR getopt (3)
+routines.
+.PP
+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.
+.PP
+A simple short option is a
+.RB ' \- '
+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.
+.PP
+It is possible to specify several short options after one
+.RB ' \- ',
+as long as all (except possibly the last) do not have required or
+optional arguments.
+.PP
+A long option normally begins with
+.RB ' \-\- '
+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
+.RB ' = ',
+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
+.RB ' = ',
+if present (if you add the
+.RB ' = '
+but nothing behind it, it is interpreted as if no argument was
+present; this is a slight bug, see the
+.BR BUGS ).
+Long options may be abbreviated, as long as the abbreviation is not
+ambiguous.
+.PP
+Each parameter not starting with a
+.RB ' \- ',
+and not a required argument of a previous option, is a non\-option
+parameter. Each parameter after a
+.RB ' \-\- '
+parameter is always interpreted as a non\-option parameter. If the
+environment variable
+.B POSIXLY_CORRECT
+is set, or if the short option string started with a
+.RB ' + ',
+all remaining parameters are interpreted as non\-option parameters as
+soon as the first non\-option parameter is found.
+.SH 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
+.I compatible
+.RI ( unquoted )
+mode, or in such way that whitespace and other special characters
+within arguments and non\-option parameters are preserved (see
+.BR 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.
+.PP
+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.
+.PP
+For a short option, a single
+.RB ' \- '
+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
+.BR getopt (1)
+implementations do not support optional arguments.
+.PP
+If several short options were specified after a single
+.RB ' \- ',
+each will be present in the output as a separate parameter.
+.PP
+For a long option,
+.RB ' \-\- '
+and the full option name are generated as one parameter. This is
+done regardless whether the option was abbreviated or specified with
+a single
+.RB ' \- '
+in the input. Arguments are handled as with short options.
+.PP
+Normally, no non\-option parameters output is generated until all
+options and their arguments have been generated. Then
+.RB ' \-\- '
+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
+.RB ' \- ',
+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
+.B SYNOPSIS
+is used; in that case all preceding occurrences of
+.RB ' \- '
+and
+.RB ' + '
+are ignored).
+.SH QUOTING
+In compatible 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
+.B eval
+command), it is split correctly into separate parameters.
+.PP
+Quoting is not enabled if the environment variable
+.B GETOPT_COMPATIBLE
+is set, if the first form of the
+.B SYNOPSIS
+is used, or if the option
+.RB ' \-u '
+is found.
+.PP
+Different shells use different quoting conventions. You can use the
+.RB ' \-s '
+option to select the shell you are using. The following shells are
+currently supported:
+.RB ' sh ',
+.RB ' bash ',
+.RB ' csh '
+and
+.RB ' 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.
+.SH "SCANNING MODES"
+The first character of the short options string may be a
+.RB ' \- '
+or a
+.RB ' + '
+to indicate a special scanning mode. If the first calling form in
+the
+.B SYNOPSIS
+is used they are ignored; the environment variable
+.B POSIXLY_CORRECT
+is still examined, though.
+.PP
+If the first character is
+.RB ' + ',
+or if the environment variable
+.B POSIXLY_CORRECT
+is set, parsing stops as soon as the first non\-option parameter
+(i.e., a parameter that does not start with a
+.RB ' \- ')
+is found that is not an option argument. The remaining parameters
+are all interpreted as non\-option parameters.
+.PP
+If the first character is a
+.RB ' \- ',
+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
+.RB ' \-\- '
+parameter has been generated. Note that this
+.RB ' \-\- '
+parameter is still generated, but it will always be the last
+parameter in this mode.
+.SH COMPATIBILITY
+This version of
+.BR 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.
+.PP
+If the first character of the first parameter of getopt is not a
+.RB ' \- ',
+.B 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
+.B POSIXLY_CORRECT
+is set.
+.PP
+The environment variable
+.B GETOPT_COMPATIBLE
+forces
+.B getopt
+into compatibility mode. Setting both this environment variable and
+.B POSIXLY_CORRECT
+offers 100% compatibility for 'difficult' programs. Usually, though,
+neither is needed.
+.PP
+In compatibility mode, leading
+.RB ' \- '
+and
+.RB ' + '
+characters in the short options string are ignored.
+.SH RETURN CODES
+.B getopt
+returns error code
+.B 0
+for successful parsing,
+.B 1
+if
+.BR getopt (3)
+returns errors,
+.B 2
+if it does not understand its own parameters,
+.B 3
+if an internal error occurs like out\-of\-memory, and
+.B 4
+if it is called with
+.BR \-T .
+.SH EXAMPLES
+Example scripts for (ba)sh and (t)csh are provided with the
+.BR getopt (1)
+distribution, and are installed in
+.I /usr/share/doc/util-linux/getopt/
+directory.
+.SH ENVIRONMENT
+.IP POSIXLY_CORRECT
+This environment variable is examined by the
+.BR 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
+.RB ' \- '.
+.IP GETOPT_COMPATIBLE
+Forces
+.B getopt
+to use the first calling format as specified in the
+.BR SYNOPSIS .
+.SH BUGS
+.BR getopt (3)
+can parse long options with optional arguments that are given an
+empty optional argument (but cannot do this for short options).
+This
+.BR getopt (1)
+treats optional arguments that are empty as if they were not present.
+.PP
+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
+.MT frodo@frodo.looijaard.name
+Frodo Looijaard
+.ME
+.SH "SEE ALSO"
+.BR bash (1),
+.BR tcsh (1),
+.BR getopt (3)
+.SH AVAILABILITY
+The getopt command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/misc-utils/getopt.1.in b/misc-utils/getopt.1.in
new file mode 100644
index 0000000..1e913ea
--- /dev/null
+++ b/misc-utils/getopt.1.in
@@ -0,0 +1,460 @@
+.TH GETOPT "1" "December 2014" "util-linux" "User Commands"
+.SH NAME
+getopt \- parse command options (enhanced)
+.SH SYNOPSIS
+.B getopt
+.I optstring parameters
+.br
+.B getopt
+[options]
+.RB [ \-\- ]
+.I optstring parameters
+.br
+.B getopt
+[options]
+.BR \-o | \-\-options
+.I optstring
+[options]
+.RB [ \-\- ]
+.I parameters
+.SH DESCRIPTION
+.B getopt
+is used to break up
+.RI ( parse )
+options in command lines for easy parsing by shell procedures, and to
+check for valid options. It uses the
+.SM GNU
+.BR getopt (3)
+routines to do this.
+.PP
+The parameters
+.B getopt
+is called with can be divided into two parts: options which modify
+the way
+.B getopt
+will do the parsing
+.RI "(the " options
+and the
+.I optstring
+in the
+.BR SYNOPSIS ),
+and the parameters which are to be parsed
+.RI ( parameters
+in the
+.BR SYNOPSIS ).
+The second part will start at the first non\-option parameter that is
+not an option argument, or after the first occurrence of
+.RB ' \-\- '.
+If no
+.RB ' \-o '
+or
+.RB ' \-\-options '
+option is found in the first part, the first parameter of the second
+part is used as the short options string.
+.PP
+If the environment variable
+.B GETOPT_COMPATIBLE
+is set, or if the first
+.I parameter
+is not an option (does not start with a
+.RB ' \- ',
+the first format in the
+.BR SYNOPSIS ),
+.B getopt
+will generate output that is compatible with that of other versions of
+.BR getopt (1).
+It will still do parameter shuffling and recognize optional arguments
+(see section
+.B COMPATIBILITY
+for more information).
+.PP
+Traditional implementations of
+.BR 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
+.B eval
+command). This has the effect of preserving those characters, but
+you must call
+.B getopt
+in a way that is no longer compatible with other versions (the second
+or third format in the
+.BR SYNOPSIS ).
+To determine whether this enhanced version of
+.BR getopt (1)
+is installed, a special test option
+.RB ( \-T )
+can be used.
+.SH OPTIONS
+.TP
+.BR \-a , " \-\-alternative"
+Allow long options to start with a single
+.RB ' \- '.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit. No other output is generated.
+.TP
+.BR \-l , " \-\-longoptions \fIlongopts\fP"
+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
+.I longopts
+are cumulative. Each long option name in
+.I 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.
+.TP
+.BR \-n , " \-\-name \fIprogname\fP"
+The name that will be used by the
+.BR getopt (3)
+routines when it reports errors. Note that errors of
+.BR getopt (1)
+are still reported as coming from getopt.
+.TP
+.BR \-o , " \-\-options \fIshortopts\fP"
+The short (one\-character) options to be recognized. If this option
+is not found, the first parameter of
+.B getopt
+that does not start with a
+.RB ' \- '
+(and is not an option argument) is used as the short options string.
+Each short option character in
+.I 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
+.RB ' + '
+or
+.RB ' \- '
+to influence the way options are parsed and output is generated (see
+section
+.B SCANNING MODES
+for details).
+.TP
+.BR \-q , " \-\-quiet"
+Disable error reporting by getopt(3).
+.TP
+.BR \-Q , " \-\-quiet\-output"
+Do not generate normal output. Errors are still reported by
+.BR getopt (3),
+unless you also use
+.BR \-q .
+.TP
+.BR \-s , " \-\-shell \fIshell\fP"
+Set quoting conventions to those of
+.IR shell .
+If the \fB\-s\fR option is not given, the
+.SM BASH
+conventions are used. Valid arguments are currently
+.RB ' sh '
+.RB ' bash ',
+.RB ' csh ',
+and
+.RB ' tcsh '.
+.TP
+.BR \-T , " \-\-test"
+Test if your
+.BR getopt (1)
+is this enhanced version or an old version. This generates no
+output, and sets the error status to 4. Other implementations of
+.BR getopt (1),
+and this version if the environment variable
+.B GETOPT_COMPATIBLE
+is set, will return
+.RB ' \-\- '
+and error status 0.
+.TP
+.BR \-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
+.BR getopt (1)
+implementations).
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit. No other output is generated.
+.SH PARSING
+This section specifies the format of the second part of the
+parameters of
+.B getopt
+(the
+.I parameters
+in the
+.BR SYNOPSIS ).
+The next section
+.RB ( 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
+.B getopt
+(see the
+.BR EXAMPLES ).
+All parsing is done by the GNU
+.BR getopt (3)
+routines.
+.PP
+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.
+.PP
+A simple short option is a
+.RB ' \- '
+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.
+.PP
+It is possible to specify several short options after one
+.RB ' \- ',
+as long as all (except possibly the last) do not have required or
+optional arguments.
+.PP
+A long option normally begins with
+.RB ' \-\- '
+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
+.RB ' = ',
+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
+.RB ' = ',
+if present (if you add the
+.RB ' = '
+but nothing behind it, it is interpreted as if no argument was
+present; this is a slight bug, see the
+.BR BUGS ).
+Long options may be abbreviated, as long as the abbreviation is not
+ambiguous.
+.PP
+Each parameter not starting with a
+.RB ' \- ',
+and not a required argument of a previous option, is a non\-option
+parameter. Each parameter after a
+.RB ' \-\- '
+parameter is always interpreted as a non\-option parameter. If the
+environment variable
+.B POSIXLY_CORRECT
+is set, or if the short option string started with a
+.RB ' + ',
+all remaining parameters are interpreted as non\-option parameters as
+soon as the first non\-option parameter is found.
+.SH 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
+.I compatible
+.RI ( unquoted )
+mode, or in such way that whitespace and other special characters
+within arguments and non\-option parameters are preserved (see
+.BR 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.
+.PP
+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.
+.PP
+For a short option, a single
+.RB ' \- '
+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
+.BR getopt (1)
+implementations do not support optional arguments.
+.PP
+If several short options were specified after a single
+.RB ' \- ',
+each will be present in the output as a separate parameter.
+.PP
+For a long option,
+.RB ' \-\- '
+and the full option name are generated as one parameter. This is
+done regardless whether the option was abbreviated or specified with
+a single
+.RB ' \- '
+in the input. Arguments are handled as with short options.
+.PP
+Normally, no non\-option parameters output is generated until all
+options and their arguments have been generated. Then
+.RB ' \-\- '
+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
+.RB ' \- ',
+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
+.B SYNOPSIS
+is used; in that case all preceding occurrences of
+.RB ' \- '
+and
+.RB ' + '
+are ignored).
+.SH QUOTING
+In compatible 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
+.B eval
+command), it is split correctly into separate parameters.
+.PP
+Quoting is not enabled if the environment variable
+.B GETOPT_COMPATIBLE
+is set, if the first form of the
+.B SYNOPSIS
+is used, or if the option
+.RB ' \-u '
+is found.
+.PP
+Different shells use different quoting conventions. You can use the
+.RB ' \-s '
+option to select the shell you are using. The following shells are
+currently supported:
+.RB ' sh ',
+.RB ' bash ',
+.RB ' csh '
+and
+.RB ' 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.
+.SH "SCANNING MODES"
+The first character of the short options string may be a
+.RB ' \- '
+or a
+.RB ' + '
+to indicate a special scanning mode. If the first calling form in
+the
+.B SYNOPSIS
+is used they are ignored; the environment variable
+.B POSIXLY_CORRECT
+is still examined, though.
+.PP
+If the first character is
+.RB ' + ',
+or if the environment variable
+.B POSIXLY_CORRECT
+is set, parsing stops as soon as the first non\-option parameter
+(i.e., a parameter that does not start with a
+.RB ' \- ')
+is found that is not an option argument. The remaining parameters
+are all interpreted as non\-option parameters.
+.PP
+If the first character is a
+.RB ' \- ',
+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
+.RB ' \-\- '
+parameter has been generated. Note that this
+.RB ' \-\- '
+parameter is still generated, but it will always be the last
+parameter in this mode.
+.SH COMPATIBILITY
+This version of
+.BR 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.
+.PP
+If the first character of the first parameter of getopt is not a
+.RB ' \- ',
+.B 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
+.B POSIXLY_CORRECT
+is set.
+.PP
+The environment variable
+.B GETOPT_COMPATIBLE
+forces
+.B getopt
+into compatibility mode. Setting both this environment variable and
+.B POSIXLY_CORRECT
+offers 100% compatibility for 'difficult' programs. Usually, though,
+neither is needed.
+.PP
+In compatibility mode, leading
+.RB ' \- '
+and
+.RB ' + '
+characters in the short options string are ignored.
+.SH RETURN CODES
+.B getopt
+returns error code
+.B 0
+for successful parsing,
+.B 1
+if
+.BR getopt (3)
+returns errors,
+.B 2
+if it does not understand its own parameters,
+.B 3
+if an internal error occurs like out\-of\-memory, and
+.B 4
+if it is called with
+.BR \-T .
+.SH EXAMPLES
+Example scripts for (ba)sh and (t)csh are provided with the
+.BR getopt (1)
+distribution, and are installed in
+.I @docdir@/getopt/
+directory.
+.SH ENVIRONMENT
+.IP POSIXLY_CORRECT
+This environment variable is examined by the
+.BR 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
+.RB ' \- '.
+.IP GETOPT_COMPATIBLE
+Forces
+.B getopt
+to use the first calling format as specified in the
+.BR SYNOPSIS .
+.SH BUGS
+.BR getopt (3)
+can parse long options with optional arguments that are given an
+empty optional argument (but cannot do this for short options).
+This
+.BR getopt (1)
+treats optional arguments that are empty as if they were not present.
+.PP
+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
+.MT frodo@frodo.looijaard.name
+Frodo Looijaard
+.ME
+.SH "SEE ALSO"
+.BR bash (1),
+.BR tcsh (1),
+.BR getopt (3)
+.SH AVAILABILITY
+The getopt command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/misc-utils/getopt.c b/misc-utils/getopt.c
new file mode 100644
index 0000000..01a2df6
--- /dev/null
+++ b/misc-utils/getopt.c
@@ -0,0 +1,472 @@
+/*
+ * 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 "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;
+ }
+}
+
+
+/*
+ * 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':
+ free(ctl.optstr);
+ ctl.optstr = xstrdup(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 {
+ ctl.optstr = xstrdup(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..de26d34
--- /dev/null
+++ b/misc-utils/hardlink.1
@@ -0,0 +1,69 @@
+.TH "hardlink" "1"
+.SH NAME
+hardlink \- Consolidate duplicate files via hardlinks
+.SH SYNOPSIS
+.B hardlink
+[options]
+.RI [ directory ...]
+.SH DESCRIPTION
+This manual page documents \fBhardlink\fR, a
+program which consolidates duplicate files in one or more directories
+using hardlinks.
+.PP
+\fBhardlink\fR traverses one
+or more directories searching for duplicate files. When it finds duplicate
+files, it uses one of them as the master. It then removes all other
+duplicates and places a hardlink for each one pointing to the master file.
+This allows for conservation of disk space where multiple directories
+on a single filesystem contain many duplicate files.
+.PP
+Since hard links can only span a single filesystem, \fBhardlink\fR
+is only useful when all directories specified are on the same filesystem.
+.SH OPTIONS
+.TP
+.BR \-c , " \-\-content"
+Compare only the contents of the files being considered for consolidation.
+Disregards permission, ownership and other differences.
+.TP
+.BR \-f , " \-\-force"
+Force hardlinking across file systems.
+.TP
+.BR \-n , " \-\-dry\-run"
+Do not perform the consolidation; only print what would be changed.
+.TP
+.BR \-v , " \-\-verbose"
+Print summary after hardlinking. The option may be specified more than once. In
+this case (e.g., \fB\-vv\fR) it prints every hardlinked file and bytes saved.
+.TP
+.BR \-x , " \-\-exclude " \fIregex\fR
+Exclude files and directories matching pattern from hardlinking.
+.sp
+The optional pattern for excluding files and directories must be a PCRE2
+compatible regular expression. Only the basename of the file or directory
+is checked, not its path. Excluded directories' contents will not be examined.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.SH BUGS
+\fBhardlink\fR assumes that its target directory trees do not change from under
+it. If a directory tree does change, this may result in \fBhardlink\fR
+accessing files and/or directories outside of the intended directory tree.
+Thus, you must avoid running \fBhardlink\fR on potentially changing directory
+trees, and especially on directory trees under control of another user.
+.PP
+Historically \fBhardlink\fR silently excluded any names beginning with
+".in.", as well as any names beginning with "." followed by exactly 6
+other characters. That prior behavior can be achieved by specifying
+.br
+\-x '^(\\.in\\.|\\.[^.]{6}$)'
+.SH AUTHORS
+\fBhardlink\fR was written by Jakub Jelinek <jakub@redhat.com> and later modified by
+Ruediger Meier <ruediger.meier@ga-group.nl> and Karel Zak <kzak@redhat.com> for util-linux.
+.PP
+Man page written by Brian Long and later updated by Jindrich Novy <jnovy@redhat.com>
+.SH AVAILABILITY
+The hardlink command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c
new file mode 100644
index 0000000..2711b2a
--- /dev/null
+++ b/misc-utils/hardlink.c
@@ -0,0 +1,531 @@
+/*
+ * hardlink - consolidate duplicate files via hardlinks
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All rights reserved.
+ * Written by Jakub Jelinek <jakub@redhat.com>
+ *
+ * Copyright (C) 2019 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/types.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <string.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <errno.h>
+#ifdef HAVE_PCRE
+# define PCRE2_CODE_UNIT_WIDTH 8
+# include <pcre2.h>
+#endif
+
+#include "c.h"
+#include "xalloc.h"
+#include "nls.h"
+#include "closestream.h"
+
+#define NHASH (1<<17) /* Must be a power of 2! */
+#define NBUF 64
+
+struct hardlink_file;
+
+struct hardlink_hash {
+ struct hardlink_hash *next;
+ struct hardlink_file *chain;
+ off_t size;
+ time_t mtime;
+};
+
+struct hardlink_dir {
+ struct hardlink_dir *next;
+ char name[];
+};
+
+struct hardlink_file {
+ struct hardlink_file *next;
+ ino_t ino;
+ dev_t dev;
+ unsigned int cksum;
+ char name[];
+};
+
+struct hardlink_dynstr {
+ char *buf;
+ size_t alloc;
+};
+
+struct hardlink_ctl {
+ struct hardlink_dir *dirs;
+ struct hardlink_hash *hps[NHASH];
+ char iobuf1[BUFSIZ];
+ char iobuf2[BUFSIZ];
+ /* summary counters */
+ unsigned long long ndirs;
+ unsigned long long nobjects;
+ unsigned long long nregfiles;
+ unsigned long long ncomp;
+ unsigned long long nlinks;
+ unsigned long long nsaved;
+ /* current device */
+ dev_t dev;
+ /* flags */
+ unsigned int verbose;
+ unsigned int
+ no_link:1,
+ content_only:1,
+ force:1;
+};
+/* ctl is in global scope due use in atexit() */
+struct hardlink_ctl global_ctl;
+
+__attribute__ ((always_inline))
+static inline unsigned int hash(off_t size, time_t mtime)
+{
+ return (size ^ mtime) & (NHASH - 1);
+}
+
+__attribute__ ((always_inline))
+static inline int stcmp(struct stat *st1, struct stat *st2, int content_scope)
+{
+ if (content_scope)
+ return st1->st_size != st2->st_size;
+
+ return st1->st_mode != st2->st_mode
+ || st1->st_uid != st2->st_uid
+ || st1->st_gid != st2->st_gid
+ || st1->st_size != st2->st_size
+ || st1->st_mtime != st2->st_mtime;
+}
+
+static void print_summary(void)
+{
+ struct hardlink_ctl const *const ctl = &global_ctl;
+
+ if (!ctl->verbose)
+ return;
+
+ if (ctl->verbose > 1 && ctl->nlinks)
+ fputc('\n', stdout);
+
+ printf(_("Directories: %9lld\n"), ctl->ndirs);
+ printf(_("Objects: %9lld\n"), ctl->nobjects);
+ printf(_("Regular files: %9lld\n"), ctl->nregfiles);
+ printf(_("Comparisons: %9lld\n"), ctl->ncomp);
+ printf( "%s%9lld\n", (ctl->no_link ?
+ _("Would link: ") :
+ _("Linked: ")), ctl->nlinks);
+ printf( "%s %9lld\n", (ctl->no_link ?
+ _("Would save: ") :
+ _("Saved: ")), ctl->nsaved);
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ fputs(USAGE_HEADER, stdout);
+ printf(_(" %s [options] directory...\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, stdout);
+ puts(_("Consolidate duplicate files using hardlinks."));
+
+ fputs(USAGE_OPTIONS, stdout);
+ puts(_(" -c, --content compare only contents, ignore permission, etc."));
+ puts(_(" -n, --dry-run don't actually link anything"));
+ puts(_(" -v, --verbose print summary after hardlinking"));
+ puts(_(" -vv print every hardlinked file and summary"));
+ puts(_(" -f, --force force hardlinking across filesystems"));
+ puts(_(" -x, --exclude <regex> exclude files matching pattern"));
+
+ fputs(USAGE_SEPARATOR, stdout);
+ printf(USAGE_HELP_OPTIONS(16)); /* char offset to align option descriptions */
+ printf(USAGE_MAN_TAIL("hardlink(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+__attribute__ ((always_inline))
+static inline size_t add2(size_t a, size_t b)
+{
+ size_t sum = a + b;
+
+ if (sum < a)
+ errx(EXIT_FAILURE, _("integer overflow"));
+ return sum;
+}
+
+__attribute__ ((always_inline))
+static inline size_t add3(size_t a, size_t b, size_t c)
+{
+ return add2(add2(a, b), c);
+}
+
+static void growstr(struct hardlink_dynstr *str, size_t newlen)
+{
+ if (newlen < str->alloc)
+ return;
+ str->buf = xrealloc(str->buf, str->alloc = add2(newlen, 1));
+}
+
+static void process_path(struct hardlink_ctl *ctl, const char *name)
+{
+ struct stat st, st2, st3;
+ const size_t namelen = strlen(name);
+
+ ctl->nobjects++;
+ if (lstat(name, &st))
+ return;
+
+ if (st.st_dev != ctl->dev && !ctl->force) {
+ if (ctl->dev)
+ errx(EXIT_FAILURE,
+ _("%s is on different filesystem than the rest "
+ "(use -f option to override)."), name);
+ ctl->dev = st.st_dev;
+ }
+ if (S_ISDIR(st.st_mode)) {
+ struct hardlink_dir *dp = xmalloc(add3(sizeof(*dp), namelen, 1));
+ memcpy(dp->name, name, namelen + 1);
+ dp->next = ctl->dirs;
+ ctl->dirs = dp;
+
+ } else if (S_ISREG(st.st_mode)) {
+ int fd, i;
+ struct hardlink_file *fp, *fp2;
+ struct hardlink_hash *hp;
+ const char *n1, *n2;
+ unsigned int buf[NBUF];
+ int cksumsize = sizeof(buf);
+ unsigned int cksum;
+ time_t mtime = ctl->content_only ? 0 : st.st_mtime;
+ unsigned int hsh = hash(st.st_size, mtime);
+ off_t fsize;
+
+ ctl->nregfiles++;
+ if (ctl->verbose > 1)
+ printf("%s\n", name);
+
+ fd = open(name, O_RDONLY);
+ if (fd < 0)
+ return;
+
+ if ((size_t)st.st_size < sizeof(buf)) {
+ cksumsize = st.st_size;
+ memset(((char *)buf) + cksumsize, 0,
+ (sizeof(buf) - cksumsize) % sizeof(buf[0]));
+ }
+ if (read(fd, buf, cksumsize) != cksumsize) {
+ close(fd);
+ return;
+ }
+ cksumsize = (cksumsize + sizeof(buf[0]) - 1) / sizeof(buf[0]);
+ for (i = 0, cksum = 0; i < cksumsize; i++) {
+ if (cksum + buf[i] < cksum)
+ cksum += buf[i] + 1;
+ else
+ cksum += buf[i];
+ }
+ for (hp = ctl->hps[hsh]; hp; hp = hp->next) {
+ if (hp->size == st.st_size && hp->mtime == mtime)
+ break;
+ }
+ if (!hp) {
+ hp = xmalloc(sizeof(*hp));
+ hp->size = st.st_size;
+ hp->mtime = mtime;
+ hp->chain = NULL;
+ hp->next = ctl->hps[hsh];
+ ctl->hps[hsh] = hp;
+ }
+ for (fp = hp->chain; fp; fp = fp->next) {
+ if (fp->cksum == cksum)
+ break;
+ }
+ for (fp2 = fp; fp2 && fp2->cksum == cksum; fp2 = fp2->next) {
+ if (fp2->ino == st.st_ino && fp2->dev == st.st_dev) {
+ close(fd);
+ return;
+ }
+ }
+ for (fp2 = fp; fp2 && fp2->cksum == cksum; fp2 = fp2->next) {
+
+ if (!lstat(fp2->name, &st2) && S_ISREG(st2.st_mode) &&
+ !stcmp(&st, &st2, ctl->content_only) &&
+ st2.st_ino != st.st_ino &&
+ st2.st_dev == st.st_dev) {
+
+ int fd2 = open(fp2->name, O_RDONLY);
+ if (fd2 < 0)
+ continue;
+
+ if (fstat(fd2, &st2) || !S_ISREG(st2.st_mode)
+ || st2.st_size == 0) {
+ close(fd2);
+ continue;
+ }
+ ctl->ncomp++;
+ lseek(fd, 0, SEEK_SET);
+
+ for (fsize = st.st_size; fsize > 0;
+ fsize -= (off_t)sizeof(ctl->iobuf1)) {
+ ssize_t xsz;
+ ssize_t rsize = fsize > (ssize_t) sizeof(ctl->iobuf1) ?
+ (ssize_t) sizeof(ctl->iobuf1) : fsize;
+
+ if ((xsz = read(fd, ctl->iobuf1, rsize)) != rsize)
+ warn(_("cannot read %s"), name);
+ else if ((xsz = read(fd2, ctl->iobuf2, rsize)) != rsize)
+ warn(_("cannot read %s"), fp2->name);
+
+ if (xsz != rsize) {
+ close(fd);
+ close(fd2);
+ return;
+ }
+ if (memcmp(ctl->iobuf1, ctl->iobuf2, rsize) != 0)
+ break;
+ }
+ close(fd2);
+ if (fsize > 0)
+ continue;
+ if (lstat(name, &st3)) {
+ warn(_("cannot stat %s"), name);
+ close(fd);
+ return;
+ }
+ st3.st_atime = st.st_atime;
+ if (stcmp(&st, &st3, 0)) {
+ warnx(_("file %s changed underneath us"), name);
+ close(fd);
+ return;
+ }
+ n1 = fp2->name;
+ n2 = name;
+
+ if (!ctl->no_link) {
+ const char *suffix =
+ ".$$$___cleanit___$$$";
+ const size_t suffixlen = strlen(suffix);
+ size_t n2len = strlen(n2);
+ struct hardlink_dynstr nam2 = { NULL, 0 };
+
+ growstr(&nam2, add2(n2len, suffixlen));
+ memcpy(nam2.buf, n2, n2len);
+ memcpy(&nam2.buf[n2len], suffix,
+ suffixlen + 1);
+ /* First create a temporary link to n1 under a new name */
+ if (link(n1, nam2.buf)) {
+ warn(_("failed to hardlink %s to %s (create temporary link as %s failed)"),
+ n1, n2, nam2.buf);
+ free(nam2.buf);
+ continue;
+ }
+ /* Then rename into place over the existing n2 */
+ if (rename(nam2.buf, n2)) {
+ warn(_("failed to hardlink %s to %s (rename temporary link to %s failed)"),
+ n1, n2, n2);
+ /* Something went wrong, try to remove the now redundant temporary link */
+ if (unlink(nam2.buf))
+ warn(_("failed to remove temporary link %s"), nam2.buf);
+ free(nam2.buf);
+ continue;
+ }
+ free(nam2.buf);
+ }
+ ctl->nlinks++;
+ if (st3.st_nlink > 1) {
+ /* We actually did not save anything this time, since the link second argument
+ had some other links as well. */
+ if (ctl->verbose > 1)
+ printf(_(" %s %s to %s\n"),
+ (ctl->no_link ? _("Would link") : _("Linked")),
+ n1, n2);
+ } else {
+ ctl->nsaved += ((st.st_size + 4095) / 4096) * 4096;
+ if (ctl->verbose > 1)
+ printf(_(" %s %s to %s, %s %jd\n"),
+ (ctl->no_link ? _("Would link") : _("Linked")),
+ n1, n2,
+ (ctl->no_link ? _("would save") : _("saved")),
+ (intmax_t)st.st_size);
+ }
+ close(fd);
+ return;
+ }
+ }
+ fp2 = xmalloc(add3(sizeof(*fp2), namelen, 1));
+ close(fd);
+ fp2->ino = st.st_ino;
+ fp2->dev = st.st_dev;
+ fp2->cksum = cksum;
+ memcpy(fp2->name, name, namelen + 1);
+
+ if (fp) {
+ fp2->next = fp->next;
+ fp->next = fp2;
+ } else {
+ fp2->next = hp->chain;
+ hp->chain = fp2;
+ }
+ return;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int ch;
+ int i;
+#ifdef HAVE_PCRE
+ int errornumber;
+ PCRE2_SIZE erroroffset;
+ pcre2_code *re = NULL;
+ PCRE2_SPTR exclude_pattern = NULL;
+ pcre2_match_data *match_data = NULL;
+#endif
+ struct hardlink_dynstr nam1 = { NULL, 0 };
+ struct hardlink_ctl *ctl = &global_ctl;
+
+ static const struct option longopts[] = {
+ { "content", no_argument, NULL, 'c' },
+ { "dry-run", no_argument, NULL, 'n' },
+ { "exclude", required_argument, NULL, 'x' },
+ { "force", no_argument, NULL, 'f' },
+ { "help", no_argument, NULL, 'h' },
+ { "verbose", no_argument, NULL, 'v' },
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 },
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((ch = getopt_long(argc, argv, "cnvfx:Vh", longopts, NULL)) != -1) {
+ switch (ch) {
+ case 'n':
+ ctl->no_link = 1;
+ break;
+ case 'v':
+ ctl->verbose++;
+ break;
+ case 'c':
+ ctl->content_only = 1;
+ break;
+ case 'f':
+ ctl->force = 1;
+ break;
+ case 'x':
+#ifdef HAVE_PCRE
+ exclude_pattern = (PCRE2_SPTR) optarg;
+#else
+ errx(EXIT_FAILURE,
+ _("option --exclude not supported (built without pcre2)"));
+#endif
+ break;
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (optind == argc) {
+ warnx(_("no directory specified"));
+ errtryhelp(EXIT_FAILURE);
+ }
+
+#ifdef HAVE_PCRE
+ if (exclude_pattern) {
+ re = pcre2_compile(exclude_pattern, /* the pattern */
+ PCRE2_ZERO_TERMINATED, /* indicates pattern is zero-terminate */
+ 0, /* default options */
+ &errornumber, &erroroffset, NULL); /* use default compile context */
+ if (!re) {
+ PCRE2_UCHAR buffer[256];
+ pcre2_get_error_message(errornumber, buffer,
+ sizeof(buffer));
+ errx(EXIT_FAILURE, _("pattern error at offset %d: %s"),
+ (int)erroroffset, buffer);
+ }
+ match_data = pcre2_match_data_create_from_pattern(re, NULL);
+ }
+#endif
+ atexit(print_summary);
+
+ for (i = optind; i < argc; i++)
+ process_path(ctl, argv[i]);
+
+ while (ctl->dirs) {
+ DIR *dh;
+ struct dirent *di;
+ struct hardlink_dir *dp = ctl->dirs;
+ size_t nam1baselen = strlen(dp->name);
+
+ ctl->dirs = dp->next;
+ growstr(&nam1, add2(nam1baselen, 1));
+ memcpy(nam1.buf, dp->name, nam1baselen);
+ free(dp);
+ nam1.buf[nam1baselen++] = '/';
+ nam1.buf[nam1baselen] = 0;
+ dh = opendir(nam1.buf);
+
+ if (dh == NULL)
+ continue;
+ ctl->ndirs++;
+
+ while ((di = readdir(dh)) != NULL) {
+ if (!di->d_name[0])
+ continue;
+ if (di->d_name[0] == '.') {
+ if (!di->d_name[1] || !strcmp(di->d_name, ".."))
+ continue;
+ }
+#ifdef HAVE_PCRE
+ if (re && pcre2_match(re, /* compiled regex */
+ (PCRE2_SPTR) di->d_name, strlen(di->d_name), 0, /* start at offset 0 */
+ 0, /* default options */
+ match_data, /* block for storing the result */
+ NULL) /* use default match context */
+ >=0) {
+ if (ctl->verbose) {
+ nam1.buf[nam1baselen] = 0;
+ printf(_("Skipping %s%s\n"), nam1.buf, di->d_name);
+ }
+ continue;
+ }
+#endif
+ {
+ size_t subdirlen;
+ growstr(&nam1,
+ add2(nam1baselen, subdirlen =
+ strlen(di->d_name)));
+ memcpy(&nam1.buf[nam1baselen], di->d_name,
+ add2(subdirlen, 1));
+ }
+ process_path(ctl, nam1.buf);
+ }
+ closedir(dh);
+ }
+#ifdef HAVE_PCRE
+ pcre2_code_free(re);
+ pcre2_match_data_free(match_data);
+#endif
+ return 0;
+}
diff --git a/misc-utils/kill.1 b/misc-utils/kill.1
new file mode 100644
index 0000000..5e0ebaf
--- /dev/null
+++ b/misc-utils/kill.1
@@ -0,0 +1,240 @@
+.\" 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
+.TH KILL 1 "November 2019" "util-linux" "User Commands"
+.SH NAME
+kill \- terminate a process
+.SH SYNOPSIS
+.B kill
+.RB [ \- \fIsignal\fR| \-s
+.IR signal | \fB\-p\fP ]
+.RB [ \-q
+.IR value ]
+.RB [ \-a ]
+\fR[\fB\-\-timeout \fImilliseconds signal\fR]
+.RB [ \-\- ]
+.IR pid | name ...
+.br
+.B kill \-l
+.RI [ number ]
+.RB "| " \-L
+.SH DESCRIPTION
+The command
+.B kill
+sends the specified \fIsignal\fR to the specified processes or process groups.
+.PP
+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.
+.PP
+Most modern shells have a builtin
+.B kill
+command, with a usage rather similar to
+that of the command described here. The
+.BR \-\-all ,
+.BR \-\-pid ", and"
+.B \-\-queue
+options, and the possibility to specify processes by command name, are local extensions.
+.PP
+If \fIsignal\fR is 0, then no actual signal is sent, but error checking is still performed.
+
+.SH ARGUMENTS
+The list of processes to be signaled can be a mixture of names and PIDs.
+.TP
+.I pid
+Each
+.I pid
+can be expressed in one of the following ways:
+.RS
+.TP
+.I n
+where
+.I n
+is larger than 0. The process with PID
+.I n
+is signaled.
+.TP
+.B 0
+All processes in the current process group are signaled.
+.TP
+.B \-1
+All processes with a PID larger than 1 are signaled.
+.TP
+.BI \- n
+where
+.I n
+is larger than 1. All processes in process group
+.I 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.
+.RE
+.TP
+.I name
+All processes invoked using this \fIname\fR will be signaled.
+
+.SH OPTIONS
+.TP
+\fB\-s\fR, \fB\-\-signal\fR \fIsignal\fR
+The signal to send. It may be given as a name or a number.
+.TP
+\fB\-l\fR, \fB\-\-list\fR [\fInumber\fR]
+Print a list of signal names, or convert the given signal number to a name.
+The signals can be found in
+.IR /usr/\:include/\:linux/\:signal.h .
+.TP
+\fB\-L\fR, \fB\-\-table\fR
+Similar to \fB\-l\fR, but it will print signal names and their corresponding
+numbers.
+.TP
+\fB\-a\fR, \fB\-\-all\fR
+Do not restrict the command-name-to-PID conversion to processes with the same
+UID as the present process.
+.TP
+\fB\-p\fR, \fB\-\-pid\fR
+Only print the process ID (PID) of the named processes, do not send any
+signals.
+.TP
+\fB\-\-verbose\fR
+Print PID(s) that will be signaled with
+.B kill
+along with the signal.
+.TP
+\fB\-q\fR, \fB\-\-queue\fR \fIvalue\fR
+Send the signal using
+.BR sigqueue (3)
+rather than
+.BR kill (2).
+The
+.I 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
+.B SA_SIGINFO
+flag to
+.BR sigaction (2),
+then it can obtain this data via the
+.I si_sigval
+field of the
+.I siginfo_t
+structure.
+.TP
+\fB\-\-timeout\fR \fImilliseconds signal\fR
+Send a signal defined in the usual way to a process,
+followed by an additional signal after a specified delay.
+The
+.B \-\-timeout
+option causes
+.B kill
+to wait for a period defined in
+.I milliseconds
+before sending a follow-up
+.I 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.
+.IP
+Note that the operating system may re-use PIDs and implementing an
+equivalent feature in a shell using
+.B kill
+and
+.B sleep
+would be subject to races whereby the follow-up signal might be sent
+to a different process that used a recycled PID.
+.IP
+The
+.B \-\-timeout
+option can be specified multiple times: the signals are sent
+sequentially with the specified timeouts. The
+.B \-\-timeout
+option can be combined with the
+.B \-\-queue
+option.
+.IP
+As an example, the following command sends
+the signals QUIT, TERM and KILL in sequence and waits for 1000
+milliseconds between sending the signals:
+.IP
+.in +4n
+.EX
+kill \-\-verbose \-\-timeout 1000 TERM \-\-timeout 1000 KILL \e
+ \-\-signal QUIT 12345
+.EE
+.in
+.SH EXIT STATUS
+.B kill
+has the following exit status values:
+.PP
+.RS
+.PD 0
+.TP
+.B 0
+success
+.TP
+.B 1
+failure
+.TP
+.B 64
+partial success (when more than one process specified)
+.PD
+.RE
+.SH NOTES
+Although it is possible to specify the TID (thread ID, see
+.BR gettid (2))
+of one of the threads in a multithreaded process as the argument of
+.BR 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
+.BR signal (7)
+and the description of
+.B CLONE_THREAD
+in
+.BR clone (2).
+.P
+Various shells provide a builtin
+.B kill
+command that is
+preferred in relation to the
+.BR 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:
+.B "/bin/kill \-\-version"
+.SH AUTHORS
+.MT svalente@mit.edu
+Salvatore Valente
+.ME
+.br
+.MT kzak@redhat.com
+Karel Zak
+.ME
+.br
+.PP
+The original version was taken from BSD 4.4.
+
+.SH SEE ALSO
+.BR bash (1),
+.BR tcsh (1),
+.BR sigaction (2),
+.BR kill (2),
+.BR sigqueue (3),
+.BR signal (7)
+
+.SH AVAILABILITY
+The kill command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/misc-utils/kill.c b/misc-utils/kill.c
new file mode 100644
index 0000000..9cfb03c
--- /dev/null
+++ b/misc-utils/kill.c
@@ -0,0 +1,513 @@
+/*
+ * 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 "procutils.h"
+#include "signames.h"
+#include "strutils.h"
+#include "ttyutils.h"
+#include "xalloc.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,
+ 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)) {
+ numsig = strtol(arg, &ep, 10);
+ if (NSIG <= numsig && maskbit && (numsig & 128) != 0)
+ numsig -= 128;
+ if (*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(_(" --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, "-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;
+}
+
+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 (kill_verbose(&ctl) != 0)
+ nerrs++;
+ ct++;
+ } else {
+ struct proc_processes *ps = proc_open_processes();
+ int found = 0;
+
+ if (!ps)
+ continue;
+ if (!ctl.check_all)
+ proc_processes_filter_by_uid(ps, getuid());
+
+ proc_processes_filter_by_name(ps, ctl.arg);
+ while (proc_next_pid(ps, &ctl.pid) == 0) {
+ if (kill_verbose(&ctl) != 0)
+ nerrs++;
+ ct++;
+ found = 1;
+ }
+ proc_close_processes(ps);
+
+ 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..448311d
--- /dev/null
+++ b/misc-utils/logger.1
@@ -0,0 +1,383 @@
+.\" 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
+.\"
+.TH LOGGER "1" "November 2015" "util-linux" "User Commands"
+.SH NAME
+logger \- enter messages into the system log
+.SH SYNOPSIS
+.B logger
+[options]
+.RI [ message ]
+.SH DESCRIPTION
+.B logger
+makes entries in the system log.
+.sp
+When the optional \fImessage\fR argument is present, it is written
+to the log. If it is not present, and the \fB\-f\fR option is not
+given either, then standard input is logged.
+.SH OPTIONS
+.TP
+.BR \-d , " \-\-udp"
+Use datagrams (UDP) only. By default the connection is tried to the
+syslog port defined in /etc/services, which is often 514 .
+.sp
+See also \fB\-\-server\fR and \fB\-\-socket\fR to specify where to connect.
+.TP
+.BR \-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 \fB\-\-prio\-prefix\fR 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>\fR).
+.TP
+.BR \-f , " \-\-file " \fIfile
+Log the contents of the specified \fIfile\fR.
+This option cannot be combined with a command-line message.
+.TP
+.B \-i
+Log the PID of the logger process with each line.
+.TP
+.BR "\-\-id" [ =\fIid ]
+Log the PID of the logger process with each line. When the optional
+argument \fIid\fR is specified, then it is used instead of the logger
+command's PID. The use of \fB\-\-id=$$\fR
+(PPID) is recommended in scripts that send several messages.
+
+Note that the system logging infrastructure (for example \fBsystemd\fR when
+listening on /dev/log) may follow local socket credentials to overwrite the
+PID specified in the message.
+.BR logger (1)
+is able to set those socket credentials to the given \fIid\fR, 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.
+.TP
+.BR \-\-journald [ =\fIfile ]
+Write a systemd journal entry. The entry is read from the given \fIfile\fR,
+when specified, otherwise from standard input.
+Each line must begin with a field that is accepted by journald; see
+.BR 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:
+.IP
+.nf
+\fB logger \-\-journald <<end
+\fB MESSAGE_ID=67feb6ffbaf24c5cbec13c008dd72309
+\fB MESSAGE=The dogs bark, but the caravan goes on.
+\fB DOGS=bark
+\fB CARAVAN=goes on
+\fB end
+.IP
+\fB logger \-\-journald=entry.txt
+.fi
+.IP
+Notice that
+.B \-\-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
+.B journalctl
+will display MESSAGE field. Use
+.B journalctl \-\-output json-pretty
+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.
+.TP
+.BI \-\-msgid " msgid"
+Sets the RFC5424 MSGID field. Note that the space character is not permitted
+inside of \fImsgid\fR. This option is only used if \fB\-\-rfc5424\fR is
+specified as well; otherwise, it is silently ignored.
+.TP
+.BR \-n , " \-\-server " \fIserver
+Write to the specified remote syslog \fIserver\fR
+instead of to the system log socket. Unless
+\fB\-\-udp\fR or \fB\-\-tcp\fR
+is specified, \fBlogger\fR will first try to use UDP,
+but if this fails a TCP connection is attempted.
+.TP
+.B \-\-no\-act
+Causes everything to be done except for writing the log message to the system
+log, and removing the connection or the journal. This option can be used
+together with \fB\-\-stderr\fR for testing purposes.
+.TP
+.B \-\-octet\-count
+Use the 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.
+.TP
+.BR \-P , " \-\-port " \fIport
+Use the specified \fIport\fR. When this option is not specified, the
+port defaults to syslog for udp and to syslog-conn for tcp connections.
+.TP
+.BR \-p , " \-\-priority " \fIpriority
+Enter the message into the log with the specified \fIpriority\fR.
+The priority may be specified numerically or as a
+.IR facility . level
+pair.
+For example, \fB\-p local3.info\fR
+logs the message as informational in the local3 facility.
+The default is \fBuser.notice\fR.
+.TP
+.B \-\-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, \fBlocal0.info\fR,
+meaning facility=16 and level=6, becomes \fB<134>\fR.
+.sp
+If the prefix contains no facility, the facility defaults to what is
+specified by the \fB\-p\fR option. Similarly, if no prefix is provided,
+the line is logged using the \fIpriority\fR given with \fB\-p\fR.
+.sp
+This option doesn't affect a command-line message.
+.TP
+.B \-\-rfc3164
+Use the RFC 3164 BSD syslog protocol to submit messages to a remote server.
+.TP
+.BR \-\-rfc5424 [ =\fIwithout ]
+Use the RFC 5424 syslog protocol to submit messages to a remote server.
+The optional \fIwithout\fR argument can be a comma-separated list of
+the following values: \fBnotq\fR, \fBnotime\fR, \fBnohost\fR.
+
+The \fBnotq\fR 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\fR is specified.
+
+The \fBnotime\fR value (which implies \fBnotq\fR)
+suppresses the complete sender timestamp that is in
+ISO-8601 format, including microseconds and timezone.
+
+The \fBnohost\fR value suppresses
+.BR gethostname (2)
+information from the message header.
+.IP
+The RFC 5424 protocol has been the default for
+.B logger
+since version 2.26.
+.TP
+.BR \-s , " \-\-stderr"
+Output the message to standard error as well as to the system log.
+.TP
+.BR "\-\-sd\-id \fIname" [ @\fIdigits ]
+Specifies a structured data element ID for an RFC 5424 message header. The
+option has to be used before \fB\-\-sd\-param\fR to introduce a new element.
+The number of structured data elements is unlimited. The ID (\fIname\fR plus
+possibly \fB@\fIdigits\fR) 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@\fIdigits\fR part is required for user-defined
+non-standardized IDs.
+
+\fBlogger\fR currently generates the \fBtimeQuality\fR standardized element
+only. RFC 5424 also describes the elements \fBorigin\fR (with parameters
+ip, enterpriseId, software and swVersion) and \fBmeta\fR (with parameters
+sequenceId, sysUpTime and language).
+These element IDs may be specified without the \fB@\fIdigits\fR suffix.
+
+.TP
+.BR "\-\-sd\-param " \fIname ="\fIvalue\fB"
+Specifies a structured data element parameter, a name and value pair.
+The option has to be used after \fB\-\-sd\-id\fR and may be specified more
+than once for the same element. Note that the quotation marks around
+\fIvalue\fR are required and must be escaped on the command line.
+.IP
+.nf
+\fB logger \-\-rfc5424 \-\-sd-id zoo@123 \\
+\fB \-\-sd-param tiger=\\"hungry\\" \\
+\fB \-\-sd-param zebra=\\"running\\" \\
+\fB \-\-sd-id manager@123 \\
+\fB \-\-sd-param onMeeting=\\"yes\\" \\
+\fB "this is message"
+.fi
+.IP
+produces:
+.IP
+.\".nf
+.\" this long line gets cut of in the output of "troff", and wraps
+.\" in "nroff"
+\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
+.\".fi
+.TP
+.BR \-S , " \-\-size " \fIsize
+Sets the maximum permitted message size to \fIsize\fR. 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 \fB\-\-size\fR option affects logger in
+all cases (not only when \fB\-\-rfc5424\fR 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.
+
+.TP
+.BR \-\-socket\-errors [ =\fImode ]
+Print errors about Unix socket connections. The \fImode\fR can be a value of
+\fBoff\fR, \fBon\fR, or \fBauto\fR. When the mode is auto logger will detect
+if the init process is systemd, 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
+.BR openlog (3)
+system call. The
+.BR logger (1)
+before version 2.26 used openlog, and hence was unable to detected loss of
+messages sent to Unix sockets.
+.IP
+The default mode is \fBauto\fR. When errors are not enabled lost messages are
+not communicated and will result to successful exit status of
+.BR logger (1)
+invocation.
+.TP
+.BR \-T , " \-\-tcp"
+Use stream (TCP) only. By default the connection is tried to the
+.I syslog-conn
+port defined in /etc/services, which is often
+.IR 601 .
+.sp
+See also \fB\-\-server\fR and \fB\-\-socket\fR to specify where to connect.
+.TP
+.BR \-t , " \-\-tag " \fItag
+Mark every line to be logged with the specified
+.IR tag .
+The default tag is the name of the user logged in on the terminal (or a user
+name based on effective user ID).
+.TP
+.BR \-u , " \-\-socket " \fIsocket
+Write to the specified
+.I socket
+instead of to the system log socket.
+.TP
+.B \-\-
+End the argument list. This allows the \fImessage\fR
+to start with a hyphen (\-).
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH EXIT STATUS
+The
+.B logger
+utility exits 0 on success, and >0 if an error occurs.
+.SH FACILITIES AND LEVELS
+Valid facility names are:
+.IP
+.nr WI \n(.lu-\n(.iu-\w'\fBauthpriv\fR'u-3n
+.TS
+tab(:);
+l lw(\n(WIu).
+\fBauth
+\fBauthpriv\fR:for security information of a sensitive nature
+\fBcron
+\fBdaemon
+\fBftp
+\fBkern\fR:T{
+cannot be generated from userspace process, automatically converted to \fBuser
+T}
+\fBlpr
+\fBmail
+\fBnews
+\fBsyslog
+\fBuser
+\fBuucp
+\fBlocal0
+ to:
+\fBlocal7
+\fBsecurity\fR:deprecated synonym for \fBauth
+.TE
+.PP
+Valid level names are:
+.IP
+.TS
+tab(:);
+l l.
+\fBemerg
+\fBalert
+\fBcrit
+\fBerr
+\fBwarning
+\fBnotice
+\fBinfo
+\fBdebug
+\fBpanic\fR:deprecated synonym for \fBemerg
+\fBerror\fR:deprecated synonym for \fBerr
+\fBwarn\fR:deprecated synonym for \fBwarning
+.TE
+.PP
+For the priority order and intended purposes of these facilities and levels, see
+.BR syslog (3).
+.SH CONFORMING TO
+The
+.B logger
+command is expected to be IEEE Std 1003.2 ("POSIX.2") compatible.
+.SH EXAMPLES
+.B logger System rebooted
+.br
+.B logger \-p local0.notice \-t HOSTIDM \-f /dev/idmc
+.br
+.B logger \-n loghost.example.com System rebooted
+.SH AUTHORS
+The
+.B logger
+command
+was originally written by University of California in 1983-1993 and later
+rewritten by
+.MT kzak@redhat.com
+Karel Zak
+.ME ,
+.MT rgerhards@adiscon.com
+Rainer Gerhards
+.ME
+and
+.MT kerolasa@iki.fi
+Sami Kerola
+.ME .
+.SH SEE ALSO
+.BR journalctl (1),
+.BR syslog (3),
+.BR systemd.journal-fields (7)
+.SH AVAILABILITY
+The logger command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/misc-utils/logger.c b/misc-utils/logger.c
new file mode 100644
index 0000000..d51cd59
--- /dev/null
+++ b/misc-utils/logger.c
@@ -0,0 +1,1316 @@
+/*
+ * 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"
+
+#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 *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
+
+static char const *xgetlogin(void)
+{
+ char const *cp;
+ struct passwd *pw;
+
+ if (!(cp = getlogin()) || !*cp)
+ cp = (pw = getpwuid(geteuid()))? pw->pw_name : "<someone>";
+ return cp;
+}
+
+/* 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;
+#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 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 = strappend(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 = strappend(sys, usr);
+ free(sys);
+ free(usr);
+ } else
+ res = sys ? sys : usr;
+
+ return res;
+}
+
+static int valid_structured_data_param(const char *str)
+{
+ char *eq = strchr(str, '='),
+ *qm1 = strchr(str, '"'),
+ *qm2 = qm1 ? strchr(qm1 + 1, '"') : NULL;
+
+ if (!eq || !qm1 || !qm2) /* something is missing */
+ return 0;
+
+ /* 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 = xgetlogin();
+
+ generate_syslog_header(ctl);
+}
+
+/* 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.
+ * The initial header is generated by logger_open().
+ */
+ int has_header = 1;
+ int default_priority = ctl->pri;
+ int last_pri = default_priority;
+ size_t max_usrmsg_size = ctl->max_message_size - strlen(ctl->hdr);
+ char *const buf = xmalloc(max_usrmsg_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 < 8) /* kern facility is forbidden */
+ pri |= 8;
+ ctl->pri = pri;
+ } else
+ ctl->pri = default_priority;
+
+ if (ctl->pri != last_pri) {
+ has_header = 0;
+ max_usrmsg_size =
+ ctl->max_message_size - strlen(ctl->hdr);
+ last_pri = ctl->pri;
+ }
+ if (c != EOF && c != '\n')
+ c = getchar();
+ }
+
+ while (c != EOF && c != '\n' && i < max_usrmsg_size) {
+ buf[i++] = c;
+ c = getchar();
+ }
+ buf[i] = '\0';
+
+ if (i > 0 || !ctl->skip_empty_lines) {
+ if (!has_header)
+ generate_syslog_header(ctl);
+ write_output(ctl, buf);
+ has_header = 0;
+ }
+
+ 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);
+}
+
+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)
+ 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..2de4f8d
--- /dev/null
+++ b/misc-utils/look.1
@@ -0,0 +1,123 @@
+.\" 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
+.\"
+.TH LOOK 1 "June 2011" "util-linux" "User Commands"
+.SH NAME
+look \- display lines beginning with a given string
+.SH SYNOPSIS
+.B look
+[options]
+.IR "string " [ file ]
+.SH DESCRIPTION
+The
+.B look
+utility displays any lines in
+.I file
+which contain
+.IR string .
+As
+.B look
+performs a binary search, the lines in
+.I file
+must be sorted (where
+.BR sort (1)
+was given the same options
+.BR "\-d " and/or " \-f " that
+.B look
+is invoked with).
+.PP
+If
+.I file
+is not specified, the file
+.I /usr/share/dict/words
+is used, only alphanumeric characters are compared and the case of
+alphabetic characters is ignored.
+.SH OPTIONS
+.TP
+.BR \-a , " \-\-alternative"
+Use the alternative dictionary file.
+.TP
+.BR \-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 \fBsort \-d\fR command since version 2.28.
+.TP
+.BR \-f , " \-\-ignore\-case"
+Ignore the case of alphabetic characters. This is on by default if no file is
+specified.
+.TP
+.BR \-t , " \-\-terminate " \fIcharacter\fR
+Specify a string termination character, i.e., only the characters
+in \fIstring\fR up to and including the first occurrence of \fIcharacter\fR
+are compared.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.PP
+The
+.B 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.
+.SH ENVIRONMENT
+.TP
+.B WORDLIST
+Path to a dictionary file. The environment variable has greater priority
+than the dictionary path defined in FILES segment.
+.SH FILES
+.IP "\fB/usr/share/dict/words\fR" 4
+the dictionary
+.IP "\fB/usr/share/dict/web2\fR" 4
+the alternative dictionary
+.SH HISTORY
+The
+.B look
+utility appeared in Version 7 AT&T Unix.
+.SH EXAMPLES
+.RS
+.nf
+sort \-d /etc/passwd \-o /tmp/look.dict
+look \-t: root:foobar /tmp/look.dict
+.fi
+.RE
+.SH SEE ALSO
+.BR grep (1),
+.BR sort (1)
+.SH AVAILABILITY
+The look command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/misc-utils/look.c b/misc-utils/look.c
new file mode 100644
index 0000000..d3f0622
--- /dev/null
+++ b/misc-utils/look.c
@@ -0,0 +1,375 @@
+/*-
+ * 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();
+
+ setlocale(LC_ALL, "");
+
+ 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..4d15f70
--- /dev/null
+++ b/misc-utils/lsblk-devtree.c
@@ -0,0 +1,472 @@
+/*
+ * These functions implement tree of block devices. The devtree struct contains
+ * two basic lists:
+ *
+ * 1) devtree->devices -- This is simple list without any hierarchy. We use
+ * reference counting here.
+ *
+ * 2) devtree->roots -- The root nodes of the trees. The code does not use
+ * reference counting here due to complexity and it's unnecessary.
+ *
+ * Note that the same device maybe have more parents and more children. The
+ * device is allocated only once and shared within the tree. The dependence
+ * (devdep struct) contains reference to child as well as to parent and the
+ * dependence is reference by ls_childs from parent device and by ls_parents
+ * from child. (Yes, "childs" is used for children ;-)
+ *
+ * Copyright (C) 2018 Karel Zak <kzak@redhat.com>
+ */
+#include "lsblk.h"
+#include "sysfs.h"
+
+
+void lsblk_reset_iter(struct lsblk_iter *itr, int direction)
+{
+ if (direction == -1)
+ direction = itr->direction;
+
+ memset(itr, 0, sizeof(*itr));
+ itr->direction = direction;
+}
+
+struct lsblk_device *lsblk_new_device()
+{
+ struct lsblk_device *dev;
+
+ dev = calloc(1, sizeof(*dev));
+ if (!dev)
+ return NULL;
+
+ dev->refcount = 1;
+ dev->removable = -1;
+ dev->discard_granularity = (uint64_t) -1;
+
+ INIT_LIST_HEAD(&dev->childs);
+ INIT_LIST_HEAD(&dev->parents);
+ INIT_LIST_HEAD(&dev->ls_roots);
+ INIT_LIST_HEAD(&dev->ls_devices);
+
+ DBG(DEV, ul_debugobj(dev, "alloc"));
+ return dev;
+}
+
+void lsblk_ref_device(struct lsblk_device *dev)
+{
+ if (dev)
+ dev->refcount++;
+}
+
+/* removes dependence from child as well as from parent */
+static int remove_dependence(struct lsblk_devdep *dep)
+{
+ if (!dep)
+ return -EINVAL;
+
+ DBG(DEP, ul_debugobj(dep, " dealloc"));
+
+ list_del_init(&dep->ls_childs);
+ list_del_init(&dep->ls_parents);
+
+ free(dep);
+ return 0;
+}
+
+static int device_remove_dependences(struct lsblk_device *dev)
+{
+ if (!dev)
+ return -EINVAL;
+
+ if (!list_empty(&dev->childs))
+ DBG(DEV, ul_debugobj(dev, " %s: remove all children deps", dev->name));
+ while (!list_empty(&dev->childs)) {
+ struct lsblk_devdep *dp = list_entry(dev->childs.next,
+ struct lsblk_devdep, ls_childs);
+ remove_dependence(dp);
+ }
+
+ if (!list_empty(&dev->parents))
+ DBG(DEV, ul_debugobj(dev, " %s: remove all parents deps", dev->name));
+ while (!list_empty(&dev->parents)) {
+ struct lsblk_devdep *dp = list_entry(dev->parents.next,
+ struct lsblk_devdep, ls_parents);
+ remove_dependence(dp);
+ }
+
+ return 0;
+}
+
+void lsblk_unref_device(struct lsblk_device *dev)
+{
+ if (!dev)
+ return;
+
+ if (--dev->refcount <= 0) {
+ DBG(DEV, ul_debugobj(dev, " freeing [%s] <<", dev->name));
+
+ device_remove_dependences(dev);
+ lsblk_device_free_properties(dev->properties);
+
+ lsblk_unref_device(dev->wholedisk);
+
+ free(dev->dm_name);
+ free(dev->filename);
+ free(dev->mountpoint);
+ free(dev->dedupkey);
+
+ ul_unref_path(dev->sysfs);
+
+ DBG(DEV, ul_debugobj(dev, " >> dealloc [%s]", dev->name));
+ free(dev->name);
+ free(dev);
+ }
+}
+
+int lsblk_device_has_child(struct lsblk_device *dev, struct lsblk_device *child)
+{
+ struct lsblk_device *x = NULL;
+ struct lsblk_iter itr;
+
+ lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD);
+
+ while (lsblk_device_next_child(dev, &itr, &x) == 0) {
+ if (x == child)
+ return 1;
+ }
+
+ return 0;
+}
+
+int lsblk_device_new_dependence(struct lsblk_device *parent, struct lsblk_device *child)
+{
+ struct lsblk_devdep *dp;
+
+ if (!parent || !child)
+ return -EINVAL;
+
+ if (lsblk_device_has_child(parent, child))
+ return 1;
+
+ dp = calloc(1, sizeof(*dp));
+ if (!dp)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&dp->ls_childs);
+ INIT_LIST_HEAD(&dp->ls_parents);
+
+ dp->child = child;
+ list_add_tail(&dp->ls_childs, &parent->childs);
+
+ dp->parent = parent;
+ list_add_tail(&dp->ls_parents, &child->parents);
+
+ DBG(DEV, ul_debugobj(parent, "add dependence 0x%p [%s->%s]", dp, parent->name, child->name));
+
+ return 0;
+}
+
+static int device_next_child(struct lsblk_device *dev,
+ struct lsblk_iter *itr,
+ struct lsblk_devdep **dp)
+{
+ int rc = 1;
+
+ if (!dev || !itr || !dp)
+ return -EINVAL;
+ *dp = NULL;
+
+ if (!itr->head)
+ LSBLK_ITER_INIT(itr, &dev->childs);
+ if (itr->p != itr->head) {
+ LSBLK_ITER_ITERATE(itr, *dp, struct lsblk_devdep, ls_childs);
+ rc = 0;
+ }
+
+ return rc;
+}
+
+int lsblk_device_next_child(struct lsblk_device *dev,
+ struct lsblk_iter *itr,
+ struct lsblk_device **child)
+{
+ struct lsblk_devdep *dp = NULL;
+ int rc = device_next_child(dev, itr, &dp);
+
+ if (!child)
+ return -EINVAL;
+
+ *child = rc == 0 ? dp->child : NULL;
+ return rc;
+}
+
+int lsblk_device_is_last_parent(struct lsblk_device *dev, struct lsblk_device *parent)
+{
+ struct lsblk_devdep *dp = list_last_entry(
+ &dev->parents,
+ struct lsblk_devdep, ls_parents);
+ 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()
+{
+ struct lsblk_devtree *tr;
+
+ tr = calloc(1, sizeof(*tr));
+ if (!tr)
+ return NULL;
+
+ tr->refcount = 1;
+
+ INIT_LIST_HEAD(&tr->roots);
+ INIT_LIST_HEAD(&tr->devices);
+
+ DBG(TREE, ul_debugobj(tr, "alloc"));
+ return tr;
+}
+
+void lsblk_ref_devtree(struct lsblk_devtree *tr)
+{
+ if (tr)
+ tr->refcount++;
+}
+
+void lsblk_unref_devtree(struct lsblk_devtree *tr)
+{
+ if (!tr)
+ return;
+
+ if (--tr->refcount <= 0) {
+ DBG(TREE, ul_debugobj(tr, "dealloc"));
+
+ while (!list_empty(&tr->devices)) {
+ struct lsblk_device *dev = list_entry(tr->devices.next,
+ struct lsblk_device, ls_devices);
+ lsblk_devtree_remove_device(tr, dev);
+ }
+ free(tr);
+ }
+}
+
+int lsblk_devtree_add_root(struct lsblk_devtree *tr, struct lsblk_device *dev)
+{
+ if (!lsblk_devtree_has_device(tr, dev))
+ lsblk_devtree_add_device(tr, dev);
+
+ /* We don't increment reference counter for tr->roots list. The primary
+ * reference is tr->devices */
+
+ DBG(TREE, ul_debugobj(tr, "add root device 0x%p [%s]", dev, dev->name));
+ list_add_tail(&dev->ls_roots, &tr->roots);
+ return 0;
+}
+
+int lsblk_devtree_next_root(struct lsblk_devtree *tr,
+ struct lsblk_iter *itr,
+ struct lsblk_device **dev)
+{
+ int rc = 1;
+
+ if (!tr || !itr || !dev)
+ return -EINVAL;
+ *dev = NULL;
+ if (!itr->head)
+ LSBLK_ITER_INIT(itr, &tr->roots);
+ if (itr->p != itr->head) {
+ LSBLK_ITER_ITERATE(itr, *dev, struct lsblk_device, ls_roots);
+ rc = 0;
+ }
+ return rc;
+}
+
+int lsblk_devtree_add_device(struct lsblk_devtree *tr, struct lsblk_device *dev)
+{
+ lsblk_ref_device(dev);
+
+ DBG(TREE, ul_debugobj(tr, "add device 0x%p [%s]", dev, dev->name));
+ list_add_tail(&dev->ls_devices, &tr->devices);
+ return 0;
+}
+
+int lsblk_devtree_next_device(struct lsblk_devtree *tr,
+ struct lsblk_iter *itr,
+ struct lsblk_device **dev)
+{
+ int rc = 1;
+
+ if (!tr || !itr || !dev)
+ return -EINVAL;
+ *dev = NULL;
+ if (!itr->head)
+ LSBLK_ITER_INIT(itr, &tr->devices);
+ if (itr->p != itr->head) {
+ LSBLK_ITER_ITERATE(itr, *dev, struct lsblk_device, ls_devices);
+ rc = 0;
+ }
+ return rc;
+}
+
+int lsblk_devtree_has_device(struct lsblk_devtree *tr, struct lsblk_device *dev)
+{
+ struct lsblk_device *x = NULL;
+ struct lsblk_iter itr;
+
+ lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD);
+
+ while (lsblk_devtree_next_device(tr, &itr, &x) == 0) {
+ if (x == dev)
+ return 1;
+ }
+
+ return 0;
+}
+
+struct lsblk_device *lsblk_devtree_get_device(struct lsblk_devtree *tr, const char *name)
+{
+ struct lsblk_device *dev = NULL;
+ struct lsblk_iter itr;
+
+ lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD);
+
+ while (lsblk_devtree_next_device(tr, &itr, &dev) == 0) {
+ if (strcmp(name, dev->name) == 0)
+ return dev;
+ }
+
+ return NULL;
+}
+
+int lsblk_devtree_remove_device(struct lsblk_devtree *tr, struct lsblk_device *dev)
+{
+ DBG(TREE, ul_debugobj(tr, "remove device 0x%p [%s]", dev, dev->name));
+
+ if (!lsblk_devtree_has_device(tr, dev))
+ return 1;
+
+ list_del_init(&dev->ls_roots);
+ list_del_init(&dev->ls_devices);
+ lsblk_unref_device(dev);
+
+ return 0;
+}
+
+static int device_dedupkey_is_equal(
+ struct lsblk_device *dev,
+ struct lsblk_device *pattern)
+{
+ assert(pattern->dedupkey);
+
+ if (!dev->dedupkey || dev == pattern)
+ return 0;
+ if (strcmp(dev->dedupkey, pattern->dedupkey) == 0) {
+ if (!device_is_partition(dev) ||
+ !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..c034e34
--- /dev/null
+++ b/misc-utils/lsblk-mnt.c
@@ -0,0 +1,127 @@
+#include "c.h"
+#include "pathnames.h"
+#include "xalloc.h"
+#include "nls.h"
+
+#include <libmount.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 int is_active_swap(const char *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) != NULL;
+}
+
+char *lsblk_device_get_mountpoint(struct lsblk_device *dev)
+{
+ struct libmnt_fs *fs;
+ const char *fsroot;
+
+ assert(dev);
+ assert(dev->filename);
+
+ if (dev->is_mounted || dev->is_swap)
+ return dev->mountpoint;
+
+ 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);
+ }
+ }
+
+ /* Note that maj:min in /proc/self/mountinfo does not have to match with
+ * devno as returned by stat(), so we have to try devname too
+ */
+ fs = mnt_table_find_devno(mtab, makedev(dev->maj, dev->min), MNT_ITER_BACKWARD);
+ if (!fs)
+ fs = mnt_table_find_srcpath(mtab, dev->filename, MNT_ITER_BACKWARD);
+ if (!fs) {
+ if (is_active_swap(dev->filename)) {
+ dev->mountpoint = xstrdup("[SWAP]");
+ dev->is_swap = 1;
+ } else
+ dev->mountpoint = NULL;
+
+ return dev->mountpoint;
+ }
+
+ /* found */
+ fsroot = mnt_fs_get_root(fs);
+ if (fsroot && strcmp(fsroot, "/") != 0) {
+ /* hmm.. we found bind mount or btrfs subvolume, let's try to
+ * get real FS root mountpoint */
+ struct libmnt_fs *rfs;
+ struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_BACKWARD);
+
+ mnt_table_set_iter(mtab, itr, fs);
+ while (mnt_table_next_fs(mtab, itr, &rfs) == 0) {
+ fsroot = mnt_fs_get_root(rfs);
+ if ((!fsroot || strcmp(fsroot, "/") == 0)
+ && mnt_fs_match_source(rfs, dev->filename, mntcache)) {
+ fs = rfs;
+ break;
+ }
+ }
+ mnt_free_iter(itr);
+ }
+
+ DBG(DEV, ul_debugobj(dev, "mountpoint: %s", mnt_fs_get_target(fs)));
+ dev->mountpoint = xstrdup(mnt_fs_get_target(fs));
+ dev->is_mounted = 1;
+ return dev->mountpoint;
+}
+
+void lsblk_mnt_init(void)
+{
+ 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..1940171
--- /dev/null
+++ b/misc-utils/lsblk-properties.c
@@ -0,0 +1,381 @@
+
+#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 "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->wwn);
+ free(p->serial);
+ free(p->model);
+ free(p->partflags);
+
+ 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
+static struct lsblk_devprop *get_properties_by_udev(struct lsblk_device *ld)
+{
+ struct udev_device *dev;
+
+ 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) {
+ const char *data;
+ struct lsblk_devprop *prop;
+
+ 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_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, "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);
+
+ if ((data = udev_device_get_property_value(dev, "ID_MODEL")))
+ prop->model = xstrdup(data);
+
+ udev_device_unref(dev);
+ DBG(DEV, ul_debugobj(ld, "%s: found udev properties", ld->name));
+ }
+
+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, 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_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)) ;
+
+ /* 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);
+
+ 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..3da4e2e
--- /dev/null
+++ b/misc-utils/lsblk.8
@@ -0,0 +1,215 @@
+.TH LSBLK 8 "February 2013" "util-linux" "System Administration"
+.SH NAME
+lsblk \- list block devices
+.SH SYNOPSIS
+.B lsblk
+[options]
+.RI [ device ...]
+.SH DESCRIPTION
+.B lsblk
+lists information about all available or the specified block devices. The
+.B lsblk
+command reads the
+.B sysfs
+filesystem and
+.B udev db
+to gather information. If the udev db is not available or lsblk is compiled without udev support than it
+tries to read LABELs, UUIDs and filesystem types from the block device. In this case root permissions
+are necessary.
+.PP
+The command prints all block devices (except RAM disks) in a tree-like format
+by default. Use
+.B "lsblk \-\-help"
+to get a list of all available columns.
+.PP
+The default output, as well as the default output from options like
+.B \-\-fs
+and
+.BR \-\-topology ,
+is subject to change. So whenever possible, you should avoid using default
+outputs in your scripts. Always explicitly define expected columns by using
+.B \-\-output
+.I columns-list
+and
+.B \-\-list
+in environments where a stable output is required.
+.PP
+Note that
+.B lsblk
+might be executed in time when
+.B udev
+does not have all information about recently added or modified devices yet. In this
+case it is recommended to use
+.B "udevadm settle"
+before lsblk to synchronize with udev.
+.SH OPTIONS
+.TP
+.BR \-a , " \-\-all"
+Also list empty devices and RAM disk devices.
+.TP
+.BR \-b , " \-\-bytes"
+Print the SIZE column in bytes rather than in a human-readable format.
+.TP
+.BR \-D , " \-\-discard"
+Print information about the discarding capabilities (TRIM, UNMAP) for each device.
+.TP
+.BR \-d , " \-\-nodeps"
+Do not print holder devices or slaves. For example, \fBlsblk \-\-nodeps /dev/sda\fR prints
+information about the sda device only.
+.TP
+.BR \-E , " \-\-dedup " \fIcolumn\fP
+Use \fIcolumn\fP as a de-duplication key to de-duplicate output tree. If the
+key is not available for the device, or the device is a partition and parental
+whole-disk device provides the same key than the device is always printed.
+
+The usual use case is to de-duplicate output on system multi-path devices, for
+example by \fB\-E WWN\fR.
+.TP
+.BR \-e , " \-\-exclude " \fIlist\fP
+Exclude the devices specified by the comma-separated \fIlist\fR of major device numbers.
+Note that RAM disks (major=1) are excluded by default if \fB\-\-all\fR is not specified.
+The filter is applied to the top-level devices only. This may be confusing for
+\fB\-\-list\fR output format where hierarchy of the devices is not obvious.
+.TP
+.BR \-f , " \-\-fs"
+Output info about filesystems. This option is equivalent to
+.BR \-o\ NAME,FSTYPE,LABEL,UUID,FSAVAIL,FSUSE%,MOUNTPOINT .
+The authoritative information about filesystems and raids is provided by the
+.BR blkid (8)
+command.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.TP
+.BR \-I , " \-\-include " \fIlist\fP
+Include devices specified by the comma-separated \fIlist\fR of major device numbers.
+The filter is applied to the top-level devices only. This may be confusing for
+\fB\-\-list\fR output format where hierarchy of the devices is not obvious.
+.TP
+.BR \-i , " \-\-ascii"
+Use ASCII characters for tree formatting.
+.TP
+.BR \-J , " \-\-json"
+Use JSON output format. It's strongly recommended to use \fB\-\-output\fR and
+also \fB\-\-tree\fR if necessary.
+.TP
+.BR \-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 \fB\-\-pairs\fR or \fB\-\-raw\fR not specified (the
+parsable outputs are maintained in backwardly compatible way).
+.TP
+.BR \-M , " \-\-merge"
+Group parents of sub-trees to provide more readable output for RAIDs and
+Multi-path devices. The tree-like output is required.
+.TP
+.BR \-m , " \-\-perms"
+Output info about device owner, group and mode. This option is equivalent to
+.BR \-o\ NAME,SIZE,OWNER,GROUP,MODE .
+.TP
+.BR \-n , " \-\-noheadings"
+Do not print a header line.
+.TP
+.BR \-o , " \-\-output " \fIlist\fP
+Specify which output columns to print. Use
+.B \-\-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 \fB\-\-tree\fR).
+
+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).
+.TP
+.BR \-O , " \-\-output\-all"
+Output all available columns.
+.TP
+.BR \-P , " \-\-pairs"
+Produce output in the form of key="value" pairs. The output lines are still ordered by
+dependencies. All potentially unsafe characters are hex-escaped (\\x<code>).
+.TP
+.BR \-p , " \-\-paths"
+Print full device paths.
+.TP
+.BR \-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.
+.TP
+.BR \-S , " \-\-scsi"
+Output info about SCSI devices only. All partitions, slaves and holder devices are ignored.
+.TP
+.BR \-s , " \-\-inverse"
+Print dependencies in inverse order. If the \fB\-\-list\fR output is requested then
+the lines are still ordered by dependencies.
+.TP
+.BR \-T , " \-\-tree" [ =\fIcolumn ]
+Force tree-like output format. If \fIcolumn\fP is specified, then a tree is printed in the column.
+The default is NAME column.
+.TP
+.BR \-t , " \-\-topology"
+Output info about block-device topology.
+This option is equivalent to
+.BR \-o\ NAME,ALIGNMENT,MIN-IO,OPT-IO,PHY-SEC,LOG-SEC,ROTA,SCHED,RQ-SIZE,RA,WSAME .
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-x , " \-\-sort " \fIcolumn\fP
+Sort output lines by \fIcolumn\fP. This option enables \fB\-\-list\fR output format by default.
+It is possible to use the option \fI\-\-tree\fP to force tree-like output and
+than the tree branches are sorted by the \fIcolumn\fP.
+.TP
+.BR \-z , " \-\-zoned"
+Print the zone model for each device.
+.TP
+.BR " \-\-sysroot " \fIdirectory\fP
+Gather data for a Linux instance other than the instance from which the lsblk
+command is issued. The specified directory is the system root of the Linux
+instance to be inspected. The real device nodes in the target directory can
+be replaced by text files with udev attributes.
+
+.SH EXIT STATUS
+.IP 0
+success
+.IP 1
+failure
+.IP 32
+none of specified devices found
+.IP 64
+some specified devices found, some not found
+
+.SH ENVIRONMENT
+.IP LSBLK_DEBUG=all
+enables lsblk debug output.
+.IP LIBBLKID_DEBUG=all
+enables libblkid debug output.
+.IP LIBMOUNT_DEBUG=all
+enables libmount debug output.
+.IP LIBSMARTCOLS_DEBUG=all
+enables libsmartcols debug output.
+.IP LIBSMARTCOLS_DEBUG_PADDING=on
+use visible padding characters. Requires enabled LIBSMARTCOLS_DEBUG.
+.SH NOTES
+For partitions, some information (e.g., queue attributes) is inherited from the
+parent device.
+.PP
+The
+.B lsblk
+command needs to be able to look up each block device by major:minor numbers,
+which is done by using
+.IR /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.
+
+.SH AUTHORS
+.nf
+Milan Broz <mbroz@redhat.com>
+Karel Zak <kzak@redhat.com>
+.fi
+.SH SEE ALSO
+.BR ls (1),
+.BR blkid (8),
+.BR findmnt (8)
+.SH AVAILABILITY
+The lsblk command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/misc-utils/lsblk.c b/misc-utils/lsblk.c
new file mode 100644
index 0000000..49c15ab
--- /dev/null
+++ b/misc-utils/lsblk.c
@@ -0,0 +1,2202 @@
+/*
+ * lsblk(8) - list block devices
+ *
+ * Copyright (C) 2010-2018 Red Hat, Inc. All rights reserved.
+ * Written by Milan Broz <mbroz@redhat.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 "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_NAME = 0,
+ COL_KNAME,
+ COL_PATH,
+ COL_MAJMIN,
+ COL_FSAVAIL,
+ COL_FSSIZE,
+ COL_FSTYPE,
+ COL_FSUSED,
+ COL_FSUSEPERC,
+ COL_FSVERSION,
+ COL_TARGET,
+ COL_LABEL,
+ COL_UUID,
+ COL_PTUUID,
+ COL_PTTYPE,
+ COL_PARTTYPE,
+ COL_PARTTYPENAME,
+ COL_PARTLABEL,
+ COL_PARTUUID,
+ COL_PARTFLAGS,
+ COL_RA,
+ COL_RO,
+ COL_RM,
+ COL_HOTPLUG,
+ COL_MODEL,
+ COL_SERIAL,
+ COL_SIZE,
+ COL_STATE,
+ COL_OWNER,
+ COL_GROUP,
+ COL_MODE,
+ COL_ALIOFF,
+ COL_MINIO,
+ COL_OPTIO,
+ COL_PHYSEC,
+ COL_LOGSEC,
+ COL_ROTA,
+ COL_SCHED,
+ COL_RQ_SIZE,
+ COL_TYPE,
+ COL_DALIGN,
+ COL_DGRAN,
+ COL_DMAX,
+ COL_DZERO,
+ COL_WSAME,
+ COL_WWN,
+ COL_RAND,
+ COL_PKNAME,
+ COL_HCTL,
+ COL_TRANSPORT,
+ COL_SUBSYS,
+ COL_REV,
+ COL_VENDOR,
+ COL_ZONED,
+ COL_DAX
+};
+
+/* 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),
+};
+
+/* 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_NAME] = { "NAME", 0.25, SCOLS_FL_NOEXTREMES, N_("device name") },
+ [COL_KNAME] = { "KNAME", 0.3, 0, N_("internal kernel device name") },
+ [COL_PKNAME] = { "PKNAME", 0.3, 0, N_("internal parent kernel device name") },
+ [COL_PATH] = { "PATH", 0.3, 0, N_("path to the device node") },
+ [COL_MAJMIN] = { "MAJ:MIN", 6, 0, N_("major:minor device number"), COLTYPE_SORTNUM },
+
+ [COL_FSAVAIL] = { "FSAVAIL", 5, SCOLS_FL_RIGHT, N_("filesystem size available") },
+ [COL_FSSIZE] = { "FSSIZE", 5, SCOLS_FL_RIGHT, N_("filesystem size") },
+ [COL_FSTYPE] = { "FSTYPE", 0.1, SCOLS_FL_TRUNC, N_("filesystem type") },
+ [COL_FSUSED] = { "FSUSED", 5, SCOLS_FL_RIGHT, N_("filesystem size used") },
+ [COL_FSUSEPERC] = { "FSUSE%", 3, SCOLS_FL_RIGHT, N_("filesystem use percentage") },
+ [COL_FSVERSION] = { "FSVER", 0.1, SCOLS_FL_TRUNC, N_("filesystem version") },
+
+ [COL_TARGET] = { "MOUNTPOINT", 0.10, SCOLS_FL_TRUNC, N_("where the device is mounted") },
+ [COL_LABEL] = { "LABEL", 0.1, 0, N_("filesystem LABEL") },
+ [COL_UUID] = { "UUID", 36, 0, N_("filesystem UUID") },
+
+ [COL_PTUUID] = { "PTUUID", 36, 0, N_("partition table identifier (usually UUID)") },
+ [COL_PTTYPE] = { "PTTYPE", 0.1, 0, N_("partition table type") },
+
+ [COL_PARTTYPE] = { "PARTTYPE", 36, 0, N_("partition type code or UUID") },
+ [COL_PARTTYPENAME] = { "PARTTYPENAME", 0.1, 0, N_("partition type name") },
+ [COL_PARTLABEL] = { "PARTLABEL", 0.1, 0, N_("partition LABEL") },
+ [COL_PARTUUID] = { "PARTUUID", 36, 0, N_("partition UUID") },
+ [COL_PARTFLAGS] = { "PARTFLAGS", 36, 0, N_("partition flags") },
+
+ [COL_RA] = { "RA", 3, SCOLS_FL_RIGHT, N_("read-ahead of the device"), COLTYPE_NUM },
+ [COL_RO] = { "RO", 1, SCOLS_FL_RIGHT, N_("read-only device"), COLTYPE_BOOL },
+ [COL_RM] = { "RM", 1, SCOLS_FL_RIGHT, N_("removable device"), COLTYPE_BOOL },
+ [COL_HOTPLUG]= { "HOTPLUG", 1, SCOLS_FL_RIGHT, N_("removable or hotplug device (usb, pcmcia, ...)"), COLTYPE_BOOL },
+ [COL_ROTA] = { "ROTA", 1, SCOLS_FL_RIGHT, N_("rotational device"), COLTYPE_BOOL },
+ [COL_RAND] = { "RAND", 1, SCOLS_FL_RIGHT, N_("adds randomness"), COLTYPE_BOOL },
+ [COL_MODEL] = { "MODEL", 0.1, SCOLS_FL_TRUNC, N_("device identifier") },
+ [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_STATE] = { "STATE", 7, SCOLS_FL_TRUNC, N_("state of the device") },
+ [COL_OWNER] = { "OWNER", 0.1, SCOLS_FL_TRUNC, N_("user name"), },
+ [COL_GROUP] = { "GROUP", 0.1, SCOLS_FL_TRUNC, N_("group name") },
+ [COL_MODE] = { "MODE", 10, 0, N_("device node permissions") },
+ [COL_ALIOFF] = { "ALIGNMENT", 6, SCOLS_FL_RIGHT, N_("alignment offset"), COLTYPE_NUM },
+ [COL_MINIO] = { "MIN-IO", 6, SCOLS_FL_RIGHT, N_("minimum I/O size"), COLTYPE_NUM },
+ [COL_OPTIO] = { "OPT-IO", 6, SCOLS_FL_RIGHT, N_("optimal I/O size"), COLTYPE_NUM },
+ [COL_PHYSEC] = { "PHY-SEC", 7, SCOLS_FL_RIGHT, N_("physical sector size"), COLTYPE_NUM },
+ [COL_LOGSEC] = { "LOG-SEC", 7, SCOLS_FL_RIGHT, N_("logical sector size"), COLTYPE_NUM },
+ [COL_SCHED] = { "SCHED", 0.1, 0, N_("I/O scheduler name") },
+ [COL_RQ_SIZE]= { "RQ-SIZE", 5, SCOLS_FL_RIGHT, N_("request queue size"), COLTYPE_NUM },
+ [COL_TYPE] = { "TYPE", 4, 0, N_("device type") },
+ [COL_DALIGN] = { "DISC-ALN", 6, SCOLS_FL_RIGHT, N_("discard alignment offset"), COLTYPE_NUM },
+ [COL_DGRAN] = { "DISC-GRAN", 6, SCOLS_FL_RIGHT, N_("discard granularity"), COLTYPE_SIZE },
+ [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_WSAME] = { "WSAME", 6, SCOLS_FL_RIGHT, N_("write same max bytes"), COLTYPE_SIZE },
+ [COL_WWN] = { "WWN", 18, 0, N_("unique storage identifier") },
+ [COL_HCTL] = { "HCTL", 10, 0, N_("Host:Channel:Target:Lun for SCSI") },
+ [COL_TRANSPORT] = { "TRAN", 6, 0, N_("device transport type") },
+ [COL_SUBSYS] = { "SUBSYSTEMS", 0.1, SCOLS_FL_NOEXTREMES, N_("de-duplicated chain of subsystems") },
+ [COL_REV] = { "REV", 4, SCOLS_FL_RIGHT, N_("device revision") },
+ [COL_VENDOR] = { "VENDOR", 0.1, SCOLS_FL_TRUNC, N_("device vendor") },
+ [COL_ZONED] = { "ZONED", 0.3, 0, N_("zone model") },
+ [COL_DAX] = { "DAX", 1, SCOLS_FL_RIGHT, N_("dax-capable device"), COLTYPE_BOOL },
+};
+
+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 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";
+
+ return trans ? xstrdup(trans) : NULL;
+}
+
+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;
+ char *mnt;
+
+ if (!dev->fsstat.f_blocks) {
+ 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;
+ if (ul_path_scanf(dev->sysfs, "removable", "%d", &dev->removable) == 1)
+ goto done;
+
+ if (parent) {
+ pc = sysfs_blkdev_get_parent(dev->sysfs);
+ if (!pc)
+ goto done;
+
+ /* dev is partition and parent is whole-disk */
+ if (pc == parent->sysfs)
+ dev->removable = is_removable_device(parent, NULL);
+
+ /* parent is something else, use sysfs parent */
+ else if (ul_path_scanf(pc, "removable", "%d", &dev->removable) != 1)
+ dev->removable = 0;
+ }
+done:
+ if (dev->removable == -1)
+ dev->removable = 0;
+ return dev->removable;
+}
+
+static uint64_t device_get_discard_granularity(struct lsblk_device *dev)
+{
+ if (dev->discard_granularity == (uint64_t) -1
+ && ul_path_read_u64(dev->sysfs, &dev->discard_granularity,
+ "queue/discard_granularity") != 0)
+ dev->discard_granularity = 0;
+
+ return dev->discard_granularity;
+}
+
+/*
+ * Generates data (string) for column specified by column ID for specified device. If sortdata
+ * is not NULL then returns number usable to sort the column if the data are available for the
+ * column.
+ */
+static char *device_get_data(
+ struct lsblk_device *dev, /* device */
+ struct lsblk_device *parent, /* device parent as defined in the tree */
+ int id, /* column ID (COL_*) */
+ uint64_t *sortdata) /* returns sort data as number */
+{
+ struct lsblk_devprop *prop = 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:
+ {
+ char *s = lsblk_device_get_mountpoint(dev);
+ if (s)
+ str = xstrdup(s);
+ else
+ str = 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_WWN:
+ prop = lsblk_device_get_properties(dev);
+ if (prop && prop->wwn)
+ str = xstrdup(prop->wwn);
+ 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)
+ 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_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:
+ str = get_transport(dev);
+ 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:
+ if (lsblk->bytes) {
+ ul_path_read_string(dev->sysfs, &str, "queue/discard_max_bytes");
+ if (sortdata)
+ str2u64(str, sortdata);
+ } else {
+ uint64_t x;
+ if (ul_path_read_u64(dev->sysfs, &x, "queue/discard_max_bytes") == 0) {
+ str = size_to_human_string(SIZE_SUFFIX_1LETTER, x);
+ if (sortdata)
+ *sortdata = x;
+ }
+ }
+ 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:
+ if (lsblk->bytes) {
+ ul_path_read_string(dev->sysfs, &str, "queue/write_same_max_bytes");
+ if (sortdata)
+ str2u64(str, sortdata);
+ } else {
+ uint64_t x;
+
+ if (ul_path_read_u64(dev->sysfs, &x, "queue/write_same_max_bytes") == 0) {
+ str = size_to_human_string(SIZE_SUFFIX_1LETTER, x);
+ if (sortdata)
+ *sortdata = x;
+ }
+ }
+ if (!str)
+ str = xstrdup("0");
+ break;
+ case COL_ZONED:
+ ul_path_read_string(dev->sysfs, &str, "queue/zoned");
+ break;
+ case COL_DAX:
+ ul_path_read_string(dev->sysfs, &str, "queue/dax");
+ 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);
+}
+
+/*
+ * 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 && dev->size == 0) {
+ 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;
+ }
+
+ 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 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;
+
+ 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)"));
+ return 0;
+ }
+
+ depname = lsblk->inverse ? "slaves" : "holders";
+ dir = ul_path_opendir(dev->sysfs, depname);
+ if (!dir) {
+ DBG(DEV, ul_debugobj(dev, " ignore (no slaves/holders directory)"));
+ return 0;
+ }
+ ul_path_close_dirfd(dev->sysfs);
+
+ DBG(DEV, ul_debugobj(dev, " %s: checking for '%s' dependence", dev->name, depname));
+
+ while ((d = xreaddir(dir))) {
+ struct lsblk_device *dep = NULL;
+ struct lsblk_device *disk = NULL;
+
+ /* Is the dependency a partition? */
+ if (sysfs_blkdev_is_partition_dirent(dir, d, NULL)) {
+
+ 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);
+
+ DBG(DEV, ul_debugobj(dev, "%s: checking for '%s' -- done", dev->name, depname));
+ 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(_(" -D, --discard print discard capabilities\n"), out);
+ fputs(_(" -E, --dedup <column> de-duplicate output by <column>\n"), out);
+ fputs(_(" -I, --include <list> show only devices with specified major numbers\n"), out);
+ fputs(_(" -J, --json use JSON output format\n"), out);
+ fputs(_(" -O, --output-all output all columns\n"), out);
+ fputs(_(" -P, --pairs use key=\"value\" output format\n"), out);
+ fputs(_(" -S, --scsi output info about SCSI devices\n"), out);
+ fputs(_(" -T, --tree[=<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, --merge group parents of sub-trees (usable for RAIDs, Multi-path)\n"), out);
+ fputs(_(" -m, --perms output info about permissions\n"), out);
+ fputs(_(" -n, --noheadings don't print headings\n"), out);
+ fputs(_(" -o, --output <list> output columns\n"), out);
+ fputs(_(" -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(_(" -z, --zoned print zone model\n"), out);
+ fputs(_(" -x, --sort <column> sort output by <column>\n"), out);
+ fputs(_(" --sysroot <dir> use specified directory as system root\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(22));
+
+ 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("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;
+ 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' },
+ { "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' },
+ { "sort", required_argument, NULL, 'x' },
+ { "sysroot", required_argument, NULL, OPT_SYSROOT },
+ { "tree", optional_argument, NULL, 'T' },
+ { "version", no_argument, NULL, 'V' },
+ { 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,
+ "abdDzE:e:fhJlnMmo:OpPiI:rstVST::x:", longopts, NULL)) != -1) {
+
+ err_exclusive_options(c, longopts, excl, excl_st);
+
+ switch(c) {
+ 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);
+ 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 '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_TARGET);
+ 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 '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 '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_TARGET);
+ }
+
+ 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_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");
+
+ 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);
+ }
+ 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:
+ 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..8722127
--- /dev/null
+++ b/misc-utils/lsblk.h
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2010-2018 Red Hat, Inc. All rights reserved.
+ * Written by Milan Broz <mbroz@redhat.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 "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 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 */
+};
+
+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 *wwn; /* storage WWN */
+ char *serial; /* disk serial number */
+ char *model; /* disk model */
+
+ /* 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;
+
+ char *mountpoint; /* device mountpoint */
+ 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)
+
+/*
+ * 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 */
+
+ unsigned int is_inverse : 1; /* inverse tree */
+};
+
+
+/*
+ * Generic iterator
+ */
+struct lsblk_iter {
+ struct list_head *p; /* current position */
+ struct list_head *head; /* start position */
+ int direction; /* LSBLK_ITER_{FOR,BACK}WARD */
+};
+
+#define LSBLK_ITER_FORWARD 0
+#define LSBLK_ITER_BACKWARD 1
+
+#define IS_ITER_FORWARD(_i) ((_i)->direction == LSBLK_ITER_FORWARD)
+#define IS_ITER_BACKWARD(_i) ((_i)->direction == LSBLK_ITER_BACKWARD)
+
+#define LSBLK_ITER_INIT(itr, list) \
+ do { \
+ (itr)->p = IS_ITER_FORWARD(itr) ? \
+ (list)->next : (list)->prev; \
+ (itr)->head = (list); \
+ } while(0)
+
+#define LSBLK_ITER_ITERATE(itr, res, restype, member) \
+ do { \
+ res = list_entry((itr)->p, restype, member); \
+ (itr)->p = IS_ITER_FORWARD(itr) ? \
+ (itr)->p->next : (itr)->p->prev; \
+ } while(0)
+
+
+/* lsblk-mnt.c */
+extern void lsblk_mnt_init(void);
+extern void lsblk_mnt_deinit(void);
+
+extern char *lsblk_device_get_mountpoint(struct lsblk_device *dev);
+
+/* lsblk-properties.c */
+extern void lsblk_device_free_properties(struct lsblk_devprop *p);
+extern struct lsblk_devprop *lsblk_device_get_properties(struct 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);
+
+int lsblk_device_is_last_parent(struct lsblk_device *dev, struct lsblk_device *parent);
+int lsblk_device_next_parent(
+ struct lsblk_device *dev,
+ struct lsblk_iter *itr,
+ struct lsblk_device **parent);
+
+struct lsblk_devtree *lsblk_new_devtree(void);
+void lsblk_ref_devtree(struct lsblk_devtree *tr);
+void lsblk_unref_devtree(struct lsblk_devtree *tr);
+int lsblk_devtree_add_root(struct lsblk_devtree *tr, struct lsblk_device *dev);
+int lsblk_devtree_next_root(struct lsblk_devtree *tr,
+ struct lsblk_iter *itr,
+ struct lsblk_device **dev);
+int lsblk_devtree_add_device(struct lsblk_devtree *tr, struct lsblk_device *dev);
+int lsblk_devtree_next_device(struct lsblk_devtree *tr,
+ struct lsblk_iter *itr,
+ struct lsblk_device **dev);
+int lsblk_devtree_has_device(struct lsblk_devtree *tr, struct lsblk_device *dev);
+struct lsblk_device *lsblk_devtree_get_device(struct lsblk_devtree *tr, const char *name);
+int lsblk_devtree_remove_device(struct lsblk_devtree *tr, struct lsblk_device *dev);
+int lsblk_devtree_deduplicate_devices(struct lsblk_devtree *tr);
+
+#endif /* UTIL_LINUX_LSBLK_H */
diff --git a/misc-utils/lslocks.8 b/misc-utils/lslocks.8
new file mode 100644
index 0000000..89b1686
--- /dev/null
+++ b/misc-utils/lslocks.8
@@ -0,0 +1,108 @@
+.\" Man page for the lslocks command.
+.\" Copyright 2012 Davidlohr Bueso <dave@gnu.org>
+.\" May be distributed under the GNU General Public License
+
+.TH LSLOCKS 8 "December 2014" "util-linux" "System Administration"
+.SH NAME
+lslocks \- list local system locks
+.SH SYNOPSIS
+.B lslocks
+[options]
+
+.SH DESCRIPTION
+.B lslocks
+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\fR(2) for more details.
+
+.SH OPTIONS
+.TP
+.BR \-b , " \-\-bytes"
+Print the SIZE column in bytes rather than in a human-readable format.
+.TP
+.BR \-i , " \-\-noinaccessible"
+Ignore lock files which are inaccessible for the current user.
+.TP
+.BR \-J , " \-\-json"
+Use JSON output format.
+.TP
+.BR \-n , " \-\-noheadings"
+Do not print a header line.
+.TP
+.BR \-o , " \-\-output " \fIlist\fP
+Specify which output columns to print. Use
+.B \-\-help
+to get a list of all supported columns.
+
+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).
+.TP
+.B \-\-output\-all
+Output all available columns.
+.TP
+.BR \-p , " \-\-pid " \fIpid\fP
+Display only the locks held by the process with this \fIpid\fR.
+.TP
+.BR \-r , " \-\-raw"
+Use the raw output format.
+.TP
+.BR \-u , " \-\-notruncate"
+Do not truncate text in columns.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+
+.SH OUTPUT
+.IP "COMMAND"
+The command name of the process holding the lock.
+.IP "PID"
+The process ID of the process which holds the lock or \-1 for OFDLCK.
+.IP "TYPE"
+The type of lock; can be FLOCK (created with \fBflock\fR(2)), POSIX
+(created with \fBfcntl\fR(2) and \fBlockf\fR(3)) or OFDLCK (created with fcntl(2).
+.IP "SIZE"
+Size of the locked file.
+.IP "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).
+.IP "M"
+Whether the lock is mandatory; 0 means no (meaning the lock is only advisory), 1 means yes.
+(See \fBfcntl\fR(2).)
+.IP "START"
+Relative byte offset of the lock.
+.IP "END"
+Ending offset of the lock.
+.IP "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
+\fB\-\-notruncate\fR to get the full path.
+.IP "BLOCKER"
+The PID of the process which blocks the lock.
+
+.SH NOTES
+.nf
+The \fBlslocks\fR command is meant to replace the \fBlslk\fR(8) command,
+originally written by Victor A. Abell <abe@purdue.edu> and unmaintained
+since 2001.
+.fi
+
+.SH AUTHORS
+.nf
+Davidlohr Bueso <dave@gnu.org>
+.fi
+
+.SH SEE ALSO
+.BR flock (1),
+.BR fcntl (2),
+.BR lockf (3)
+
+.SH AVAILABILITY
+The lslocks command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/misc-utils/lslocks.c b/misc-utils/lslocks.c
new file mode 100644
index 0000000..18922e5
--- /dev/null
+++ b/misc-utils/lslocks.c
@@ -0,0 +1,662 @@
+/*
+ * 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 "procutils.h"
+
+/* column IDs */
+enum {
+ COL_SRC = 0,
+ COL_PID,
+ COL_TYPE,
+ COL_SIZE,
+ 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_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;
+ 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.
+ */
+ sprintf(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;
+
+ /* care only for numerical descriptors */
+ if (!strtol(dp->d_name, (char **) NULL, 10))
+ 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;
+
+ sscanf(str, "%02x:%02x:%ju", &maj, &min, &inum);
+
+ *dev = (dev_t) makedev(maj, min);
+ return inum;
+}
+
+static int get_local_locks(struct list_head *locks)
+{
+ int i;
+ ino_t inode = 0;
+ FILE *fp;
+ char buf[PATH_MAX], *tok = NULL;
+ size_t sz;
+ struct lock *l;
+ dev_t dev = 0;
+
+ 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 = proc_get_command_name(l->pid);
+ if (!l->cmdname)
+ l->cmdname = xstrdup(_("(unknown)"));
+ } else
+ l->cmdname = xstrdup(_("(undefined)"));
+ break;
+
+ case 5: /* device major:minor and inode number */
+ inode = get_dev_inode(tok, &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(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(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_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:
+ 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..fcb37ed
--- /dev/null
+++ b/misc-utils/mcookie.1
@@ -0,0 +1,69 @@
+.\" mcookie.1 --
+.\" Public Domain 1995 Rickard E. Faith (faith@cs.unc.edu)
+.TH MCOOKIE 1 "December 2014" "util-linux" "User Commands"
+.SH NAME
+mcookie \- generate magic cookies for xauth
+.SH SYNOPSIS
+.B mcookie
+[options]
+.SH DESCRIPTION
+.B mcookie
+generates a 128-bit random hexadecimal number for use with the X authority
+system. Typical usage:
+.sp
+.RS
+.B xauth add :0 . `mcookie`
+.RE
+.PP
+The "random" number generated is actually the MD5 message
+digest of random information coming from one of the sources
+.IR getrandom ()
+system call,
+.IR /dev/urandom ,
+.IR /dev/random ,
+or the
+.IR "libc pseudo-random functions" ,
+in this preference order. See also the option \fB\-\-file\fR.
+.SH OPTIONS
+.TP
+.BR \-f , " \-\-file " \fIfile
+Use this \fIfile\fR as an additional source of randomness (for example /dev/urandom).
+When \fIfile\fR is '-', characters are read from standard input.
+.TP
+.BR \-m , " \-\-max\-size " \fInumber
+Read from \fIfile\fR only this \fInumber\fR of bytes.
+This option is meant to be used when reading additional
+randomness from a file or device.
+.IP
+The
+.I 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.
+.TP
+.BR \-v , " \-\-verbose"
+Inform where randomness originated, with amount of entropy read from each
+source.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH FILES
+.I /dev/urandom
+.br
+.I /dev/random
+.SH BUGS
+It is assumed that none of the randomness sources will block.
+.SH SEE ALSO
+.BR md5sum (1),
+.BR X (7),
+.BR xauth (1),
+.BR rand (3)
+.SH AVAILABILITY
+The mcookie command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/misc-utils/mcookie.c b/misc-utils/mcookie.c
new file mode 100644
index 0000000..3157401
--- /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/namei.1 b/misc-utils/namei.1
new file mode 100644
index 0000000..84eae8b
--- /dev/null
+++ b/misc-utils/namei.1
@@ -0,0 +1,80 @@
+.TH NAMEI 1 "June 2011" "util-linux" "User Commands"
+.SH NAME
+namei \- follow a pathname until a terminal point is found
+.SH SYNOPSIS
+.B namei
+[options]
+.IR pathname ...
+.SH DESCRIPTION
+.B namei
+interprets its arguments as pathnames to any type
+of Unix file (symlinks, files, directories, and so forth).
+.B 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.
+.PP
+This program is useful for finding "too many levels of
+symbolic links" problems.
+.PP
+For each line of output,
+.B namei
+uses the following characters to identify the file type found:
+.LP
+.nf
+ 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
+.fi
+.PP
+.B namei
+prints an informative message when
+the maximum number of symbolic links this system can have has been exceeded.
+.SH OPTIONS
+.TP
+.BR \-l , " \-\-long"
+Use the long listing format (same as \fB\-m \-o \-v\fR).
+.TP
+.BR \-m , " \-\-modes"
+Show the mode bits of each file type in the style of ls(1),
+for example 'rwxr-xr-x'.
+.TP
+.BR \-n , " \-\-nosymlinks"
+Don't follow symlinks.
+.TP
+.BR \-o , " \-\-owners"
+Show owner and group name of each file.
+.TP
+.BR \-v , " \-\-vertical"
+Vertically align the modes and owners.
+.TP
+.BR \-x , " \-\-mountpoints"
+Show mountpoint directories with a 'D' rather than a 'd'.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH BUGS
+To be discovered.
+.SH AUTHORS
+The original
+.B namei
+program was written by Roger Southwick <rogers@amadeus.wr.tek.com>.
+.sp
+The program was rewritten by Karel Zak <kzak@redhat.com>.
+.SH SEE ALSO
+.BR ls (1),
+.BR stat (1),
+.BR symlink (7)
+.SH AVAILABILITY
+The namei command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/misc-utils/namei.c b/misc-utils/namei.c
new file mode 100644
index 0000000..9c5f5fa
--- /dev/null
+++ b/misc-utils/namei.c
@@ -0,0 +1,450 @@
+/*
+ * 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>
+
+#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)
+
+
+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()) */
+};
+
+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;
+ 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 (*sym != '/' && 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);
+
+ 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;
+ 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);
+ }
+
+ 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);
+ 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' },
+ { NULL, 0, NULL, 0 },
+};
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ int rc = EXIT_SUCCESS;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c = getopt_long(argc, argv, "hVlmnovx", 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;
+
+ 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/rename.1 b/misc-utils/rename.1
new file mode 100644
index 0000000..6b69ffc
--- /dev/null
+++ b/misc-utils/rename.1
@@ -0,0 +1,122 @@
+.\" Written by Andries E. Brouwer (aeb@cwi.nl)
+.\" Placed in the public domain
+.\"
+.TH RENAME 1 "June 2011" "util-linux" "User Commands"
+.SH NAME
+rename \- rename files
+.SH SYNOPSIS
+.B rename
+[options]
+.IR "expression replacement file" ...
+.SH DESCRIPTION
+.B rename
+will rename the specified files by replacing the first occurrence of
+.I expression
+in their name by
+.IR replacement .
+.SH OPTIONS
+.TP
+.BR \-s , " \-\-symlink"
+Do not rename a symlink but its target.
+.TP
+.BR \-v , " \-\-verbose"
+Show which files were renamed, if any.
+.TP
+.BR \-n , " \-\-no\-act"
+Do not make any changes; add
+.B \-\-verbose
+to see what would be made.
+.TP
+.BR \-o , " \-\-no\-overwrite"
+Do not overwrite existing files. When
+.B \-\-symlink
+is active, do not overwrite symlinks pointing to existing targets.
+.TP
+.BR \-i , " \-\-interactive"
+Ask before overwriting existing files.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH WARNING
+The renaming has no safeguards by default or without any one of the options
+.B \-\-no-overwrite\fR,
+.B \-\-interactive
+or
+.B \-\-no\-act\fR.
+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.
+.SH 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:
+.PP
+.RS
+.nf
+sh \-c 'stty \-icanon min 1; "$0" "$@"; stty icanon' rename \-i from to files
+.fi
+.RE
+.SH EXIT STATUS
+.RS
+.PD 0
+.TP
+.B 0
+all requested rename operations were successful
+.TP
+.B 1
+all rename operations failed
+.TP
+.B 2
+some rename operations failed
+.TP
+.B 4
+nothing was renamed
+.TP
+.B 64
+unanticipated error occurred
+.PD
+.RE
+.SH EXAMPLES
+Given the files
+.IR foo1 ", ..., " foo9 ", " foo10 ", ..., " foo278 ,
+the commands
+.PP
+.RS
+.nf
+rename foo foo00 foo?
+rename foo foo0 foo??
+.fi
+.RE
+.PP
+will turn them into
+.IR foo001 ", ..., " foo009 ", " foo010 ", ..., " foo278 .
+And
+.PP
+.RS
+.nf
+rename .htm .html *.htm
+.fi
+.RE
+.PP
+will fix the extension of your html files.
+Provide an empty string for shortening:
+.PP
+.RS
+.nf
+rename '_with_long_name' '' file_with_long_name.*
+.fi
+.RE
+.PP
+will remove the substring in the filenames.
+.SH SEE ALSO
+.BR mv (1)
+.SH AVAILABILITY
+The rename command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/misc-utils/rename.c b/misc-utils/rename.c
new file mode 100644
index 0000000..0f0d883
--- /dev/null
+++ b/misc-utils/rename.c
@@ -0,0 +1,322 @@
+/*
+ * 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 "rpmatch.h"
+
+#define RENAME_EXIT_SOMEOK 2
+#define RENAME_EXIT_NOTHING 4
+#define RENAME_EXIT_UNEXPLAINED 64
+
+static int tty_cbreak = 0;
+
+static int string_replace(char *from, char *to, char *s, char *orig, char **newname)
+{
+ char *p, *q, *where;
+
+ where = strstr(s, from);
+ if (where == NULL)
+ return 1;
+ p = orig;
+ *newname = xmalloc(strlen(orig) + strlen(to) + 1);
+ q = *newname;
+ while (p < where)
+ *q++ = *p++;
+ p = to;
+ while (*p)
+ *q++ = *p++;
+ p = where + strlen(from);
+ 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;
+ 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);
+ if (readlink(s, target, sb.st_size + 1) < 0) {
+ warn(_("%s: readlink failed"), s);
+ free(target);
+ return 2;
+ }
+ target[sb.st_size] = '\0';
+ if (string_replace(from, to, target, 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, *file=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 (strchr(from, '/') == NULL && strchr(to, '/') == NULL)
+ file = strrchr(s, '/');
+ if (file == NULL)
+ file = s;
+ if (string_replace(from, to, file, 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(_(" -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'},
+ {"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}
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c = getopt_long(argc, argv, "vsVhnoi", longopts, NULL)) != -1)
+ switch (c) {
+ case 'n':
+ noact = 1;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case 'o':
+ nooverwrite = 1;
+ interactive = 0;
+ break;
+ case 'i':
+ interactive = 1;
+ nooverwrite = 0;
+ 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..779b28d
--- /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) 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) 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) 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..1316184
--- /dev/null
+++ b/misc-utils/uuidd.8
@@ -0,0 +1,94 @@
+.\" -*- nroff -*-
+.\" Copyright 2007 by Theodore Ts'o. All Rights Reserved.
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH UUIDD 8 "July 2014" "util-linux" "System Administration"
+.SH NAME
+uuidd \- UUID generation daemon
+.SH SYNOPSIS
+.B uuidd
+[options]
+.SH DESCRIPTION
+The
+.B 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.
+.SH OPTIONS
+.TP
+.BR \-d , " \-\-debug"
+Run uuidd in debugging mode. This prevents uuidd from running as a daemon.
+.TP
+.BR \-F , " \-\-no-fork"
+Do not daemonize using a double-fork.
+.TP
+.BR \-k , " \-\-kill"
+If currently a uuidd daemon is running, kill it.
+.TP
+.BR \-n , " \-\-uuids " \fInumber\fR
+When issuing a test request to a running uuidd, request a bulk response
+of
+.I number
+UUIDs.
+.TP
+.BR \-P , " \-\-no-pid"
+Do not create a pid file.
+.TP
+.BR \-p , " \-\-pid " \fIpath\fR
+Specify the pathname where the pid file should be written. By default,
+the pid file is written to /run/uuidd/uuidd.pid.
+.TP
+.BR \-q , " \-\-quiet"
+Suppress some failure messages.
+.TP
+.BR \-r , " \-\-random"
+Test uuidd by trying to connect to a running uuidd daemon and
+request it to return a random-based UUID.
+.TP
+.BR \-S , " \-\-socket-activation"
+Do not create a socket but instead expect it to be provided by the calling
+process. This implies \fB\-\-no-fork\fR and \fB\-\-no-pid\fR. This option is
+intended to be used only with \fBsystemd\fR(1). It needs to be enabled with
+a configure option.
+.TP
+.BR \-s , " \-\-socket " \fIpath\fR
+Make uuidd use this pathname for the unix-domain socket. By default, the
+pathname used is /run/uuidd/request. This option is primarily
+for debugging purposes, since the pathname is hard-coded in the libuuid
+library.
+.TP
+.BR \-T , " \-\-timeout " \fInumber\fR
+Make uuidd exit after \fInumber\fR seconds of inactivity.
+.TP
+.BR \-t , " \-\-time"
+Test uuidd by trying to connect to a running uuidd daemon and
+request it to return a time-based UUID.
+.TP
+.BR \-V , " \-\-version"
+Output version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help screen and exit.
+.SH EXAMPLE
+Start up a daemon, print 42 random keys, and then stop the daemon:
+.PP
+.RS
+.nf
+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
+.fi
+.RE
+.SH AUTHOR
+The
+.B uuidd
+daemon was written by Theodore Ts'o <tytso@mit.edu>.
+.SH "SEE ALSO"
+.BR uuid (3),
+.BR uuidgen (1)
+.SH AVAILABILITY
+The uuidd daemon is part of the util-linux package and is available from the
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/misc-utils/uuidd.8.in b/misc-utils/uuidd.8.in
new file mode 100644
index 0000000..fda5108
--- /dev/null
+++ b/misc-utils/uuidd.8.in
@@ -0,0 +1,94 @@
+.\" -*- nroff -*-
+.\" Copyright 2007 by Theodore Ts'o. All Rights Reserved.
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH UUIDD 8 "July 2014" "util-linux" "System Administration"
+.SH NAME
+uuidd \- UUID generation daemon
+.SH SYNOPSIS
+.B uuidd
+[options]
+.SH DESCRIPTION
+The
+.B 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.
+.SH OPTIONS
+.TP
+.BR \-d , " \-\-debug"
+Run uuidd in debugging mode. This prevents uuidd from running as a daemon.
+.TP
+.BR \-F , " \-\-no-fork"
+Do not daemonize using a double-fork.
+.TP
+.BR \-k , " \-\-kill"
+If currently a uuidd daemon is running, kill it.
+.TP
+.BR \-n , " \-\-uuids " \fInumber\fR
+When issuing a test request to a running uuidd, request a bulk response
+of
+.I number
+UUIDs.
+.TP
+.BR \-P , " \-\-no-pid"
+Do not create a pid file.
+.TP
+.BR \-p , " \-\-pid " \fIpath\fR
+Specify the pathname where the pid file should be written. By default,
+the pid file is written to @runstatedir@/uuidd/uuidd.pid.
+.TP
+.BR \-q , " \-\-quiet"
+Suppress some failure messages.
+.TP
+.BR \-r , " \-\-random"
+Test uuidd by trying to connect to a running uuidd daemon and
+request it to return a random-based UUID.
+.TP
+.BR \-S , " \-\-socket-activation"
+Do not create a socket but instead expect it to be provided by the calling
+process. This implies \fB\-\-no-fork\fR and \fB\-\-no-pid\fR. This option is
+intended to be used only with \fBsystemd\fR(1). It needs to be enabled with
+a configure option.
+.TP
+.BR \-s , " \-\-socket " \fIpath\fR
+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.
+.TP
+.BR \-T , " \-\-timeout " \fInumber\fR
+Make uuidd exit after \fInumber\fR seconds of inactivity.
+.TP
+.BR \-t , " \-\-time"
+Test uuidd by trying to connect to a running uuidd daemon and
+request it to return a time-based UUID.
+.TP
+.BR \-V , " \-\-version"
+Output version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help screen and exit.
+.SH EXAMPLE
+Start up a daemon, print 42 random keys, and then stop the daemon:
+.PP
+.RS
+.nf
+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
+.fi
+.RE
+.SH AUTHOR
+The
+.B uuidd
+daemon was written by Theodore Ts'o <tytso@mit.edu>.
+.SH "SEE ALSO"
+.BR uuid (3),
+.BR uuidgen (1)
+.SH AVAILABILITY
+The uuidd daemon is part of the util-linux package and is available from the
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/misc-utils/uuidd.c b/misc-utils/uuidd.c
new file mode 100644
index 0000000..ef95946
--- /dev/null
+++ b/misc-utils/uuidd.c
@@ -0,0 +1,714 @@
+/*
+ * 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%
+ */
+#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"
+
+/* length of binary representation of UUID */
+#define UUID_LEN (sizeof(uuid_t))
+
+/* server loop control structure */
+struct uuidd_cxt_t {
+ const char *cleanup_pidfile;
+ const char *cleanup_socket;
+ uint32_t timeout;
+ unsigned int debug: 1,
+ quiet: 1,
+ no_fork: 1,
+ no_sock: 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(_(" -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, int op, char *buf,
+ size_t buflen, int *num, const char **err_context)
+{
+ char op_buf[8];
+ int 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 ((*num) * UUID_LEN > buflen - 4)
+ *num = (buflen - 4) / UUID_LEN;
+ }
+ op_buf[0] = op;
+ op_len = 1;
+ if ((op == UUIDD_OP_BULK_TIME_UUID) ||
+ (op == UUIDD_OP_BULK_RANDOM_UUID)) {
+ memcpy(op_buf + 1, num, sizeof(int));
+ op_len += sizeof(int);
+ }
+
+ 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 (reply_len >= (int) (UUID_LEN + sizeof(int)))
+ memcpy(buf + UUID_LEN, num, sizeof(int));
+ else
+ *num = -1;
+ }
+ if ((ret > 0) && (op == UUIDD_OP_BULK_RANDOM_UUID)) {
+ if (reply_len >= (int) sizeof(int))
+ memcpy(buf, num, sizeof(int));
+ 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[1024], *cp;
+ char op, str[UUID_STR_LEN];
+ int i, ns, len;
+ int 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) {
+ sprintf(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;
+
+ 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, 1);
+ if (len != 1) {
+ 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)) != 4)
+ 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:
+ sprintf(reply_buf, "%d", getpid());
+ reply_len = strlen(reply_buf) + 1;
+ break;
+ case UUIDD_OP_GET_MAXOP:
+ sprintf(reply_buf, "%d", UUIDD_MAX_OP);
+ reply_len = strlen(reply_buf) + 1;
+ break;
+ case UUIDD_OP_TIME_UUID:
+ num = 1;
+ __uuid_generate_time(uu, &num);
+ 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:
+ __uuid_generate_time(uu, &num);
+ 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 (num > 1000)
+ num = 1000;
+ if (num * UUID_LEN > (int) (sizeof(reply_buf) - sizeof(num)))
+ num = (sizeof(reply_buf) - sizeof(num)) / UUID_LEN;
+ __uuid_generate_random((unsigned char *) reply_buf +
+ sizeof(num), &num);
+ if (uuidd_cxt->debug) {
+ fprintf(stderr, P_("Generated %d UUID:\n",
+ "Generated %d UUIDs:\n", num), num);
+ for (i = 0, cp = reply_buf + sizeof(num);
+ i < num;
+ i++, cp += UUID_LEN) {
+ uuid_unparse((unsigned char *)cp, str);
+ fprintf(stderr, "\t%s\n", str);
+ }
+ }
+ reply_len = (num * UUID_LEN) + sizeof(num);
+ memcpy(reply_buf, &num, sizeof(num));
+ break;
+ default:
+ if (uuidd_cxt->debug)
+ fprintf(stderr, _("Invalid operation %d\n"), op);
+ goto shutdown_socket;
+ }
+ write_all(ns, (char *) &reply_len, sizeof(reply_len));
+ 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);
+}
+
+int main(int argc, char **argv)
+{
+ const char *socket_path = UUIDD_SOCKET_PATH;
+ const char *pidfile_path = NULL;
+ const char *err_context = NULL;
+ char buf[1024], *cp;
+ char str[UUID_STR_LEN];
+ uuid_t uu;
+ int i, c, ret;
+ int do_type = 0, do_kill = 0, num = 0;
+ int no_pid = 0;
+ int s_flag = 0;
+
+ struct uuidd_cxt_t uuidd_cxt = { .timeout = 0 };
+
+ static 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'},
+ {"debug", no_argument, NULL, 'd'},
+ {"quiet", no_argument, NULL, 'q'},
+ {"version", no_argument, NULL, 'V'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+ static const ul_excl_t excl[] = {
+ { 'P', 'p' },
+ { 'd', 'q' },
+ { 'r', 't' },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c =
+ getopt_long(argc, argv, "p:s:T:krtn:PFSdqVh", longopts,
+ NULL)) != -1) {
+ err_exclusive_options(c, longopts, excl, excl_st);
+ switch (c) {
+ case 'd':
+ uuidd_cxt.debug = 1;
+ break;
+ case 'k':
+ do_kill++;
+ break;
+ case 'n':
+ num = strtou32_or_err(optarg,
+ _("failed to parse --uuids"));
+ break;
+ case 'p':
+ pidfile_path = optarg;
+ break;
+ case 'P':
+ 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;
+ 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':
+ do_type = UUIDD_OP_RANDOM_UUID;
+ break;
+ case 's':
+ socket_path = optarg;
+ s_flag = 1;
+ break;
+ case 't':
+ 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 (strlen(socket_path) >= sizeof(((struct sockaddr_un *)0)->sun_path))
+ errx(EXIT_FAILURE, _("socket name too long: %s"), socket_path);
+
+ if (!no_pid && !pidfile_path)
+ pidfile_path = UUIDD_PIDFILE_PATH;
+
+ /* custom socket path and socket-activation make no sense */
+ if (s_flag && uuidd_cxt.no_sock && !uuidd_cxt.quiet)
+ warnx(_("Both --socket-activation and --socket specified. "
+ "Ignoring --socket."));
+
+ if (num && do_type) {
+ ret = call_daemon(socket_path, do_type + 2, buf,
+ sizeof(buf), &num, &err_context);
+ if (ret < 0)
+ err(EXIT_FAILURE, _("error calling uuidd daemon (%s)"),
+ err_context ? : _("unexpected error"));
+
+ if (do_type == UUIDD_OP_TIME_UUID) {
+ if (ret != sizeof(uu) + sizeof(num))
+ unexpected_size(ret);
+
+ uuid_unparse((unsigned char *) buf, str);
+
+ printf(P_("%s and %d subsequent UUID\n",
+ "%s and %d subsequent UUIDs\n", num - 1),
+ str, num - 1);
+ } else {
+ printf(_("List of UUIDs:\n"));
+ cp = buf + 4;
+ if (ret != (int) (sizeof(num) + num * sizeof(uu)))
+ unexpected_size(ret);
+ for (i = 0; i < num; i++, cp += UUID_LEN) {
+ uuid_unparse((unsigned char *) cp, str);
+ printf("\t%s\n", str);
+ }
+ }
+ return EXIT_SUCCESS;
+ }
+ if (do_type) {
+ ret = call_daemon(socket_path, 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 (do_kill) {
+ ret = call_daemon(socket_path, UUIDD_OP_GETPID, buf, sizeof(buf), 0, NULL);
+ if ((ret > 0) && ((do_kill = atoi((char *) buf)) > 0)) {
+ ret = kill(do_kill, SIGTERM);
+ if (ret < 0) {
+ if (!uuidd_cxt.quiet)
+ warn(_("couldn't kill uuidd running "
+ "at pid %d"), do_kill);
+ return EXIT_FAILURE;
+ }
+ if (!uuidd_cxt.quiet)
+ printf(_("Killed uuidd running at pid %d.\n"),
+ do_kill);
+ }
+ return EXIT_SUCCESS;
+ }
+
+ server_loop(socket_path, 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..b4c9c46
--- /dev/null
+++ b/misc-utils/uuidd.service.in
@@ -0,0 +1,24 @@
+[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
+PrivateNetwork=yes
+PrivateUsers=yes
+ProtectKernelTunables=yes
+ProtectKernelModules=yes
+ProtectControlGroups=yes
+RestrictAddressFamilies=AF_UNIX
+MemoryDenyWriteExecute=yes
+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..0189587
--- /dev/null
+++ b/misc-utils/uuidgen.1
@@ -0,0 +1,102 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" This file may be copied under the terms of the GNU Public License.
+.TH UUIDGEN 1 "June 2011" "util-linux" "User Commands"
+.SH NAME
+uuidgen \- create a new UUID value
+.SH SYNOPSIS
+.B uuidgen
+[options]
+.SH DESCRIPTION
+The
+.B uuidgen
+program creates (and prints)
+a new universally unique identifier (UUID) using the
+.BR 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.
+.PP
+There are three types of UUIDs which
+.B uuidgen
+can generate: time-based UUIDs, random-based UUIDs, and hash-based UUIDs.
+By default
+.B 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
+.B \-\-random
+or
+.B \-\-time
+options.
+.PP
+The third type of UUID is generated with the
+.B \-\-md5
+or
+.B \-\-sha1
+options, followed by
+\fB\-\-namespace\fR \fInamespace\fR
+and
+\fB\-\-name\fR \fIname\fR.
+The \fInamespace\fR may either be a well-known UUID, or else
+an alias to one of the well-known UUIDs defined in RFC 4122, that is
+.BR @dns ,
+.BR @url ,
+.BR @oid ,
+or
+.BR @x500 .
+The \fIname\fR 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.
+.SH OPTIONS
+.TP
+.BR \-r , " \-\-random"
+Generate a random-based UUID. This method creates a UUID consisting mostly
+of random bits. It requires that the operating system have a high
+quality random number generator, such as
+.IR /dev/random .
+.TP
+.BR \-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.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-m , " \-\-md5"
+Use MD5 as the hash algorithm.
+.TP
+.BR \-s , " \-\-sha1"
+Use SHA1 as the hash algorithm.
+.TP
+.BR \-n , " \-\-namespace " \fInamespace\fP
+Generate the hash with the \fInamespace\fP prefix. The \fInamespace\fP is UUID,
+or '@ns' where "ns" is well-known predefined UUID addressed by namespace name
+(see above).
+.TP
+.BR \-N , " \-\-name " \fIname\fR
+Generate the hash of the \fIname\fR.
+.TP
+.BR \-x , " \-\-hex"
+Interpret name \fIname\fR as a hexadecimal string.
+.SH CONFORMING TO
+OSF DCE 1.1
+.SH EXAMPLES
+uuidgen \-\-sha1 \-\-namespace @dns \-\-name "www.example.com"
+.SH AUTHORS
+.B uuidgen
+was written by Andreas Dilger for libuuid.
+.SH SEE ALSO
+.BR libuuid (3),
+.B "RFC 4122"
+.SH AVAILABILITY
+The uuidgen command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/misc-utils/uuidgen.c b/misc-utils/uuidgen.c
new file mode 100644
index 0000000..fa148ab
--- /dev/null
+++ b/misc-utils/uuidgen.c
@@ -0,0 +1,209 @@
+/*
+ * 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);
+ 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(18));
+ 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:
+ fprintf(stderr, "%s: not a valid hex string\n", program_invocation_short_name);
+ 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) {
+ fprintf(stderr, "%s: --namespace requires --name argument\n", program_invocation_short_name);
+ errtryhelp(EXIT_FAILURE);
+ }
+ if (do_type != UUID_TYPE_DCE_MD5 && do_type != UUID_TYPE_DCE_SHA1) {
+ fprintf(stderr, "%s: --namespace requires --md5 or --sha1\n", program_invocation_short_name);
+ errtryhelp(EXIT_FAILURE);
+ }
+ } else {
+ if (name) {
+ fprintf(stderr, "%s: --name requires --namespace argument\n", program_invocation_short_name);
+ errtryhelp(EXIT_FAILURE);
+ }
+ if (do_type == UUID_TYPE_DCE_MD5 || do_type == UUID_TYPE_DCE_SHA1) {
+ fprintf(stderr, "%s: --md5 or --sha1 require --namespace\n", program_invocation_short_name);
+ 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) {
+ fprintf(stderr, "%s: unknown namespace alias '%s'\n", program_invocation_short_name, namespace);
+ errtryhelp(EXIT_FAILURE);
+ }
+ memcpy(ns, *uuidptr, sizeof(ns));
+ } else {
+ if (uuid_parse(namespace, ns) != 0) {
+ fprintf(stderr, "%s: invalid uuid for namespace '%s'\n", program_invocation_short_name, 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..ae735dc
--- /dev/null
+++ b/misc-utils/uuidparse.1
@@ -0,0 +1,78 @@
+.\" Copyright (c) 2017 Sami Kerola
+.\" The 3-Clause BSD License
+.TH UUIDPARSE "1" "2017-06-18" "util-linux" "User Commands"
+.SH NAME
+uuidparse \- a utility to parse unique identifiers
+.SH SYNOPSIS
+.B uuidparse
+[options]
+.I uuid
+.SH DESCRIPTION
+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
+.nr WI \n(.lu-\n(.iu-\w'Microsoft'u-3n
+.TS
+tab(:);
+l lw(\n(WIu).
+NCS:T{
+Network Computing System identifier. These were the original UUIDs.
+T}
+DCE:T{
+The Open Software Foundation's (OSF) Distributed Computing Environment UUIDs.
+T}
+Microsoft:T{
+Microsoft Windows platform globally unique identifier (GUID).
+T}
+other:T{
+Unknown variant. Usually invalid input data.
+T}
+.TE
+.SS Types
+.TS
+tab(:);
+l l.
+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.
+.TE
+.SH OPTIONS
+.TP
+\fB\-J\fR, \fB\-\-json\fR
+Use JSON output format.
+.TP
+\fB\-n\fR, \fB\-\-noheadings\fR
+Do not print a header line.
+.TP
+\fB\-o\fR, \fB\-\-output\fR
+Specify which output columns to print. Use \-\-help to get a list of all
+supported columns.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+Use the raw output format.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display help text and exit.
+.SH AUTHORS
+.MT kerolasa@iki.fi
+Sami Kerola
+.ME
+.SH SEE ALSO
+.BR uuidgen (1),
+.BR libuuid (3),
+.UR https://\:tools.ietf.org\:/html\:/rfc4122
+RFC 4122
+.UE
+.SH AVAILABILITY
+The uuidparse command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/misc-utils/uuidparse.c b/misc-utils/uuidparse.c
new file mode 100644
index 0000000..eae0b71
--- /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 0:
+ if (strspn(uuid, "0-") == 36)
+ str = xstrdup(_("nil"));
+ else
+ str = xstrdup(_("unknown"));
+ break;
+ case 1:
+ str = xstrdup(_("time-based"));
+ break;
+ case 2:
+ str = xstrdup("DCE");
+ break;
+ case 3:
+ str = xstrdup(_("name-based"));
+ break;
+ case 4:
+ str = xstrdup(_("random"));
+ break;
+ case 5:
+ 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 == 1) {
+ 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/whereis.1 b/misc-utils/whereis.1
new file mode 100644
index 0000000..08e351c
--- /dev/null
+++ b/misc-utils/whereis.1
@@ -0,0 +1,170 @@
+.\" 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
+.TH WHEREIS 1 "October 2014" "util-linux" "User Commands"
+.SH NAME
+whereis \- locate the binary, source, and manual page files for a command
+.SH SYNOPSIS
+.B whereis
+[options]
+.RB [ \-BMS
+.IR directory "... " \fB\-f\fR ]
+.IR name ...
+.SH DESCRIPTION
+.B whereis
+locates the binary, source and manual files for the specified command names.
+The supplied names are first stripped of leading pathname components and any
+(single) trailing extension of the form
+.BI . ext
+(for example:
+.BR .c )
+Prefixes of
+.B s.
+resulting from use of source code control are also dealt with.
+.B whereis
+then attempts to locate the desired program in the standard Linux places, and
+in the places specified by
+.B $PATH
+and
+.BR $MANPATH .
+.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
+.sp
+.B "whereis \-bm ls tr \-m gcc"
+.sp
+.RE
+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
+.sp
+.B "whereis \-m ls \-M /usr/share/man/man1 \-f cal"
+.sp
+.RE
+searches for "ls" man pages in all default paths, but for "cal" in
+the /usr/share/man/man1 directory only.
+
+.SH OPTIONS
+.IP \fB\-b\fP
+Search for binaries.
+.IP \fB\-m\fP
+Search for manuals.
+.IP \fB\-s\fP
+Search for sources.
+.IP \fB\-u\fP
+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
+.RB ' "whereis \-m \-u *" '
+asks for those files in the current directory which have no documentation file,
+or more than one.
+.IP "\fB\-B \fIlist\fP"
+Limit the places where
+.B whereis
+searches for binaries, by a whitespace-separated list of directories.
+.IP "\fB\-M \fIlist\fP"
+Limit the places where
+.B whereis
+searches for manuals and documentation in Info format, by a
+whitespace-separated list of directories.
+.IP "\fB\-S \fIlist\fP"
+Limit the places where
+.B whereis
+searches for sources, by a whitespace-separated list of directories.
+.IP "\fB\-f\fP"
+Terminates the directory list and signals the start of filenames. It
+.I must
+be used when any of the
+.BR \-B ,
+.BR \-M ,
+or
+.B \-S
+options is used.
+.IP "\fB\-l"
+Output the list of effective lookup paths that
+.B whereis
+is using. When none of
+.BR \-B ,
+.BR \-M ,
+or
+.B \-S
+is specified, the option will output the hard-coded paths
+that the command was able to find on the system.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display help text and exit.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.SH FILE SEARCH PATHS
+By default
+.B whereis
+tries to find files from hard-coded paths, which are defined with glob
+patterns. The command attempts to use the contents of
+.B $PATH
+and
+.B $MANPATH
+environment variables as default search path. The easiest way to know
+what paths are in use is to add the
+.B \-l
+listing option. Effects of the
+.BR \-B ,
+.BR \-M ,
+and
+.B \-S
+are displayed with
+.BR \-l .
+.SH ENVIRONMENT
+.IP WHEREIS_DEBUG=all
+enables debug output.
+.SH EXAMPLES
+To find all files in
+.I /usr/\:bin
+which are not documented
+in
+.I /usr/\:man/\:man1
+or have no source in
+.IR /usr/\:src :
+.IP
+.B cd /usr/bin
+.br
+.B whereis \-u \-ms \-M /usr/man/man1 \-S /usr/src \-f *
+.SH AVAILABILITY
+The whereis command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/misc-utils/whereis.c b/misc-utils/whereis.c
new file mode 100644
index 0000000..0247dbe
--- /dev/null
+++ b/misc-utils/whereis.c
@@ -0,0 +1,656 @@
+/*-
+ * 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>
+
+#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 = 0;
+
+/* 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/lib64",
+ "/etc",
+ "/usr/etc",
+ "/lib",
+ "/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(_(" -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 i = strlen(dp);
+
+ DBG(SEARCH, ul_debug("compare '%s' and '%s'", cp, dp));
+
+ if (dp[0] == 's' && dp[1] == '.' && filename_equal(cp, dp + 2))
+ return 1;
+ 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;
+ while (isdigit(*dp))
+ dp++;
+ if (*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)
+{
+ 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))
+ 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" : ""));
+ p = strrchr(patbuf, '.');
+ if (p)
+ *p = '\0';
+
+ 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);
+ }
+
+ 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 '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..303e661
--- /dev/null
+++ b/misc-utils/wipefs.8
@@ -0,0 +1,148 @@
+.\" Copyright 2009 by Karel Zak. All Rights Reserved.
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH WIPEFS 8 "December 2014" "util-linux" "System Administration"
+.SH NAME
+wipefs \- wipe a signature from a device
+.SH SYNOPSIS
+.B wipefs
+.RB [ options ]
+.IR device ...
+.sp
+.B wipefs
+.RB [ \-\-backup ]
+.B \-o
+.I offset
+.IR device ...
+.sp
+.B wipefs
+.RB [ \-\-backup ]
+.B \-a
+.IR device ...
+.SH DESCRIPTION
+.B wipefs
+can erase filesystem, raid or partition-table signatures (magic strings) from
+the specified
+.I device
+to make the signatures invisible for libblkid.
+.B 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
+.B \-\-output
+.I columns-list
+in environments where a stable output is required.
+
+.B 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 \fBwipefs \-a /dev/sdc1 /dev/sdc2 /dev/sdc\fR.
+
+Note that some filesystems and some partition tables store more magic strings on
+the device (e.g., FAT, ZFS, GPT). The
+.B wipefs
+command (since v2.31) lists all the offset where a magic strings have been
+detected.
+
+When option \fB\-a\fR is used, all magic strings that are visible for libblkid are
+erased. In this case the
+.B wipefs
+scans the device again after each modification (erase) until no magic string is found.
+
+Note that by default
+.B wipefs
+does not erase nested partition tables on non-whole disk devices.
+For this the option \fB\-\-force\fR is required.
+
+.SH OPTIONS
+.TP
+.BR \-a , " \-\-all"
+Erase all available signatures. The set of erased signatures can be
+restricted with the \fB\-t\fR option.
+.TP
+.BR \-b , " \-\-backup"
+Create a signature backup to the file $HOME/wipefs-<devname>-<offset>.bak.
+For more details see the \fBEXAMPLE\fR section.
+.TP
+.BR \-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.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.TP
+.BR \-J , " \-\-json"
+Use JSON output format.
+.TP
+\fB\-\-lock\fR[=\fImode\fR]
+Use exclusive BSD lock for device or file it operates. The optional argument
+\fImode\fP can be \fByes\fR, \fBno\fR (or 1 and 0) or \fBnonblock\fR. If the \fImode\fR
+argument is omitted, it defaults to \fB"yes"\fR. This option overwrites
+environment variable \fB$LOCK_BLOCK_DEVICE\fR. The default is not to use any
+lock at all, but it's recommended to avoid collisions with udevd or other
+tools.
+.TP
+.BR \-i , " \-\-noheadings"
+Do not print a header line.
+.TP
+.BR \-O , " \-\-output " \fIlist\fP
+Specify which output columns to print. Use \-\-help to
+get a list of all supported columns.
+.TP
+.BR \-n , " \-\-no\-act"
+Causes everything to be done except for the write() call.
+.TP
+.BR \-o , " \-\-offset " \fIoffset\fP
+Specify the location (in bytes) of the signature which should be erased from the
+device. The \fIoffset\fR number may include a "0x" prefix; then the number will be
+interpreted as a hex value. It is possible to specify multiple \fB\-o\fR options.
+.sp
+The \fIoffset\fR 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.
+.TP
+.BR \-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'.
+.TP
+.BR \-q , " \-\-quiet"
+Suppress any messages after a successful signature wipe.
+.TP
+.BR \-t , " \-\-types " \fIlist\fP
+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).
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.SH ENVIRONMENT
+.IP LIBBLKID_DEBUG=all
+enables libblkid debug output.
+.IP LOCK_BLOCK_DEVICE=<mode>
+use exclusive BSD lock. The mode is "1" or "0". See \fB\-\-lock\fR for more details.
+.SH EXAMPLE
+.TP
+.B wipefs /dev/sda*
+Prints information about sda and all partitions on sda.
+.TP
+.B 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.
+.TP
+.B 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.
+.SH AUTHORS
+Karel Zak <kzak@redhat.com>
+.SH SEE ALSO
+.BR blkid (8),
+.BR findfs (8)
+.SH AVAILABILITY
+The wipefs command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/misc-utils/wipefs.c b/misc-utils/wipefs.c
new file mode 100644
index 0000000..77dfe6a
--- /dev/null
+++ b/misc-utils/wipefs.c
@@ -0,0 +1,851 @@
+/*
+ * 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)
+{
+ if (ctl->parsable && !ctl->no_headings
+ && !scols_table_is_empty(ctl->outtab)) {
+ struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD);
+ struct libscols_column *cl;
+ int i = 0;
+
+ if (!itr)
+ err_oom();
+
+ fputs("# ", stdout);
+ while (scols_table_next_column(ctl->outtab, itr, &cl) == 0) {
+ struct libscols_cell *hdr = scols_column_get_header(cl);
+ const char *name = scols_cell_get_data(hdr);
+
+ if (i)
+ fputc(',', stdout);
+ fputs(name, stdout);
+ i++;
+ }
+ fputc('\n', stdout);
+ scols_free_iter(itr);
+ }
+ 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;
+
+ *offset = strtoll(off, NULL, 10);
+
+ /* 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(25000);
+
+ 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."));
+
+ fsync(blkid_probe_get_fd(pr));
+
+#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
+
+ close(blkid_probe_get_fd(pr));
+ 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;
+ 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 (size_t 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;
+}