diff options
Diffstat (limited to 'misc-utils')
50 files changed, 13801 insertions, 0 deletions
diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am new file mode 100644 index 0000000..910e428 --- /dev/null +++ b/misc-utils/Makemodule.am @@ -0,0 +1,175 @@ + +usrbin_exec_PROGRAMS += cal +dist_man_MANS += misc-utils/cal.1 +cal_SOURCES = \ + misc-utils/cal.c \ + lib/mbsalign.c \ + lib/strutils.c + +if !HAVE_LANGINFO +cal_SOURCES += lib/langinfo.c +endif + +cal_LDADD = $(LDADD) + +if HAVE_TINFO +cal_LDADD += -ltinfo @NCURSES_LIBS@ +else +if HAVE_NCURSES +cal_LDADD += @NCURSES_LIBS@ +else +if HAVE_TERMCAP +cal_LDADD += -ltermcap +endif +endif # !HAVE_NCURSES +endif # !HAVE_TINFO + + +usrbin_exec_PROGRAMS += logger +dist_man_MANS += misc-utils/logger.1 +logger_SOURCES = misc-utils/logger.c lib/strutils.c + +usrbin_exec_PROGRAMS += look +dist_man_MANS += misc-utils/look.1 +look_SOURCES = misc-utils/look.c + +usrbin_exec_PROGRAMS += mcookie +dist_man_MANS += misc-utils/mcookie.1 +mcookie_SOURCES = misc-utils/mcookie.c lib/md5.c + +usrbin_exec_PROGRAMS += namei +dist_man_MANS += misc-utils/namei.1 +namei_SOURCES = misc-utils/namei.c lib/strutils.c + +usrbin_exec_PROGRAMS += whereis +dist_man_MANS += misc-utils/whereis.1 +whereis_SOURCES = misc-utils/whereis.c + + +if BUILD_CHKDUPEXE +dist_usrbin_exec_SCRIPTS += misc-utils/chkdupexe +CLEANFILES += misc-utils/chkdupexe +dist_man_MANS += misc-utils/chkdupexe.1 +endif + +if BUILD_DDATE +usrbin_exec_PROGRAMS += ddate +dist_man_MANS += misc-utils/ddate.1 +ddate_SOURCES = misc-utils/ddate.c +ruman1_DATA += man/ru/ddate.1 +endif + +if BUILD_LSLOCKS +usrbin_exec_PROGRAMS += lslocks +dist_man_MANS += misc-utils/lslocks.8 +lslocks_SOURCES = \ + misc-utils/lslocks.c \ + lib/at.c \ + lib/strutils.c \ + lib/tt.c \ + lib/mbsalign.c +endif + +if BUILD_LSBLK +bin_PROGRAMS += lsblk +dist_man_MANS += misc-utils/lsblk.8 +lsblk_SOURCES = misc-utils/lsblk.c +lsblk_LDADD = $(LDADD) libblkid.la libmount.la libcommon.la +lsblk_CFLAGS = $(AM_CFLAGS) -I$(ul_libblkid_incdir) -I$(ul_libmount_incdir) +if HAVE_UDEV +lsblk_LDADD += -ludev +endif +endif + +if BUILD_LIBUUID +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_UUIDD +usrsbin_exec_PROGRAMS += uuidd +dist_man_MANS += misc-utils/uuidd.8 +uuidd_LDADD = $(LDADD) libuuid.la +uuidd_CFLAGS = $(AM_CFLAGS) -I$(ul_libuuid_incdir) +uuidd_SOURCES = misc-utils/uuidd.c +if USE_SOCKET_ACTIVATION +uuidd_SOURCES += misc-utils/sd-daemon.c misc-utils/sd-daemon.h +uuidd_LDADD += -lrt +endif +if HAVE_SYSTEMD +systemdsystemunit_DATA += \ + misc-utils/uuidd.service \ + misc-utils/uuidd.socket +endif +endif # BUILD_UUIDD + +PATHFILES += \ + misc-utils/uuidd.8 \ + misc-utils/uuidd.rc \ + misc-utils/uuidd.service \ + misc-utils/uuidd.socket + +if BUILD_LIBBLKID +sbin_PROGRAMS += blkid +dist_man_MANS += misc-utils/blkid.8 +blkid_SOURCES = misc-utils/blkid.c +blkid_LDADD = $(LDADD) libblkid.la libcommon.la +blkid_CFLAGS = $(AM_CFLAGS) -I$(ul_libblkid_incdir) + +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) + +sbin_PROGRAMS += wipefs +dist_man_MANS += misc-utils/wipefs.8 +wipefs_SOURCES = misc-utils/wipefs.c +wipefs_LDADD = $(LDADD) libblkid.la libcommon.la +wipefs_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_LIBBLKID + + +if BUILD_LIBMOUNT +bin_PROGRAMS += findmnt +dist_man_MANS += misc-utils/findmnt.8 +findmnt_LDADD = $(LDADD) libmount.la libcommon.la +findmnt_CFLAGS = $(AM_CFLAGS) -I$(ul_libmount_incdir) +findmnt_SOURCES = misc-utils/findmnt.c +endif # BUILD_LIBMOUNT + + +if BUILD_KILL +bin_PROGRAMS += kill +kill_SOURCES = \ + misc-utils/kill.c \ + misc-utils/kill.h \ + misc-utils/procs.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 + +usrbin_exec_PROGRAMS += getopt +dist_man_MANS += misc-utils/getopt.1 +getopt_SOURCES = misc-utils/getopt.c +getoptexampledir = $(datadir)/getopt/ +dist_getoptexample_SCRIPTS = \ + misc-utils/getopt-parse.bash \ + misc-utils/getopt-parse.tcsh diff --git a/misc-utils/blkid.8 b/misc-utils/blkid.8 new file mode 100644 index 0000000..dc1e68f --- /dev/null +++ b/misc-utils/blkid.8 @@ -0,0 +1,299 @@ +.\" Copyright 2000 Andreas Dilger (adilger@turbolinux.com) +.\" +.\" This man page was created for blkid from e2fsprogs-1.25. +.\" +.\" This file may be copied under the terms of the GNU Public License. +.\" +.\" Based on uuidgen, Mon Sep 17 10:42:12 2000, Andreas Dilger +.TH BLKID 8 "February 2011" "util-linux" "System Administration" +.SH NAME +blkid \- locate/print block device attributes +.SH SYNOPSIS +.B blkid +.RB \-L +.IR label " | " +.RB \-U +.IR uuid + +.B blkid +.RB [ \-dghlv ] +.RB [ \-c +.IR file ] +.RB [ \-o +.IR format ] +.in +6 +.RB [ \-s +.IR tag ] +.RB [ \-t +.IR NAME=value ] +[\fIdevice\fR ...] +.in -6 + +.B blkid +.RB -p +.RB [ \-O +.IR offset ] +.RB [ \-S +.IR size ] +.RB [ \-o +.IR format ] +.RB [ \-s +.IR tag ] +.in +9 +.RB [ \-n +.IR list ] +.RB [ \-u +.IR list ] +.IR device " ... " +.in -9 + +.B blkid +.RB -i +.RB [ \-o +.IR format ] +.RB [ \-s +.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 attributes (tokens, NAME=value pairs) +from the content metadata (e.g. LABEL or UUID fields). +.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. +.SH OPTIONS +The \fIsize\fR and \fIoffset\fR arguments may be followed by the multiplicative +suffixes KiB=1024, MiB=1024*1024, and so on for GiB, TiB, PiB, EiB, ZiB and YiB +(the "iB" is optional, e.g. "K" has the same meaning as "KiB") or the suffixes +KB=1000, MB=1000*1000, and so on for GB, PB, EB, ZB and YB. +.TP +.BI \-c " cachefile" +Read from +.I cachefile +instead of reading from the default cache file (see 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 +.B \-d +Don't encode non-printing characters. The non-printing characters are encoded +by ^ and M- notation by default. Note that \fB-o udev\fR output format uses +a different encoding and this encoding cannot be disabled. +.TP +.B \-g +Perform a garbage collection pass on the blkid cache to remove +devices which no longer exist. +.TP +.B \-h +Display a usage message and exit. +.TP +.B \-i +Display I/O Limits (aka I/O topology) information. The 'export' output format is +automatically enabled. This option can be used together with the \fB-p\fR option. +.TP +.B \-l +Look up only one device that matches the search parameter specified with \fB-t\fR. +.TP +.B \-k +List all known filesystems and RAIDs and exit. +.TP +.B \-t +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. 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. +.TP +.BI \-L " label" +Look up the device that uses this filesystem \fIlabel\fR (equal to: -l -o device -t +LABEL=<label>). This lookup method is able to reliably use /dev/disk/by-label +udev symlinks (dependent on a setting in /etc/blkid.conf). Avoid using the +symlinks directly; it is not reliable to use the symlinks without verification. +The \fB-L\fR option works on systems with and without udev. + +Unfortunately, the original +.B blkid(8) +from e2fsprogs use the \fB-L\fR option as a +synonym for the \fB-o list\fR option. For better portability, use \fB-l -o device +-t LABEL=<label>\fR and \fB-o list\fR in your scripts rather than the \fB-L\fR option. +.TP +.BI \-n " list " +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 -p -n vfat,ext3,ext4 /dev/sda1 +.sp +probes for vfat, ext3 and ext4 filesystems, and +.sp + blkid -p -n nominix /dev/sda1 +.sp +probes for all supported formats except minix filesystems. +This option is only useful together with \fB-p\fR. +.TP +.BI \-o " format" +Display +.BR blkid 's +output using the specified format. Note that the variables order and +devices order is variable. See also \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-p\fR or \fB-i\fR). This output format is \fBDEPRECATED\fR +in favour of +.BR lsblk (8) +command. +.TP +.B device +print the device name only; this output format is always enabled for \fB-L\fR +and \fB-U\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 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-i\fR option) are requested +.RE +.TP +.BI \-O " offset" +Probe at the given \fIoffset\fR (only useful with \fB-p\fR). This option can be +used together with the \fB-i\fR option. +.TP +.BI \-p +Switch to low-level superblock probing mode (bypass cache). + +Note that low-level probing also returns information about partition table type +(PTTYPE tag) and partitions (PART_ENTRY_* tags). +.TP +.BI \-s " tag" +For each (specified) device, show only the tags that match +.IR tag . +It is possible to specify multiple +.B \-s +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 "-s none" +with no other options. +.TP +.BI \-S " size" +Overwrite device/file size (only useful with \fB-p\fR). +.TP +.BI \-t " NAME" = "value" +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 +.BI \-u " list " +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 -p -u filesystem,other /dev/sda1 +.sp +probes for all filesystem and other (e.g. swap) formats, and +.sp + blkid -p -u noraid /dev/sda1 +.sp +probes for all supported formats except RAIDs. +This option is only useful together with \fB-p\fR. +.TP +.BI \-U " uuid " +Look up the device that uses this filesystem \fIuuid\fR. For more details see the \fB-L\fR option. +.TP +.B \-v +Display version number and exit. +.TP +.I device +Display tokens from only the specified device. It is possible to +give multiple +.I device +options on the command line. If none is given, all devices which +appear in +.I /proc/partitions +are shown, if they are recognized. +.SH "RETURN CODE" +If the specified token was found, or if any tags were shown from (specified) +devices, 0 is returned. + +If the specified token was not found, or no (specified) devices could be +identified, an exit code of 2 is returned. + +For usage or other errors, an exit code of 4 is returned. + +If the ambivalent low-level probing result was detected, an exit code 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 +.I /run/blkid/blkid.tab +or +.I /etc/blkid.tab +on systems without /run direcotry +.TP +.I EVALUATE=<methods> +Defines LABEL and UUID evaluation method(s). Currently, the libblkid library +supports "udev" and "scan" methods. More than one methods 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 AUTHOR +.B blkid +was written by Andreas Dilger for libblkid and improved by Theodore Ts'o +and Karel Zak. +.SH AVAILABILITY +The blkid command is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. +.SH "SEE ALSO" +.BR libblkid (3) +.BR findfs (8) +.BR wipefs (8) diff --git a/misc-utils/blkid.c b/misc-utils/blkid.c new file mode 100644 index 0000000..fa38d82 --- /dev/null +++ b/misc-utils/blkid.c @@ -0,0 +1,957 @@ +/* + * 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> +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#else +extern int getopt(int argc, char * const argv[], const char *optstring); +extern char *optarg; +extern int optind; +#endif + +#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 LOWPROBE_TOPOLOGY (1 << 1) +#define LOWPROBE_SUPERBLOCKS (1 << 2) + +#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" + +#define STRTOXX_EXIT_CODE BLKID_EXIT_OTHER /* strtoxx_or_err() */ +#include "strutils.h" +#define OPTUTILS_EXIT_CODE BLKID_EXIT_OTHER /* exclusive_option() */ +#include "optutils.h" + +#include "closestream.h" +#include "ttyutils.h" + +const char *progname = "blkid"; + +int raw_chars; + +static void print_version(FILE *out) +{ + fprintf(out, "%s from %s (libblkid %s, %s)\n", + progname, PACKAGE_STRING, LIBBLKID_VERSION, LIBBLKID_DATE); +} + +static void usage(int error) +{ + FILE *out = error ? stderr : stdout; + + print_version(out); + fprintf(out, + "Usage:\n" + " %1$s -L <label> | -U <uuid>\n\n" + " %1$s [-c <file>] [-ghlLv] [-o <format>] [-s <tag>] \n" + " [-t <token>] [<dev> ...]\n\n" + " %1$s -p [-s <tag>] [-O <offset>] [-S <size>] \n" + " [-o <format>] <dev> ...\n\n" + " %1$s -i [-s <tag>] [-o <format>] <dev> ...\n\n" + "Options:\n" + " -c <file> read from <file> instead of reading from the default\n" + " cache file (-c /dev/null means no cache)\n" + " -d don't encode non-printing characters\n" + " -h print this usage message and exit\n" + " -g garbage collect the blkid cache\n" + " -o <format> output format; can be one of:\n" + " value, device, export or full; (default: full)\n" + " -k list all known filesystems/RAIDs and exit\n" + " -s <tag> show specified tag(s) (default show all tags)\n" + " -t <token> find device with a specific token (NAME=value pair)\n" + " -l look up only first device with token specified by -t\n" + " -L <label> convert LABEL to device name\n" + " -U <uuid> convert UUID to device name\n" + " -v print version and exit\n" + " <dev> specify device(s) to probe (default: all devices)\n\n" + "Low-level probing options:\n" + " -p low-level superblocks probing (bypass cache)\n" + " -i gather information about I/O limits\n" + " -S <size> overwrite device size\n" + " -O <offset> probe at the given offset\n" + " -u <list> filter by \"usage\" (e.g. -u filesystem,raid)\n" + " -n <list> filter by filesystem type (e.g. -n vfat,ext3)\n" + "\n", progname); + + exit(error); +} + +/* + * This function does "safe" printing. It will convert non-printable + * ASCII characters using '^' and M- notation. + */ +static void safe_print(const char *cp, int len) +{ + unsigned char ch; + + if (len < 0) + len = strlen(cp); + + while (len--) { + ch = *cp++; + if (!raw_chars) { + if (ch >= 128) { + fputs("M-", stdout); + ch -= 128; + } + if ((ch < 32) || (ch == 0x7f)) { + fputc('^', stdout); + ch ^= 0x40; /* ^@, ^A, ^B; ^? for DEL */ + } + } + 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(); + if (term_width <= 0) + term_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()-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) { + if (mount_flags & MF_MOUNTED) { + if (!mtpt[0]) + strcpy(mtpt, "(mounted, mtpt unknown)"); + } else if (mount_flags & MF_BUSY) + strcpy(mtpt, "(in use)"); + else + strcpy(mtpt, "(not mounted)"); + } + + 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") || + !strcmp(name, "LABEL") || + !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, "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(char *ary[], const char *item) +{ + char **p; + + for (p = ary; *p != NULL; p++) + if (!strcmp(item, *p)) + return 1; + return 0; +} + +static void print_value(int output, int num, const char *devname, + const char *value, const char *name, size_t valsz) +{ + if (output & OUTPUT_VALUE_ONLY) { + fputs(value, stdout); + fputc('\n', stdout); + + } else if (output & OUTPUT_UDEV_LIST) { + print_udev_format(name, value); + + } else if (output & OUTPUT_EXPORT_LIST) { + if (num == 1 && devname) + printf("DEVNAME=%s\n", devname); + fputs(name, stdout); + fputs("=", stdout); + safe_print(value, valsz); + fputs("\n", stdout); + + } else { + if (num == 1 && devname) + printf("%s: ", devname); + fputs(name, stdout); + fputs("=\"", stdout); + safe_print(value, valsz); + fputs("\" ", stdout); + } +} + +static void print_tags(blkid_dev dev, char *show[], int output) +{ + blkid_tag_iterate iter; + const char *type, *value, *devname; + int num = 1; + static int first = 1; + + if (!dev) + return; + + if (output & OUTPUT_PRETTY_LIST) { + pretty_print_dev(dev); + return; + } + + devname = blkid_dev_devname(dev); + + if (output & OUTPUT_DEVICE_ONLY) { + printf("%s\n", devname); + return; + } + + iter = blkid_tag_iterate_begin(dev); + while (blkid_tag_next(iter, &type, &value) == 0) { + if (show[0] && !has_item(show, type)) + continue; + + if (num == 1 && !first && + (output & (OUTPUT_UDEV_LIST | OUTPUT_EXPORT_LIST))) + /* add extra line between output from more devices */ + fputc('\n', stdout); + + print_value(output, num++, devname, value, type, strlen(value)); + } + blkid_tag_iterate_end(iter); + + if (num > 1) { + if (!(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; + + str = realloc(str, len + 1); + if (!str) { + free(*res); + return -1; + } + + *res = str; + 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_AMBIVALEN=%s\n", val); + rc = 0; + } +done: + free(val); + return rc; +} + +static int lowprobe_superblocks(blkid_probe pr) +{ + 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 = succes */ + + if (blkid_probe_lookup_value(pr, "PTTYPE", NULL, NULL) == 0) + return 0; /* partition table detected */ + } + + 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, + int chain, char *show[], int output, + blkid_loff_t offset, blkid_loff_t size) +{ + 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); + if (fd < 0) { + fprintf(stderr, "error: %s: %m\n", devname); + return BLKID_EXIT_NOTFOUND; + } + if (blkid_probe_set_device(pr, fd, offset, size)) + goto done; + + if (chain & LOWPROBE_TOPOLOGY) + rc = lowprobe_topology(pr); + if (rc >= 0 && (chain & LOWPROBE_SUPERBLOCKS)) + rc = lowprobe_superblocks(pr); + if (rc < 0) + goto done; + + if (!rc) + nvals = blkid_probe_numof_values(pr); + + if (nvals && + !(chain & LOWPROBE_TOPOLOGY) && + !(output & OUTPUT_UDEV_LIST) && + !blkid_probe_has_value(pr, "TYPE") && + !blkid_probe_has_value(pr, "PTTYPE")) + /* + * Ignore probing result if there is not any filesystem or + * partition table on the device and udev output is not + * requested. + * + * The udev db stores information about partitions, so + * PART_ENTRY_* values are alway important. + */ + nvals = 0; + + if (nvals && !first && output & (OUTPUT_UDEV_LIST | OUTPUT_EXPORT_LIST)) + /* add extra line between output from devices */ + fputc('\n', stdout); + + if (nvals && (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 (show[0] && !has_item(show, name)) + continue; + len = strnlen((char *) data, len); + print_value(output, num++, devname, (char *) data, name, len); + } + + if (first) + first = 0; + if (nvals >= 1 && !(output & (OUTPUT_VALUE_ONLY | + OUTPUT_UDEV_LIST | OUTPUT_EXPORT_LIST))) + printf("\n"); +done: + if (rc == -2) { + if (output & OUTPUT_UDEV_LIST) + print_udev_ambivalent(pr); + else + fprintf(stderr, + "%s: ambivalent result (probably more " + "filesystems on the device, use wipefs(8) " + "to see more details)\n", + 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; + fprintf(stderr, "unknown kerword in -u <list> argument: '%s'\n", + 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) { + fprintf(stderr, "error: -u <list> argument is empty\n"); + goto err; + } + for (i = 1; p && (p = strchr(p, ',')); i++, p++); + + res = calloc(i + 1, sizeof(char *)); + if (!res) + goto err_mem; + p = *flag & BLKID_FLTR_NOTIN ? list + 2 : list; + i = 0; + + while(p) { + const char *word = p; + p = strchr(p, ','); + res[i] = p ? strndup(word, p - word) : strdup(word); + if (!res[i++]) + goto err_mem; + if (p) + p++; + } + res[i] = NULL; + return res; +err_mem: + fprintf(stderr, "out of memory\n"); +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) +{ + blkid_cache cache = NULL; + char **devices = NULL; + char *show[128] = { 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 version = 0; + int err = BLKID_EXIT_OTHER; + unsigned int i; + int output_format = 0; + int lookup = 0, gc = 0, lowprobe = 0, eval = 0; + int c; + uintmax_t offset = 0, size = 0; + + static const ul_excl_t excl[] = { /* rows and cols in in ASCII order */ + { 'n','u' }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + show[0] = NULL; + atexit(close_stdout); + + while ((c = getopt (argc, argv, + "c:df:ghilL:n:ko:O:ps:S:t:u:U:w:v")) != EOF) { + + err_exclusive_options(c, NULL, excl, excl_st); + + switch (c) { + case 'c': + if (optarg && !*optarg) + read = NULL; + else + read = optarg; + break; + case 'd': + raw_chars = 1; + break; + case 'L': + eval++; + search_value = strdup(optarg); + search_type = strdup("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': + eval++; + search_value = strdup(optarg); + search_type = strdup("UUID"); + break; + case 'i': + lowprobe |= LOWPROBE_TOPOLOGY; + break; + case 'l': + lookup++; + break; + case 'g': + 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")) + output_format = OUTPUT_VALUE_ONLY; + else if (!strcmp(optarg, "device")) + output_format = OUTPUT_DEVICE_ONLY; + else if (!strcmp(optarg, "list")) + output_format = OUTPUT_PRETTY_LIST; /* deprecated */ + else if (!strcmp(optarg, "udev")) + output_format = OUTPUT_UDEV_LIST; + else if (!strcmp(optarg, "export")) + output_format = OUTPUT_EXPORT_LIST; + else if (!strcmp(optarg, "full")) + output_format = 0; + else { + fprintf(stderr, "Invalid output format %s. " + "Choose from value,\n\t" + "device, list, udev or full\n", optarg); + exit(BLKID_EXIT_OTHER); + } + break; + case 'O': + offset = strtosize_or_err(optarg, "invalid offset argument"); + break; + case 'p': + lowprobe |= LOWPROBE_SUPERBLOCKS; + break; + case 's': + if (numtag + 1 >= sizeof(show) / sizeof(*show)) { + fprintf(stderr, "Too many tags specified\n"); + usage(err); + } + show[numtag++] = optarg; + show[numtag] = NULL; + break; + case 'S': + size = strtosize_or_err(optarg, "invalid size argument"); + break; + case 't': + if (search_type) { + fprintf(stderr, "Can only search for " + "one NAME=value pair\n"); + usage(err); + } + if (blkid_parse_tag_string(optarg, + &search_type, + &search_value)) { + fprintf(stderr, "-t needs NAME=value pair\n"); + usage(err); + } + break; + case 'v': + version = 1; + break; + case 'w': + /* ignore - backward compatibility */ + break; + case 'h': + err = 0; + /* fallthrough */ + default: + usage(err); + } + } + + + /* The rest of the args are device names */ + if (optind < argc) { + devices = calloc(argc - optind, sizeof(char *)); + if (!devices) { + fprintf(stderr, "Failed to allocate device name array\n"); + goto exit; + } + + while (optind < argc) + devices[numdev++] = argv[optind++]; + } + + if (version) { + print_version(stdout); + goto exit; + } + + /* convert LABEL/UUID lookup to evaluate request */ + if (lookup && output_format == OUTPUT_DEVICE_ONLY && search_type && + (!strcmp(search_type, "LABEL") || !strcmp(search_type, "UUID"))) { + eval++; + lookup = 0; + } + + if (!lowprobe && !eval && blkid_get_cache(&cache, read) < 0) + goto exit; + + if (gc) { + blkid_gc_cache(cache); + err = 0; + goto exit; + } + err = BLKID_EXIT_NOTFOUND; + + if (eval == 0 && (output_format & OUTPUT_PRETTY_LIST)) { + if (lowprobe) { + fprintf(stderr, "The low-level probing mode does not " + "support 'list' output format\n"); + exit(BLKID_EXIT_OTHER); + } + pretty_print_dev(NULL); + } + + if (lowprobe) { + /* + * Low-level API + */ + blkid_probe pr; + + if (!numdev) { + fprintf(stderr, "The low-level probing mode " + "requires a device\n"); + exit(BLKID_EXIT_OTHER); + } + + /* automatically enable 'export' format for I/O Limits */ + if (!output_format && (lowprobe & LOWPROBE_TOPOLOGY)) + output_format = OUTPUT_EXPORT_LIST; + + pr = blkid_new_probe(); + if (!pr) + goto exit; + + if (lowprobe & 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], lowprobe, show, + output_format, + (blkid_loff_t) offset, + (blkid_loff_t) size); + if (err) + break; + } + blkid_free_probe(pr); + } else if (eval) { + /* + * Evaluate API + */ + char *res = blkid_evaluate_tag(search_type, search_value, NULL); + if (res) { + err = 0; + printf("%s\n", res); + } + } else if (lookup) { + /* + * Classic (cache based) API + */ + blkid_dev dev; + + if (!search_type) { + fprintf(stderr, "The lookup option requires a " + "search type specified using -t\n"); + exit(BLKID_EXIT_OTHER); + } + /* 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(dev, show, output_format); + 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(dev, show, output_format); + 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(dev, show, output_format); + err = 0; + } + } + +exit: + free(search_type); + free(search_value); + free_types_list(fltr_type); + if (!lowprobe && !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..4d9b3d1 --- /dev/null +++ b/misc-utils/cal.1 @@ -0,0 +1,97 @@ +.\" 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 "June 2011" "util-linux" "User Commands" +.SH NAME +cal \- display a calendar +.SH SYNOPSIS +.B cal +[\fIoptions\fR] [[[\fIday\fR] \fImonth\fR] \fIyear\fR] +.SH DESCRIPTION +.B cal +displays a simple calendar. If no arguments are specified, the current +month is displayed. +.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 prev/current/next month output. +.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\-j\fR, \fB\-\-julian\fR +Display Julian dates (days one-based, numbered from January 1). +.TP +\fB\-y\fR, \fB\-\-year\fR +Display a calendar for the current year. +.TP +\fB\-V\fR, \fB\-\-version\fR +Display version information and exit. +.TP +\fB\-h\fR, \fB\-\-help\fR +Display help screen and exit. +.SH PARAMETERS +A single parameter specifies the year (1 - 9999) to be displayed; note the +year must be fully specified: +.B "cal 89" +will not display a calendar for 1989. +.PP +Two parameters denote the month (1 - 12) and year. +.PP +Three parameters denote the day (1-31), month and year, and the day will be +highlighted if the calendar is displayed on a terminal. If no parameters are +specified, the current month's calendar is displayed. +.PP +A year starts on Jan 1. The first day of the week is determined by the +locale. +.PP +The Gregorian Reformation is assumed to have occurred in 1752 on the 3rd of +September. By this time, most countries had recognized the reformation +(although a few did not recognize it until the early 1900's). Ten days +following that date were eliminated by the reformation, so the calendar for +that month is a bit unusual. +.SH HISTORY +A cal command appeared in Version 6 AT&T UNIX. +.SH AVAILABILITY +The cal command is part of the util-linux package and is available from +ftp://ftp.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..60e85df --- /dev/null +++ b/misc-utils/cal.c @@ -0,0 +1,800 @@ +/* + * 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 over-ridable default NUM_MONTHS 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <errno.h> + +#include "c.h" +#include "closestream.h" +#include "nls.h" +#include "mbsalign.h" +#include "strutils.h" + +#if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) + +#ifdef HAVE_NCURSES_H +#include <ncurses.h> +#elif defined(HAVE_NCURSES_NCURSES_H) +#include <ncurses/ncurses.h> +#endif + +#include <term.h> /* include after <curses.h> */ + +static void +my_setupterm(const char *term, int fildes, int *errret) { + setupterm((char*)term, fildes, errret); +} + +static void +my_putstring(char *s) { + putp(s); +} + +static const char * +my_tgetstr(char *s __attribute__ ((__unused__)), char *ss) { + const char* ret = tigetstr(ss); + if (!ret || ret==(char*)-1) + return ""; + else + return ret; +} + +#elif defined(HAVE_LIBTERMCAP) + +#include <termcap.h> + +char termbuffer[4096]; +char tcbuffer[4096]; +char *strbuf = termbuffer; + +static void +my_setupterm(const char *term, int fildes, int *errret) { + *errret = tgetent(tcbuffer, term); +} + +static void +my_putstring(char *s) { + tputs (s, 1, putchar); +} + +static const char * +my_tgetstr(char *s, char *ss __attribute__ ((__unused__))) { + const char* ret = tgetstr(s, &strbuf); + if (!ret) + return ""; + else + return ret; +} + +#else /* ! (HAVE_LIBTERMCAP || HAVE_LIBNCURSES || HAVE_LIBNCURSESW) */ + +static void +my_putstring(char *s) { + fputs(s, stdout); +} + +#endif + + +const char *term=""; +const char *Senter="", *Sexit="";/* enter and exit standout mode */ +int Slen; /* strlen of Senter+Sexit */ +char *Hrow; /* pointer to highlighted row in month */ + +#include "widechar.h" + +/* allow compile-time define to over-ride default */ +#ifndef NUM_MONTHS +#define NUM_MONTHS 1 +#endif + +#if ( NUM_MONTHS != 1 && NUM_MONTHS !=3 ) +#error NUM_MONTHS must be 1 or 3 +#endif + +#define THURSDAY 4 /* for reformation */ +#define SATURDAY 6 /* 1 Jan 1 was a Saturday */ + +#define FIRST_MISSING_DAY 639799 /* 3 Sep 1752 */ +#define NUMBER_MISSING_DAYS 11 /* 11 day correction */ + +#define MAXDAYS 42 /* slots in a month array */ +#define SPACE -1 /* used in day array */ + +static 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}, +}; + +#define SEP1752_OFS 4 /* sep1752[4] is a Sunday */ + +/* 1 Sep 1752 is represented by sep1752[6] and j_sep1752[6] */ +int sep1752[MAXDAYS+6] = { + SPACE, SPACE, SPACE, SPACE, + SPACE, SPACE, 1, 2, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, + SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, + SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, + SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, + SPACE, SPACE +}, j_sep1752[MAXDAYS+6] = { + SPACE, SPACE, SPACE, SPACE, + SPACE, SPACE, 245, 246, 258, 259, 260, + 261, 262, 263, 264, 265, 266, 267, + 268, 269, 270, 271, 272, 273, 274, + SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, + SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, + SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, + SPACE, SPACE +}, empty[MAXDAYS] = { + SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, + SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, + SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, + SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, + SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, + SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE +}; + +#define DAY_LEN 3 /* 3 spaces per day */ +#define J_DAY_LEN 4 /* 4 spaces per day */ +#define WEEK_LEN 21 /* 7 days * 3 characters */ +#define J_WEEK_LEN 28 /* 7 days * 4 characters */ +#define HEAD_SEP 2 /* spaces between day headings */ +#define J_HEAD_SEP 2 + +/* utf-8 can have up to 6 bytes per char; and an extra byte for ending \0 */ +char day_headings[WEEK_LEN*6+1]; +/* weekstart = 1 => " M Tu W Th F S S " */ +char j_day_headings[J_WEEK_LEN*6+1]; +/* weekstart = 1 => " M Tu W Th F S S " */ +const char *full_month[12]; + +/* leap year -- account for gregorian reformation in 1752 */ +#define leap_year(yr) \ + ((yr) <= 1752 ? !((yr) % 4) : \ + (!((yr) % 4) && ((yr) % 100)) || !((yr) % 400)) + +/* number of centuries since 1700, not inclusive */ +#define centuries_since_1700(yr) \ + ((yr) > 1700 ? (yr) / 100 - 17 : 0) + +/* number of centuries since 1700 whose modulo of 400 is 0 */ +#define quad_centuries_since_1700(yr) \ + ((yr) > 1600 ? ((yr) - 1600) / 400 : 0) + +/* number of leap years between year 1 and this year, not inclusive */ +#define leap_years_since_year_1(yr) \ + ((yr) / 4 - centuries_since_1700(yr) + quad_centuries_since_1700(yr)) + +/* 0 => sunday, 1 => monday */ +int weekstart=0; +int julian; + +#define TODAY_FLAG 0x400 /* flag day for highlighting */ + +#define FMT_ST_LINES 8 +#define FMT_ST_CHARS 300 /* 90 suffices in most locales */ +struct fmt_st +{ + char s[FMT_ST_LINES][FMT_ST_CHARS]; +}; + +char * ascii_day(char *, int); +int center_str(const char* src, char* dest, size_t dest_size, size_t width); +void center(const char *, size_t, int); +void day_array(int, int, int, int *); +int day_in_week(int, int, int); +int day_in_year(int, int, int); +void yearly(int, int); +void j_yearly(int, int); +void do_monthly(int, int, int, struct fmt_st*); +void monthly(int, int, int); +void monthly3(int, int, int); +void trim_trailing_spaces(char *); +static void __attribute__ ((__noreturn__)) usage(FILE * out); +void headers_init(void); + +int +main(int argc, char **argv) { + struct tm *local_time; + time_t now; + int ch, day, month, year, yflag; + int num_months = NUM_MONTHS; + + 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'}, + {"year", no_argument, NULL, 'y'}, + {"version", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + +#if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) || defined(HAVE_LIBTERMCAP) + if ((term = getenv("TERM"))) { + int ret; + my_setupterm(term, 1, &ret); + if (ret > 0) { + Senter = my_tgetstr("so","smso"); + Sexit = my_tgetstr("se","rmso"); + Slen = strlen(Senter) + strlen(Sexit); + } + } +#endif + +/* + * 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(wfd % 100, (wfd / 100) % 100, wfd / (100 * 100)); + weekstart = (wfd + *nl_langinfo(_NL_TIME_FIRST_WEEKDAY) - 1) % 7; + } +#endif + + yflag = 0; + while ((ch = getopt_long(argc, argv, "13mjsyVh", longopts, NULL)) != -1) + switch(ch) { + case '1': + num_months = 1; /* default */ + break; + case '3': + num_months = 3; + break; + case 's': + weekstart = 0; /* default */ + break; + case 'm': + weekstart = 1; + break; + case 'j': + julian = 1; + break; + case 'y': + yflag = 1; + break; + case 'V': + printf(_("%s from %s\n"), program_invocation_short_name, + PACKAGE_STRING); + return EXIT_SUCCESS; + case 'h': + usage(stdout); + case '?': + default: + usage(stderr); + } + argc -= optind; + argv += optind; + + time(&now); + local_time = localtime(&now); + + day = month = year = 0; + switch(argc) { + case 3: + day = strtos32_or_err(*argv++, _("illegal day value")); + if (day < 1 || 31 < day) + errx(EXIT_FAILURE, _("illegal day value: use 1-%d"), 31); + /* FALLTHROUGH */ + case 2: + month = strtos32_or_err(*argv++, _("illegal month value: use 1-12")); + if (month < 1 || 12 < month) + errx(EXIT_FAILURE, _("illegal month value: use 1-12")); + /* FALLTHROUGH */ + case 1: + year = strtos32_or_err(*argv++, _("illegal year value: use 1-9999")); + if (year < 1 || 9999 < year) + errx(EXIT_FAILURE, _("illegal year value: use 1-9999")); + if (day) { + int dm = days_in_month[leap_year(year)][month]; + if (day > dm) + errx(EXIT_FAILURE, _("illegal day value: use 1-%d"), dm); + day = day_in_year(day, month, year); + } else if ((local_time->tm_year + 1900) == year) { + day = local_time->tm_yday + 1; + } + if (!month) + yflag=1; + break; + case 0: + day = local_time->tm_yday + 1; + year = local_time->tm_year + 1900; + month = local_time->tm_mon + 1; + break; + default: + usage(stderr); + } + headers_init(); + + if (!isatty(1)) + day = 0; /* don't highlight */ + + if (yflag && julian) + j_yearly(day, year); + else if (yflag) + yearly(day, year); + else if (num_months == 1) + monthly(day, month, year); + else if (num_months == 3) + monthly3(day, month, year); + + return EXIT_SUCCESS; +} + +void headers_init(void) +{ + int i, wd; + char *cur_dh = day_headings, *cur_j_dh = j_day_headings; + + strcpy(day_headings, ""); + strcpy(j_day_headings, ""); + + for (i = 0; i < 7; i++) { + ssize_t space_left; + wd = (i + weekstart) % 7; + + if (i) + strcat(cur_dh++, " "); + space_left = + sizeof(day_headings) - (cur_dh - day_headings); + if (space_left <= 2) + break; + cur_dh += + center_str(nl_langinfo(ABDAY_1 + wd), cur_dh, + space_left, 2); + + if (i) + strcat(cur_j_dh++, " "); + space_left = + sizeof(j_day_headings) - (cur_j_dh - j_day_headings); + if (space_left <= 3) + break; + cur_j_dh += + center_str(nl_langinfo(ABDAY_1 + wd), cur_j_dh, + space_left, 3); + } + + for (i = 0; i < 12; i++) + full_month[i] = nl_langinfo(MON_1 + i); +} + +void +do_monthly(int day, int month, int year, struct fmt_st *out) { + int col, row, days[MAXDAYS]; + char *p, lineout[FMT_ST_CHARS]; + int width = (julian ? J_WEEK_LEN : WEEK_LEN) - 1; + + day_array(day, month, year, days); + + /* + * %s is the month name, %d the year number. + * you can change the order and/or add something here; eg for + * Basque the translation should be: "%2$dko %1$s", and + * the Vietnamese should be "%s na(m %d", etc. + */ + snprintf(lineout, sizeof(lineout), _("%s %d"), + full_month[month - 1], year); + center_str(lineout, out->s[0], ARRAY_SIZE(out->s[0]), width); + + snprintf(out->s[1], FMT_ST_CHARS, "%s", + julian ? j_day_headings : day_headings); + for (row = 0; row < 6; row++) { + int has_hl = 0; + for (col = 0, p = lineout; col < 7; col++) { + int xd = days[row * 7 + col]; + if (xd != SPACE && (xd & TODAY_FLAG)) + has_hl = 1; + p = ascii_day(p, xd); + } + *p = '\0'; + trim_trailing_spaces(lineout); + snprintf(out->s[row+2], FMT_ST_CHARS, "%s", lineout); + if (has_hl) + Hrow = out->s[row+2]; + } +} + +void +monthly(int day, int month, int year) { + int i; + struct fmt_st out; + + do_monthly(day, month, year, &out); + for (i = 0; i < FMT_ST_LINES; i++) { + my_putstring(out.s[i]); + putchar('\n'); + } +} + +void +monthly3(int day, int month, int year) { + char lineout[FMT_ST_CHARS]; + int i; + int width; + struct fmt_st out_prev; + struct fmt_st out_curm; + struct fmt_st out_next; + int prev_month, prev_year; + int next_month, next_year; + + if (month == 1) { + prev_month = 12; + prev_year = year - 1; + } else { + prev_month = month - 1; + prev_year = year; + } + if (month == 12) { + next_month = 1; + next_year = year + 1; + } else { + next_month = month + 1; + next_year = year; + } + + do_monthly(day, prev_month, prev_year, &out_prev); + do_monthly(day, month, year, &out_curm); + do_monthly(day, next_month, next_year, &out_next); + + width = (julian ? J_WEEK_LEN : WEEK_LEN) -1; + for (i = 0; i < 2; i++) + printf("%s %s %s\n", out_prev.s[i], out_curm.s[i], out_next.s[i]); + for (i = 2; i < FMT_ST_LINES; i++) { + int w1, w2, w3; + w1 = w2 = w3 = width; + +#if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) || defined(HAVE_LIBTERMCAP) + /* adjust width to allow for non printable characters */ + w1 += (out_prev.s[i] == Hrow ? Slen : 0); + w2 += (out_curm.s[i] == Hrow ? Slen : 0); + w3 += (out_next.s[i] == Hrow ? Slen : 0); +#endif + snprintf(lineout, sizeof(lineout), "%-*s %-*s %-*s\n", + w1, out_prev.s[i], + w2, out_curm.s[i], + w3, out_next.s[i]); + + my_putstring(lineout); + } +} + +void +j_yearly(int day, int year) { + int col, *dp, i, month, row, which_cal; + int days[12][MAXDAYS]; + char *p, lineout[80]; + + snprintf(lineout, sizeof(lineout), "%d", year); + center(lineout, J_WEEK_LEN*2 + J_HEAD_SEP - 1, 0); + printf("\n\n"); + + for (i = 0; i < 12; i++) + day_array(day, i + 1, year, days[i]); + memset(lineout, ' ', sizeof(lineout) - 1); + lineout[sizeof(lineout) - 1] = '\0'; + for (month = 0; month < 12; month += 2) { + center(full_month[month], J_WEEK_LEN-1, J_HEAD_SEP+1); + center(full_month[month + 1], J_WEEK_LEN-1, 0); + printf("\n%s%*s %s\n", j_day_headings, J_HEAD_SEP, "", + j_day_headings); + for (row = 0; row < 6; row++) { + p = lineout; + for (which_cal = 0; which_cal < 2; which_cal++) { + dp = &days[month + which_cal][row * 7]; + for (col = 0; col < 7; col++) + p = ascii_day(p, *dp++); + p += sprintf(p, " "); + } + *p = '\0'; + trim_trailing_spaces(lineout); + my_putstring(lineout); + putchar('\n'); + } + } + printf("\n"); +} + +void +yearly(int day, int year) { + int col, *dp, i, month, row, which_cal; + int days[12][MAXDAYS]; + char *p, lineout[100]; + + snprintf(lineout, sizeof(lineout), "%d", year); + center(lineout, WEEK_LEN*3 + HEAD_SEP*2 - 1, 0); + printf("\n\n"); + + for (i = 0; i < 12; i++) + day_array(day, i + 1, year, days[i]); + memset(lineout, ' ', sizeof(lineout) - 1); + lineout[sizeof(lineout) - 1] = '\0'; + for (month = 0; month < 12; month += 3) { + center(full_month[month], WEEK_LEN-1, HEAD_SEP+1); + center(full_month[month + 1], WEEK_LEN-1, HEAD_SEP+1); + center(full_month[month + 2], WEEK_LEN-1, 0); + printf("\n%s%*s %s%*s %s\n", day_headings, HEAD_SEP, + "", day_headings, HEAD_SEP, "", day_headings); + for (row = 0; row < 6; row++) { + p = lineout; + for (which_cal = 0; which_cal < 3; which_cal++) { + dp = &days[month + which_cal][row * 7]; + for (col = 0; col < 7; col++) + p = ascii_day(p, *dp++); + p += sprintf(p, " "); + } + *p = '\0'; + trim_trailing_spaces(lineout); + my_putstring(lineout); + putchar('\n'); + } + } + putchar('\n'); +} + +/* + * day_array -- + * Fill in an array of 42 integers with a calendar. Assume for a moment + * that you took the (maximum) 6 rows in a calendar and stretched them + * out end to end. You would have 42 numbers or spaces. This routine + * builds that array for any month from Jan. 1 through Dec. 9999. + */ +void +day_array(int day, int month, int year, int *days) { + int julday, daynum, dw, dm; + int *d_sep1752; + + if (month == 9 && year == 1752) { + int sep1752_ofs = (weekstart + SEP1752_OFS) % 7; + d_sep1752 = julian ? j_sep1752 : sep1752; + memcpy(days, d_sep1752 + sep1752_ofs, MAXDAYS * sizeof(int)); + for (dm=0; dm<MAXDAYS; dm++) + if (j_sep1752[dm + sep1752_ofs] == day) + days[dm] |= TODAY_FLAG; + return; + } + memcpy(days, empty, MAXDAYS * sizeof(int)); + dm = days_in_month[leap_year(year)][month]; + dw = (day_in_week(1, month, year) - weekstart + 7) % 7; + julday = day_in_year(1, month, year); + daynum = julian ? julday : 1; + while (dm--) { + days[dw] = daynum++; + if (julday++ == day) + days[dw] |= TODAY_FLAG; + dw++; + } +} + +/* + * day_in_year -- + * return the 1 based day number within the year + */ +int +day_in_year(int day, int month, int year) { + int i, leap; + + leap = leap_year(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. Returns Thursday for all + * missing days. + */ +int +day_in_week(int day, int month, int year) { + long temp; + + temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1) + + day_in_year(day, month, year); + if (temp < FIRST_MISSING_DAY) + return ((temp - 1 + SATURDAY) % 7); + if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS)) + return (((temp - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7); + return (THURSDAY); +} + +char * +ascii_day(char *p, int day) { + int display, val; + int highlight = 0; + static char *aday[] = { + "", + " 1", " 2", " 3", " 4", " 5", " 6", " 7", + " 8", " 9", "10", "11", "12", "13", "14", + "15", "16", "17", "18", "19", "20", "21", + "22", "23", "24", "25", "26", "27", "28", + "29", "30", "31", + }; + + if (day == SPACE) { + int len = julian ? J_DAY_LEN : DAY_LEN; + memset(p, ' ', len); + return p+len; + } + if (day & TODAY_FLAG) { + day &= ~TODAY_FLAG; + p += sprintf(p, "%s", Senter); + highlight = 1; + } + if (julian) { + if ((val = day / 100)) { + day %= 100; + *p++ = val + '0'; + display = 1; + } else { + *p++ = ' '; + display = 0; + } + val = day / 10; + if (val || display) + *p++ = val + '0'; + else + *p++ = ' '; + *p++ = day % 10 + '0'; + } else { + *p++ = aday[day][0]; + *p++ = aday[day][1]; + } + if (highlight) + p += sprintf(p, "%s", Sexit); + *p++ = ' '; + return p; +} + +void +trim_trailing_spaces(char *s) +{ + char *p; + + for (p = s; *p; ++p) + continue; + while (p > s && isspace(*--p)) + continue; + if (p > s) + ++p; + *p = '\0'; +} + +/* + * 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. + */ +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); +} + +void +center(const char *str, size_t len, int separate) +{ + char lineout[FMT_ST_CHARS]; + center_str(str, lineout, ARRAY_SIZE(lineout), len); + fputs(lineout, stdout); + if (separate) + printf("%*s", separate, ""); +} + +static void __attribute__ ((__noreturn__)) usage(FILE * out) +{ + fputs(_("\nUsage:\n"), out); + fprintf(out, + _(" %s [options] [[[day] month] year]\n"), + program_invocation_short_name); + + fputs(_("\nOptions:\n"), out); + fputs(_(" -1, --one show only current month (default)\n" + " -3, --three show previous, current and next month\n" + " -s, --sunday Sunday as first day of week\n" + " -m, --monday Monday as first day of week\n" + " -j, --julian output Julian dates\n" + " -y, --year show whole current year\n" + " -V, --version display version information and exit\n" + " -h, --help display this help text and exit\n\n"), out); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} diff --git a/misc-utils/chkdupexe b/misc-utils/chkdupexe new file mode 100644 index 0000000..b82b102 --- /dev/null +++ b/misc-utils/chkdupexe @@ -0,0 +1,120 @@ +#! -w +# +# chkdupexe version 2.1.1 +# +# Simple script to look for and list duplicate executables and dangling +# symlinks in the system executable directories. +# +# Copyright 1993 Nicolai Langfeldt. janl@math.uio.no +# Distribute under gnu copyleft (included in perl package) +# +# Modified 1995-07-04 Michael Shields <shields@tembel.org> +# Don't depend on GNU ls. +# Cleanups. +# Merge together $ENV{'PATH'} and $execdirs. +# Don't break if there are duplicates in $PATH. +# +# Modified 1996-02-16 Nicolai Langfeldt (janl@math.uio.no). +# I was thinking admins would edit the $execdirs list to suit their +# machine(s) when I wrote this. This is ofcourse not the case, thus +# Michaels fixes. And my fixes to his :-) +# - Working duplicate dirs detection. +# - Added more checks +# - Took out $PATH from the list of checked directories and added a +# check for $execdirs and $PATH consistency instead +# - Made it possible to run with perl -w + +$execdirs='/bin /sbin /usr/bin /usr/sbin /usr/local/bin /usr/local/sbin '. + '/usr/X11/bin /usr/bin/X11 /usr/local/X11/bin '. + '/usr/TeX/bin /usr/tex/bin /usr/games '. + '/usr/local/games'; + +# Turn off buffering for the output channel. +$|=1; + +# Values from /usr/include/linux/errno.h. Existence of linux/errno.ph is not +# something to count on... :-( +$ENOENT=2; + +%didthis=(); + +foreach $dir (split(/\s+/, "$execdirs"), "\0", split(/:/, $ENV{PATH})) { + + if ($dir eq "\0") { $checkingpath = 1; next; } + + # It's like this: One directory corresponds to one $device,$inode tuple + # If a symlink points to a directory we already checked that directory + # will have the same $device,$inode tuple. + + # Does this directory have any real exstence outside the ravings of + # symlinks pointing hither and dither? + ($device,$inode)=stat($dir); + if (!defined($device)) { + # Nonexistant directory, or dangling symlink? + ($dum)=lstat($dir); + next if $! == $ENOENT; + if (!$dum) { + print "Dangling symlink: $dir\n"; + next; + } + warn "Nonexistent directory: $dir\n" if ($checkingpath); + next; + } + + if (!-d _) { + print "Not a directory: $dir\n"; + next; + } + + next if defined($didthis{$device,$inode}); + + $didthis{$device,$inode}=1; + + chdir($dir) || die "Could not chdir $dir: $!\n"; +# This would give us the true directory name, do we want that? +# chop($dir=`pwd`); + opendir(DIR,".") || + die "NUTS! Personaly I think your perl or filesystem is broken.\n". + "I've done all sorts of checks on $dir, and now I can't open it!\n"; + foreach $_ (readdir(DIR)) { + lstat($_); + if (-l _) { + ($dum)=stat($_); + print "Dangling symlink: $dir/$_\n" unless defined($dum); + next; + } + next unless -f _ && -x _; # Only handle regular executable files + if (defined($count{$_})) { + $progs{$_}.=" $dir/$_"; + $count{$_}++; + } else { + $progs{$_}="$dir/$_"; + $count{$_}=1; + } + } + closedir(DIR); +} + +open(LS,"| xargs -r ls -ldU"); +while (($prog,$paths)=each %progs) { + print LS "$paths\n" if ($count{$prog}>1); +} +close(LS); + +exit 0; + +@unchecked=(); +# Check if the users PATH contains something I've not checked. The site admin +# might want to know about inconsistencies in user PATHs and chkdupexec +# configuration +foreach $dir (split(/:/,$ENV{'PATH'})) { + ($device,$inode)=stat($dir); + next unless defined($device); + next if defined($didthis{$device,$inode}); + push(@unchecked,$dir); + $didthis{$device,$inode}=1; +} + +print "Warning: Your path contains these directories which chkdupexe has not checked:\n",join(',',@unchecked), + ".\nPlease review the execdirs list in chkdupexe.\n" + if ($#unchecked>=$[); diff --git a/misc-utils/chkdupexe.1 b/misc-utils/chkdupexe.1 new file mode 100644 index 0000000..dd3664a --- /dev/null +++ b/misc-utils/chkdupexe.1 @@ -0,0 +1,38 @@ +.\" chkdupexe.1 -- +.\" Created: Sat Mar 11 18:19:44 1995 by faith@cs.unc.edu +.\" Revised: Sat Mar 11 19:07:05 1995 by faith@cs.unc.edu +.\" Revised: Wed Jul 5 01:56:26 1995 by shields@tembel.org +.\" Copyright 1995 Rickard E. Faith (faith@cs.unc.edu) +.\" +.\" Permission is granted to make and distribute verbatim copies of this +.\" manual provided the copyright notice and this permission notice are +.\" preserved on all copies. +.\" +.\" Permission is granted to copy and distribute modified versions of this +.\" manual under the conditions for verbatim copying, provided that the +.\" entire resulting derived work is distributed under the terms of a +.\" permission notice identical to this one +.\" +.\" Since the Linux kernel and libraries are constantly changing, this +.\" manual page may be incorrect or out-of-date. The author(s) assume no +.\" responsibility for errors or omissions, or for damages resulting from +.\" the use of the information contained herein. The author(s) may not +.\" have taken the same level of care in the production of this manual, +.\" which is licensed free of charge, as they might when working +.\" professionally. +.\" +.\" Formatted or processed versions of this manual, if unaccompanied by +.\" the source, must acknowledge the copyright and authors of this work. +.\" +.TH CHKDUPEXE 1 "March 1995" "util-linux" "User Commands" +.SH NAME +chkdupexe \- find duplicate executables +.SH SYNOPSIS +.B chkdupexe +.SH DESCRIPTION +.B chkdupexe +will scan the union of $PATH and a hardcoded list of common locations +for binaries. It will report dangling symlinks and duplicately-named +binaries. +.SH AUTHOR +Nicolai Langfeldt, Michael Shields. diff --git a/misc-utils/chkdupexe.pl b/misc-utils/chkdupexe.pl new file mode 100755 index 0000000..c2c2384 --- /dev/null +++ b/misc-utils/chkdupexe.pl @@ -0,0 +1,120 @@ +#!@PERL@ -w +# +# chkdupexe version 2.1.1 +# +# Simple script to look for and list duplicate executables and dangling +# symlinks in the system executable directories. +# +# Copyright 1993 Nicolai Langfeldt. janl@math.uio.no +# Distribute under gnu copyleft (included in perl package) +# +# Modified 1995-07-04 Michael Shields <shields@tembel.org> +# Don't depend on GNU ls. +# Cleanups. +# Merge together $ENV{'PATH'} and $execdirs. +# Don't break if there are duplicates in $PATH. +# +# Modified 1996-02-16 Nicolai Langfeldt (janl@math.uio.no). +# I was thinking admins would edit the $execdirs list to suit their +# machine(s) when I wrote this. This is ofcourse not the case, thus +# Michaels fixes. And my fixes to his :-) +# - Working duplicate dirs detection. +# - Added more checks +# - Took out $PATH from the list of checked directories and added a +# check for $execdirs and $PATH consistency instead +# - Made it possible to run with perl -w + +$execdirs='/bin /sbin /usr/bin /usr/sbin /usr/local/bin /usr/local/sbin '. + '/usr/X11/bin /usr/bin/X11 /usr/local/X11/bin '. + '/usr/TeX/bin /usr/tex/bin /usr/games '. + '/usr/local/games'; + +# Turn off buffering for the output channel. +$|=1; + +# Values from /usr/include/linux/errno.h. Existence of linux/errno.ph is not +# something to count on... :-( +$ENOENT=2; + +%didthis=(); + +foreach $dir (split(/\s+/, "$execdirs"), "\0", split(/:/, $ENV{PATH})) { + + if ($dir eq "\0") { $checkingpath = 1; next; } + + # It's like this: One directory corresponds to one $device,$inode tuple + # If a symlink points to a directory we already checked that directory + # will have the same $device,$inode tuple. + + # Does this directory have any real exstence outside the ravings of + # symlinks pointing hither and dither? + ($device,$inode)=stat($dir); + if (!defined($device)) { + # Nonexistant directory, or dangling symlink? + ($dum)=lstat($dir); + next if $! == $ENOENT; + if (!$dum) { + print "Dangling symlink: $dir\n"; + next; + } + warn "Nonexistent directory: $dir\n" if ($checkingpath); + next; + } + + if (!-d _) { + print "Not a directory: $dir\n"; + next; + } + + next if defined($didthis{$device,$inode}); + + $didthis{$device,$inode}=1; + + chdir($dir) || die "Could not chdir $dir: $!\n"; +# This would give us the true directory name, do we want that? +# chop($dir=`pwd`); + opendir(DIR,".") || + die "NUTS! Personaly I think your perl or filesystem is broken.\n". + "I've done all sorts of checks on $dir, and now I can't open it!\n"; + foreach $_ (readdir(DIR)) { + lstat($_); + if (-l _) { + ($dum)=stat($_); + print "Dangling symlink: $dir/$_\n" unless defined($dum); + next; + } + next unless -f _ && -x _; # Only handle regular executable files + if (defined($count{$_})) { + $progs{$_}.=" $dir/$_"; + $count{$_}++; + } else { + $progs{$_}="$dir/$_"; + $count{$_}=1; + } + } + closedir(DIR); +} + +open(LS,"| xargs -r ls -ldU"); +while (($prog,$paths)=each %progs) { + print LS "$paths\n" if ($count{$prog}>1); +} +close(LS); + +exit 0; + +@unchecked=(); +# Check if the users PATH contains something I've not checked. The site admin +# might want to know about inconsistencies in user PATHs and chkdupexec +# configuration +foreach $dir (split(/:/,$ENV{'PATH'})) { + ($device,$inode)=stat($dir); + next unless defined($device); + next if defined($didthis{$device,$inode}); + push(@unchecked,$dir); + $didthis{$device,$inode}=1; +} + +print "Warning: Your path contains these directories which chkdupexe has not checked:\n",join(',',@unchecked), + ".\nPlease review the execdirs list in chkdupexe.\n" + if ($#unchecked>=$[); diff --git a/misc-utils/ddate.1 b/misc-utils/ddate.1 new file mode 100644 index 0000000..0b01e09 --- /dev/null +++ b/misc-utils/ddate.1 @@ -0,0 +1,114 @@ +.\" All Rites Reversed. This file is in the PUBLIC DOMAIN. +.\" Kallisti. +.TH DDATE 1 "Bureaucracy 3161" "util-linux" "Emperor Norton User Command" +.SH NAME +ddate \- convert Gregorian dates to Discordian dates +.SH SYNOPSIS +.B ddate +.RI [ \fB+\fPformat] +.RI [ date ] +.SH DESCRIPTION +.B ddate +prints the date in Discordian date format. +.PP +If called with no arguments, +.B ddate +will get the current system date, convert this to the Discordian +date format and print this on the standard output. Alternatively, a +Gregorian date may be specified on the command line, in the form of a numerical +day, month and year. +.PP +If a format string is specified, the Discordian date will be printed in +a format specified by the string. This mechanism works similarly to the +format string mechanism of +.B date(1), +only almost completely differently. The fields are: +.IP %A +Full name of the day of the week (i.e., Sweetmorn) +.IP %a +Abbreviated name of the day of the week (i.e., SM) +.IP %B +Full name of the season (i.e., Chaos) +.IP %b +Abbreviated name of the season (i.e., Chs) +.IP %d +Ordinal number of day in season (i.e., 23) +.IP %e +Cardinal number of day in season (i.e., 23rd) +.IP %H +Name of current Holyday, if any +.IP %N +Magic code to prevent rest of format from being printed unless today is +a Holyday. +.IP %n +Newline +.IP %t +Tab +.IP %X +Number of days remaining until X-Day. (Not valid if the SubGenius options +are not compiled in.) +.IP %{ +.IP %} +Used to enclose the part of the string which is to be replaced with the +words "St. Tib's Day" if the current day is St. Tib's Day. +.IP %\. +Try it and see. +.bp +.SH EXAMPLES +.nf +% ddate +.br +Sweetmorn, Bureaucracy 42, 3161 YOLD +.PP +% ddate +'Today is %{%A, the %e of %B%}, %Y. %N%nCelebrate %H' +.br +Today is Sweetmorn, the 42nd of Bureaucracy, 3161. +.PP +% ddate +"It's %{%A, the %e of %B%}, %Y. %N%nCelebrate %H" 26 9 1995 +.br +It's Prickle-Prickle, the 50th of Bureaucracy, 3161. +.br +Celebrate Bureflux +.PP +% ddate +"Today's %{%A, the %e of %B%}, %Y. %N%nCelebrate %H" 29 2 1996 +.br +Today's St. Tib's Day, 3162. +.br + +.SH BUGS + +.B ddate(1) +will produce undefined behavior if asked to produce the date for St. Tib's +day and its format string does not contain the St. Tib's Day delimiters +%{ and %}. + +.SH NOTE + +After `X-Day' passed without incident, the Church of the SubGenius +declared that it had got the year upside down - X-Day is actually in 8661 AD +rather than 1998 AD. Thus, the True X-Day is Cfn 40, 9827. + +.SH AUTHOR +.nh +Original program by Druel the Chaotic aka Jeremy Johnson (mpython@gnu.ai.mit.edu) +.br +Major rewrite by Lee H:. O:. Smith, KYTP, aka Andrew Bulhak (acb@dev.null.org) +.br +Five tons of flax. + +.SH DISTRIBUTION POLICY + +Public domain. All rites reversed. + +.SH SEE ALSO + +date(1), +.br +http://www.subgenius.com/ +.br +Malaclypse the Younger, +.I "Principia Discordia, Or How I Found Goddess And What I Did To Her When I Found Her" + +.SH AVAILABILITY +The ddate command is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. diff --git a/misc-utils/ddate.c b/misc-utils/ddate.c new file mode 100644 index 0000000..c0b4ce4 --- /dev/null +++ b/misc-utils/ddate.c @@ -0,0 +1,374 @@ +/* $ DVCS ID: $jer|,523/lhos,KYTP!41023161\b"?" <<= DO NOT DELETE! */ + +/* ddate.c .. converts boring normal dates to fun Discordian Date -><- + written the 65th day of The Aftermath in the Year of Our Lady of + Discord 3157 by Druel the Chaotic aka Jeremy Johnson aka + mpython@gnu.ai.mit.edu + 28 Sever St Apt #3 + Worcester MA 01609 + + and I'm not responsible if this program messes anything up (except your + mind, I'm responsible for that) + + (k) YOLD 3161 and all time before and after. + Reprint, reuse, and recycle what you wish. + This program is in the public domain. Distribute freely. Or not. + + Majorly hacked, extended and bogotified/debogotified on + Sweetmorn, Bureaucracy 42, 3161 YOLD, by Lee H:. O:. Smith, KYTP, + aka Andrew Bulhak, aka acb@dev.null.org + + and I'm not responsible if this program messes anything up (except your + mind, I'm responsible for that) (and that goes for me as well --lhos) + + Version history: + Bureflux 3161: First release of enhanced ddate with format strings + 59 Bcy, 3161: PRAISE_BOB and KILL_BOB options split, other minor + changes. + + 1999-02-22 Arkadiusz Miskiewicz <misiek@pld.ORG.PL> + - added Native Language Support + + 2000-03-17 Burt Holzman <bnh@iname.com> + - added range checks for dates +*/ + +/* configuration options VVVVV READ THIS!!! */ + +/* If you wish ddate(1) to print the date in the same format as Druel's + * original ddate when called in immediate mode, define OLD_IMMEDIATE_FMT + */ + +#define OLD_IMMEDIATE_FMT + +/* If you wish to use the US format for aneristic dates (m-d-y), as opposed to + * the Commonwealth format, define US_FORMAT. + */ + +/* #define US_FORMAT */ + +/* If you are ideologically, theologically or otherwise opposed to the + * Church of the SubGenius and do not wish your copy of ddate(1) to contain + * code for counting down to X-Day, undefine KILL_BOB */ + +#define KILL_BOB 13013 + +/* If you wish ddate(1) to contain SubGenius slogans, define PRAISE_BOB */ + +/*#define PRAISE_BOB 13013*/ + +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <stdio.h> + +#include "nls.h" +#include "closestream.h" +#include "c.h" + +#ifndef __GNUC__ +#define inline /* foo */ +#endif + +#ifdef KILL_BOB +int xday_countdown(int yday, int year); +#endif + + +/* string constants */ + +char *day_long[5] = { + "Sweetmorn", "Boomtime", "Pungenday", "Prickle-Prickle", "Setting Orange" +}; + +char *day_short[5] = {"SM","BT","PD","PP","SO"}; + +char *season_long[5] = { + "Chaos", "Discord", "Confusion", "Bureaucracy", "The Aftermath" +}; + +char *season_short[5] = {"Chs", "Dsc", "Cfn", "Bcy", "Afm"}; + +char *holyday[5][2] = { + { "Mungday", "Chaoflux" }, + { "Mojoday", "Discoflux" }, + { "Syaday", "Confuflux" }, + { "Zaraday", "Bureflux" }, + { "Maladay", "Afflux" } +}; + +struct disc_time { + int season; /* 0-4 */ + int day; /* 0-72 */ + int yday; /* 0-365 */ + int year; /* 3066- */ +}; + +char *excl[] = { + "Hail Eris!", "All Hail Discordia!", "Kallisti!", "Fnord.", "Or not.", + "Wibble.", "Pzat!", "P'tang!", "Frink!", +#ifdef PRAISE_BOB + "Slack!", "Praise \"Bob\"!", "Or kill me.", +#endif /* PRAISE_BOB */ + /* randomness, from the Net and other places. Feel free to add (after + checking with the relevant authorities, of course). */ + "Grudnuk demand sustenance!", "Keep the Lasagna flying!", + "You are what you see.", + "Or is it?", "This statement is false.", +#if defined(linux) || defined (__linux__) || defined (__linux) + "Hail Eris, Hack Linux!", +#endif + "" +}; + +char default_fmt[] = "%{%A, %B %d%}, %Y YOLD"; +char *default_immediate_fmt= +#ifdef OLD_IMMEDIATE_FMT +"Today is %{%A, the %e day of %B%} in the YOLD %Y%N%nCelebrate %H" +#else +default_fmt +#endif +; + +#define DY(y) (y+1166) + +static inline char *ending(int i) { + return i/10==1?"th":(i%10==1?"st":(i%10==2?"nd":(i%10==3?"rd":"th"))); +} + +static inline int leapp(int i) { + return (!(DY(i)%4))&&((DY(i)%100)||(!(DY(i)%400))); +} + +/* select a random string */ +static inline char *sel(char **strings, int num) { + return(strings[random()%num]); +} + +void print(struct disc_time,char **); /* old */ +void format(char *buf, const char* fmt, struct disc_time dt); +/* read a fortune file */ +int load_fortunes(char *fn, char *delim, char** result); + +struct disc_time convert(int,int); +struct disc_time makeday(int,int,int); + +int +main (int argc, char *argv[]) { + long t; + struct tm *eris; + int bob,raw; + struct disc_time hastur; + char schwa[23*17], *fnord=0; + int pi; + char *progname, *p; + + progname = argv[0]; + if ((p = strrchr(progname, '/')) != NULL) + progname = p+1; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + srandom(time(NULL)); + /* do args here */ + for(pi=1; pi<argc; pi++) { + switch(argv[pi][0]) { + case '+': fnord=argv[pi]+1; break; + case '-': + switch(argv[pi][1]) { + case 'V': + printf(_("%s (%s)\n"), progname, PACKAGE_STRING); + default: goto usage; + } + default: goto thud; + } + } + + thud: + if (argc-pi==3){ + int moe=atoi(argv[pi]), larry=atoi(argv[pi+1]), curly=atoi(argv[pi+2]); + hastur=makeday( +#ifdef US_FORMAT + moe,larry, +#else + larry,moe, +#endif + curly); + if (hastur.season == -1) { + printf("Invalid date -- out of range\n"); + return -1; + } + fnord=fnord?fnord:default_fmt; + } else if (argc!=pi) { + usage: + fprintf(stderr,_("usage: %s [+format] [day month year]\n"), argv[0]); + exit(1); + } else { + t= time(NULL); + eris=localtime(&t); + bob=eris->tm_yday; /* days since Jan 1. */ + raw=eris->tm_year; /* years since 1980 */ + hastur=convert(bob,raw); + fnord=fnord?fnord:default_immediate_fmt; + } + format(schwa, fnord, hastur); + printf("%s\n", schwa); + + return 0; +} + +void format(char *buf, const char* fmt, struct disc_time dt) +{ + int tib_start=-1, tib_end=0; + int i, fmtlen=strlen(fmt); + char *bufptr=buf; + +/* fprintf(stderr, "format(%p, \"%s\", dt)\n", buf, fmt);*/ + + /* first, find extents of St. Tib's Day area, if defined */ + for(i=0; i<fmtlen; i++) { + if(fmt[i]=='%') { + switch(fmt[i+1]) { + case 'A': + case 'a': + case 'd': + case 'e': + if(tib_start>0) tib_end=i+1; + else tib_start=i; + break; + case '{': tib_start=i; break; + case '}': tib_end=i+1; break; + } + } + } + + /* now do the formatting */ + buf[0]=0; + + for(i=0; i<fmtlen; i++) { + if((i==tib_start) && (dt.day==-1)) { + /* handle St. Tib's Day */ + strcpy(bufptr, _("St. Tib's Day")); + bufptr += strlen(bufptr); + i=tib_end; + } else { + if(fmt[i]=='%') { + char *wibble=0, snarf[23]; + switch(fmt[++i]) { + case 'A': wibble=day_long[dt.yday%5]; break; + case 'a': wibble=day_short[dt.yday%5]; break; + case 'B': wibble=season_long[dt.season]; break; + case 'b': wibble=season_short[dt.season]; break; + case 'd': sprintf(snarf, "%d", dt.day+1); wibble=snarf; break; + case 'e': sprintf(snarf, "%d%s", dt.day+1, ending(dt.day+1)); + wibble=snarf; break; + case 'H': if(dt.day==4||dt.day==49) + wibble=holyday[dt.season][dt.day==49]; break; + case 'N': if(dt.day!=4&&dt.day!=49) goto eschaton; break; + case 'n': *(bufptr++)='\n'; break; + case 't': *(bufptr++)='\t'; break; + + case 'Y': sprintf(snarf, "%d", dt.year); wibble=snarf; break; + case '.': wibble=sel(excl, ARRAY_SIZE(excl)); + break; +#ifdef KILL_BOB + case 'X': sprintf(snarf, "%d", + xday_countdown(dt.yday, dt.year)); + wibble = snarf; break; +#endif /* KILL_BOB */ + } + if(wibble) { +/* fprintf(stderr, "wibble = (%s)\n", wibble);*/ + strcpy(bufptr, wibble); bufptr+=strlen(wibble); + } + } else { + *(bufptr++) = fmt[i]; + } + } + } + eschaton: + *(bufptr)=0; +} + +struct disc_time makeday(int imonth,int iday,int iyear) /*i for input */ +{ + struct disc_time funkychickens; + + int cal[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 }; + int dayspast=0; + + memset(&funkychickens,0,sizeof(funkychickens)); + /* basic range checks */ + if (imonth < 1 || imonth > 12) { + funkychickens.season = -1; + return funkychickens; + } + if (iday < 1 || iday > cal[imonth-1]) { + if (!(imonth == 2 && iday == 29 && iyear%4 == 0 && + (iyear%100 != 0 || iyear%400 == 0))) { + funkychickens.season = -1; + return funkychickens; + } + } + + imonth--; + funkychickens.year= iyear+1166; + while(imonth>0) { dayspast+=cal[--imonth]; } + funkychickens.day=dayspast+iday-1; + funkychickens.season=0; + if((funkychickens.year%4)==2) { + if (funkychickens.day==59 && iday==29) funkychickens.day=-1; + } + funkychickens.yday=funkychickens.day; +/* note: EQUAL SIGN...hopefully that fixes it */ + while(funkychickens.day>=73) { + funkychickens.season++; + funkychickens.day-=73; + } + return funkychickens; +} + +struct disc_time convert(int nday, int nyear) +{ struct disc_time funkychickens; + + funkychickens.year = nyear+3066; + funkychickens.day=nday; + funkychickens.season=0; + if ((funkychickens.year%4)==2) + {if (funkychickens.day==59) + funkychickens.day=-1; + else if (funkychickens.day >59) + funkychickens.day-=1; + } + funkychickens.yday=funkychickens.day; + while (funkychickens.day>=73) + { funkychickens.season++; + funkychickens.day-=73; + } + return funkychickens; + + } + +#ifdef KILL_BOB + +/* Code for counting down to X-Day, X-Day being Cfn 40, 3164 + * + * After `X-Day' passed without incident, the CoSG declared that it had + * got the year upside down --- X-Day is actually in 8661 AD rather than + * 1998 AD. + * + * Thus, the True X-Day is Cfn 40, 9827. + * + */ + +int xday_countdown(int yday, int year) { + int r=(185-yday)+(((yday<59)&&(leapp(year)))?1:0); + while(year<9827) r+=(leapp(++year)?366:365); + while(year>9827) r-=(leapp(year--)?366:365); + return r; +} + +#endif diff --git a/misc-utils/findfs.8 b/misc-utils/findfs.8 new file mode 100644 index 0000000..b754903 --- /dev/null +++ b/misc-utils/findfs.8 @@ -0,0 +1,34 @@ +.\" -*- nroff -*- +.\" 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 "February 2009" "util-linux" "System Administration" +.SH NAME +findfs \- find a filesystem by label or UUID +.SH SYNOPSIS +.B findfs +.BI LABEL= label +.sp +.B findfs +.BI UUID= uuid +.SH DESCRIPTION +.B findfs +will search the disks in the system looking for a filesystem which has +a label matching +.I label +or a UUID equal to +.IR uuid . +If the filesystem is found, the device name for the filesystem will +be printed on stdout. +.PP +.SH AUTHOR +.B findfs +was originally written by Theodore Ts'o (tytso@mit.edu) and re-written for +the util-linux package by Karel Zak (kzak@redhat.com). +.SH AVAILABILITY +The findfs command is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. +.SH SEE ALSO +.BR blkid (8) +.BR fsck (8) + diff --git a/misc-utils/findfs.c b/misc-utils/findfs.c new file mode 100644 index 0000000..bc4a843 --- /dev/null +++ b/misc-utils/findfs.c @@ -0,0 +1,69 @@ +/* + * 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 <blkid.h> + +#include "nls.h" +#include "closestream.h" +#include "c.h" + +static void __attribute__((__noreturn__)) usage(int rc) +{ + FILE *out = rc ? stderr : stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _(" %1$s [options] LABEL=<label>\n" + " %1$s [options] UUID=<uuid>\n"), + program_invocation_short_name); + fputs(USAGE_OPTIONS, out); + fputs(USAGE_HELP, out); + fputs(USAGE_VERSION, out); + fprintf(out, USAGE_MAN_TAIL("findfs(8)")); + exit(rc); +} + +int main(int argc, char **argv) +{ + char *dev, *tk, *vl; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + if (argc != 2) + /* we return '2' for backward compatibility + * with version from e2fsprogs */ + usage(2); + + if (!strncmp(argv[1], "LABEL=", 6)) { + tk = "LABEL"; + vl = argv[1] + 6; + } else if (!strncmp(argv[1], "UUID=", 5)) { + tk = "UUID"; + vl = argv[1] + 5; + } else if (strcmp(argv[1], "-V") == 0 || + strcmp(argv[1], "--version") == 0) { + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + } else if (strcmp(argv[1], "-h") == 0 || + strcmp(argv[1], "--help") == 0) { + usage(EXIT_SUCCESS); + } else + usage(2); + + dev = blkid_evaluate_tag(tk, vl, NULL); + if (!dev) + errx(EXIT_FAILURE, _("unable to resolve '%s'"), argv[1]); + + puts(dev); + exit(EXIT_SUCCESS); +} + diff --git a/misc-utils/findmnt.8 b/misc-utils/findmnt.8 new file mode 100644 index 0000000..2072c52 --- /dev/null +++ b/misc-utils/findmnt.8 @@ -0,0 +1,217 @@ +.\" -*- nroff -*- +.TH FINDMNT 8 "April 2010" "util-linux" "System Administration" +.SH NAME +findmnt \- find a filesystem +.SH SYNOPSIS +.B findmnt +.RB [ options ] +.sp +.B findmnt +.RB [ options ] +.IR device | mountpoint +.sp +.B findmnt +.RB [ options ] +.RB [ \--source ] +.IR device +.RB [ \--target ] +.IR mountpoint +.SH DESCRIPTION +.B findmnt +will list all mounted filesytems or search for a filesystem. The +.B findmnt +command is able to search in +.IR /etc/fstab , +.IR /etc/mtab +or +.IR /proc/self/mountinfo . +If +.IR device +or +.IR mountpoint +is not given, all filesystems are shown. +.PP +The device may be specified by device name, maj:min, filesystem LABEL or UUID +or partition PARTUUID or PARTLABEL. Note that device name may be interpreted +as mountpoint (and vice versa) if --target or --source options are not +specified. +.PP +The command prints all mounted filesystems in the tree-like format by default. +.SH OPTIONS +.IP "\fB\-h, \-\-help\fP" +Print help and exit. +.IP "\fB\-s, \-\-fstab\fP" +Search in +.IR /etc/fstab . +The output is in the list format (see --list). +.IP "\fB\-m, \-\-mtab\fP" +Search in +.IR /etc/mtab . +The output is in the list format (see \fB\-\-list\fP). +.IP "\fB\-k, \-\-kernel\fP" +Search in +.IR /proc/self/mountinfo . +The output is in the tree-like format. This is the default. + +.IP "\fB\-A, \-\-all\fP" +Disable all built-in filters and print all filesystems. +.IP "\fB\-a, \-\-ascii\fP" +Use ascii characters for tree formatting. +.IP "\fB\-c, \-\-canonicalize\fP" +Canonicalize all printed paths. +.IP "\fB\-D, \-\-df\fP" +Imitate the output of df(1). This option is equivalent to +"-o SOURCE,FSTYPE,SIZE,USED,AVAIL,USE%,TARGET", but excludes all +pseudo filesystem. Use \fB\-\-all\fP to print all filesystems. +.IP "\fB\-d, \-\-direction \fIword\fP" +The search direction - +.IR forward +or +.IR backward . +.IP "\fB\-e, \-\-evaluate\fP" +Convert all tags (LABEL, UUID, PARTUUID or PARTLABEL) to the device names. +.IP "\fB\-F, \-\-tab\-file \fIpath\fP" +Search in an alternative file, if used with \fB\-\-fstab\fP, \fB\-\-mtab\fP +or \fB\-\-kernel\fP then overwrites the default paths, if specified more than +once, then tree-like output is disabled (see the \fB\-\-list\fP option). +.IP "\fB\-f, \-\-first\-only\fP" +Print the first matching filesystem only. +.IP "\fB\-i, \-\-invert\fP" +Invert the sense of matching. +.IP "\fB\-l, \-\-list\fP" +Use the list output format. This output format is automatically enabled if the +output is restricted by \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. +.IP "\fB\-v, \-\-nofsroot\fP" +Do not print a [/dir] in the SOURCE column for bind-mounts or btrfs subvolumes. +.IP "\fB\-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 +.BR unshare (1) +command. +.IP "\fB\-n, \-\-noheadings\fP" +Do not print a header line. +.IP "\fB\-u, \-\-notruncate\fP" +Do not truncate text in columns. The default is to not truncate the +.BR TARGET , +.BR SOURCE , +.BR UUID , +.BR LABEL , +.BR PARTUUID , +.BR PARTLABEL +columns. This option disables text truncation also in all other columns. +.IP "\fB\-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" could used for +individual items in the list. The "no" prefix interpratation could be disabled +by "+" prefix. +.IP "\fB\-o, \-\-output \fIlist\fP" +Define output columns. See the \fB\-\-help\fP output to get list of the +currently supported columns. The +.BR TARGET +column contains tree formatting if the +.B \-\-list +or +.B \-\-raw +options are not specified. +.IP "\fB\-p, \-\-poll\fR[\fI=list\fR]\fP" +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 to use 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 +.IP "\fB\-P, \-\-pairs\fP" +Use key="value" output format. All potentially unsafe characters are hex-escaped (\\x<code>). +.IP "\fB\-r, \-\-raw\fP" +Use raw output format. All potentially unsafe characters are hex-escaped (\\x<code>). +.IP "\fB\-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 +.I no +to specify the filesystem types on which no action should be taken. For +more details see +.BR mount (8). +.IP "\fB\-R, \-\-submounts\fP" +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. +.IP "\fB\-S, \-\-source \fIspec\fP" +Explicitly define the mount source. Supported are \fIdevice\fR, \fImaj:min\fR, +\fILABEL=\fR, \fIUUID=\fR, \fIPARTLABEL=\fR or \fIPARTUUID=\fR. +.IP "\fB\-T, \-\-target \fIdir\fP" +Explicitly define the mount target (mountpoint directory). +.IP "\fB\-w, \-\-timeout \fImilliseconds\fP" +Specify an upper limit on the time for which \fB--poll\fR will block, in milliseconds. +.SH EXAMPLES +.IP "\fBfindmnt \-\-fstab \-t nfs\fP" +Prints all nfs filesystems defined in +.IR /etc/fstab . +.IP "\fBfindmnt \-\-fstab /mnt/foo\fP" +Prints all +.IR /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 +.IR /etc/fstab +filesystems where the mountpoint directory is /mnt/foo. +.IP "\fBfindmnt --fstab --evaluate\fP" +Prints all +.IR /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 --target /mnt/foo\fP" +Monitors mount, umount, remount and move on /mnt/foo. +.IP "\fBfindmnt --poll=umount --first-only --target /mnt/foo\fP" +Waits for /mnt/foo umount. +.IP "\fBfindmnt --poll=remount -t ext3 -O ro\fP" +Monitors remounts to read-only mode on all ext3 filesystems. +.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=0xffff +enables debug output +.SH AUTHORS +.nf +Karel Zak <kzak@redhat.com> +.fi +.SH SEE ALSO +.BR mount (8), +.BR fstab (5) +.SH AVAILABILITY +The findmnt command is part of the util-linux package and is available from +ftp://ftp.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..853fae0 --- /dev/null +++ b/misc-utils/findmnt.c @@ -0,0 +1,1368 @@ +/* + * findmnt(8) + * + * Copyright (C) 2010,2011 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> + +#include <libmount.h> + +#include "pathnames.h" +#include "nls.h" +#include "closestream.h" +#include "c.h" +#include "tt.h" +#include "strutils.h" +#include "xalloc.h" +#include "optutils.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) +}; + +/* 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, + + FINDMNT_NCOLUMNS +}; + +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; /* tt 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[FINDMNT_NCOLUMNS] = { + [COL_SOURCE] = { "SOURCE", 0.25, TT_FL_NOEXTREMES, N_("source device") }, + [COL_TARGET] = { "TARGET", 0.30, TT_FL_TREE | TT_FL_NOEXTREMES, N_("mountpoint") }, + [COL_FSTYPE] = { "FSTYPE", 0.10, TT_FL_TRUNC, N_("filesystem type") }, + [COL_OPTIONS] = { "OPTIONS", 0.10, TT_FL_TRUNC, N_("all mount options") }, + [COL_VFS_OPTIONS] = { "VFS-OPTIONS", 0.20, TT_FL_TRUNC, N_("VFS specific mount options") }, + [COL_FS_OPTIONS] = { "FS-OPTIONS", 0.10, TT_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, TT_FL_STRICTWIDTH, N_("action detected by --poll") }, + [COL_OLD_OPTIONS] = { "OLD-OPTIONS", 0.10, TT_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, TT_FL_RIGHT, N_("filesystem size") }, + [COL_AVAIL] = { "AVAIL", 5, TT_FL_RIGHT, N_("filesystem size available") }, + [COL_USED] = { "USED", 5, TT_FL_RIGHT, N_("filesystem size used") }, + [COL_USEPERC] = { "USE%", 3, TT_FL_RIGHT, N_("filesystem use percentage") }, + [COL_FSROOT] = { "FSROOT", 0.25, TT_FL_NOEXTREMES, N_("filesystem root") }, + [COL_TID] = { "TID", 4, TT_FL_RIGHT, N_("task ID") }, +}; + +/* global flags */ +static int flags; +static int tt_flags; + +/* array with IDs of enabled columns */ +static int columns[FINDMNT_NCOLUMNS]; +static int ncolumns; + +/* poll actions (parsed --poll=<list> */ +#define FINDMNT_NACTIONS 4 /* mount, umount, move, remount */ +static int actions[FINDMNT_NACTIONS]; +static int nactions; + +/* libmount cache */ +static struct libmnt_cache *cache; + +static int get_column_id(int num) +{ + assert(num < ncolumns); + assert(columns[num] < FINDMNT_NCOLUMNS); + 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(id < FINDMNT_NCOLUMNS); + 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(id < FINDMNT_NCOLUMNS); + return infos[id].match; +} + +static void *get_match_data(int id) +{ + assert(id < FINDMNT_NCOLUMNS); + return infos[id].match_data; +} + +static void set_match(int id, const char *match) +{ + assert(id < FINDMNT_NCOLUMNS); + infos[id].match = match; +} + +static void set_match_data(int id, void *data) +{ + assert(id < FINDMNT_NCOLUMNS); + 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); +} + +static void enable_extra_target_match(void) +{ + char *cn = NULL, *mnt = NULL; + + /* + * Check if match pattern is mountpoint, if not use the + * real mountpoint. + */ + cn = mnt_resolve_path(get_match(COL_TARGET), cache); + if (!cn) + return; + + mnt = mnt_get_mountpoint(cn); + if (!mnt || strcmp(mnt, cn) == 0) + return; + + /* replace the current setting with the real mountpoint */ + set_match(COL_TARGET, mnt); +} + + +static int is_tabdiff_column(int id) +{ + assert(id < FINDMNT_NCOLUMNS); + + switch(id) { + case COL_ACTION: + case COL_OLD_TARGET: + case COL_OLD_OPTIONS: + return 1; + default: + break; + } + return 0; +} + +/* + * "findmnt" without any filter + */ +static int is_listall_mode(void) +{ + if ((flags & FL_DF) && !(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) +{ + int i; + + for (i = 0; i < FINDMNT_NCOLUMNS; i++) + infos[i].flags &= ~TT_FL_TRUNC; +} + +/* + * converts @name to column ID + */ +static int column_name_to_id(const char *name, size_t namesz) +{ + int i; + + for (i = 0; i < FINDMNT_NCOLUMNS; 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; +} + +/* Returns LABEL or UUID */ +static const char *get_tag(struct libmnt_fs *fs, const char *tagname) +{ + const char *t, *v, *res; + + if (!mnt_fs_get_tag(fs, &t, &v) && !strcmp(t, tagname)) + res = v; + else { + res = mnt_fs_get_source(fs); + if (res) + res = mnt_resolve_spec(res, cache); + if (res) + res = mnt_cache_find_tag_value(cache, res, tagname); + } + + return res; +} + +static const 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 "-"; + + xasprintf(&sizestr, "%.0f%%", + (double)(buf.f_blocks - buf.f_bfree) / + buf.f_blocks * 100); + return sizestr; + } + + return vfs_attr == 0 ? "0" : + size_to_human_string(SIZE_SUFFIX_1LETTER, vfs_attr); +} + +/* reads FS data from libmount + */ +static const char *get_data(struct libmnt_fs *fs, int num) +{ + const char *str = NULL; + int col_id = get_column_id(num); + + switch (col_id) { + case COL_SOURCE: + { + const char *root = mnt_fs_get_root(fs); + + str = mnt_fs_get_srcpath(fs); + + if (str && (flags & FL_CANONICALIZE)) + str = mnt_resolve_path(str, cache); + if (!str) { + str = mnt_fs_get_source(fs); + + if (str && (flags & FL_EVALUATE)) + str = mnt_resolve_spec(str, cache); + } + if (root && str && !(flags & FL_NOFSROOT) && strcmp(root, "/")) { + char *tmp; + + if (xasprintf(&tmp, "%s[%s]", str, root) > 0) + str = tmp; + } + break; + } + case COL_TARGET: + str = mnt_fs_get_target(fs); + break; + case COL_FSTYPE: + str = mnt_fs_get_fstype(fs); + break; + case COL_OPTIONS: + str = mnt_fs_get_options(fs); + break; + case COL_VFS_OPTIONS: + str = mnt_fs_get_vfs_options(fs); + break; + case COL_FS_OPTIONS: + str = mnt_fs_get_fs_options(fs); + break; + case COL_UUID: + str = get_tag(fs, "UUID"); + break; + case COL_PARTUUID: + str = get_tag(fs, "PARTUUID"); + break; + case COL_LABEL: + str = get_tag(fs, "LABEL"); + break; + case COL_PARTLABEL: + str = get_tag(fs, "PARTLABEL"); + break; + + case COL_MAJMIN: + { + dev_t devno = mnt_fs_get_devno(fs); + if (devno) { + char *tmp; + int rc = 0; + if ((tt_flags & TT_FL_RAW) || (tt_flags & TT_FL_EXPORT)) + rc = xasprintf(&tmp, "%u:%u", + major(devno), minor(devno)); + else + rc = xasprintf(&tmp, "%3u:%-3u", + major(devno), minor(devno)); + if (rc) + str = tmp; + } + break; + } + case COL_SIZE: + case COL_AVAIL: + case COL_USED: + case COL_USEPERC: + str = get_vfs_attr(fs, col_id); + break; + case COL_FSROOT: + str = mnt_fs_get_root(fs); + break; + case COL_TID: + if (mnt_fs_get_tid(fs)) { + char *tmp; + if (xasprintf(&tmp, "%d", mnt_fs_get_tid(fs)) > 0) + str = tmp; + } + break; + default: + break; + } + return str; +} + +static const char *get_tabdiff_data(struct libmnt_fs *old_fs, + struct libmnt_fs *new_fs, + int change, + int num) +{ + const 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; + } + break; + case COL_OLD_OPTIONS: + if (old_fs && (change == MNT_TABDIFF_REMOUNT || + change == MNT_TABDIFF_UMOUNT)) + str = mnt_fs_get_options(old_fs); + break; + case COL_OLD_TARGET: + if (old_fs && (change == MNT_TABDIFF_MOVE || + change == MNT_TABDIFF_UMOUNT)) + str = 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 tt_line *add_line(struct tt *tt, struct libmnt_fs *fs, + struct tt_line *parent) +{ + int i; + struct tt_line *line = tt_add_line(tt, parent); + + if (!line) { + warn(_("failed to add line to output")); + return NULL; + } + for (i = 0; i < ncolumns; i++) + tt_line_set_data(line, i, get_data(fs, i)); + + tt_line_set_userdata(line, fs); + return line; +} + +static struct tt_line *add_tabdiff_line(struct tt *tt, struct libmnt_fs *new_fs, + struct libmnt_fs *old_fs, int change) +{ + int i; + struct tt_line *line = tt_add_line(tt, NULL); + + if (!line) { + warn(_("failed to add line to output")); + return NULL; + } + for (i = 0; i < ncolumns; i++) + tt_line_set_data(line, i, + get_tabdiff_data(old_fs, new_fs, change, i)); + + return line; +} + +static int has_line(struct tt *tt, struct libmnt_fs *fs) +{ + struct list_head *p; + + list_for_each(p, &tt->tb_lines) { + struct tt_line *ln = list_entry(p, struct tt_line, ln_lines); + if ((struct libmnt_fs *) ln->userdata == fs) + return 1; + } + return 0; +} + +/* reads filesystems from @tb (libmount) and fillin @tt (output table) */ +static int create_treenode(struct tt *tt, struct libmnt_table *tb, + struct libmnt_fs *fs, struct tt_line *parent_line) +{ + struct libmnt_fs *chld = NULL; + struct libmnt_iter *itr = NULL; + struct tt_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(tt, fs)) + return 0; + + itr = mnt_new_iter(MNT_ITER_FORWARD); + if (!itr) + goto leave; + + line = add_line(tt, fs, parent_line); + if (!line) + goto leave; + + /* + * add all children to the output table + */ + while(mnt_table_next_child_fs(tb, itr, fs, &chld) == 0) { + if (create_treenode(tt, 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"), filename, line); + return 0; +} + +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_free_table(tb); + warn(_("can't read %s"), path); + return NULL; + } + } while (--nfiles > 0); + + return 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 = NULL; + int rc = 0; + + itr = mnt_new_iter(MNT_ITER_BACKWARD); + if (!itr) + return 0; + + if (mnt_table_next_fs(tb, itr, &fs) == 0) + rc = mnt_fs_get_id(fs) > 0 && mnt_fs_get_parent_id(fs) > 0; + + mnt_free_iter(itr); + return rc; +} + + +/* 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_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; + + 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; + + 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; + } + + return !rc; +} + +/* iterate over filesystems in @tb */ +static 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) searchs 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: + mnt_table_find_next_fs(tb, itr, match_func, NULL, &fs); + + 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; +} + +static int add_matching_lines(struct libmnt_table *tb, + struct tt *tt, int direction) +{ + struct libmnt_iter *itr = NULL; + 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 ((tt_flags & TT_FL_TREE) || (flags & FL_SUBMOUNTS)) + rc = create_treenode(tt, tb, fs, NULL); + else + rc = !add_line(tt, 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 tt *tt, int direction) +{ + FILE *f = NULL; + int rc = -1; + struct libmnt_iter *itr = NULL; + struct libmnt_table *tb_new = NULL; + 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(tt, new, old, change); + if (rc) + goto done; + if (flags & FL_FIRSTONLY) + break; + } + + if (count) { + rc = tt_print_table(tt); + if (rc) + goto done; + } + + /* swap tables */ + tmp = tb; + tb = tb_new; + tb_new = tmp; + + tt_remove_lines(tt); + mnt_reset_table(tb_new); + + if (count && (flags & FL_FIRSTONLY)) + break; + } + + rc = 0; +done: + mnt_free_table(tb_new); + mnt_free_tabdiff(diff); + mnt_free_iter(itr); + if (f) + fclose(f); + return rc; +} + +static void __attribute__((__noreturn__)) usage(FILE *out) +{ + int 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 <mountpoint>]\n"), + program_invocation_short_name); + + fprintf(out, _( + "\nOptions:\n" + " -s, --fstab search in static table of filesystems\n" + " -m, --mtab search in table of mounted filesystems\n" + " -k, --kernel search in kernel table of mounted\n" + " filesystems (default)\n\n")); + + fprintf(out, _( + " -p, --poll[=<list>] monitor changes in table of mounted filesystems\n" + " -w, --timeout <num> upper limit in milliseconds that --poll will block\n\n")); + + fprintf(out, _( + " -A, --all disable all built-in filters, print all filesystems\n" + " -a, --ascii use ASCII chars for tree formatting\n" + " -c, --canonicalize canonicalize printed paths\n" + " -D, --df imitate the output of df(1)\n" + " -d, --direction <word> direction of search, 'forward' or 'backward'\n" + " -e, --evaluate convert tags (LABEL,UUID,PARTUUID,PARTLABEL) \n" + " to device names\n" + " -F, --tab-file <path> alternative file for --fstab, --mtab or --kernel options\n" + " -f, --first-only print the first found filesystem only\n")); + + fprintf(out, _( + " -i, --invert invert the sense of matching\n" + " -l, --list use list format output\n" + " -N, --task <tid> use alternative namespace (/proc/<tid>/mountinfo file)\n" + " -n, --noheadings don't print column headings\n" + " -u, --notruncate don't truncate text in columns\n")); + fprintf(out, _( + " -O, --options <list> limit the set of filesystems by mount options\n" + " -o, --output <list> the output columns to be shown\n" + " -P, --pairs use key=\"value\" output format\n" + " -r, --raw use raw output format\n" + " -t, --types <list> limit the set of filesystems by FS types\n")); + fprintf(out, _( + " -v, --nofsroot don't print [/dir] for bind or btrfs mounts\n" + " -R, --submounts print all submounts for the matching filesystems\n" + " -S, --source <string> the device to mount (by name, maj:min, \n" + " LABEL=, UUID=, PARTUUID=, PARTLABEL=)\n" + " -T, --target <string> the mountpoint to use\n")); + + fputs(USAGE_SEPARATOR, out); + fputs(USAGE_HELP, out); + fputs(USAGE_VERSION, out); + + fprintf(out, _("\nAvailable columns:\n")); + + for (i = 0; i < FINDMNT_NCOLUMNS; i++) + fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help)); + + fprintf(out, USAGE_MAN_TAIL("findmnt(8)")); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + struct libmnt_table *tb = NULL; + char **tabfiles = NULL; + int direction = MNT_ITER_FORWARD; + int i, c, rc = -1, timeout = -1; + int ntabfiles = 0, tabtype = 0; + char *outarg = NULL; + + struct tt *tt = NULL; + + static const struct option longopts[] = { + { "all", 0, 0, 'A' }, + { "ascii", 0, 0, 'a' }, + { "canonicalize", 0, 0, 'c' }, + { "direction", 1, 0, 'd' }, + { "df", 0, 0, 'D' }, + { "evaluate", 0, 0, 'e' }, + { "first-only", 0, 0, 'f' }, + { "fstab", 0, 0, 's' }, + { "help", 0, 0, 'h' }, + { "invert", 0, 0, 'i' }, + { "kernel", 0, 0, 'k' }, + { "list", 0, 0, 'l' }, + { "mtab", 0, 0, 'm' }, + { "noheadings", 0, 0, 'n' }, + { "notruncate", 0, 0, 'u' }, + { "options", 1, 0, 'O' }, + { "output", 1, 0, 'o' }, + { "poll", 2, 0, 'p' }, + { "pairs", 0, 0, 'P' }, + { "raw", 0, 0, 'r' }, + { "types", 1, 0, 't' }, + { "fsroot", 0, 0, 'v' }, + { "submounts", 0, 0, 'R' }, + { "source", 1, 0, 'S' }, + { "tab-file", 1, 0, 'F' }, + { "task", 1, 0, 'N' }, + { "target", 1, 0, 'T' }, + { "timeout", 1, 0, 'w' }, + { "version", 0, 0, 'V' }, + + { NULL, 0, 0, 0 } + }; + + static const ul_excl_t excl[] = { /* rows and cols in in ASCII order */ + { 'N','k','m','s' }, /* task,kernel,mtab,fstab */ + { 'P','l','r' }, /* pairs,list,raw */ + { 'm','p','s' }, /* mtab,poll,fstab */ + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + assert(ARRAY_SIZE(columns) == FINDMNT_NCOLUMNS); + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + /* default output format */ + tt_flags |= TT_FL_TREE; + + while ((c = getopt_long(argc, argv, + "AacDd:ehifF:o:O:p::PklmnN:rst:uvRS:T:w:V", + longopts, NULL)) != -1) { + + err_exclusive_options(c, longopts, excl, excl_st); + + switch(c) { + case 'A': + flags |= FL_ALL; + break; + case 'a': + tt_flags |= TT_FL_ASCII; + break; + case 'c': + flags |= FL_CANONICALIZE; + break; + case 'D': + tt_flags &= ~TT_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 'h': + usage(stdout); + break; + case 'i': + flags |= FL_INVERT; + 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 '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; + tt_flags &= ~TT_FL_TREE; + break; + case 'P': + tt_flags |= TT_FL_EXPORT; + tt_flags &= ~TT_FL_TREE; + break; + case 'm': /* mtab */ + tabtype = TABTYPE_MTAB; + tt_flags &= ~TT_FL_TREE; + break; + case 's': /* fstab */ + tabtype = TABTYPE_FSTAB; + tt_flags &= ~TT_FL_TREE; + break; + case 'k': /* kernel (mountinfo) */ + tabtype = TABTYPE_KERNEL; + break; + case 't': + set_match(COL_FSTYPE, optarg); + break; + case 'r': + tt_flags &= ~TT_FL_TREE; /* disable the default */ + tt_flags |= TT_FL_RAW; /* enable raw */ + break; + case 'l': + tt_flags &= ~TT_FL_TREE; /* disable the default */ + break; + case 'n': + tt_flags |= TT_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 'T': + set_match(COL_TARGET, optarg); + flags |= FL_NOSWAPMATCH; + break; + case 'w': + timeout = strtos32_or_err(optarg, _("invalid timeout argument")); + break; + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + default: + usage(stderr); + break; + } + } + + if (!ncolumns && (flags & FL_DF)) { + columns[ncolumns++] = COL_SOURCE; + columns[ncolumns++] = COL_FSTYPE; + columns[ncolumns++] = COL_SIZE; + columns[ncolumns++] = COL_USED; + columns[ncolumns++] = COL_AVAIL; + columns[ncolumns++] = COL_USEPERC; + columns[ncolumns++] = COL_TARGET; + } + + /* default columns */ + if (!ncolumns) { + if (flags & FL_POLL) + columns[ncolumns++] = COL_ACTION; + + columns[ncolumns++] = COL_TARGET; + columns[ncolumns++] = COL_SOURCE; + columns[ncolumns++] = COL_FSTYPE; + 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 = 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) && + (!is_listall_mode() || (flags & FL_FIRSTONLY))) + tt_flags &= ~TT_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 ((tt_flags & TT_FL_TREE) && (ntabfiles > 1 || !tab_is_tree(tb))) + tt_flags &= ~TT_FL_TREE; + + cache = mnt_new_cache(); + if (!cache) { + warn(_("failed to initialize libmount cache")); + goto leave; + } + mnt_table_set_cache(tb, cache); + + if (tabtype == TABTYPE_KERNEL + && (flags & FL_NOSWAPMATCH) + && get_match(COL_TARGET)) + /* + * enable extra functionality for target match + */ + enable_extra_target_match(); + + /* + * initialize output formatting (tt.h) + */ + tt = tt_new_table(tt_flags); + if (!tt) { + warn(_("failed to initialize output table")); + goto leave; + } + + for (i = 0; i < ncolumns; i++) { + int fl = get_column_flags(i); + int id = get_column_id(i); + + if (!(tt_flags & TT_FL_TREE)) + fl &= ~TT_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; + } + if (!tt_define_column(tt, get_column_name(i), + get_column_whint(i), fl)) { + warn(_("failed to initialize output column")); + goto leave; + } + } + + /* + * 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, tt, direction); + + } else if ((tt_flags & TT_FL_TREE) && is_listall_mode()) + /* whole tree */ + rc = create_treenode(tt, tb, NULL, NULL); + else + /* whole lits of sub-tree */ + rc = add_matching_lines(tb, tt, direction); + + /* + * Print the output table for non-poll modes + */ + if (!rc && !(flags & FL_POLL)) + tt_print_table(tt); +leave: + tt_free_table(tt); + + mnt_free_table(tb); + mnt_free_cache(cache); + free(tabfiles); + + return rc ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/misc-utils/getopt-parse.bash b/misc-utils/getopt-parse.bash new file mode 100644 index 0000000..29e7f6f --- /dev/null +++ b/misc-utils/getopt-parse.bash @@ -0,0 +1,47 @@ +#!/bin/bash + +# A small example program for using the new getopt(1) program. +# This program will only work with bash(1) +# An similar program using the tcsh(1) script language can be found +# as parse.tcsh + +# Example input and output (from the bash prompt): +# ./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 [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi + +# Note the quotes around `$TEMP': they are essential! +eval set -- "$TEMP" + +while true ; do + case "$1" in + -a|--a-long) echo "Option a" ; shift ;; + -b|--b-long) echo "Option b, argument \`$2'" ; shift 2 ;; + -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"; shift 2 ;; + *) echo "Option c, argument \`$2'" ; shift 2 ;; + esac ;; + --) shift ; break ;; + *) echo "Internal error!" ; 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..408c470 --- /dev/null +++ b/misc-utils/getopt-parse.tcsh @@ -0,0 +1,77 @@ +#!/bin/tcsh + +# A small example program for using the new getopt(1) program. +# This program will only work with tcsh(1) +# An similar program using the bash(1) script language can be found +# as 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..6558e12 --- /dev/null +++ b/misc-utils/getopt.1 @@ -0,0 +1,448 @@ +.TH GETOPT "1" "June 2012" "util-linux" "User Commands" +.SH NAME +getopt \- parse command options (enhanced) +.SH SYNOPSIS +getopt optstring parameters +.br +getopt [options] [\-\-] optstring parameters +.br +getopt [options] \-o|\-\-options optstring [options] [\-\-] +.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 legal 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 getopt will parse +.RI ( options +and +.BR \-o | \-\-options +.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 its first parameter is not an option (does not start +with a +.RB ' \- ', +this is 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" +Output a small usage guide and exit successfully. 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 +.IR \-q . +.TP +.BR \-s , " \-\-shell \fIshell\fP" +Set quoting conventions to those of shell. If no \-s argument is +found, the +.SM BASH +conventions are used. Valid arguments are currently +.RB ' sh ' +.RB ' bash ', +.RB ' csh ', +and +.RB ' tcsh '. +.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 \-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 \-V , " \-\-version" +Output version information and exit successfully. 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 (ie. 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 (ie. +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 ' \- ', +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 (ie. all +non\-option parameters are outputted 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 optionally installed in +.BR /usr/share/getopt/ . +.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 can not 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 getopt (3), +.BR bash (1), +.BR tcsh (1). +.SH AVAILABILITY +The getopt command is part of the util-linux package and is available from +.UR ftp://\:ftp.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..0efe5d8 --- /dev/null +++ b/misc-utils/getopt.c @@ -0,0 +1,458 @@ +/* + * getopt.c - Enhanced implementation of BSD getopt(1) + * Copyright (c) 1997-2005 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<B6>kiewicz + * <misiek@pld.org.pl>) + * Version 1.1.4: Mon Nov 7 2005 + * Fixed a few type's in the manpage + */ + +/* 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 TEST_EXIT_CODE 4 + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <getopt.h> + +#include "closestream.h" +#include "nls.h" +#include "xalloc.h" + +/* NON_OPT is the code that is returned when a non-option is found in '+' + * mode */ +#define NON_OPT 1 +/* LONG_OPT is the code that is returned when a long option is found. */ +#define LONG_OPT 2 + +/* The shells recognized. */ +typedef enum { BASH, TCSH } shell_t; + + +/* Some global variables that tells us how to parse. */ +static shell_t shell = BASH; /* The shell we generate output for. */ +static int quiet_errors = 0; /* 0 is not quiet. */ +static int quiet_output = 0; /* 0 is not quiet. */ +static int quote = 1; /* 1 is do quote. */ + +/* Allow changing which getopt is in use with function pointer */ +int (*getopt_long_fp) (int argc, char *const *argv, const char *optstr, + const struct option * longopts, int *longindex); + +/* Function prototypes */ +static const char *normalize(const char *arg); +static int generate_output(char *argv[], int argc, const char *optstr, + const struct option *longopts); +static void parse_error(const char *message); +static void add_long_options(char *options); +static void add_longopt(const char *name, int has_arg); +static void print_help(void); +static void set_shell(const char *new_shell); + +/* + * 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 const char *normalize(const char *arg) +{ + static char *BUFFER = NULL; + const char *argptr = arg; + char *bufptr; + + if (!quote) { + /* Just copy arg */ + BUFFER = xmalloc(strlen(arg) + 1); + strcpy(BUFFER, arg); + return BUFFER; + } + + /* + * 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'. + */ + BUFFER = xmalloc(strlen(arg) * 4 + 3); + + bufptr = BUFFER; + *bufptr++ = '\''; + + while (*argptr) { + if (*argptr == '\'') { + /* Quote: replace it with: '\'' */ + *bufptr++ = '\''; + *bufptr++ = '\\'; + *bufptr++ = '\''; + *bufptr++ = '\''; + } else if (shell == TCSH && *argptr == '!') { + /* Exclamation mark: replace it with: \! */ + *bufptr++ = '\''; + *bufptr++ = '\\'; + *bufptr++ = '!'; + *bufptr++ = '\''; + } else if (shell == TCSH && *argptr == '\n') { + /* Newline: replace it with: \n */ + *bufptr++ = '\\'; + *bufptr++ = 'n'; + } else if (shell == TCSH && isspace(*argptr)) { + /* Non-newline whitespace: replace it with \<ws> */ + *bufptr++ = '\''; + *bufptr++ = '\\'; + *bufptr++ = *argptr; + *bufptr++ = '\''; + } else + /* Just copy */ + *bufptr++ = *argptr; + argptr++; + } + *bufptr++ = '\''; + *bufptr++ = '\0'; + return BUFFER; +} + +/* + * 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(char *argv[], int argc, const char *optstr, + const struct option *longopts) +{ + int exit_code = EXIT_SUCCESS; /* Assume everything will be OK */ + int opt; + int longindex; + const char *charptr; + + if (quiet_errors) + /* No error reporting from getopt(3) */ + opterr = 0; + /* Reset getopt(3) */ + optind = 0; + + while ((opt = + (getopt_long_fp(argc, argv, optstr, longopts, &longindex))) + != EOF) + if (opt == '?' || opt == ':') + exit_code = GETOPT_EXIT_CODE; + else if (!quiet_output) { + if (opt == LONG_OPT) { + printf(" --%s", longopts[longindex].name); + if (longopts[longindex].has_arg) + printf(" %s", normalize(optarg ? optarg : "")); + } else if (opt == NON_OPT) + printf(" %s", normalize(optarg)); + else { + printf(" -%c", opt); + charptr = strchr(optstr, opt); + if (charptr != NULL && *++charptr == ':') + printf(" %s", normalize(optarg ? optarg : "")); + } + } + + if (!quiet_output) { + printf(" --"); + while (optind < argc) + printf(" %s", normalize(argv[optind++])); + printf("\n"); + } + 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); + fprintf(stderr, _("Try `%s --help' for more information.\n"), + program_invocation_short_name); + exit(PARAMETER_EXIT_CODE); +} + +static struct option *long_options = NULL; +static int long_options_length = 0; /* Length of array */ +static int long_options_nr = 0; /* Nr of used elements in array */ +#define LONG_OPTIONS_INCR 10 +#define init_longopt() add_longopt(NULL,0) + +/* Register a long option. The contents of name is copied. */ +static void add_longopt(const char *name, int has_arg) +{ + char *tmp; + if (!name) { + /* init */ + free(long_options); + long_options = NULL; + long_options_length = 0; + long_options_nr = 0; + } + + if (long_options_nr == long_options_length) { + long_options_length += LONG_OPTIONS_INCR; + long_options = xrealloc(long_options, + sizeof(struct option) * + long_options_length); + } + + long_options[long_options_nr].name = NULL; + long_options[long_options_nr].has_arg = 0; + long_options[long_options_nr].flag = NULL; + long_options[long_options_nr].val = 0; + + if (long_options_nr) { + /* Not for init! */ + long_options[long_options_nr - 1].has_arg = has_arg; + long_options[long_options_nr - 1].flag = NULL; + long_options[long_options_nr - 1].val = LONG_OPT; + tmp = xmalloc(strlen(name) + 1); + strcpy(tmp, name); + long_options[long_options_nr - 1].name = tmp; + } + long_options_nr++; +} + + +/* + * Register several long options. options is a string of long options, + * separated by commas or whitespace. This nukes options! + */ +static void add_long_options(char *options) +{ + int arg_opt; + char *tokptr = strtok(options, ", \t\n"); + while (tokptr) { + arg_opt = no_argument; + if (strlen(tokptr) > 0) { + if (tokptr[strlen(tokptr) - 1] == ':') { + if (tokptr[strlen(tokptr) - 2] == ':') { + tokptr[strlen(tokptr) - 2] = '\0'; + arg_opt = optional_argument; + } else { + tokptr[strlen(tokptr) - 1] = '\0'; + arg_opt = required_argument; + } + if (strlen(tokptr) == 0) + parse_error(_ + ("empty long option after " + "-l or --long argument")); + } + add_longopt(tokptr, arg_opt); + } + tokptr = strtok(NULL, ", \t\n"); + } +} + +static void set_shell(const char *new_shell) +{ + if (!strcmp(new_shell, "bash")) + shell = BASH; + else if (!strcmp(new_shell, "tcsh")) + shell = TCSH; + else if (!strcmp(new_shell, "sh")) + shell = BASH; + else if (!strcmp(new_shell, "csh")) + shell = TCSH; + else + parse_error(_ + ("unknown shell after -s or --shell argument")); +} + +static void __attribute__ ((__noreturn__)) print_help(void) +{ + fputs(_("\nUsage:\n"), stderr); + + fprintf(stderr, _( + " %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(_("\nOptions:\n"), stderr); + fputs(_(" -a, --alternative Allow long options starting with single -\n"), stderr); + fputs(_(" -h, --help This small usage guide\n"), stderr); + fputs(_(" -l, --longoptions <longopts> Long options to be recognized\n"), stderr); + fputs(_(" -n, --name <progname> The name under which errors are reported\n"), stderr); + fputs(_(" -o, --options <optstring> Short options to be recognized\n"), stderr); + fputs(_(" -q, --quiet Disable error reporting by getopt(3)\n"), stderr); + fputs(_(" -Q, --quiet-output No normal output\n"), stderr); + fputs(_(" -s, --shell <shell> Set shell quoting conventions\n"), stderr); + fputs(_(" -T, --test Test for getopt(1) version\n"), stderr); + fputs(_(" -u, --unquote Do not quote the output\n"), stderr); + fputs(_(" -V, --version Output version information\n"), stderr); + fputc('\n', stderr); + + exit(PARAMETER_EXIT_CODE); +} + +int main(int argc, char *argv[]) +{ + char *optstr = NULL; + char *name = NULL; + int opt; + int compatible = 0; + + /* 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); + atexit(close_stdout); + + init_longopt(); + getopt_long_fp = getopt_long; + + if (getenv("GETOPT_COMPATIBLE")) + compatible = 1; + + if (argc == 1) { + if (compatible) { + /* + * For some reason, the original getopt gave no + * error when there were no arguments. + */ + printf(" --\n"); + return EXIT_SUCCESS; + } else + parse_error(_("missing optstring argument")); + } + + if (argv[1][0] != '-' || compatible) { + quote = 0; + optstr = xmalloc(strlen(argv[1]) + 1); + strcpy(optstr, argv[1] + strspn(argv[1], "-+")); + argv[1] = argv[0]; + return generate_output(argv + 1, argc - 1, optstr, + long_options); + } + + while ((opt = + getopt_long(argc, argv, shortopts, longopts, NULL)) != EOF) + switch (opt) { + case 'a': + getopt_long_fp = getopt_long_only; + break; + case 'h': + print_help(); + case 'o': + free(optstr); + optstr = xmalloc(strlen(optarg) + 1); + strcpy(optstr, optarg); + break; + case 'l': + add_long_options(optarg); + break; + case 'n': + free(name); + name = xmalloc(strlen(optarg) + 1); + strcpy(name, optarg); + break; + case 'q': + quiet_errors = 1; + break; + case 'Q': + quiet_output = 1; + break; + case 's': + set_shell(optarg); + break; + case 'T': + return TEST_EXIT_CODE; + case 'u': + quote = 0; + break; + case 'V': + printf(_("%s from %s\n"), + program_invocation_short_name, + PACKAGE_STRING); + return EXIT_SUCCESS; + case '?': + case ':': + parse_error(NULL); + default: + parse_error(_("internal error, contact the author.")); + } + + if (!optstr) { + if (optind >= argc) + parse_error(_("missing optstring argument")); + else { + optstr = xmalloc(strlen(argv[optind]) + 1); + strcpy(optstr, argv[optind]); + optind++; + } + } + if (name) + argv[optind - 1] = name; + else + argv[optind - 1] = argv[0]; + + return generate_output(argv + optind - 1, argc-optind + 1, + optstr, long_options); +} diff --git a/misc-utils/kill.1 b/misc-utils/kill.1 new file mode 100644 index 0000000..09d30e9 --- /dev/null +++ b/misc-utils/kill.1 @@ -0,0 +1,111 @@ +.\" 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 "February 2011" "util-linux" "User Commands" +.SH NAME +kill \- terminate a process +.SH SYNOPSIS +.B kill +.RB [ \-s +.IR signal | \fB\-p\fP ] +.RB [ \-q +.IR sigval ] +.RB [ \-a ] +.RB [ \-\- ] +.IR pid ... +.br +.B kill -l +.RI [ signal ] +.SH DESCRIPTION +The command +.B kill +sends the specified signal to the specified process or process group. +If no signal is specified, the TERM signal is sent. The TERM signal +will kill processes which do not catch this signal. For other processes, +it may be necessary to use the KILL (9) signal, since this signal cannot +be caught. +.PP +Most modern shells have a builtin kill function, with a usage rather similar +to that of the command described here. The `-a' and `-p' options, +and the possibility to specify processes by command name are a local extension. +.PP +If sig is 0, then no signal is sent, but error checking is still performed. +.SH OPTIONS +.TP +.IR pid ... +Specify the list of processes that +.B kill +should signal. Each +.I pid +can be one of five things: + +.RS +.TP +.I n +where +.I n +is larger than 0. The process with pid +.I n +will be signaled. +.TP +.B 0 +All processes in the current process group are signaled. +.TP +.B -1 +All processes with pid larger than 1 will be 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 the signal must be specified first, or the argument must be preceded +by a `--' option, otherwise it will be taken as the signal to send. +.TP +.I commandname +All processes invoked using that name will be signaled. +.RE +.TP +.BI \-s " signal" +Specify the signal to send. +The signal may be given as a signal name or number. +.TP +.B \-l +Print a list of signal names. These are found in +.I /usr/include/linux/signal.h +.TP +.B \-a +Do not restrict the commandname-to-pid conversion to processes +with the same uid as the present process. +.TP +.B \-p +Specify that +.B kill +should only print the process id (pid) +of the named processes, and not send any signals. +.TP +.BI \-q " sigval" +Use +.BR sigqueue (2) +rather than +.BR kill (2) +and the sigval argument is used to specify an integer to be sent with the +signal. If the receiving process has installed a handler for this signal using +the SA_SIGINFO flag to +.BR sigaction (2), +then it can obtain this data via the si_value field of the siginfo_t structure. +.SH "SEE ALSO" +.BR bash (1), +.BR tcsh (1), +.BR kill (2), +.BR sigvec (2), +.BR signal (7) +.SH AUTHOR +Taken from BSD 4.4. The ability to translate process names to process +ids was added by Salvatore Valente <svalente@mit.edu>. +.SH AVAILABILITY +The kill command is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. diff --git a/misc-utils/kill.c b/misc-utils/kill.c new file mode 100644 index 0000000..6abfd24 --- /dev/null +++ b/misc-utils/kill.c @@ -0,0 +1,439 @@ +/* + * 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 numers 128+s. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> /* for isdigit() */ +#include <unistd.h> +#include <signal.h> + +#include "c.h" +#include "kill.h" +#include "nls.h" +#include "closestream.h" +#include "strutils.h" + +struct signv { + char *name; + int val; +} sys_signame[] = { + /* POSIX signals */ + { "HUP", SIGHUP }, /* 1 */ + { "INT", SIGINT }, /* 2 */ + { "QUIT", SIGQUIT }, /* 3 */ + { "ILL", SIGILL }, /* 4 */ + { "ABRT", SIGABRT }, /* 6 */ + { "FPE", SIGFPE }, /* 8 */ + { "KILL", SIGKILL }, /* 9 */ + { "SEGV", SIGSEGV }, /* 11 */ + { "PIPE", SIGPIPE }, /* 13 */ + { "ALRM", SIGALRM }, /* 14 */ + { "TERM", SIGTERM }, /* 15 */ + { "USR1", SIGUSR1 }, /* 10 (arm,i386,m68k,ppc), 30 (alpha,sparc*), 16 (mips) */ + { "USR2", SIGUSR2 }, /* 12 (arm,i386,m68k,ppc), 31 (alpha,sparc*), 17 (mips) */ + { "CHLD", SIGCHLD }, /* 17 (arm,i386,m68k,ppc), 20 (alpha,sparc*), 18 (mips) */ + { "CONT", SIGCONT }, /* 18 (arm,i386,m68k,ppc), 19 (alpha,sparc*), 25 (mips) */ + { "STOP", SIGSTOP }, /* 19 (arm,i386,m68k,ppc), 17 (alpha,sparc*), 23 (mips) */ + { "TSTP", SIGTSTP }, /* 20 (arm,i386,m68k,ppc), 18 (alpha,sparc*), 24 (mips) */ + { "TTIN", SIGTTIN }, /* 21 (arm,i386,m68k,ppc,alpha,sparc*), 26 (mips) */ + { "TTOU", SIGTTOU }, /* 22 (arm,i386,m68k,ppc,alpha,sparc*), 27 (mips) */ + /* Miscellaneous other signals */ +#ifdef SIGTRAP + { "TRAP", SIGTRAP }, /* 5 */ +#endif +#ifdef SIGIOT + { "IOT", SIGIOT }, /* 6, same as SIGABRT */ +#endif +#ifdef SIGEMT + { "EMT", SIGEMT }, /* 7 (mips,alpha,sparc*) */ +#endif +#ifdef SIGBUS + { "BUS", SIGBUS }, /* 7 (arm,i386,m68k,ppc), 10 (mips,alpha,sparc*) */ +#endif +#ifdef SIGSYS + { "SYS", SIGSYS }, /* 12 (mips,alpha,sparc*) */ +#endif +#ifdef SIGSTKFLT + { "STKFLT", SIGSTKFLT }, /* 16 (arm,i386,m68k,ppc) */ +#endif +#ifdef SIGURG + { "URG", SIGURG }, /* 23 (arm,i386,m68k,ppc), 16 (alpha,sparc*), 21 (mips) */ +#endif +#ifdef SIGIO + { "IO", SIGIO }, /* 29 (arm,i386,m68k,ppc), 23 (alpha,sparc*), 22 (mips) */ +#endif +#ifdef SIGPOLL + { "POLL", SIGPOLL }, /* same as SIGIO */ +#endif +#ifdef SIGCLD + { "CLD", SIGCLD }, /* same as SIGCHLD (mips) */ +#endif +#ifdef SIGXCPU + { "XCPU", SIGXCPU }, /* 24 (arm,i386,m68k,ppc,alpha,sparc*), 30 (mips) */ +#endif +#ifdef SIGXFSZ + { "XFSZ", SIGXFSZ }, /* 25 (arm,i386,m68k,ppc,alpha,sparc*), 31 (mips) */ +#endif +#ifdef SIGVTALRM + { "VTALRM", SIGVTALRM }, /* 26 (arm,i386,m68k,ppc,alpha,sparc*), 28 (mips) */ +#endif +#ifdef SIGPROF + { "PROF", SIGPROF }, /* 27 (arm,i386,m68k,ppc,alpha,sparc*), 29 (mips) */ +#endif +#ifdef SIGPWR + { "PWR", SIGPWR }, /* 30 (arm,i386,m68k,ppc), 29 (alpha,sparc*), 19 (mips) */ +#endif +#ifdef SIGINFO + { "INFO", SIGINFO }, /* 29 (alpha) */ +#endif +#ifdef SIGLOST + { "LOST", SIGLOST }, /* 29 (arm,i386,m68k,ppc,sparc*) */ +#endif +#ifdef SIGWINCH + { "WINCH", SIGWINCH }, /* 28 (arm,i386,m68k,ppc,alpha,sparc*), 20 (mips) */ +#endif +#ifdef SIGUNUSED + { "UNUSED", SIGUNUSED }, /* 31 (arm,i386,m68k,ppc) */ +#endif +}; + +extern char *mybasename(char *); + +static int arg_to_signum (char *arg, int mask); +static void nosig (char *name); +static void printsig (int sig); +static void printsignals (FILE *fp); +static int usage (int status); +static int kill_verbose (char *procname, int pid, int sig); + +static char *progname; + +#ifdef HAVE_SIGQUEUE +static int use_sigval; +static union sigval sigdata; +#endif + +int main (int argc, char *argv[]) +{ + int errors, numsig, pid; + char *ep, *arg, *p; + int do_pid, do_kill, check_all; + int *pids, *ip; + + progname = argv[0]; + if ((p = strrchr(progname, '/')) != NULL) + progname = p+1; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + numsig = SIGTERM; + do_pid = (! strcmp (progname, "pid")); /* Yecch */ + do_kill = 0; + check_all = 0; + + /* loop through the arguments. + actually, -a is the only option can be used with other options. + `kill' is basically a one-option-at-most program. */ + for (argc--, argv++; argc > 0; argc--, argv++) { + arg = *argv; + if (*arg != '-') { + break; + } + if (! strcmp (arg, "--")) { + argc--, argv++; + break; + } + if (! strcmp (arg, "-v") || ! strcmp (arg, "-V") || + ! strcmp (arg, "--version")) { + printf(_("%s from %s\n"), progname, PACKAGE_STRING); + return 0; + } + if (! strcmp (arg, "-a")) { + check_all++; + continue; + } + if (! strcmp (arg, "-l")) { + if (argc < 2) { + printsignals (stdout); + return 0; + } + if (argc > 2) { + return usage (1); + } + /* argc == 2, accept "kill -l $?" */ + arg = argv[1]; + if ((numsig = arg_to_signum (arg, 1)) < 0) { + fprintf (stderr, _("%s: unknown signal %s\n"), progname, arg); + return 1; + } + printsig (numsig); + return 0; + } + if (! strcmp (arg, "-p")) { + do_pid++; + if (do_kill) + return usage (1); + continue; + } + if (! strcmp (arg, "-s")) { + if (argc < 2) { + return usage (1); + } + do_kill++; + if (do_pid) + return usage (1); + argc--, argv++; + arg = *argv; + if ((numsig = arg_to_signum (arg, 0)) < 0) { + nosig (arg); + return 1; + } + continue; + } + if (! strcmp (arg, "-q")) { + if (argc < 2) + return usage (1); + argc--, argv++; + arg = *argv; +#ifdef HAVE_SIGQUEUE + sigdata.sival_int = strtos32_or_err(arg, _("invalid sigval argument")); + use_sigval = 1; +#endif + continue; + } + /* `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 signal n, or pid -n (i.e. process group n). + In case of doubt POSIX tells us to assume a signal. + If a signal has been parsed, assume it's a pid, break */ + if (do_kill) + break; + arg++; + if ((numsig = arg_to_signum (arg, 0)) < 0) { + return usage (1); + } + do_kill++; + if (do_pid) + return usage (1); + continue; + } + + if (! *argv) { + return usage (1); + } + if (do_pid) { + numsig = -1; + } + + /* we're done with the options. + the rest of the arguments should be process ids and names. + kill them. */ + for (errors = 0; (arg = *argv) != NULL; argv++) { + pid = strtol (arg, &ep, 10); + if (! *ep) + errors += kill_verbose (arg, pid, numsig); + else { + pids = get_pids (arg, check_all); + if (! pids) { + errors++; + fprintf (stderr, _("%s: can't find process \"%s\"\n"), + progname, arg); + continue; + } + for (ip = pids; *ip >= 0; ip++) + errors += kill_verbose (arg, *ip, numsig); + free (pids); + } + } + return (errors); +} + +#ifdef SIGRTMIN +static int rtsig_to_signum(char *sig) +{ + int num, maxi = 0; + char *ep = NULL; + + if (strncasecmp(sig, "min+", 4) == 0) + sig += 4; + else if (strncasecmp(sig, "max-", 4) == 0) { + sig += 4; + maxi = 1; + } + + if (!isdigit(*sig)) + return -1; + + errno = 0; + num = strtol(sig, &ep, 10); + if (!ep || sig == ep || errno || num < 0) + return -1; + + num = maxi ? SIGRTMAX - num : SIGRTMIN + num; + + if (num < SIGRTMIN || num > SIGRTMAX) + return -1; + + return num; +} +#endif + +static int signame_to_signum (char *sig) +{ + size_t n; + + if (! strncasecmp (sig, "sig", 3)) + sig += 3; + +#ifdef SIGRTMIN + /* RT signals */ + if (!strncasecmp(sig, "rt", 2)) + return rtsig_to_signum(sig + 2); +#endif + /* Normal sugnals */ + for (n = 0; n < ARRAY_SIZE(sys_signame); n++) { + if (! strcasecmp (sys_signame[n].name, sig)) + return sys_signame[n].val; + } + return (-1); +} + +static int arg_to_signum (char *arg, int maskbit) +{ + int numsig; + char *ep; + + if (isdigit (*arg)) { + numsig = strtol (arg, &ep, 10); + if (numsig >= NSIG && maskbit && (numsig & 128) != 0) + numsig -= 128; + if (*ep != 0 || numsig < 0 || numsig >= NSIG) + return (-1); + return (numsig); + } + return signame_to_signum (arg); +} + +static void nosig (char *name) +{ + fprintf (stderr, _("%s: unknown signal %s; valid signals:\n"), progname, name); + printsignals (stderr); +} + +static void printsig (int sig) +{ + size_t n; + + for (n = 0; n < ARRAY_SIZE(sys_signame); n++) { + if (sys_signame[n].val == sig) { + printf ("%s\n", sys_signame[n].name); + return; + } + } +#ifdef SIGRTMIN + if (sig >= SIGRTMIN && sig <= SIGRTMAX) { + printf ("RT%d\n", sig - SIGRTMIN); + return; + } +#endif + printf("%d\n", sig); +} + +static void printsignals (FILE *fp) +{ + size_t n, lth, lpos = 0; + + for (n = 0; n < ARRAY_SIZE(sys_signame); n++) { + lth = 1+strlen(sys_signame[n].name); + if (lpos+lth > 72) { + fputc ('\n', fp); + lpos = 0; + } else if (lpos) + fputc (' ', fp); + lpos += lth; + fputs (sys_signame[n].name, fp); + } +#ifdef SIGRTMIN + fputs (" RT<N> RTMIN+<N> RTMAX-<N>", fp); +#endif + fputc ('\n', fp); +} + +static int usage (int status) +{ + FILE *fp; + + fp = (status == 0 ? stdout : stderr); + fprintf (fp, _("usage: %s [ -s signal | -p ] [ -a ] pid ...\n"), progname); + fprintf (fp, _(" %s -l [ signal ]\n"), progname); + return status; +} + +static int kill_verbose (char *procname, int pid, int sig) +{ + int rc; + + if (sig < 0) { + printf ("%d\n", pid); + return 0; + } +#ifdef HAVE_SIGQUEUE + if (use_sigval) + rc = sigqueue(pid, sig, sigdata); + else +#endif + rc = kill (pid, sig); + + if (rc < 0) { + fprintf (stderr, "%s ", progname); + perror (procname); + return 1; + } + return 0; +} diff --git a/misc-utils/kill.h b/misc-utils/kill.h new file mode 100644 index 0000000..27a12a8 --- /dev/null +++ b/misc-utils/kill.h @@ -0,0 +1 @@ +extern int *get_pids (char *process_name, int get_all); diff --git a/misc-utils/logger.1 b/misc-utils/logger.1 new file mode 100644 index 0000000..d863ac3 --- /dev/null +++ b/misc-utils/logger.1 @@ -0,0 +1,155 @@ +.\" 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 +.\" +.\" Section on valid facility and level strings added by +.\" and1000@debian.org, 26 Oct 1997. +.TH LOGGER "1" "September 2011" "util-linux" "User Commands" +.SH NAME +logger \- a shell command interface to the syslog(3) system log module +.SH SYNOPSIS +.B logger +[options] [message] +.SH DESCRIPTION +.B logger +makes entries in the system log. It provides a shell command +interface to the +.BR syslog (3) +system log module. +.SH OPTIONS +.TP +\fB\-d\fR, \fB\-\-udp\fR +Use datagram (UDP) instead of the default stream connection (TCP). +.TP +\fB\-i\fR, \fB\-\-id\fR +Log the process ID of the logger process with each line. +.TP +\fB\-f\fR, \fB\-\-file\fR \fIfile\fR +Log the contents of the specified +.IR file . +This option cannot be combined with a command-line message. +.TP +\fB\-h\fR, \fB\-\-help\fR +Display a help text and exit. +.TP +\fB\-n\fR, \fB\-\-server\fR \fIserver\fR +Write to the specified remote syslog +.I server +using UDP instead of to the builtin syslog routines. +.TP +\fB\-P\fR, \fB\-\-port\fR \fIport\fR +Use the specified UDP +.IR port . +The default port number is 514. +.TP +\fB\-p\fR, \fB\-\-priority\fR \fIpriority\fR +Enter the message into the log with the specified +.IR priority . +The priority may be specified numerically or as a +.I facility.level +pair. +For example, +.B -p +.I local3.info +logs the message as informational in the local3 facility. +The default is +.IR user.notice . +.TP +\fB\-s\fR, \fB\-\-stderr\fR +Output the message to standard error as well as to the system log. +.TP +\fB\-t\fR, \fB\-\-tag\fR \fItag\fR +Mark every line to be logged with the specified +.IR tag . +.TP +\fB\-u\fR, \fB\-\-socket\fR \fIsocket\fR +Write to the specified +.I socket +instead of to the builtin syslog routines. +.TP +\fB\-V\fR, \fB\-\-version\fR +Display version information and exit. +.TP +\fB\-\-\fR +End the argument list. This is to allow the +.I message +to start with a hyphen (\-). +.TP +.I message +Write the message to log; if not specified, and the +.I \-f +flag is not provided, standard input is logged. +.PP +The +.B logger +utility exits 0 on success, and >0 if an error occurs. +.PP +Valid facility names are: +.IR auth , \ authpriv +(for security information of a sensitive nature), +.IR cron , \ daemon , \ ftp , \ kern +(can't be generated from user process), +.IR lpr , \ mail , \ news , \ security +(deprecated synonym for +.IR auth ), \ syslog , \ user , \ uucp , +and +.IR local0 \ to \ local7 , +inclusive. +.PP +Valid level names are: +.IR alert , \ crit , \ debug , \ emerg , \ err , \ error +(deprecated synonym for +.IR err ), \ info , \ notice , \ panic +(deprecated synonym for +.IR emerg ), \ warning , \ warn +(deprecated synonym for +.IR warning ). +For the priority order and intended purposes of these levels, see +.BR syslog (3). +.SH EXAMPLES +logger System rebooted +.br +logger \-p local0.notice \-t HOSTIDM \-f /dev/idmc +.br +logger \-n loghost.example.com System rebooted +.SH SEE ALSO +.BR syslog (3), +.BR syslogd (8) +.SH STANDARDS +The +.B logger +command is expected to be IEEE Std 1003.2 ("POSIX.2") compatible. +.SH AVAILABILITY +The logger command is part of the util-linux package and is available from +.UR ftp://\:ftp.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..6f9edc2 --- /dev/null +++ b/misc-utils/logger.c @@ -0,0 +1,338 @@ +/* + * 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 <unistd.h> +#include <stdlib.h> +#include <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 "c.h" +#include "closestream.h" +#include "nls.h" +#include "strutils.h" + +#define SYSLOG_NAMES +#include <syslog.h> + +static int optd = 0; +static uint16_t udpport = 514; + +static int decode(char *name, CODE *codetab) +{ + register CODE *c; + + if (isdigit(*name)) + return (atoi(name)); + + for (c = codetab; c->c_name; c++) + if (!strcasecmp(name, c->c_name)) + return (c->c_val); + + return -1; +} + +static int pencode(char *s) +{ + char *save; + int fac, lev; + + for (save = s; *s && *s != '.'; ++s); + if (*s) { + *s = '\0'; + fac = decode(save, facilitynames); + if (fac < 0) + errx(EXIT_FAILURE, _("unknown facility name: %s."), save); + *s++ = '.'; + } + else { + fac = LOG_USER; + s = save; + } + lev = decode(s, prioritynames); + if (lev < 0) + errx(EXIT_FAILURE, _("unknown priority name: %s."), save); + return ((lev & LOG_PRIMASK) | (fac & LOG_FACMASK)); +} + +static int +myopenlog(const char *sock) { + int fd; + static struct sockaddr_un s_addr; /* AF_UNIX address of local logger */ + + if (strlen(sock) >= sizeof(s_addr.sun_path)) + errx(EXIT_FAILURE, _("openlog %s: pathname too long"), sock); + + s_addr.sun_family = AF_UNIX; + (void)strcpy(s_addr.sun_path, sock); + + if ((fd = socket(AF_UNIX, optd ? SOCK_DGRAM : SOCK_STREAM, 0)) == -1) + err(EXIT_FAILURE, _("socket %s"), sock); + + if (connect(fd, (struct sockaddr *) &s_addr, sizeof(s_addr)) == -1) + err(EXIT_FAILURE, _("connect %s"), sock); + + return fd; +} + +static int +udpopenlog(const char *servername, uint16_t port) { + int fd; + struct sockaddr_in s_addr; + struct hostent *serverhost; + + if ((serverhost = gethostbyname(servername)) == NULL ) + errx(EXIT_FAILURE, _("unable to resolve '%s'"), servername); + + if ((fd = socket(AF_INET, SOCK_DGRAM , 0)) == -1) + err(EXIT_FAILURE, _("socket")); + + memcpy(&s_addr.sin_addr, serverhost->h_addr, serverhost->h_length); + s_addr.sin_family=AF_INET; + s_addr.sin_port=htons(port); + + if (connect(fd, (struct sockaddr *) &s_addr, sizeof(s_addr)) == -1) + err(EXIT_FAILURE, _("connect")); + + return fd; +} +static void +mysyslog(int fd, int logflags, int pri, char *tag, char *msg) { + char buf[1000], pid[30], *cp, *tp; + time_t now; + + if (fd > -1) { + if (logflags & LOG_PID) + snprintf (pid, sizeof(pid), "[%d]", getpid()); + else + pid[0] = 0; + if (tag) + cp = tag; + else { + cp = getlogin(); + if (!cp) + cp = "<someone>"; + } + (void)time(&now); + tp = ctime(&now)+4; + + snprintf(buf, sizeof(buf), "<%d>%.15s %.200s%s: %.400s", + pri, tp, cp, pid, msg); + + if (write(fd, buf, strlen(buf)+1) < 0) + return; /* error */ + } +} + +static void __attribute__ ((__noreturn__)) usage(FILE *out) +{ + fputs(_("\nUsage:\n"), out); + fprintf(out, + _(" %s [options] [message]\n"), program_invocation_short_name); + + fputs(_("\nOptions:\n"), out); + fputs(_(" -d, --udp use UDP (TCP is default)\n" + " -i, --id log the process ID too\n" + " -f, --file <file> log the contents of this file\n" + " -h, --help display this help text and exit\n"), out); + fputs(_(" -n, --server <name> write to this remote syslog server\n" + " -P, --port <number> use this UDP port\n" + " -p, --priority <prio> mark given message with this priority\n" + " -s, --stderr output message to standard error as well\n"), out); + fputs(_(" -t, --tag <tag> mark every line with this tag\n" + " -u, --socket <socket> write to this Unix socket\n" + " -V, --version output version information and exit\n\n"), out); + + exit(out == stderr ? EXIT_FAILURE : 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) { + int ch, logflags, pri; + char *tag, buf[1024]; + char *usock = NULL; + char *udpserver = NULL; + int LogSock = -1; + + static const struct option longopts[] = { + { "id", no_argument, 0, 'i' }, + { "stderr", no_argument, 0, 's' }, + { "file", required_argument, 0, 'f' }, + { "priority", required_argument, 0, 'p' }, + { "tag", required_argument, 0, 't' }, + { "socket", required_argument, 0, 'u' }, + { "udp", no_argument, 0, 'd' }, + { "server", required_argument, 0, 'n' }, + { "port", required_argument, 0, 'P' }, + { "version", no_argument, 0, 'V' }, + { "help", no_argument, 0, 'h' }, + { NULL, 0, 0, 0 } + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + tag = NULL; + pri = LOG_NOTICE; + logflags = 0; + while ((ch = getopt_long(argc, argv, "f:ip:st:u:dn:P:Vh", + longopts, NULL)) != -1) { + switch((char)ch) { + case 'f': /* file to log */ + if (freopen(optarg, "r", stdin) == NULL) + err(EXIT_FAILURE, _("file %s"), + optarg); + break; + case 'i': /* log process id also */ + logflags |= LOG_PID; + break; + case 'p': /* priority */ + pri = pencode(optarg); + break; + case 's': /* log to standard error */ + logflags |= LOG_PERROR; + break; + case 't': /* tag */ + tag = optarg; + break; + case 'u': /* unix socket */ + usock = optarg; + break; + case 'd': + optd = 1; /* use datagrams */ + break; + case 'n': /* udp socket */ + optd = 1; /* use datagrams because udp */ + udpserver = optarg; + break; + case 'P': /* change udp port */ + udpport = strtou16_or_err(optarg, + _("invalid port number argument")); + break; + case 'V': + printf(_("%s from %s\n"), program_invocation_short_name, + PACKAGE_STRING); + exit(EXIT_SUCCESS); + case 'h': + usage(stdout); + case '?': + default: + usage(stderr); + } + } + argc -= optind; + argv += optind; + + /* setup for logging */ + if (!usock && !udpserver) + openlog(tag ? tag : getlogin(), logflags, 0); + else if (udpserver) + LogSock = udpopenlog(udpserver,udpport); + else + LogSock = myopenlog(usock); + + /* log input line if appropriate */ + if (argc > 0) { + register char *p, *endp; + size_t len; + + for (p = buf, endp = buf + sizeof(buf) - 2; *argv;) { + len = strlen(*argv); + if (p + len > endp && p > buf) { + if (!usock && !udpserver) + syslog(pri, "%s", buf); + else + mysyslog(LogSock, logflags, pri, tag, buf); + p = buf; + } + if (len > sizeof(buf) - 1) { + if (!usock && !udpserver) + syslog(pri, "%s", *argv++); + else + mysyslog(LogSock, logflags, pri, tag, *argv++); + } else { + if (p != buf) + *p++ = ' '; + memmove(p, *argv++, len); + *(p += len) = '\0'; + } + } + if (p != buf) { + if (!usock && !udpserver) + syslog(pri, "%s", buf); + else + mysyslog(LogSock, logflags, pri, tag, buf); + } + } else { + while (fgets(buf, sizeof(buf), stdin) != NULL) { + /* glibc is buggy and adds an additional newline, + so we have to remove it here until glibc is fixed */ + int len = strlen(buf); + + if (len > 0 && buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + if (!usock && !udpserver) + syslog(pri, "%s", buf); + else + mysyslog(LogSock, logflags, pri, tag, buf); + } + } + if (!usock && !udpserver) + closelog(); + else + close(LogSock); + + return EXIT_SUCCESS; +} diff --git a/misc-utils/look.1 b/misc-utils/look.1 new file mode 100644 index 0000000..fe8d8fd --- /dev/null +++ b/misc-utils/look.1 @@ -0,0 +1,116 @@ +.\" 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 +.RI [ options ] " 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 sort(1) got 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 alphanumeric characters +are compared. (This is on by default if no file is specified.) +.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 \-h , " \-\-help" +Display help text and exit. +.TP +.BR \-V , " \-\-version" +Output version information 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 EXAMPLE +.RS +.nf +sort -d /etc/passwd -o /tmp/look.dict +look -t: root:foobar /tmp/look.dict +.nf +.RE +.SH FILES +.IX Header "FILES" +.IP "\fB/usr/share/dict/words\fR" 4 +the dictionary +.IP "\fB/usr/share/dict/web2\fR" 4 +the alternative dictionary +.SH "SEE ALSO" +.BR grep (1), +.BR sort (1) +.SH COMPATIBILITY +The original manual page stated that tabs and blank characters participated +in comparisons when the alphanum option was specified. This was incorrect, +and the current man page matches the historic implementation. +.SH HISTORY +The +.B look +utility appeared in Version 7 AT&T Unix. +.SH AVAILABILITY +The look command is part of the util-linux package and is available from +ftp://ftp.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..351d007 --- /dev/null +++ b/misc-utils/look.c @@ -0,0 +1,381 @@ +/*- + * 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) + +int dflag, fflag; +/* uglified the source a bit with globals, so that we only need + to allocate comparbuf once */ +int stringlen; +char *string; +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(FILE * out); + +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); + atexit(close_stdout); + + setlocale(LC_ALL, ""); + + 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': + printf(_("%s from %s\n"), + program_invocation_short_name, + PACKAGE_STRING); + return EXIT_SUCCESS; + case 'h': + usage(stdout); + case '?': + default: + usage(stderr); + } + 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: + usage(stderr); + } + + 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, +#ifdef MAP_FILE + MAP_FILE | +#endif + MAP_SHARED, fd, (off_t) 0); + if +#ifdef MAP_FAILED + (front == MAP_FAILED) +#else + ((void *)(front) <= (void *)0) +#endif + err(EXIT_FAILURE, "%s", file); + +#if 0 + /* workaround for mmap problem (rmiller@duskglow.com) */ + if (front == (void *)0) + return 1; +#endif + + back = front + sb.st_size; + return look(front, back); +} + +int +look(char *front, char *back) +{ + int ch; + char *readp, *writep; + + /* Reformat string string to avoid doing it multiple times later. */ + if (dflag) { + for (readp = writep = string; (ch = *readp++) != 0;) { + if (isalnum(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') + +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. + */ +char * +linear_search(char *front, char *back) +{ + while (front < back) { + switch (compare(front, back)) { + case EQUAL: /* Found it. */ + return (front); + break; + case LESS: /* No such string. */ + return (NULL); + break; + case GREATER: /* Keep going. */ + break; + } + SKIP_PAST_NEWLINE(front, back); + } + return (NULL); +} + +/* + * Print as many lines as match string, starting at front. + */ +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. + */ +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)) + { + *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(FILE * out) +{ + fputs(_("\nUsage:\n"), out), + fprintf(out, + _(" %s [options] string [file]\n"), program_invocation_short_name); + + fputs(_("\nOptions:\n"), out); + fputs(_(" -a, --alternative use alternate dictionary\n" + " -d, --alphanum compare only alpha numeric characters\n" + " -f, --ignore-case ignore when comparing\n" + " -t, --terminate <char> define string termination character\n" + " -V, --version output version information and exit\n" + " -h, --help display this help and exit\n\n"), out); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} diff --git a/misc-utils/lsblk.8 b/misc-utils/lsblk.8 new file mode 100644 index 0000000..4ed024f --- /dev/null +++ b/misc-utils/lsblk.8 @@ -0,0 +1,104 @@ +.\" -*- nroff -*- +.TH LSBLK 8 "April 2010" "util-linux" "System Administration" +.SH NAME +lsblk \- list block devices +.SH SYNOPSIS +.B lsblk +.RB [ options ] +.sp +.B lsblk +.RB [ options ] +.IR device... +.SH DESCRIPTION +.B lsblk +lists information about all or the specified block devices. The +.B lsblk +command reads the +.I sysfs +filesystem to gather information. +.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 default output from options like --topology and +--fs is subject to change, so whenever possible you should avoid using default +outputs in your scripts. Always explicitly define expected columns by +.B \-\-output +.IR columns +in environment where a stable output is required. +.SH OPTIONS +.IP "\fB\-a, \-\-all\fP" +.B lsblk +does not list empty devices by default. This option disables this restriction. +.IP "\fB\-b, \-\-bytes\fP" +Print the SIZE column in bytes rather than in human-readable format. +.IP "\fB\-d, \-\-nodeps\fP" +Don't print device holders or slaves. For example "lsblk --nodeps /dev/sda" prints +information about the sda device only. +.IP "\fB\-D, \-\-discard\fP" +Print information about the discard (TRIM, UNMAP) capabilities for each device. +.IP "\fB\-e, \-\-exclude \fIlist\fP +Exclude the devices specified by a comma-separated \fIlist\fR of major device numbers. +Note that RAM disks (major=1) are excluded by default. The filter is applied to the top-level +devices only. +.IP "\fB\-I, \-\-include \fIlist\fP +Include devices specified by a comma-separated \fIlist\fR of major device numbers only. +The filter is applied to the top-level devices. +.IP "\fB\-f, \-\-fs\fP +Output info about filesystems. This option is equivalent to "-o NAME,FSTYPE,LABEL,MOUNTPOINT". +The authoritative information about filesystems and raids is provided by the +.BR blkid (8) +command. +.IP "\fB\-h, \-\-help\fP" +Print a help text and exit. +.IP "\fB\-i, \-\-ascii\fP" +Use ASCII characters for tree formatting. +.IP "\fB\-m, \-\-perms\fP +Output info about device owner, group and mode. This option is equivalent to "-o NAME,SIZE,OWNER,GROUP,MODE". +.IP "\fB\-l, \-\-list\fP" +Use the list output format. +.IP "\fB\-n, \-\-noheadings\fP" +Do not print a header line. +.IP "\fB\-o, \-\-output \fIlist\fP" +Specify which output columns to print. Use +.B "--help" +to get a list of all supported columns. +.IP "\fB\-P, \-\-pairs\fP" +Use key="value" output format. All potentially unsafe characters are hex-escaped (\\x<code>). +.IP "\fB\-r, \-\-raw\fP" +Use the raw output format. All potentially unsafe characters are hex-escaped +(\\x<code>) in NAME, KNAME, LABEL, PARTLABEL and MOUNTPOINT columns. +.IP "\fB\-s, \-\-inverse\fP" +Print dependencies in inverse order. +.IP "\fB\-t, \-\-topology\fP" +Output info about block device topology. +This option is equivalent to "-o NAME,ALIGNMENT,MIN-IO,OPT-IO,PHY-SEC,LOG-SEC,ROTA,SCHED,RQ-SIZE". +.IP "\fB\-V, \-\-version\fP" +Output version information and exit. +.SH NOTES +For partitions, some information (e.g. queue attributes) is inherited from the +parent device. + +.PP +The +.B lsblk +needs to be able to lookup sysfs path by major:minor, which is done +done by using +.BR /sys/dev/block . +The block sysfs appeared in kernel 2.6.27 (October 2008). In case of +problem with new enough kernel check that CONFIG_SYSFS was enabled at +the time of kernel build. +.SH AUTHORS +.nf +Milan Broz <mbroz@redhat.com> +Karel Zak <kzak@redhat.com> +.fi +.SH SEE ALSO +.BR findmnt (8), +.BR blkid (8), +.BR ls (1) +.SH AVAILABILITY +The lsblk command is part of the util-linux package and is available from +ftp://ftp.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..2410de4 --- /dev/null +++ b/misc-utils/lsblk.c @@ -0,0 +1,1421 @@ +/* + * lsblk(8) - list block devices + * + * Copyright (C) 2010,2011 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 <stdint.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <fcntl.h> +#include <string.h> +#include <sys/ioctl.h> +#include <inttypes.h> +#include <stdarg.h> +#include <locale.h> +#include <pwd.h> +#include <grp.h> +#include <ctype.h> + +#include <blkid.h> +#include <libmount.h> + +#ifdef HAVE_LIBUDEV +#include <libudev.h> +#endif + +#include <assert.h> + +#include "c.h" +#include "pathnames.h" +#include "blkdev.h" +#include "canonicalize.h" +#include "nls.h" +#include "tt.h" +#include "xalloc.h" +#include "strutils.h" +#include "at.h" +#include "sysfs.h" +#include "closestream.h" +#include "mangle.h" +#include "optutils.h" + +/* column IDs */ +enum { + COL_NAME = 0, + COL_KNAME, + COL_MAJMIN, + COL_FSTYPE, + COL_TARGET, + COL_LABEL, + COL_UUID, + COL_PARTLABEL, + COL_PARTUUID, + COL_RA, + COL_RO, + COL_RM, + COL_MODEL, + 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_WWN, +}; + +/* column names */ +struct colinfo { + const char *name; /* header */ + double whint; /* width hint (N < 1 is in percent of termwidth) */ + int flags; /* TT_FL_* */ + const char *help; +}; + +/* columns descriptions */ +static struct colinfo infos[] = { + [COL_NAME] = { "NAME", 0.25, TT_FL_TREE | TT_FL_NOEXTREMES, N_("device name") }, + [COL_KNAME] = { "KNAME", 0.3, 0, N_("internal kernel device name") }, + [COL_MAJMIN] = { "MAJ:MIN", 6, 0, N_("major:minor device number") }, + [COL_FSTYPE] = { "FSTYPE", 0.1, TT_FL_TRUNC, N_("filesystem type") }, + [COL_TARGET] = { "MOUNTPOINT", 0.10, TT_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_PARTLABEL] = { "PARTLABEL", 0.1, 0, N_("partition LABEL") }, + [COL_PARTUUID] = { "PARTUUID", 36, 0, N_("partition UUID") }, + + [COL_RA] = { "RA", 4, TT_FL_RIGHT, N_("read-ahead of the device") }, + [COL_RO] = { "RO", 1, TT_FL_RIGHT, N_("read-only device") }, + [COL_RM] = { "RM", 1, TT_FL_RIGHT, N_("removable device") }, + [COL_ROTA] = { "ROTA", 1, TT_FL_RIGHT, N_("rotational device") }, + [COL_MODEL] = { "MODEL", 0.1, TT_FL_TRUNC, N_("device identifier") }, + [COL_SIZE] = { "SIZE", 5, TT_FL_RIGHT, N_("size of the device") }, + [COL_STATE] = { "STATE", 7, TT_FL_TRUNC, N_("state of the device") }, + [COL_OWNER] = { "OWNER", 0.1, TT_FL_TRUNC, N_("user name"), }, + [COL_GROUP] = { "GROUP", 0.1, TT_FL_TRUNC, N_("group name") }, + [COL_MODE] = { "MODE", 10, 0, N_("device node permissions") }, + [COL_ALIOFF] = { "ALIGNMENT", 6, TT_FL_RIGHT, N_("alignment offset") }, + [COL_MINIO] = { "MIN-IO", 6, TT_FL_RIGHT, N_("minimum I/O size") }, + [COL_OPTIO] = { "OPT-IO", 6, TT_FL_RIGHT, N_("optimal I/O size") }, + [COL_PHYSEC] = { "PHY-SEC", 7, TT_FL_RIGHT, N_("physical sector size") }, + [COL_LOGSEC] = { "LOG-SEC", 7, TT_FL_RIGHT, N_("logical sector size") }, + [COL_SCHED] = { "SCHED", 0.1, 0, N_("I/O scheduler name") }, + [COL_RQ_SIZE]= { "RQ-SIZE", 5, TT_FL_RIGHT, N_("request queue size") }, + [COL_TYPE] = { "TYPE", 4, 0, N_("device type") }, + [COL_DALIGN] = { "DISC-ALN", 6, TT_FL_RIGHT, N_("discard alignment offset") }, + [COL_DGRAN] = { "DISC-GRAN", 6, TT_FL_RIGHT, N_("discard granularity") }, + [COL_DMAX] = { "DISC-MAX", 6, TT_FL_RIGHT, N_("discard max bytes") }, + [COL_DZERO] = { "DISC-ZERO", 1, TT_FL_RIGHT, N_("discard zeroes data") }, + [COL_WWN] = { "WWN", 18, 0, N_("unique storage identifier") }, + +}; + +struct lsblk { + struct tt *tt; /* output table */ + 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 nodeps:1; /* don't print slaves/holders */ +}; + +struct lsblk *lsblk; /* global handler */ + +#define NCOLS ARRAY_SIZE(infos) +static int columns[NCOLS];/* enabled columns */ +static int ncolumns; /* number of enabled columns */ + +static int excludes[256]; +static size_t nexcludes; + +static int includes[256]; +static size_t nincludes; + +static struct libmnt_table *mtab, *swaps; +static struct libmnt_cache *mntcache; + +struct blkdev_cxt { + struct blkdev_cxt *parent; + + struct tt_line *tt_line; + struct stat st; + + char *name; /* kernel name in /sys/block */ + char *dm_name; /* DM name (dm/block) */ + + char *filename; /* path to device node */ + + struct sysfs_cxt sysfs; + + int partition; /* is partition? TRUE/FALSE */ + + int probed; /* already probed */ + char *fstype; /* detected fs, NULL or "?" if cannot detect */ + char *uuid; /* filesystem UUID (or stack uuid) */ + char *label; /* filesystem label */ + char *partuuid; /* partition UUID */ + char *partlabel; /* partiton label */ + char *wwn; /* storage WWN */ + + 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 */ + int discard; /* supports discard */ + + uint64_t size; /* device size */ +}; + +static int is_maj_excluded(int maj) +{ + size_t i; + + assert(ARRAY_SIZE(excludes) > nexcludes); + + if (!nexcludes) + return 0; /* filter not enabled, device not exluded */ + + for (i = 0; i < nexcludes; i++) + if (excludes[i] == 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) + return 1; + return 0; +} + +/* array with IDs of enabled columns */ +static int get_column_id(int num) +{ + assert(ARRAY_SIZE(columns) == NCOLS); + assert(num < ncolumns); + assert(columns[num] < (int) NCOLS); + return columns[num]; +} + +static struct colinfo *get_column_info(int num) +{ + return &infos[ get_column_id(num) ]; +} + +static int column_name_to_id(const char *name, size_t namesz) +{ + size_t i; + + for (i = 0; i < NCOLS; i++) { + const char *cn = infos[i].name; + + if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) + return i; + } + warnx(_("unknown column: %s"), name); + return -1; +} + +static void reset_blkdev_cxt(struct blkdev_cxt *cxt) +{ + if (!cxt) + return; + free(cxt->name); + free(cxt->dm_name); + free(cxt->filename); + free(cxt->fstype); + free(cxt->uuid); + free(cxt->label); + free(cxt->partuuid); + free(cxt->partlabel); + free(cxt->wwn); + + sysfs_deinit(&cxt->sysfs); + + memset(cxt, 0, sizeof(*cxt)); +} + +static int is_dm(const char *name) +{ + return strncmp(name, "dm-", 3) ? 0 : 1; +} + +static struct dirent *xreaddir(DIR *dp) +{ + struct dirent *d; + + assert(dp); + + while ((d = readdir(dp))) { + if (!strcmp(d->d_name, ".") || + !strcmp(d->d_name, "..")) + continue; + + /* blacklist here? */ + break; + } + return d; +} + +static char *get_device_path(struct blkdev_cxt *cxt) +{ + char path[PATH_MAX]; + + assert(cxt); + assert(cxt->name); + + if (is_dm(cxt->name)) + return canonicalize_dm_name(cxt->name); + + snprintf(path, sizeof(path), "/dev/%s", cxt->name); + return xstrdup(path); +} + +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_cache(swaps, mntcache); + mnt_table_parse_swaps(swaps, NULL); + } + + return mnt_table_find_srcpath(swaps, filename, MNT_ITER_BACKWARD) != 0; +} + +static char *get_device_mountpoint(struct blkdev_cxt *cxt) +{ + struct libmnt_fs *fs; + const char *fsroot; + + assert(cxt); + assert(cxt->filename); + + if (!mtab) { + mtab = mnt_new_table(); + if (!mtab) + return NULL; + if (!mntcache) + mntcache = mnt_new_cache(); + + mnt_table_set_cache(mtab, mntcache); + mnt_table_parse_mtab(mtab, NULL); + } + + /* try /etc/mtab or /proc/self/mountinfo */ + fs = mnt_table_find_srcpath(mtab, cxt->filename, MNT_ITER_BACKWARD); + if (!fs) + return is_active_swap(cxt->filename) ? xstrdup("[SWAP]") : NULL; + + /* 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, cxt->filename, mntcache)) { + fs = rfs; + break; + } + } + mnt_free_iter(itr); + } + + return xstrdup(mnt_fs_get_target(fs)); +} + +#ifndef HAVE_LIBUDEV +static int get_udev_properties(struct blkdev_cxt *cxt + __attribute__((__unused__))) +{ + return -1; +} +#else +static int get_udev_properties(struct blkdev_cxt *cxt) +{ + struct udev *udev; + struct udev_device *dev; + + if (cxt->probed) + return 0; /* already done */ + + udev = udev_new(); + if (!udev) + return -1; + + dev = udev_device_new_from_subsystem_sysname(udev, "block", cxt->name); + if (dev) { + const char *data; + + if ((data = udev_device_get_property_value(dev, "ID_FS_LABEL_ENC"))) { + cxt->label = xstrdup(data); + unhexmangle_string(cxt->label); + } + if ((data = udev_device_get_property_value(dev, "ID_FS_UUID_ENC"))) { + cxt->uuid = xstrdup(data); + unhexmangle_string(cxt->uuid); + } + if ((data = udev_device_get_property_value(dev, "ID_PART_ENTRY_NAME"))) { + cxt->partlabel = xstrdup(data); + unhexmangle_string(cxt->partlabel); + } + if ((data = udev_device_get_property_value(dev, "ID_FS_TYPE"))) + cxt->fstype = xstrdup(data); + if ((data = udev_device_get_property_value(dev, "ID_PART_ENTRY_UUID"))) + cxt->partuuid = xstrdup(data); + if ((data = udev_device_get_property_value(dev, "ID_WWN"))) + cxt->wwn = xstrdup(data); + + udev_device_unref(dev); + cxt->probed = 1; + } + + udev_unref(udev); + + return cxt->probed == 1 ? 0 : -1; + +} +#endif /* HAVE_LIBUDEV */ + +static void probe_device(struct blkdev_cxt *cxt) +{ + blkid_probe pr = NULL; + + if (cxt->probed) + return; + + if (!cxt->size) + return; + + /* try udev DB */ + if (get_udev_properties(cxt) == 0) + return; /* success */ + + cxt->probed = 1; + + /* try libblkid (fallback) */ + if (getuid() != 0) + return; /* no permissions to read from the device */ + + pr = blkid_new_probe_from_filename(cxt->filename); + if (!pr) + return; + + 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; + + if (!blkid_probe_lookup_value(pr, "TYPE", &data, NULL)) + cxt->fstype = xstrdup(data); + if (!blkid_probe_lookup_value(pr, "UUID", &data, NULL)) + cxt->uuid = xstrdup(data); + if (!blkid_probe_lookup_value(pr, "LABEL", &data, NULL)) + cxt->label = xstrdup(data); + if (!blkid_probe_lookup_value(pr, "PART_ENTRY_UUID", &data, NULL)) + cxt->partuuid = xstrdup(data); + if (!blkid_probe_lookup_value(pr, "PART_ENTRY_NAME", &data, NULL)) + cxt->partlabel = xstrdup(data); + + } + + blkid_free_probe(pr); + return; +} + +static int is_readonly_device(struct blkdev_cxt *cxt) +{ + int fd, ro = 0; + + if (sysfs_scanf(&cxt->sysfs, "ro", "%d", &ro) == 1) + return ro; + + /* fallback if "ro" attribute does not exist */ + fd = open(cxt->filename, O_RDONLY); + if (fd != -1) { + if (ioctl(fd, BLKROGET, &ro) != 0) + ro = 0; + close(fd); + } + return ro; +} + +static char *get_scheduler(struct blkdev_cxt *cxt) +{ + char *str = sysfs_strdup(&cxt->sysfs, "queue/scheduler"); + char *p, *res = NULL; + + if (!str) + return NULL; + p = strchr(str, '['); + if (p) { + res = p + 1; + p = strchr(res, ']'); + if (p) { + *p = '\0'; + res = xstrdup(res); + } else + res = NULL; + } + free(str); + return res; +} + +static char *get_type(struct blkdev_cxt *cxt) +{ + char *res = NULL, *p; + + if (is_dm(cxt->name)) { + char *dm_uuid = sysfs_strdup(&cxt->sysfs, "dm/uuid"); + + /* The DM_UUID prefix should be set to subsystem owning + * the device - LVM, CRYPT, DMRAID, MPATH, PART */ + if (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(cxt->name, "loop", 4)) { + res = xstrdup("loop"); + + } else if (!strncmp(cxt->name, "md", 2)) { + char *md_level = sysfs_strdup(&cxt->sysfs, "md/level"); + res = md_level ? md_level : xstrdup("md"); + + } else { + const char *type; + int x = 0; + + sysfs_read_int(&cxt->sysfs, "device/type", &x); + + type = blkdev_scsi_type_to_name(x); + if (!type) + type = cxt->partition ? "part" : "disk"; + res = xstrdup(type); + } + + for (p = res; p && *p; p++) + *p = tolower((unsigned char) *p); + return res; +} + +#define is_parsable(_l) (((_l)->tt->flags & TT_FL_RAW) || \ + ((_l)->tt->flags & TT_FL_EXPORT)) + +static void set_tt_data(struct blkdev_cxt *cxt, int col, int id, struct tt_line *ln) +{ + char buf[1024]; + char *p = NULL; + int st_rc = 0; + + if (!cxt->st.st_rdev && (id == COL_OWNER || id == COL_GROUP || + id == COL_MODE)) + st_rc = stat(cxt->filename, &cxt->st); + + switch(id) { + case COL_NAME: + if (cxt->dm_name) { + if (is_parsable(lsblk)) + tt_line_set_data(ln, col, xstrdup(cxt->dm_name)); + else { + snprintf(buf, sizeof(buf), "%s (%s)", + cxt->dm_name, cxt->name); + tt_line_set_data(ln, col, xstrdup(buf)); + } + break; + } + case COL_KNAME: + tt_line_set_data(ln, col, xstrdup(cxt->name)); + break; + case COL_OWNER: + { + struct passwd *pw = st_rc ? NULL : getpwuid(cxt->st.st_uid); + if (pw) + tt_line_set_data(ln, col, xstrdup(pw->pw_name)); + break; + } + case COL_GROUP: + { + struct group *gr = st_rc ? NULL : getgrgid(cxt->st.st_gid); + if (gr) + tt_line_set_data(ln, col, xstrdup(gr->gr_name)); + break; + } + case COL_MODE: + { + char md[11]; + + if (!st_rc) { + strmode(cxt->st.st_mode, md); + tt_line_set_data(ln, col, xstrdup(md)); + } + break; + } + case COL_MAJMIN: + if (is_parsable(lsblk)) + snprintf(buf, sizeof(buf), "%u:%u", cxt->maj, cxt->min); + else + snprintf(buf, sizeof(buf), "%3u:%-3u", cxt->maj, cxt->min); + tt_line_set_data(ln, col, xstrdup(buf)); + break; + case COL_FSTYPE: + probe_device(cxt); + if (cxt->fstype) + tt_line_set_data(ln, col, xstrdup(cxt->fstype)); + break; + case COL_TARGET: + if (!(cxt->nholders + cxt->npartitions)) { + if ((p = get_device_mountpoint(cxt))) + tt_line_set_data(ln, col, p); + } + break; + case COL_LABEL: + probe_device(cxt); + if (!cxt->label) + break; + + tt_line_set_data(ln, col, xstrdup(cxt->label)); + break; + case COL_UUID: + probe_device(cxt); + if (cxt->uuid) + tt_line_set_data(ln, col, xstrdup(cxt->uuid)); + break; + case COL_PARTLABEL: + probe_device(cxt); + if (!cxt->partlabel) + break; + + tt_line_set_data(ln, col, xstrdup(cxt->partlabel)); + break; + case COL_PARTUUID: + probe_device(cxt); + if (cxt->uuid) + tt_line_set_data(ln, col, xstrdup(cxt->partuuid)); + break; + case COL_WWN: + get_udev_properties(cxt); + if (cxt->wwn) + tt_line_set_data(ln, col, xstrdup(cxt->wwn)); + break; + case COL_RA: + p = sysfs_strdup(&cxt->sysfs, "queue/read_ahead_kb"); + if (p) + tt_line_set_data(ln, col, p); + break; + case COL_RO: + tt_line_set_data(ln, col, is_readonly_device(cxt) ? + xstrdup("1") : xstrdup("0")); + break; + case COL_RM: + p = sysfs_strdup(&cxt->sysfs, "removable"); + if (!p && cxt->sysfs.parent) + p = sysfs_strdup(cxt->sysfs.parent, "removable"); + if (p) + tt_line_set_data(ln, col, p); + break; + case COL_ROTA: + p = sysfs_strdup(&cxt->sysfs, "queue/rotational"); + if (p) + tt_line_set_data(ln, col, p); + break; + case COL_MODEL: + if (!cxt->partition && cxt->nslaves == 0) { + p = sysfs_strdup(&cxt->sysfs, "device/model"); + if (p) + tt_line_set_data(ln, col, p); + } + break; + case COL_SIZE: + if (cxt->size) { + if (lsblk->bytes) { + if (xasprintf(&p, "%jd", cxt->size) < 0) + p = NULL; + } else + p = size_to_human_string(SIZE_SUFFIX_1LETTER, cxt->size); + if (p) + tt_line_set_data(ln, col, p); + } + break; + case COL_STATE: + if (!cxt->partition && !cxt->dm_name) { + p = sysfs_strdup(&cxt->sysfs, "device/state"); + } else if (cxt->dm_name) { + int x = 0; + if (sysfs_read_int(&cxt->sysfs, "dm/suspended", &x) == 0) + p = x ? xstrdup("suspended") : xstrdup("running"); + } + if (p) + tt_line_set_data(ln, col, p); + break; + case COL_ALIOFF: + p = sysfs_strdup(&cxt->sysfs, "alignment_offset"); + if (p) + tt_line_set_data(ln, col, p); + break; + case COL_MINIO: + p = sysfs_strdup(&cxt->sysfs, "queue/minimum_io_size"); + if (p) + tt_line_set_data(ln, col, p); + break; + case COL_OPTIO: + p = sysfs_strdup(&cxt->sysfs, "queue/optimal_io_size"); + if (p) + tt_line_set_data(ln, col, p); + break; + case COL_PHYSEC: + p = sysfs_strdup(&cxt->sysfs, "queue/physical_block_size"); + if (p) + tt_line_set_data(ln, col, p); + break; + case COL_LOGSEC: + p = sysfs_strdup(&cxt->sysfs, "queue/logical_block_size"); + if (p) + tt_line_set_data(ln, col, p); + break; + case COL_SCHED: + p = get_scheduler(cxt); + if (p) + tt_line_set_data(ln, col, p); + break; + case COL_RQ_SIZE: + p = sysfs_strdup(&cxt->sysfs, "queue/nr_requests"); + if (p) + tt_line_set_data(ln, col, p); + break; + case COL_TYPE: + p = get_type(cxt); + if (p) + tt_line_set_data(ln, col, p); + break; + case COL_DALIGN: + p = sysfs_strdup(&cxt->sysfs, "discard_alignment"); + if (cxt->discard && p) + tt_line_set_data(ln, col, p); + else + tt_line_set_data(ln, col, "0"); + break; + case COL_DGRAN: + if (lsblk->bytes) + p = sysfs_strdup(&cxt->sysfs, "queue/discard_granularity"); + else { + uint64_t x; + + if (sysfs_read_u64(&cxt->sysfs, + "queue/discard_granularity", &x) == 0) + p = size_to_human_string(SIZE_SUFFIX_1LETTER, x); + } + if (p) + tt_line_set_data(ln, col, p); + break; + case COL_DMAX: + if (lsblk->bytes) + p = sysfs_strdup(&cxt->sysfs, "queue/discard_max_bytes"); + else { + uint64_t x; + + if (sysfs_read_u64(&cxt->sysfs, + "queue/discard_max_bytes", &x) == 0) + p = size_to_human_string(SIZE_SUFFIX_1LETTER, x); + } + if (p) + tt_line_set_data(ln, col, p); + break; + case COL_DZERO: + p = sysfs_strdup(&cxt->sysfs, "queue/discard_zeroes_data"); + if (cxt->discard && p) + tt_line_set_data(ln, col, p); + else + tt_line_set_data(ln, col, "0"); + break; + }; +} + +static void print_device(struct blkdev_cxt *cxt, struct tt_line *tt_parent) +{ + int i; + + cxt->tt_line = tt_add_line(lsblk->tt, tt_parent); + + for (i = 0; i < ncolumns; i++) + set_tt_data(cxt, i, get_column_id(i), cxt->tt_line); +} + +static int set_cxt(struct blkdev_cxt *cxt, + struct blkdev_cxt *parent, + struct blkdev_cxt *wholedisk, + const char *name) +{ + dev_t devno; + + cxt->parent = parent; + cxt->name = xstrdup(name); + cxt->partition = wholedisk != NULL; + + cxt->filename = get_device_path(cxt); + if (!cxt->filename) { + warnx(_("%s: failed to get device path"), name); + return -1; + } + + devno = sysfs_devname_to_devno(name, wholedisk ? wholedisk->name : NULL); + + if (!devno) { + warnx(_("%s: unknown device name"), name); + return -1; + } + + if (lsblk->inverse) { + if (sysfs_init(&cxt->sysfs, devno, wholedisk ? &wholedisk->sysfs : NULL)) { + warnx(_("%s: failed to initialize sysfs handler"), name); + return -1; + } + if (parent) + parent->sysfs.parent = &cxt->sysfs; + } else { + if (sysfs_init(&cxt->sysfs, devno, parent ? &parent->sysfs : NULL)) { + warnx(_("%s: failed to initialize sysfs handler"), name); + return -1; + } + } + + cxt->maj = major(devno); + cxt->min = minor(devno); + cxt->size = 0; + + if (sysfs_read_u64(&cxt->sysfs, "size", &cxt->size) == 0) /* in sectors */ + cxt->size <<= 9; /* in bytes */ + + sysfs_read_int(&cxt->sysfs, "queue/discard_granularity", &cxt->discard); + + /* Ignore devices of zero size */ + if (!lsblk->all_devices && cxt->size == 0) + return -1; + + if (is_dm(name)) { + cxt->dm_name = sysfs_strdup(&cxt->sysfs, "dm/name"); + if (!cxt->dm_name) { + warnx(_("%s: failed to get dm name"), name); + return -1; + } + } + + cxt->npartitions = sysfs_count_partitions(&cxt->sysfs, name); + cxt->nholders = sysfs_count_dirents(&cxt->sysfs, "holders"); + cxt->nslaves = sysfs_count_dirents(&cxt->sysfs, "slaves"); + + return 0; +} + +static int process_blkdev(struct blkdev_cxt *cxt, struct blkdev_cxt *parent, + int do_partitions, const char *part_name); + +/* + * List device partitions if any. + */ +static int list_partitions(struct blkdev_cxt *wholedisk_cxt, struct blkdev_cxt *parent_cxt, + const char *part_name) +{ + DIR *dir; + struct dirent *d; + struct blkdev_cxt part_cxt = {}; + int r = -1; + + assert(wholedisk_cxt); + + /* + * Do not process further if there are no partitions for + * this device or the device itself is a partition. + */ + if (!wholedisk_cxt->npartitions || wholedisk_cxt->partition) + return -1; + + dir = sysfs_opendir(&wholedisk_cxt->sysfs, NULL); + if (!dir) + err(EXIT_FAILURE, _("failed to open device directory in sysfs")); + + while ((d = xreaddir(dir))) { + /* Process particular partition only? */ + if (part_name && strcmp(part_name, d->d_name)) + continue; + + if (!(sysfs_is_partition_dirent(dir, d, wholedisk_cxt->name))) + continue; + + if (lsblk->inverse) { + /* + * <parent_cxt> + * `-<part_cxt> + * `-<wholedisk_cxt> + * `-... + */ + if (set_cxt(&part_cxt, parent_cxt, wholedisk_cxt, d->d_name)) + goto next; + + if (!parent_cxt && part_cxt.nholders) + goto next; + + wholedisk_cxt->parent = &part_cxt; + print_device(&part_cxt, parent_cxt ? parent_cxt->tt_line : NULL); + if (!lsblk->nodeps) + process_blkdev(wholedisk_cxt, &part_cxt, 0, NULL); + } else { + /* + * <parent_cxt> + * `-<wholedisk_cxt> + * `-<part_cxt> + * `-... + */ + if (set_cxt(&part_cxt, wholedisk_cxt, wholedisk_cxt, d->d_name)) + goto next; + /* Print whole disk only once */ + if (r) + print_device(wholedisk_cxt, parent_cxt ? parent_cxt->tt_line : NULL); + if (!lsblk->nodeps) + process_blkdev(&part_cxt, wholedisk_cxt, 0, NULL); + } + next: + reset_blkdev_cxt(&part_cxt); + r = 0; + } + + closedir(dir); + return r; +} + +static int get_wholedisk_from_partition_dirent(DIR *dir, const char *dirname, + struct dirent *d, struct blkdev_cxt *cxt) +{ + char path[PATH_MAX]; + char *p; + int len; + + if ((len = readlink_at(dirfd(dir), dirname, + d->d_name, path, sizeof(path))) < 0) + return 0; + + path[len] = '\0'; + + /* The path ends with ".../<device>/<partition>" */ + p = strrchr(path, '/'); + if (!p) + return 0; + *p = '\0'; + + p = strrchr(path, '/'); + if (!p) + return 0; + p++; + + return set_cxt(cxt, NULL, NULL, p); +} + +/* + * List device dependencies: partitions, holders (inverse = 0) or slaves (inverse = 1). + */ +static int list_deps(struct blkdev_cxt *cxt) +{ + DIR *dir; + struct dirent *d; + struct blkdev_cxt dep = {}; + char dirname[PATH_MAX]; + const char *depname; + + assert(cxt); + + if (lsblk->nodeps) + return 0; + + if (!(lsblk->inverse ? cxt->nslaves : cxt->nholders)) + return 0; + + depname = lsblk->inverse ? "slaves" : "holders"; + dir = sysfs_opendir(&cxt->sysfs, depname); + if (!dir) + return 0; + + snprintf(dirname, sizeof(dirname), "%s/%s", cxt->sysfs.dir_path, depname); + + while ((d = xreaddir(dir))) { + /* Is the dependency a partition? */ + if (sysfs_is_partition_dirent(dir, d, NULL)) { + if (!get_wholedisk_from_partition_dirent(dir, dirname, d, &dep)) + process_blkdev(&dep, cxt, 1, d->d_name); + } + /* The dependency is a whole device. */ + else if (!set_cxt(&dep, cxt, NULL, d->d_name)) + process_blkdev(&dep, cxt, 1, NULL); + + reset_blkdev_cxt(&dep); + } + closedir(dir); + + return 0; +} + +static int process_blkdev(struct blkdev_cxt *cxt, struct blkdev_cxt *parent, + int do_partitions, const char *part_name) +{ + if (do_partitions && cxt->npartitions) + return list_partitions(cxt, parent, part_name); + + print_device(cxt, parent ? parent->tt_line : NULL); + return list_deps(cxt); +} + +/* Iterate devices in sysfs */ +static int iterate_block_devices(void) +{ + DIR *dir; + struct dirent *d; + struct blkdev_cxt cxt = {}; + + if (!(dir = opendir(_PATH_SYS_BLOCK))) + return EXIT_FAILURE; + + while ((d = xreaddir(dir))) { + if (set_cxt(&cxt, NULL, NULL, d->d_name)) + goto next; + + if (is_maj_excluded(cxt.maj) || !is_maj_included(cxt.maj)) + goto next; + + /* Skip devices in the middle of dependency tree. */ + if ((lsblk->inverse ? cxt.nholders : cxt.nslaves) > 0) + goto next; + + process_blkdev(&cxt, NULL, 1, NULL); + next: + reset_blkdev_cxt(&cxt); + } + + closedir(dir); + + return EXIT_SUCCESS; +} + +static int process_one_device(char *devname) +{ + struct blkdev_cxt parent = {}, cxt = {}; + struct stat st; + char buf[PATH_MAX + 1], *diskname = NULL; + dev_t disk = 0; + int status = EXIT_FAILURE; + + if (stat(devname, &st) || !S_ISBLK(st.st_mode)) { + warnx(_("%s: not a block device"), devname); + return EXIT_FAILURE; + } + if (blkid_devno_to_wholedisk(st.st_rdev, buf, sizeof(buf), &disk)) { + warn(_("%s: failed to get whole-disk device number"), devname); + return EXIT_FAILURE; + } + if (st.st_rdev == disk) { + /* + * Device is not a partition. + */ + if (set_cxt(&cxt, NULL, NULL, buf)) + goto leave; + process_blkdev(&cxt, NULL, !lsblk->inverse, NULL); + } else { + /* + * Partition, read sysfs name of the device. + */ + ssize_t len; + char path[PATH_MAX], *name; + + if (!sysfs_devno_path(st.st_rdev, path, sizeof(path))) { + warn(_("failed to compose sysfs path for %s"), devname); + goto leave; + } + + diskname = xstrdup(buf); + len = readlink(path, buf, PATH_MAX); + if (len < 0) { + warn(_("%s: failed to read link"), path); + goto leave; + } + buf[len] = '\0'; + + /* sysfs device name */ + name = strrchr(buf, '/') + 1; + + if (set_cxt(&parent, NULL, NULL, diskname)) + goto leave; + if (set_cxt(&cxt, &parent, &parent, name)) + goto leave; + + if (lsblk->inverse) + process_blkdev(&parent, &cxt, 1, cxt.name); + else + process_blkdev(&cxt, &parent, 1, NULL); + } + + status = EXIT_SUCCESS; +leave: + free(diskname); + reset_blkdev_cxt(&cxt); + + if (st.st_rdev != disk) + reset_blkdev_cxt(&parent); + + return status; +} + +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; + } +} + +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; + } +} + +static void __attribute__((__noreturn__)) help(FILE *out) +{ + size_t i; + + fprintf(out, _( + "\nUsage:\n" + " %s [options] [<device> ...]\n"), program_invocation_short_name); + + fprintf(out, _( + "\nOptions:\n" + " -a, --all print all devices\n" + " -b, --bytes print SIZE in bytes rather than in human readable format\n" + " -d, --nodeps don't print slaves or holders\n" + " -D, --discard print discard capabilities\n" + " -e, --exclude <list> exclude devices by major number (default: RAM disks)\n" + " -I, --include <list> show only devices with specified major numbers\n" + " -f, --fs output info about filesystems\n" + " -h, --help usage information (this)\n" + " -i, --ascii use ascii characters only\n" + " -m, --perms output info about permissions\n" + " -l, --list use list format ouput\n" + " -n, --noheadings don't print headings\n" + " -o, --output <list> output columns\n" + " -P, --pairs use key=\"value\" output format\n" + " -r, --raw use raw output format\n" + " -s, --inverse inverse dependencies\n" + " -t, --topology output info about topology\n" + " -V, --version output version information and exit\n")); + + fprintf(out, _("\nAvailable columns (for --output):\n")); + + for (i = 0; i < NCOLS; i++) + fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help)); + + fprintf(out, USAGE_MAN_TAIL("lsblk(8)")); + + exit(out == stderr ? EXIT_FAILURE : 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; + int tt_flags = TT_FL_TREE; + int i, c, status = EXIT_FAILURE; + char *outarg = NULL; + + static const struct option longopts[] = { + { "all", 0, 0, 'a' }, + { "bytes", 0, 0, 'b' }, + { "nodeps", 0, 0, 'd' }, + { "discard", 0, 0, 'D' }, + { "help", 0, 0, 'h' }, + { "output", 1, 0, 'o' }, + { "perms", 0, 0, 'm' }, + { "noheadings", 0, 0, 'n' }, + { "list", 0, 0, 'l' }, + { "ascii", 0, 0, 'i' }, + { "raw", 0, 0, 'r' }, + { "inverse", 0, 0, 's' }, + { "fs", 0, 0, 'f' }, + { "exclude", 1, 0, 'e' }, + { "include", 1, 0, 'I' }, + { "topology", 0, 0, 't' }, + { "pairs", 0, 0, 'P' }, + { "version", 0, 0, 'V' }, + { NULL, 0, 0, 0 }, + }; + + static const ul_excl_t excl[] = { /* rows and cols in in ASCII order */ + { 'I','e' }, + { 'P','l','r' }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + lsblk = &_ls; + memset(lsblk, 0, sizeof(*lsblk)); + + while((c = getopt_long(argc, argv, + "abdDe:fhlnmo:PiI:rstV", 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': + columns[ncolumns++] = COL_NAME; + columns[ncolumns++] = COL_DALIGN; + columns[ncolumns++] = COL_DGRAN; + columns[ncolumns++] = COL_DMAX; + columns[ncolumns++] = COL_DZERO; + break; + case 'e': + parse_excludes(optarg); + break; + case 'h': + help(stdout); + break; + case 'l': + tt_flags &= ~TT_FL_TREE; /* disable the default */ + break; + case 'n': + tt_flags |= TT_FL_NOHEADINGS; + break; + case 'o': + outarg = optarg; + break; + case 'P': + tt_flags |= TT_FL_EXPORT; + tt_flags &= ~TT_FL_TREE; /* disable the default */ + break; + case 'i': + tt_flags |= TT_FL_ASCII; + break; + case 'I': + parse_includes(optarg); + break; + case 'r': + tt_flags &= ~TT_FL_TREE; /* disable the default */ + tt_flags |= TT_FL_RAW; /* enable raw */ + break; + case 's': + lsblk->inverse = 1; + break; + case 'f': + columns[ncolumns++] = COL_NAME; + columns[ncolumns++] = COL_FSTYPE; + columns[ncolumns++] = COL_LABEL; + columns[ncolumns++] = COL_UUID; + columns[ncolumns++] = COL_TARGET; + break; + case 'm': + columns[ncolumns++] = COL_NAME; + columns[ncolumns++] = COL_SIZE; + columns[ncolumns++] = COL_OWNER; + columns[ncolumns++] = COL_GROUP; + columns[ncolumns++] = COL_MODE; + break; + case 't': + columns[ncolumns++] = COL_NAME; + columns[ncolumns++] = COL_ALIOFF; + columns[ncolumns++] = COL_MINIO; + columns[ncolumns++] = COL_OPTIO; + columns[ncolumns++] = COL_PHYSEC; + columns[ncolumns++] = COL_LOGSEC; + columns[ncolumns++] = COL_ROTA; + columns[ncolumns++] = COL_SCHED; + columns[ncolumns++] = COL_RQ_SIZE; + columns[ncolumns++] = COL_RA; + break; + case 'V': + printf(_("%s from %s\n"), program_invocation_short_name, + PACKAGE_STRING); + return EXIT_SUCCESS; + default: + help(stderr); + } + } + + check_sysdevblock(); + + if (!ncolumns) { + columns[ncolumns++] = COL_NAME; + columns[ncolumns++] = COL_MAJMIN; + columns[ncolumns++] = COL_RM; + columns[ncolumns++] = COL_SIZE; + columns[ncolumns++] = COL_RO; + columns[ncolumns++] = COL_TYPE; + columns[ncolumns++] = COL_TARGET; + } + + if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns), + &ncolumns, column_name_to_id) < 0) + return EXIT_FAILURE; + + if (nexcludes == 0 && nincludes == 0) + excludes[nexcludes++] = 1; /* default: ignore RAM disks */ + + mnt_init_debug(0); + + /* + * initialize output columns + */ + if (!(lsblk->tt = tt_new_table(tt_flags))) + errx(EXIT_FAILURE, _("failed to initialize output table")); + + for (i = 0; i < ncolumns; i++) { + struct colinfo *ci = get_column_info(i); + int fl = ci->flags; + + if (!(tt_flags & TT_FL_TREE) && get_column_id(i) == COL_NAME) + fl &= ~TT_FL_TREE; + + if (!tt_define_column(lsblk->tt, ci->name, ci->whint, fl)) { + warn(_("failed to initialize output column")); + goto leave; + } + } + + if (optind == argc) + status = iterate_block_devices(); + else while (optind < argc) + status = process_one_device(argv[optind++]); + + tt_print_table(lsblk->tt); + +leave: + tt_free_table(lsblk->tt); + mnt_free_table(mtab); + mnt_free_table(swaps); + mnt_free_cache(mntcache); + return status; +} diff --git a/misc-utils/lslocks.8 b/misc-utils/lslocks.8 new file mode 100644 index 0000000..24cda14 --- /dev/null +++ b/misc-utils/lslocks.8 @@ -0,0 +1,82 @@ +.\" lslocks.8 -- +.\" Copyright 2012 Davidlohr Bueso <dave@gnu.org> +.\" May be distributed under the GNU General Public License + +.TH LSLOCKS 8 "February 2012" "util-linux" "System Administration" +.SH NAME +lslocks \- +list local system locks +.SH SYNOPSIS +.B lslocks +.RB [options] + +.SH DESCRIPTION +.B lslocks +lists information about all the file held locks in a Linux system. + +.SH OPTIONS +.IP "\fB\-p, \-\-pid\fP" +Specify the process id, if none is given, it will display all locks +.IP "\fB\-h, \-\-help\fP" +Print a help text and exit. +.IP "\fB\-o, \-\-output \fIlist\fP" +Specify which output columns to print. Use +.B "--help" +to get a list of all supported columns. +.IP "\fB\-n, \-\-noheadings\fP" +Do not print a header line. +.IP "\fB\-u, \-\-notruncate\fP" +Do not truncate text in columns. +.IP "\fB\-r, \-\-raw\fP" +Use the raw output format. + +.SH OUTPUT +.IP "COMMAND" +The command name of the process holding the lock. + +.IP "PID" +Process ID which holds the lock. + +.IP "TYPE" +Type of lock, can be FLOCK (created with flock(2)) or POSIX (created with fcntl(2) and lockf(2)) + +.IP "SIZE" +Size of the locked file. + +.IP "MODE" +Lock access permissions (read, write). + +.IP "M" +Mandatory state of the lock: 0 if none; 1 if set. (See chmod(1)). + +.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 no permissions to read the path it +will fallback to the device's mountpoint. The path might be truncated, use +.B "--notruncate" +to get the full path. + +.SH NOTES +.nf +The lslocks command is meant to replace the lslk(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 (2) + +.SH AVAILABILITY +The lslocks command is part of the util-linux package and is available from +ftp://ftp.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..45fb6de --- /dev/null +++ b/misc-utils/lslocks.c @@ -0,0 +1,574 @@ +/* + * 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 maingained 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 "pathnames.h" +#include "canonicalize.h" +#include "nls.h" +#include "tt.h" +#include "xalloc.h" +#include "at.h" +#include "strutils.h" +#include "c.h" +#include "closestream.h" + +/* column IDs */ +enum { + COL_SRC = 0, + COL_PID, + COL_TYPE, + COL_SIZE, + COL_MODE, + COL_M, + COL_START, + COL_END, + COL_PATH, +}; + +/* column names */ +struct colinfo { + const char *name; /* header */ + double whint; /* width hint (N < 1 is in percent of termwidth) */ + int flags; /* TT_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, TT_FL_RIGHT, N_("PID of the process holding the lock") }, + [COL_TYPE] = { "TYPE", 5, TT_FL_RIGHT, N_("kind of lock: FL_FLOCK or FL_POSIX.") }, + [COL_SIZE] = { "SIZE", 4, TT_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, TT_FL_RIGHT, N_("relative byte offset of the lock")}, + [COL_END] = { "END", 10, TT_FL_RIGHT, N_("ending offset of the lock")}, + [COL_PATH] = { "PATH", 0, TT_FL_TRUNC, N_("path of the locked file")}, +}; +#define NCOLS ARRAY_SIZE(infos) +static int columns[NCOLS], ncolumns; +static pid_t pid = 0; + +struct lock { + struct list_head locks; + + char *cmdname; + pid_t pid; + char *path; + char *type; + char *mode; + off_t start; + off_t end; + int mandatory; + char *size; +}; + +static void disable_columns_truncate(void) +{ + size_t i; + + for (i = 0; i < NCOLS; i++) + infos[i].flags &= ~TT_FL_TRUNC; +} + +/* + * Return a PID's command name + */ +static char *get_cmdname(pid_t id) +{ + FILE *fp; + char path[PATH_MAX], *ret = NULL; + + sprintf(path, "/proc/%d/comm", id); + if (!(fp = fopen(path, "r"))) + return NULL; + + if (!fgets(path, sizeof(path), fp)) + goto out; + + path[strlen(path) - 1] = '\0'; + ret = xstrdup(path); +out: + fclose(fp); + return ret; +} + +/* + * Associate the device's mountpoint for a filename + */ +static char *get_fallback_filename(dev_t dev) +{ + char buf[BUFSIZ], target[PATH_MAX], *ret = NULL; + int maj, min; + FILE *fp; + + if (!(fp = fopen(_PATH_PROC_MOUNTINFO, "r"))) + return NULL; + + while (fgets(buf, sizeof(buf), fp)) { + sscanf(buf, "%*u %*u %u:%u %*s %s", + &maj, &min, target); + + if (dev == makedev(maj, min)) { + ret = xstrdup(target); + goto out; + + } + } +out: + fclose(fp); + return ret; +} + +/* + * 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 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/", 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 (!fstat_at(fd, path, dp->d_name, &sb, 0) + && inode != sb.st_ino) + continue; + + if ((len = readlink_at(fd, path, dp->d_name, + sym, sizeof(path))) < 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) +{ + int maj = 0, min = 0; + ino_t inum = 0; + + sscanf(str, "%02x:%02x:%lu", &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], *szstr = NULL, *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: /* ignore */ + break; + case 1: /* posix, flock, etc */ + l->type = xstrdup(tok); + break; + + case 2: /* is this a mandatory lock? other values are advisory or noinode */ + l->mandatory = *tok == 'M' ? TRUE : FALSE; + 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. + */ + l->pid = strtos32_or_err(tok, _("failed to parse pid")); + l->cmdname = get_cmdname(l->pid); + if (!l->cmdname) + l->cmdname = xstrdup(_("(unknown)")); + 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); + if (!l->path) + /* probably no permission to peek into l->pid's path */ + l->path = get_fallback_filename(dev); + + /* avoid leaking */ + szstr = size_to_human_string(SIZE_SUFFIX_1LETTER, sz); + l->size = xstrdup(szstr); + free(szstr); + } + + if (pid && pid != l->pid) { + /* + * It's easier to just parse the file then decide if + * it should be added to the list - otherwise just + * get rid of stored data + */ + free(l->path); + free(l->size); + free(l->mode); + free(l->cmdname); + free(l->type); + free(l); + + continue; + } + + 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 < NCOLS; 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(ARRAY_SIZE(columns) == NCOLS); + assert(num < ncolumns); + assert(columns[num] < (int) NCOLS); + + return columns[num]; +} + + +static inline struct colinfo *get_column_info(unsigned num) +{ + return &infos[ get_column_id(num) ]; +} + +static void rem_lock(struct lock *lock) +{ + if (!lock) + return; + + free(lock->path); + free(lock->size); + free(lock->mode); + free(lock->cmdname); + free(lock->type); + list_del(&lock->locks); + free(lock); +} + +static void add_tt_line(struct tt *tt, struct lock *l) +{ + int i; + struct tt_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(tt); + + line = tt_add_line(tt, NULL); + if (!line) { + warn(_("failed to add line to output")); + return; + } + + for (i = 0; i < ncolumns; i++) { + char *str = NULL; + int rc = 0; + + switch (get_column_id(i)) { + case COL_SRC: + rc = xasprintf(&str, "%s", l->cmdname ? l->cmdname : notfnd); + break; + case COL_PID: + rc = xasprintf(&str, "%d", l->pid); + break; + case COL_TYPE: + rc = xasprintf(&str, "%s", l->type); + break; + case COL_SIZE: + rc = xasprintf(&str, "%s", l->size); + break; + case COL_MODE: + rc = xasprintf(&str, "%s", l->mode); + break; + case COL_M: + rc = xasprintf(&str, "%d", l->mandatory); + break; + case COL_START: + rc = xasprintf(&str, "%ld", l->start); + break; + case COL_END: + rc = xasprintf(&str, "%ld", l->end); + break; + case COL_PATH: + rc = xasprintf(&str, "%s", l->path ? l->path : notfnd); + break; + default: + break; + } + + if (rc || str) + tt_line_set_data(line, i, str); + } +} + +static int show_locks(struct list_head *locks, int tt_flags) +{ + int i, rc = 0; + struct list_head *p, *pnext; + struct tt *tt; + + tt = tt_new_table(tt_flags); + if (!tt) { + warn(_("failed to initialize output table")); + return -1; + } + + for (i = 0; i < ncolumns; i++) { + struct colinfo *col = get_column_info(i); + + if (!tt_define_column(tt, col->name, col->whint, col->flags)) { + warnx(_("failed to initialize output column")); + rc = -1; + goto done; + } + } + + list_for_each_safe(p, pnext, locks) { + struct lock *lock = list_entry(p, struct lock, locks); + add_tt_line(tt, lock); + rem_lock(lock); + } + + tt_print_table(tt); +done: + tt_free_table(tt); + return rc; +} + + +static void __attribute__ ((__noreturn__)) usage(FILE * out) +{ + size_t i; + + fputs(USAGE_HEADER, out); + + fprintf(out, + _(" %s [options]\n"), program_invocation_short_name); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -p, --pid <pid> process id\n" + " -o, --output <list> define which output columns to use\n" + " -n, --noheadings don't print headings\n" + " -r --raw use the raw output format\n" + " -u, --notruncate don't truncate text in columns\n" + " -h, --help display this help and exit\n" + " -V, --version output version information and exit\n"), out); + + fputs(_("\nAvailable columns (for --output):\n"), out); + + for (i = 0; i < NCOLS; i++) + fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help)); + + fprintf(out, USAGE_MAN_TAIL("lslocks(8)")); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + int c, tt_flags = 0, rc = 0; + struct list_head locks; + static const struct option long_opts[] = { + { "pid", required_argument, NULL, 'p' }, + { "help", no_argument, NULL, 'h' }, + { "output", required_argument, NULL, 'o' }, + { "notruncate", no_argument, NULL, 'u' }, + { "version", no_argument, NULL, 'V' }, + { "noheadings", no_argument, NULL, 'n' }, + { "raw", no_argument, NULL, 'r' }, + { NULL, 0, NULL, 0 } + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((c = getopt_long(argc, argv, + "p:o:nruhV", long_opts, NULL)) != -1) { + + switch(c) { + case 'p': + pid = strtos32_or_err(optarg, _("invalid PID argument")); + break; + case 'o': + ncolumns = string_to_idarray(optarg, + columns, ARRAY_SIZE(columns), + column_name_to_id); + if (ncolumns < 0) + return EXIT_FAILURE; + break; + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case 'h': + usage(stdout); + case 'n': + tt_flags |= TT_FL_NOHEADINGS; + break; + case 'r': + tt_flags |= TT_FL_RAW; + break; + case 'u': + disable_columns_truncate(); + break; + case '?': + default: + usage(stderr); + } + } + + 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; + } + + rc = get_local_locks(&locks); + + if (!rc && !list_empty(&locks)) + rc = show_locks(&locks, tt_flags); + + return rc; +} diff --git a/misc-utils/mcookie.1 b/misc-utils/mcookie.1 new file mode 100644 index 0000000..e7579c4 --- /dev/null +++ b/misc-utils/mcookie.1 @@ -0,0 +1,65 @@ +.\" mcookie.1 -- +.\" Public Domain 1995 Rickard E. Faith (faith@cs.unc.edu) +.TH MCOOKIE 1 "June 2011" "util-linux" "User Commands" +.SH NAME +mcookie \- generate magic cookies for xauth +.SH SYNOPSIS +.B mcookie +[\fIoptions\fR] +.SH DESCRIPTION +.B mcookie +generates a 128-bit random hexadecimal number for use with the X authority +system. Typical usage: +.RS +xauth add :0 . `mcookie` +.RE +.PP +The "random" number generated is actually the output of the MD5 message +digest fed with various pieces of random information: the current time, the +process id, the parent process id, and optionally the contents of an input +file. and several bytes of information from the first of the following +devices which is present: +.IR /dev/random , +.IR /dev/urandom , +files in +.IR /proc , +.IR /dev/audio . +.SH OPTIONS +.TP +\fB\-f\fR, \fB\-\-file\fR=\fIFILE\fR +Use file as a macig cookie seed. When file is defined as `-' character +input is read from stdin. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +Explain what is being done. +.TP +\fB\-V\fR, \fB\-\-version\fR +Output version information and exit. +.TP +\fB\-h\fR, \fB\-\-help\fR +Display this help and exit. +.SH BUGS +The entropy in the generated 128-bit is probably quite small (and, +therefore, vulnerable to attack) unless a non-pseudorandom number generator +is used (e.g., +.I /dev/random +under Linux). +.PP +It is assumed that none of the devices opened will block. +.SH FILES +.I /dev/random +.br +.I /dev/urandom +.br +.I /dev/audio +.br +.I /proc/stat +.br +.I /proc/loadavg +.SH "SEE ALSO" +.BR X (1), +.BR xauth (1), +.BR md5sum (1) +.SH AVAILABILITY +The mcookie command is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. diff --git a/misc-utils/mcookie.c b/misc-utils/mcookie.c new file mode 100644 index 0000000..b7b1762 --- /dev/null +++ b/misc-utils/mcookie.c @@ -0,0 +1,190 @@ +/* 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 <fcntl.h> +#include <getopt.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/time.h> +#include <unistd.h> + +#define BUFFERSIZE 4096 + +struct rngs { + const char *path; + int minlength, maxlength; +} rngs[] = { + {"/dev/random", 16, 16}, /* 16 bytes = 128 bits suffice */ + {"/proc/interrupts", 0, 0}, + {"/proc/slabinfo", 0, 0}, + {"/proc/stat", 0, 0}, + {"/dev/urandom", 32, 64}, +}; + +#define RNGS (sizeof(rngs)/sizeof(struct rngs)) + +/* The basic function to hash a file */ +static off_t hash_file(struct MD5Context *ctx, int fd) +{ + off_t count = 0; + ssize_t r; + unsigned char buf[BUFFERSIZE]; + + while ((r = read(fd, buf, sizeof(buf))) > 0) { + MD5Update(ctx, buf, r); + count += r; + } + /* Separate files with a null byte */ + buf[0] = '\0'; + MD5Update(ctx, buf, 1); + return count; +} + +static void __attribute__ ((__noreturn__)) usage(FILE * out) +{ + fputs(_("\nUsage:\n"), out); + fprintf(out, + _(" %s [options]\n"), program_invocation_short_name); + + fputs(_("\nOptions:\n"), out); + fputs(_(" -f, --file <file> use file as a cookie seed\n" + " -v, --verbose explain what is being done\n" + " -V, --version output version information and exit\n" + " -h, --help display this help and exit\n\n"), out); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + size_t i; + struct MD5Context ctx; + unsigned char digest[MD5LENGTH]; + unsigned char buf[BUFFERSIZE]; + int fd; + int c; + pid_t pid; + char *file = NULL; + int verbose = 0; + int r; + struct timeval tv; + struct timezone tz; + + static const struct option longopts[] = { + {"file", required_argument, NULL, 'f'}, + {"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); + atexit(close_stdout); + + while ((c = + getopt_long(argc, argv, "f:vVh", longopts, NULL)) != -1) + switch (c) { + case 'v': + verbose = 1; + break; + case 'f': + file = optarg; + break; + case 'V': + printf(_("%s from %s\n"), + program_invocation_short_name, + PACKAGE_STRING); + return EXIT_SUCCESS; + case 'h': + usage(stdout); + default: + usage(stderr); + } + + MD5Init(&ctx); + gettimeofday(&tv, &tz); + MD5Update(&ctx, (unsigned char *) &tv, sizeof(tv)); + + pid = getppid(); + MD5Update(&ctx, (unsigned char *) &pid, sizeof(pid)); + pid = getpid(); + MD5Update(&ctx, (unsigned char *) &pid, sizeof(pid)); + + if (file) { + int count = 0; + + if (file[0] == '-' && !file[1]) + fd = STDIN_FILENO; + else + fd = open(file, O_RDONLY); + + if (fd < 0) { + warn(_("cannot open %s"), file); + } else { + count = hash_file(&ctx, fd); + if (verbose) + fprintf(stderr, + _("Got %d bytes from %s\n"), count, + file); + + if (fd != STDIN_FILENO) + if (close(fd)) + err(EXIT_FAILURE, + _("closing %s failed"), file); + } + } + + for (i = 0; i < RNGS; i++) { + if ((fd = open(rngs[i].path, O_RDONLY | O_NONBLOCK)) >= 0) { + int count = sizeof(buf); + + if (rngs[i].maxlength && count > rngs[i].maxlength) + count = rngs[i].maxlength; + r = read(fd, buf, count); + if (r > 0) + MD5Update(&ctx, buf, r); + else + r = 0; + close(fd); + if (verbose) + fprintf(stderr, + _("Got %d bytes from %s\n"), r, + rngs[i].path); + if (rngs[i].minlength && r >= rngs[i].minlength) + break; + } else if (verbose) + warn(_("cannot open %s"), rngs[i].path); + } + + MD5Final(digest, &ctx); + for (i = 0; i < 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..88e249d --- /dev/null +++ b/misc-utils/namei.1 @@ -0,0 +1,74 @@ +.\" +.\" Version 1.4 of namei +.\" +.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 +.RI [ options ] +.IR pathname ... +.SH DESCRIPTION +.B namei +uses 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 +.IP "\fB\-l, \-\-long\fP" +Use the long listing format (same as -m -o -v). +.IP "\fB\-m, \-\-modes\fP" +Show the mode bits of each file type in the style of ls(1), +for example 'rwxr-xr-x'. +.IP "\fB\-o, \-\-owners\fP" +Show owner and group name of each file. +.IP "\fB\-n, \-\-nosymlinks\fP" +Don't follow symlinks. +.IP "\fB\-v, \-\-vertical\fP" +Vertically align the modes and owners. +.IP "\fB\-x, \-\-mountpoints\fP" +Show mountpoint directories with a 'D' rather than a 'd'. +.IP "\fB\-h\fR, \fB\-\-help\fR" +Output help text and exit. +.IP "\fB\-V\fR, \fB\-\-version\fR" +Output version information and exit. +.SH AUTHOR +The original +.B namei +program was written by Roger Southwick <rogers@amadeus.wr.tek.com>. + +The program was re-written by Karel Zak <kzak@redhat.com>. +.SH BUGS +To be discovered. +.SH "SEE ALSO" +.BR ls (1), +.BR stat (1) +.SH AVAILABILITY +The namei command is part of the util-linux package and is available from +ftp://ftp.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..376a2f0 --- /dev/null +++ b/misc-utils/namei.c @@ -0,0 +1,528 @@ +/* + * 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 writtent by: + * Roger S. Southwick (May 2, 1990) + * Steve Tell (March 28, 1991) + * Arkadiusz Mikiewicz (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" + +#ifndef MAXSYMLINKS +#define MAXSYMLINKS 256 +#endif + +#ifndef LOGIN_NAME_MAX +#define LOGIN_NAME_MAX 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; /* is this item not existing */ +}; + +struct idcache { + unsigned long int id; + char *name; + struct idcache *next; +}; + +static int flags; +static int uwidth; /* maximal width of username */ +static int gwidth; /* maximal width of groupname */ +static struct idcache *gcache; /* groupnames */ +static struct idcache *ucache; /* usernames */ + +static struct idcache * +get_id(struct idcache *ic, unsigned long int id) +{ + while(ic) { + if (ic->id == id) + return ic; + ic = ic->next; + } + return NULL; +} + +static void +free_idcache(struct idcache *ic) +{ + while(ic) { + struct idcache *next = ic->next; + free(ic->name); + free(ic); + ic = next; + } +} + +static void +add_id(struct idcache **ic, char *name, unsigned long int id, int *width) +{ + struct idcache *nc, *x; + int w = 0; + + nc = xcalloc(1, sizeof(*nc)); + nc->id = id; + + if (name) { +#ifdef HAVE_WIDECHAR + wchar_t wc[LOGIN_NAME_MAX + 1]; + + if (mbstowcs(wc, name, LOGIN_NAME_MAX) > 0) { + wc[LOGIN_NAME_MAX] = '\0'; + w = wcswidth(wc, LOGIN_NAME_MAX); + } + else +#endif + w = strlen(name); + } + /* note, we ignore names with non-printable widechars */ + if (w > 0) + nc->name = xstrdup(name); + else if (xasprintf(&nc->name, "%lu", id) == -1) + nc->name = NULL; + + for (x = *ic; x && x->next; x = x->next); + + /* add 'nc' at end of the 'ic' list */ + if (x) + x->next = nc; + else + *ic = nc; + if (w <= 0) + w = nc->name ? strlen(nc->name) : 0; + + *width = *width < w ? w : *width; + return; +} + +static void +add_uid(unsigned long int id) +{ + struct idcache *ic = get_id(ucache, id); + + if (!ic) { + struct passwd *pw = getpwuid((uid_t) id); + add_id(&ucache, pw ? pw->pw_name : NULL, id, &uwidth); + } +} + +static void +add_gid(unsigned long int id) +{ + struct idcache *ic = get_id(gcache, id); + + if (!ic) { + struct group *gr = getgrgid((gid_t) id); + add_id(&gcache, gr ? gr->gr_name : NULL, id, &gwidth); + } +} + +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 ? p - path : 0; + 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 failed %s"), 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); + + nm->noent = (lstat(path, &nm->st) == -1); + if (nm->noent) + return nm; + + if (S_ISLNK(nm->st.st_mode)) + readlink_to_namei(nm, path); + if (flags & NAMEI_OWNERS) { + add_uid(nm->st.st_uid); + add_gid(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) { + printf(_("%s - No such file or directory\n"), nm->name); + return -1; + } + + strmode(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", uwidth, + get_id(ucache, nm->st.st_uid)->name); + printf(" %-*s", gwidth, + 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 usage(int rc) +{ + const char *p = program_invocation_short_name; + FILE *out = rc == EXIT_FAILURE ? stderr : stdout; + + if (!*p) + p = "namei"; + + fputs(_("\nUsage:\n"), out); + fprintf(out, + _(" %s [options] pathname [pathname ...]\n"), p); + + fputs(_("\nOptions:\n"), out); + fputs(_(" -h, --help displays this help text\n" + " -V, --version output version information and exit\n" + " -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); + + fputs(_("\nFor more information see namei(1).\n"), out); + exit(rc); +} + +static const struct option longopts[] = +{ + { "help", 0, 0, 'h' }, + { "version", 0, 0, 'V' }, + { "mountpoints",0, 0, 'x' }, + { "modes", 0, 0, 'm' }, + { "owners", 0, 0, 'o' }, + { "long", 0, 0, 'l' }, + { "nolinks", 0, 0, 'n' }, + { "vertical", 0, 0, 'v' }, + { NULL, 0, 0, 0 }, +}; + +int +main(int argc, char **argv) +{ + int c; + int rc = EXIT_SUCCESS; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((c = getopt_long(argc, argv, "hVlmnovx", longopts, NULL)) != -1) { + switch(c) { + case 'h': + usage(EXIT_SUCCESS); + break; + case 'V': + printf(_("%s from %s\n"), program_invocation_short_name, + PACKAGE_STRING); + return EXIT_SUCCESS; + 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; + default: + usage(EXIT_FAILURE); + } + } + + if (optind == argc) { + warnx(_("pathname argument is missing")); + usage(EXIT_FAILURE); + } + + 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/procs.c b/misc-utils/procs.c new file mode 100644 index 0000000..e76c390 --- /dev/null +++ b/misc-utils/procs.c @@ -0,0 +1,126 @@ +/* + * procs.c -- functions to parse the linux /proc filesystem. + * (c) 1994 salvatore valente <svalente@mit.edu> + * + * this program is free software. you can redistribute it and + * modify it under the terms of the gnu general public license. + * there is no warranty. + * + * faith + * 1.2 + * 1995/02/23 01:20:40 + * + */ + +#define _POSIX_SOURCE 1 + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> +#include <ctype.h> +#include <unistd.h> +#include "kill.h" +#include "xalloc.h" + +extern char *mybasename (char *); +static char *parse_parens (char *buf); + +int * +get_pids (char *process_name, int get_all) { + DIR *dir; + struct dirent *ent; + int status; + char *dname, fname[100], *cp, buf[256]; + struct stat st; + uid_t uid; + FILE *fp; + int pid, *pids, num_pids, pids_size; + + dir = opendir ("/proc"); + if (! dir) { + perror ("opendir /proc"); + return NULL; + } + uid = getuid (); + pids = NULL; + num_pids = pids_size = 0; + + while ((ent = readdir (dir)) != NULL) { + dname = ent->d_name; + if (! isdigit (*dname)) continue; + pid = atoi (dname); + sprintf (fname, "/proc/%d/cmdline", pid); + /* get the process owner */ + status = stat (fname, &st); + if (status != 0) continue; + if (! get_all && uid != st.st_uid) continue; + /* get the command line */ + fp = fopen (fname, "r"); + if (! fp) continue; + cp = fgets (buf, sizeof (buf), fp); + fclose (fp); + /* an empty command line means the process is swapped out */ + if (! cp || ! *cp) { + /* get the process name from the statfile */ + sprintf (fname, "/proc/%d/stat", pid); + fp = fopen (fname, "r"); + if (! fp) continue; + cp = fgets (buf, sizeof (buf), fp); + fclose (fp); + if (cp == NULL) continue; + cp = parse_parens (buf); + if (cp == NULL) continue; + } + /* ok, we got the process name. */ + if (strcmp (process_name, mybasename (cp))) continue; + while (pids_size < num_pids + 2) { + pids_size += 5; + pids = (int *) xrealloc (pids, sizeof (int) * pids_size); + } + if (pids) { + pids[num_pids++] = pid; + pids[num_pids] = -1; + } + } + closedir (dir); + return pids; +} + +/* + * parse_parens () -- return an index just past the first open paren in + * buf, and terminate the string at the matching close paren. + */ +static char *parse_parens (char *buf) +{ + char *cp, *ip; + int depth; + + cp = strchr (buf, '('); + if (cp == NULL) return NULL; + cp++; + depth = 1; + for (ip = cp; *ip; ip++) { + if (*ip == '(') + depth++; + if (*ip == ')') { + depth--; + if (depth == 0) { + *ip = 0; + break; + } + } + } + return cp; +} + +char *mybasename (char *path) +{ + char *cp; + + cp = strrchr (path, '/'); + return (cp ? cp + 1 : path); +} + diff --git a/misc-utils/rename.1 b/misc-utils/rename.1 new file mode 100644 index 0000000..5f86b23 --- /dev/null +++ b/misc-utils/rename.1 @@ -0,0 +1,60 @@ +.\" 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 +.RI [ options ] " 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 +\fB\-v\fR, \fB\-\-verbose\fR +Give visual feedback which files where renamed, if any. +.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 EXAMPLES +Given the files +.IR foo1 ", ..., " foo9 ", " foo10 ", ..., " foo278 , +the commands +.RS +.PP +.nf +rename foo foo0 foo? +rename foo foo0 foo?? +.fi +.PP +.RE +will turn them into +.IR foo001 ", ..., " foo009 ", " foo010 ", ..., " foo278 . +And +.RS +.PP +.nf +rename .htm .html *.htm +.fi +.PP +.RE +will fix the extension of your html files. +.SH WARNING +The renaming has no safeguards. 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 "SEE ALSO" +.BR mmv (1), +.BR mv (1) +.SH AVAILABILITY +The rename command is part of the util-linux package and is available from +ftp://ftp.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..b17e03b --- /dev/null +++ b/misc-utils/rename.c @@ -0,0 +1,126 @@ +/* + * 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> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <getopt.h> + +#include "nls.h" +#include "xalloc.h" +#include "c.h" +#include "closestream.h" + +static int do_rename(char *from, char *to, char *s, int verbose) +{ + char *newname, *where, *p, *q; + int flen, tlen, slen; + + where = strstr(s, from); + if (where == NULL) + return 0; + + flen = strlen(from); + tlen = strlen(to); + slen = strlen(s); + newname = xmalloc(tlen + slen + 1); + + p = s; + q = newname; + while (p < where) + *q++ = *p++; + p = to; + while (*p) + *q++ = *p++; + p = where + flen; + while (*p) + *q++ = *p++; + *q = 0; + + if (rename(s, newname) != 0) + err(EXIT_FAILURE, _("renaming %s to %s failed"), + s, newname); + if (verbose) + printf("`%s' -> `%s'\n", s, newname); + + free(newname); + return 1; +} + +static void __attribute__ ((__noreturn__)) usage(FILE * out) +{ + fputs(_("\nUsage:\n"), out); + fprintf(out, + _(" %s [options] expression replacement file...\n"), + program_invocation_short_name); + + fputs(_("\nOptions:\n"), out); + fputs(_(" -v, --verbose explain what is being done\n" + " -V, --version output version information and exit\n" + " -h, --help display this help and exit\n\n"), out); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + char *from, *to; + int i, c, verbose = 0; + + static const struct option longopts[] = { + {"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); + atexit(close_stdout); + + while ((c = getopt_long(argc, argv, "vVh", longopts, NULL)) != -1) + switch (c) { + case 'v': + verbose = 1; + break; + case 'V': + printf(_("%s from %s\n"), + program_invocation_short_name, + PACKAGE_STRING); + return EXIT_SUCCESS; + case 'h': + usage(stdout); + default: + usage(stderr); + } + + argc -= optind; + argv += optind; + + if (argc < 3) { + warnx("not enough arguments"); + usage(stderr); + } + + from = argv[0]; + to = argv[1]; + + for (i = 2; i < argc; i++) + do_rename(from, to, argv[i], verbose); + + return EXIT_SUCCESS; +} diff --git a/misc-utils/sd-daemon.c b/misc-utils/sd-daemon.c new file mode 100644 index 0000000..763e079 --- /dev/null +++ b/misc-utils/sd-daemon.c @@ -0,0 +1,530 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + Copyright 2010 Lennart Poettering + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#ifdef __BIONIC__ +#include <linux/fcntl.h> +#else +#include <sys/fcntl.h> +#endif +#include <netinet/in.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <stdarg.h> +#include <stdio.h> +#include <stddef.h> +#include <limits.h> + +#if defined(__linux__) +#include <mqueue.h> +#endif + +#include "sd-daemon.h" + +#if (__GNUC__ >= 4) +#ifdef SD_EXPORT_SYMBOLS +/* Export symbols */ +#define _sd_export_ __attribute__ ((visibility("default"))) +#else +/* Don't export the symbols */ +#define _sd_export_ __attribute__ ((visibility("hidden"))) +#endif +#else +#define _sd_export_ +#endif + +_sd_export_ int sd_listen_fds(int unset_environment) { + +#if defined(DISABLE_SYSTEMD) || !defined(__linux__) + return 0; +#else + int r, fd; + const char *e; + char *p = NULL; + unsigned long l; + + if (!(e = getenv("LISTEN_PID"))) { + r = 0; + goto finish; + } + + errno = 0; + l = strtoul(e, &p, 10); + + if (errno != 0) { + r = -errno; + goto finish; + } + + if (!p || *p || l <= 0) { + r = -EINVAL; + goto finish; + } + + /* Is this for us? */ + if (getpid() != (pid_t) l) { + r = 0; + goto finish; + } + + if (!(e = getenv("LISTEN_FDS"))) { + r = 0; + goto finish; + } + + errno = 0; + l = strtoul(e, &p, 10); + + if (errno != 0) { + r = -errno; + goto finish; + } + + if (!p || *p) { + r = -EINVAL; + goto finish; + } + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + (int) l; fd ++) { + int flags; + + if ((flags = fcntl(fd, F_GETFD)) < 0) { + r = -errno; + goto finish; + } + + if (flags & FD_CLOEXEC) + continue; + + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) { + r = -errno; + goto finish; + } + } + + r = (int) l; + +finish: + if (unset_environment) { + unsetenv("LISTEN_PID"); + unsetenv("LISTEN_FDS"); + } + + return r; +#endif +} + +_sd_export_ int sd_is_fifo(int fd, const char *path) { + struct stat st_fd; + + if (fd < 0) + return -EINVAL; + + memset(&st_fd, 0, sizeof(st_fd)); + if (fstat(fd, &st_fd) < 0) + return -errno; + + if (!S_ISFIFO(st_fd.st_mode)) + return 0; + + if (path) { + struct stat st_path; + + memset(&st_path, 0, sizeof(st_path)); + if (stat(path, &st_path) < 0) { + + if (errno == ENOENT || errno == ENOTDIR) + return 0; + + return -errno; + } + + return + st_path.st_dev == st_fd.st_dev && + st_path.st_ino == st_fd.st_ino; + } + + return 1; +} + +_sd_export_ int sd_is_special(int fd, const char *path) { + struct stat st_fd; + + if (fd < 0) + return -EINVAL; + + if (fstat(fd, &st_fd) < 0) + return -errno; + + if (!S_ISREG(st_fd.st_mode) && !S_ISCHR(st_fd.st_mode)) + return 0; + + if (path) { + struct stat st_path; + + if (stat(path, &st_path) < 0) { + + if (errno == ENOENT || errno == ENOTDIR) + return 0; + + return -errno; + } + + if (S_ISREG(st_fd.st_mode) && S_ISREG(st_path.st_mode)) + return + st_path.st_dev == st_fd.st_dev && + st_path.st_ino == st_fd.st_ino; + else if (S_ISCHR(st_fd.st_mode) && S_ISCHR(st_path.st_mode)) + return st_path.st_rdev == st_fd.st_rdev; + else + return 0; + } + + return 1; +} + +static int sd_is_socket_internal(int fd, int type, int listening) { + struct stat st_fd; + + if (fd < 0 || type < 0) + return -EINVAL; + + if (fstat(fd, &st_fd) < 0) + return -errno; + + if (!S_ISSOCK(st_fd.st_mode)) + return 0; + + if (type != 0) { + int other_type = 0; + socklen_t l = sizeof(other_type); + + if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0) + return -errno; + + if (l != sizeof(other_type)) + return -EINVAL; + + if (other_type != type) + return 0; + } + + if (listening >= 0) { + int accepting = 0; + socklen_t l = sizeof(accepting); + + if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0) + return -errno; + + if (l != sizeof(accepting)) + return -EINVAL; + + if (!accepting != !listening) + return 0; + } + + return 1; +} + +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in in4; + struct sockaddr_in6 in6; + struct sockaddr_un un; + struct sockaddr_storage storage; +}; + +_sd_export_ int sd_is_socket(int fd, int family, int type, int listening) { + int r; + + if (family < 0) + return -EINVAL; + + if ((r = sd_is_socket_internal(fd, type, listening)) <= 0) + return r; + + if (family > 0) { + union sockaddr_union sockaddr; + socklen_t l; + + memset(&sockaddr, 0, sizeof(sockaddr)); + l = sizeof(sockaddr); + + if (getsockname(fd, &sockaddr.sa, &l) < 0) + return -errno; + + if (l < sizeof(sa_family_t)) + return -EINVAL; + + return sockaddr.sa.sa_family == family; + } + + return 1; +} + +_sd_export_ int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) { + union sockaddr_union sockaddr; + socklen_t l; + int r; + + if (family != 0 && family != AF_INET && family != AF_INET6) + return -EINVAL; + + if ((r = sd_is_socket_internal(fd, type, listening)) <= 0) + return r; + + memset(&sockaddr, 0, sizeof(sockaddr)); + l = sizeof(sockaddr); + + if (getsockname(fd, &sockaddr.sa, &l) < 0) + return -errno; + + if (l < sizeof(sa_family_t)) + return -EINVAL; + + if (sockaddr.sa.sa_family != AF_INET && + sockaddr.sa.sa_family != AF_INET6) + return 0; + + if (family > 0) + if (sockaddr.sa.sa_family != family) + return 0; + + if (port > 0) { + if (sockaddr.sa.sa_family == AF_INET) { + if (l < sizeof(struct sockaddr_in)) + return -EINVAL; + + return htons(port) == sockaddr.in4.sin_port; + } else { + if (l < sizeof(struct sockaddr_in6)) + return -EINVAL; + + return htons(port) == sockaddr.in6.sin6_port; + } + } + + return 1; +} + +_sd_export_ int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) { + union sockaddr_union sockaddr; + socklen_t l; + int r; + + if ((r = sd_is_socket_internal(fd, type, listening)) <= 0) + return r; + + memset(&sockaddr, 0, sizeof(sockaddr)); + l = sizeof(sockaddr); + + if (getsockname(fd, &sockaddr.sa, &l) < 0) + return -errno; + + if (l < sizeof(sa_family_t)) + return -EINVAL; + + if (sockaddr.sa.sa_family != AF_UNIX) + return 0; + + if (path) { + if (length <= 0) + length = strlen(path); + + if (length <= 0) + /* Unnamed socket */ + return l == offsetof(struct sockaddr_un, sun_path); + + if (path[0]) + /* Normal path socket */ + return + (l >= offsetof(struct sockaddr_un, sun_path) + length + 1) && + memcmp(path, sockaddr.un.sun_path, length+1) == 0; + else + /* Abstract namespace socket */ + return + (l == offsetof(struct sockaddr_un, sun_path) + length) && + memcmp(path, sockaddr.un.sun_path, length) == 0; + } + + return 1; +} + +_sd_export_ int sd_is_mq(int fd, const char *path) { +#if !defined(__linux__) + return 0; +#else + struct mq_attr attr; + + if (fd < 0) + return -EINVAL; + + if (mq_getattr(fd, &attr) < 0) + return -errno; + + if (path) { + char fpath[PATH_MAX]; + struct stat a, b; + + if (path[0] != '/') + return -EINVAL; + + if (fstat(fd, &a) < 0) + return -errno; + + strncpy(stpcpy(fpath, "/dev/mqueue"), path, sizeof(fpath) - 12); + fpath[sizeof(fpath)-1] = 0; + + if (stat(fpath, &b) < 0) + return -errno; + + if (a.st_dev != b.st_dev || + a.st_ino != b.st_ino) + return 0; + } + + return 1; +#endif +} + +_sd_export_ int sd_notify(int unset_environment, const char *state) { +#if defined(DISABLE_SYSTEMD) || !defined(__linux__) || !defined(SOCK_CLOEXEC) + return 0; +#else + int fd = -1, r; + struct msghdr msghdr; + struct iovec iovec; + union sockaddr_union sockaddr; + const char *e; + + if (!state) { + r = -EINVAL; + goto finish; + } + + if (!(e = getenv("NOTIFY_SOCKET"))) + return 0; + + /* Must be an abstract socket, or an absolute path */ + if ((e[0] != '@' && e[0] != '/') || e[1] == 0) { + r = -EINVAL; + goto finish; + } + + if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) { + r = -errno; + goto finish; + } + + memset(&sockaddr, 0, sizeof(sockaddr)); + sockaddr.sa.sa_family = AF_UNIX; + strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path)); + + if (sockaddr.un.sun_path[0] == '@') + sockaddr.un.sun_path[0] = 0; + + memset(&iovec, 0, sizeof(iovec)); + iovec.iov_base = (char*) state; + iovec.iov_len = strlen(state); + + memset(&msghdr, 0, sizeof(msghdr)); + msghdr.msg_name = &sockaddr; + msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(e); + + if (msghdr.msg_namelen > sizeof(struct sockaddr_un)) + msghdr.msg_namelen = sizeof(struct sockaddr_un); + + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + + if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) < 0) { + r = -errno; + goto finish; + } + + r = 1; + +finish: + if (unset_environment) + unsetenv("NOTIFY_SOCKET"); + + if (fd >= 0) + close(fd); + + return r; +#endif +} + +_sd_export_ int sd_notifyf(int unset_environment, const char *format, ...) { +#if defined(DISABLE_SYSTEMD) || !defined(__linux__) + return 0; +#else + va_list ap; + char *p = NULL; + int r; + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0 || !p) + return -ENOMEM; + + r = sd_notify(unset_environment, p); + free(p); + + return r; +#endif +} + +_sd_export_ int sd_booted(void) { +#if defined(DISABLE_SYSTEMD) || !defined(__linux__) + return 0; +#else + + struct stat a, b; + + /* We simply test whether the systemd cgroup hierarchy is + * mounted */ + + if (lstat("/sys/fs/cgroup", &a) < 0) + return 0; + + if (lstat("/sys/fs/cgroup/systemd", &b) < 0) + return 0; + + return a.st_dev != b.st_dev; +#endif +} diff --git a/misc-utils/sd-daemon.h b/misc-utils/sd-daemon.h new file mode 100644 index 0000000..fe51159 --- /dev/null +++ b/misc-utils/sd-daemon.h @@ -0,0 +1,282 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosddaemonhfoo +#define foosddaemonhfoo + +/*** + Copyright 2010 Lennart Poettering + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#include <sys/types.h> +#include <inttypes.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + Reference implementation of a few systemd related interfaces for + writing daemons. These interfaces are trivial to implement. To + simplify porting we provide this reference implementation. + Applications are welcome to reimplement the algorithms described + here if they do not want to include these two source files. + + The following functionality is provided: + + - Support for logging with log levels on stderr + - File descriptor passing for socket-based activation + - Daemon startup and status notification + - Detection of systemd boots + + You may compile this with -DDISABLE_SYSTEMD to disable systemd + support. This makes all those calls NOPs that are directly related to + systemd (i.e. only sd_is_xxx() will stay useful). + + Since this is drop-in code we don't want any of our symbols to be + exported in any case. Hence we declare hidden visibility for all of + them. + + You may find an up-to-date version of these source files online: + + http://cgit.freedesktop.org/systemd/systemd/plain/src/systemd/sd-daemon.h + http://cgit.freedesktop.org/systemd/systemd/plain/src/sd-daemon.c + + This should compile on non-Linux systems, too, but with the + exception of the sd_is_xxx() calls all functions will become NOPs. + + See sd-daemon(7) for more information. +*/ + +#ifndef _sd_printf_attr_ +#if __GNUC__ >= 4 +#define _sd_printf_attr_(a,b) __attribute__ ((format (printf, a, b))) +#else +#define _sd_printf_attr_(a,b) +#endif +#endif + +/* + Log levels for usage on stderr: + + fprintf(stderr, SD_NOTICE "Hello World!\n"); + + This is similar to printk() usage in the kernel. +*/ +#define SD_EMERG "<0>" /* system is unusable */ +#define SD_ALERT "<1>" /* action must be taken immediately */ +#define SD_CRIT "<2>" /* critical conditions */ +#define SD_ERR "<3>" /* error conditions */ +#define SD_WARNING "<4>" /* warning conditions */ +#define SD_NOTICE "<5>" /* normal but significant condition */ +#define SD_INFO "<6>" /* informational */ +#define SD_DEBUG "<7>" /* debug-level messages */ + +/* The first passed file descriptor is fd 3 */ +#define SD_LISTEN_FDS_START 3 + +/* + Returns how many file descriptors have been passed, or a negative + errno code on failure. Optionally, removes the $LISTEN_FDS and + $LISTEN_PID file descriptors from the environment (recommended, but + problematic in threaded environments). If r is the return value of + this function you'll find the file descriptors passed as fds + SD_LISTEN_FDS_START to SD_LISTEN_FDS_START+r-1. Returns a negative + errno style error code on failure. This function call ensures that + the FD_CLOEXEC flag is set for the passed file descriptors, to make + sure they are not passed on to child processes. If FD_CLOEXEC shall + not be set, the caller needs to unset it after this call for all file + descriptors that are used. + + See sd_listen_fds(3) for more information. +*/ +int sd_listen_fds(int unset_environment); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a FIFO in the file system stored under the + specified path, 0 otherwise. If path is NULL a path name check will + not be done and the call only verifies if the file descriptor + refers to a FIFO. Returns a negative errno style error code on + failure. + + See sd_is_fifo(3) for more information. +*/ +int sd_is_fifo(int fd, const char *path); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a special character device on the file + system stored under the specified path, 0 otherwise. + If path is NULL a path name check will not be done and the call + only verifies if the file descriptor refers to a special character. + Returns a negative errno style error code on failure. + + See sd_is_special(3) for more information. +*/ +int sd_is_special(int fd, const char *path); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a socket of the specified family (AF_INET, + ...) and type (SOCK_DGRAM, SOCK_STREAM, ...), 0 otherwise. If + family is 0 a socket family check will not be done. If type is 0 a + socket type check will not be done and the call only verifies if + the file descriptor refers to a socket. If listening is > 0 it is + verified that the socket is in listening mode. (i.e. listen() has + been called) If listening is == 0 it is verified that the socket is + not in listening mode. If listening is < 0 no listening mode check + is done. Returns a negative errno style error code on failure. + + See sd_is_socket(3) for more information. +*/ +int sd_is_socket(int fd, int family, int type, int listening); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is an Internet socket, of the specified family + (either AF_INET or AF_INET6) and the specified type (SOCK_DGRAM, + SOCK_STREAM, ...), 0 otherwise. If version is 0 a protocol version + check is not done. If type is 0 a socket type check will not be + done. If port is 0 a socket port check will not be done. The + listening flag is used the same way as in sd_is_socket(). Returns a + negative errno style error code on failure. + + See sd_is_socket_inet(3) for more information. +*/ +int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is an AF_UNIX socket of the specified type + (SOCK_DGRAM, SOCK_STREAM, ...) and path, 0 otherwise. If type is 0 + a socket type check will not be done. If path is NULL a socket path + check will not be done. For normal AF_UNIX sockets set length to + 0. For abstract namespace sockets set length to the length of the + socket name (including the initial 0 byte), and pass the full + socket path in path (including the initial 0 byte). The listening + flag is used the same way as in sd_is_socket(). Returns a negative + errno style error code on failure. + + See sd_is_socket_unix(3) for more information. +*/ +int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a POSIX Message Queue of the specified name, + 0 otherwise. If path is NULL a message queue name check is not + done. Returns a negative errno style error code on failure. +*/ +int sd_is_mq(int fd, const char *path); + +/* + Informs systemd about changed daemon state. This takes a number of + newline separated environment-style variable assignments in a + string. The following variables are known: + + READY=1 Tells systemd that daemon startup is finished (only + relevant for services of Type=notify). The passed + argument is a boolean "1" or "0". Since there is + little value in signaling non-readiness the only + value daemons should send is "READY=1". + + STATUS=... Passes a single-line status string back to systemd + that describes the daemon state. This is free-from + and can be used for various purposes: general state + feedback, fsck-like programs could pass completion + percentages and failing programs could pass a human + readable error message. Example: "STATUS=Completed + 66% of file system check..." + + ERRNO=... If a daemon fails, the errno-style error code, + formatted as string. Example: "ERRNO=2" for ENOENT. + + BUSERROR=... If a daemon fails, the D-Bus error-style error + code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut" + + MAINPID=... The main pid of a daemon, in case systemd did not + fork off the process itself. Example: "MAINPID=4711" + + WATCHDOG=1 Tells systemd to update the watchdog timestamp. + Services using this feature should do this in + regular intervals. A watchdog framework can use the + timestamps to detect failed services. + + Daemons can choose to send additional variables. However, it is + recommended to prefix variable names not listed above with X_. + + Returns a negative errno-style error code on failure. Returns > 0 + if systemd could be notified, 0 if it couldn't possibly because + systemd is not running. + + Example: When a daemon finished starting up, it could issue this + call to notify systemd about it: + + sd_notify(0, "READY=1"); + + See sd_notifyf() for more complete examples. + + See sd_notify(3) for more information. +*/ +int sd_notify(int unset_environment, const char *state); + +/* + Similar to sd_notify() but takes a format string. + + Example 1: A daemon could send the following after initialization: + + sd_notifyf(0, "READY=1\n" + "STATUS=Processing requests...\n" + "MAINPID=%lu", + (unsigned long) getpid()); + + Example 2: A daemon could send the following shortly before + exiting, on failure: + + sd_notifyf(0, "STATUS=Failed to start up: %s\n" + "ERRNO=%i", + strerror(errno), + errno); + + See sd_notifyf(3) for more information. +*/ +int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_attr_(2,3); + +/* + Returns > 0 if the system was booted with systemd. Returns < 0 on + error. Returns 0 if the system was not booted with systemd. Note + that all of the functions above handle non-systemd boots just + fine. You should NOT protect them with a call to this function. Also + note that this function checks whether the system, not the user + session is controlled by systemd. However the functions above work + for both user and system services. + + See sd_booted(3) for more information. +*/ +int sd_booted(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/misc-utils/uuidd.8 b/misc-utils/uuidd.8 new file mode 100644 index 0000000..39f31cb --- /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 "June 2011" "util-linux" "System Administration" +.SH NAME +uuidd \- UUID generation daemon +.SH SYNOPSIS +.B uuidd +.RI [ 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 +.B \-d +Run uuidd in debugging mode. This prevents uuidd from running as a daemon. +.TP +.BR \-h , " \-\-help " +Display help screen and exit. +.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 , " \-\-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 \-P , " \-\-no-pid " +Do not create pid file. +.TP +.BR \-F , " \-\-no-fork " +Do not daemonize using double-fork. +.TP +.BR \-S , " \-\-socket-activation " +Do not create the socket and instead expect it to be provided by the calling +process. Implies --no-fork and --no-pid. As of this writing, this option is +supposed to be used only with systemd. This option must be enabled with a configure +option. +.TP +.B \-q +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 " \fIpath\fR +Specify the pathname used for the unix-domain socket used by uuidd. By +default, the pathname used is /run/uuidd/request. This is primarily +for debugging purposes, since the pathname is hard-coded in the libuuid +library. +.TP +.BR \-T , " \-\-timeout " \fItimeout\fR +Specify a timeout for uuidd. If specified, then uuidd will exit after +.I timeout +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. +.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 +.nf +.RE +.SH AUTHOR +The +.B uuidd +daemon was written by Theodore Ts'o <tytso@mit.edu>. +.SH AVAILABILITY +The uuidd daemon is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. +.SH "SEE ALSO" +.BR uuid (3), +.BR uuidgen (1) diff --git a/misc-utils/uuidd.8.in b/misc-utils/uuidd.8.in new file mode 100644 index 0000000..f35a41e --- /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 "June 2011" "util-linux" "System Administration" +.SH NAME +uuidd \- UUID generation daemon +.SH SYNOPSIS +.B uuidd +.RI [ 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 +.B \-d +Run uuidd in debugging mode. This prevents uuidd from running as a daemon. +.TP +.BR \-h , " \-\-help " +Display help screen and exit. +.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 , " \-\-pid " \fIpath\fR +Specify the pathname where the pid file should be written. By default, +the pid file is written to @localstatedir@/uuidd/uuidd.pid. +.TP +.BR \-P , " \-\-no-pid " +Do not create pid file. +.TP +.BR \-F , " \-\-no-fork " +Do not daemonize using double-fork. +.TP +.BR \-S , " \-\-socket-activation " +Do not create the socket and instead expect it to be provided by the calling +process. Implies --no-fork and --no-pid. As of this writing, this option is +supposed to be used only with systemd. This option must be enabled with a configure +option. +.TP +.B \-q +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 " \fIpath\fR +Specify the pathname used for the unix-domain socket used by uuidd. By +default, the pathname used is @localstatedir@/uuidd/request. This is primarily +for debugging purposes, since the pathname is hard-coded in the libuuid +library. +.TP +.BR \-T , " \-\-timeout " \fItimeout\fR +Specify a timeout for uuidd. If specified, then uuidd will exit after +.I timeout +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. +.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 +.nf +.RE +.SH AUTHOR +The +.B uuidd +daemon was written by Theodore Ts'o <tytso@mit.edu>. +.SH AVAILABILITY +The uuidd daemon is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. +.SH "SEE ALSO" +.BR uuid (3), +.BR uuidgen (1) diff --git a/misc-utils/uuidd.c b/misc-utils/uuidd.c new file mode 100644 index 0000000..3dde49e --- /dev/null +++ b/misc-utils/uuidd.c @@ -0,0 +1,669 @@ +/* + * 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> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#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> +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#else +extern int getopt(int argc, char * const argv[], const char *optstring); +extern char *optarg; +extern int optind; +#endif + +#include "uuid.h" +#include "uuidd.h" +#include "all-io.h" +#include "c.h" +#include "closestream.h" + +#ifdef USE_SOCKET_ACTIVATION +#include "sd-daemon.h" +#endif + +#include "nls.h" + +#ifdef __GNUC__ +#define CODE_ATTR(x) __attribute__(x) +#else +#define CODE_ATTR(x) +#endif + +/* length of textual representation of UUID, including trailing \0 */ +#define UUID_STR_LEN 37 + +/* length of binary representation of UUID */ +#define UUID_LEN (sizeof(uuid_t)) + +/* server loop control structure */ +struct uuidd_cxt_t { + int timeout; + unsigned int debug: 1, + quiet: 1, + no_fork: 1, + no_sock: 1; +}; + +static void __attribute__ ((__noreturn__)) usage(FILE * out) +{ + fputs(_("\nUsage:\n"), out); + fprintf(out, + _(" %s [options]\n"), program_invocation_short_name); + + fputs(_("\nOptions:\n"), out); + fputs(_(" -p, --pid <path> path to pid file\n" + " -s, --socket <path> path to socket\n" + " -T, --timeout <sec> specify inactivity timeout\n" + " -k, --kill kill running daemon\n" + " -r, --random test random-based generation\n" + " -t, --time test time-based generation\n" + " -n, --uuids <num> request number of uuids\n" + " -P, --no-pid do not create pid file\n" + " -F, --no-fork do not daemonize using double-fork\n" + " -S, --socket-activation do not create listening socket\n" + " -d, --debug run in debugging mode\n" + " -q, --quiet turn on quiet mode\n" + " -V, --version output version information and exit\n" + " -h, --help display this help and exit\n\n"), out); + + exit(out == stderr ? EXIT_FAILURE : 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 const char *cleanup_pidfile, *cleanup_socket; + +static void terminate_intr(int signo CODE_ATTR((unused))) +{ + if (cleanup_pidfile) + unlink(cleanup_pidfile); + if (cleanup_socket) + unlink(cleanup_socket); + exit(EXIT_SUCCESS); +} + +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; + strncpy(srv_addr.sun_path, socket_path, sizeof(srv_addr.sun_path)); + srv_addr.sun_path[sizeof(srv_addr.sun_path) - 1] = '\0'; + + 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 + * + * Set cleanup_pidfile global variable for the cleanup + * handler. @pidfile_path must not be NULL. + * + * Return file descriptor of the created pid_file. + */ +static int create_pidfile(const char *pidfile_path, int quiet) +{ + int fd_pidfile; + struct flock fl; + + fd_pidfile = open(pidfile_path, O_CREAT | O_RDWR, 0664); + if (fd_pidfile < 0) { + if (!quiet) + fprintf(stderr, _("Failed to open/create %s: %m\n"), + pidfile_path); + exit(EXIT_FAILURE); + } + 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 (!quiet) + fprintf(stderr, _("Failed to lock %s: %m\n"), 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 wont be later closed + * during create_daemon(). + * + * Return file descriptor corresponding to created socket. + */ +static int create_socket(const char *socket_path, int will_fork, int quiet) +{ + struct sockaddr_un my_addr; + mode_t save_umask; + int s; + + if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + if (!quiet) + fprintf(stderr, _("Couldn't create unix stream socket: %m")); + 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; + strncpy(my_addr.sun_path, socket_path, sizeof(my_addr.sun_path)); + my_addr.sun_path[sizeof(my_addr.sun_path) - 1] = '\0'; + unlink(socket_path); + save_umask = umask(0); + if (bind(s, (const struct sockaddr *) &my_addr, + sizeof(struct sockaddr_un)) < 0) { + if (!quiet) + fprintf(stderr, + _("Couldn't bind unix socket %s: %m\n"), socket_path); + exit(EXIT_FAILURE); + } + umask(save_umask); + cleanup_socket = socket_path; + + return s; +} + +static void server_loop(const char *socket_path, const char *pidfile_path, + const 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, num; + int s = 0; + int fd_pidfile = -1; + int ret; + +#ifdef USE_SOCKET_ACTIVATION + if (!uuidd_cxt->no_sock) /* no_sock implies no_fork and no_pid */ +#endif + { + + signal(SIGALRM, terminate_intr); + alarm(30); + if (pidfile_path) + fd_pidfile = create_pidfile(pidfile_path, uuidd_cxt->quiet); + + ret = call_daemon(socket_path, UUIDD_OP_GETPID, reply_buf, + sizeof(reply_buf), 0, NULL); + if (ret > 0) { + if (!uuidd_cxt->quiet) + fprintf(stderr, + _("uuidd daemon already running at pid %s\n"), + reply_buf); + exit(EXIT_FAILURE); + } + alarm(0); + + s = create_socket(socket_path, + (!uuidd_cxt->debug || !uuidd_cxt->no_fork), + uuidd_cxt->quiet); + if (listen(s, SOMAXCONN) < 0) { + if (!uuidd_cxt->quiet) + fprintf(stderr, _("Couldn't listen on unix " + "socket %s: %m\n"), socket_path); + exit(EXIT_FAILURE); + } + + if (!uuidd_cxt->debug && !uuidd_cxt->no_fork) + create_daemon(); + + if (pidfile_path) { + sprintf(reply_buf, "%8d\n", getpid()); + ignore_result( ftruncate(fd_pidfile, 0) ); + write_all(fd_pidfile, reply_buf, strlen(reply_buf)); + if (fd_pidfile > 1) + close(fd_pidfile); /* Unlock the pid file */ + } + + } + + signal(SIGHUP, terminate_intr); + signal(SIGINT, terminate_intr); + signal(SIGTERM, terminate_intr); + signal(SIGALRM, terminate_intr); + signal(SIGPIPE, SIG_IGN); + +#ifdef USE_SOCKET_ACTIVATION + if (uuidd_cxt->no_sock) { + if (sd_listen_fds(0) != 1) { + fprintf(stderr, _("No or too many file descriptors received.\n")); + exit(EXIT_FAILURE); + } + + s = SD_LISTEN_FDS_START + 0; + } +#endif + + while (1) { + fromlen = sizeof(from_addr); + if (uuidd_cxt->timeout > 0) + alarm(uuidd_cxt->timeout); + ns = accept(s, (struct sockaddr *) &from_addr, &fromlen); + alarm(0); + if (ns < 0) { + if ((errno == EAGAIN) || (errno == EINTR)) + continue; + else + err(EXIT_FAILURE, "accept"); + } + len = read(ns, &op, 1); + if (len != 1) { + if (len < 0) + perror("read"); + else + fprintf(stderr, _("Error reading from client, " + "len = %d\n"), 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 *pidfile_path_param = NULL; + const char *err_context; + char buf[1024], *cp; + char str[UUID_STR_LEN], *tmp; + 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} + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((c = + getopt_long(argc, argv, "p:s:T:krtn:PFSdqVh", longopts, + NULL)) != -1) { + switch (c) { + case 'd': + uuidd_cxt.debug = 1; + break; + case 'k': + do_kill++; + break; + case 'n': + num = strtol(optarg, &tmp, 0); + if ((num < 1) || *tmp) { + fprintf(stderr, _("Bad number: %s\n"), optarg); + return EXIT_FAILURE; + } + break; + case 'p': + pidfile_path_param = optarg; + break; + case 'P': + no_pid = 1; + break; + case 'F': + uuidd_cxt.no_fork = 1; + break; + case 'S': +#ifdef USE_SOCKET_ACTIVATION + uuidd_cxt.no_sock = 1; + uuidd_cxt.no_fork = 1; + no_pid = 1; +#else + fprintf(stderr, + _("uuidd has been built without support for socket activation.\n")); + return EXIT_FAILURE; +#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 = strtol(optarg, &tmp, 0); + if (uuidd_cxt.timeout < 0 || *tmp) { + fprintf(stderr, _("Bad number: %s\n"), optarg); + return EXIT_FAILURE; + } + break; + case 'V': + printf(_("%s from %s\n"), + program_invocation_short_name, + PACKAGE_STRING); + return EXIT_SUCCESS; + case 'h': + usage(stdout); + default: + usage(stderr); + } + } + + if (no_pid && pidfile_path_param && !uuidd_cxt.quiet) + fprintf(stderr, _("Both --pid and --no-pid specified. " + "Ignoring --no-pid.\n")); + + if (!no_pid && !pidfile_path_param) + pidfile_path = UUIDD_PIDFILE_PATH; + else if (pidfile_path_param) + pidfile_path = pidfile_path_param; + + /* custom socket path and socket-activation make no sense */ + if (s_flag && uuidd_cxt.no_sock && !uuidd_cxt.quiet) + fprintf(stderr, _("Both --socket-activation and --socket specified. " + "Ignoring --socket\n")); + + if (num && do_type) { + ret = call_daemon(socket_path, do_type + 2, buf, + sizeof(buf), &num, &err_context); + if (ret < 0) { + printf(_("Error calling uuidd daemon (%s): %m\n"), err_context); + return EXIT_FAILURE; + } + 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) { + printf(_("Error calling uuidd daemon (%s): %m\n"), err_context); + return EXIT_FAILURE; + } + 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) + fprintf(stderr, + _("Couldn't kill uuidd running " + "at pid %d: %m\n"), 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..27b75c6 --- /dev/null +++ b/misc-utils/uuidd.rc.in @@ -0,0 +1,71 @@ +#! /bin/sh -e +### BEGIN INIT INFO +# Provides: uuidd +# Required-Start: $time $local_fs +# Required-Stop: $time $local_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 + +# libuuid is able to execute the uuid daemon on-demand -- in such a case +# the daemon binary must be setuid to an unprivileged user (e.g. uuidd:uuidd). +# [-- kzak Jun 2009] +UUIDD_ON_DEMAND_ONLY="no" + +PATH=/bin:/usr/bin:/sbin:/usr/sbin +DAEMON=/usr/sbin/uuidd +UUIDD_USER=uuidd +UUIDD_GROUP=uuidd +UUIDD_DIR=@localstatedir@/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 + if test "$UUIDD_ON_DEMAND_ONLY" = yes; then + echo -n "(on demand only)" + else + start_daemon -p $PIDFILE $DAEMON + fi + log_end_msg $? + ;; + stop) + log_daemon_msg "Stopping uuidd 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..89e0eac --- /dev/null +++ b/misc-utils/uuidd.service.in @@ -0,0 +1,12 @@ +[Unit] +Description=Daemon for generating UUIDs +Requires=uuidd.socket + +[Service] +ExecStart=@usrsbin_execdir@/uuidd --socket-activation --timeout 60 +Restart=no +User=uuidd +Group=uuidd + +[Install] +Also=uuidd.socket diff --git a/misc-utils/uuidd.socket.in b/misc-utils/uuidd.socket.in new file mode 100644 index 0000000..d8be72f --- /dev/null +++ b/misc-utils/uuidd.socket.in @@ -0,0 +1,8 @@ +[Unit] +Description=UUID daemon activation socket + +[Socket] +ListenStream=@localstatedir@/uuidd/request + +[Install] +WantedBy=sockets.target diff --git a/misc-utils/uuidgen.1 b/misc-utils/uuidgen.1 new file mode 100644 index 0000000..ad409b9 --- /dev/null +++ b/misc-utils/uuidgen.1 @@ -0,0 +1,63 @@ +.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca) +.\" +.\" This man page was created for libuuid.so.1.1 from e2fsprogs-1.14. +.\" +.\" This file may be copied under the terms of the GNU Public License. +.\" +.\" Created Wed Mar 10 17:42:12 1999, Andreas Dilger +.TH UUIDGEN 1 "June 2011" "util-linux" "User Commands" +.SH NAME +uuidgen \- create a new UUID value +.SH SYNOPSIS +.B uuidgen +[\fIoptions\fR] +.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 two types of UUIDs which +.B uuidgen +can generate: time-based UUIDs and random-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 two +UUID types by using the +.B \-r +or +.B \-t +options. +.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. +.SH "CONFORMING TO" +OSF DCE 1.1 +.SH AUTHOR +.B uuidgen +was written by Andreas Dilger for libuuid. +.SH AVAILABILITY +The uuidgen command is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. +.SH "SEE ALSO" +.BR libuuid (3) diff --git a/misc-utils/uuidgen.c b/misc-utils/uuidgen.c new file mode 100644 index 0000000..219be8b --- /dev/null +++ b/misc-utils/uuidgen.c @@ -0,0 +1,104 @@ +/* + * 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> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#else +extern int getopt(int argc, char * const argv[], const char *optstring); +extern char *optarg; +extern int optind; +#endif + +#include "uuid.h" +#include "nls.h" +#include "c.h" +#include "closestream.h" + +#define DO_TYPE_TIME 1 +#define DO_TYPE_RANDOM 2 + +static void __attribute__ ((__noreturn__)) usage(FILE * out) +{ + fputs(_("\nUsage:\n"), out); + fprintf(out, + _(" %s [options]\n"), program_invocation_short_name); + + fputs(_("\nOptions:\n"), out); + fputs(_(" -r, --random generate random-based uuid\n" + " -t, --time generate time-based uuid\n" + " -V, --version output version information and exit\n" + " -h, --help display this help and exit\n\n"), out); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +int +main (int argc, char *argv[]) +{ + int c; + int do_type = 0; + char str[37]; + uuid_t uu; + + static const struct option longopts[] = { + {"random", required_argument, NULL, 'r'}, + {"time", no_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); + atexit(close_stdout); + + while ((c = getopt_long(argc, argv, "rtVh", longopts, NULL)) != -1) + switch (c) { + case 't': + do_type = DO_TYPE_TIME; + break; + case 'r': + do_type = DO_TYPE_RANDOM; + break; + case 'V': + printf(_("%s from %s\n"), + program_invocation_short_name, + PACKAGE_STRING); + return EXIT_SUCCESS; + case 'h': + usage(stdout); + default: + usage(stderr); + } + + switch (do_type) { + case DO_TYPE_TIME: + uuid_generate_time(uu); + break; + case DO_TYPE_RANDOM: + uuid_generate_random(uu); + break; + default: + uuid_generate(uu); + break; + } + + uuid_unparse(uu, str); + + printf("%s\n", str); + + return EXIT_SUCCESS; +} diff --git a/misc-utils/whereis.1 b/misc-utils/whereis.1 new file mode 100644 index 0000000..988462f --- /dev/null +++ b/misc-utils/whereis.1 @@ -0,0 +1,140 @@ +.\" 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 "June 2012" "util-linux" "User Commands" +.SH NAME +whereis \- locate the binary, source, and manual page files for a command +.SH SYNOPSIS +.B whereis +.RB [ options ] +.RB [ \-BMS +.IR directory ... +.BR \-f ] +.IR filename ... +.SH DESCRIPTION +.B whereis +locates source/binary and manuals sections for specified files. +The supplied names are first stripped of leading pathname components +and any (single) trailing extension of the form +.BI . ext\fR,\fP +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 +a list of standard Linux places. +.SH OPTIONS +.TP +.IP "\fB\-b\fP" +Search only for binaries. +.IP "\fB\-m\fP" +Search only for manual sections. +.IP "\fB\-s\fP" +Search only for sources. +.IP "\fB\-u\fP" +Search for unusual entries. A file is said to be unusual if it does +not have one entry of each requested type. Thus +.RB ` "whereis\ \ \-m\ \ \-u\ \ *" ' +asks for those files in the current +directory which have no documentation. +.IP "\fB\-B \fIlist\fP" +Change or otherwise limit the places where +.B whereis +searches for binaries by white-space separated list of directories. +.IP "\fB\-M \fIlist\fP" +Change or otherwise limit the places where +.B whereis +searches for manual sections by white-space separated list of directories. +.IP "\fB\-S \fIlist\fP" +.B \-S +Change or otherwise limit the places where +.B whereis +searches for sources white-space separated list of directories. +.IP "\fB\-f\fP" +Terminate the last directory list and signals the start of file names, +and +.I must +be used when any of the +.BR \-B , +.BR \-M , +or +.BR \-S +options are used. +.SH EXAMPLE +Find all files in +.B /usr/bin +which are not documented +in +.B /usr/man/man1 +with source in +.BR /usr/src : +.IP +.nf +.ft B +$ cd /usr/bin +$ whereis \-u \-M /usr/man/man1 \-S /usr/src \-f * +.fi +.ft R +.SH FILES +.B whereis +has basic set of hard-coded paths (see below). If the option +.B \-B +is not specified then also follows +.B $PATH +environment variable (since version 2.21). + +.TP 20 +/{bin,sbin,etc} +.TP +/usr/{lib,bin,old,new,local,games,include,etc,src,man,sbin,X386,TeX,g++-include} +.TP +/usr/local/{X386,TeX,X11,include,lib,man,etc,bin,games,emacs} +.SH "SEE ALSO" +.BR chdir (2V) +.SH BUGS +Since +.B whereis +uses +.BR chdir (2V) +to run faster, pathnames given with the +.BR \-M , +.BR \-S , +or +.B \-B +must be full; that is, they must begin with a +.RB ` / '. +.PP +.SH AVAILABILITY +The whereis command is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. diff --git a/misc-utils/whereis.c b/misc-utils/whereis.c new file mode 100644 index 0000000..05af8e0 --- /dev/null +++ b/misc-utils/whereis.c @@ -0,0 +1,485 @@ +/*- + * 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. + */ + +/* *:aeb */ + +/* 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 + */ + +#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 "xalloc.h" +#include "nls.h" +#include "c.h" +#include "closestream.h" + +static char *bindirs[] = { + "/bin", + "/usr/bin", + "/sbin", + "/usr/sbin", + "/etc", + "/usr/etc", + "/lib", + "/usr/lib", + "/lib64", + "/usr/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", + + 0 +}; + +static char *mandirs[] = { + "/usr/man/*", + "/usr/share/man/*", + "/usr/X386/man/*", + "/usr/X11/man/*", + "/usr/TeX/man/*", + "/usr/interviews/man/mann", + 0 +}; + +static 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", + 0 +}; + +static char sflag = 1, bflag = 1, mflag = 1, uflag; +static char **Sflag, **Bflag, **Mflag, **pathdir, **pathdir_p; +static int Scnt, Bcnt, Mcnt, count, print; + +static void __attribute__ ((__noreturn__)) usage(FILE * out) +{ + fputs(_("\nUsage:\n"), out); + fprintf(out, + _(" %s [options] file\n"), program_invocation_short_name); + + fputs(_("\nOptions:\n"), out); + fputs(_(" -f <file> define search scope\n" + " -b search only binaries\n" + " -B <dirs> define binaries lookup path\n" + " -m search only manual paths\n" + " -M <dirs> define man lookup path\n" + " -s search only sources path\n" + " -S <dirs> define sources lookup path\n" + " -u search from unusual entities\n" + " -V output version information and exit\n" + " -h display this help and exit\n\n"), out); + + fputs(_("See how to use file and dirs arguments from whereis(1) manual.\n"), out); + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +static int +itsit(char *cp, char *dp) +{ + int i = strlen(dp); + + if (dp[0] == 's' && dp[1] == '.' && itsit(cp, dp + 2)) + return 1; + if (!strcmp(dp + i - 2, ".Z")) + i -= 2; + else if (!strcmp(dp + i - 3, ".gz")) + i -= 3; + else if (!strcmp(dp + i - 4, ".bz2")) + 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(char *dir, char *cp) +{ + DIR *dirp; + struct dirent *dp; + char *d, *dd; + size_t l; + char dirbuf[1024]; + struct stat statbuf; + + dd = strchr(dir, '*'); + if (!dd) { + dirp = opendir(dir); + if (dirp == NULL) + return; + while ((dp = readdir(dirp)) != NULL) { + if (itsit(cp, dp->d_name)) { + count++; + if (print) + printf(" %s/%s", dir, dp->d_name); + } + } + closedir(dirp); + return; + } + + l = strlen(dir); + if (l < sizeof(dirbuf)) { + /* refuse excessively long names */ + strcpy(dirbuf, dir); + d = strchr(dirbuf, '*'); + *d = 0; + dirp = opendir(dirbuf); + if (dirp == NULL) + return; + while ((dp = readdir(dirp)) != NULL) { + if (!strcmp(dp->d_name, ".") || + !strcmp(dp->d_name, "..")) + continue; + if (strlen(dp->d_name) + l > sizeof(dirbuf)) + continue; + sprintf(d, "%s", dp->d_name); + if (stat(dirbuf, &statbuf)) + continue; + if (!S_ISDIR(statbuf.st_mode)) + continue; + strcat(d, dd + 1); + findin(dirbuf, cp); + } + closedir(dirp); + } + return; + +} + +static int inpath(const char *str) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(bindirs) - 1 ; i++) + if (!strcmp(bindirs[i], str)) + return 1; + + for (i = 0; i < ARRAY_SIZE(mandirs) - 1; i++) + if (!strcmp(mandirs[i], str)) + return 1; + + for (i = 0; i < ARRAY_SIZE(srcdirs) - 1; i++) + if (!strcmp(srcdirs[i], str)) + return 1; + + return 0; +} + +static void fillpath(void) +{ + char *key=NULL, *tok=NULL, *pathcp, *path = getenv("PATH"); + int i = 0; + + + if (!path) + return; + pathcp = xstrdup(path); + + for (tok = strtok_r(pathcp, ":", &key); tok; + tok = strtok_r(NULL, ":", &key)) { + + /* make sure we don't repeat the search path */ + if (inpath(tok)) + continue; + + pathdir = xrealloc(pathdir, (i + 1) * sizeof(char *)); + pathdir[i++] = xstrdup(tok); + } + + pathdir = xrealloc(pathdir, (i + 1) * sizeof(char *)); + pathdir[i] = NULL; + + pathdir_p = pathdir; + free(pathcp); +} + +static void freepath(void) +{ + free(pathdir); +} + +static void +findv(char **dirv, int dirc, char *cp) +{ + + while (dirc > 0) + findin(*dirv++, cp), dirc--; +} + +static void +looksrc(char *cp) +{ + if (Sflag == 0) + findv(srcdirs, ARRAY_SIZE(srcdirs)-1, cp); + else + findv(Sflag, Scnt, cp); +} + +static void +lookbin(char *cp) +{ + if (Bflag == 0) { + findv(bindirs, ARRAY_SIZE(bindirs)-1, cp); + while (*pathdir_p) + findin(*pathdir_p++, cp); /* look $PATH */ + } else + findv(Bflag, Bcnt, cp); +} + +static void +lookman(char *cp) +{ + if (Mflag == 0) + findv(mandirs, ARRAY_SIZE(mandirs)-1, cp); + else + findv(Mflag, Mcnt, cp); +} + +static void +getlist(int *argcp, char ***argvp, char ***flagp, int *cntp) +{ + (*argvp)++; + *flagp = *argvp; + *cntp = 0; + for ((*argcp)--; *argcp > 0 && (*argvp)[0][0] != '-'; (*argcp)--) + (*cntp)++, (*argvp)++; + (*argcp)++; + (*argvp)--; +} + +static void +zerof(void) +{ + if (sflag && bflag && mflag) + sflag = bflag = mflag = 0; +} + +static int +print_again(char *cp) +{ + if (print) + printf("%s:", cp); + if (sflag) { + looksrc(cp); + if (uflag && print == 0 && count != 1) { + print = 1; + return 1; + } + } + count = 0; + if (bflag) { + lookbin(cp); + if (uflag && print == 0 && count != 1) { + print = 1; + return 1; + } + } + count = 0; + if (mflag) { + lookman(cp); + if (uflag && print == 0 && count != 1) { + print = 1; + return 1; + } + } + return 0; +} + +static void +lookup(char *cp) +{ + register char *dp; + + for (dp = cp; *dp; dp++) + continue; + for (; dp > cp; dp--) { + if (*dp == '.') { + *dp = 0; + break; + } + } + for (dp = cp; *dp; dp++) + if (*dp == '/') + cp = dp + 1; + if (uflag) { + print = 0; + count = 0; + } else + print = 1; + + while (print_again(cp)) + /* all in print_again() */ ; + + if (print) + printf("\n"); +} + +/* + * whereis name + * look for source, documentation and binaries + */ +int +main(int argc, char **argv) +{ + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + argc--, argv++; + if (argc == 0) + usage(stderr); + + do + if (argv[0][0] == '-') { + register char *cp = argv[0] + 1; + while (*cp) switch (*cp++) { + + case 'f': + break; + + case 'S': + getlist(&argc, &argv, &Sflag, &Scnt); + break; + + case 'B': + getlist(&argc, &argv, &Bflag, &Bcnt); + break; + + case 'M': + getlist(&argc, &argv, &Mflag, &Mcnt); + break; + + case 's': + zerof(); + sflag++; + continue; + + case 'u': + uflag++; + continue; + + case 'b': + zerof(); + bflag++; + continue; + + case 'm': + zerof(); + mflag++; + continue; + case 'V': + printf(_("%s from %s\n"), + program_invocation_short_name, + PACKAGE_STRING); + return EXIT_SUCCESS; + case 'h': + usage(stdout); + default: + usage(stderr); + } + argv++; + } else { + if (Bcnt == 0 && pathdir == NULL) + fillpath(); + lookup(*argv++); + } + while (--argc > 0); + + freepath(); + return EXIT_SUCCESS; +} diff --git a/misc-utils/wipefs.8 b/misc-utils/wipefs.8 new file mode 100644 index 0000000..cecdc44 --- /dev/null +++ b/misc-utils/wipefs.8 @@ -0,0 +1,74 @@ +.\" -*- nroff -*- +.\" Copyright 2009 by Karel Zak. All Rights Reserved. +.\" This file may be copied under the terms of the GNU Public License. +.\" +.TH WIPEFS 8 "October 2009" "util-linux" "System Administration" +.SH NAME +wipefs \- wipe a signature from a device +.SH SYNOPSIS +.B wipefs +.RB [ \-ahnptV ] +.RB [ \-o +.IR offset ] +.I device... +.SH DESCRIPTION +.B wipefs +can erase filesystem, raid or partition table signatures (magic strings) from +the specified +.I device +to make the signature invisible for libblkid. + +.B wipefs +does not erase the filesystem itself nor any other data from the device. +When used without options \fB-a\fR or \fB-o\fR, it lists all visible filesystems +and the offsets of their basic signatures. + +Note that some filesystems or some partition tables store more magic strings on +the devices. The +.B wipefs +lists the first offset where a magic string has been detected. The device is +not scanned for additional magic strings for the same filesystem. It's possible +that after \fBwipefs -o <offset>\fR will be the same filesystem or partition +table visible by another magic string on another offset. + +When used with option \fB-a\fR then all for libblkid visible magic strings are +erased. + +.SH OPTIONS +.IP "\fB\-a, \-\-all\fP" +Erase all available signatures. This set of erased signatures could be +restricted by \fB\-t <list>\fP option. +.IP "\fB\-h, \-\-help\fP" +Print help and exit. +.IP "\fB\-n, \-\-no\-act\fP" +Causes everything to be done except for the write() call. +.IP "\fB\-o, \-\-offset\fP \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. + +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, PB, EB, ZB and YB. +.IP "\fB\-p, \-\-parsable\fP" +Print out in parsable instead of printable format. Encode all potentially unsafe +characters of a string to the corresponding hex value prefixed by '\\x'. +.IP "\fB\-q, \-\-quiet\fP" +Suppress output messages after successful signature wipe. +.IP "\fB\-t, \-\-types\fP \fIlist\fP +Used to limit the set of printed or erased signatures. More than one type may +be specified in a comma-separated list. The list of types 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). +.IP "\fB\-V, \-\-version\fP" +Output version information and exit. +.SH AUTHOR +Karel Zak <kzak@redhat.com>. +.SH AVAILABILITY +The wipefs command is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. +.SH SEE ALSO +.BR blkid (8) +.BR findfs (8) + diff --git a/misc-utils/wipefs.c b/misc-utils/wipefs.c new file mode 100644 index 0000000..cddad8a --- /dev/null +++ b/misc-utils/wipefs.c @@ -0,0 +1,472 @@ +/* + * 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 <blkid.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" + +struct wipe_desc { + loff_t offset; /* magic string offset */ + size_t len; /* length of magic string */ + unsigned char *magic; /* magic string */ + + int zap; /* zap this offset? */ + char *usage; /* raid, filesystem, ... */ + char *type; /* FS type */ + char *label; /* FS label */ + char *uuid; /* FS uuid */ + + int on_disk; + + struct wipe_desc *next; +}; + +enum { + WP_MODE_PRETTY, /* default */ + WP_MODE_PARSABLE +}; + +static const char *type_pattern; + +static void +print_pretty(struct wipe_desc *wp, int line) +{ + if (!line) { + printf("offset type\n"); + printf("----------------------------------------------------------------\n"); + } + + printf("0x%-17jx %s [%s]", wp->offset, wp->type, wp->usage); + + if (wp->label && *wp->label) + printf("\n%27s %s", "LABEL:", wp->label); + if (wp->uuid) + printf("\n%27s %s", "UUID: ", wp->uuid); + puts("\n"); +} + +static void +print_parsable(struct wipe_desc *wp, int line) +{ + char enc[256]; + + if (!line) + printf("# offset,uuid,label,type\n"); + + printf("0x%jx,", wp->offset); + + if (wp->uuid) { + blkid_encode_string(wp->uuid, enc, sizeof(enc)); + printf("%s,", enc); + } else + fputc(',', stdout); + + if (wp->label) { + blkid_encode_string(wp->label, enc, sizeof(enc)); + printf("%s,", enc); + } else + fputc(',', stdout); + + blkid_encode_string(wp->type, enc, sizeof(enc)); + printf("%s\n", enc); +} + +static void +print_all(struct wipe_desc *wp, int mode) +{ + int n = 0; + + while (wp) { + switch (mode) { + case WP_MODE_PRETTY: + print_pretty(wp, n++); + break; + case WP_MODE_PARSABLE: + print_parsable(wp, n++); + break; + default: + abort(); + } + wp = wp->next; + } +} + +static struct wipe_desc * +add_offset(struct wipe_desc *wp0, loff_t offset, int zap) +{ + struct wipe_desc *wp = wp0; + + while (wp) { + if (wp->offset == offset) + return wp; + wp = wp->next; + } + + wp = xcalloc(1, sizeof(struct wipe_desc)); + wp->offset = offset; + wp->next = wp0; + wp->zap = zap; + return wp; +} + +static struct wipe_desc * +clone_offset(struct wipe_desc *wp0) +{ + struct wipe_desc *wp = NULL; + + while(wp0) { + wp = add_offset(wp, wp0->offset, wp0->zap); + wp0 = wp0->next; + } + + return wp; +} + +static struct wipe_desc * +get_desc_for_probe(struct wipe_desc *wp, blkid_probe pr) +{ + const char *off, *type, *mag, *p, *usage = NULL; + size_t len; + loff_t offset; + int rc; + + /* 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 wp; + + /* 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 wp; + usage = "partition table"; + } else + return wp; + + if (type_pattern && !match_fstype(type, type_pattern)) + return wp; + + offset = strtoll(off, NULL, 10); + + wp = add_offset(wp, offset, 0); + if (!wp) + return NULL; + + if (usage || blkid_probe_lookup_value(pr, "USAGE", &usage, NULL) == 0) + wp->usage = xstrdup(usage); + + wp->type = xstrdup(type); + wp->on_disk = 1; + + 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; + + if (!devname) + return NULL; + + if (mode) { + int fd = open(devname, mode); + if (fd < 0) + goto error; + + pr = blkid_new_probe(); + if (pr && blkid_probe_set_device(pr, fd, 0, 0)) + 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 | + BLKID_SUBLKS_TYPE | BLKID_SUBLKS_USAGE | + BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID); + + blkid_probe_enable_partitions(pr, 1); + blkid_probe_set_partitions_flags(pr, BLKID_PARTS_MAGIC); + + return pr; +error: + err(EXIT_FAILURE, _("error: %s: probing initialization failed"), devname); + return NULL; +} + +static struct wipe_desc * +read_offsets(struct wipe_desc *wp, const char *devname) +{ + blkid_probe pr = new_probe(devname, 0); + + if (!pr) + return NULL; + + while (blkid_do_probe(pr) == 0) { + wp = get_desc_for_probe(wp, pr); + if (!wp) + break; + } + + blkid_free_probe(pr); + return wp; +} + +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(blkid_probe pr, const char *devname, struct wipe_desc *w, int noact, int quiet) +{ + size_t i; + + if (blkid_do_wipe(pr, noact)) + warn(_("%s: failed to erase %s magic string at offset 0x%08jx"), + devname, w->type, w->offset); + + if (quiet) + return; + + printf(_("%s: %zd bytes were erased at offset 0x%08jx (%s): "), + devname, w->len, 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 struct wipe_desc * +do_wipe(struct wipe_desc *wp, const char *devname, int noact, int all, int quiet) +{ + blkid_probe pr = new_probe(devname, O_RDWR); + struct wipe_desc *w, *wp0 = clone_offset(wp); + int zap = all ? 1 : wp->zap; + + if (!pr) + return NULL; + + while (blkid_do_probe(pr) == 0) { + wp = get_desc_for_probe(wp, pr); + if (!wp) + break; + + /* Check if offset is in provided list */ + w = wp0; + while(w && w->offset != wp->offset) + w = w->next; + if (wp0 && !w) + continue; + + /* Mark done if found in provided list */ + if (w) + w->on_disk = wp->on_disk; + + if (!wp->on_disk) + continue; + + if (zap) + do_wipe_real(pr, devname, wp, noact, quiet); + } + + for (w = wp0; w != NULL; w = w->next) { + if (!w->on_disk && !quiet) + warnx(_("%s: offset 0x%jx not found"), devname, w->offset); + } + + fsync(blkid_probe_get_fd(pr)); + close(blkid_probe_get_fd(pr)); + blkid_free_probe(pr); + free_wipe(wp0); + + return wp; +} + + +static void __attribute__((__noreturn__)) +usage(FILE *out) +{ + fputs(_("\nUsage:\n"), out); + fprintf(out, + _(" %s [options] <device>\n"), program_invocation_short_name); + + fputs(_("\nOptions:\n"), out); + fputs(_(" -a, --all wipe all magic strings (BE CAREFUL!)\n" + " -h, --help show this help text\n" + " -n, --no-act do everything except the actual write() call\n" + " -o, --offset <num> offset to erase, in bytes\n" + " -p, --parsable print out in parsable instead of printable format\n" + " -q, --quiet suppress output messages\n" + " -t, --types <list> limit the set of filesystem, RAIDs or partition tables\n" + " -V, --version output version information and exit\n"), out); + + fprintf(out, _("\nFor more information see wipefs(8).\n")); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + + +int +main(int argc, char **argv) +{ + struct wipe_desc *wp0 = NULL, *wp; + int c, all = 0, has_offset = 0, noact = 0, quiet = 0; + int mode = WP_MODE_PRETTY; + + static const struct option longopts[] = { + { "all", 0, 0, 'a' }, + { "help", 0, 0, 'h' }, + { "no-act", 0, 0, 'n' }, + { "offset", 1, 0, 'o' }, + { "parsable", 0, 0, 'p' }, + { "quiet", 0, 0, 'q' }, + { "types", 1, 0, 't' }, + { "version", 0, 0, 'V' }, + { NULL, 0, 0, 0 } + }; + + static const ul_excl_t excl[] = { /* rows and cols in in ASCII order */ + { 'a','o' }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((c = getopt_long(argc, argv, "ahno:pqt:V", longopts, NULL)) != -1) { + + err_exclusive_options(c, longopts, excl, excl_st); + + switch(c) { + case 'a': + all++; + break; + case 'h': + usage(stdout); + break; + case 'n': + noact++; + break; + case 'o': + wp0 = add_offset(wp0, strtosize_or_err(optarg, + _("invalid offset argument")), 1); + has_offset++; + break; + case 'p': + mode = WP_MODE_PARSABLE; + break; + case 'q': + quiet++; + break; + case 't': + type_pattern = optarg; + break; + case 'V': + printf(_("%s from %s\n"), program_invocation_short_name, + PACKAGE_STRING); + return EXIT_SUCCESS; + default: + usage(stderr); + break; + } + } + + if (optind == argc) + usage(stderr); + + if (!all && !has_offset) { + /* + * Print only + */ + while (optind < argc) { + wp0 = read_offsets(NULL, argv[optind++]); + if (wp0) + print_all(wp0, mode); + free_wipe(wp0); + } + } else { + /* + * Erase + */ + while (optind < argc) { + wp = clone_offset(wp0); + wp = do_wipe(wp, argv[optind++], noact, all, quiet); + free_wipe(wp); + } + } + + return EXIT_SUCCESS; +} |