diff options
Diffstat (limited to 'misc-utils')
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; +} |