summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 02:42:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 02:42:50 +0000
commit8cb83eee5a58b1fad74c34094ce3afb9e430b5a4 (patch)
treea9b2e7baeca1be40eb734371e3c8b11b02294497 /lib
parentInitial commit. (diff)
downloadutil-linux-upstream.tar.xz
util-linux-upstream.zip
Adding upstream version 2.33.1.upstream/2.33.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib')
-rw-r--r--lib/Makemodule.am175
-rw-r--r--lib/blkdev.c400
-rw-r--r--lib/canonicalize.c194
-rw-r--r--lib/color-names.c58
-rw-r--r--lib/colors.c898
-rw-r--r--lib/cpuset.c397
-rw-r--r--lib/crc32.c142
-rw-r--r--lib/crc32c.c102
-rw-r--r--lib/env.c109
-rw-r--r--lib/exec_shell.c51
-rw-r--r--lib/fileutils.c186
-rw-r--r--lib/idcache.c113
-rw-r--r--lib/ismounted.c395
-rw-r--r--lib/langinfo.c121
-rw-r--r--lib/linux_version.c71
-rw-r--r--lib/loopdev.c1795
-rw-r--r--lib/mangle.c168
-rw-r--r--lib/match.c53
-rw-r--r--lib/mbsalign.c576
-rw-r--r--lib/mbsedit.c225
-rw-r--r--lib/md5.c257
-rw-r--r--lib/monotonic.c70
-rw-r--r--lib/pager.c317
-rw-r--r--lib/parse-date.c3369
-rw-r--r--lib/parse-date.y1626
-rw-r--r--lib/path.c1226
-rw-r--r--lib/plymouth-ctrl.c144
-rw-r--r--lib/procutils.c308
-rw-r--r--lib/pwdutils.c96
-rw-r--r--lib/randutils.c235
-rw-r--r--lib/setproctitle.c75
-rw-r--r--lib/sha1.c256
-rw-r--r--lib/signames.c204
-rw-r--r--lib/strutils.c1029
-rw-r--r--lib/strv.c403
-rw-r--r--lib/swapprober.c49
-rw-r--r--lib/sysfs.c1099
-rw-r--r--lib/terminal-colors.d.5192
-rw-r--r--lib/timer.c65
-rw-r--r--lib/timeutils.c594
-rw-r--r--lib/ttyutils.c143
41 files changed, 17986 insertions, 0 deletions
diff --git a/lib/Makemodule.am b/lib/Makemodule.am
new file mode 100644
index 0000000..862a06c
--- /dev/null
+++ b/lib/Makemodule.am
@@ -0,0 +1,175 @@
+
+noinst_LTLIBRARIES += libcommon.la
+libcommon_la_CFLAGS = $(AM_CFLAGS)
+libcommon_la_SOURCES = \
+ lib/blkdev.c \
+ lib/canonicalize.c \
+ lib/crc32.c \
+ lib/crc32c.c \
+ lib/env.c \
+ lib/idcache.c \
+ lib/fileutils.c \
+ lib/ismounted.c \
+ lib/color-names.c \
+ lib/mangle.c \
+ lib/match.c \
+ lib/mbsalign.c \
+ lib/mbsedit.c\
+ lib/md5.c \
+ lib/pager.c \
+ lib/parse-date.y \
+ lib/pwdutils.c \
+ lib/randutils.c \
+ lib/setproctitle.c \
+ lib/strutils.c \
+ lib/timeutils.c \
+ lib/ttyutils.c \
+ lib/exec_shell.c \
+ lib/strv.c \
+ lib/sha1.c \
+ lib/signames.c
+
+if LINUX
+libcommon_la_SOURCES += \
+ lib/linux_version.c \
+ lib/loopdev.c
+endif
+
+if USE_PLYMOUTH_SUPPORT
+libcommon_la_SOURCES += lib/plymouth-ctrl.c
+endif
+
+if !HAVE_LANGINFO
+libcommon_la_SOURCES += lib/langinfo.c
+endif
+
+if HAVE_CPU_SET_T
+libcommon_la_SOURCES += lib/cpuset.c
+endif
+
+if HAVE_OPENAT
+if HAVE_DIRFD
+libcommon_la_SOURCES += lib/path.c
+libcommon_la_SOURCES += lib/procutils.c
+libcommon_la_SOURCES += lib/sysfs.c
+endif
+endif
+
+noinst_LTLIBRARIES += libtcolors.la
+libtcolors_la_CFLAGS = $(AM_CFLAGS)
+libtcolors_la_SOURCES = lib/colors.c lib/color-names.c include/colors.h include/color-names.h
+libtcolors_la_LIBADD =
+# tinfo or ncurses are optional
+if HAVE_TINFO
+libtcolors_la_LIBADD += $(TINFO_LIBS)
+libtcolors_la_CFLAGS += $(TINFO_CFLAGS)
+else
+if HAVE_NCURSES
+libtcolors_la_LIBADD += $(NCURSES_LIBS)
+libtcolors_la_CFLAGS += $(NCURSES_CFLAGS)
+endif
+endif # !HAVE_TINFO
+
+dist_man_MANS += lib/terminal-colors.d.5
+
+
+check_PROGRAMS += \
+ test_blkdev \
+ test_canonicalize \
+ test_colors \
+ test_fileutils \
+ test_ismounted \
+ test_pwdutils \
+ test_mangle \
+ test_randutils \
+ test_strutils \
+ test_ttyutils \
+ test_timeutils
+
+
+
+if LINUX
+if HAVE_CPU_SET_T
+check_PROGRAMS += test_cpuset
+endif
+check_PROGRAMS += \
+ test_sysfs \
+ test_pager
+endif
+
+if HAVE_OPENAT
+if HAVE_DIRFD
+check_PROGRAMS += test_procutils
+check_PROGRAMS += test_path
+endif
+endif
+
+test_ttyutils_SOURCES = lib/ttyutils.c
+test_ttyutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_TTYUTILS
+test_ttyutils_LDADD = $(LDADD) libcommon.la
+
+test_blkdev_SOURCES = lib/blkdev.c
+test_blkdev_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_BLKDEV
+test_blkdev_LDADD = $(LDADD) libcommon.la
+
+test_ismounted_SOURCES = lib/ismounted.c
+test_ismounted_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_ISMOUNTED
+test_ismounted_LDADD = libcommon.la $(LDADD)
+
+test_mangle_SOURCES = lib/mangle.c
+test_mangle_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_MANGLE
+
+test_strutils_SOURCES = lib/strutils.c
+test_strutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_STRUTILS
+
+test_colors_SOURCES = lib/colors.c lib/color-names.c
+test_colors_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_COLORS
+test_colors_LDADD = $(LDADD) libtcolors.la
+
+test_randutils_SOURCES = lib/randutils.c
+test_randutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_RANDUTILS
+
+if HAVE_OPENAT
+if HAVE_DIRFD
+test_procutils_SOURCES = lib/procutils.c
+test_procutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PROCUTILS
+
+test_path_SOURCES = lib/path.c lib/fileutils.c
+if HAVE_CPU_SET_T
+test_path_SOURCES += lib/cpuset.c
+endif
+test_path_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PATH
+test_path_LDADD = $(LDADD)
+endif
+endif
+
+if LINUX
+test_cpuset_SOURCES = lib/cpuset.c
+test_cpuset_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_CPUSET
+
+test_sysfs_SOURCES = lib/sysfs.c lib/path.c lib/fileutils.c
+if HAVE_CPU_SET_T
+test_sysfs_SOURCES += lib/cpuset.c
+endif
+test_sysfs_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_SYSFS
+test_sysfs_LDADD = $(LDADD)
+
+test_pager_SOURCES = lib/pager.c
+test_pager_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PAGER
+
+check_PROGRAMS += test_linux_version
+test_linux_version_SOURCES = lib/linux_version.c
+test_linux_version_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_LINUXVERSION
+endif
+
+test_fileutils_SOURCES = lib/fileutils.c
+test_fileutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_FILEUTILS
+
+test_canonicalize_SOURCES = lib/canonicalize.c
+test_canonicalize_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_CANONICALIZE
+
+test_timeutils_SOURCES = lib/timeutils.c lib/strutils.c
+test_timeutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_TIMEUTILS
+
+test_pwdutils_SOURCES = lib/pwdutils.c
+test_pwdutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM
diff --git a/lib/blkdev.c b/lib/blkdev.c
new file mode 100644
index 0000000..0616142
--- /dev/null
+++ b/lib/blkdev.c
@@ -0,0 +1,400 @@
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <stdint.h>
+
+#ifdef HAVE_LINUX_FD_H
+#include <linux/fd.h>
+#endif
+
+#ifdef HAVE_SYS_DISKLABEL_H
+#include <sys/disklabel.h>
+#endif
+
+#ifdef HAVE_SYS_DISK_H
+# include <sys/disk.h>
+#endif
+
+#ifndef EBADFD
+# define EBADFD 77 /* File descriptor in bad state */
+#endif
+
+#include "blkdev.h"
+#include "c.h"
+#include "linux_version.h"
+#include "fileutils.h"
+#include "nls.h"
+
+static long
+blkdev_valid_offset (int fd, off_t offset) {
+ char ch;
+
+ if (lseek (fd, offset, 0) < 0)
+ return 0;
+ if (read (fd, &ch, 1) < 1)
+ return 0;
+ return 1;
+}
+
+int is_blkdev(int fd)
+{
+ struct stat st;
+ return (fstat(fd, &st) == 0 && S_ISBLK(st.st_mode));
+}
+
+off_t
+blkdev_find_size (int fd) {
+ uintmax_t high, low = 0;
+
+ for (high = 1024; blkdev_valid_offset (fd, high); ) {
+ if (high == UINTMAX_MAX)
+ return -1;
+
+ low = high;
+
+ if (high >= UINTMAX_MAX/2)
+ high = UINTMAX_MAX;
+ else
+ high *= 2;
+ }
+
+ while (low < high - 1)
+ {
+ uintmax_t mid = (low + high) / 2;
+
+ if (blkdev_valid_offset (fd, mid))
+ low = mid;
+ else
+ high = mid;
+ }
+ blkdev_valid_offset (fd, 0);
+ return (low + 1);
+}
+
+/* get size in bytes */
+int
+blkdev_get_size(int fd, unsigned long long *bytes)
+{
+#ifdef DKIOCGETBLOCKCOUNT
+ /* Apple Darwin */
+ if (ioctl(fd, DKIOCGETBLOCKCOUNT, bytes) >= 0) {
+ *bytes <<= 9;
+ return 0;
+ }
+#endif
+
+#ifdef BLKGETSIZE64
+ if (ioctl(fd, BLKGETSIZE64, bytes) >= 0)
+ return 0;
+#endif
+
+#ifdef BLKGETSIZE
+ {
+ unsigned long size;
+
+ if (ioctl(fd, BLKGETSIZE, &size) >= 0) {
+ *bytes = ((unsigned long long)size << 9);
+ return 0;
+ }
+ }
+
+#endif /* BLKGETSIZE */
+
+#ifdef DIOCGMEDIASIZE
+ /* FreeBSD */
+ if (ioctl(fd, DIOCGMEDIASIZE, bytes) >= 0)
+ return 0;
+#endif
+
+#ifdef FDGETPRM
+ {
+ struct floppy_struct this_floppy;
+
+ if (ioctl(fd, FDGETPRM, &this_floppy) >= 0) {
+ *bytes = ((unsigned long long) this_floppy.size) << 9;
+ return 0;
+ }
+ }
+#endif /* FDGETPRM */
+
+#if defined(HAVE_SYS_DISKLABEL_H) && defined(DIOCGDINFO)
+ {
+ /*
+ * This code works for FreeBSD 4.11 i386, except for the full device
+ * (such as /dev/ad0). It doesn't work properly for newer FreeBSD
+ * though. FreeBSD >= 5.0 should be covered by the DIOCGMEDIASIZE
+ * above however.
+ *
+ * Note that FreeBSD >= 4.0 has disk devices as unbuffered (raw,
+ * character) devices, so we need to check for S_ISCHR, too.
+ */
+ int part = -1;
+ struct disklabel lab;
+ struct partition *pp;
+ struct stat st;
+
+ if ((fstat(fd, &st) >= 0) &&
+ (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)))
+ part = st.st_rdev & 7;
+
+ if (part >= 0 && (ioctl(fd, DIOCGDINFO, (char *)&lab) >= 0)) {
+ pp = &lab.d_partitions[part];
+ if (pp->p_size) {
+ *bytes = pp->p_size << 9;
+ return 0;
+ }
+ }
+ }
+#endif /* defined(HAVE_SYS_DISKLABEL_H) && defined(DIOCGDINFO) */
+
+ {
+ struct stat st;
+
+ if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode)) {
+ *bytes = st.st_size;
+ return 0;
+ }
+ if (!S_ISBLK(st.st_mode))
+ return -1;
+ }
+
+ *bytes = blkdev_find_size(fd);
+ return 0;
+}
+
+/* get 512-byte sector count */
+int
+blkdev_get_sectors(int fd, unsigned long long *sectors)
+{
+ unsigned long long bytes;
+
+ if (blkdev_get_size(fd, &bytes) == 0) {
+ *sectors = (bytes >> 9);
+ return 0;
+ }
+
+ return -1;
+}
+
+/*
+ * Get logical sector size.
+ *
+ * This is the smallest unit the storage device can
+ * address. It is typically 512 bytes.
+ */
+#ifdef BLKSSZGET
+int blkdev_get_sector_size(int fd, int *sector_size)
+{
+ if (ioctl(fd, BLKSSZGET, sector_size) >= 0)
+ return 0;
+ return -1;
+}
+#else
+int blkdev_get_sector_size(int fd __attribute__((__unused__)), int *sector_size)
+{
+ *sector_size = DEFAULT_SECTOR_SIZE;
+ return 0;
+}
+#endif
+
+/*
+ * Get physical block device size. The BLKPBSZGET is supported since Linux
+ * 2.6.32. For old kernels is probably the best to assume that physical sector
+ * size is the same as logical sector size.
+ *
+ * Example:
+ *
+ * rc = blkdev_get_physector_size(fd, &physec);
+ * if (rc || physec == 0) {
+ * rc = blkdev_get_sector_size(fd, &physec);
+ * if (rc)
+ * physec = DEFAULT_SECTOR_SIZE;
+ * }
+ */
+#ifdef BLKPBSZGET
+int blkdev_get_physector_size(int fd, int *sector_size)
+{
+ if (ioctl(fd, BLKPBSZGET, &sector_size) >= 0)
+ return 0;
+ return -1;
+}
+#else
+int blkdev_get_physector_size(int fd __attribute__((__unused__)), int *sector_size)
+{
+ *sector_size = DEFAULT_SECTOR_SIZE;
+ return 0;
+}
+#endif
+
+/*
+ * Return the alignment status of a device
+ */
+#ifdef BLKALIGNOFF
+int blkdev_is_misaligned(int fd)
+{
+ int aligned;
+
+ if (ioctl(fd, BLKALIGNOFF, &aligned) < 0)
+ return 0; /* probably kernel < 2.6.32 */
+ /*
+ * Note that kernel returns -1 as alignment offset if no compatible
+ * sizes and alignments exist for stacked devices
+ */
+ return aligned != 0 ? 1 : 0;
+}
+#else
+int blkdev_is_misaligned(int fd __attribute__((__unused__)))
+{
+ return 0;
+}
+#endif
+
+int open_blkdev_or_file(const struct stat *st, const char *name, const int oflag)
+{
+ int fd;
+
+ if (S_ISBLK(st->st_mode)) {
+ fd = open(name, oflag | O_EXCL);
+ } else
+ fd = open(name, oflag);
+ if (-1 < fd && !is_same_inode(fd, st)) {
+ close(fd);
+ errno = EBADFD;
+ return -1;
+ }
+ if (-1 < fd && S_ISBLK(st->st_mode) && blkdev_is_misaligned(fd))
+ warnx(_("warning: %s is misaligned"), name);
+ return fd;
+}
+
+#ifdef CDROM_GET_CAPABILITY
+int blkdev_is_cdrom(int fd)
+{
+ int ret;
+
+ if ((ret = ioctl(fd, CDROM_GET_CAPABILITY, NULL)) < 0)
+ return 0;
+ else
+ return ret;
+}
+#else
+int blkdev_is_cdrom(int fd __attribute__((__unused__)))
+{
+ return 0;
+}
+#endif
+
+/*
+ * Get kernel's interpretation of the device's geometry.
+ *
+ * Returns the heads and sectors - but not cylinders
+ * as it's truncated for disks with more than 65535 tracks.
+ *
+ * Note that this is deprecated in favor of LBA addressing.
+ */
+#ifdef HDIO_GETGEO
+int blkdev_get_geometry(int fd, unsigned int *h, unsigned int *s)
+{
+ struct hd_geometry geometry;
+
+ if (ioctl(fd, HDIO_GETGEO, &geometry) == 0) {
+ *h = geometry.heads;
+ *s = geometry.sectors;
+ return 0;
+ }
+#else
+int blkdev_get_geometry(int fd __attribute__((__unused__)),
+ unsigned int *h, unsigned int *s)
+{
+ *h = 0;
+ *s = 0;
+#endif
+ return -1;
+}
+
+/*
+ * Convert scsi type to human readable string.
+ */
+const char *blkdev_scsi_type_to_name(int type)
+{
+ switch (type) {
+ case SCSI_TYPE_DISK:
+ return "disk";
+ case SCSI_TYPE_TAPE:
+ return "tape";
+ case SCSI_TYPE_PRINTER:
+ return "printer";
+ case SCSI_TYPE_PROCESSOR:
+ return "processor";
+ case SCSI_TYPE_WORM:
+ return "worm";
+ case SCSI_TYPE_ROM:
+ return "rom";
+ case SCSI_TYPE_SCANNER:
+ return "scanner";
+ case SCSI_TYPE_MOD:
+ return "mo-disk";
+ case SCSI_TYPE_MEDIUM_CHANGER:
+ return "changer";
+ case SCSI_TYPE_COMM:
+ return "comm";
+ case SCSI_TYPE_RAID:
+ return "raid";
+ case SCSI_TYPE_ENCLOSURE:
+ return "enclosure";
+ case SCSI_TYPE_RBC:
+ return "rbc";
+ case SCSI_TYPE_OSD:
+ return "osd";
+ case SCSI_TYPE_NO_LUN:
+ return "no-lun";
+ default:
+ break;
+ }
+ return NULL;
+}
+
+#ifdef TEST_PROGRAM_BLKDEV
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+int
+main(int argc, char **argv)
+{
+ unsigned long long bytes;
+ unsigned long long sectors;
+ int sector_size, phy_sector_size;
+ int fd;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s device\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ if ((fd = open(argv[1], O_RDONLY|O_CLOEXEC)) < 0)
+ err(EXIT_FAILURE, "open %s failed", argv[1]);
+
+ if (blkdev_get_size(fd, &bytes) < 0)
+ err(EXIT_FAILURE, "blkdev_get_size() failed");
+ if (blkdev_get_sectors(fd, &sectors) < 0)
+ err(EXIT_FAILURE, "blkdev_get_sectors() failed");
+ if (blkdev_get_sector_size(fd, &sector_size) < 0)
+ err(EXIT_FAILURE, "blkdev_get_sector_size() failed");
+ if (blkdev_get_physector_size(fd, &phy_sector_size) < 0)
+ err(EXIT_FAILURE, "blkdev_get_physector_size() failed");
+
+ printf(" bytes: %llu\n", bytes);
+ printf(" sectors: %llu\n", sectors);
+ printf(" sector size: %d\n", sector_size);
+ printf("phy-sector size: %d\n", phy_sector_size);
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM_BLKDEV */
diff --git a/lib/canonicalize.c b/lib/canonicalize.c
new file mode 100644
index 0000000..f3a2a3a
--- /dev/null
+++ b/lib/canonicalize.c
@@ -0,0 +1,194 @@
+/*
+ * canonicalize.c -- canonicalize pathname by removing symlinks
+ *
+ * This file may be distributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Copyright (C) 2009-2013 Karel Zak <kzak@redhat.com>
+ */
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "canonicalize.h"
+#include "pathnames.h"
+
+/*
+ * Converts private "dm-N" names to "/dev/mapper/<name>"
+ *
+ * Since 2.6.29 (patch 784aae735d9b0bba3f8b9faef4c8b30df3bf0128) kernel sysfs
+ * provides the real DM device names in /sys/block/<ptname>/dm/name
+ */
+char *__canonicalize_dm_name(const char *prefix, const char *ptname)
+{
+ FILE *f;
+ size_t sz;
+ char path[256], name[sizeof(path) - sizeof(_PATH_DEV_MAPPER)], *res = NULL;
+
+ if (!ptname || !*ptname)
+ return NULL;
+
+ if (!prefix)
+ prefix = "";
+
+ snprintf(path, sizeof(path), "%s/sys/block/%s/dm/name", prefix, ptname);
+ if (!(f = fopen(path, "r" UL_CLOEXECSTR)))
+ return NULL;
+
+ /* read "<name>\n" from sysfs */
+ if (fgets(name, sizeof(name), f) && (sz = strlen(name)) > 1) {
+ name[sz - 1] = '\0';
+ snprintf(path, sizeof(path), _PATH_DEV_MAPPER "/%s", name);
+
+ if (prefix || access(path, F_OK) == 0)
+ res = strdup(path);
+ }
+ fclose(f);
+ return res;
+}
+
+char *canonicalize_dm_name(const char *ptname)
+{
+ return __canonicalize_dm_name(NULL, ptname);
+}
+
+static int is_dm_devname(char *canonical, char **name)
+{
+ struct stat sb;
+ char *p = strrchr(canonical, '/');
+
+ *name = NULL;
+
+ if (!p
+ || strncmp(p, "/dm-", 4) != 0
+ || !isdigit(*(p + 4))
+ || stat(canonical, &sb) != 0
+ || !S_ISBLK(sb.st_mode))
+ return 0;
+
+ *name = p + 1;
+ return 1;
+}
+
+/*
+ * This function does not canonicalize the path! It just prepends CWD before a
+ * relative path. If the path is no relative than returns NULL. The path does
+ * not have to exist.
+ */
+char *absolute_path(const char *path)
+{
+ char cwd[PATH_MAX], *res, *p;
+ size_t psz, csz;
+
+ if (!is_relative_path(path)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ if (!getcwd(cwd, sizeof(cwd)))
+ return NULL;
+
+ /* simple clean up */
+ if (startswith(path, "./"))
+ path += 2;
+ else if (strcmp(path, ".") == 0)
+ path = NULL;
+
+ if (!path || !*path)
+ return strdup(cwd);
+
+ csz = strlen(cwd);
+ psz = strlen(path);
+
+ p = res = malloc(csz + 1 + psz + 1);
+ if (!res)
+ return NULL;
+
+ memcpy(p, cwd, csz);
+ p += csz;
+ *p++ = '/';
+ memcpy(p, path, psz + 1);
+
+ return res;
+}
+
+char *canonicalize_path(const char *path)
+{
+ char *canonical, *dmname;
+
+ if (!path || !*path)
+ return NULL;
+
+ canonical = realpath(path, NULL);
+ if (!canonical)
+ return strdup(path);
+
+ if (is_dm_devname(canonical, &dmname)) {
+ char *dm = canonicalize_dm_name(dmname);
+ if (dm) {
+ free(canonical);
+ return dm;
+ }
+ }
+
+ return canonical;
+}
+
+char *canonicalize_path_restricted(const char *path)
+{
+ char *canonical, *dmname;
+ int errsv;
+ uid_t euid;
+ gid_t egid;
+
+ if (!path || !*path)
+ return NULL;
+
+ euid = geteuid();
+ egid = getegid();
+
+ /* drop permissions */
+ if (setegid(getgid()) < 0 || seteuid(getuid()) < 0)
+ return NULL;
+
+ errsv = errno = 0;
+
+ canonical = realpath(path, NULL);
+ if (!canonical)
+ errsv = errno;
+ else if (is_dm_devname(canonical, &dmname)) {
+ char *dm = canonicalize_dm_name(dmname);
+ if (dm) {
+ free(canonical);
+ canonical = dm;
+ }
+ }
+
+ /* restore */
+ if (setegid(egid) < 0 || seteuid(euid) < 0) {
+ free(canonical);
+ return NULL;
+ }
+
+ errno = errsv;
+ return canonical;
+}
+
+
+#ifdef TEST_PROGRAM_CANONICALIZE
+int main(int argc, char **argv)
+{
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <device>\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ fprintf(stdout, "orig: %s\n", argv[1]);
+ fprintf(stdout, "real: %s\n", canonicalize_path(argv[1]));
+ exit(EXIT_SUCCESS);
+}
+#endif
diff --git a/lib/color-names.c b/lib/color-names.c
new file mode 100644
index 0000000..72add50
--- /dev/null
+++ b/lib/color-names.c
@@ -0,0 +1,58 @@
+
+#include "c.h"
+#include "color-names.h"
+
+struct ul_color_name {
+ const char *name;
+ const char *seq;
+};
+
+/*
+ * qsort/bsearch buddy
+ */
+static int cmp_color_name(const void *a0, const void *b0)
+{
+ const struct ul_color_name
+ *a = (const struct ul_color_name *) a0,
+ *b = (const struct ul_color_name *) b0;
+ return strcmp(a->name, b->name);
+}
+
+/*
+ * Maintains human readable color names
+ */
+const char *color_sequence_from_colorname(const char *str)
+{
+ static const struct ul_color_name basic_schemes[] = {
+ { "black", UL_COLOR_BLACK },
+ { "blink", UL_COLOR_BLINK },
+ { "blue", UL_COLOR_BLUE },
+ { "bold", UL_COLOR_BOLD },
+ { "brown", UL_COLOR_BROWN },
+ { "cyan", UL_COLOR_CYAN },
+ { "darkgray", UL_COLOR_DARK_GRAY },
+ { "gray", UL_COLOR_GRAY },
+ { "green", UL_COLOR_GREEN },
+ { "halfbright", UL_COLOR_HALFBRIGHT },
+ { "lightblue", UL_COLOR_BOLD_BLUE },
+ { "lightcyan", UL_COLOR_BOLD_CYAN },
+ { "lightgray,", UL_COLOR_GRAY },
+ { "lightgreen", UL_COLOR_BOLD_GREEN },
+ { "lightmagenta", UL_COLOR_BOLD_MAGENTA },
+ { "lightred", UL_COLOR_BOLD_RED },
+ { "magenta", UL_COLOR_MAGENTA },
+ { "red", UL_COLOR_RED },
+ { "reset", UL_COLOR_RESET, },
+ { "reverse", UL_COLOR_REVERSE },
+ { "yellow", UL_COLOR_BOLD_YELLOW },
+ };
+ struct ul_color_name key = { .name = str }, *res;
+
+ if (!str)
+ return NULL;
+
+ res = bsearch(&key, basic_schemes, ARRAY_SIZE(basic_schemes),
+ sizeof(struct ul_color_name),
+ cmp_color_name);
+ return res ? res->seq : NULL;
+}
diff --git a/lib/colors.c b/lib/colors.c
new file mode 100644
index 0000000..f636ecc
--- /dev/null
+++ b/lib/colors.c
@@ -0,0 +1,898 @@
+/*
+ * Copyright (C) 2012 Ondrej Oprala <ooprala@redhat.com>
+ * Copyright (C) 2012-2014 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be distributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <assert.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <ctype.h>
+
+#if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
+# if defined(HAVE_NCURSESW_NCURSES_H)
+# include <ncursesw/ncurses.h>
+# elif defined(HAVE_NCURSES_NCURSES_H)
+# include <ncurses/ncurses.h>
+# elif defined(HAVE_NCURSES_H)
+# include <ncurses.h>
+# endif
+# if defined(HAVE_NCURSESW_TERM_H)
+# include <ncursesw/term.h>
+# elif defined(HAVE_NCURSES_TERM_H)
+# include <ncurses/term.h>
+# elif defined(HAVE_TERM_H)
+# include <term.h>
+# endif
+#endif
+
+#include "c.h"
+#include "colors.h"
+#include "pathnames.h"
+#include "strutils.h"
+
+#include "debug.h"
+
+/*
+ * Default behavior, maybe be override by terminal-colors.d/{enable,disable}.
+ */
+#ifdef USE_COLORS_BY_DEFAULT
+# define UL_COLORMODE_DEFAULT UL_COLORMODE_AUTO /* check isatty() */
+#else
+# define UL_COLORMODE_DEFAULT UL_COLORMODE_NEVER /* no colors by default */
+#endif
+
+/*
+ * terminal-colors.d debug stuff
+ */
+static UL_DEBUG_DEFINE_MASK(termcolors);
+UL_DEBUG_DEFINE_MASKNAMES(termcolors) = UL_DEBUG_EMPTY_MASKNAMES;
+
+#define TERMCOLORS_DEBUG_INIT (1 << 1)
+#define TERMCOLORS_DEBUG_CONF (1 << 2)
+#define TERMCOLORS_DEBUG_SCHEME (1 << 3)
+#define TERMCOLORS_DEBUG_ALL 0xFFFF
+
+#define DBG(m, x) __UL_DBG(termcolors, TERMCOLORS_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(termcolors, TERMCOLORS_DEBUG_, m, x)
+
+/*
+ * terminal-colors.d file types
+ */
+enum {
+ UL_COLORFILE_DISABLE, /* .disable */
+ UL_COLORFILE_ENABLE, /* .enable */
+ UL_COLORFILE_SCHEME, /* .scheme */
+
+ __UL_COLORFILE_COUNT
+};
+
+struct ul_color_scheme {
+ char *name;
+ char *seq;
+};
+
+/*
+ * Global colors control struct
+ *
+ * The terminal-colors.d/ evaluation is based on "scores":
+ *
+ * filename score
+ * ---------------------------------------
+ * type 1
+ * @termname.type 10 + 1
+ * utilname.type 20 + 1
+ * utilname@termname.type 20 + 10 + 1
+ *
+ * the match with higher score wins. The score is per type.
+ */
+struct ul_color_ctl {
+ const char *utilname; /* util name */
+ const char *termname; /* terminal name ($TERM) */
+
+ char *sfile; /* path to scheme */
+
+ struct ul_color_scheme *schemes; /* array with color schemes */
+ size_t nschemes; /* number of the items */
+ size_t schemes_sz; /* number of the allocated items */
+
+ int mode; /* UL_COLORMODE_* */
+ unsigned int has_colors : 1, /* based on mode and scores[] */
+ disabled : 1, /* disable colors */
+ cs_configured : 1, /* color schemes read */
+ configured : 1; /* terminal-colors.d parsed */
+
+ int scores[__UL_COLORFILE_COUNT]; /* the best match */
+};
+
+/*
+ * Control struct, globally shared.
+ */
+static struct ul_color_ctl ul_colors;
+
+static void colors_free_schemes(struct ul_color_ctl *cc);
+static int colors_read_schemes(struct ul_color_ctl *cc);
+
+/*
+ * qsort/bsearch buddy
+ */
+static int cmp_scheme_name(const void *a0, const void *b0)
+{
+ const struct ul_color_scheme *a = (const struct ul_color_scheme *) a0,
+ *b = (const struct ul_color_scheme *) b0;
+ return strcmp(a->name, b->name);
+}
+
+/*
+ * Resets control struct (note that we don't allocate the struct)
+ */
+static void colors_reset(struct ul_color_ctl *cc)
+{
+ if (!cc)
+ return;
+
+ colors_free_schemes(cc);
+
+ free(cc->sfile);
+
+ cc->sfile = NULL;
+ cc->utilname = NULL;
+ cc->termname = NULL;
+ cc->mode = UL_COLORMODE_UNDEF;
+
+ memset(cc->scores, 0, sizeof(cc->scores));
+}
+
+static void colors_debug(struct ul_color_ctl *cc)
+{
+ size_t i;
+
+ if (!cc)
+ return;
+
+ printf("Colors:\n");
+ printf("\tutilname = '%s'\n", cc->utilname);
+ printf("\ttermname = '%s'\n", cc->termname);
+ printf("\tscheme file = '%s'\n", cc->sfile);
+ printf("\tmode = %s\n",
+ cc->mode == UL_COLORMODE_UNDEF ? "undefined" :
+ cc->mode == UL_COLORMODE_AUTO ? "auto" :
+ cc->mode == UL_COLORMODE_NEVER ? "never" :
+ cc->mode == UL_COLORMODE_ALWAYS ? "always" : "???");
+ printf("\thas_colors = %d\n", cc->has_colors);
+ printf("\tdisabled = %d\n", cc->disabled);
+ printf("\tconfigured = %d\n", cc->configured);
+ printf("\tcs configured = %d\n", cc->cs_configured);
+
+ fputc('\n', stdout);
+
+ for (i = 0; i < ARRAY_SIZE(cc->scores); i++)
+ printf("\tscore %s = %d\n",
+ i == UL_COLORFILE_DISABLE ? "disable" :
+ i == UL_COLORFILE_ENABLE ? "enable" :
+ i == UL_COLORFILE_SCHEME ? "scheme" : "???",
+ cc->scores[i]);
+
+ fputc('\n', stdout);
+
+ for (i = 0; i < cc->nschemes; i++) {
+ printf("\tscheme #%02zu ", i);
+ color_scheme_enable(cc->schemes[i].name, NULL);
+ fputs(cc->schemes[i].name, stdout);
+ color_disable();
+ fputc('\n', stdout);
+ }
+ fputc('\n', stdout);
+}
+
+/*
+ * Parses [[<utilname>][@<termname>].]<type>
+ */
+static int filename_to_tokens(const char *str,
+ const char **name, size_t *namesz,
+ const char **term, size_t *termsz,
+ int *filetype)
+{
+ const char *type_start, *term_start, *p;
+
+ if (!str || !*str || *str == '.' || strlen(str) > PATH_MAX)
+ return -EINVAL;
+
+ /* parse .type */
+ p = strrchr(str, '.');
+ type_start = p ? p + 1 : str;
+
+ if (strcmp(type_start, "disable") == 0)
+ *filetype = UL_COLORFILE_DISABLE;
+ else if (strcmp(type_start, "enable") == 0)
+ *filetype = UL_COLORFILE_ENABLE;
+ else if (strcmp(type_start, "scheme") == 0)
+ *filetype = UL_COLORFILE_SCHEME;
+ else {
+ DBG(CONF, ul_debug("unknown type '%s'", type_start));
+ return 1; /* unknown type */
+ }
+
+ if (type_start == str)
+ return 0; /* "type" only */
+
+ /* parse @termname */
+ p = strchr(str, '@');
+ term_start = p ? p + 1 : NULL;
+ if (term_start) {
+ *term = term_start;
+ *termsz = type_start - term_start - 1;
+ if (term_start - 1 == str)
+ return 0; /* "@termname.type" */
+ }
+
+ /* parse utilname */
+ p = term_start ? term_start : type_start;
+ *name = str;
+ *namesz = p - str - 1;
+
+ return 0;
+}
+
+/*
+ * Scans @dirname and select the best matches for UL_COLORFILE_* types.
+ * The result is stored to cc->scores. The path to the best "scheme"
+ * file is stored to cc->scheme.
+ */
+static int colors_readdir(struct ul_color_ctl *cc, const char *dirname)
+{
+ DIR *dir;
+ int rc = 0;
+ struct dirent *d;
+ char sfile[PATH_MAX] = { '\0' };
+ size_t namesz, termsz;
+
+ if (!dirname || !cc || !cc->utilname || !*cc->utilname)
+ return -EINVAL;
+
+ DBG(CONF, ul_debug("reading dir: '%s'", dirname));
+
+ dir = opendir(dirname);
+ if (!dir)
+ return -errno;
+
+ namesz = strlen(cc->utilname);
+ termsz = cc->termname ? strlen(cc->termname) : 0;
+
+ while ((d = readdir(dir))) {
+ int type, score = 1;
+ const char *tk_name = NULL, *tk_term = NULL;
+ size_t tk_namesz = 0, tk_termsz = 0;
+
+ if (*d->d_name == '.')
+ continue;
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (d->d_type != DT_UNKNOWN && d->d_type != DT_LNK &&
+ d->d_type != DT_REG)
+ continue;
+#endif
+ if (filename_to_tokens(d->d_name,
+ &tk_name, &tk_namesz,
+ &tk_term, &tk_termsz, &type) != 0)
+ continue;
+
+ /* count theoretical score before we check names to avoid
+ * unnecessary strcmp() */
+ if (tk_name)
+ score += 20;
+ if (tk_term)
+ score += 10;
+
+ DBG(CONF, ul_debug("item '%s': score=%d "
+ "[cur: %d, name(%zu): %s, term(%zu): %s]",
+ d->d_name, score, cc->scores[type],
+ tk_namesz, tk_name,
+ tk_termsz, tk_term));
+
+
+ if (score < cc->scores[type])
+ continue;
+
+ /* filter out by names */
+ if (tk_namesz && (tk_namesz != namesz ||
+ strncmp(tk_name, cc->utilname, namesz) != 0))
+ continue;
+
+ if (tk_termsz && (termsz == 0 || tk_termsz != termsz ||
+ strncmp(tk_term, cc->termname, termsz) != 0))
+ continue;
+
+ DBG(CONF, ul_debug("setting '%s' from %d -to-> %d",
+ type == UL_COLORFILE_SCHEME ? "scheme" :
+ type == UL_COLORFILE_DISABLE ? "disable" :
+ type == UL_COLORFILE_ENABLE ? "enable" : "???",
+ cc->scores[type], score));
+ cc->scores[type] = score;
+ if (type == UL_COLORFILE_SCHEME)
+ strncpy(sfile, d->d_name, sizeof(sfile));
+ }
+
+ if (*sfile) {
+ sfile[sizeof(sfile) - 1] = '\0';
+ if (asprintf(&cc->sfile, "%s/%s", dirname, sfile) <= 0)
+ rc = -ENOMEM;
+ }
+
+ closedir(dir);
+ return rc;
+}
+
+/* atexit() wrapper */
+static void colors_deinit(void)
+{
+ colors_reset(&ul_colors);
+}
+
+/*
+ * Returns path to $XDG_CONFIG_HOME/terminal-colors.d
+ */
+static char *colors_get_homedir(char *buf, size_t bufsz)
+{
+ char *p = getenv("XDG_CONFIG_HOME");
+
+ if (p) {
+ snprintf(buf, bufsz, "%s/" _PATH_TERMCOLORS_DIRNAME, p);
+ return buf;
+ }
+
+ p = getenv("HOME");
+ if (p) {
+ snprintf(buf, bufsz, "%s/.config/" _PATH_TERMCOLORS_DIRNAME, p);
+ return buf;
+ }
+
+ return NULL;
+}
+
+/* canonicalize sequence */
+static int cn_sequence(const char *str, char **seq)
+{
+ char *in, *out;
+ int len;
+
+ if (!str)
+ return -EINVAL;
+
+ *seq = NULL;
+
+ /* convert logical names like "red" to the real sequence */
+ if (*str != '\\' && isalpha(*str)) {
+ const char *s = color_sequence_from_colorname(str);
+ *seq = strdup(s ? s : str);
+
+ return *seq ? 0 : -ENOMEM;
+ }
+
+ /* convert xx;yy sequences to "\033[xx;yy" */
+ if ((len = asprintf(seq, "\033[%sm", str)) < 1)
+ return -ENOMEM;
+
+ for (in = *seq, out = *seq; in && *in; in++) {
+ if (*in != '\\') {
+ *out++ = *in;
+ continue;
+ }
+ switch(*(in + 1)) {
+ case 'a':
+ *out++ = '\a'; /* Bell */
+ break;
+ case 'b':
+ *out++ = '\b'; /* Backspace */
+ break;
+ case 'e':
+ *out++ = '\033'; /* Escape */
+ break;
+ case 'f':
+ *out++ = '\f'; /* Form Feed */
+ break;
+ case 'n':
+ *out++ = '\n'; /* Newline */
+ break;
+ case 'r':
+ *out++ = '\r'; /* Carriage Return */
+ break;
+ case 't':
+ *out++ = '\t'; /* Tab */
+ break;
+ case 'v':
+ *out++ = '\v'; /* Vertical Tab */
+ break;
+ case '\\':
+ *out++ = '\\'; /* Backslash */
+ break;
+ case '_':
+ *out++ = ' '; /* Space */
+ break;
+ case '#':
+ *out++ = '#'; /* Hash mark */
+ break;
+ case '?':
+ *out++ = '?'; /* Question mark */
+ break;
+ default:
+ *out++ = *in;
+ *out++ = *(in + 1);
+ break;
+ }
+ in++;
+ }
+
+ assert ((out - *seq) <= len);
+ *out = '\0';
+
+ return 0;
+}
+
+
+/*
+ * Adds one color sequence to array with color scheme.
+ * When returning success (0) this function takes ownership of
+ * @seq and @name, which have to be allocated strings.
+ */
+static int colors_add_scheme(struct ul_color_ctl *cc,
+ char *name,
+ char *seq0)
+{
+ struct ul_color_scheme *cs = NULL;
+ char *seq = NULL;
+ int rc;
+
+ if (!cc || !name || !*name || !seq0 || !*seq0)
+ return -EINVAL;
+
+ DBG(SCHEME, ul_debug("add '%s'", name));
+
+ rc = cn_sequence(seq0, &seq);
+ if (rc)
+ return rc;
+
+ rc = -ENOMEM;
+
+ /* convert logical name (e.g. "red") to real ESC code */
+ if (isalpha(*seq)) {
+ const char *s = color_sequence_from_colorname(seq);
+ char *p;
+
+ if (!s) {
+ DBG(SCHEME, ul_debug("unknown logical name: %s", seq));
+ rc = -EINVAL;
+ goto err;
+ }
+
+ p = strdup(s);
+ if (!p)
+ goto err;
+ free(seq);
+ seq = p;
+ }
+
+ /* enlarge the array */
+ if (cc->nschemes == cc->schemes_sz) {
+ void *tmp = realloc(cc->schemes, (cc->nschemes + 10)
+ * sizeof(struct ul_color_scheme));
+ if (!tmp)
+ goto err;
+ cc->schemes = tmp;
+ cc->schemes_sz = cc->nschemes + 10;
+ }
+
+ /* add a new item */
+ cs = &cc->schemes[cc->nschemes];
+ cs->seq = seq;
+ cs->name = strdup(name);
+ if (!cs->name)
+ goto err;
+
+ cc->nschemes++;
+ return 0;
+err:
+ if (cs) {
+ free(cs->seq);
+ free(cs->name);
+ cs->seq = cs->name = NULL;
+ } else
+ free(seq);
+ return rc;
+}
+
+/*
+ * Deallocates all regards to color schemes
+ */
+static void colors_free_schemes(struct ul_color_ctl *cc)
+{
+ size_t i;
+
+ DBG(SCHEME, ul_debug("free scheme"));
+
+ for (i = 0; i < cc->nschemes; i++) {
+ free(cc->schemes[i].name);
+ free(cc->schemes[i].seq);
+ }
+
+ free(cc->schemes);
+ cc->schemes = NULL;
+ cc->nschemes = 0;
+ cc->schemes_sz = 0;
+}
+
+/*
+ * The scheme configuration has to be sorted for bsearch
+ */
+static void colors_sort_schemes(struct ul_color_ctl *cc)
+{
+ if (!cc->nschemes)
+ return;
+
+ DBG(SCHEME, ul_debug("sort scheme"));
+
+ qsort(cc->schemes, cc->nschemes,
+ sizeof(struct ul_color_scheme), cmp_scheme_name);
+}
+
+/*
+ * Returns just one color scheme
+ */
+static struct ul_color_scheme *colors_get_scheme(struct ul_color_ctl *cc,
+ const char *name)
+{
+ struct ul_color_scheme key = { .name = (char *) name}, *res;
+
+ if (!cc || !name || !*name)
+ return NULL;
+
+ if (!cc->cs_configured) {
+ int rc = colors_read_schemes(cc);
+ if (rc)
+ return NULL;
+ }
+ if (!cc->nschemes)
+ return NULL;
+
+ DBG(SCHEME, ul_debug("search '%s'", name));
+
+ res = bsearch(&key, cc->schemes, cc->nschemes,
+ sizeof(struct ul_color_scheme),
+ cmp_scheme_name);
+
+ return res && res->seq ? res : NULL;
+}
+
+/*
+ * Parses filenames in terminal-colors.d
+ */
+static int colors_read_configuration(struct ul_color_ctl *cc)
+{
+ int rc = -ENOENT;
+ char *dirname, buf[PATH_MAX];
+
+ cc->termname = getenv("TERM");
+
+ dirname = colors_get_homedir(buf, sizeof(buf));
+ if (dirname)
+ rc = colors_readdir(cc, dirname); /* ~/.config */
+ if (rc == -EPERM || rc == -EACCES || rc == -ENOENT)
+ rc = colors_readdir(cc, _PATH_TERMCOLORS_DIR); /* /etc */
+
+ cc->configured = 1;
+ return rc;
+}
+
+/*
+ * Reads terminal-colors.d/ scheme file into array schemes
+ */
+static int colors_read_schemes(struct ul_color_ctl *cc)
+{
+ int rc = 0;
+ FILE *f = NULL;
+ char buf[BUFSIZ],
+ cn[129], seq[129];
+
+ if (!cc->configured)
+ rc = colors_read_configuration(cc);
+
+ cc->cs_configured = 1;
+
+ if (rc || !cc->sfile)
+ goto done;
+
+ DBG(SCHEME, ul_debug("reading file '%s'", cc->sfile));
+
+ f = fopen(cc->sfile, "r");
+ if (!f) {
+ rc = -errno;
+ goto done;
+ }
+
+ while (fgets(buf, sizeof(buf), f)) {
+ char *p = strchr(buf, '\n');
+
+ if (!p) {
+ if (feof(f))
+ p = strchr(buf, '\0');
+ else {
+ rc = -errno;
+ goto done;
+ }
+ }
+ *p = '\0';
+ p = (char *) skip_blank(buf);
+ if (*p == '\0' || *p == '#')
+ continue;
+
+ rc = sscanf(p, "%128[^ ] %128[^\n ]", cn, seq);
+ if (rc == 2 && *cn && *seq) {
+ rc = colors_add_scheme(cc, cn, seq); /* set rc=0 on success */
+ if (rc)
+ goto done;
+ }
+ }
+ rc = 0;
+
+done:
+ if (f)
+ fclose(f);
+ colors_sort_schemes(cc);
+
+ return rc;
+}
+
+
+static void termcolors_init_debug(void)
+{
+ __UL_INIT_DEBUG_FROM_ENV(termcolors, TERMCOLORS_DEBUG_, 0, TERMINAL_COLORS_DEBUG);
+}
+
+static int colors_terminal_is_ready(void)
+{
+ int ncolors = -1;
+
+ if (isatty(STDOUT_FILENO) != 1)
+ goto none;
+
+#if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
+ {
+ int ret;
+
+ if (setupterm(NULL, STDOUT_FILENO, &ret) != 0 || ret != 1)
+ goto none;
+ ncolors = tigetnum("colors");
+ if (ncolors <= 2)
+ goto none;
+ }
+#endif
+ if (ncolors != -1) {
+ DBG(CONF, ul_debug("terminal is ready (supports %d colors)", ncolors));
+ return 1;
+ }
+none:
+ DBG(CONF, ul_debug("terminal is NOT ready"));
+ return 0;
+}
+
+/**
+ * colors_init:
+ * @mode: UL_COLORMODE_*
+ * @name: util argv[0]
+ *
+ * Initialize private color control struct and initialize the colors
+ * status. The color schemes are parsed on demand by colors_get_scheme().
+ *
+ * Returns: >0 on success.
+ */
+int colors_init(int mode, const char *name)
+{
+ int ready = -1;
+ struct ul_color_ctl *cc = &ul_colors;
+
+ cc->utilname = name;
+ cc->mode = mode;
+
+ termcolors_init_debug();
+
+ if (mode == UL_COLORMODE_UNDEF && (ready = colors_terminal_is_ready())) {
+ int rc = colors_read_configuration(cc);
+ if (rc)
+ cc->mode = UL_COLORMODE_DEFAULT;
+ else {
+
+ /* evaluate scores */
+ if (cc->scores[UL_COLORFILE_DISABLE] >
+ cc->scores[UL_COLORFILE_ENABLE])
+ cc->mode = UL_COLORMODE_NEVER;
+ else
+ cc->mode = UL_COLORMODE_DEFAULT;
+
+ atexit(colors_deinit);
+ }
+ }
+
+ switch (cc->mode) {
+ case UL_COLORMODE_AUTO:
+ cc->has_colors = ready == -1 ? colors_terminal_is_ready() : ready;
+ break;
+ case UL_COLORMODE_ALWAYS:
+ cc->has_colors = 1;
+ break;
+ case UL_COLORMODE_NEVER:
+ default:
+ cc->has_colors = 0;
+ }
+
+ ON_DBG(CONF, colors_debug(cc));
+
+ return cc->has_colors;
+}
+
+/*
+ * Temporary disable colors (this setting is independent on terminal-colors.d/)
+ */
+void colors_off(void)
+{
+ ul_colors.disabled = 1;
+}
+
+/*
+ * Enable colors
+ */
+void colors_on(void)
+{
+ ul_colors.disabled = 0;
+}
+
+/*
+ * Is terminal-colors.d/ configured to use colors?
+ */
+int colors_wanted(void)
+{
+ return ul_colors.has_colors;
+}
+
+/*
+ * Enable @seq color
+ */
+void color_fenable(const char *seq, FILE *f)
+{
+ if (!ul_colors.disabled && ul_colors.has_colors && seq)
+ fputs(seq, f);
+}
+
+/*
+ * Returns escape sequence by logical @name, if undefined then returns @dflt.
+ */
+const char *color_scheme_get_sequence(const char *name, const char *dflt)
+{
+ struct ul_color_scheme *cs;
+
+ if (ul_colors.disabled || !ul_colors.has_colors)
+ return NULL;
+
+ cs = colors_get_scheme(&ul_colors, name);
+ return cs && cs->seq ? cs->seq : dflt;
+}
+
+/*
+ * Enable color by logical @name, if undefined enable @dflt.
+ */
+void color_scheme_fenable(const char *name, const char *dflt, FILE *f)
+{
+ const char *seq = color_scheme_get_sequence(name, dflt);
+
+ if (!seq)
+ return;
+ color_fenable(seq, f);
+}
+
+
+/*
+ * Disable previously enabled color
+ */
+void color_fdisable(FILE *f)
+{
+ if (!ul_colors.disabled && ul_colors.has_colors)
+ fputs(UL_COLOR_RESET, f);
+}
+
+/*
+ * Parses @str to return UL_COLORMODE_*
+ */
+int colormode_from_string(const char *str)
+{
+ size_t i;
+ static const char *modes[] = {
+ [UL_COLORMODE_AUTO] = "auto",
+ [UL_COLORMODE_NEVER] = "never",
+ [UL_COLORMODE_ALWAYS] = "always",
+ [UL_COLORMODE_UNDEF] = ""
+ };
+
+ if (!str || !*str)
+ return -EINVAL;
+
+ assert(ARRAY_SIZE(modes) == __UL_NCOLORMODES);
+
+ for (i = 0; i < ARRAY_SIZE(modes); i++) {
+ if (strcasecmp(str, modes[i]) == 0)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+/*
+ * Parses @str and exit(EXIT_FAILURE) on error
+ */
+int colormode_or_err(const char *str, const char *errmsg)
+{
+ const char *p = str && *str == '=' ? str + 1 : str;
+ int colormode;
+
+ colormode = colormode_from_string(p);
+ if (colormode < 0)
+ errx(EXIT_FAILURE, "%s: '%s'", errmsg, p);
+
+ return colormode;
+}
+
+#ifdef TEST_PROGRAM_COLORS
+# include <getopt.h>
+int main(int argc, char *argv[])
+{
+ static const struct option longopts[] = {
+ { "mode", required_argument, NULL, 'm' },
+ { "color", required_argument, NULL, 'c' },
+ { "color-scheme", required_argument, NULL, 'C' },
+ { "name", required_argument, NULL, 'n' },
+ { NULL, 0, NULL, 0 }
+ };
+ int c, mode = UL_COLORMODE_UNDEF; /* default */
+ const char *color = "red", *name = NULL, *color_scheme = NULL;
+ const char *seq = NULL;
+
+ while ((c = getopt_long(argc, argv, "C:c:m:n:", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'c':
+ color = optarg;
+ break;
+ case 'C':
+ color_scheme = optarg;
+ break;
+ case 'm':
+ mode = colormode_or_err(optarg, "unsupported color mode");
+ break;
+ case 'n':
+ name = optarg;
+ break;
+ default:
+ fprintf(stderr, "usage: %s [options]\n"
+ " -m, --mode <auto|never|always> default is undefined\n"
+ " -c, --color <red|blue|...> color for the test message\n"
+ " -C, --color-scheme <name> color for the test message\n"
+ " -n, --name <utilname> util name\n",
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+ }
+
+ colors_init(mode, name ? name : program_invocation_short_name);
+
+ seq = color_sequence_from_colorname(color);
+
+ if (color_scheme)
+ color_scheme_enable(color_scheme, seq);
+ else
+ color_enable(seq);
+ printf("Hello World!");
+ color_disable();
+ fputc('\n', stdout);
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM_COLORS */
+
diff --git a/lib/cpuset.c b/lib/cpuset.c
new file mode 100644
index 0000000..011b688
--- /dev/null
+++ b/lib/cpuset.c
@@ -0,0 +1,397 @@
+/*
+ * Terminology:
+ *
+ * cpuset - (libc) cpu_set_t data structure represents set of CPUs
+ * cpumask - string with hex mask (e.g. "0x00000001")
+ * cpulist - string with CPU ranges (e.g. "0-3,5,7,8")
+ *
+ * Based on code from taskset.c and Linux kernel.
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sched.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/syscall.h>
+
+#include "cpuset.h"
+#include "c.h"
+
+static inline int val_to_char(int v)
+{
+ if (v >= 0 && v < 10)
+ return '0' + v;
+ if (v >= 10 && v < 16)
+ return ('a' - 10) + v;
+ return -1;
+}
+
+static inline int char_to_val(int c)
+{
+ int cl;
+
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ cl = tolower(c);
+ if (cl >= 'a' && cl <= 'f')
+ return cl + (10 - 'a');
+ return -1;
+}
+
+static const char *nexttoken(const char *q, int sep)
+{
+ if (q)
+ q = strchr(q, sep);
+ if (q)
+ q++;
+ return q;
+}
+
+/*
+ * Number of bits in a CPU bitmask on current system
+ */
+int get_max_number_of_cpus(void)
+{
+#ifdef SYS_sched_getaffinity
+ int n, cpus = 2048;
+ size_t setsize;
+ cpu_set_t *set = cpuset_alloc(cpus, &setsize, NULL);
+
+ if (!set)
+ return -1; /* error */
+
+ for (;;) {
+ CPU_ZERO_S(setsize, set);
+
+ /* the library version does not return size of cpumask_t */
+ n = syscall(SYS_sched_getaffinity, 0, setsize, set);
+
+ if (n < 0 && errno == EINVAL && cpus < 1024 * 1024) {
+ cpuset_free(set);
+ cpus *= 2;
+ set = cpuset_alloc(cpus, &setsize, NULL);
+ if (!set)
+ return -1; /* error */
+ continue;
+ }
+ cpuset_free(set);
+ return n * 8;
+ }
+#endif
+ return -1;
+}
+
+/*
+ * Allocates a new set for ncpus and returns size in bytes and size in bits
+ */
+cpu_set_t *cpuset_alloc(int ncpus, size_t *setsize, size_t *nbits)
+{
+ cpu_set_t *set = CPU_ALLOC(ncpus);
+
+ if (!set)
+ return NULL;
+ if (setsize)
+ *setsize = CPU_ALLOC_SIZE(ncpus);
+ if (nbits)
+ *nbits = cpuset_nbits(CPU_ALLOC_SIZE(ncpus));
+ return set;
+}
+
+void cpuset_free(cpu_set_t *set)
+{
+ CPU_FREE(set);
+}
+
+#if !HAVE_DECL_CPU_ALLOC
+/* Please, use CPU_COUNT_S() macro. This is fallback */
+int __cpuset_count_s(size_t setsize, const cpu_set_t *set)
+{
+ int s = 0;
+ const __cpu_mask *p = set->__bits;
+ const __cpu_mask *end = &set->__bits[setsize / sizeof (__cpu_mask)];
+
+ while (p < end) {
+ __cpu_mask l = *p++;
+
+ if (l == 0)
+ continue;
+# if LONG_BIT > 32
+ l = (l & 0x5555555555555555ul) + ((l >> 1) & 0x5555555555555555ul);
+ l = (l & 0x3333333333333333ul) + ((l >> 2) & 0x3333333333333333ul);
+ l = (l & 0x0f0f0f0f0f0f0f0ful) + ((l >> 4) & 0x0f0f0f0f0f0f0f0ful);
+ l = (l & 0x00ff00ff00ff00fful) + ((l >> 8) & 0x00ff00ff00ff00fful);
+ l = (l & 0x0000ffff0000fffful) + ((l >> 16) & 0x0000ffff0000fffful);
+ l = (l & 0x00000000fffffffful) + ((l >> 32) & 0x00000000fffffffful);
+# else
+ l = (l & 0x55555555ul) + ((l >> 1) & 0x55555555ul);
+ l = (l & 0x33333333ul) + ((l >> 2) & 0x33333333ul);
+ l = (l & 0x0f0f0f0ful) + ((l >> 4) & 0x0f0f0f0ful);
+ l = (l & 0x00ff00fful) + ((l >> 8) & 0x00ff00fful);
+ l = (l & 0x0000fffful) + ((l >> 16) & 0x0000fffful);
+# endif
+ s += l;
+ }
+ return s;
+}
+#endif
+
+/*
+ * Returns human readable representation of the cpuset. The output format is
+ * a list of CPUs with ranges (for example, "0,1,3-9").
+ */
+char *cpulist_create(char *str, size_t len,
+ cpu_set_t *set, size_t setsize)
+{
+ size_t i;
+ char *ptr = str;
+ int entry_made = 0;
+ size_t max = cpuset_nbits(setsize);
+
+ for (i = 0; i < max; i++) {
+ if (CPU_ISSET_S(i, setsize, set)) {
+ int rlen;
+ size_t j, run = 0;
+ entry_made = 1;
+ for (j = i + 1; j < max; j++) {
+ if (CPU_ISSET_S(j, setsize, set))
+ run++;
+ else
+ break;
+ }
+ if (!run)
+ rlen = snprintf(ptr, len, "%zu,", i);
+ else if (run == 1) {
+ rlen = snprintf(ptr, len, "%zu,%zu,", i, i + 1);
+ i++;
+ } else {
+ rlen = snprintf(ptr, len, "%zu-%zu,", i, i + run);
+ i += run;
+ }
+ if (rlen < 0 || (size_t) rlen >= len)
+ return NULL;
+ ptr += rlen;
+ len -= rlen;
+ }
+ }
+ ptr -= entry_made;
+ *ptr = '\0';
+
+ return str;
+}
+
+/*
+ * Returns string with CPU mask.
+ */
+char *cpumask_create(char *str, size_t len,
+ cpu_set_t *set, size_t setsize)
+{
+ char *ptr = str;
+ char *ret = NULL;
+ int cpu;
+
+ for (cpu = cpuset_nbits(setsize) - 4; cpu >= 0; cpu -= 4) {
+ char val = 0;
+
+ if (len == (size_t) (ptr - str))
+ break;
+
+ if (CPU_ISSET_S(cpu, setsize, set))
+ val |= 1;
+ if (CPU_ISSET_S(cpu + 1, setsize, set))
+ val |= 2;
+ if (CPU_ISSET_S(cpu + 2, setsize, set))
+ val |= 4;
+ if (CPU_ISSET_S(cpu + 3, setsize, set))
+ val |= 8;
+
+ if (!ret && val)
+ ret = ptr;
+ *ptr++ = val_to_char(val);
+ }
+ *ptr = '\0';
+ return ret ? ret : ptr - 1;
+}
+
+/*
+ * Parses string with CPUs mask.
+ */
+int cpumask_parse(const char *str, cpu_set_t *set, size_t setsize)
+{
+ int len = strlen(str);
+ const char *ptr = str + len - 1;
+ int cpu = 0;
+
+ /* skip 0x, it's all hex anyway */
+ if (len > 1 && !memcmp(str, "0x", 2L))
+ str += 2;
+
+ CPU_ZERO_S(setsize, set);
+
+ while (ptr >= str) {
+ char val;
+
+ /* cpu masks in /sys uses comma as a separator */
+ if (*ptr == ',')
+ ptr--;
+
+ val = char_to_val(*ptr);
+ if (val == (char) -1)
+ return -1;
+ if (val & 1)
+ CPU_SET_S(cpu, setsize, set);
+ if (val & 2)
+ CPU_SET_S(cpu + 1, setsize, set);
+ if (val & 4)
+ CPU_SET_S(cpu + 2, setsize, set);
+ if (val & 8)
+ CPU_SET_S(cpu + 3, setsize, set);
+ ptr--;
+ cpu += 4;
+ }
+
+ return 0;
+}
+
+/*
+ * Parses string with list of CPU ranges.
+ * Returns 0 on success.
+ * Returns 1 on error.
+ * Returns 2 if fail is set and a cpu number passed in the list doesn't fit
+ * into the cpu_set. If fail is not set cpu numbers that do not fit are
+ * ignored and 0 is returned instead.
+ */
+int cpulist_parse(const char *str, cpu_set_t *set, size_t setsize, int fail)
+{
+ size_t max = cpuset_nbits(setsize);
+ const char *p, *q;
+ int r = 0;
+
+ q = str;
+ CPU_ZERO_S(setsize, set);
+
+ while (p = q, q = nexttoken(q, ','), p) {
+ unsigned int a; /* beginning of range */
+ unsigned int b; /* end of range */
+ unsigned int s; /* stride */
+ const char *c1, *c2;
+ char c;
+
+ if ((r = sscanf(p, "%u%c", &a, &c)) < 1)
+ return 1;
+ b = a;
+ s = 1;
+
+ c1 = nexttoken(p, '-');
+ c2 = nexttoken(p, ',');
+ if (c1 != NULL && (c2 == NULL || c1 < c2)) {
+ if ((r = sscanf(c1, "%u%c", &b, &c)) < 1)
+ return 1;
+ c1 = nexttoken(c1, ':');
+ if (c1 != NULL && (c2 == NULL || c1 < c2)) {
+ if ((r = sscanf(c1, "%u%c", &s, &c)) < 1)
+ return 1;
+ if (s == 0)
+ return 1;
+ }
+ }
+
+ if (!(a <= b))
+ return 1;
+ while (a <= b) {
+ if (fail && (a >= max))
+ return 2;
+ CPU_SET_S(a, setsize, set);
+ a += s;
+ }
+ }
+
+ if (r == 2)
+ return 1;
+ return 0;
+}
+
+#ifdef TEST_PROGRAM_CPUSET
+
+#include <getopt.h>
+
+int main(int argc, char *argv[])
+{
+ cpu_set_t *set;
+ size_t setsize, buflen, nbits;
+ char *buf, *mask = NULL, *range = NULL;
+ int ncpus = 2048, rc, c;
+
+ static const struct option longopts[] = {
+ { "ncpus", 1, NULL, 'n' },
+ { "mask", 1, NULL, 'm' },
+ { "range", 1, NULL, 'r' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ while ((c = getopt_long(argc, argv, "n:m:r:", longopts, NULL)) != -1) {
+ switch(c) {
+ case 'n':
+ ncpus = atoi(optarg);
+ break;
+ case 'm':
+ mask = strdup(optarg);
+ break;
+ case 'r':
+ range = strdup(optarg);
+ break;
+ default:
+ goto usage_err;
+ }
+ }
+
+ if (!mask && !range)
+ goto usage_err;
+
+ set = cpuset_alloc(ncpus, &setsize, &nbits);
+ if (!set)
+ err(EXIT_FAILURE, "failed to allocate cpu set");
+
+ /*
+ fprintf(stderr, "ncpus: %d, cpuset bits: %zd, cpuset bytes: %zd\n",
+ ncpus, nbits, setsize);
+ */
+
+ buflen = 7 * nbits;
+ buf = malloc(buflen);
+ if (!buf)
+ err(EXIT_FAILURE, "failed to allocate cpu set buffer");
+
+ if (mask)
+ rc = cpumask_parse(mask, set, setsize);
+ else
+ rc = cpulist_parse(range, set, setsize, 0);
+
+ if (rc)
+ errx(EXIT_FAILURE, "failed to parse string: %s", mask ? : range);
+
+ printf("%-15s = %15s ", mask ? : range,
+ cpumask_create(buf, buflen, set, setsize));
+ printf("[%s]\n", cpulist_create(buf, buflen, set, setsize));
+
+ free(buf);
+ free(mask);
+ free(range);
+ cpuset_free(set);
+
+ return EXIT_SUCCESS;
+
+usage_err:
+ fprintf(stderr,
+ "usage: %s [--ncpus <num>] --mask <mask> | --range <list>",
+ program_invocation_short_name);
+ exit(EXIT_FAILURE);
+}
+#endif
diff --git a/lib/crc32.c b/lib/crc32.c
new file mode 100644
index 0000000..824693d
--- /dev/null
+++ b/lib/crc32.c
@@ -0,0 +1,142 @@
+/*
+ * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or
+ * code or tables extracted from it, as desired without restriction.
+ *
+ * First, the polynomial itself and its table of feedback terms. The
+ * polynomial is
+ * X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0
+ *
+ * Note that we take it "backwards" and put the highest-order term in
+ * the lowest-order bit. The X^32 term is "implied"; the LSB is the
+ * X^31 term, etc. The X^0 term (usually shown as "+1") results in
+ * the MSB being 1.
+ *
+ * Note that the usual hardware shift register implementation, which
+ * is what we're using (we're merely optimizing it by doing eight-bit
+ * chunks at a time) shifts bits into the lowest-order term. In our
+ * implementation, that means shifting towards the right. Why do we
+ * do it this way? Because the calculated CRC must be transmitted in
+ * order from highest-order term to lowest-order term. UARTs transmit
+ * characters in order from LSB to MSB. By storing the CRC this way,
+ * we hand it to the UART in the order low-byte to high-byte; the UART
+ * sends each low-bit to high-bit; and the result is transmission bit
+ * by bit from highest- to lowest-order term without requiring any bit
+ * shuffling on our part. Reception works similarly.
+ *
+ * The feedback terms table consists of 256, 32-bit entries. Notes
+ *
+ * The table can be generated at runtime if desired; code to do so
+ * is shown later. It might not be obvious, but the feedback
+ * terms simply represent the results of eight shift/xor opera-
+ * tions for all combinations of data and CRC register values.
+ *
+ * The values must be right-shifted by eight bits by the "updcrc"
+ * logic; the shift must be unsigned (bring in zeroes). On some
+ * hardware you could probably optimize the shift in assembler by
+ * using byte-swap instructions.
+ * polynomial $edb88320
+ *
+ */
+
+#include <stdio.h>
+
+#include "crc32.h"
+
+
+static const uint32_t crc32_tab[] = {
+ 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L,
+ 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L,
+ 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L,
+ 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL,
+ 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L,
+ 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L,
+ 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L,
+ 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
+ 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L,
+ 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL,
+ 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L,
+ 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L,
+ 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L,
+ 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL,
+ 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL,
+ 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
+ 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL,
+ 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L,
+ 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L,
+ 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L,
+ 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL,
+ 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L,
+ 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L,
+ 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
+ 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L,
+ 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L,
+ 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L,
+ 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L,
+ 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L,
+ 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL,
+ 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL,
+ 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
+ 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L,
+ 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL,
+ 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL,
+ 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L,
+ 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL,
+ 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L,
+ 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL,
+ 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
+ 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL,
+ 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L,
+ 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L,
+ 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL,
+ 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L,
+ 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L,
+ 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L,
+ 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
+ 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L,
+ 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L,
+ 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL,
+ 0x2d02ef8dL
+};
+
+static inline uint32_t crc32_add_char(uint32_t crc, unsigned char c)
+{
+ return crc32_tab[(crc ^ c) & 0xff] ^ (crc >> 8);
+}
+
+/*
+ * This a generic crc32() function, it takes seed as an argument,
+ * and does __not__ xor at the end. Then individual users can do
+ * whatever they need.
+ */
+uint32_t ul_crc32(uint32_t seed, const unsigned char *buf, size_t len)
+{
+ uint32_t crc = seed;
+ const unsigned char *p = buf;
+
+ while (len) {
+ crc = crc32_add_char(crc, *p++);
+ len--;
+ }
+
+ return crc;
+}
+
+uint32_t ul_crc32_exclude_offset(uint32_t seed, const unsigned char *buf, size_t len,
+ size_t exclude_off, size_t exclude_len)
+{
+ uint32_t crc = seed;
+ const unsigned char *p = buf;
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ unsigned char x = *p++;
+
+ if (i >= exclude_off && i < exclude_off + exclude_len)
+ x = 0;
+
+ crc = crc32_add_char(crc, x);
+ }
+
+ return crc;
+}
+
diff --git a/lib/crc32c.c b/lib/crc32c.c
new file mode 100644
index 0000000..49e7543
--- /dev/null
+++ b/lib/crc32c.c
@@ -0,0 +1,102 @@
+/*
+ * This code is from freebsd/sys/libkern/crc32.c
+ *
+ * Simplest table-based crc32c. Performance is not important
+ * for checking crcs on superblocks
+ */
+
+/*-
+ * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or
+ * code or tables extracted from it, as desired without restriction.
+ */
+
+#include "crc32c.h"
+
+static const uint32_t crc32Table[256] = {
+ 0x00000000L, 0xF26B8303L, 0xE13B70F7L, 0x1350F3F4L,
+ 0xC79A971FL, 0x35F1141CL, 0x26A1E7E8L, 0xD4CA64EBL,
+ 0x8AD958CFL, 0x78B2DBCCL, 0x6BE22838L, 0x9989AB3BL,
+ 0x4D43CFD0L, 0xBF284CD3L, 0xAC78BF27L, 0x5E133C24L,
+ 0x105EC76FL, 0xE235446CL, 0xF165B798L, 0x030E349BL,
+ 0xD7C45070L, 0x25AFD373L, 0x36FF2087L, 0xC494A384L,
+ 0x9A879FA0L, 0x68EC1CA3L, 0x7BBCEF57L, 0x89D76C54L,
+ 0x5D1D08BFL, 0xAF768BBCL, 0xBC267848L, 0x4E4DFB4BL,
+ 0x20BD8EDEL, 0xD2D60DDDL, 0xC186FE29L, 0x33ED7D2AL,
+ 0xE72719C1L, 0x154C9AC2L, 0x061C6936L, 0xF477EA35L,
+ 0xAA64D611L, 0x580F5512L, 0x4B5FA6E6L, 0xB93425E5L,
+ 0x6DFE410EL, 0x9F95C20DL, 0x8CC531F9L, 0x7EAEB2FAL,
+ 0x30E349B1L, 0xC288CAB2L, 0xD1D83946L, 0x23B3BA45L,
+ 0xF779DEAEL, 0x05125DADL, 0x1642AE59L, 0xE4292D5AL,
+ 0xBA3A117EL, 0x4851927DL, 0x5B016189L, 0xA96AE28AL,
+ 0x7DA08661L, 0x8FCB0562L, 0x9C9BF696L, 0x6EF07595L,
+ 0x417B1DBCL, 0xB3109EBFL, 0xA0406D4BL, 0x522BEE48L,
+ 0x86E18AA3L, 0x748A09A0L, 0x67DAFA54L, 0x95B17957L,
+ 0xCBA24573L, 0x39C9C670L, 0x2A993584L, 0xD8F2B687L,
+ 0x0C38D26CL, 0xFE53516FL, 0xED03A29BL, 0x1F682198L,
+ 0x5125DAD3L, 0xA34E59D0L, 0xB01EAA24L, 0x42752927L,
+ 0x96BF4DCCL, 0x64D4CECFL, 0x77843D3BL, 0x85EFBE38L,
+ 0xDBFC821CL, 0x2997011FL, 0x3AC7F2EBL, 0xC8AC71E8L,
+ 0x1C661503L, 0xEE0D9600L, 0xFD5D65F4L, 0x0F36E6F7L,
+ 0x61C69362L, 0x93AD1061L, 0x80FDE395L, 0x72966096L,
+ 0xA65C047DL, 0x5437877EL, 0x4767748AL, 0xB50CF789L,
+ 0xEB1FCBADL, 0x197448AEL, 0x0A24BB5AL, 0xF84F3859L,
+ 0x2C855CB2L, 0xDEEEDFB1L, 0xCDBE2C45L, 0x3FD5AF46L,
+ 0x7198540DL, 0x83F3D70EL, 0x90A324FAL, 0x62C8A7F9L,
+ 0xB602C312L, 0x44694011L, 0x5739B3E5L, 0xA55230E6L,
+ 0xFB410CC2L, 0x092A8FC1L, 0x1A7A7C35L, 0xE811FF36L,
+ 0x3CDB9BDDL, 0xCEB018DEL, 0xDDE0EB2AL, 0x2F8B6829L,
+ 0x82F63B78L, 0x709DB87BL, 0x63CD4B8FL, 0x91A6C88CL,
+ 0x456CAC67L, 0xB7072F64L, 0xA457DC90L, 0x563C5F93L,
+ 0x082F63B7L, 0xFA44E0B4L, 0xE9141340L, 0x1B7F9043L,
+ 0xCFB5F4A8L, 0x3DDE77ABL, 0x2E8E845FL, 0xDCE5075CL,
+ 0x92A8FC17L, 0x60C37F14L, 0x73938CE0L, 0x81F80FE3L,
+ 0x55326B08L, 0xA759E80BL, 0xB4091BFFL, 0x466298FCL,
+ 0x1871A4D8L, 0xEA1A27DBL, 0xF94AD42FL, 0x0B21572CL,
+ 0xDFEB33C7L, 0x2D80B0C4L, 0x3ED04330L, 0xCCBBC033L,
+ 0xA24BB5A6L, 0x502036A5L, 0x4370C551L, 0xB11B4652L,
+ 0x65D122B9L, 0x97BAA1BAL, 0x84EA524EL, 0x7681D14DL,
+ 0x2892ED69L, 0xDAF96E6AL, 0xC9A99D9EL, 0x3BC21E9DL,
+ 0xEF087A76L, 0x1D63F975L, 0x0E330A81L, 0xFC588982L,
+ 0xB21572C9L, 0x407EF1CAL, 0x532E023EL, 0xA145813DL,
+ 0x758FE5D6L, 0x87E466D5L, 0x94B49521L, 0x66DF1622L,
+ 0x38CC2A06L, 0xCAA7A905L, 0xD9F75AF1L, 0x2B9CD9F2L,
+ 0xFF56BD19L, 0x0D3D3E1AL, 0x1E6DCDEEL, 0xEC064EEDL,
+ 0xC38D26C4L, 0x31E6A5C7L, 0x22B65633L, 0xD0DDD530L,
+ 0x0417B1DBL, 0xF67C32D8L, 0xE52CC12CL, 0x1747422FL,
+ 0x49547E0BL, 0xBB3FFD08L, 0xA86F0EFCL, 0x5A048DFFL,
+ 0x8ECEE914L, 0x7CA56A17L, 0x6FF599E3L, 0x9D9E1AE0L,
+ 0xD3D3E1ABL, 0x21B862A8L, 0x32E8915CL, 0xC083125FL,
+ 0x144976B4L, 0xE622F5B7L, 0xF5720643L, 0x07198540L,
+ 0x590AB964L, 0xAB613A67L, 0xB831C993L, 0x4A5A4A90L,
+ 0x9E902E7BL, 0x6CFBAD78L, 0x7FAB5E8CL, 0x8DC0DD8FL,
+ 0xE330A81AL, 0x115B2B19L, 0x020BD8EDL, 0xF0605BEEL,
+ 0x24AA3F05L, 0xD6C1BC06L, 0xC5914FF2L, 0x37FACCF1L,
+ 0x69E9F0D5L, 0x9B8273D6L, 0x88D28022L, 0x7AB90321L,
+ 0xAE7367CAL, 0x5C18E4C9L, 0x4F48173DL, 0xBD23943EL,
+ 0xF36E6F75L, 0x0105EC76L, 0x12551F82L, 0xE03E9C81L,
+ 0x34F4F86AL, 0xC69F7B69L, 0xD5CF889DL, 0x27A40B9EL,
+ 0x79B737BAL, 0x8BDCB4B9L, 0x988C474DL, 0x6AE7C44EL,
+ 0xBE2DA0A5L, 0x4C4623A6L, 0x5F16D052L, 0xAD7D5351L
+};
+
+/*
+ *This was singletable_crc32c() in bsd
+ *
+ * If you will not be passing crc back into this function to process more bytes,
+ * the answer is:
+ *
+ * crc = crc32c(~0L, buf, size);
+ * [ crc = crc32c(crc, buf, size); ]
+ * crc ^= ~0L
+ *
+ */
+uint32_t
+crc32c(uint32_t crc, const void *buf, size_t size)
+{
+ const uint8_t *p = buf;
+
+ while (size--)
+ crc = crc32Table[(crc ^ *p++) & 0xff] ^ (crc >> 8);
+
+ return crc;
+}
diff --git a/lib/env.c b/lib/env.c
new file mode 100644
index 0000000..b2e3d97
--- /dev/null
+++ b/lib/env.c
@@ -0,0 +1,109 @@
+/*
+ * Security checks of environment
+ * Added from shadow-utils package
+ * by Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_SYS_PRCTL_H
+#include <sys/prctl.h>
+#else
+#define PR_GET_DUMPABLE 3
+#endif
+#if (!defined(HAVE_PRCTL) && defined(linux))
+#include <sys/syscall.h>
+#endif
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "env.h"
+
+#ifndef HAVE_ENVIRON_DECL
+extern char **environ;
+#endif
+
+static char * const forbid[] = {
+ "BASH_ENV=", /* GNU creeping featurism strikes again... */
+ "ENV=",
+ "HOME=",
+ "IFS=",
+ "KRB_CONF=",
+ "LD_", /* anything with the LD_ prefix */
+ "LIBPATH=",
+ "MAIL=",
+ "NLSPATH=",
+ "PATH=",
+ "SHELL=",
+ "SHLIB_PATH=",
+ (char *) 0
+};
+
+/* these are allowed, but with no slashes inside
+ (to work around security problems in GNU gettext) */
+static char * const noslash[] = {
+ "LANG=",
+ "LANGUAGE=",
+ "LC_", /* anything with the LC_ prefix */
+ (char *) 0
+};
+
+void
+sanitize_env(void)
+{
+ char **envp = environ;
+ char * const *bad;
+ char **cur;
+ char **move;
+
+ for (cur = envp; *cur; cur++) {
+ for (bad = forbid; *bad; bad++) {
+ if (strncmp(*cur, *bad, strlen(*bad)) == 0) {
+ for (move = cur; *move; move++)
+ *move = *(move + 1);
+ cur--;
+ break;
+ }
+ }
+ }
+
+ for (cur = envp; *cur; cur++) {
+ for (bad = noslash; *bad; bad++) {
+ if (strncmp(*cur, *bad, strlen(*bad)) != 0)
+ continue;
+ if (!strchr(*cur, '/'))
+ continue; /* OK */
+ for (move = cur; *move; move++)
+ *move = *(move + 1);
+ cur--;
+ break;
+ }
+ }
+}
+
+
+char *safe_getenv(const char *arg)
+{
+ uid_t ruid = getuid();
+
+ if (ruid != 0 || (ruid != geteuid()) || (getgid() != getegid()))
+ return NULL;
+#ifdef HAVE_PRCTL
+ if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 0)
+ return NULL;
+#else
+#if (defined(linux) && defined(SYS_prctl))
+ if (syscall(SYS_prctl, PR_GET_DUMPABLE, 0, 0, 0, 0) == 0)
+ return NULL;
+#endif
+#endif
+#ifdef HAVE_SECURE_GETENV
+return secure_getenv(arg);
+#elif HAVE___SECURE_GETENV
+ return __secure_getenv(arg);
+#else
+ return getenv(arg);
+#endif
+}
diff --git a/lib/exec_shell.c b/lib/exec_shell.c
new file mode 100644
index 0000000..18798eb
--- /dev/null
+++ b/lib/exec_shell.c
@@ -0,0 +1,51 @@
+/*
+ * exec_shell() - launch a shell, else exit!
+ *
+ * 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, 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.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <libgen.h>
+
+#include "nls.h"
+#include "c.h"
+#include "xalloc.h"
+
+#include "exec_shell.h"
+
+#define DEFAULT_SHELL "/bin/sh"
+
+void __attribute__((__noreturn__)) exec_shell(void)
+{
+ const char *shell = getenv("SHELL");
+ char *shellc;
+ const char *shell_basename;
+ char *arg0;
+
+ if (!shell)
+ shell = DEFAULT_SHELL;
+
+ shellc = xstrdup(shell);
+ shell_basename = basename(shellc);
+ arg0 = xmalloc(strlen(shell_basename) + 2);
+ arg0[0] = '-';
+ strcpy(arg0 + 1, shell_basename);
+
+ execl(shell, arg0, NULL);
+ errexec(shell);
+}
diff --git a/lib/fileutils.c b/lib/fileutils.c
new file mode 100644
index 0000000..822ca96
--- /dev/null
+++ b/lib/fileutils.c
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2012 Sami Kerola <kerolasa@iki.fi>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include "c.h"
+#include "fileutils.h"
+#include "pathnames.h"
+
+int mkstemp_cloexec(char *template)
+{
+#ifdef HAVE_MKOSTEMP
+ return mkostemp(template, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC);
+#else
+ int fd, old_flags, errno_save;
+
+ fd = mkstemp(template);
+ if (fd < 0)
+ return fd;
+
+ old_flags = fcntl(fd, F_GETFD, 0);
+ if (old_flags < 0)
+ goto unwind;
+ if (fcntl(fd, F_SETFD, old_flags | O_CLOEXEC) < 0)
+ goto unwind;
+
+ return fd;
+
+unwind:
+ errno_save = errno;
+ unlink(template);
+ close(fd);
+ errno = errno_save;
+
+ return -1;
+#endif
+}
+
+/* Create open temporary file in safe way. Please notice that the
+ * file permissions are -rw------- by default. */
+int xmkstemp(char **tmpname, const char *dir, const char *prefix)
+{
+ char *localtmp;
+ const char *tmpenv;
+ mode_t old_mode;
+ int fd, rc;
+
+ /* Some use cases must be capable of being moved atomically
+ * with rename(2), which is the reason why dir is here. */
+ tmpenv = dir ? dir : getenv("TMPDIR");
+ if (!tmpenv)
+ tmpenv = _PATH_TMP;
+
+ rc = asprintf(&localtmp, "%s/%s.XXXXXX", tmpenv, prefix);
+ if (rc < 0)
+ return -1;
+
+ old_mode = umask(077);
+ fd = mkstemp_cloexec(localtmp);
+ umask(old_mode);
+ if (fd == -1) {
+ free(localtmp);
+ localtmp = NULL;
+ }
+ *tmpname = localtmp;
+ return fd;
+}
+
+int dup_fd_cloexec(int oldfd, int lowfd)
+{
+ int fd, flags, errno_save;
+
+#ifdef F_DUPFD_CLOEXEC
+ fd = fcntl(oldfd, F_DUPFD_CLOEXEC, lowfd);
+ if (fd >= 0)
+ return fd;
+#endif
+
+ fd = dup(oldfd);
+ if (fd < 0)
+ return fd;
+
+ flags = fcntl(fd, F_GETFD);
+ if (flags < 0)
+ goto unwind;
+ if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0)
+ goto unwind;
+
+ return fd;
+
+unwind:
+ errno_save = errno;
+ close(fd);
+ errno = errno_save;
+
+ return -1;
+}
+
+/*
+ * portable getdtablesize()
+ */
+int get_fd_tabsize(void)
+{
+ int m;
+
+#if defined(HAVE_GETDTABLESIZE)
+ m = getdtablesize();
+#elif defined(HAVE_GETRLIMIT) && defined(RLIMIT_NOFILE)
+ struct rlimit rl;
+
+ getrlimit(RLIMIT_NOFILE, &rl);
+ m = rl.rlim_cur;
+#elif defined(HAVE_SYSCONF) && defined(_SC_OPEN_MAX)
+ m = sysconf(_SC_OPEN_MAX);
+#else
+ m = OPEN_MAX;
+#endif
+ return m;
+}
+
+#ifdef TEST_PROGRAM_FILEUTILS
+int main(void)
+{
+ FILE *f;
+ char *tmpname;
+ f = xfmkstemp(&tmpname, NULL, "test");
+ unlink(tmpname);
+ free(tmpname);
+ fclose(f);
+ return EXIT_FAILURE;
+}
+#endif
+
+
+int mkdir_p(const char *path, mode_t mode)
+{
+ char *p, *dir;
+ int rc = 0;
+
+ if (!path || !*path)
+ return -EINVAL;
+
+ dir = p = strdup(path);
+ if (!dir)
+ return -ENOMEM;
+
+ if (*p == '/')
+ p++;
+
+ while (p && *p) {
+ char *e = strchr(p, '/');
+ if (e)
+ *e = '\0';
+ if (*p) {
+ rc = mkdir(dir, mode);
+ if (rc && errno != EEXIST)
+ break;
+ rc = 0;
+ }
+ if (!e)
+ break;
+ *e = '/';
+ p = e + 1;
+ }
+
+ free(dir);
+ return rc;
+}
+
+/* returns basename and keeps dirname in the @path, if @path is "/" (root)
+ * then returns empty string */
+char *stripoff_last_component(char *path)
+{
+ char *p = path ? strrchr(path, '/') : NULL;
+
+ if (!p)
+ return NULL;
+ *p = '\0';
+ return p + 1;
+}
diff --git a/lib/idcache.c b/lib/idcache.c
new file mode 100644
index 0000000..2facf13
--- /dev/null
+++ b/lib/idcache.c
@@ -0,0 +1,113 @@
+
+#include <wchar.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/types.h>
+
+#include "c.h"
+#include "idcache.h"
+
+struct identry *get_id(struct idcache *ic, unsigned long int id)
+{
+ struct identry *ent;
+
+ if (!ic)
+ return NULL;
+
+ for (ent = ic->ent; ent; ent = ent->next) {
+ if (ent->id == id)
+ return ent;
+ }
+
+ return NULL;
+}
+
+struct idcache *new_idcache(void)
+{
+ return calloc(1, sizeof(struct idcache));
+}
+
+void free_idcache(struct idcache *ic)
+{
+ struct identry *ent = ic->ent;
+
+ while (ent) {
+ struct identry *next = ent->next;
+ free(ent->name);
+ free(ent);
+ ent = next;
+ }
+
+ free(ic);
+}
+
+static void add_id(struct idcache *ic, char *name, unsigned long int id)
+{
+ struct identry *ent, *x;
+ int w = 0;
+
+ ent = calloc(1, sizeof(struct identry));
+ if (!ent)
+ return;
+ ent->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) {
+ ent->name = strdup(name);
+ if (!ent->name) {
+ free(ent);
+ return;
+ }
+ } else {
+ if (asprintf(&ent->name, "%lu", id) < 0) {
+ free(ent);
+ return;
+ }
+ }
+
+ for (x = ic->ent; x && x->next; x = x->next);
+
+ if (x)
+ x->next = ent;
+ else
+ ic->ent = ent;
+
+ if (w <= 0)
+ w = ent->name ? strlen(ent->name) : 0;
+ ic->width = ic->width < w ? w : ic->width;
+ return;
+}
+
+void add_uid(struct idcache *cache, unsigned long int id)
+{
+ struct identry *ent= get_id(cache, id);
+
+ if (!ent) {
+ struct passwd *pw = getpwuid((uid_t) id);
+ add_id(cache, pw ? pw->pw_name : NULL, id);
+ }
+}
+
+void add_gid(struct idcache *cache, unsigned long int id)
+{
+ struct identry *ent = get_id(cache, id);
+
+ if (!ent) {
+ struct group *gr = getgrgid((gid_t) id);
+ add_id(cache, gr ? gr->gr_name : NULL, id);
+ }
+}
+
diff --git a/lib/ismounted.c b/lib/ismounted.c
new file mode 100644
index 0000000..682104d
--- /dev/null
+++ b/lib/ismounted.c
@@ -0,0 +1,395 @@
+/*
+ * ismounted.c --- Check to see if the filesystem was mounted
+ *
+ * Copyright (C) 1995,1996,1997,1998,1999,2000,2008 Theodore Ts'o.
+ *
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifdef HAVE_MNTENT_H
+#include <mntent.h>
+#endif
+#include <string.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <sys/param.h>
+
+#ifndef __linux__
+# ifdef HAVE_SYS_UCRED_H
+# include <sys/ucred.h>
+# endif
+# ifdef HAVE_SYS_MOUNT_H
+# include <sys/mount.h>
+# endif
+#endif
+
+#include "pathnames.h"
+#include "ismounted.h"
+#include "c.h"
+#ifdef __linux__
+# include "loopdev.h"
+#endif
+
+
+
+#ifdef HAVE_MNTENT_H
+/*
+ * Helper function which checks a file in /etc/mtab format to see if a
+ * filesystem is mounted. Returns an error if the file doesn't exist
+ * or can't be opened.
+ */
+static int check_mntent_file(const char *mtab_file, const char *file,
+ int *mount_flags, char *mtpt, int mtlen)
+{
+ struct mntent *mnt;
+ struct stat st_buf;
+ int retval = 0;
+ dev_t file_dev=0, file_rdev=0;
+ ino_t file_ino=0;
+ FILE *f;
+ int fd;
+
+ *mount_flags = 0;
+ if ((f = setmntent (mtab_file, "r")) == NULL)
+ return errno;
+
+ if (stat(file, &st_buf) == 0) {
+ if (S_ISBLK(st_buf.st_mode)) {
+#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */
+ file_rdev = st_buf.st_rdev;
+#endif /* __GNU__ */
+ } else {
+ file_dev = st_buf.st_dev;
+ file_ino = st_buf.st_ino;
+ }
+ }
+
+ while ((mnt = getmntent (f)) != NULL) {
+ if (mnt->mnt_fsname[0] != '/')
+ continue;
+ if (strcmp(file, mnt->mnt_fsname) == 0)
+ break;
+ if (stat(mnt->mnt_fsname, &st_buf) != 0)
+ continue;
+
+ if (S_ISBLK(st_buf.st_mode)) {
+#ifndef __GNU__
+ if (file_rdev && file_rdev == st_buf.st_rdev)
+ break;
+#ifdef __linux__
+ /* maybe the file is loopdev backing file */
+ if (file_dev
+ && major(st_buf.st_rdev) == LOOPDEV_MAJOR
+ && loopdev_is_used(mnt->mnt_fsname, file, 0, 0, 0))
+ break;
+#endif /* __linux__ */
+#endif /* __GNU__ */
+ } else {
+ if (file_dev && ((file_dev == st_buf.st_dev) &&
+ (file_ino == st_buf.st_ino)))
+ break;
+ }
+ }
+
+ if (mnt == NULL) {
+#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */
+ /*
+ * Do an extra check to see if this is the root device. We
+ * can't trust /etc/mtab, and /proc/mounts will only list
+ * /dev/root for the root filesystem. Argh. Instead we
+ * check if the given device has the same major/minor number
+ * as the device that the root directory is on.
+ */
+ if (file_rdev && stat("/", &st_buf) == 0 &&
+ st_buf.st_dev == file_rdev) {
+ *mount_flags = MF_MOUNTED;
+ if (mtpt)
+ strncpy(mtpt, "/", mtlen);
+ goto is_root;
+ }
+#endif /* __GNU__ */
+ goto errout;
+ }
+#ifndef __GNU__ /* The GNU hurd is deficient; what else is new? */
+ /* Validate the entry in case /etc/mtab is out of date */
+ /*
+ * We need to be paranoid, because some broken distributions
+ * (read: Slackware) don't initialize /etc/mtab before checking
+ * all of the non-root filesystems on the disk.
+ */
+ if (stat(mnt->mnt_dir, &st_buf) < 0) {
+ retval = errno;
+ if (retval == ENOENT) {
+#ifdef DEBUG
+ printf("Bogus entry in %s! (%s does not exist)\n",
+ mtab_file, mnt->mnt_dir);
+#endif /* DEBUG */
+ retval = 0;
+ }
+ goto errout;
+ }
+ if (file_rdev && (st_buf.st_dev != file_rdev)) {
+#ifdef DEBUG
+ printf("Bogus entry in %s! (%s not mounted on %s)\n",
+ mtab_file, file, mnt->mnt_dir);
+#endif /* DEBUG */
+ goto errout;
+ }
+#endif /* __GNU__ */
+ *mount_flags = MF_MOUNTED;
+
+#ifdef MNTOPT_RO
+ /* Check to see if the ro option is set */
+ if (hasmntopt(mnt, MNTOPT_RO))
+ *mount_flags |= MF_READONLY;
+#endif
+
+ if (mtpt)
+ strncpy(mtpt, mnt->mnt_dir, mtlen);
+ /*
+ * Check to see if we're referring to the root filesystem.
+ * If so, do a manual check to see if we can open /etc/mtab
+ * read/write, since if the root is mounted read/only, the
+ * contents of /etc/mtab may not be accurate.
+ */
+ if (!strcmp(mnt->mnt_dir, "/")) {
+is_root:
+#define TEST_FILE "/.ismount-test-file"
+ *mount_flags |= MF_ISROOT;
+ fd = open(TEST_FILE, O_RDWR|O_CREAT|O_CLOEXEC, 0600);
+ if (fd < 0) {
+ if (errno == EROFS)
+ *mount_flags |= MF_READONLY;
+ } else
+ close(fd);
+ (void) unlink(TEST_FILE);
+ }
+ retval = 0;
+errout:
+ endmntent (f);
+ return retval;
+}
+
+static int check_mntent(const char *file, int *mount_flags,
+ char *mtpt, int mtlen)
+{
+ int retval;
+
+#ifdef DEBUG
+ retval = check_mntent_file("/tmp/mtab", file, mount_flags,
+ mtpt, mtlen);
+ if (retval == 0)
+ return 0;
+#endif /* DEBUG */
+#ifdef __linux__
+ retval = check_mntent_file("/proc/mounts", file, mount_flags,
+ mtpt, mtlen);
+ if (retval == 0 && (*mount_flags != 0))
+ return 0;
+ if (access("/proc/mounts", R_OK) == 0) {
+ *mount_flags = 0;
+ return retval;
+ }
+#endif /* __linux__ */
+#if defined(MOUNTED) || defined(_PATH_MOUNTED)
+#ifndef MOUNTED
+#define MOUNTED _PATH_MOUNTED
+#endif /* MOUNTED */
+ retval = check_mntent_file(MOUNTED, file, mount_flags, mtpt, mtlen);
+ return retval;
+#else
+ *mount_flags = 0;
+ return 0;
+#endif /* defined(MOUNTED) || defined(_PATH_MOUNTED) */
+}
+
+#else
+#if defined(HAVE_GETMNTINFO)
+
+static int check_getmntinfo(const char *file, int *mount_flags,
+ char *mtpt, int mtlen)
+{
+ struct statfs *mp;
+ int len, n;
+ const char *s1;
+ char *s2;
+
+ n = getmntinfo(&mp, MNT_NOWAIT);
+ if (n == 0)
+ return errno;
+
+ len = sizeof(_PATH_DEV) - 1;
+ s1 = file;
+ if (strncmp(_PATH_DEV, s1, len) == 0)
+ s1 += len;
+
+ *mount_flags = 0;
+ while (--n >= 0) {
+ s2 = mp->f_mntfromname;
+ if (strncmp(_PATH_DEV, s2, len) == 0) {
+ s2 += len - 1;
+ *s2 = 'r';
+ }
+ if (strcmp(s1, s2) == 0 || strcmp(s1, &s2[1]) == 0) {
+ *mount_flags = MF_MOUNTED;
+ break;
+ }
+ ++mp;
+ }
+ if (mtpt)
+ strncpy(mtpt, mp->f_mntonname, mtlen);
+ return 0;
+}
+#endif /* HAVE_GETMNTINFO */
+#endif /* HAVE_MNTENT_H */
+
+/*
+ * Check to see if we're dealing with the swap device.
+ */
+static int is_swap_device(const char *file)
+{
+ FILE *f;
+ char buf[1024], *cp;
+ dev_t file_dev;
+ struct stat st_buf;
+ int ret = 0;
+
+ file_dev = 0;
+#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */
+ if ((stat(file, &st_buf) == 0) &&
+ S_ISBLK(st_buf.st_mode))
+ file_dev = st_buf.st_rdev;
+#endif /* __GNU__ */
+
+ if (!(f = fopen("/proc/swaps", "r" UL_CLOEXECSTR)))
+ return 0;
+ /* Skip the first line */
+ if (!fgets(buf, sizeof(buf), f))
+ goto leave;
+ if (*buf && strncmp(buf, "Filename\t", 9))
+ /* Linux <=2.6.19 contained a bug in the /proc/swaps
+ * code where the header would not be displayed
+ */
+ goto valid_first_line;
+
+ while (fgets(buf, sizeof(buf), f)) {
+valid_first_line:
+ if ((cp = strchr(buf, ' ')) != NULL)
+ *cp = 0;
+ if ((cp = strchr(buf, '\t')) != NULL)
+ *cp = 0;
+ if (strcmp(buf, file) == 0) {
+ ret++;
+ break;
+ }
+#ifndef __GNU__
+ if (file_dev && (stat(buf, &st_buf) == 0) &&
+ S_ISBLK(st_buf.st_mode) &&
+ file_dev == st_buf.st_rdev) {
+ ret++;
+ break;
+ }
+#endif /* __GNU__ */
+ }
+
+leave:
+ fclose(f);
+ return ret;
+}
+
+
+/*
+ * check_mount_point() fills determines if the device is mounted or otherwise
+ * busy, and fills in mount_flags with one or more of the following flags:
+ * MF_MOUNTED, MF_ISROOT, MF_READONLY, MF_SWAP, and MF_BUSY. If mtpt is
+ * non-NULL, the directory where the device is mounted is copied to where mtpt
+ * is pointing, up to mtlen characters.
+ */
+#ifdef __TURBOC__
+ #pragma argsused
+#endif
+int check_mount_point(const char *device, int *mount_flags,
+ char *mtpt, int mtlen)
+{
+ int retval = 0;
+
+ if (is_swap_device(device)) {
+ *mount_flags = MF_MOUNTED | MF_SWAP;
+ if (mtpt && mtlen)
+ strncpy(mtpt, "[SWAP]", mtlen);
+ } else {
+#ifdef HAVE_MNTENT_H
+ retval = check_mntent(device, mount_flags, mtpt, mtlen);
+#else
+#ifdef HAVE_GETMNTINFO
+ retval = check_getmntinfo(device, mount_flags, mtpt, mtlen);
+#else
+#ifdef __GNUC__
+ #warning "Can't use getmntent or getmntinfo to check for mounted filesystems!"
+#endif
+ *mount_flags = 0;
+#endif /* HAVE_GETMNTINFO */
+#endif /* HAVE_MNTENT_H */
+ }
+ if (retval)
+ return retval;
+
+#ifdef __linux__ /* This only works on Linux 2.6+ systems */
+ {
+ struct stat st_buf;
+ int fd;
+ if ((stat(device, &st_buf) != 0) ||
+ !S_ISBLK(st_buf.st_mode))
+ return 0;
+ fd = open(device, O_RDONLY|O_EXCL|O_CLOEXEC);
+ if (fd < 0) {
+ if (errno == EBUSY)
+ *mount_flags |= MF_BUSY;
+ } else
+ close(fd);
+ }
+#endif
+
+ return 0;
+}
+
+int is_mounted(const char *file)
+{
+ int retval;
+ int mount_flags = 0;
+
+ retval = check_mount_point(file, &mount_flags, NULL, 0);
+ if (retval)
+ return 0;
+ return mount_flags & MF_MOUNTED;
+}
+
+#ifdef TEST_PROGRAM_ISMOUNTED
+int main(int argc, char **argv)
+{
+ int flags = 0;
+ char devname[PATH_MAX];
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s device\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (check_mount_point(argv[1], &flags, devname, sizeof(devname)) == 0 &&
+ (flags & MF_MOUNTED)) {
+ if (flags & MF_SWAP)
+ printf("used swap device\n");
+ else
+ printf("mounted on %s\n", devname);
+ return EXIT_SUCCESS;
+ }
+
+ printf("not mounted\n");
+ return EXIT_FAILURE;
+}
+#endif /* DEBUG */
diff --git a/lib/langinfo.c b/lib/langinfo.c
new file mode 100644
index 0000000..2aa0a3b
--- /dev/null
+++ b/lib/langinfo.c
@@ -0,0 +1,121 @@
+/*
+ * This is callback solution for systems without nl_langinfo(), this function
+ * returns hardcoded and on locale setting indepndent value.
+ *
+ * See langinfo.h man page for more details.
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ */
+#include "nls.h"
+
+char *langinfo_fallback(nl_item item)
+{
+ switch (item) {
+ case CODESET:
+ return "ISO-8859-1";
+ case THOUSEP:
+ return ",";
+ case D_T_FMT:
+ case ERA_D_T_FMT:
+ return "%a %b %e %H:%M:%S %Y";
+ case D_FMT:
+ case ERA_D_FMT:
+ return "%m/%d/%y";
+ case T_FMT:
+ case ERA_T_FMT:
+ return "%H:%M:%S";
+ case T_FMT_AMPM:
+ return "%I:%M:%S %p";
+ case AM_STR:
+ return "AM";
+ case PM_STR:
+ return "PM";
+ case DAY_1:
+ return "Sunday";
+ case DAY_2:
+ return "Monday";
+ case DAY_3:
+ return "Tuesday";
+ case DAY_4:
+ return "Wednesday";
+ case DAY_5:
+ return "Thursday";
+ case DAY_6:
+ return "Friday";
+ case DAY_7:
+ return "Saturday";
+ case ABDAY_1:
+ return "Sun";
+ case ABDAY_2:
+ return "Mon";
+ case ABDAY_3:
+ return "Tue";
+ case ABDAY_4:
+ return "Wed";
+ case ABDAY_5:
+ return "Thu";
+ case ABDAY_6:
+ return "Fri";
+ case ABDAY_7:
+ return "Sat";
+ case MON_1:
+ return "January";
+ case MON_2:
+ return "February";
+ case MON_3:
+ return "March";
+ case MON_4:
+ return "April";
+ case MON_5:
+ return "May";
+ case MON_6:
+ return "June";
+ case MON_7:
+ return "July";
+ case MON_8:
+ return "August";
+ case MON_9:
+ return "September";
+ case MON_10:
+ return "October";
+ case MON_11:
+ return "November";
+ case MON_12:
+ return "December";
+ case ABMON_1:
+ return "Jan";
+ case ABMON_2:
+ return "Feb";
+ case ABMON_3:
+ return "Mar";
+ case ABMON_4:
+ return "Apr";
+ case ABMON_5:
+ return "May";
+ case ABMON_6:
+ return "Jun";
+ case ABMON_7:
+ return "Jul";
+ case ABMON_8:
+ return "Aug";
+ case ABMON_9:
+ return "Sep";
+ case ABMON_10:
+ return "Oct";
+ case ABMON_11:
+ return "Nov";
+ case ABMON_12:
+ return "Dec";
+ case ALT_DIGITS:
+ return "\0\0\0\0\0\0\0\0\0\0";
+ case CRNCYSTR:
+ return "-";
+ case YESEXPR:
+ return "^[yY]";
+ case NOEXPR:
+ return "^[nN]";
+ default:
+ return "";
+ }
+}
+
diff --git a/lib/linux_version.c b/lib/linux_version.c
new file mode 100644
index 0000000..137bbe7
--- /dev/null
+++ b/lib/linux_version.c
@@ -0,0 +1,71 @@
+#include <stdio.h>
+#include <sys/utsname.h>
+
+#include "c.h"
+#include "linux_version.h"
+
+int get_linux_version (void)
+{
+ static int kver = -1;
+ struct utsname uts;
+ int x = 0, y = 0, z = 0;
+ int n;
+
+ if (kver != -1)
+ return kver;
+ if (uname(&uts))
+ return kver = 0;
+
+ n = sscanf(uts.release, "%d.%d.%d", &x, &y, &z);
+ if (n < 1 || n > 3)
+ return kver = 0;
+
+ return kver = KERNEL_VERSION(x, y, z);
+}
+
+#ifdef TEST_PROGRAM_LINUXVERSION
+# include <stdlib.h>
+int main(int argc, char *argv[])
+{
+ int rc = EXIT_FAILURE;
+
+ if (argc == 1) {
+ printf("Linux version: %d\n", get_linux_version());
+ rc = EXIT_SUCCESS;
+
+ } else if (argc == 5) {
+ const char *oper = argv[1];
+
+ int x = atoi(argv[2]),
+ y = atoi(argv[3]),
+ z = atoi(argv[4]);
+ int kver = get_linux_version();
+ int uver = KERNEL_VERSION(x, y, z);
+
+ if (strcmp(oper, "==") == 0)
+ rc = kver == uver;
+ else if (strcmp(oper, "<=") == 0)
+ rc = kver <= uver;
+ else if (strcmp(oper, ">=") == 0)
+ rc = kver >= uver;
+ else
+ errx(EXIT_FAILURE, "unsupported operator");
+
+ if (rc)
+ printf("match\n");
+ else
+ printf("not-match [%d %s %d, x.y.z: %d.%d.%d]\n",
+ kver, oper, uver, x, y, z);
+
+ rc = rc ? EXIT_SUCCESS : EXIT_FAILURE;
+
+ } else
+ fprintf(stderr, "Usage:\n"
+ " %s [<oper> <x> <y> <z>]\n"
+ "supported operators:\n"
+ " ==, <=, >=\n",
+ program_invocation_short_name);
+
+ return rc;
+}
+#endif
diff --git a/lib/loopdev.c b/lib/loopdev.c
new file mode 100644
index 0000000..de0d50b
--- /dev/null
+++ b/lib/loopdev.c
@@ -0,0 +1,1795 @@
+
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ *
+ * -- based on mount/losetup.c
+ *
+ * Simple library for work with loop devices.
+ *
+ * - requires kernel 2.6.x
+ * - reads info from /sys/block/loop<N>/loop/<attr> (new kernels)
+ * - reads info by ioctl
+ * - supports *unlimited* number of loop devices
+ * - supports /dev/loop<N> as well as /dev/loop/<N>
+ * - minimize overhead (fd, loopinfo, ... are shared for all operations)
+ * - setup (associate device and backing file)
+ * - delete (dis-associate file)
+ * - old LOOP_{SET,GET}_STATUS (32bit) ioctls are unsupported
+ * - extendible
+ */
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <inttypes.h>
+#include <dirent.h>
+
+#include "linux_version.h"
+#include "c.h"
+#include "sysfs.h"
+#include "pathnames.h"
+#include "loopdev.h"
+#include "canonicalize.h"
+#include "blkdev.h"
+#include "debug.h"
+
+/*
+ * Debug stuff (based on include/debug.h)
+ */
+static UL_DEBUG_DEFINE_MASK(loopdev);
+UL_DEBUG_DEFINE_MASKNAMES(loopdev) = UL_DEBUG_EMPTY_MASKNAMES;
+
+#define LOOPDEV_DEBUG_INIT (1 << 1)
+#define LOOPDEV_DEBUG_CXT (1 << 2)
+#define LOOPDEV_DEBUG_ITER (1 << 3)
+#define LOOPDEV_DEBUG_SETUP (1 << 4)
+
+#define DBG(m, x) __UL_DBG(loopdev, LOOPDEV_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(loopdev, LOOPDEV_DEBUG_, m, x)
+
+#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(loopdev)
+#include "debugobj.h"
+
+static void loopdev_init_debug(void)
+{
+ if (loopdev_debug_mask)
+ return;
+ __UL_INIT_DEBUG_FROM_ENV(loopdev, LOOPDEV_DEBUG_, 0, LOOPDEV_DEBUG);
+}
+
+/*
+ * see loopcxt_init()
+ */
+#define loopcxt_ioctl_enabled(_lc) (!((_lc)->flags & LOOPDEV_FL_NOIOCTL))
+#define loopcxt_sysfs_available(_lc) (!((_lc)->flags & LOOPDEV_FL_NOSYSFS)) \
+ && !loopcxt_ioctl_enabled(_lc)
+
+/*
+ * @lc: context
+ * @device: device name, absolute device path or NULL to reset the current setting
+ *
+ * Sets device, absolute paths (e.g. "/dev/loop<N>") are unchanged, device
+ * names ("loop<N>") are converted to the path (/dev/loop<N> or to
+ * /dev/loop/<N>)
+ *
+ * This sets the device name, but does not check if the device exists!
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_set_device(struct loopdev_cxt *lc, const char *device)
+{
+ if (!lc)
+ return -EINVAL;
+
+ if (lc->fd >= 0) {
+ close(lc->fd);
+ DBG(CXT, ul_debugobj(lc, "closing old open fd"));
+ }
+ lc->fd = -1;
+ lc->mode = 0;
+ lc->has_info = 0;
+ lc->info_failed = 0;
+ *lc->device = '\0';
+ memset(&lc->info, 0, sizeof(lc->info));
+
+ /* set new */
+ if (device) {
+ if (*device != '/') {
+ const char *dir = _PATH_DEV;
+
+ /* compose device name for /dev/loop<n> or /dev/loop/<n> */
+ if (lc->flags & LOOPDEV_FL_DEVSUBDIR) {
+ if (strlen(device) < 5)
+ return -1;
+ device += 4;
+ dir = _PATH_DEV_LOOP "/"; /* _PATH_DEV uses tailing slash */
+ }
+ snprintf(lc->device, sizeof(lc->device), "%s%s",
+ dir, device);
+ } else {
+ strncpy(lc->device, device, sizeof(lc->device));
+ lc->device[sizeof(lc->device) - 1] = '\0';
+ }
+ DBG(CXT, ul_debugobj(lc, "%s name assigned", device));
+ }
+
+ ul_unref_path(lc->sysfs);
+ lc->sysfs = NULL;
+ return 0;
+}
+
+int loopcxt_has_device(struct loopdev_cxt *lc)
+{
+ return lc && *lc->device;
+}
+
+/*
+ * @lc: context
+ * @flags: LOOPDEV_FL_* flags
+ *
+ * Initialize loop handler.
+ *
+ * We have two sets of the flags:
+ *
+ * * LOOPDEV_FL_* flags control loopcxt_* API behavior
+ *
+ * * LO_FLAGS_* are kernel flags used for LOOP_{SET,GET}_STAT64 ioctls
+ *
+ * Note about LOOPDEV_FL_{RDONLY,RDWR} flags. These flags are used for open(2)
+ * syscall to open loop device. By default is the device open read-only.
+ *
+ * The exception is loopcxt_setup_device(), where the device is open read-write
+ * if LO_FLAGS_READ_ONLY flags is not set (see loopcxt_set_flags()).
+ *
+ * Returns: <0 on error, 0 on success.
+ */
+int loopcxt_init(struct loopdev_cxt *lc, int flags)
+{
+ int rc;
+ struct stat st;
+ struct loopdev_cxt dummy = UL_LOOPDEVCXT_EMPTY;
+
+ if (!lc)
+ return -EINVAL;
+
+ loopdev_init_debug();
+ DBG(CXT, ul_debugobj(lc, "initialize context"));
+
+ memcpy(lc, &dummy, sizeof(dummy));
+ lc->flags = flags;
+
+ rc = loopcxt_set_device(lc, NULL);
+ if (rc)
+ return rc;
+
+ if (stat(_PATH_SYS_BLOCK, &st) || !S_ISDIR(st.st_mode)) {
+ lc->flags |= LOOPDEV_FL_NOSYSFS;
+ lc->flags &= ~LOOPDEV_FL_NOIOCTL;
+ DBG(CXT, ul_debugobj(lc, "init: disable /sys usage"));
+ }
+
+ if (!(lc->flags & LOOPDEV_FL_NOSYSFS) &&
+ get_linux_version() >= KERNEL_VERSION(2,6,37)) {
+ /*
+ * Use only sysfs for basic information about loop devices
+ */
+ lc->flags |= LOOPDEV_FL_NOIOCTL;
+ DBG(CXT, ul_debugobj(lc, "init: ignore ioctls"));
+ }
+
+ if (!(lc->flags & LOOPDEV_FL_CONTROL) && !stat(_PATH_DEV_LOOPCTL, &st)) {
+ lc->flags |= LOOPDEV_FL_CONTROL;
+ DBG(CXT, ul_debugobj(lc, "init: loop-control detected "));
+ }
+
+ return 0;
+}
+
+/*
+ * @lc: context
+ *
+ * Deinitialize loop context
+ */
+void loopcxt_deinit(struct loopdev_cxt *lc)
+{
+ int errsv = errno;
+
+ if (!lc)
+ return;
+
+ DBG(CXT, ul_debugobj(lc, "de-initialize"));
+
+ free(lc->filename);
+ lc->filename = NULL;
+
+ ignore_result( loopcxt_set_device(lc, NULL) );
+ loopcxt_deinit_iterator(lc);
+
+ errno = errsv;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns newly allocated device path.
+ */
+char *loopcxt_strdup_device(struct loopdev_cxt *lc)
+{
+ if (!lc || !*lc->device)
+ return NULL;
+ return strdup(lc->device);
+}
+
+/*
+ * @lc: context
+ *
+ * Returns pointer device name in the @lc struct.
+ */
+const char *loopcxt_get_device(struct loopdev_cxt *lc)
+{
+ return lc && *lc->device ? lc->device : NULL;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns pointer to the sysfs context (see lib/sysfs.c)
+ */
+static struct path_cxt *loopcxt_get_sysfs(struct loopdev_cxt *lc)
+{
+ if (!lc || !*lc->device || (lc->flags & LOOPDEV_FL_NOSYSFS))
+ return NULL;
+
+ if (!lc->sysfs) {
+ dev_t devno = sysfs_devname_to_devno(lc->device);
+ if (!devno) {
+ DBG(CXT, ul_debugobj(lc, "sysfs: failed devname to devno"));
+ return NULL;
+ }
+
+ lc->sysfs = ul_new_sysfs_path(devno, NULL, NULL);
+ if (!lc->sysfs)
+ DBG(CXT, ul_debugobj(lc, "sysfs: init failed"));
+ }
+
+ return lc->sysfs;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns: file descriptor to the open loop device or <0 on error. The mode
+ * depends on LOOPDEV_FL_{RDWR,RDONLY} context flags. Default is
+ * read-only.
+ */
+int loopcxt_get_fd(struct loopdev_cxt *lc)
+{
+ if (!lc || !*lc->device)
+ return -EINVAL;
+
+ if (lc->fd < 0) {
+ lc->mode = lc->flags & LOOPDEV_FL_RDWR ? O_RDWR : O_RDONLY;
+ lc->fd = open(lc->device, lc->mode | O_CLOEXEC);
+ DBG(CXT, ul_debugobj(lc, "open %s [%s]: %m", lc->device,
+ lc->flags & LOOPDEV_FL_RDWR ? "rw" : "ro"));
+ }
+ return lc->fd;
+}
+
+int loopcxt_set_fd(struct loopdev_cxt *lc, int fd, int mode)
+{
+ if (!lc)
+ return -EINVAL;
+
+ lc->fd = fd;
+ lc->mode = mode;
+ return 0;
+}
+
+/*
+ * @lc: context
+ * @flags: LOOPITER_FL_* flags
+ *
+ * Iterator allows to scan list of the free or used loop devices.
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_init_iterator(struct loopdev_cxt *lc, int flags)
+{
+ struct loopdev_iter *iter;
+ struct stat st;
+
+ if (!lc)
+ return -EINVAL;
+
+
+ iter = &lc->iter;
+ DBG(ITER, ul_debugobj(iter, "initialize"));
+
+ /* always zeroize
+ */
+ memset(iter, 0, sizeof(*iter));
+ iter->ncur = -1;
+ iter->flags = flags;
+ iter->default_check = 1;
+
+ if (!lc->extra_check) {
+ /*
+ * Check for /dev/loop/<N> subdirectory
+ */
+ if (!(lc->flags & LOOPDEV_FL_DEVSUBDIR) &&
+ stat(_PATH_DEV_LOOP, &st) == 0 && S_ISDIR(st.st_mode))
+ lc->flags |= LOOPDEV_FL_DEVSUBDIR;
+
+ lc->extra_check = 1;
+ }
+ return 0;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_deinit_iterator(struct loopdev_cxt *lc)
+{
+ struct loopdev_iter *iter;
+
+ if (!lc)
+ return -EINVAL;
+
+ iter = &lc->iter;
+ DBG(ITER, ul_debugobj(iter, "de-initialize"));
+
+ free(iter->minors);
+ if (iter->proc)
+ fclose(iter->proc);
+ if (iter->sysblock)
+ closedir(iter->sysblock);
+
+ memset(iter, 0, sizeof(*iter));
+ return 0;
+}
+
+/*
+ * Same as loopcxt_set_device, but also checks if the device is
+ * associeted with any file.
+ *
+ * Returns: <0 on error, 0 on success, 1 device does not match with
+ * LOOPITER_FL_{USED,FREE} flags.
+ */
+static int loopiter_set_device(struct loopdev_cxt *lc, const char *device)
+{
+ int rc = loopcxt_set_device(lc, device);
+ int used;
+
+ if (rc)
+ return rc;
+
+ if (!(lc->iter.flags & LOOPITER_FL_USED) &&
+ !(lc->iter.flags & LOOPITER_FL_FREE))
+ return 0; /* caller does not care about device status */
+
+ if (!is_loopdev(lc->device)) {
+ DBG(ITER, ul_debugobj(&lc->iter, "%s does not exist", lc->device));
+ return -errno;
+ }
+
+ DBG(ITER, ul_debugobj(&lc->iter, "%s exist", lc->device));
+
+ used = loopcxt_get_offset(lc, NULL) == 0;
+
+ if ((lc->iter.flags & LOOPITER_FL_USED) && used)
+ return 0;
+
+ if ((lc->iter.flags & LOOPITER_FL_FREE) && !used)
+ return 0;
+
+ DBG(ITER, ul_debugobj(&lc->iter, "failed to use %s device", lc->device));
+
+ ignore_result( loopcxt_set_device(lc, NULL) );
+ return 1;
+}
+
+static int cmpnum(const void *p1, const void *p2)
+{
+ return (((* (const int *) p1) > (* (const int *) p2)) -
+ ((* (const int *) p1) < (* (const int *) p2)));
+}
+
+/*
+ * The classic scandir() is more expensive and less portable.
+ * We needn't full loop device names -- loop numbers (loop<N>)
+ * are enough.
+ */
+static int loop_scandir(const char *dirname, int **ary, int hasprefix)
+{
+ DIR *dir;
+ struct dirent *d;
+ unsigned int n, count = 0, arylen = 0;
+
+ if (!dirname || !ary)
+ return 0;
+
+ DBG(ITER, ul_debug("scan dir: %s", dirname));
+
+ dir = opendir(dirname);
+ if (!dir)
+ return 0;
+ free(*ary);
+ *ary = NULL;
+
+ while((d = readdir(dir))) {
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (d->d_type != DT_BLK && d->d_type != DT_UNKNOWN &&
+ d->d_type != DT_LNK)
+ continue;
+#endif
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ if (hasprefix) {
+ /* /dev/loop<N> */
+ if (sscanf(d->d_name, "loop%u", &n) != 1)
+ continue;
+ } else {
+ /* /dev/loop/<N> */
+ char *end = NULL;
+
+ errno = 0;
+ n = strtol(d->d_name, &end, 10);
+ if (d->d_name == end || (end && *end) || errno)
+ continue;
+ }
+ if (n < LOOPDEV_DEFAULT_NNODES)
+ continue; /* ignore loop<0..7> */
+
+ if (count + 1 > arylen) {
+ int *tmp;
+
+ arylen += 1;
+
+ tmp = realloc(*ary, arylen * sizeof(int));
+ if (!tmp) {
+ free(*ary);
+ *ary = NULL;
+ closedir(dir);
+ return -1;
+ }
+ *ary = tmp;
+ }
+ if (*ary)
+ (*ary)[count++] = n;
+ }
+ if (count && *ary)
+ qsort(*ary, count, sizeof(int), cmpnum);
+
+ closedir(dir);
+ return count;
+}
+
+/*
+ * Set the next *used* loop device according to /proc/partitions.
+ *
+ * Loop devices smaller than 512 bytes are invisible for this function.
+ */
+static int loopcxt_next_from_proc(struct loopdev_cxt *lc)
+{
+ struct loopdev_iter *iter = &lc->iter;
+ char buf[BUFSIZ];
+
+ DBG(ITER, ul_debugobj(iter, "scan /proc/partitions"));
+
+ if (!iter->proc)
+ iter->proc = fopen(_PATH_PROC_PARTITIONS, "r" UL_CLOEXECSTR);
+ if (!iter->proc)
+ return 1;
+
+ while (fgets(buf, sizeof(buf), iter->proc)) {
+ unsigned int m;
+ char name[128 + 1];
+
+
+ if (sscanf(buf, " %u %*s %*s %128[^\n ]",
+ &m, name) != 2 || m != LOOPDEV_MAJOR)
+ continue;
+
+ DBG(ITER, ul_debugobj(iter, "checking %s", name));
+
+ if (loopiter_set_device(lc, name) == 0)
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * Set the next *used* loop device according to
+ * /sys/block/loopN/loop/backing_file (kernel >= 2.6.37 is required).
+ *
+ * This is preferred method.
+ */
+static int loopcxt_next_from_sysfs(struct loopdev_cxt *lc)
+{
+ struct loopdev_iter *iter = &lc->iter;
+ struct dirent *d;
+ int fd;
+
+ DBG(ITER, ul_debugobj(iter, "scanning /sys/block"));
+
+ if (!iter->sysblock)
+ iter->sysblock = opendir(_PATH_SYS_BLOCK);
+
+ if (!iter->sysblock)
+ return 1;
+
+ fd = dirfd(iter->sysblock);
+
+ while ((d = readdir(iter->sysblock))) {
+ char name[NAME_MAX + 18 + 1];
+ struct stat st;
+
+ DBG(ITER, ul_debugobj(iter, "check %s", d->d_name));
+
+ if (strcmp(d->d_name, ".") == 0
+ || strcmp(d->d_name, "..") == 0
+ || strncmp(d->d_name, "loop", 4) != 0)
+ continue;
+
+ snprintf(name, sizeof(name), "%s/loop/backing_file", d->d_name);
+ if (fstatat(fd, name, &st, 0) != 0)
+ continue;
+
+ if (loopiter_set_device(lc, d->d_name) == 0)
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * @lc: context, has to initialized by loopcxt_init_iterator()
+ *
+ * Returns: 0 on success, -1 on error, 1 at the end of scanning. The details
+ * about the current loop device are available by
+ * loopcxt_get_{fd,backing_file,device,offset, ...} functions.
+ */
+int loopcxt_next(struct loopdev_cxt *lc)
+{
+ struct loopdev_iter *iter;
+
+ if (!lc)
+ return -EINVAL;
+
+
+ iter = &lc->iter;
+ if (iter->done)
+ return 1;
+
+ DBG(ITER, ul_debugobj(iter, "next"));
+
+ /* A) Look for used loop devices in /proc/partitions ("losetup -a" only)
+ */
+ if (iter->flags & LOOPITER_FL_USED) {
+ int rc;
+
+ if (loopcxt_sysfs_available(lc))
+ rc = loopcxt_next_from_sysfs(lc);
+ else
+ rc = loopcxt_next_from_proc(lc);
+ if (rc == 0)
+ return 0;
+ goto done;
+ }
+
+ /* B) Classic way, try first eight loop devices (default number
+ * of loop devices). This is enough for 99% of all cases.
+ */
+ if (iter->default_check) {
+ DBG(ITER, ul_debugobj(iter, "next: default check"));
+ for (++iter->ncur; iter->ncur < LOOPDEV_DEFAULT_NNODES;
+ iter->ncur++) {
+ char name[16];
+ snprintf(name, sizeof(name), "loop%d", iter->ncur);
+
+ if (loopiter_set_device(lc, name) == 0)
+ return 0;
+ }
+ iter->default_check = 0;
+ }
+
+ /* C) the worst possibility, scan whole /dev or /dev/loop/<N>
+ */
+ if (!iter->minors) {
+ DBG(ITER, ul_debugobj(iter, "next: scanning /dev"));
+ iter->nminors = (lc->flags & LOOPDEV_FL_DEVSUBDIR) ?
+ loop_scandir(_PATH_DEV_LOOP, &iter->minors, 0) :
+ loop_scandir(_PATH_DEV, &iter->minors, 1);
+ iter->ncur = -1;
+ }
+ for (++iter->ncur; iter->ncur < iter->nminors; iter->ncur++) {
+ char name[16];
+ snprintf(name, sizeof(name), "loop%d", iter->minors[iter->ncur]);
+
+ if (loopiter_set_device(lc, name) == 0)
+ return 0;
+ }
+done:
+ loopcxt_deinit_iterator(lc);
+ return 1;
+}
+
+/*
+ * @device: path to device
+ */
+int is_loopdev(const char *device)
+{
+ struct stat st;
+
+ if (device && stat(device, &st) == 0 &&
+ S_ISBLK(st.st_mode) &&
+ major(st.st_rdev) == LOOPDEV_MAJOR)
+ return 1;
+
+ errno = ENODEV;
+ return 0;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns result from LOOP_GET_STAT64 ioctl or NULL on error.
+ */
+struct loop_info64 *loopcxt_get_info(struct loopdev_cxt *lc)
+{
+ int fd;
+
+ if (!lc || lc->info_failed) {
+ errno = EINVAL;
+ return NULL;
+ }
+ errno = 0;
+ if (lc->has_info)
+ return &lc->info;
+
+ fd = loopcxt_get_fd(lc);
+ if (fd < 0)
+ return NULL;
+
+ if (ioctl(fd, LOOP_GET_STATUS64, &lc->info) == 0) {
+ lc->has_info = 1;
+ lc->info_failed = 0;
+ DBG(CXT, ul_debugobj(lc, "reading loop_info64 OK"));
+ return &lc->info;
+ }
+
+ lc->info_failed = 1;
+ DBG(CXT, ul_debugobj(lc, "reading loop_info64 FAILED"));
+
+ return NULL;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns (allocated) string with path to the file assicieted
+ * with the current loop device.
+ */
+char *loopcxt_get_backing_file(struct loopdev_cxt *lc)
+{
+ struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
+ char *res = NULL;
+
+ if (sysfs)
+ /*
+ * This is always preffered, the loop_info64
+ * has too small buffer for the filename.
+ */
+ ul_path_read_string(sysfs, &res, "loop/backing_file");
+
+ if (!res && loopcxt_ioctl_enabled(lc)) {
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+
+ if (lo) {
+ lo->lo_file_name[LO_NAME_SIZE - 2] = '*';
+ lo->lo_file_name[LO_NAME_SIZE - 1] = '\0';
+ res = strdup((char *) lo->lo_file_name);
+ }
+ }
+
+ DBG(CXT, ul_debugobj(lc, "get_backing_file [%s]", res));
+ return res;
+}
+
+/*
+ * @lc: context
+ * @offset: returns offset number for the given device
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_get_offset(struct loopdev_cxt *lc, uint64_t *offset)
+{
+ struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
+ int rc = -EINVAL;
+
+ if (sysfs)
+ rc = ul_path_read_u64(sysfs, offset, "loop/offset");
+
+ if (rc && loopcxt_ioctl_enabled(lc)) {
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+ if (lo) {
+ if (offset)
+ *offset = lo->lo_offset;
+ rc = 0;
+ } else
+ rc = -errno;
+ }
+
+ DBG(CXT, ul_debugobj(lc, "get_offset [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * @lc: context
+ * @blocksize: returns logical blocksize for the given device
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_get_blocksize(struct loopdev_cxt *lc, uint64_t *blocksize)
+{
+ struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
+ int rc = -EINVAL;
+
+ if (sysfs)
+ rc = ul_path_read_u64(sysfs, blocksize, "queue/logical_block_size");
+
+ /* Fallback based on BLKSSZGET ioctl */
+ if (rc) {
+ int fd = loopcxt_get_fd(lc);
+ int sz = 0;
+
+ if (fd < 0)
+ return -EINVAL;
+ rc = blkdev_get_sector_size(fd, &sz);
+ if (rc)
+ return rc;
+
+ *blocksize = sz;
+ }
+
+ DBG(CXT, ul_debugobj(lc, "get_blocksize [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * @lc: context
+ * @sizelimit: returns size limit for the given device
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_get_sizelimit(struct loopdev_cxt *lc, uint64_t *size)
+{
+ struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
+ int rc = -EINVAL;
+
+ if (sysfs)
+ rc = ul_path_read_u64(sysfs, size, "loop/sizelimit");
+
+ if (rc && loopcxt_ioctl_enabled(lc)) {
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+ if (lo) {
+ if (size)
+ *size = lo->lo_sizelimit;
+ rc = 0;
+ } else
+ rc = -errno;
+ }
+
+ DBG(CXT, ul_debugobj(lc, "get_sizelimit [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * @lc: context
+ * @devno: returns encryption type
+ *
+ * Cryptoloop is DEPRECATED!
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_get_encrypt_type(struct loopdev_cxt *lc, uint32_t *type)
+{
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+ int rc;
+
+ /* not provided by sysfs */
+ if (lo) {
+ if (type)
+ *type = lo->lo_encrypt_type;
+ rc = 0;
+ } else
+ rc = -errno;
+
+ DBG(CXT, ul_debugobj(lc, "get_encrypt_type [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * @lc: context
+ * @devno: returns crypt name
+ *
+ * Cryptoloop is DEPRECATED!
+ *
+ * Returns: <0 on error, 0 on success
+ */
+const char *loopcxt_get_crypt_name(struct loopdev_cxt *lc)
+{
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+
+ if (lo)
+ return (char *) lo->lo_crypt_name;
+
+ DBG(CXT, ul_debugobj(lc, "get_crypt_name failed"));
+ return NULL;
+}
+
+/*
+ * @lc: context
+ * @devno: returns backing file devno
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_get_backing_devno(struct loopdev_cxt *lc, dev_t *devno)
+{
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+ int rc;
+
+ if (lo) {
+ if (devno)
+ *devno = lo->lo_device;
+ rc = 0;
+ } else
+ rc = -errno;
+
+ DBG(CXT, ul_debugobj(lc, "get_backing_devno [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * @lc: context
+ * @ino: returns backing file inode
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_get_backing_inode(struct loopdev_cxt *lc, ino_t *ino)
+{
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+ int rc;
+
+ if (lo) {
+ if (ino)
+ *ino = lo->lo_inode;
+ rc = 0;
+ } else
+ rc = -errno;
+
+ DBG(CXT, ul_debugobj(lc, "get_backing_inode [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * Check if the kernel supports partitioned loop devices.
+ *
+ * Notes:
+ * - kernels < 3.2 support partitioned loop devices and PT scanning
+ * only if max_part= module parameter is non-zero
+ *
+ * - kernels >= 3.2 always support partitioned loop devices
+ *
+ * - kernels >= 3.2 always support BLKPG_{ADD,DEL}_PARTITION ioctls
+ *
+ * - kernels >= 3.2 enable PT scanner only if max_part= is non-zero or if the
+ * LO_FLAGS_PARTSCAN flag is set for the device. The PT scanner is disabled
+ * by default.
+ *
+ * See kernel commit e03c8dd14915fabc101aa495828d58598dc5af98.
+ */
+int loopmod_supports_partscan(void)
+{
+ int rc, ret = 0;
+ FILE *f;
+
+ if (get_linux_version() >= KERNEL_VERSION(3,2,0))
+ return 1;
+
+ f = fopen("/sys/module/loop/parameters/max_part", "r" UL_CLOEXECSTR);
+ if (!f)
+ return 0;
+ rc = fscanf(f, "%d", &ret);
+ fclose(f);
+ return rc == 1 ? ret : 0;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns: 1 if the partscan flags is set *or* (for old kernels) partitions
+ * scanning is enabled for all loop devices.
+ */
+int loopcxt_is_partscan(struct loopdev_cxt *lc)
+{
+ struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
+
+ if (sysfs) {
+ /* kernel >= 3.2 */
+ int fl;
+ if (ul_path_read_s32(sysfs, &fl, "loop/partscan") == 0)
+ return fl;
+ }
+
+ /* old kernels (including kernels without loopN/loop/<flags> directory */
+ return loopmod_supports_partscan();
+}
+
+/*
+ * @lc: context
+ *
+ * Returns: 1 if the autoclear flags is set.
+ */
+int loopcxt_is_autoclear(struct loopdev_cxt *lc)
+{
+ struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
+
+ if (sysfs) {
+ int fl;
+ if (ul_path_read_s32(sysfs, &fl, "loop/autoclear") == 0)
+ return fl;
+ }
+
+ if (loopcxt_ioctl_enabled(lc)) {
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+ if (lo)
+ return lo->lo_flags & LO_FLAGS_AUTOCLEAR;
+ }
+ return 0;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns: 1 if the readonly flags is set.
+ */
+int loopcxt_is_readonly(struct loopdev_cxt *lc)
+{
+ struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
+
+ if (sysfs) {
+ int fl;
+ if (ul_path_read_s32(sysfs, &fl, "ro") == 0)
+ return fl;
+ }
+
+ if (loopcxt_ioctl_enabled(lc)) {
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+ if (lo)
+ return lo->lo_flags & LO_FLAGS_READ_ONLY;
+ }
+ return 0;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns: 1 if the dio flags is set.
+ */
+int loopcxt_is_dio(struct loopdev_cxt *lc)
+{
+ struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
+
+ if (sysfs) {
+ int fl;
+ if (ul_path_read_s32(sysfs, &fl, "loop/dio") == 0)
+ return fl;
+ }
+ if (loopcxt_ioctl_enabled(lc)) {
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+ if (lo)
+ return lo->lo_flags & LO_FLAGS_DIRECT_IO;
+ }
+ return 0;
+}
+
+/*
+ * @lc: context
+ * @st: backing file stat or NULL
+ * @backing_file: filename
+ * @offset: offset (use LOOPDEV_FL_OFFSET if specified)
+ * @sizelimit: size limit (use LOOPDEV_FL_SIZELIMIT if specified)
+ * @flags: LOOPDEV_FL_{OFFSET,SIZELIMIT}
+ *
+ * Returns 1 if the current @lc loopdev is associated with the given backing
+ * file. Note that the preferred way is to use devno and inode number rather
+ * than filename. The @backing_file filename is poor solution usable in case
+ * that you don't have rights to call stat().
+ *
+ * LOOPDEV_FL_SIZELIMIT requires LOOPDEV_FL_OFFSET being set as well.
+ *
+ * Don't forget that old kernels provide very restricted (in size) backing
+ * filename by LOOP_GET_STAT64 ioctl only.
+ */
+int loopcxt_is_used(struct loopdev_cxt *lc,
+ struct stat *st,
+ const char *backing_file,
+ uint64_t offset,
+ uint64_t sizelimit,
+ int flags)
+{
+ ino_t ino;
+ dev_t dev;
+
+ if (!lc)
+ return 0;
+
+ DBG(CXT, ul_debugobj(lc, "checking %s vs. %s",
+ loopcxt_get_device(lc),
+ backing_file));
+
+ if (st && loopcxt_get_backing_inode(lc, &ino) == 0 &&
+ loopcxt_get_backing_devno(lc, &dev) == 0) {
+
+ if (ino == st->st_ino && dev == st->st_dev)
+ goto found;
+
+ /* don't use filename if we have devno and inode */
+ return 0;
+ }
+
+ /* poor man's solution */
+ if (backing_file) {
+ char *name = loopcxt_get_backing_file(lc);
+ int rc = name && strcmp(name, backing_file) == 0;
+
+ free(name);
+ if (rc)
+ goto found;
+ }
+
+ return 0;
+found:
+ if (flags & LOOPDEV_FL_OFFSET) {
+ uint64_t off;
+
+ int rc = loopcxt_get_offset(lc, &off) == 0 && off == offset;
+
+ if (rc && flags & LOOPDEV_FL_SIZELIMIT) {
+ uint64_t sz;
+
+ return loopcxt_get_sizelimit(lc, &sz) == 0 && sz == sizelimit;
+ } else
+ return rc;
+ }
+ return 1;
+}
+
+/*
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ */
+int loopcxt_set_offset(struct loopdev_cxt *lc, uint64_t offset)
+{
+ if (!lc)
+ return -EINVAL;
+ lc->info.lo_offset = offset;
+
+ DBG(CXT, ul_debugobj(lc, "set offset=%jd", offset));
+ return 0;
+}
+
+/*
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ */
+int loopcxt_set_sizelimit(struct loopdev_cxt *lc, uint64_t sizelimit)
+{
+ if (!lc)
+ return -EINVAL;
+ lc->info.lo_sizelimit = sizelimit;
+
+ DBG(CXT, ul_debugobj(lc, "set sizelimit=%jd", sizelimit));
+ return 0;
+}
+
+/*
+ * @lc: context
+ * @flags: kernel LO_FLAGS_{READ_ONLY,USE_AOPS,AUTOCLEAR} flags
+ *
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int loopcxt_set_flags(struct loopdev_cxt *lc, uint32_t flags)
+{
+ if (!lc)
+ return -EINVAL;
+ lc->info.lo_flags = flags;
+
+ DBG(CXT, ul_debugobj(lc, "set flags=%u", (unsigned) flags));
+ return 0;
+}
+
+/*
+ * @lc: context
+ * @filename: backing file path (the path will be canonicalized)
+ *
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int loopcxt_set_backing_file(struct loopdev_cxt *lc, const char *filename)
+{
+ if (!lc)
+ return -EINVAL;
+
+ lc->filename = canonicalize_path(filename);
+ if (!lc->filename)
+ return -errno;
+
+ strncpy((char *)lc->info.lo_file_name, lc->filename, LO_NAME_SIZE);
+ lc->info.lo_file_name[LO_NAME_SIZE- 1] = '\0';
+
+ DBG(CXT, ul_debugobj(lc, "set backing file=%s", lc->info.lo_file_name));
+ return 0;
+}
+
+/*
+ * In kernels prior to v3.9, if the offset or sizelimit options
+ * are used, the block device's size won't be synced automatically.
+ * blockdev --getsize64 and filesystems will use the backing
+ * file size until the block device has been re-opened or the
+ * LOOP_SET_CAPACITY ioctl is called to sync the sizes.
+ *
+ * Since mount -oloop uses the LO_FLAGS_AUTOCLEAR option and passes
+ * the open file descriptor to the mount system call, we need to use
+ * the ioctl. Calling losetup directly doesn't have this problem since
+ * it closes the device when it exits and whatever consumes the device
+ * next will re-open it, causing the resync.
+ */
+static int loopcxt_check_size(struct loopdev_cxt *lc, int file_fd)
+{
+ uint64_t size, expected_size;
+ int dev_fd;
+ struct stat st;
+
+ if (!lc->info.lo_offset && !lc->info.lo_sizelimit)
+ return 0;
+
+ if (fstat(file_fd, &st)) {
+ DBG(CXT, ul_debugobj(lc, "failed to fstat backing file"));
+ return -errno;
+ }
+ if (S_ISBLK(st.st_mode)) {
+ if (blkdev_get_size(file_fd,
+ (unsigned long long *) &expected_size)) {
+ DBG(CXT, ul_debugobj(lc, "failed to determine device size"));
+ return -errno;
+ }
+ } else
+ expected_size = st.st_size;
+
+ if (expected_size == 0 || expected_size <= lc->info.lo_offset) {
+ DBG(CXT, ul_debugobj(lc, "failed to determine expected size"));
+ return 0; /* ignore this error */
+ }
+
+ if (lc->info.lo_offset > 0)
+ expected_size -= lc->info.lo_offset;
+
+ if (lc->info.lo_sizelimit > 0 && lc->info.lo_sizelimit < expected_size)
+ expected_size = lc->info.lo_sizelimit;
+
+ dev_fd = loopcxt_get_fd(lc);
+ if (dev_fd < 0) {
+ DBG(CXT, ul_debugobj(lc, "failed to get loop FD"));
+ return -errno;
+ }
+
+ if (blkdev_get_size(dev_fd, (unsigned long long *) &size)) {
+ DBG(CXT, ul_debugobj(lc, "failed to determine loopdev size"));
+ return -errno;
+ }
+
+ /* It's block device, so, align to 512-byte sectors */
+ if (expected_size % 512) {
+ DBG(CXT, ul_debugobj(lc, "expected size misaligned to 512-byte sectors"));
+ expected_size = (expected_size >> 9) << 9;
+ }
+
+ if (expected_size != size) {
+ DBG(CXT, ul_debugobj(lc, "warning: loopdev and expected "
+ "size mismatch (%ju/%ju)",
+ size, expected_size));
+
+ if (loopcxt_set_capacity(lc)) {
+ /* ioctl not available */
+ if (errno == ENOTTY || errno == EINVAL)
+ errno = ERANGE;
+ return -errno;
+ }
+
+ if (blkdev_get_size(dev_fd, (unsigned long long *) &size))
+ return -errno;
+
+ if (expected_size != size) {
+ errno = ERANGE;
+ DBG(CXT, ul_debugobj(lc, "failed to set loopdev size, "
+ "size: %ju, expected: %ju",
+ size, expected_size));
+ return -errno;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * @lc: context
+ *
+ * Associate the current device (see loopcxt_{set,get}_device()) with
+ * a file (see loopcxt_set_backing_file()).
+ *
+ * The device is initialized read-write by default. If you want read-only
+ * device then set LO_FLAGS_READ_ONLY by loopcxt_set_flags(). The LOOPDEV_FL_*
+ * flags are ignored and modified according to LO_FLAGS_*.
+ *
+ * If the device is already open by loopcxt_get_fd() then this setup device
+ * function will re-open the device to fix read/write mode.
+ *
+ * The device is also initialized read-only if the backing file is not
+ * possible to open read-write (e.g. read-only FS).
+ *
+ * Returns: <0 on error, 0 on success.
+ */
+int loopcxt_setup_device(struct loopdev_cxt *lc)
+{
+ int file_fd, dev_fd, mode = O_RDWR, rc = -1, cnt = 0;
+ int errsv = 0;
+
+ if (!lc || !*lc->device || !lc->filename)
+ return -EINVAL;
+
+ DBG(SETUP, ul_debugobj(lc, "device setup requested"));
+
+ /*
+ * Open backing file and device
+ */
+ if (lc->info.lo_flags & LO_FLAGS_READ_ONLY)
+ mode = O_RDONLY;
+
+ if ((file_fd = open(lc->filename, mode | O_CLOEXEC)) < 0) {
+ if (mode != O_RDONLY && (errno == EROFS || errno == EACCES))
+ file_fd = open(lc->filename, mode = O_RDONLY);
+
+ if (file_fd < 0) {
+ DBG(SETUP, ul_debugobj(lc, "open backing file failed: %m"));
+ return -errno;
+ }
+ }
+ DBG(SETUP, ul_debugobj(lc, "backing file open: OK"));
+
+ if (lc->fd != -1 && lc->mode != mode) {
+ DBG(SETUP, ul_debugobj(lc, "closing already open device (mode mismatch)"));
+ close(lc->fd);
+ lc->fd = -1;
+ lc->mode = 0;
+ }
+
+ if (mode == O_RDONLY) {
+ lc->flags |= LOOPDEV_FL_RDONLY; /* open() mode */
+ lc->info.lo_flags |= LO_FLAGS_READ_ONLY; /* kernel loopdev mode */
+ } else {
+ lc->flags |= LOOPDEV_FL_RDWR; /* open() mode */
+ lc->info.lo_flags &= ~LO_FLAGS_READ_ONLY;
+ lc->flags &= ~LOOPDEV_FL_RDONLY;
+ }
+
+ do {
+ errno = 0;
+ dev_fd = loopcxt_get_fd(lc);
+ if (dev_fd >= 0 || lc->control_ok == 0)
+ break;
+ if (errno != EACCES && errno != ENOENT)
+ break;
+ /* We have permissions to open /dev/loop-control, but open
+ * /dev/loopN failed with EACCES, it's probably because udevd
+ * does not applied chown yet. Let's wait a moment. */
+ xusleep(25000);
+ } while (cnt++ < 16);
+
+ if (dev_fd < 0) {
+ rc = -errno;
+ goto err;
+ }
+
+ DBG(SETUP, ul_debugobj(lc, "device open: OK"));
+
+ /*
+ * Set FD
+ */
+ if (ioctl(dev_fd, LOOP_SET_FD, file_fd) < 0) {
+ rc = -errno;
+ errsv = errno;
+ DBG(SETUP, ul_debugobj(lc, "LOOP_SET_FD failed: %m"));
+ goto err;
+ }
+
+ DBG(SETUP, ul_debugobj(lc, "LOOP_SET_FD: OK"));
+
+ if (ioctl(dev_fd, LOOP_SET_STATUS64, &lc->info)) {
+ rc = -errno;
+ errsv = errno;
+ DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64 failed: %m"));
+ goto err;
+ }
+
+ DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64: OK"));
+
+ if ((rc = loopcxt_check_size(lc, file_fd)))
+ goto err;
+
+ close(file_fd);
+
+ memset(&lc->info, 0, sizeof(lc->info));
+ lc->has_info = 0;
+ lc->info_failed = 0;
+
+ DBG(SETUP, ul_debugobj(lc, "success [rc=0]"));
+ return 0;
+err:
+ if (file_fd >= 0)
+ close(file_fd);
+ if (dev_fd >= 0 && rc != -EBUSY)
+ ioctl(dev_fd, LOOP_CLR_FD, 0);
+ if (errsv)
+ errno = errsv;
+
+ DBG(SETUP, ul_debugobj(lc, "failed [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * @lc: context
+ *
+ * Update status of the current device (see loopcxt_{set,get}_device()).
+ *
+ * Note that once initialized, kernel accepts only selected changes:
+ * LO_FLAGS_AUTOCLEAR and LO_FLAGS_PARTSCAN
+ * For more see linux/drivers/block/loop.c:loop_set_status()
+ *
+ * Returns: <0 on error, 0 on success.
+ */
+int loopcxt_set_status(struct loopdev_cxt *lc)
+{
+ int dev_fd, rc = -1;
+
+ errno = 0;
+ dev_fd = loopcxt_get_fd(lc);
+
+ if (dev_fd < 0) {
+ rc = -errno;
+ return rc;
+ }
+ DBG(SETUP, ul_debugobj(lc, "device open: OK"));
+
+ if (ioctl(dev_fd, LOOP_SET_STATUS64, &lc->info)) {
+ rc = -errno;
+ DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64 failed: %m"));
+ return rc;
+ }
+
+ DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64: OK"));
+ return 0;
+}
+
+int loopcxt_set_capacity(struct loopdev_cxt *lc)
+{
+ int fd = loopcxt_get_fd(lc);
+
+ if (fd < 0)
+ return -EINVAL;
+
+ /* Kernels prior to v2.6.30 don't support this ioctl */
+ if (ioctl(fd, LOOP_SET_CAPACITY, 0) < 0) {
+ int rc = -errno;
+ DBG(CXT, ul_debugobj(lc, "LOOP_SET_CAPACITY failed: %m"));
+ return rc;
+ }
+
+ DBG(CXT, ul_debugobj(lc, "capacity set"));
+ return 0;
+}
+
+int loopcxt_set_dio(struct loopdev_cxt *lc, unsigned long use_dio)
+{
+ int fd = loopcxt_get_fd(lc);
+
+ if (fd < 0)
+ return -EINVAL;
+
+ /* Kernels prior to v4.4 don't support this ioctl */
+ if (ioctl(fd, LOOP_SET_DIRECT_IO, use_dio) < 0) {
+ int rc = -errno;
+ DBG(CXT, ul_debugobj(lc, "LOOP_SET_DIRECT_IO failed: %m"));
+ return rc;
+ }
+
+ DBG(CXT, ul_debugobj(lc, "direct io set"));
+ return 0;
+}
+
+/*
+ * Kernel uses "unsigned long" as ioctl arg, but we use u64 for all sizes to
+ * keep loopdev internal API simple.
+ */
+int loopcxt_set_blocksize(struct loopdev_cxt *lc, uint64_t blocksize)
+{
+ int fd = loopcxt_get_fd(lc);
+
+ if (fd < 0)
+ return -EINVAL;
+
+ /* Kernels prior to v4.14 don't support this ioctl */
+ if (ioctl(fd, LOOP_SET_BLOCK_SIZE, (unsigned long) blocksize) < 0) {
+ int rc = -errno;
+ DBG(CXT, ul_debugobj(lc, "LOOP_SET_BLOCK_SIZE failed: %m"));
+ return rc;
+ }
+
+ DBG(CXT, ul_debugobj(lc, "logical block size set"));
+ return 0;
+}
+
+int loopcxt_delete_device(struct loopdev_cxt *lc)
+{
+ int fd = loopcxt_get_fd(lc);
+
+ if (fd < 0)
+ return -EINVAL;
+
+ if (ioctl(fd, LOOP_CLR_FD, 0) < 0) {
+ DBG(CXT, ul_debugobj(lc, "LOOP_CLR_FD failed: %m"));
+ return -errno;
+ }
+
+ DBG(CXT, ul_debugobj(lc, "device removed"));
+ return 0;
+}
+
+int loopcxt_add_device(struct loopdev_cxt *lc)
+{
+ int rc = -EINVAL;
+ int ctl, nr = -1;
+ const char *p, *dev = loopcxt_get_device(lc);
+
+ if (!dev)
+ goto done;
+
+ if (!(lc->flags & LOOPDEV_FL_CONTROL)) {
+ rc = -ENOSYS;
+ goto done;
+ }
+
+ p = strrchr(dev, '/');
+ if (!p || (sscanf(p, "/loop%d", &nr) != 1 && sscanf(p, "/%d", &nr) != 1)
+ || nr < 0)
+ goto done;
+
+ ctl = open(_PATH_DEV_LOOPCTL, O_RDWR|O_CLOEXEC);
+ if (ctl >= 0) {
+ DBG(CXT, ul_debugobj(lc, "add_device %d", nr));
+ rc = ioctl(ctl, LOOP_CTL_ADD, nr);
+ close(ctl);
+ }
+ lc->control_ok = rc >= 0 ? 1 : 0;
+done:
+ DBG(CXT, ul_debugobj(lc, "add_device done [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * Note that LOOP_CTL_GET_FREE ioctl is supported since kernel 3.1. In older
+ * kernels we have to check all loop devices to found unused one.
+ *
+ * See kernel commit 770fe30a46a12b6fb6b63fbe1737654d28e8484.
+ */
+int loopcxt_find_unused(struct loopdev_cxt *lc)
+{
+ int rc = -1;
+
+ DBG(CXT, ul_debugobj(lc, "find_unused requested"));
+
+ if (lc->flags & LOOPDEV_FL_CONTROL) {
+ int ctl;
+
+ DBG(CXT, ul_debugobj(lc, "using loop-control"));
+
+ ctl = open(_PATH_DEV_LOOPCTL, O_RDWR|O_CLOEXEC);
+ if (ctl >= 0)
+ rc = ioctl(ctl, LOOP_CTL_GET_FREE);
+ if (rc >= 0) {
+ char name[16];
+ snprintf(name, sizeof(name), "loop%d", rc);
+
+ rc = loopiter_set_device(lc, name);
+ }
+ lc->control_ok = ctl >= 0 && rc == 0 ? 1 : 0;
+ if (ctl >= 0)
+ close(ctl);
+ DBG(CXT, ul_debugobj(lc, "find_unused by loop-control [rc=%d]", rc));
+ }
+
+ if (rc < 0) {
+ DBG(CXT, ul_debugobj(lc, "using loop scan"));
+ rc = loopcxt_init_iterator(lc, LOOPITER_FL_FREE);
+ if (rc)
+ return rc;
+
+ rc = loopcxt_next(lc);
+ loopcxt_deinit_iterator(lc);
+ DBG(CXT, ul_debugobj(lc, "find_unused by scan [rc=%d]", rc));
+ }
+ return rc;
+}
+
+
+
+/*
+ * Return: TRUE/FALSE
+ */
+int loopdev_is_autoclear(const char *device)
+{
+ struct loopdev_cxt lc;
+ int rc;
+
+ if (!device)
+ return 0;
+
+ rc = loopcxt_init(&lc, 0);
+ if (!rc)
+ rc = loopcxt_set_device(&lc, device);
+ if (!rc)
+ rc = loopcxt_is_autoclear(&lc);
+
+ loopcxt_deinit(&lc);
+ return rc;
+}
+
+char *loopdev_get_backing_file(const char *device)
+{
+ struct loopdev_cxt lc;
+ char *res = NULL;
+
+ if (!device)
+ return NULL;
+ if (loopcxt_init(&lc, 0))
+ return NULL;
+ if (loopcxt_set_device(&lc, device) == 0)
+ res = loopcxt_get_backing_file(&lc);
+
+ loopcxt_deinit(&lc);
+ return res;
+}
+
+/*
+ * Returns: TRUE/FALSE
+ */
+int loopdev_is_used(const char *device, const char *filename,
+ uint64_t offset, uint64_t sizelimit, int flags)
+{
+ struct loopdev_cxt lc;
+ struct stat st;
+ int rc = 0;
+
+ if (!device || !filename)
+ return 0;
+
+ rc = loopcxt_init(&lc, 0);
+ if (!rc)
+ rc = loopcxt_set_device(&lc, device);
+ if (rc)
+ return rc;
+
+ rc = !stat(filename, &st);
+ rc = loopcxt_is_used(&lc, rc ? &st : NULL, filename, offset, sizelimit, flags);
+
+ loopcxt_deinit(&lc);
+ return rc;
+}
+
+int loopdev_delete(const char *device)
+{
+ struct loopdev_cxt lc;
+ int rc;
+
+ if (!device)
+ return -EINVAL;
+
+ rc = loopcxt_init(&lc, 0);
+ if (!rc)
+ rc = loopcxt_set_device(&lc, device);
+ if (!rc)
+ rc = loopcxt_delete_device(&lc);
+ loopcxt_deinit(&lc);
+ return rc;
+}
+
+/*
+ * Returns: 0 = success, < 0 error, 1 not found
+ */
+int loopcxt_find_by_backing_file(struct loopdev_cxt *lc, const char *filename,
+ uint64_t offset, uint64_t sizelimit, int flags)
+{
+ int rc, hasst;
+ struct stat st;
+
+ if (!filename)
+ return -EINVAL;
+
+ hasst = !stat(filename, &st);
+
+ rc = loopcxt_init_iterator(lc, LOOPITER_FL_USED);
+ if (rc)
+ return rc;
+
+ while ((rc = loopcxt_next(lc)) == 0) {
+
+ if (loopcxt_is_used(lc, hasst ? &st : NULL,
+ filename, offset, sizelimit, flags))
+ break;
+ }
+
+ loopcxt_deinit_iterator(lc);
+ return rc;
+}
+
+/*
+ * Returns: 0 = not found, < 0 error, 1 found, 2 found full size and offset match
+ */
+int loopcxt_find_overlap(struct loopdev_cxt *lc, const char *filename,
+ uint64_t offset, uint64_t sizelimit)
+{
+ int rc, hasst;
+ struct stat st;
+
+ if (!filename)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(lc, "find_overlap requested"));
+ hasst = !stat(filename, &st);
+
+ rc = loopcxt_init_iterator(lc, LOOPITER_FL_USED);
+ if (rc)
+ return rc;
+
+ while ((rc = loopcxt_next(lc)) == 0) {
+ uint64_t lc_sizelimit, lc_offset;
+
+ rc = loopcxt_is_used(lc, hasst ? &st : NULL,
+ filename, offset, sizelimit, 0);
+ if (!rc)
+ continue; /* unused */
+ if (rc < 0)
+ break; /* error */
+
+ DBG(CXT, ul_debugobj(lc, "found %s backed by %s",
+ loopcxt_get_device(lc), filename));
+
+ rc = loopcxt_get_offset(lc, &lc_offset);
+ if (rc) {
+ DBG(CXT, ul_debugobj(lc, "failed to get offset for device %s",
+ loopcxt_get_device(lc)));
+ break;
+ }
+ rc = loopcxt_get_sizelimit(lc, &lc_sizelimit);
+ if (rc) {
+ DBG(CXT, ul_debugobj(lc, "failed to get sizelimit for device %s",
+ loopcxt_get_device(lc)));
+ break;
+ }
+
+ /* full match */
+ if (lc_sizelimit == sizelimit && lc_offset == offset) {
+ DBG(CXT, ul_debugobj(lc, "overlapping loop device %s (full match)",
+ loopcxt_get_device(lc)));
+ rc = 2;
+ goto found;
+ }
+
+ /* overlap */
+ if (lc_sizelimit != 0 && offset >= lc_offset + lc_sizelimit)
+ continue;
+ if (sizelimit != 0 && offset + sizelimit <= lc_offset)
+ continue;
+
+ DBG(CXT, ul_debugobj(lc, "overlapping loop device %s",
+ loopcxt_get_device(lc)));
+ rc = 1;
+ goto found;
+ }
+
+ if (rc == 1)
+ rc = 0; /* not found */
+found:
+ loopcxt_deinit_iterator(lc);
+ DBG(CXT, ul_debugobj(lc, "find_overlap done [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * Returns allocated string with device name
+ */
+char *loopdev_find_by_backing_file(const char *filename, uint64_t offset, uint64_t sizelimit, int flags)
+{
+ struct loopdev_cxt lc;
+ char *res = NULL;
+
+ if (!filename)
+ return NULL;
+
+ if (loopcxt_init(&lc, 0))
+ return NULL;
+ if (loopcxt_find_by_backing_file(&lc, filename, offset, sizelimit, flags) == 0)
+ res = loopcxt_strdup_device(&lc);
+ loopcxt_deinit(&lc);
+
+ return res;
+}
+
+/*
+ * Returns number of loop devices associated with @file, if only one loop
+ * device is associeted with the given @filename and @loopdev is not NULL then
+ * @loopdev returns name of the device.
+ */
+int loopdev_count_by_backing_file(const char *filename, char **loopdev)
+{
+ struct loopdev_cxt lc;
+ int count = 0, rc;
+
+ if (!filename)
+ return -1;
+
+ rc = loopcxt_init(&lc, 0);
+ if (rc)
+ return rc;
+ if (loopcxt_init_iterator(&lc, LOOPITER_FL_USED))
+ return -1;
+
+ while(loopcxt_next(&lc) == 0) {
+ char *backing = loopcxt_get_backing_file(&lc);
+
+ if (!backing || strcmp(backing, filename)) {
+ free(backing);
+ continue;
+ }
+
+ free(backing);
+ if (loopdev && count == 0)
+ *loopdev = loopcxt_strdup_device(&lc);
+ count++;
+ }
+
+ loopcxt_deinit(&lc);
+
+ if (loopdev && count > 1) {
+ free(*loopdev);
+ *loopdev = NULL;
+ }
+ return count;
+}
+
diff --git a/lib/mangle.c b/lib/mangle.c
new file mode 100644
index 0000000..b61c57f
--- /dev/null
+++ b/lib/mangle.c
@@ -0,0 +1,168 @@
+/*
+ * Functions for \oct encoding used in mtab/fstab/swaps/etc.
+ *
+ * Based on code from mount(8).
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "mangle.h"
+#include "c.h"
+
+#define isoctal(a) (((a) & ~7) == '0')
+
+#define from_hex(c) (isdigit(c) ? c - '0' : tolower(c) - 'a' + 10)
+
+#define is_unwanted_char(x) (strchr(" \t\n\\", (unsigned int) x) != NULL)
+
+
+char *mangle(const char *s)
+{
+ char *ss, *sp;
+
+ if (!s)
+ return NULL;
+
+ ss = sp = malloc(4 * strlen(s) + 1);
+ if (!sp)
+ return NULL;
+ while(1) {
+ if (!*s) {
+ *sp = '\0';
+ break;
+ }
+ if (is_unwanted_char(*s)) {
+ *sp++ = '\\';
+ *sp++ = '0' + ((*s & 0300) >> 6);
+ *sp++ = '0' + ((*s & 070) >> 3);
+ *sp++ = '0' + (*s & 07);
+ } else
+ *sp++ = *s;
+ s++;
+ }
+ return ss;
+}
+
+
+void unmangle_to_buffer(const char *s, char *buf, size_t len)
+{
+ size_t sz = 0;
+
+ if (!s)
+ return;
+
+ while(*s && sz < len - 1) {
+ if (*s == '\\' && sz + 3 < len - 1 && isoctal(s[1]) &&
+ isoctal(s[2]) && isoctal(s[3])) {
+
+ *buf++ = 64*(s[1] & 7) + 8*(s[2] & 7) + (s[3] & 7);
+ s += 4;
+ sz += 4;
+ } else {
+ *buf++ = *s++;
+ sz++;
+ }
+ }
+ *buf = '\0';
+}
+
+size_t unhexmangle_to_buffer(const char *s, char *buf, size_t len)
+{
+ size_t sz = 0;
+ const char *buf0 = buf;
+
+ if (!s)
+ return 0;
+
+ while(*s && sz < len - 1) {
+ if (*s == '\\' && sz + 3 < len - 1 && s[1] == 'x' &&
+ isxdigit(s[2]) && isxdigit(s[3])) {
+
+ *buf++ = from_hex(s[2]) << 4 | from_hex(s[3]);
+ s += 4;
+ sz += 4;
+ } else {
+ *buf++ = *s++;
+ sz++;
+ }
+ }
+ *buf = '\0';
+ return buf - buf0 + 1;
+}
+
+static inline const char *skip_nonspaces(const char *s)
+{
+ while (*s && !(*s == ' ' || *s == '\t'))
+ s++;
+ return s;
+}
+
+/*
+ * Returns mallocated buffer or NULL in case of error.
+ */
+char *unmangle(const char *s, const char **end)
+{
+ char *buf;
+ const char *e;
+ size_t sz;
+
+ if (!s)
+ return NULL;
+
+ e = skip_nonspaces(s);
+ sz = e - s + 1;
+
+ if (end)
+ *end = e;
+ if (e == s)
+ return NULL; /* empty string */
+
+ buf = malloc(sz);
+ if (!buf)
+ return NULL;
+
+ unmangle_to_buffer(s, buf, sz);
+ return buf;
+}
+
+#ifdef TEST_PROGRAM_MANGLE
+#include <errno.h>
+int main(int argc, char *argv[])
+{
+ char *p = NULL;
+ if (argc < 3) {
+ fprintf(stderr, "usage: %s --mangle|unmangle <string>\n",
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+
+ if (!strcmp(argv[1], "--mangle")) {
+ p = mangle(argv[2]);
+ printf("mangled: '%s'\n", p);
+ free(p);
+ }
+
+ else if (!strcmp(argv[1], "--unmangle")) {
+ char *x = unmangle(argv[2], NULL);
+
+ if (x) {
+ printf("unmangled: '%s'\n", x);
+ free(x);
+ }
+
+ x = strdup(argv[2]);
+ unmangle_to_buffer(x, x, strlen(x) + 1);
+
+ if (x) {
+ printf("self-unmangled: '%s'\n", x);
+ free(x);
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM_MANGLE */
diff --git a/lib/match.c b/lib/match.c
new file mode 100644
index 0000000..a286a19
--- /dev/null
+++ b/lib/match.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2011 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <string.h>
+
+#include "match.h"
+
+/*
+ * match_fstype:
+ * @type: filesystem type
+ * @pattern: filesystem name or comma delimited list of names
+ *
+ * The @pattern list of filesystem can be prefixed with a global
+ * "no" prefix to invert matching of the whole list. The "no" could
+ * also be used for individual items in the @pattern list. So,
+ * "nofoo,bar" has the same meaning as "nofoo,nobar".
+ */
+int match_fstype(const char *type, const char *pattern)
+{
+ int no = 0; /* negated types list */
+ int len;
+ const char *p;
+
+ if (!pattern && !type)
+ return 1;
+ if (!pattern)
+ return 0;
+
+ if (!strncmp(pattern, "no", 2)) {
+ no = 1;
+ pattern += 2;
+ }
+
+ /* Does type occur in types, separated by commas? */
+ len = strlen(type);
+ p = pattern;
+ while(1) {
+ if (!strncmp(p, "no", 2) && !strncasecmp(p+2, type, len) &&
+ (p[len+2] == 0 || p[len+2] == ','))
+ return 0;
+ if (strncasecmp(p, type, len) == 0 && (p[len] == 0 || p[len] == ','))
+ return !no;
+ p = strchr(p,',');
+ if (!p)
+ break;
+ p++;
+ }
+ return no;
+}
diff --git a/lib/mbsalign.c b/lib/mbsalign.c
new file mode 100644
index 0000000..8fdab9e
--- /dev/null
+++ b/lib/mbsalign.c
@@ -0,0 +1,576 @@
+/* Align/Truncate a string in a given screen width
+ Copyright (C) 2009-2010 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 2.1 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, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Pádraig Brady. */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <limits.h>
+#include <ctype.h>
+
+#include "c.h"
+#include "mbsalign.h"
+#include "strutils.h"
+#include "widechar.h"
+
+/* Replace non printable chars.
+ Note \t and \n etc. are non printable.
+ Return 1 if replacement made, 0 otherwise. */
+
+/*
+ * Counts number of cells in multibyte string. For all control and
+ * non-printable chars is the result width enlarged to store \x?? hex
+ * sequence. See mbs_safe_encode().
+ *
+ * Returns: number of cells, @sz returns number of bytes.
+ */
+size_t mbs_safe_nwidth(const char *buf, size_t bufsz, size_t *sz)
+{
+ const char *p = buf, *last = buf;
+ size_t width = 0, bytes = 0;
+
+#ifdef HAVE_WIDECHAR
+ mbstate_t st;
+ memset(&st, 0, sizeof(st));
+#endif
+ if (p && *p && bufsz)
+ last = p + (bufsz - 1);
+
+ while (p && *p && p <= last) {
+ if ((p < last && *p == '\\' && *(p + 1) == 'x')
+ || iscntrl((unsigned char) *p)) {
+ width += 4, bytes += 4; /* *p encoded to \x?? */
+ p++;
+ }
+#ifdef HAVE_WIDECHAR
+ else {
+ wchar_t wc;
+ size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
+
+ if (len == 0)
+ break;
+
+ if (len == (size_t) -1 || len == (size_t) -2) {
+ len = 1;
+ if (isprint((unsigned char) *p))
+ width += 1, bytes += 1;
+ else
+ width += 4, bytes += 4;
+
+ } else if (!iswprint(wc)) {
+ width += len * 4; /* hex encode whole sequence */
+ bytes += len * 4;
+ } else {
+ width += wcwidth(wc); /* number of cells */
+ bytes += len; /* number of bytes */
+ }
+ p += len;
+ }
+#else
+ else if (!isprint((unsigned char) *p)) {
+ width += 4, bytes += 4; /* *p encoded to \x?? */
+ p++;
+ } else {
+ width++, bytes++;
+ p++;
+ }
+#endif
+ }
+
+ if (sz)
+ *sz = bytes;
+ return width;
+}
+
+size_t mbs_safe_width(const char *s)
+{
+ if (!s || !*s)
+ return 0;
+ return mbs_safe_nwidth(s, strlen(s), NULL);
+}
+
+/*
+ * Copy @s to @buf and replace control and non-printable chars with
+ * \x?? hex sequence. The @width returns number of cells. The @safechars
+ * are not encoded.
+ *
+ * The @buf has to be big enough to store mbs_safe_encode_size(strlen(s)))
+ * bytes.
+ */
+char *mbs_safe_encode_to_buffer(const char *s, size_t *width, char *buf, const char *safechars)
+{
+ const char *p = s;
+ char *r;
+ size_t sz = s ? strlen(s) : 0;
+
+#ifdef HAVE_WIDECHAR
+ mbstate_t st;
+ memset(&st, 0, sizeof(st));
+#endif
+ if (!sz || !buf)
+ return NULL;
+
+ r = buf;
+ *width = 0;
+
+ while (p && *p) {
+ if (safechars && strchr(safechars, *p)) {
+ *r++ = *p++;
+ continue;
+ }
+
+ if ((*p == '\\' && *(p + 1) == 'x')
+ || iscntrl((unsigned char) *p)) {
+ sprintf(r, "\\x%02x", (unsigned char) *p);
+ r += 4;
+ *width += 4;
+ p++;
+ }
+#ifdef HAVE_WIDECHAR
+ else {
+ wchar_t wc;
+ size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
+
+ if (len == 0)
+ break; /* end of string */
+
+ if (len == (size_t) -1 || len == (size_t) -2) {
+ len = 1;
+ /*
+ * Not valid multibyte sequence -- maybe it's
+ * printable char according to the current locales.
+ */
+ if (!isprint((unsigned char) *p)) {
+ sprintf(r, "\\x%02x", (unsigned char) *p);
+ r += 4;
+ *width += 4;
+ } else {
+ (*width)++;
+ *r++ = *p;
+ }
+ } else if (!iswprint(wc)) {
+ size_t i;
+ for (i = 0; i < len; i++) {
+ sprintf(r, "\\x%02x", (unsigned char) p[i]);
+ r += 4;
+ *width += 4;
+ }
+ } else {
+ memcpy(r, p, len);
+ r += len;
+ *width += wcwidth(wc);
+ }
+ p += len;
+ }
+#else
+ else if (!isprint((unsigned char) *p)) {
+ sprintf(r, "\\x%02x", (unsigned char) *p);
+ p++;
+ r += 4;
+ *width += 4;
+ } else {
+ *r++ = *p++;
+ (*width)++;
+ }
+#endif
+ }
+
+ *r = '\0';
+ return buf;
+}
+
+/*
+ * Copy @s to @buf and replace broken sequences to \x?? hex sequence. The
+ * @width returns number of cells. The @safechars are not encoded.
+ *
+ * The @buf has to be big enough to store mbs_safe_encode_size(strlen(s)))
+ * bytes.
+ */
+char *mbs_invalid_encode_to_buffer(const char *s, size_t *width, char *buf)
+{
+ const char *p = s;
+ char *r;
+ size_t sz = s ? strlen(s) : 0;
+
+#ifdef HAVE_WIDECHAR
+ mbstate_t st;
+ memset(&st, 0, sizeof(st));
+#endif
+ if (!sz || !buf)
+ return NULL;
+
+ r = buf;
+ *width = 0;
+
+ while (p && *p) {
+#ifdef HAVE_WIDECHAR
+ wchar_t wc;
+ size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
+#else
+ size_t len = 1;
+#endif
+
+ if (len == 0)
+ break; /* end of string */
+
+ if (len == (size_t) -1 || len == (size_t) -2) {
+ len = 1;
+ /*
+ * Not valid multibyte sequence -- maybe it's
+ * printable char according to the current locales.
+ */
+ if (!isprint((unsigned char) *p)) {
+ sprintf(r, "\\x%02x", (unsigned char) *p);
+ r += 4;
+ *width += 4;
+ } else {
+ (*width)++;
+ *r++ = *p;
+ }
+ } else if (*p == '\\' && *(p + 1) == 'x') {
+ sprintf(r, "\\x%02x", (unsigned char) *p);
+ r += 4;
+ *width += 4;
+ } else {
+ memcpy(r, p, len);
+ r += len;
+ *width += wcwidth(wc);
+ }
+ p += len;
+ }
+
+ *r = '\0';
+ return buf;
+}
+
+size_t mbs_safe_encode_size(size_t bytes)
+{
+ return (bytes * 4) + 1;
+}
+
+/*
+ * Returns allocated string where all control and non-printable chars are
+ * replaced with \x?? hex sequence.
+ */
+char *mbs_safe_encode(const char *s, size_t *width)
+{
+ size_t sz = s ? strlen(s) : 0;
+ char *buf, *ret = NULL;
+
+ if (!sz)
+ return NULL;
+ buf = malloc(mbs_safe_encode_size(sz));
+ if (buf)
+ ret = mbs_safe_encode_to_buffer(s, width, buf, NULL);
+ if (!ret)
+ free(buf);
+ return ret;
+}
+
+/*
+ * Returns allocated string where all broken widechars chars are
+ * replaced with \x?? hex sequence.
+ */
+char *mbs_invalid_encode(const char *s, size_t *width)
+{
+ size_t sz = s ? strlen(s) : 0;
+ char *buf, *ret = NULL;
+
+ if (!sz)
+ return NULL;
+ buf = malloc(mbs_safe_encode_size(sz));
+ if (buf)
+ ret = mbs_invalid_encode_to_buffer(s, width, buf);
+ if (!ret)
+ free(buf);
+ return ret;
+}
+
+#ifdef HAVE_WIDECHAR
+
+static bool
+wc_ensure_printable (wchar_t *wchars)
+{
+ bool replaced = false;
+ wchar_t *wc = wchars;
+ while (*wc)
+ {
+ if (!iswprint ((wint_t) *wc))
+ {
+ *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
+ replaced = true;
+ }
+ wc++;
+ }
+ return replaced;
+}
+
+/* Truncate wchar string to width cells.
+ * Returns number of cells used. */
+
+static size_t
+wc_truncate (wchar_t *wc, size_t width)
+{
+ size_t cells = 0;
+ int next_cells = 0;
+
+ while (*wc)
+ {
+ next_cells = wcwidth (*wc);
+ if (next_cells == -1) /* non printable */
+ {
+ *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
+ next_cells = 1;
+ }
+ if (cells + next_cells > width)
+ break;
+
+ cells += next_cells;
+ wc++;
+ }
+ *wc = L'\0';
+ return cells;
+}
+
+/* FIXME: move this function to gnulib as it's missing on:
+ OpenBSD 3.8, IRIX 5.3, Solaris 2.5.1, mingw, BeOS */
+
+static int
+rpl_wcswidth (const wchar_t *s, size_t n)
+{
+ int ret = 0;
+
+ while (n-- > 0 && *s != L'\0')
+ {
+ int nwidth = wcwidth (*s++);
+ if (nwidth == -1) /* non printable */
+ return -1;
+ if (ret > (INT_MAX - nwidth)) /* overflow */
+ return -1;
+ ret += nwidth;
+ }
+
+ return ret;
+}
+#endif /* HAVE_WIDECHAR */
+
+/* Truncate multi-byte string to @width and returns number of
+ * bytes of the new string @str, and in @width returns number
+ * of cells.
+ */
+size_t
+mbs_truncate(char *str, size_t *width)
+{
+ ssize_t bytes = strlen(str);
+#ifdef HAVE_WIDECHAR
+ ssize_t sz = mbstowcs(NULL, str, 0);
+ wchar_t *wcs = NULL;
+
+ if (sz == (ssize_t) -1)
+ goto done;
+
+ wcs = calloc(1, (sz + 1) * sizeof(wchar_t));
+ if (!wcs)
+ goto done;
+
+ if (!mbstowcs(wcs, str, sz))
+ goto done;
+ *width = wc_truncate(wcs, *width);
+ bytes = wcstombs(str, wcs, bytes);
+done:
+ free(wcs);
+#else
+ if (bytes >= 0 && *width < (size_t) bytes)
+ bytes = *width;
+#endif
+ if (bytes >= 0)
+ str[bytes] = '\0';
+ return bytes;
+}
+
+/* Write N_SPACES space characters to DEST while ensuring
+ nothing is written beyond DEST_END. A terminating NUL
+ is always added to DEST.
+ A pointer to the terminating NUL is returned. */
+
+static char*
+mbs_align_pad (char *dest, const char* dest_end, size_t n_spaces, int padchar)
+{
+ /* FIXME: Should we pad with "figure space" (\u2007)
+ if non ascii data present? */
+ for (/* nothing */; n_spaces && (dest < dest_end); n_spaces--)
+ *dest++ = padchar;
+ *dest = '\0';
+ return dest;
+}
+
+size_t
+mbsalign (const char *src, char *dest, size_t dest_size,
+ size_t *width, mbs_align_t align, int flags)
+{
+ return mbsalign_with_padding(src, dest, dest_size, width, align, flags, ' ');
+}
+
+/* Align a string, SRC, in a field of *WIDTH columns, handling multi-byte
+ characters; write the result into the DEST_SIZE-byte buffer, DEST.
+ ALIGNMENT specifies whether to left- or right-justify or to center.
+ If SRC requires more than *WIDTH columns, truncate it to fit.
+ When centering, the number of trailing spaces may be one less than the
+ number of leading spaces. The FLAGS parameter is unused at present.
+ Return the length in bytes required for the final result, not counting
+ the trailing NUL. A return value of DEST_SIZE or larger means there
+ wasn't enough space. DEST will be NUL terminated in any case.
+ Return (size_t) -1 upon error (invalid multi-byte sequence in SRC,
+ or malloc failure), unless MBA_UNIBYTE_FALLBACK is specified.
+ Update *WIDTH to indicate how many columns were used before padding. */
+
+size_t
+mbsalign_with_padding (const char *src, char *dest, size_t dest_size,
+ size_t *width, mbs_align_t align,
+#ifdef HAVE_WIDECHAR
+ int flags,
+#else
+ int flags __attribute__((__unused__)),
+#endif
+ int padchar)
+{
+ size_t ret = -1;
+ size_t src_size = strlen (src) + 1;
+ char *newstr = NULL;
+ wchar_t *str_wc = NULL;
+ const char *str_to_print = src;
+ size_t n_cols = src_size - 1;
+ size_t n_used_bytes = n_cols; /* Not including NUL */
+ size_t n_spaces = 0, space_left;
+
+#ifdef HAVE_WIDECHAR
+ bool conversion = false;
+ bool wc_enabled = false;
+
+ /* In multi-byte locales convert to wide characters
+ to allow easy truncation. Also determine number
+ of screen columns used. */
+ if (MB_CUR_MAX > 1)
+ {
+ size_t src_chars = mbstowcs (NULL, src, 0);
+ if (src_chars == (size_t) -1)
+ {
+ if (flags & MBA_UNIBYTE_FALLBACK)
+ goto mbsalign_unibyte;
+ else
+ goto mbsalign_cleanup;
+ }
+ src_chars += 1; /* make space for NUL */
+ str_wc = malloc (src_chars * sizeof (wchar_t));
+ if (str_wc == NULL)
+ {
+ if (flags & MBA_UNIBYTE_FALLBACK)
+ goto mbsalign_unibyte;
+ else
+ goto mbsalign_cleanup;
+ }
+ if (mbstowcs (str_wc, src, src_chars) != 0)
+ {
+ str_wc[src_chars - 1] = L'\0';
+ wc_enabled = true;
+ conversion = wc_ensure_printable (str_wc);
+ n_cols = rpl_wcswidth (str_wc, src_chars);
+ }
+ }
+
+ /* If we transformed or need to truncate the source string
+ then create a modified copy of it. */
+ if (wc_enabled && (conversion || (n_cols > *width)))
+ {
+ if (conversion)
+ {
+ /* May have increased the size by converting
+ \t to \uFFFD for example. */
+ src_size = wcstombs(NULL, str_wc, 0) + 1;
+ }
+ newstr = malloc (src_size);
+ if (newstr == NULL)
+ {
+ if (flags & MBA_UNIBYTE_FALLBACK)
+ goto mbsalign_unibyte;
+ else
+ goto mbsalign_cleanup;
+ }
+ str_to_print = newstr;
+ n_cols = wc_truncate (str_wc, *width);
+ n_used_bytes = wcstombs (newstr, str_wc, src_size);
+ }
+
+mbsalign_unibyte:
+#endif
+
+ if (n_cols > *width) /* Unibyte truncation required. */
+ {
+ n_cols = *width;
+ n_used_bytes = n_cols;
+ }
+
+ if (*width > n_cols) /* Padding required. */
+ n_spaces = *width - n_cols;
+
+ /* indicate to caller how many cells needed (not including padding). */
+ *width = n_cols;
+
+ /* indicate to caller how many bytes needed (not including NUL). */
+ ret = n_used_bytes + (n_spaces * 1);
+
+ /* Write as much NUL terminated output to DEST as possible. */
+ if (dest_size != 0)
+ {
+ char *dest_end = dest + dest_size - 1;
+ size_t start_spaces;
+ size_t end_spaces;
+
+ switch (align)
+ {
+ case MBS_ALIGN_CENTER:
+ start_spaces = n_spaces / 2 + n_spaces % 2;
+ end_spaces = n_spaces / 2;
+ break;
+ case MBS_ALIGN_LEFT:
+ start_spaces = 0;
+ end_spaces = n_spaces;
+ break;
+ case MBS_ALIGN_RIGHT:
+ start_spaces = n_spaces;
+ end_spaces = 0;
+ break;
+ default:
+ abort();
+ }
+
+ dest = mbs_align_pad (dest, dest_end, start_spaces, padchar);
+ space_left = dest_end - dest;
+ dest = mempcpy (dest, str_to_print, min (n_used_bytes, space_left));
+ mbs_align_pad (dest, dest_end, end_spaces, padchar);
+ }
+#ifdef HAVE_WIDECHAR
+mbsalign_cleanup:
+#endif
+ free (str_wc);
+ free (newstr);
+
+ return ret;
+}
diff --git a/lib/mbsedit.c b/lib/mbsedit.c
new file mode 100644
index 0000000..8ce5901
--- /dev/null
+++ b/lib/mbsedit.c
@@ -0,0 +1,225 @@
+/*
+ * Very simple multibyte buffer editor. Allows to maintaine the current
+ * position in the string, add and remove chars on the current position.
+ *
+ * This file may be distributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Copyright (C) 2017 Karel Zak <kzak@redhat.com>
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+
+#include "mbsalign.h"
+#include "mbsedit.h"
+
+struct mbs_editor *mbs_new_edit(char *buf, size_t bufsz, size_t ncells)
+{
+ struct mbs_editor *edit = calloc(1, sizeof(*edit));
+
+ if (edit) {
+ edit->buf = buf;
+ edit->max_bytes = bufsz;
+ edit->max_cells = ncells;
+ edit->cur_cells = mbs_safe_width(buf);
+ edit->cur_bytes = strlen(buf);
+ }
+ return edit;
+}
+
+char *mbs_free_edit(struct mbs_editor *edit)
+{
+ char *ret = edit ? edit->buf : NULL;
+
+ free(edit);
+ return ret;
+}
+
+static size_t mbs_next(const char *str, size_t *ncells)
+{
+#ifdef HAVE_WIDECHAR
+ wchar_t wc;
+ size_t n = 0;
+
+ if (!str || !*str)
+ return 0;
+
+ n = mbrtowc(&wc, str, MB_CUR_MAX, NULL);
+ *ncells = wcwidth(wc);
+ return n;
+#else
+ if (!str || !*str)
+ return 0;
+ *ncells = 1;
+ return 1;
+#endif
+}
+
+static size_t mbs_prev(const char *start, const char *end, size_t *ncells)
+{
+#ifdef HAVE_WIDECHAR
+ wchar_t wc = 0;
+ const char *p, *prev;
+ size_t n = 0;
+
+ if (!start || !end || start == end || !*start)
+ return 0;
+
+ prev = p = start;
+ while (p < end) {
+ n = mbrtowc(&wc, p, MB_CUR_MAX, NULL);
+ prev = p;
+
+ if (n == (size_t) -1 || n == (size_t) -2)
+ p++;
+ else
+ p += n;
+ }
+
+ if (prev == end)
+ return 0;
+ *ncells = wcwidth(wc);
+ return n;
+#else
+ if (!start || !end || start == end || !*start)
+ return 0;
+ *ncells = 1;
+ return 1;
+#endif
+}
+
+int mbs_edit_goto(struct mbs_editor *edit, int where)
+{
+ switch (where) {
+ case MBS_EDIT_LEFT:
+ if (edit->cursor == 0)
+ return 1;
+ else {
+ size_t n, cells;
+ n = mbs_prev(edit->buf, edit->buf + edit->cursor, &cells);
+ if (n) {
+ edit->cursor -= n;
+ edit->cursor_cells -= cells;
+ }
+ }
+ break;
+ case MBS_EDIT_RIGHT:
+ if (edit->cursor_cells >= edit->cur_cells)
+ return 1;
+ else {
+ size_t n, cells;
+ n = mbs_next(edit->buf + edit->cursor, &cells);
+ if (n) {
+ edit->cursor += n;
+ edit->cursor_cells += cells;
+ }
+ }
+ break;
+ case MBS_EDIT_HOME:
+ edit->cursor = 0;
+ edit->cursor_cells = 0;
+ break;
+ case MBS_EDIT_END:
+ edit->cursor = edit->cur_bytes;
+ edit->cursor_cells = edit->cur_cells;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* Remove next MB from @str, returns number of removed bytes */
+static size_t remove_next(char *str, size_t *ncells)
+{
+ /* all in bytes! */
+ size_t bytes, move_bytes, n;
+
+ n = mbs_next(str, ncells);
+ bytes = strlen(str);
+ move_bytes = bytes - n;
+
+ memmove(str, str + n, move_bytes);
+ str[bytes - n] = '\0';
+ return n;
+}
+
+static size_t mbs_insert(char *str, wint_t c, size_t *ncells)
+{
+ /* all in bytes! */
+ size_t n = 1, bytes;
+ char *in;
+
+#ifdef HAVE_WIDECHAR
+ wchar_t wc = (wchar_t) c;
+ char in_buf[MB_CUR_MAX];
+
+ n = wctomb(in_buf, wc);
+ if (n == (size_t) -1)
+ return n;
+ *ncells = wcwidth(wc);
+ in = in_buf;
+#else
+ *ncells = 1;
+ in = (char *) &c;
+#endif
+ bytes = strlen(str);
+
+ memmove(str + n, str, bytes);
+ memcpy(str, in, n);
+ str[bytes + n] = '\0';
+ return n;
+}
+
+static int mbs_edit_remove(struct mbs_editor *edit)
+{
+ size_t n, ncells;
+
+ if (edit->cur_cells == 0 || edit->cursor >= edit->cur_bytes)
+ return 1;
+
+ n = remove_next(edit->buf + edit->cursor, &ncells);
+ if (n == (size_t)-1)
+ return 1;
+
+ edit->cur_bytes -= n;
+ edit->cur_cells = mbs_safe_width(edit->buf);
+ return 0;
+}
+
+int mbs_edit_delete(struct mbs_editor *edit)
+{
+ if (edit->cursor >= edit->cur_bytes
+ && mbs_edit_goto(edit, MBS_EDIT_LEFT) == 1)
+ return 1;
+
+ return mbs_edit_remove(edit);
+}
+
+int mbs_edit_backspace(struct mbs_editor *edit)
+{
+ if (mbs_edit_goto(edit, MBS_EDIT_LEFT) == 0)
+ return mbs_edit_remove(edit);
+ return 1;
+}
+
+int mbs_edit_insert(struct mbs_editor *edit, wint_t c)
+{
+ size_t n, ncells;
+
+ if (edit->cur_bytes + MB_CUR_MAX > edit->max_bytes)
+ return 1;
+
+ n = mbs_insert(edit->buf + edit->cursor, c, &ncells);
+ if (n == (size_t)-1)
+ return 1;
+
+ edit->cursor += n;
+ edit->cursor_cells += ncells;
+ edit->cur_bytes += n;
+ edit->cur_cells = mbs_safe_width(edit->buf);
+ return 0;
+}
diff --git a/lib/md5.c b/lib/md5.c
new file mode 100644
index 0000000..3765ab9
--- /dev/null
+++ b/lib/md5.c
@@ -0,0 +1,257 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+#include <string.h> /* for memcpy() */
+
+#include "md5.h"
+
+#if !defined(WORDS_BIGENDIAN)
+# define byteReverse(buf, len) /* Nothing */
+#else
+static void byteReverse(unsigned char *buf, unsigned longs);
+
+#ifndef ASM_MD5
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+static void byteReverse(unsigned char *buf, unsigned longs)
+{
+ uint32_t t;
+ do {
+ t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
+ ((unsigned) buf[1] << 8 | buf[0]);
+ *(uint32_t *) buf = t;
+ buf += 4;
+ } while (--longs);
+}
+#endif /* !ASM_MD5 */
+#endif /* !WORDS_BIGENDIAN */
+
+/*
+ * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void ul_MD5Init(struct UL_MD5Context *ctx)
+{
+ ctx->buf[0] = 0x67452301;
+ ctx->buf[1] = 0xefcdab89;
+ ctx->buf[2] = 0x98badcfe;
+ ctx->buf[3] = 0x10325476;
+
+ ctx->bits[0] = 0;
+ ctx->bits[1] = 0;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+void ul_MD5Update(struct UL_MD5Context *ctx, unsigned char const *buf, unsigned len)
+{
+ uint32_t t;
+
+ /* Update bitcount */
+
+ t = ctx->bits[0];
+ if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
+ ctx->bits[1]++; /* Carry from low to high */
+ ctx->bits[1] += len >> 29;
+
+ t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
+
+ /* Handle any leading odd-sized chunks */
+
+ if (t) {
+ unsigned char *p = (unsigned char *) ctx->in + t;
+
+ t = 64 - t;
+ if (len < t) {
+ memcpy(p, buf, len);
+ return;
+ }
+ memcpy(p, buf, t);
+ byteReverse(ctx->in, 16);
+ ul_MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ buf += t;
+ len -= t;
+ }
+ /* Process data in 64-byte chunks */
+
+ while (len >= 64) {
+ memcpy(ctx->in, buf, 64);
+ byteReverse(ctx->in, 16);
+ ul_MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ buf += 64;
+ len -= 64;
+ }
+
+ /* Handle any remaining bytes of data. */
+
+ memcpy(ctx->in, buf, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+void ul_MD5Final(unsigned char digest[UL_MD5LENGTH], struct UL_MD5Context *ctx)
+{
+ unsigned count;
+ unsigned char *p;
+
+ /* Compute number of bytes mod 64 */
+ count = (ctx->bits[0] >> 3) & 0x3F;
+
+ /* Set the first char of padding to 0x80. This is safe since there is
+ always at least one byte free */
+ p = ctx->in + count;
+ *p++ = 0x80;
+
+ /* Bytes of padding needed to make 64 bytes */
+ count = 64 - 1 - count;
+
+ /* Pad out to 56 mod 64 */
+ if (count < 8) {
+ /* Two lots of padding: Pad the first block to 64 bytes */
+ memset(p, 0, count);
+ byteReverse(ctx->in, 16);
+ ul_MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+
+ /* Now fill the next block with 56 bytes */
+ memset(ctx->in, 0, 56);
+ } else {
+ /* Pad block to 56 bytes */
+ memset(p, 0, count - 8);
+ }
+ byteReverse(ctx->in, 14);
+
+ /* Append length in bits and transform.
+ * Use memcpy to avoid aliasing problems. On most systems,
+ * this will be optimized away to the same code.
+ */
+ memcpy(&ctx->in[14 * sizeof(uint32_t)], &ctx->bits[0], 4);
+ memcpy(&ctx->in[15 * sizeof(uint32_t)], &ctx->bits[1], 4);
+
+ ul_MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ byteReverse((unsigned char *) ctx->buf, 4);
+ memcpy(digest, ctx->buf, UL_MD5LENGTH);
+ memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */
+}
+
+#ifndef ASM_MD5
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+ ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data. MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+void ul_MD5Transform(uint32_t buf[4], uint32_t const in[16])
+{
+ register uint32_t a, b, c, d;
+
+ a = buf[0];
+ b = buf[1];
+ c = buf[2];
+ d = buf[3];
+
+ MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+ MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+ MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+ MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+ MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+ MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+ MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+ MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+ MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+ MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+ MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+ MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+ MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+ MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+ MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+ MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+ MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+ MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+ MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+ MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+ MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+ MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+ MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+ MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+ MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+ MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+ MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+ MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+ MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+ MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+ MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+ MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+ MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+ MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+ MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+ MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+ MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+ MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+ MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+ MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+ MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+ MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+ MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+ MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+ MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+ MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+ MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+ MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+ MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+ MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+ MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+ MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+ MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+ MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+ MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+ MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+ MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+ MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+ MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+ MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+ MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+ MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+ MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+ MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+
+#endif
+
diff --git a/lib/monotonic.c b/lib/monotonic.c
new file mode 100644
index 0000000..bb58e15
--- /dev/null
+++ b/lib/monotonic.c
@@ -0,0 +1,70 @@
+/*
+ * Please, don't add this file to libcommon because clock_gettime() requires
+ * -lrt on systems with old libc.
+ */
+#include <time.h>
+#include <signal.h>
+#ifdef HAVE_SYSINFO
+#include <sys/sysinfo.h>
+#endif
+#include <sys/time.h>
+
+#include "c.h"
+#include "monotonic.h"
+
+int get_boot_time(struct timeval *boot_time)
+{
+#ifdef CLOCK_BOOTTIME
+ struct timespec hires_uptime;
+ struct timeval lores_uptime;
+#endif
+ struct timeval now;
+#ifdef HAVE_SYSINFO
+ struct sysinfo info;
+#endif
+
+ if (gettimeofday(&now, NULL) != 0)
+ return -errno;
+#ifdef CLOCK_BOOTTIME
+ if (clock_gettime(CLOCK_BOOTTIME, &hires_uptime) == 0) {
+ TIMESPEC_TO_TIMEVAL(&lores_uptime, &hires_uptime);
+ timersub(&now, &lores_uptime, boot_time);
+ return 0;
+ }
+#endif
+#ifdef HAVE_SYSINFO
+ /* fallback */
+ if (sysinfo(&info) != 0)
+ return -errno;
+
+ boot_time->tv_sec = now.tv_sec - info.uptime;
+ boot_time->tv_usec = 0;
+ return 0;
+#else
+ return -ENOSYS;
+#endif
+}
+
+int gettime_monotonic(struct timeval *tv)
+{
+#ifdef CLOCK_MONOTONIC
+ /* Can slew only by ntp and adjtime */
+ int ret;
+ struct timespec ts;
+
+# ifdef CLOCK_MONOTONIC_RAW
+ /* Linux specific, can't slew */
+ if (!(ret = clock_gettime(CLOCK_MONOTONIC_RAW, &ts))) {
+# else
+ if (!(ret = clock_gettime(CLOCK_MONOTONIC, &ts))) {
+# endif
+ tv->tv_sec = ts.tv_sec;
+ tv->tv_usec = ts.tv_nsec / 1000;
+ }
+ return ret;
+#else
+ return gettimeofday(tv, NULL);
+#endif
+}
+
+
diff --git a/lib/pager.c b/lib/pager.c
new file mode 100644
index 0000000..b3cf6ee
--- /dev/null
+++ b/lib/pager.c
@@ -0,0 +1,317 @@
+/*
+ * Based on linux-perf/git scm
+ *
+ * Some modifications and simplifications for util-linux
+ * by Davidlohr Bueso <dave@xxxxxxx> - March 2012.
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <err.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+#include "c.h"
+#include "xalloc.h"
+#include "nls.h"
+#include "ttyutils.h"
+#include "pager.h"
+
+#define NULL_DEVICE "/dev/null"
+
+static const char *pager_argv[] = { "sh", "-c", NULL, NULL };
+
+struct child_process {
+ const char **argv;
+ pid_t pid;
+ int in;
+ int out;
+ int err;
+
+ int org_err;
+ int org_out;
+ struct sigaction orig_sigint;
+ struct sigaction orig_sighup;
+ struct sigaction orig_sigterm;
+ struct sigaction orig_sigquit;
+ struct sigaction orig_sigpipe;
+
+ unsigned no_stdin:1;
+ void (*preexec_cb)(void);
+};
+static struct child_process pager_process;
+
+static inline void close_pair(int fd[2])
+{
+ close(fd[0]);
+ close(fd[1]);
+}
+
+static int start_command(struct child_process *cmd)
+{
+ int need_in;
+ int fdin[2];
+
+ /*
+ * In case of errors we must keep the promise to close FDs
+ * that have been passed in via ->in and ->out.
+ */
+ need_in = !cmd->no_stdin && cmd->in < 0;
+ if (need_in) {
+ if (pipe(fdin) < 0) {
+ if (cmd->out > 0)
+ close(cmd->out);
+ return -1;
+ }
+ cmd->in = fdin[1];
+ }
+
+ fflush(NULL);
+ cmd->pid = fork();
+ if (!cmd->pid) {
+ if (need_in) {
+ dup2(fdin[0], STDIN_FILENO);
+ close_pair(fdin);
+ } else if (cmd->in > 0) {
+ dup2(cmd->in, STDIN_FILENO);
+ close(cmd->in);
+ }
+
+ cmd->preexec_cb();
+ execvp(cmd->argv[0], (char *const*) cmd->argv);
+ errexec(cmd->argv[0]);
+ }
+
+ if (cmd->pid < 0) {
+ if (need_in)
+ close_pair(fdin);
+ else if (cmd->in)
+ close(cmd->in);
+ return -1;
+ }
+
+ if (need_in)
+ close(fdin[0]);
+ else if (cmd->in)
+ close(cmd->in);
+ return 0;
+}
+
+static int wait_or_whine(pid_t pid)
+{
+ for (;;) {
+ int status, code;
+ pid_t waiting = waitpid(pid, &status, 0);
+
+ if (waiting < 0) {
+ if (errno == EINTR)
+ continue;
+ err(EXIT_FAILURE, _("waitpid failed (%s)"), strerror(errno));
+ }
+ if (waiting != pid)
+ return -1;
+ if (WIFSIGNALED(status))
+ return -1;
+
+ if (!WIFEXITED(status))
+ return -1;
+ code = WEXITSTATUS(status);
+ switch (code) {
+ case 127:
+ return -1;
+ case 0:
+ return 0;
+ default:
+ return -1;
+ }
+ }
+}
+
+static int finish_command(struct child_process *cmd)
+{
+ return wait_or_whine(cmd->pid);
+}
+
+static void pager_preexec(void)
+{
+ /*
+ * Work around bug in "less" by not starting it until we
+ * have real input
+ */
+ fd_set in, ex;
+
+ FD_ZERO(&in);
+ FD_SET(STDIN_FILENO, &in);
+ ex = in;
+
+ select(STDIN_FILENO + 1, &in, NULL, &ex, NULL);
+
+ if (setenv("LESS", "FRSX", 0) != 0)
+ warn(_("failed to set the %s environment variable"), "LESS");
+}
+
+static void wait_for_pager(void)
+{
+ if (pager_process.pid == 0)
+ return;
+
+ fflush(stdout);
+ fflush(stderr);
+ /* signal EOF to pager */
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ finish_command(&pager_process);
+}
+
+static void wait_for_pager_signal(int signo)
+{
+ wait_for_pager();
+ raise(signo);
+}
+
+static int has_command(const char *cmd)
+{
+ const char *path;
+ char *p, *s;
+ int rc = 0;
+
+ if (!cmd)
+ goto done;
+ if (*cmd == '/') {
+ rc = access(cmd, X_OK) == 0;
+ goto done;
+ }
+
+ path = getenv("PATH");
+ if (!path)
+ goto done;
+ p = xstrdup(path);
+ if (!p)
+ goto done;
+
+ for(s = strtok(p, ":"); s; s = strtok(NULL, ":")) {
+ int fd = open(s, O_RDONLY|O_CLOEXEC);
+ if (fd < 0)
+ continue;
+ rc = faccessat(fd, cmd, X_OK, 0) == 0;
+ close(fd);
+ if (rc)
+ break;
+ }
+ free(p);
+done:
+ /*fprintf(stderr, "has PAGER %s rc=%d\n", cmd, rc);*/
+ return rc;
+}
+
+static void __setup_pager(void)
+{
+ const char *pager = getenv("PAGER");
+ struct sigaction sa;
+
+ if (!isatty(STDOUT_FILENO))
+ return;
+
+ if (!pager)
+ pager = "less";
+ else if (!*pager || !strcmp(pager, "cat"))
+ return;
+
+ if (!has_command(pager))
+ return;
+
+ /* spawn the pager */
+ pager_argv[2] = pager;
+ pager_process.argv = pager_argv;
+ pager_process.in = -1;
+ pager_process.preexec_cb = pager_preexec;
+
+ if (start_command(&pager_process))
+ return;
+
+ /* original process continues, but writes to the pipe */
+ dup2(pager_process.in, STDOUT_FILENO);
+ if (isatty(STDERR_FILENO))
+ dup2(pager_process.in, STDERR_FILENO);
+ close(pager_process.in);
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = wait_for_pager_signal;
+
+ /* this makes sure that the parent terminates after the pager */
+ sigaction(SIGINT, &sa, &pager_process.orig_sigint);
+ sigaction(SIGHUP, &sa, &pager_process.orig_sighup);
+ sigaction(SIGTERM, &sa, &pager_process.orig_sigterm);
+ sigaction(SIGQUIT, &sa, &pager_process.orig_sigquit);
+ sigaction(SIGPIPE, &sa, &pager_process.orig_sigpipe);
+}
+
+/* Setup pager and redirects output to the $PAGER. The pager is closed at exit.
+ */
+void pager_redirect(void)
+{
+ if (pager_process.pid)
+ return; /* already running */
+
+ __setup_pager();
+
+ atexit(wait_for_pager);
+}
+
+/* Setup pager and redirect output, the pager may be closed by pager_close().
+ */
+void pager_open(void)
+{
+ if (pager_process.pid)
+ return; /* already running */
+
+ pager_process.org_out = dup(STDOUT_FILENO);
+ pager_process.org_err = dup(STDERR_FILENO);
+
+ __setup_pager();
+}
+
+/* Close pager and restore original std{out,err}.
+ */
+void pager_close(void)
+{
+ if (pager_process.pid == 0)
+ return;
+
+ wait_for_pager();
+
+ /* restore original output */
+ dup2(pager_process.org_out, STDOUT_FILENO);
+ dup2(pager_process.org_err, STDERR_FILENO);
+
+ close(pager_process.org_out);
+ close(pager_process.org_err);
+
+ /* restore original segnals setting */
+ sigaction(SIGINT, &pager_process.orig_sigint, NULL);
+ sigaction(SIGHUP, &pager_process.orig_sighup, NULL);
+ sigaction(SIGTERM, &pager_process.orig_sigterm, NULL);
+ sigaction(SIGQUIT, &pager_process.orig_sigquit, NULL);
+ sigaction(SIGPIPE, &pager_process.orig_sigpipe, NULL);
+
+ memset(&pager_process, 0, sizeof(pager_process));
+}
+
+#ifdef TEST_PROGRAM_PAGER
+
+#define MAX 255
+
+int main(int argc __attribute__ ((__unused__)),
+ char *argv[] __attribute__ ((__unused__)))
+{
+ int i;
+
+ pager_redirect();
+ for (i = 0; i < MAX; i++)
+ printf("%d\n", i);
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM_PAGER */
diff --git a/lib/parse-date.c b/lib/parse-date.c
new file mode 100644
index 0000000..b70c009
--- /dev/null
+++ b/lib/parse-date.c
@@ -0,0 +1,3369 @@
+/* A Bison parser, made by GNU Bison 3.0.4. */
+
+/* Bison implementation for Yacc-like parsers in C
+
+ Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc.
+
+ 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 3 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, see <http://www.gnu.org/licenses/>. */
+
+/* As a special exception, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* C LALR(1) parser skeleton written by Richard Stallman, by
+ simplifying the original so-called "semantic" parser. */
+
+/* All symbols defined below should begin with yy or YY, to avoid
+ infringing on user name space. This should be done even for local
+ variables, as they might otherwise be expanded by user macros.
+ There are some unavoidable exceptions within include files to
+ define necessary library symbols; they are noted "INFRINGES ON
+ USER NAME SPACE" below. */
+
+/* Identify Bison output. */
+#define YYBISON 1
+
+/* Bison version. */
+#define YYBISON_VERSION "3.0.4"
+
+/* Skeleton name. */
+#define YYSKELETON_NAME "yacc.c"
+
+/* Pure parsers. */
+#define YYPURE 1
+
+/* Push parsers. */
+#define YYPUSH 0
+
+/* Pull parsers. */
+#define YYPULL 1
+
+
+
+
+/* Copy the first part of user declarations. */
+#line 1 "lib/parse-date.y" /* yacc.c:339 */
+
+/**
+ * Parse a string into an internal timestamp.
+ *
+ * This file is based on gnulib parse-datetime.y-dd7a871 with
+ * the other gnulib dependencies removed for use in util-linux.
+ *
+ * Copyright (C) 1999-2000, 2002-2017 Free Software Foundation, Inc.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Originally written by Steven M. Bellovin <smb@research.att.com> while
+ * at the University of North Carolina at Chapel Hill. Later tweaked by
+ * a couple of people on Usenet. Completely overhauled by Rich $alz
+ * <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
+ *
+ * Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
+ * the right thing about local DST. Also modified by Paul Eggert
+ * <eggert@cs.ucla.edu> in February 2004 to support
+ * nanosecond-resolution timestamps, and in October 2004 to support
+ * TZ strings in dates.
+ */
+
+/**
+ * FIXME: Check for arithmetic overflow in all cases, not just
+ * some of them.
+ */
+
+#include <sys/time.h>
+#include <time.h>
+
+#include "c.h"
+#include "timeutils.h"
+
+/**
+ * There's no need to extend the stack, so there's no need to involve
+ * alloca.
+ */
+#define YYSTACK_USE_ALLOCA 0
+
+/**
+ * Tell Bison how much stack space is needed. 20 should be plenty for
+ * this grammar, which is not right recursive. Beware setting it too
+ * high, since that might cause problems on machines whose
+ * implementations have lame stack-overflow checking.
+ */
+#define YYMAXDEPTH 20
+#define YYINITDEPTH YYMAXDEPTH
+
+/**
+ * Since the code of parse-datetime.y is not included in the Emacs executable
+ * itself, there is no need to #define static in this file. Even if
+ * the code were included in the Emacs executable, it probably
+ * wouldn't do any harm to #undef it here; this will only cause
+ * problems if we try to write to a static variable, which I don't
+ * think this code needs to do.
+ */
+#ifdef emacs
+# undef static
+#endif
+
+#include <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+#include <stdarg.h>
+#include "cctype.h"
+#include "nls.h"
+
+/**
+ * Bison's skeleton tests _STDLIB_H, while some stdlib.h headers
+ * use _STDLIB_H_ as witness. Map the latter to the one bison uses.
+ * FIXME: this is temporary. Remove when we have a mechanism to ensure
+ * that the version we're using is fixed, too.
+ */
+#ifdef _STDLIB_H_
+# undef _STDLIB_H
+# define _STDLIB_H 1
+#endif
+
+/**
+ * Shift A right by B bits portably, by dividing A by 2**B and
+ * truncating towards minus infinity. A and B should be free of side
+ * effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
+ * INT_BITS is the number of useful bits in an int. GNU code can
+ * assume that INT_BITS is at least 32.
+ *
+ * ISO C99 says that A >> B is implementation-defined if A < 0. Some
+ * implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
+ * right in the usual way when A < 0, so SHR falls back on division if
+ * ordinary A >> B doesn't seem to be the usual signed shift.
+ */
+#define SHR(a, b) \
+ (-1 >> 1 == -1 \
+ ? (a) >> (b) \
+ : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
+
+#define TM_YEAR_BASE 1900
+
+#define HOUR(x) ((x) * 60)
+
+#define STREQ(a, b) (strcmp (a, b) == 0)
+
+/**
+ * Convert a possibly-signed character to an unsigned character. This is
+ * a bit safer than casting to unsigned char, since it catches some type
+ * errors that the cast doesn't.
+ */
+static unsigned char to_uchar (char ch) { return ch; }
+
+/**
+ * FIXME: It also assumes that signed integer overflow silently wraps around,
+ * but this is not true any more with recent versions of GCC 4.
+ */
+
+/**
+ * An integer value, and the number of digits in its textual
+ * representation.
+ */
+typedef struct {
+ int negative;
+ intmax_t value;
+ size_t digits;
+} textint;
+
+/* An entry in the lexical lookup table. */
+typedef struct {
+ char const *name;
+ int type;
+ int value;
+} table;
+
+/* Meridian: am, pm, or 24-hour style. */
+enum { MERam, MERpm, MER24 };
+
+enum { BILLION = 1000000000, LOG10_BILLION = 9 };
+
+/* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
+typedef struct {
+ intmax_t year;
+ intmax_t month;
+ intmax_t day;
+ intmax_t hour;
+ intmax_t minutes;
+ time_t seconds;
+ int ns;
+} relative_time;
+
+#if HAVE_COMPOUND_LITERALS
+# define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
+#else
+static relative_time const RELATIVE_TIME_0;
+#endif
+
+/* Information passed to and from the parser. */
+typedef struct {
+ /* The input string remaining to be parsed. */
+ const char *input;
+
+ /* N, if this is the Nth Tuesday. */
+ intmax_t day_ordinal;
+
+ /* Day of week; Sunday is 0. */
+ int day_number;
+
+ /* tm_isdst flag for the local zone. */
+ int local_isdst;
+
+ /* Time zone, in minutes east of UTC. */
+ int time_zone;
+
+ /* Style used for time. */
+ int meridian;
+
+ /* Gregorian year, month, day, hour, minutes, seconds, and ns. */
+ textint year;
+ intmax_t month;
+ intmax_t day;
+ intmax_t hour;
+ intmax_t minutes;
+ struct timespec seconds; /* includes nanoseconds */
+
+ /* Relative year, month, day, hour, minutes, seconds, and ns. */
+ relative_time rel;
+
+ /* Presence or counts of some nonterminals parsed so far. */
+ int timespec_seen;
+ int rels_seen;
+ size_t dates_seen;
+ size_t days_seen;
+ size_t local_zones_seen;
+ size_t dsts_seen;
+ size_t times_seen;
+ size_t zones_seen;
+
+ /* Table of local time zone abbreviations, null terminated. */
+ table local_time_zone_table[3];
+} parser_control;
+
+union YYSTYPE;
+static int yylex (union YYSTYPE *, parser_control *);
+static int yyerror (parser_control const *, char const *);
+static int time_zone_hhmm (parser_control *, textint, textint);
+
+/**
+ * Extract into *PC any date and time info from a string of digits
+ * of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
+ * YYYY, ...).
+ */
+static void digits_to_date_time(parser_control *pc, textint text_int)
+{
+ if (pc->dates_seen && ! pc->year.digits
+ && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits)) {
+ pc->year = text_int;
+ } else {
+ if (4 < text_int.digits) {
+ pc->dates_seen++;
+ pc->day = text_int.value % 100;
+ pc->month = (text_int.value / 100) % 100;
+ pc->year.value = text_int.value / 10000;
+ pc->year.digits = text_int.digits - 4;
+ } else {
+ pc->times_seen++;
+ if (text_int.digits <= 2) {
+ pc->hour = text_int.value;
+ pc->minutes = 0;
+ }
+ else {
+ pc->hour = text_int.value / 100;
+ pc->minutes = text_int.value % 100;
+ }
+ pc->seconds.tv_sec = 0;
+ pc->seconds.tv_nsec = 0;
+ pc->meridian = MER24;
+ }
+ }
+}
+
+/* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). */
+static void apply_relative_time(parser_control *pc, relative_time rel,
+ int factor)
+{
+ pc->rel.ns += factor * rel.ns;
+ pc->rel.seconds += factor * rel.seconds;
+ pc->rel.minutes += factor * rel.minutes;
+ pc->rel.hour += factor * rel.hour;
+ pc->rel.day += factor * rel.day;
+ pc->rel.month += factor * rel.month;
+ pc->rel.year += factor * rel.year;
+ pc->rels_seen = 1;
+}
+
+/* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
+static void
+set_hhmmss(parser_control *pc, intmax_t hour, intmax_t minutes,
+ time_t sec, int nsec)
+{
+ pc->hour = hour;
+ pc->minutes = minutes;
+ pc->seconds.tv_sec = sec;
+ pc->seconds.tv_nsec = nsec;
+}
+
+
+#line 345 "lib/parse-date.c" /* yacc.c:339 */
+
+# ifndef YY_NULLPTR
+# if defined __cplusplus && 201103L <= __cplusplus
+# define YY_NULLPTR nullptr
+# else
+# define YY_NULLPTR 0
+# endif
+# endif
+
+/* Enabling verbose error messages. */
+#ifdef YYERROR_VERBOSE
+# undef YYERROR_VERBOSE
+# define YYERROR_VERBOSE 1
+#else
+# define YYERROR_VERBOSE 0
+#endif
+
+
+/* Debug traces. */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int yydebug;
+#endif
+
+/* Token type. */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+ enum yytokentype
+ {
+ tAGO = 258,
+ tDST = 259,
+ tYEAR_UNIT = 260,
+ tMONTH_UNIT = 261,
+ tHOUR_UNIT = 262,
+ tMINUTE_UNIT = 263,
+ tSEC_UNIT = 264,
+ tDAY_UNIT = 265,
+ tDAY_SHIFT = 266,
+ tDAY = 267,
+ tDAYZONE = 268,
+ tLOCAL_ZONE = 269,
+ tMERIDIAN = 270,
+ tMONTH = 271,
+ tORDINAL = 272,
+ tZONE = 273,
+ tSNUMBER = 274,
+ tUNUMBER = 275,
+ tSDECIMAL_NUMBER = 276,
+ tUDECIMAL_NUMBER = 277
+ };
+#endif
+/* Tokens. */
+#define tAGO 258
+#define tDST 259
+#define tYEAR_UNIT 260
+#define tMONTH_UNIT 261
+#define tHOUR_UNIT 262
+#define tMINUTE_UNIT 263
+#define tSEC_UNIT 264
+#define tDAY_UNIT 265
+#define tDAY_SHIFT 266
+#define tDAY 267
+#define tDAYZONE 268
+#define tLOCAL_ZONE 269
+#define tMERIDIAN 270
+#define tMONTH 271
+#define tORDINAL 272
+#define tZONE 273
+#define tSNUMBER 274
+#define tUNUMBER 275
+#define tSDECIMAL_NUMBER 276
+#define tUDECIMAL_NUMBER 277
+
+/* Value type. */
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+
+union YYSTYPE
+{
+#line 291 "lib/parse-date.y" /* yacc.c:355 */
+
+ intmax_t intval;
+ textint textintval;
+ struct timespec timespec;
+ relative_time rel;
+
+#line 433 "lib/parse-date.c" /* yacc.c:355 */
+};
+
+typedef union YYSTYPE YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+
+
+int yyparse (parser_control *pc);
+
+
+
+/* Copy the second part of user declarations. */
+
+#line 449 "lib/parse-date.c" /* yacc.c:358 */
+
+#ifdef short
+# undef short
+#endif
+
+#ifdef YYTYPE_UINT8
+typedef YYTYPE_UINT8 yytype_uint8;
+#else
+typedef unsigned char yytype_uint8;
+#endif
+
+#ifdef YYTYPE_INT8
+typedef YYTYPE_INT8 yytype_int8;
+#else
+typedef signed char yytype_int8;
+#endif
+
+#ifdef YYTYPE_UINT16
+typedef YYTYPE_UINT16 yytype_uint16;
+#else
+typedef unsigned short int yytype_uint16;
+#endif
+
+#ifdef YYTYPE_INT16
+typedef YYTYPE_INT16 yytype_int16;
+#else
+typedef short int yytype_int16;
+#endif
+
+#ifndef YYSIZE_T
+# ifdef __SIZE_TYPE__
+# define YYSIZE_T __SIZE_TYPE__
+# elif defined size_t
+# define YYSIZE_T size_t
+# elif ! defined YYSIZE_T
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# define YYSIZE_T size_t
+# else
+# define YYSIZE_T unsigned int
+# endif
+#endif
+
+#define YYSIZE_MAXIMUM ((YYSIZE_T) -1)
+
+#ifndef YY_
+# if defined YYENABLE_NLS && YYENABLE_NLS
+# if ENABLE_NLS
+# include <libintl.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_(Msgid) dgettext ("bison-runtime", Msgid)
+# endif
+# endif
+# ifndef YY_
+# define YY_(Msgid) Msgid
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE
+# if (defined __GNUC__ \
+ && (2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__))) \
+ || defined __SUNPRO_C && 0x5110 <= __SUNPRO_C
+# define YY_ATTRIBUTE(Spec) __attribute__(Spec)
+# else
+# define YY_ATTRIBUTE(Spec) /* empty */
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE_PURE
+# define YY_ATTRIBUTE_PURE YY_ATTRIBUTE ((__pure__))
+#endif
+
+#ifndef YY_ATTRIBUTE_UNUSED
+# define YY_ATTRIBUTE_UNUSED YY_ATTRIBUTE ((__unused__))
+#endif
+
+#if !defined _Noreturn \
+ && (!defined __STDC_VERSION__ || __STDC_VERSION__ < 201112)
+# if defined _MSC_VER && 1200 <= _MSC_VER
+# define _Noreturn __declspec (noreturn)
+# else
+# define _Noreturn YY_ATTRIBUTE ((__noreturn__))
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E. */
+#if ! defined lint || defined __GNUC__
+# define YYUSE(E) ((void) (E))
+#else
+# define YYUSE(E) /* empty */
+#endif
+
+#if defined __GNUC__ && 407 <= __GNUC__ * 100 + __GNUC_MINOR__
+/* Suppress an incorrect diagnostic about yylval being uninitialized. */
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")\
+ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END \
+ _Pragma ("GCC diagnostic pop")
+#else
+# define YY_INITIAL_VALUE(Value) Value
+#endif
+#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END
+#endif
+#ifndef YY_INITIAL_VALUE
+# define YY_INITIAL_VALUE(Value) /* Nothing. */
+#endif
+
+
+#if ! defined yyoverflow || YYERROR_VERBOSE
+
+/* The parser invokes alloca or malloc; define the necessary symbols. */
+
+# ifdef YYSTACK_USE_ALLOCA
+# if YYSTACK_USE_ALLOCA
+# ifdef __GNUC__
+# define YYSTACK_ALLOC __builtin_alloca
+# elif defined __BUILTIN_VA_ARG_INCR
+# include <alloca.h> /* INFRINGES ON USER NAME SPACE */
+# elif defined _AIX
+# define YYSTACK_ALLOC __alloca
+# elif defined _MSC_VER
+# include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+# define alloca _alloca
+# else
+# define YYSTACK_ALLOC alloca
+# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+ /* Use EXIT_SUCCESS as a witness for stdlib.h. */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# endif
+# endif
+# endif
+
+# ifdef YYSTACK_ALLOC
+ /* Pacify GCC's 'empty if-body' warning. */
+# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0)
+# ifndef YYSTACK_ALLOC_MAXIMUM
+ /* The OS might guarantee only one guard page at the bottom of the stack,
+ and a page size can be as small as 4096 bytes. So we cannot safely
+ invoke alloca (N) if N exceeds 4096. Use a slightly smaller number
+ to allow for a few compiler-allocated temporary stack slots. */
+# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
+# endif
+# else
+# define YYSTACK_ALLOC YYMALLOC
+# define YYSTACK_FREE YYFREE
+# ifndef YYSTACK_ALLOC_MAXIMUM
+# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
+# endif
+# if (defined __cplusplus && ! defined EXIT_SUCCESS \
+ && ! ((defined YYMALLOC || defined malloc) \
+ && (defined YYFREE || defined free)))
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# ifndef YYMALLOC
+# define YYMALLOC malloc
+# if ! defined malloc && ! defined EXIT_SUCCESS
+void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# ifndef YYFREE
+# define YYFREE free
+# if ! defined free && ! defined EXIT_SUCCESS
+void free (void *); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# endif
+#endif /* ! defined yyoverflow || YYERROR_VERBOSE */
+
+
+#if (! defined yyoverflow \
+ && (! defined __cplusplus \
+ || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
+
+/* A type that is properly aligned for any stack member. */
+union yyalloc
+{
+ yytype_int16 yyss_alloc;
+ YYSTYPE yyvs_alloc;
+};
+
+/* The size of the maximum gap between one aligned stack and the next. */
+# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1)
+
+/* The size of an array large to enough to hold all stacks, each with
+ N elements. */
+# define YYSTACK_BYTES(N) \
+ ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \
+ + YYSTACK_GAP_MAXIMUM)
+
+# define YYCOPY_NEEDED 1
+
+/* Relocate STACK from its old location to the new one. The
+ local variables YYSIZE and YYSTACKSIZE give the old and new number of
+ elements in the stack, and YYPTR gives the new location of the
+ stack. Advance YYPTR to a properly aligned location for the next
+ stack. */
+# define YYSTACK_RELOCATE(Stack_alloc, Stack) \
+ do \
+ { \
+ YYSIZE_T yynewbytes; \
+ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \
+ Stack = &yyptr->Stack_alloc; \
+ yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \
+ yyptr += yynewbytes / sizeof (*yyptr); \
+ } \
+ while (0)
+
+#endif
+
+#if defined YYCOPY_NEEDED && YYCOPY_NEEDED
+/* Copy COUNT objects from SRC to DST. The source and destination do
+ not overlap. */
+# ifndef YYCOPY
+# if defined __GNUC__ && 1 < __GNUC__
+# define YYCOPY(Dst, Src, Count) \
+ __builtin_memcpy (Dst, Src, (Count) * sizeof (*(Src)))
+# else
+# define YYCOPY(Dst, Src, Count) \
+ do \
+ { \
+ YYSIZE_T yyi; \
+ for (yyi = 0; yyi < (Count); yyi++) \
+ (Dst)[yyi] = (Src)[yyi]; \
+ } \
+ while (0)
+# endif
+# endif
+#endif /* !YYCOPY_NEEDED */
+
+/* YYFINAL -- State number of the termination state. */
+#define YYFINAL 12
+/* YYLAST -- Last index in YYTABLE. */
+#define YYLAST 112
+
+/* YYNTOKENS -- Number of terminals. */
+#define YYNTOKENS 28
+/* YYNNTS -- Number of nonterminals. */
+#define YYNNTS 26
+/* YYNRULES -- Number of rules. */
+#define YYNRULES 91
+/* YYNSTATES -- Number of states. */
+#define YYNSTATES 114
+
+/* YYTRANSLATE[YYX] -- Symbol number corresponding to YYX as returned
+ by yylex, with out-of-bounds checking. */
+#define YYUNDEFTOK 2
+#define YYMAXUTOK 277
+
+#define YYTRANSLATE(YYX) \
+ ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK)
+
+/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM
+ as returned by yylex, without out-of-bounds checking. */
+static const yytype_uint8 yytranslate[] =
+{
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 26, 2, 2, 27, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 25, 2,
+ 2, 2, 2, 2, 23, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 24, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22
+};
+
+#if YYDEBUG
+ /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */
+static const yytype_uint16 yyrline[] =
+{
+ 0, 318, 318, 319, 323, 329, 331, 335, 338, 341,
+ 344, 347, 350, 353, 354, 355, 359, 363, 367, 371,
+ 375, 379, 383, 387, 391, 397, 399, 403, 428, 432,
+ 443, 446, 449, 453, 457, 461, 464, 470, 474, 478,
+ 482, 489, 493, 511, 518, 525, 529, 534, 538, 543,
+ 547, 556, 558, 560, 565, 567, 569, 571, 573, 575,
+ 577, 579, 581, 583, 585, 587, 589, 591, 593, 595,
+ 597, 599, 604, 609, 611, 615, 617, 619, 621, 623,
+ 625, 630, 634, 634, 637, 638, 643, 644, 649, 654,
+ 666, 667
+};
+#endif
+
+#if YYDEBUG || YYERROR_VERBOSE || 0
+/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+ First, the terminals, then, starting at YYNTOKENS, nonterminals. */
+static const char *const yytname[] =
+{
+ "$end", "error", "$undefined", "tAGO", "tDST", "tYEAR_UNIT",
+ "tMONTH_UNIT", "tHOUR_UNIT", "tMINUTE_UNIT", "tSEC_UNIT", "tDAY_UNIT",
+ "tDAY_SHIFT", "tDAY", "tDAYZONE", "tLOCAL_ZONE", "tMERIDIAN", "tMONTH",
+ "tORDINAL", "tZONE", "tSNUMBER", "tUNUMBER", "tSDECIMAL_NUMBER",
+ "tUDECIMAL_NUMBER", "'@'", "'T'", "':'", "','", "'/'", "$accept", "spec",
+ "timespec", "items", "item", "datetime", "iso_8601_datetime", "time",
+ "iso_8601_time", "o_zone_offset", "zone_offset", "local_zone", "zone",
+ "day", "date", "iso_8601_date", "rel", "relunit", "relunit_snumber",
+ "dayshift", "seconds", "signed_seconds", "unsigned_seconds", "number",
+ "hybrid", "o_colon_minutes", YY_NULLPTR
+};
+#endif
+
+# ifdef YYPRINT
+/* YYTOKNUM[NUM] -- (External) token number corresponding to the
+ (internal) symbol number NUM (which must be that of a token). */
+static const yytype_uint16 yytoknum[] =
+{
+ 0, 256, 257, 258, 259, 260, 261, 262, 263, 264,
+ 265, 266, 267, 268, 269, 270, 271, 272, 273, 274,
+ 275, 276, 277, 64, 84, 58, 44, 47
+};
+# endif
+
+#define YYPACT_NINF -93
+
+#define yypact_value_is_default(Yystate) \
+ (!!((Yystate) == (-93)))
+
+#define YYTABLE_NINF -1
+
+#define yytable_value_is_error(Yytable_value) \
+ 0
+
+ /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+ STATE-NUM. */
+static const yytype_int8 yypact[] =
+{
+ 38, 27, 77, -93, 46, -93, -93, -93, -93, -93,
+ -93, -93, -93, -93, -93, -93, -93, -93, -93, -93,
+ 62, -93, 82, -3, 66, 3, 74, -4, 83, 84,
+ 75, -93, -93, -93, -93, -93, -93, -93, -93, -93,
+ 71, -93, 93, -93, -93, -93, -93, -93, -93, 78,
+ 72, -93, -93, -93, -93, -93, -93, -93, -93, 25,
+ -93, -93, -93, -93, -93, -93, -93, -93, -93, -93,
+ -93, -93, -93, -93, -93, 21, 19, 79, 80, -93,
+ -93, -93, -93, -93, 81, -93, -93, 85, 86, -93,
+ -93, -93, -93, -93, -6, 76, 17, -93, -93, -93,
+ -93, 87, 69, -93, -93, 88, 89, -1, -93, 18,
+ -93, -93, 69, 91
+};
+
+ /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM.
+ Performed when YYTABLE does not specify something else to do. Zero
+ means the default is an error. */
+static const yytype_uint8 yydefact[] =
+{
+ 5, 0, 0, 2, 3, 85, 87, 84, 86, 4,
+ 82, 83, 1, 56, 59, 65, 68, 73, 62, 81,
+ 37, 35, 28, 0, 0, 30, 0, 88, 0, 0,
+ 31, 6, 7, 16, 8, 21, 9, 10, 12, 11,
+ 49, 13, 52, 74, 53, 14, 15, 38, 29, 0,
+ 45, 54, 57, 63, 66, 69, 60, 39, 36, 90,
+ 32, 75, 76, 78, 79, 80, 77, 55, 58, 64,
+ 67, 70, 61, 40, 18, 47, 90, 0, 0, 22,
+ 89, 71, 72, 33, 0, 51, 44, 0, 0, 34,
+ 43, 48, 50, 27, 25, 41, 0, 17, 46, 91,
+ 19, 90, 0, 23, 26, 0, 0, 25, 42, 25,
+ 20, 24, 0, 25
+};
+
+ /* YYPGOTO[NTERM-NUM]. */
+static const yytype_int8 yypgoto[] =
+{
+ -93, -93, -93, -93, -93, -93, -93, -93, 20, -68,
+ -27, -93, -93, -93, -93, -93, -93, -93, 60, -93,
+ -93, -93, -92, -93, -93, 43
+};
+
+ /* YYDEFGOTO[NTERM-NUM]. */
+static const yytype_int8 yydefgoto[] =
+{
+ -1, 2, 3, 4, 31, 32, 33, 34, 35, 103,
+ 104, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 9, 10, 11, 45, 46, 93
+};
+
+ /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If
+ positive, shift that token. If negative, reduce the rule whose
+ number is the opposite. If YYTABLE_NINF, syntax error. */
+static const yytype_uint8 yytable[] =
+{
+ 79, 67, 68, 69, 70, 71, 72, 58, 73, 100,
+ 107, 74, 75, 101, 110, 76, 49, 50, 101, 102,
+ 113, 77, 59, 78, 61, 62, 63, 64, 65, 66,
+ 61, 62, 63, 64, 65, 66, 101, 101, 92, 111,
+ 90, 91, 106, 112, 88, 111, 5, 6, 7, 8,
+ 88, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 1, 23, 24, 25, 26, 27, 28, 29, 79,
+ 30, 51, 52, 53, 54, 55, 56, 12, 57, 61,
+ 62, 63, 64, 65, 66, 60, 48, 80, 47, 6,
+ 83, 8, 81, 82, 26, 84, 85, 86, 87, 94,
+ 95, 96, 89, 105, 97, 98, 99, 0, 108, 109,
+ 101, 0, 88
+};
+
+static const yytype_int8 yycheck[] =
+{
+ 27, 5, 6, 7, 8, 9, 10, 4, 12, 15,
+ 102, 15, 16, 19, 15, 19, 19, 20, 19, 25,
+ 112, 25, 19, 27, 5, 6, 7, 8, 9, 10,
+ 5, 6, 7, 8, 9, 10, 19, 19, 19, 107,
+ 19, 20, 25, 25, 25, 113, 19, 20, 21, 22,
+ 25, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+ 14, 23, 16, 17, 18, 19, 20, 21, 22, 96,
+ 24, 5, 6, 7, 8, 9, 10, 0, 12, 5,
+ 6, 7, 8, 9, 10, 25, 4, 27, 26, 20,
+ 30, 22, 9, 9, 19, 24, 3, 19, 26, 20,
+ 20, 20, 59, 27, 84, 20, 20, -1, 20, 20,
+ 19, -1, 25
+};
+
+ /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing
+ symbol of state STATE-NUM. */
+static const yytype_uint8 yystos[] =
+{
+ 0, 23, 29, 30, 31, 19, 20, 21, 22, 48,
+ 49, 50, 0, 5, 6, 7, 8, 9, 10, 11,
+ 12, 13, 14, 16, 17, 18, 19, 20, 21, 22,
+ 24, 32, 33, 34, 35, 36, 39, 40, 41, 42,
+ 43, 44, 45, 46, 47, 51, 52, 26, 4, 19,
+ 20, 5, 6, 7, 8, 9, 10, 12, 4, 19,
+ 46, 5, 6, 7, 8, 9, 10, 5, 6, 7,
+ 8, 9, 10, 12, 15, 16, 19, 25, 27, 38,
+ 46, 9, 9, 46, 24, 3, 19, 26, 25, 53,
+ 19, 20, 19, 53, 20, 20, 20, 36, 20, 20,
+ 15, 19, 25, 37, 38, 27, 25, 50, 20, 20,
+ 15, 37, 25, 50
+};
+
+ /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */
+static const yytype_uint8 yyr1[] =
+{
+ 0, 28, 29, 29, 30, 31, 31, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 33, 34, 35, 35,
+ 35, 35, 36, 36, 36, 37, 37, 38, 39, 39,
+ 40, 40, 40, 40, 40, 40, 40, 41, 41, 41,
+ 41, 42, 42, 42, 42, 42, 42, 42, 42, 42,
+ 43, 44, 44, 44, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 46, 46, 46, 46, 46,
+ 46, 47, 48, 48, 49, 49, 50, 50, 51, 52,
+ 53, 53
+};
+
+ /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */
+static const yytype_uint8 yyr2[] =
+{
+ 0, 2, 1, 1, 2, 0, 2, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 3, 2, 4,
+ 6, 1, 2, 4, 6, 0, 1, 2, 1, 2,
+ 1, 1, 2, 2, 3, 1, 2, 1, 2, 2,
+ 2, 3, 5, 3, 3, 2, 4, 2, 3, 1,
+ 3, 2, 1, 1, 2, 2, 1, 2, 2, 1,
+ 2, 2, 1, 2, 2, 1, 2, 2, 1, 2,
+ 2, 2, 2, 1, 1, 2, 2, 2, 2, 2,
+ 2, 1, 1, 1, 1, 1, 1, 1, 1, 2,
+ 0, 2
+};
+
+
+#define yyerrok (yyerrstatus = 0)
+#define yyclearin (yychar = YYEMPTY)
+#define YYEMPTY (-2)
+#define YYEOF 0
+
+#define YYACCEPT goto yyacceptlab
+#define YYABORT goto yyabortlab
+#define YYERROR goto yyerrorlab
+
+
+#define YYRECOVERING() (!!yyerrstatus)
+
+#define YYBACKUP(Token, Value) \
+do \
+ if (yychar == YYEMPTY) \
+ { \
+ yychar = (Token); \
+ yylval = (Value); \
+ YYPOPSTACK (yylen); \
+ yystate = *yyssp; \
+ goto yybackup; \
+ } \
+ else \
+ { \
+ yyerror (pc, YY_("syntax error: cannot back up")); \
+ YYERROR; \
+ } \
+while (0)
+
+/* Error token number */
+#define YYTERROR 1
+#define YYERRCODE 256
+
+
+
+/* Enable debugging if requested. */
+#if YYDEBUG
+
+# ifndef YYFPRINTF
+# include <stdio.h> /* INFRINGES ON USER NAME SPACE */
+# define YYFPRINTF fprintf
+# endif
+
+# define YYDPRINTF(Args) \
+do { \
+ if (yydebug) \
+ YYFPRINTF Args; \
+} while (0)
+
+/* This macro is provided for backward compatibility. */
+#ifndef YY_LOCATION_PRINT
+# define YY_LOCATION_PRINT(File, Loc) ((void) 0)
+#endif
+
+
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \
+do { \
+ if (yydebug) \
+ { \
+ YYFPRINTF (stderr, "%s ", Title); \
+ yy_symbol_print (stderr, \
+ Type, Value, pc); \
+ YYFPRINTF (stderr, "\n"); \
+ } \
+} while (0)
+
+
+/*----------------------------------------.
+| Print this symbol's value on YYOUTPUT. |
+`----------------------------------------*/
+
+static void
+yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, parser_control *pc)
+{
+ FILE *yyo = yyoutput;
+ YYUSE (yyo);
+ YYUSE (pc);
+ if (!yyvaluep)
+ return;
+# ifdef YYPRINT
+ if (yytype < YYNTOKENS)
+ YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep);
+# endif
+ YYUSE (yytype);
+}
+
+
+/*--------------------------------.
+| Print this symbol on YYOUTPUT. |
+`--------------------------------*/
+
+static void
+yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, parser_control *pc)
+{
+ YYFPRINTF (yyoutput, "%s %s (",
+ yytype < YYNTOKENS ? "token" : "nterm", yytname[yytype]);
+
+ yy_symbol_value_print (yyoutput, yytype, yyvaluep, pc);
+ YYFPRINTF (yyoutput, ")");
+}
+
+/*------------------------------------------------------------------.
+| yy_stack_print -- Print the state stack from its BOTTOM up to its |
+| TOP (included). |
+`------------------------------------------------------------------*/
+
+static void
+yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop)
+{
+ YYFPRINTF (stderr, "Stack now");
+ for (; yybottom <= yytop; yybottom++)
+ {
+ int yybot = *yybottom;
+ YYFPRINTF (stderr, " %d", yybot);
+ }
+ YYFPRINTF (stderr, "\n");
+}
+
+# define YY_STACK_PRINT(Bottom, Top) \
+do { \
+ if (yydebug) \
+ yy_stack_print ((Bottom), (Top)); \
+} while (0)
+
+
+/*------------------------------------------------.
+| Report that the YYRULE is going to be reduced. |
+`------------------------------------------------*/
+
+static void
+yy_reduce_print (yytype_int16 *yyssp, YYSTYPE *yyvsp, int yyrule, parser_control *pc)
+{
+ unsigned long int yylno = yyrline[yyrule];
+ int yynrhs = yyr2[yyrule];
+ int yyi;
+ YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n",
+ yyrule - 1, yylno);
+ /* The symbols being reduced. */
+ for (yyi = 0; yyi < yynrhs; yyi++)
+ {
+ YYFPRINTF (stderr, " $%d = ", yyi + 1);
+ yy_symbol_print (stderr,
+ yystos[yyssp[yyi + 1 - yynrhs]],
+ &(yyvsp[(yyi + 1) - (yynrhs)])
+ , pc);
+ YYFPRINTF (stderr, "\n");
+ }
+}
+
+# define YY_REDUCE_PRINT(Rule) \
+do { \
+ if (yydebug) \
+ yy_reduce_print (yyssp, yyvsp, Rule, pc); \
+} while (0)
+
+/* Nonzero means print parse trace. It is left uninitialized so that
+ multiple parsers can coexist. */
+int yydebug;
+#else /* !YYDEBUG */
+# define YYDPRINTF(Args)
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location)
+# define YY_STACK_PRINT(Bottom, Top)
+# define YY_REDUCE_PRINT(Rule)
+#endif /* !YYDEBUG */
+
+
+/* YYINITDEPTH -- initial size of the parser's stacks. */
+#ifndef YYINITDEPTH
+# define YYINITDEPTH 200
+#endif
+
+/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
+ if the built-in stack extension method is used).
+
+ Do not make this value too large; the results are undefined if
+ YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
+ evaluated with infinite-precision integer arithmetic. */
+
+#ifndef YYMAXDEPTH
+# define YYMAXDEPTH 10000
+#endif
+
+
+#if YYERROR_VERBOSE
+
+# ifndef yystrlen
+# if defined __GLIBC__ && defined _STRING_H
+# define yystrlen strlen
+# else
+/* Return the length of YYSTR. */
+static YYSIZE_T
+yystrlen (const char *yystr)
+{
+ YYSIZE_T yylen;
+ for (yylen = 0; yystr[yylen]; yylen++)
+ continue;
+ return yylen;
+}
+# endif
+# endif
+
+# ifndef yystpcpy
+# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE
+# define yystpcpy stpcpy
+# else
+/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in
+ YYDEST. */
+static char *
+yystpcpy (char *yydest, const char *yysrc)
+{
+ char *yyd = yydest;
+ const char *yys = yysrc;
+
+ while ((*yyd++ = *yys++) != '\0')
+ continue;
+
+ return yyd - 1;
+}
+# endif
+# endif
+
+# ifndef yytnamerr
+/* Copy to YYRES the contents of YYSTR after stripping away unnecessary
+ quotes and backslashes, so that it's suitable for yyerror. The
+ heuristic is that double-quoting is unnecessary unless the string
+ contains an apostrophe, a comma, or backslash (other than
+ backslash-backslash). YYSTR is taken from yytname. If YYRES is
+ null, do not copy; instead, return the length of what the result
+ would have been. */
+static YYSIZE_T
+yytnamerr (char *yyres, const char *yystr)
+{
+ if (*yystr == '"')
+ {
+ YYSIZE_T yyn = 0;
+ char const *yyp = yystr;
+
+ for (;;)
+ switch (*++yyp)
+ {
+ case '\'':
+ case ',':
+ goto do_not_strip_quotes;
+
+ case '\\':
+ if (*++yyp != '\\')
+ goto do_not_strip_quotes;
+ /* Fall through. */
+ default:
+ if (yyres)
+ yyres[yyn] = *yyp;
+ yyn++;
+ break;
+
+ case '"':
+ if (yyres)
+ yyres[yyn] = '\0';
+ return yyn;
+ }
+ do_not_strip_quotes: ;
+ }
+
+ if (! yyres)
+ return yystrlen (yystr);
+
+ return yystpcpy (yyres, yystr) - yyres;
+}
+# endif
+
+/* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message
+ about the unexpected token YYTOKEN for the state stack whose top is
+ YYSSP.
+
+ Return 0 if *YYMSG was successfully written. Return 1 if *YYMSG is
+ not large enough to hold the message. In that case, also set
+ *YYMSG_ALLOC to the required number of bytes. Return 2 if the
+ required number of bytes is too large to store. */
+static int
+yysyntax_error (YYSIZE_T *yymsg_alloc, char **yymsg,
+ yytype_int16 *yyssp, int yytoken)
+{
+ YYSIZE_T yysize0 = yytnamerr (YY_NULLPTR, yytname[yytoken]);
+ YYSIZE_T yysize = yysize0;
+ enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 };
+ /* Internationalized format string. */
+ const char *yyformat = YY_NULLPTR;
+ /* Arguments of yyformat. */
+ char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM];
+ /* Number of reported tokens (one for the "unexpected", one per
+ "expected"). */
+ int yycount = 0;
+
+ /* There are many possibilities here to consider:
+ - If this state is a consistent state with a default action, then
+ the only way this function was invoked is if the default action
+ is an error action. In that case, don't check for expected
+ tokens because there are none.
+ - The only way there can be no lookahead present (in yychar) is if
+ this state is a consistent state with a default action. Thus,
+ detecting the absence of a lookahead is sufficient to determine
+ that there is no unexpected or expected token to report. In that
+ case, just report a simple "syntax error".
+ - Don't assume there isn't a lookahead just because this state is a
+ consistent state with a default action. There might have been a
+ previous inconsistent state, consistent state with a non-default
+ action, or user semantic action that manipulated yychar.
+ - Of course, the expected token list depends on states to have
+ correct lookahead information, and it depends on the parser not
+ to perform extra reductions after fetching a lookahead from the
+ scanner and before detecting a syntax error. Thus, state merging
+ (from LALR or IELR) and default reductions corrupt the expected
+ token list. However, the list is correct for canonical LR with
+ one exception: it will still contain any token that will not be
+ accepted due to an error action in a later state.
+ */
+ if (yytoken != YYEMPTY)
+ {
+ int yyn = yypact[*yyssp];
+ yyarg[yycount++] = yytname[yytoken];
+ if (!yypact_value_is_default (yyn))
+ {
+ /* Start YYX at -YYN if negative to avoid negative indexes in
+ YYCHECK. In other words, skip the first -YYN actions for
+ this state because they are default actions. */
+ int yyxbegin = yyn < 0 ? -yyn : 0;
+ /* Stay within bounds of both yycheck and yytname. */
+ int yychecklim = YYLAST - yyn + 1;
+ int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS;
+ int yyx;
+
+ for (yyx = yyxbegin; yyx < yyxend; ++yyx)
+ if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR
+ && !yytable_value_is_error (yytable[yyx + yyn]))
+ {
+ if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM)
+ {
+ yycount = 1;
+ yysize = yysize0;
+ break;
+ }
+ yyarg[yycount++] = yytname[yyx];
+ {
+ YYSIZE_T yysize1 = yysize + yytnamerr (YY_NULLPTR, yytname[yyx]);
+ if (! (yysize <= yysize1
+ && yysize1 <= YYSTACK_ALLOC_MAXIMUM))
+ return 2;
+ yysize = yysize1;
+ }
+ }
+ }
+ }
+
+ switch (yycount)
+ {
+# define YYCASE_(N, S) \
+ case N: \
+ yyformat = S; \
+ break
+ YYCASE_(0, YY_("syntax error"));
+ YYCASE_(1, YY_("syntax error, unexpected %s"));
+ YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s"));
+ YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s"));
+ YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s"));
+ YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"));
+# undef YYCASE_
+ }
+
+ {
+ YYSIZE_T yysize1 = yysize + yystrlen (yyformat);
+ if (! (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM))
+ return 2;
+ yysize = yysize1;
+ }
+
+ if (*yymsg_alloc < yysize)
+ {
+ *yymsg_alloc = 2 * yysize;
+ if (! (yysize <= *yymsg_alloc
+ && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM))
+ *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM;
+ return 1;
+ }
+
+ /* Avoid sprintf, as that infringes on the user's name space.
+ Don't have undefined behavior even if the translation
+ produced a string with the wrong number of "%s"s. */
+ {
+ char *yyp = *yymsg;
+ int yyi = 0;
+ while ((*yyp = *yyformat) != '\0')
+ if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount)
+ {
+ yyp += yytnamerr (yyp, yyarg[yyi++]);
+ yyformat += 2;
+ }
+ else
+ {
+ yyp++;
+ yyformat++;
+ }
+ }
+ return 0;
+}
+#endif /* YYERROR_VERBOSE */
+
+/*-----------------------------------------------.
+| Release the memory associated to this symbol. |
+`-----------------------------------------------*/
+
+static void
+yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, parser_control *pc)
+{
+ YYUSE (yyvaluep);
+ YYUSE (pc);
+ if (!yymsg)
+ yymsg = "Deleting";
+ YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp);
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ YYUSE (yytype);
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+}
+
+
+
+
+/*----------.
+| yyparse. |
+`----------*/
+
+int
+yyparse (parser_control *pc)
+{
+/* The lookahead symbol. */
+int yychar;
+
+
+/* The semantic value of the lookahead symbol. */
+/* Default value used for initialization, for pacifying older GCCs
+ or non-GCC compilers. */
+YY_INITIAL_VALUE (static YYSTYPE yyval_default;)
+YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default);
+
+ /* Number of syntax errors so far. */
+ int yynerrs;
+
+ int yystate;
+ /* Number of tokens to shift before error messages enabled. */
+ int yyerrstatus;
+
+ /* The stacks and their tools:
+ 'yyss': related to states.
+ 'yyvs': related to semantic values.
+
+ Refer to the stacks through separate pointers, to allow yyoverflow
+ to reallocate them elsewhere. */
+
+ /* The state stack. */
+ yytype_int16 yyssa[YYINITDEPTH];
+ yytype_int16 *yyss;
+ yytype_int16 *yyssp;
+
+ /* The semantic value stack. */
+ YYSTYPE yyvsa[YYINITDEPTH];
+ YYSTYPE *yyvs;
+ YYSTYPE *yyvsp;
+
+ YYSIZE_T yystacksize;
+
+ int yyn;
+ int yyresult;
+ /* Lookahead token as an internal (translated) token number. */
+ int yytoken = 0;
+ /* The variables used to return semantic value and location from the
+ action routines. */
+ YYSTYPE yyval;
+
+#if YYERROR_VERBOSE
+ /* Buffer for error messages, and its allocated size. */
+ char yymsgbuf[128];
+ char *yymsg = yymsgbuf;
+ YYSIZE_T yymsg_alloc = sizeof yymsgbuf;
+#endif
+
+#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N))
+
+ /* The number of symbols on the RHS of the reduced rule.
+ Keep to zero when no symbol should be popped. */
+ int yylen = 0;
+
+ yyssp = yyss = yyssa;
+ yyvsp = yyvs = yyvsa;
+ yystacksize = YYINITDEPTH;
+
+ YYDPRINTF ((stderr, "Starting parse\n"));
+
+ yystate = 0;
+ yyerrstatus = 0;
+ yynerrs = 0;
+ yychar = YYEMPTY; /* Cause a token to be read. */
+ goto yysetstate;
+
+/*------------------------------------------------------------.
+| yynewstate -- Push a new state, which is found in yystate. |
+`------------------------------------------------------------*/
+ yynewstate:
+ /* In all cases, when you get here, the value and location stacks
+ have just been pushed. So pushing a state here evens the stacks. */
+ yyssp++;
+
+ yysetstate:
+ *yyssp = yystate;
+
+ if (yyss + yystacksize - 1 <= yyssp)
+ {
+ /* Get the current used size of the three stacks, in elements. */
+ YYSIZE_T yysize = yyssp - yyss + 1;
+
+#ifdef yyoverflow
+ {
+ /* Give user a chance to reallocate the stack. Use copies of
+ these so that the &'s don't force the real ones into
+ memory. */
+ YYSTYPE *yyvs1 = yyvs;
+ yytype_int16 *yyss1 = yyss;
+
+ /* Each stack pointer address is followed by the size of the
+ data in use in that stack, in bytes. This used to be a
+ conditional around just the two extra args, but that might
+ be undefined if yyoverflow is a macro. */
+ yyoverflow (YY_("memory exhausted"),
+ &yyss1, yysize * sizeof (*yyssp),
+ &yyvs1, yysize * sizeof (*yyvsp),
+ &yystacksize);
+
+ yyss = yyss1;
+ yyvs = yyvs1;
+ }
+#else /* no yyoverflow */
+# ifndef YYSTACK_RELOCATE
+ goto yyexhaustedlab;
+# else
+ /* Extend the stack our own way. */
+ if (YYMAXDEPTH <= yystacksize)
+ goto yyexhaustedlab;
+ yystacksize *= 2;
+ if (YYMAXDEPTH < yystacksize)
+ yystacksize = YYMAXDEPTH;
+
+ {
+ yytype_int16 *yyss1 = yyss;
+ union yyalloc *yyptr =
+ (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize));
+ if (! yyptr)
+ goto yyexhaustedlab;
+ YYSTACK_RELOCATE (yyss_alloc, yyss);
+ YYSTACK_RELOCATE (yyvs_alloc, yyvs);
+# undef YYSTACK_RELOCATE
+ if (yyss1 != yyssa)
+ YYSTACK_FREE (yyss1);
+ }
+# endif
+#endif /* no yyoverflow */
+
+ yyssp = yyss + yysize - 1;
+ yyvsp = yyvs + yysize - 1;
+
+ YYDPRINTF ((stderr, "Stack size increased to %lu\n",
+ (unsigned long int) yystacksize));
+
+ if (yyss + yystacksize - 1 <= yyssp)
+ YYABORT;
+ }
+
+ YYDPRINTF ((stderr, "Entering state %d\n", yystate));
+
+ if (yystate == YYFINAL)
+ YYACCEPT;
+
+ goto yybackup;
+
+/*-----------.
+| yybackup. |
+`-----------*/
+yybackup:
+
+ /* Do appropriate processing given the current state. Read a
+ lookahead token if we need one and don't already have one. */
+
+ /* First try to decide what to do without reference to lookahead token. */
+ yyn = yypact[yystate];
+ if (yypact_value_is_default (yyn))
+ goto yydefault;
+
+ /* Not known => get a lookahead token if don't already have one. */
+
+ /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol. */
+ if (yychar == YYEMPTY)
+ {
+ YYDPRINTF ((stderr, "Reading a token: "));
+ yychar = yylex (&yylval, pc);
+ }
+
+ if (yychar <= YYEOF)
+ {
+ yychar = yytoken = YYEOF;
+ YYDPRINTF ((stderr, "Now at end of input.\n"));
+ }
+ else
+ {
+ yytoken = YYTRANSLATE (yychar);
+ YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
+ }
+
+ /* If the proper action on seeing token YYTOKEN is to reduce or to
+ detect an error, take that action. */
+ yyn += yytoken;
+ if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
+ goto yydefault;
+ yyn = yytable[yyn];
+ if (yyn <= 0)
+ {
+ if (yytable_value_is_error (yyn))
+ goto yyerrlab;
+ yyn = -yyn;
+ goto yyreduce;
+ }
+
+ /* Count tokens shifted since error; after three, turn off error
+ status. */
+ if (yyerrstatus)
+ yyerrstatus--;
+
+ /* Shift the lookahead token. */
+ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
+
+ /* Discard the shifted token. */
+ yychar = YYEMPTY;
+
+ yystate = yyn;
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+ goto yynewstate;
+
+
+/*-----------------------------------------------------------.
+| yydefault -- do the default action for the current state. |
+`-----------------------------------------------------------*/
+yydefault:
+ yyn = yydefact[yystate];
+ if (yyn == 0)
+ goto yyerrlab;
+ goto yyreduce;
+
+
+/*-----------------------------.
+| yyreduce -- Do a reduction. |
+`-----------------------------*/
+yyreduce:
+ /* yyn is the number of a rule to reduce with. */
+ yylen = yyr2[yyn];
+
+ /* If YYLEN is nonzero, implement the default value of the action:
+ '$$ = $1'.
+
+ Otherwise, the following line sets YYVAL to garbage.
+ This behavior is undocumented and Bison
+ users should not rely upon it. Assigning to YYVAL
+ unconditionally makes the parser a bit smaller, and it avoids a
+ GCC warning that YYVAL may be used uninitialized. */
+ yyval = yyvsp[1-yylen];
+
+
+ YY_REDUCE_PRINT (yyn);
+ switch (yyn)
+ {
+ case 4:
+#line 323 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->seconds = (yyvsp[0].timespec);
+ pc->timespec_seen = 1;
+ }
+#line 1621 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 7:
+#line 335 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->times_seen++; pc->dates_seen++;
+ }
+#line 1629 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 8:
+#line 338 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->times_seen++;
+ }
+#line 1637 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 9:
+#line 341 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->local_zones_seen++;
+ }
+#line 1645 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 10:
+#line 344 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->zones_seen++;
+ }
+#line 1653 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 11:
+#line 347 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->dates_seen++;
+ }
+#line 1661 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 12:
+#line 350 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->days_seen++;
+ }
+#line 1669 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 18:
+#line 367 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ set_hhmmss (pc, (yyvsp[-1].textintval).value, 0, 0, 0);
+ pc->meridian = (yyvsp[0].intval);
+ }
+#line 1678 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 19:
+#line 371 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ set_hhmmss (pc, (yyvsp[-3].textintval).value, (yyvsp[-1].textintval).value, 0, 0);
+ pc->meridian = (yyvsp[0].intval);
+ }
+#line 1687 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 20:
+#line 375 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ set_hhmmss (pc, (yyvsp[-5].textintval).value, (yyvsp[-3].textintval).value, (yyvsp[-1].timespec).tv_sec, (yyvsp[-1].timespec).tv_nsec);
+ pc->meridian = (yyvsp[0].intval);
+ }
+#line 1696 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 22:
+#line 383 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ set_hhmmss (pc, (yyvsp[-1].textintval).value, 0, 0, 0);
+ pc->meridian = MER24;
+ }
+#line 1705 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 23:
+#line 387 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ set_hhmmss (pc, (yyvsp[-3].textintval).value, (yyvsp[-1].textintval).value, 0, 0);
+ pc->meridian = MER24;
+ }
+#line 1714 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 24:
+#line 391 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ set_hhmmss (pc, (yyvsp[-5].textintval).value, (yyvsp[-3].textintval).value, (yyvsp[-1].timespec).tv_sec, (yyvsp[-1].timespec).tv_nsec);
+ pc->meridian = MER24;
+ }
+#line 1723 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 27:
+#line 403 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->zones_seen++;
+ if (! time_zone_hhmm (pc, (yyvsp[-1].textintval), (yyvsp[0].textintval))) YYABORT;
+ }
+#line 1732 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 28:
+#line 428 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->local_isdst = (yyvsp[0].intval);
+ pc->dsts_seen += (0 < (yyvsp[0].intval));
+ }
+#line 1741 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 29:
+#line 432 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->local_isdst = 1;
+ pc->dsts_seen += (0 < (yyvsp[-1].intval)) + 1;
+ }
+#line 1750 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 30:
+#line 443 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->time_zone = (yyvsp[0].intval);
+ }
+#line 1758 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 31:
+#line 446 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->time_zone = HOUR(7);
+ }
+#line 1766 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 32:
+#line 449 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->time_zone = (yyvsp[-1].intval);
+ apply_relative_time (pc, (yyvsp[0].rel), 1);
+ }
+#line 1775 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 33:
+#line 453 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->time_zone = HOUR(7);
+ apply_relative_time (pc, (yyvsp[0].rel), 1);
+ }
+#line 1784 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 34:
+#line 457 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ if (! time_zone_hhmm (pc, (yyvsp[-1].textintval), (yyvsp[0].textintval))) YYABORT;
+ pc->time_zone += (yyvsp[-2].intval);
+ }
+#line 1793 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 35:
+#line 461 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->time_zone = (yyvsp[0].intval) + 60;
+ }
+#line 1801 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 36:
+#line 464 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->time_zone = (yyvsp[-1].intval) + 60;
+ }
+#line 1809 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 37:
+#line 470 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->day_ordinal = 0;
+ pc->day_number = (yyvsp[0].intval);
+ }
+#line 1818 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 38:
+#line 474 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->day_ordinal = 0;
+ pc->day_number = (yyvsp[-1].intval);
+ }
+#line 1827 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 39:
+#line 478 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->day_ordinal = (yyvsp[-1].intval);
+ pc->day_number = (yyvsp[0].intval);
+ }
+#line 1836 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 40:
+#line 482 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->day_ordinal = (yyvsp[-1].textintval).value;
+ pc->day_number = (yyvsp[0].intval);
+ }
+#line 1845 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 41:
+#line 489 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->month = (yyvsp[-2].textintval).value;
+ pc->day = (yyvsp[0].textintval).value;
+ }
+#line 1854 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 42:
+#line 493 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ /**
+ * Interpret as YYYY/MM/DD if the first value has 4 or more digits,
+ * otherwise as MM/DD/YY.
+ * The goal in recognizing YYYY/MM/DD is solely to support legacy
+ * machine-generated dates like those in an RCS log listing. If
+ * you want portability, use the ISO 8601 format.
+ */
+ if (4 <= (yyvsp[-4].textintval).digits) {
+ pc->year = (yyvsp[-4].textintval);
+ pc->month = (yyvsp[-2].textintval).value;
+ pc->day = (yyvsp[0].textintval).value;
+ } else {
+ pc->month = (yyvsp[-4].textintval).value;
+ pc->day = (yyvsp[-2].textintval).value;
+ pc->year = (yyvsp[0].textintval);
+ }
+ }
+#line 1877 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 43:
+#line 511 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ /* e.g. 17-JUN-1992. */
+ pc->day = (yyvsp[-2].textintval).value;
+ pc->month = (yyvsp[-1].intval);
+ pc->year.value = -(yyvsp[0].textintval).value;
+ pc->year.digits = (yyvsp[0].textintval).digits;
+ }
+#line 1889 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 44:
+#line 518 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ /* e.g. JUN-17-1992. */
+ pc->month = (yyvsp[-2].intval);
+ pc->day = -(yyvsp[-1].textintval).value;
+ pc->year.value = -(yyvsp[0].textintval).value;
+ pc->year.digits = (yyvsp[0].textintval).digits;
+ }
+#line 1901 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 45:
+#line 525 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->month = (yyvsp[-1].intval);
+ pc->day = (yyvsp[0].textintval).value;
+ }
+#line 1910 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 46:
+#line 529 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->month = (yyvsp[-3].intval);
+ pc->day = (yyvsp[-2].textintval).value;
+ pc->year = (yyvsp[0].textintval);
+ }
+#line 1920 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 47:
+#line 534 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->day = (yyvsp[-1].textintval).value;
+ pc->month = (yyvsp[0].intval);
+ }
+#line 1929 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 48:
+#line 538 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ pc->day = (yyvsp[-2].textintval).value;
+ pc->month = (yyvsp[-1].intval);
+ pc->year = (yyvsp[0].textintval);
+ }
+#line 1939 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 50:
+#line 547 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ /* ISO 8601 format.YYYY-MM-DD. */
+ pc->year = (yyvsp[-2].textintval);
+ pc->month = -(yyvsp[-1].textintval).value;
+ pc->day = -(yyvsp[0].textintval).value;
+ }
+#line 1950 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 51:
+#line 557 "lib/parse-date.y" /* yacc.c:1646 */
+ { apply_relative_time (pc, (yyvsp[-1].rel), (yyvsp[0].intval)); }
+#line 1956 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 52:
+#line 559 "lib/parse-date.y" /* yacc.c:1646 */
+ { apply_relative_time (pc, (yyvsp[0].rel), 1); }
+#line 1962 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 53:
+#line 561 "lib/parse-date.y" /* yacc.c:1646 */
+ { apply_relative_time (pc, (yyvsp[0].rel), 1); }
+#line 1968 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 54:
+#line 566 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).year = (yyvsp[-1].intval); }
+#line 1974 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 55:
+#line 568 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).year = (yyvsp[-1].textintval).value; }
+#line 1980 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 56:
+#line 570 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).year = 1; }
+#line 1986 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 57:
+#line 572 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).month = (yyvsp[-1].intval); }
+#line 1992 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 58:
+#line 574 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).month = (yyvsp[-1].textintval).value; }
+#line 1998 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 59:
+#line 576 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).month = 1; }
+#line 2004 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 60:
+#line 578 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).day = (yyvsp[-1].intval) * (yyvsp[0].intval); }
+#line 2010 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 61:
+#line 580 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).day = (yyvsp[-1].textintval).value * (yyvsp[0].intval); }
+#line 2016 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 62:
+#line 582 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).day = (yyvsp[0].intval); }
+#line 2022 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 63:
+#line 584 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).hour = (yyvsp[-1].intval); }
+#line 2028 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 64:
+#line 586 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).hour = (yyvsp[-1].textintval).value; }
+#line 2034 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 65:
+#line 588 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).hour = 1; }
+#line 2040 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 66:
+#line 590 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).minutes = (yyvsp[-1].intval); }
+#line 2046 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 67:
+#line 592 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).minutes = (yyvsp[-1].textintval).value; }
+#line 2052 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 68:
+#line 594 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).minutes = 1; }
+#line 2058 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 69:
+#line 596 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).seconds = (yyvsp[-1].intval); }
+#line 2064 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 70:
+#line 598 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).seconds = (yyvsp[-1].textintval).value; }
+#line 2070 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 71:
+#line 599 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ (yyval.rel) = RELATIVE_TIME_0;
+ (yyval.rel).seconds = (yyvsp[-1].timespec).tv_sec;
+ (yyval.rel).ns = (yyvsp[-1].timespec).tv_nsec;
+ }
+#line 2080 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 72:
+#line 604 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ (yyval.rel) = RELATIVE_TIME_0;
+ (yyval.rel).seconds = (yyvsp[-1].timespec).tv_sec;
+ (yyval.rel).ns = (yyvsp[-1].timespec).tv_nsec;
+ }
+#line 2090 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 73:
+#line 610 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).seconds = 1; }
+#line 2096 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 75:
+#line 616 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).year = (yyvsp[-1].textintval).value; }
+#line 2102 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 76:
+#line 618 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).month = (yyvsp[-1].textintval).value; }
+#line 2108 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 77:
+#line 620 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).day = (yyvsp[-1].textintval).value * (yyvsp[0].intval); }
+#line 2114 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 78:
+#line 622 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).hour = (yyvsp[-1].textintval).value; }
+#line 2120 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 79:
+#line 624 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).minutes = (yyvsp[-1].textintval).value; }
+#line 2126 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 80:
+#line 626 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).seconds = (yyvsp[-1].textintval).value; }
+#line 2132 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 81:
+#line 631 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).day = (yyvsp[0].intval); }
+#line 2138 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 85:
+#line 639 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.timespec).tv_sec = (yyvsp[0].textintval).value; (yyval.timespec).tv_nsec = 0; }
+#line 2144 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 87:
+#line 645 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.timespec).tv_sec = (yyvsp[0].textintval).value; (yyval.timespec).tv_nsec = 0; }
+#line 2150 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 88:
+#line 650 "lib/parse-date.y" /* yacc.c:1646 */
+ { digits_to_date_time (pc, (yyvsp[0].textintval)); }
+#line 2156 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 89:
+#line 654 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ /**
+ * Hybrid all-digit and relative offset, so that we accept e.g.,
+ * "YYYYMMDD +N days" as well as "YYYYMMDD N days".
+ */
+ digits_to_date_time (pc, (yyvsp[-1].textintval));
+ apply_relative_time (pc, (yyvsp[0].rel), 1);
+ }
+#line 2169 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 90:
+#line 666 "lib/parse-date.y" /* yacc.c:1646 */
+ { (yyval.textintval).value = (yyval.textintval).digits = 0; }
+#line 2175 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+ case 91:
+#line 667 "lib/parse-date.y" /* yacc.c:1646 */
+ {
+ (yyval.textintval) = (yyvsp[0].textintval);
+ }
+#line 2183 "lib/parse-date.c" /* yacc.c:1646 */
+ break;
+
+
+#line 2187 "lib/parse-date.c" /* yacc.c:1646 */
+ default: break;
+ }
+ /* User semantic actions sometimes alter yychar, and that requires
+ that yytoken be updated with the new translation. We take the
+ approach of translating immediately before every use of yytoken.
+ One alternative is translating here after every semantic action,
+ but that translation would be missed if the semantic action invokes
+ YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or
+ if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an
+ incorrect destructor might then be invoked immediately. In the
+ case of YYERROR or YYBACKUP, subsequent parser actions might lead
+ to an incorrect destructor call or verbose syntax error message
+ before the lookahead is translated. */
+ YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc);
+
+ YYPOPSTACK (yylen);
+ yylen = 0;
+ YY_STACK_PRINT (yyss, yyssp);
+
+ *++yyvsp = yyval;
+
+ /* Now 'shift' the result of the reduction. Determine what state
+ that goes to, based on the state we popped back to and the rule
+ number reduced by. */
+
+ yyn = yyr1[yyn];
+
+ yystate = yypgoto[yyn - YYNTOKENS] + *yyssp;
+ if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp)
+ yystate = yytable[yystate];
+ else
+ yystate = yydefgoto[yyn - YYNTOKENS];
+
+ goto yynewstate;
+
+
+/*--------------------------------------.
+| yyerrlab -- here on detecting error. |
+`--------------------------------------*/
+yyerrlab:
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = yychar == YYEMPTY ? YYEMPTY : YYTRANSLATE (yychar);
+
+ /* If not already recovering from an error, report this error. */
+ if (!yyerrstatus)
+ {
+ ++yynerrs;
+#if ! YYERROR_VERBOSE
+ yyerror (pc, YY_("syntax error"));
+#else
+# define YYSYNTAX_ERROR yysyntax_error (&yymsg_alloc, &yymsg, \
+ yyssp, yytoken)
+ {
+ char const *yymsgp = YY_("syntax error");
+ int yysyntax_error_status;
+ yysyntax_error_status = YYSYNTAX_ERROR;
+ if (yysyntax_error_status == 0)
+ yymsgp = yymsg;
+ else if (yysyntax_error_status == 1)
+ {
+ if (yymsg != yymsgbuf)
+ YYSTACK_FREE (yymsg);
+ yymsg = (char *) YYSTACK_ALLOC (yymsg_alloc);
+ if (!yymsg)
+ {
+ yymsg = yymsgbuf;
+ yymsg_alloc = sizeof yymsgbuf;
+ yysyntax_error_status = 2;
+ }
+ else
+ {
+ yysyntax_error_status = YYSYNTAX_ERROR;
+ yymsgp = yymsg;
+ }
+ }
+ yyerror (pc, yymsgp);
+ if (yysyntax_error_status == 2)
+ goto yyexhaustedlab;
+ }
+# undef YYSYNTAX_ERROR
+#endif
+ }
+
+
+
+ if (yyerrstatus == 3)
+ {
+ /* If just tried and failed to reuse lookahead token after an
+ error, discard it. */
+
+ if (yychar <= YYEOF)
+ {
+ /* Return failure if at end of input. */
+ if (yychar == YYEOF)
+ YYABORT;
+ }
+ else
+ {
+ yydestruct ("Error: discarding",
+ yytoken, &yylval, pc);
+ yychar = YYEMPTY;
+ }
+ }
+
+ /* Else will try to reuse lookahead token after shifting the error
+ token. */
+ goto yyerrlab1;
+
+
+/*---------------------------------------------------.
+| yyerrorlab -- error raised explicitly by YYERROR. |
+`---------------------------------------------------*/
+yyerrorlab:
+
+ /* Pacify compilers like GCC when the user code never invokes
+ YYERROR and the label yyerrorlab therefore never appears in user
+ code. */
+ if (/*CONSTCOND*/ 0)
+ goto yyerrorlab;
+
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYERROR. */
+ YYPOPSTACK (yylen);
+ yylen = 0;
+ YY_STACK_PRINT (yyss, yyssp);
+ yystate = *yyssp;
+ goto yyerrlab1;
+
+
+/*-------------------------------------------------------------.
+| yyerrlab1 -- common code for both syntax error and YYERROR. |
+`-------------------------------------------------------------*/
+yyerrlab1:
+ yyerrstatus = 3; /* Each real token shifted decrements this. */
+
+ for (;;)
+ {
+ yyn = yypact[yystate];
+ if (!yypact_value_is_default (yyn))
+ {
+ yyn += YYTERROR;
+ if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR)
+ {
+ yyn = yytable[yyn];
+ if (0 < yyn)
+ break;
+ }
+ }
+
+ /* Pop the current state because it cannot handle the error token. */
+ if (yyssp == yyss)
+ YYABORT;
+
+
+ yydestruct ("Error: popping",
+ yystos[yystate], yyvsp, pc);
+ YYPOPSTACK (1);
+ yystate = *yyssp;
+ YY_STACK_PRINT (yyss, yyssp);
+ }
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+
+ /* Shift the error token. */
+ YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp);
+
+ yystate = yyn;
+ goto yynewstate;
+
+
+/*-------------------------------------.
+| yyacceptlab -- YYACCEPT comes here. |
+`-------------------------------------*/
+yyacceptlab:
+ yyresult = 0;
+ goto yyreturn;
+
+/*-----------------------------------.
+| yyabortlab -- YYABORT comes here. |
+`-----------------------------------*/
+yyabortlab:
+ yyresult = 1;
+ goto yyreturn;
+
+#if !defined yyoverflow || YYERROR_VERBOSE
+/*-------------------------------------------------.
+| yyexhaustedlab -- memory exhaustion comes here. |
+`-------------------------------------------------*/
+yyexhaustedlab:
+ yyerror (pc, YY_("memory exhausted"));
+ yyresult = 2;
+ /* Fall through. */
+#endif
+
+yyreturn:
+ if (yychar != YYEMPTY)
+ {
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = YYTRANSLATE (yychar);
+ yydestruct ("Cleanup: discarding lookahead",
+ yytoken, &yylval, pc);
+ }
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYABORT or YYACCEPT. */
+ YYPOPSTACK (yylen);
+ YY_STACK_PRINT (yyss, yyssp);
+ while (yyssp != yyss)
+ {
+ yydestruct ("Cleanup: popping",
+ yystos[*yyssp], yyvsp, pc);
+ YYPOPSTACK (1);
+ }
+#ifndef yyoverflow
+ if (yyss != yyssa)
+ YYSTACK_FREE (yyss);
+#endif
+#if YYERROR_VERBOSE
+ if (yymsg != yymsgbuf)
+ YYSTACK_FREE (yymsg);
+#endif
+ return yyresult;
+}
+#line 672 "lib/parse-date.y" /* yacc.c:1906 */
+
+
+static table const meridian_table[] = {
+ { "AM", tMERIDIAN, MERam },
+ { "A.M.", tMERIDIAN, MERam },
+ { "PM", tMERIDIAN, MERpm },
+ { "P.M.", tMERIDIAN, MERpm },
+ { NULL, 0, 0 }
+};
+
+static table const dst_table[] = {
+ { "DST", tDST, 0 }
+};
+
+static table const month_and_day_table[] = {
+ { "JANUARY", tMONTH, 1 },
+ { "FEBRUARY", tMONTH, 2 },
+ { "MARCH", tMONTH, 3 },
+ { "APRIL", tMONTH, 4 },
+ { "MAY", tMONTH, 5 },
+ { "JUNE", tMONTH, 6 },
+ { "JULY", tMONTH, 7 },
+ { "AUGUST", tMONTH, 8 },
+ { "SEPTEMBER",tMONTH, 9 },
+ { "SEPT", tMONTH, 9 },
+ { "OCTOBER", tMONTH, 10 },
+ { "NOVEMBER", tMONTH, 11 },
+ { "DECEMBER", tMONTH, 12 },
+ { "SUNDAY", tDAY, 0 },
+ { "MONDAY", tDAY, 1 },
+ { "TUESDAY", tDAY, 2 },
+ { "TUES", tDAY, 2 },
+ { "WEDNESDAY",tDAY, 3 },
+ { "WEDNES", tDAY, 3 },
+ { "THURSDAY", tDAY, 4 },
+ { "THUR", tDAY, 4 },
+ { "THURS", tDAY, 4 },
+ { "FRIDAY", tDAY, 5 },
+ { "SATURDAY", tDAY, 6 },
+ { NULL, 0, 0 }
+};
+
+static table const time_units_table[] = {
+ { "YEAR", tYEAR_UNIT, 1 },
+ { "MONTH", tMONTH_UNIT, 1 },
+ { "FORTNIGHT",tDAY_UNIT, 14 },
+ { "WEEK", tDAY_UNIT, 7 },
+ { "DAY", tDAY_UNIT, 1 },
+ { "HOUR", tHOUR_UNIT, 1 },
+ { "MINUTE", tMINUTE_UNIT, 1 },
+ { "MIN", tMINUTE_UNIT, 1 },
+ { "SECOND", tSEC_UNIT, 1 },
+ { "SEC", tSEC_UNIT, 1 },
+ { NULL, 0, 0 }
+};
+
+/* Assorted relative-time words. */
+static table const relative_time_table[] = {
+ { "TOMORROW", tDAY_SHIFT, 1 },
+ { "YESTERDAY",tDAY_SHIFT, -1 },
+ { "TODAY", tDAY_SHIFT, 0 },
+ { "NOW", tDAY_SHIFT, 0 },
+ { "LAST", tORDINAL, -1 },
+ { "THIS", tORDINAL, 0 },
+ { "NEXT", tORDINAL, 1 },
+ { "FIRST", tORDINAL, 1 },
+ /*{ "SECOND", tORDINAL, 2 }, */
+ { "THIRD", tORDINAL, 3 },
+ { "FOURTH", tORDINAL, 4 },
+ { "FIFTH", tORDINAL, 5 },
+ { "SIXTH", tORDINAL, 6 },
+ { "SEVENTH", tORDINAL, 7 },
+ { "EIGHTH", tORDINAL, 8 },
+ { "NINTH", tORDINAL, 9 },
+ { "TENTH", tORDINAL, 10 },
+ { "ELEVENTH", tORDINAL, 11 },
+ { "TWELFTH", tORDINAL, 12 },
+ { "AGO", tAGO, -1 },
+ { "HENCE", tAGO, 1 },
+ { NULL, 0, 0 }
+};
+
+/**
+ * The universal time zone table. These labels can be used even for
+ * timestamps that would not otherwise be valid, e.g., GMT timestamps
+ * in London during summer.
+ */
+static table const universal_time_zone_table[] = {
+ { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
+ { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
+ { "UTC", tZONE, HOUR ( 0) },
+ { NULL, 0, 0 }
+};
+
+/**
+ * The time zone table. This table is necessarily incomplete, as time
+ * zone abbreviations are ambiguous; e.g. Australians interpret "EST"
+ * as Eastern time in Australia, not as US Eastern Standard Time.
+ * You cannot rely on parse_date to handle arbitrary time zone
+ * abbreviations; use numeric abbreviations like "-0500" instead.
+ */
+static table const time_zone_table[] = {
+ { "WET", tZONE, HOUR ( 0) }, /* Western European */
+ { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
+ { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
+ { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
+ { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
+ { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
+ { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
+ { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
+ { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
+ { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
+ { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
+ { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
+ { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
+ { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
+ { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
+ { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
+ { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
+ { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
+ { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
+ { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
+ { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
+ { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
+ { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
+ { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
+ { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
+ { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
+ { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
+ { "CET", tZONE, HOUR ( 1) }, /* Central European */
+ { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
+ { "MET", tZONE, HOUR ( 1) }, /* Middle European */
+ { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
+ { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
+ { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
+ { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
+ { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
+ { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
+ { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
+ { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
+ { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
+ { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
+ { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
+ { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
+ { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
+ { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
+ { "GST", tZONE, HOUR (10) }, /* Guam Standard */
+ { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
+ { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
+ { NULL, 0, 0 }
+};
+
+/**
+ * Military time zone table.
+ *
+ * Note 'T' is a special case, as it is used as the separator in ISO
+ * 8601 date and time of day representation.
+ */
+static table const military_table[] = {
+ { "A", tZONE, -HOUR ( 1) },
+ { "B", tZONE, -HOUR ( 2) },
+ { "C", tZONE, -HOUR ( 3) },
+ { "D", tZONE, -HOUR ( 4) },
+ { "E", tZONE, -HOUR ( 5) },
+ { "F", tZONE, -HOUR ( 6) },
+ { "G", tZONE, -HOUR ( 7) },
+ { "H", tZONE, -HOUR ( 8) },
+ { "I", tZONE, -HOUR ( 9) },
+ { "K", tZONE, -HOUR (10) },
+ { "L", tZONE, -HOUR (11) },
+ { "M", tZONE, -HOUR (12) },
+ { "N", tZONE, HOUR ( 1) },
+ { "O", tZONE, HOUR ( 2) },
+ { "P", tZONE, HOUR ( 3) },
+ { "Q", tZONE, HOUR ( 4) },
+ { "R", tZONE, HOUR ( 5) },
+ { "S", tZONE, HOUR ( 6) },
+ { "T", 'T', 0 },
+ { "U", tZONE, HOUR ( 8) },
+ { "V", tZONE, HOUR ( 9) },
+ { "W", tZONE, HOUR (10) },
+ { "X", tZONE, HOUR (11) },
+ { "Y", tZONE, HOUR (12) },
+ { "Z", tZONE, HOUR ( 0) },
+ { NULL, 0, 0 }
+};
+
+/**
+ * Convert a time offset expressed as HH:MM or HHMM into an integer count of
+ * minutes. If hh is more than 2 digits then it is of the form HHMM and must be
+ * delimited; in that case 'mm' is required to be absent. Otherwise, hh and mm
+ * are used ('mm' contains digits that were prefixed with a colon).
+ *
+ * POSIX TZ and ISO 8601 both define the maximum offset as 24:59. POSIX also
+ * allows seconds, but currently the parser rejects them. Both require minutes
+ * to be zero padded (2 digits). ISO requires hours to be zero padded, POSIX
+ * does not, either is accepted; which means an invalid ISO offset could pass.
+ */
+
+static int time_zone_hhmm(parser_control *pc, textint hh, textint mm)
+{
+ int h, m;
+
+ if (hh.digits > 2 && hh.digits < 5 && mm.digits == 0) {
+ h = hh.value / 100;
+ m = hh.value % 100;
+ } else if (hh.digits < 3 && (mm.digits == 0 || mm.digits == 2)) {
+ h = hh.value;
+ m = hh.negative ? -mm.value : mm.value;
+ } else
+ return 0;
+
+ if (abs(h) > 24 || abs(m) > 59)
+ return 0;
+
+ pc->time_zone = h * 60 + m;
+ return 1;
+}
+
+static int to_hour(intmax_t hours, int meridian)
+{
+ switch (meridian) {
+ default: /* Pacify GCC. */
+ case MER24:
+ return 0 <= hours && hours < 24 ? hours : -1;
+ case MERam:
+ return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
+ case MERpm:
+ return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
+ }
+}
+
+static long int to_year(textint textyear)
+{
+ intmax_t year = textyear.value;
+
+ if (year < 0)
+ year = -year;
+
+ /**
+ * XPG4 suggests that years 00-68 map to 2000-2068, and
+ * years 69-99 map to 1969-1999.
+ */
+ else if (textyear.digits == 2)
+ year += year < 69 ? 2000 : 1900;
+
+ return year;
+}
+
+static table const * lookup_zone(parser_control const *pc, char const *name)
+{
+ table const *tp;
+
+ for (tp = universal_time_zone_table; tp->name; tp++)
+ if (strcmp (name, tp->name) == 0)
+ return tp;
+
+ /**
+ * Try local zone abbreviations before those in time_zone_table, as
+ * the local ones are more likely to be right.
+ */
+ for (tp = pc->local_time_zone_table; tp->name; tp++)
+ if (strcmp (name, tp->name) == 0)
+ return tp;
+
+ for (tp = time_zone_table; tp->name; tp++)
+ if (strcmp (name, tp->name) == 0)
+ return tp;
+
+ return NULL;
+}
+
+#if ! HAVE_TM_GMTOFF
+/**
+ * Yield the difference between *A and *B,
+ * measured in seconds, ignoring leap seconds.
+ * The body of this function is taken directly from the GNU C Library;
+ * see src/strftime.c.
+ */
+static int tm_diff(struct tm const *a, struct tm const *b)
+{
+ /**
+ * Compute intervening leap days correctly even if year is negative.
+ * Take care to avoid int overflow in leap day calculations.
+ */
+ int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
+ int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
+ int a100 = a4 / 25 - (a4 % 25 < 0);
+ int b100 = b4 / 25 - (b4 % 25 < 0);
+ int a400 = SHR (a100, 2);
+ int b400 = SHR (b100, 2);
+ int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
+ int years = a->tm_year - b->tm_year;
+ int days = (365 * years + intervening_leap_days
+ + (a->tm_yday - b->tm_yday));
+ return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
+ + (a->tm_min - b->tm_min))
+ + (a->tm_sec - b->tm_sec));
+}
+#endif /* ! HAVE_TM_GMTOFF */
+
+static table const * lookup_word(parser_control const *pc, char *word)
+{
+ char *p;
+ char *q;
+ size_t wordlen;
+ table const *tp;
+ int period_found;
+ int abbrev;
+
+ /* Make it uppercase. */
+ for (p = word; *p; p++)
+ *p = c_toupper (to_uchar (*p));
+
+ for (tp = meridian_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* See if we have an abbreviation for a month. */
+ wordlen = strlen (word);
+ abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
+
+ for (tp = month_and_day_table; tp->name; tp++)
+ if ((abbrev ? strncmp (word, tp->name, 3) :
+ strcmp (word, tp->name)) == 0)
+ return tp;
+
+ if ((tp = lookup_zone (pc, word)))
+ return tp;
+
+ if (strcmp (word, dst_table[0].name) == 0)
+ return dst_table;
+
+ for (tp = time_units_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* Strip off any plural and try the units table again. */
+ if (word[wordlen - 1] == 'S') {
+ word[wordlen - 1] = '\0';
+ for (tp = time_units_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+ word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
+ }
+
+ for (tp = relative_time_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* Military time zones. */
+ if (wordlen == 1)
+ for (tp = military_table; tp->name; tp++)
+ if (word[0] == tp->name[0])
+ return tp;
+
+ /* Drop out any periods and try the time zone table again. */
+ for (period_found = 0, p = q = word; (*p = *q); q++)
+ if (*q == '.')
+ period_found = 1;
+ else
+ p++;
+ if (period_found && (tp = lookup_zone (pc, word)))
+ return tp;
+
+ return NULL;
+}
+
+static int yylex (union YYSTYPE *lvalp, parser_control *pc)
+{
+ unsigned char c;
+ size_t count;
+
+ for (;;) {
+ while (c = *pc->input, c_isspace (c))
+ pc->input++;
+
+ if (c_isdigit (c) || c == '-' || c == '+') {
+ char const *p;
+ int sign;
+ uintmax_t value;
+ if (c == '-' || c == '+') {
+ sign = c == '-' ? -1 : 1;
+ while (c = *++pc->input, c_isspace (c))
+ continue;
+ if (! c_isdigit (c))
+ /* skip the '-' sign */
+ continue;
+ } else
+ sign = 0;
+ p = pc->input;
+ for (value = 0; ; value *= 10) {
+ uintmax_t value1 = value + (c - '0');
+ if (value1 < value)
+ return '?';
+ value = value1;
+ c = *++p;
+ if (! c_isdigit (c))
+ break;
+ if (UINTMAX_MAX / 10 < value)
+ return '?';
+ }
+ if ((c == '.' || c == ',') && c_isdigit (p[1])) {
+ time_t s;
+ int ns;
+ int digits;
+ uintmax_t value1;
+
+ /* Check for overflow when converting value to
+ * time_t.
+ */
+ if (sign < 0) {
+ s = - value;
+ if (0 < s)
+ return '?';
+ value1 = -s;
+ } else {
+ s = value;
+ if (s < 0)
+ return '?';
+ value1 = s;
+ }
+ if (value != value1)
+ return '?';
+
+ /* Accumulate fraction, to ns precision. */
+ p++;
+ ns = *p++ - '0';
+ for (digits = 2;
+ digits <= LOG10_BILLION; digits++) {
+ ns *= 10;
+ if (c_isdigit (*p))
+ ns += *p++ - '0';
+ }
+
+ /* Skip excess digits, truncating toward
+ * -Infinity.
+ */
+ if (sign < 0)
+ for (; c_isdigit (*p); p++)
+ if (*p != '0') {
+ ns++;
+ break;
+ }
+ while (c_isdigit (*p))
+ p++;
+
+ /* Adjust to the timespec convention, which is
+ * that tv_nsec is always a positive offset even
+ * if tv_sec is negative.
+ */
+ if (sign < 0 && ns) {
+ s--;
+ if (! (s < 0))
+ return '?';
+ ns = BILLION - ns;
+ }
+
+ lvalp->timespec.tv_sec = s;
+ lvalp->timespec.tv_nsec = ns;
+ pc->input = p;
+ return
+ sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
+ } else {
+ lvalp->textintval.negative = sign < 0;
+ if (sign < 0) {
+ lvalp->textintval.value = - value;
+ if (0 < lvalp->textintval.value)
+ return '?';
+ } else {
+ lvalp->textintval.value = value;
+ if (lvalp->textintval.value < 0)
+ return '?';
+ }
+ lvalp->textintval.digits = p - pc->input;
+ pc->input = p;
+ return sign ? tSNUMBER : tUNUMBER;
+ }
+ }
+
+ if (c_isalpha (c)) {
+ char buff[20];
+ char *p = buff;
+ table const *tp;
+
+ do {
+ if (p < buff + sizeof buff - 1)
+ *p++ = c;
+ c = *++pc->input;
+ }
+ while (c_isalpha (c) || c == '.');
+
+ *p = '\0';
+ tp = lookup_word (pc, buff);
+ if (! tp) {
+ return '?';
+ }
+ lvalp->intval = tp->value;
+ return tp->type;
+ }
+
+ if (c != '(')
+ return to_uchar (*pc->input++);
+
+ count = 0;
+ do {
+ c = *pc->input++;
+ if (c == '\0')
+ return c;
+ if (c == '(')
+ count++;
+ else if (c == ')')
+ count--;
+ }
+ while (count != 0);
+ }
+}
+
+/* Do nothing if the parser reports an error. */
+static int yyerror(parser_control const *pc __attribute__((__unused__)),
+ char const *s __attribute__((__unused__)))
+{
+ return 0;
+}
+
+/**
+ * If *TM0 is the old and *TM1 is the new value of a struct tm after
+ * passing it to mktime, return 1 if it's OK that mktime returned T.
+ * It's not OK if *TM0 has out-of-range members.
+ */
+
+static int mktime_ok(struct tm const *tm0, struct tm const *tm1, time_t t)
+{
+ if (t == (time_t) -1) {
+ /**
+ * Guard against falsely reporting an error when parsing a
+ * timestamp that happens to equal (time_t) -1, on a host that
+ * supports such a timestamp.
+ */
+ tm1 = localtime (&t);
+ if (!tm1)
+ return 0;
+ }
+
+ return ! ((tm0->tm_sec ^ tm1->tm_sec)
+ | (tm0->tm_min ^ tm1->tm_min)
+ | (tm0->tm_hour ^ tm1->tm_hour)
+ | (tm0->tm_mday ^ tm1->tm_mday)
+ | (tm0->tm_mon ^ tm1->tm_mon)
+ | (tm0->tm_year ^ tm1->tm_year));
+}
+
+/**
+ * A reasonable upper bound for the size of ordinary TZ strings.
+ * Use heap allocation if TZ's length exceeds this.
+ */
+enum { TZBUFSIZE = 100 };
+
+/**
+ * Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
+ * otherwise.
+ */
+static char * get_tz(char tzbuf[TZBUFSIZE])
+{
+ char *tz = getenv ("TZ");
+ if (tz) {
+ size_t tzsize = strlen (tz) + 1;
+ tz = (tzsize <= TZBUFSIZE
+ ? memcpy (tzbuf, tz, tzsize)
+ : strdup (tz));
+ }
+ return tz;
+}
+
+/**
+ * Parse a date/time string, storing the resulting time value into *result.
+ * The string itself is pointed to by *p. Return 1 if successful.
+ * *p can be an incomplete or relative time specification; if so, use
+ * *now as the basis for the returned time.
+ */
+int parse_date(struct timespec *result, char const *p,
+ struct timespec const *now)
+{
+ time_t Start;
+ intmax_t Start_ns;
+ struct tm const *tmp;
+ struct tm tm;
+ struct tm tm0;
+ parser_control pc;
+ struct timespec gettime_buffer;
+ unsigned char c;
+ int tz_was_altered = 0;
+ char *tz0 = NULL;
+ char tz0buf[TZBUFSIZE];
+ int ok = 1;
+ struct timeval tv;
+
+ if (! now) {
+ gettimeofday (&tv, NULL);
+ gettime_buffer.tv_sec = tv.tv_sec;
+ gettime_buffer.tv_nsec = tv.tv_usec * 1000;
+ now = &gettime_buffer;
+ }
+
+ Start = now->tv_sec;
+ Start_ns = now->tv_nsec;
+
+ tmp = localtime (&now->tv_sec);
+ if (! tmp)
+ return 0;
+
+ while (c = *p, c_isspace (c))
+ p++;
+
+ if (strncmp (p, "TZ=\"", 4) == 0) {
+ char const *tzbase = p + 4;
+ size_t tzsize = 1;
+ char const *s;
+
+ for (s = tzbase; *s; s++, tzsize++)
+ if (*s == '\\') {
+ s++;
+ if (! (*s == '\\' || *s == '"'))
+ break;
+ } else if (*s == '"') {
+ char *z;
+ char *tz1;
+ char tz1buf[TZBUFSIZE];
+ int large_tz = TZBUFSIZE < tzsize;
+ int setenv_ok;
+
+ tz0 = get_tz (tz0buf);
+ if (!tz0)
+ goto fail;
+
+ if (large_tz) {
+ z = tz1 = malloc (tzsize);
+ if (!tz1)
+ goto fail;
+ } else
+ z = tz1 = tz1buf;
+
+ for (s = tzbase; *s != '"'; s++)
+ *z++ = *(s += *s == '\\');
+ *z = '\0';
+ setenv_ok = setenv ("TZ", tz1, 1) == 0;
+ if (large_tz)
+ free (tz1);
+ if (!setenv_ok)
+ goto fail;
+ tz_was_altered = 1;
+
+ p = s + 1;
+ while (c = *p, c_isspace (c))
+ p++;
+
+ break;
+ }
+ }
+
+ /**
+ * As documented, be careful to treat the empty string just like
+ * a date string of "0". Without this, an empty string would be
+ * declared invalid when parsed during a DST transition.
+ */
+ if (*p == '\0')
+ p = "0";
+
+ pc.input = p;
+ pc.year.value = tmp->tm_year;
+ pc.year.value += TM_YEAR_BASE;
+ pc.year.digits = 0;
+ pc.month = tmp->tm_mon + 1;
+ pc.day = tmp->tm_mday;
+ pc.hour = tmp->tm_hour;
+ pc.minutes = tmp->tm_min;
+ pc.seconds.tv_sec = tmp->tm_sec;
+ pc.seconds.tv_nsec = Start_ns;
+ tm.tm_isdst = tmp->tm_isdst;
+
+ pc.meridian = MER24;
+ pc.rel = RELATIVE_TIME_0;
+ pc.timespec_seen = 0;
+ pc.rels_seen = 0;
+ pc.dates_seen = 0;
+ pc.days_seen = 0;
+ pc.times_seen = 0;
+ pc.local_zones_seen = 0;
+ pc.dsts_seen = 0;
+ pc.zones_seen = 0;
+
+#if HAVE_STRUCT_TM_TM_ZONE
+ pc.local_time_zone_table[0].name = tmp->tm_zone;
+ pc.local_time_zone_table[0].type = tLOCAL_ZONE;
+ pc.local_time_zone_table[0].value = tmp->tm_isdst;
+ pc.local_time_zone_table[1].name = NULL;
+
+ /**
+ * Probe the names used in the next three calendar quarters, looking
+ * for a tm_isdst different from the one we already have.
+ */
+ {
+ int quarter;
+ for (quarter = 1; quarter <= 3; quarter++) {
+ time_t probe = Start + quarter * (90 * 24 * 60 * 60);
+ struct tm const *probe_tm = localtime (&probe);
+ if (probe_tm && probe_tm->tm_zone
+ && probe_tm->tm_isdst
+ != pc.local_time_zone_table[0].value) {
+ {
+ pc.local_time_zone_table[1].name
+ = probe_tm->tm_zone;
+ pc.local_time_zone_table[1].type
+ = tLOCAL_ZONE;
+ pc.local_time_zone_table[1].value
+ = probe_tm->tm_isdst;
+ pc.local_time_zone_table[2].name
+ = NULL;
+ }
+ break;
+ }
+ }
+ }
+#else
+#if HAVE_TZNAME
+ {
+# if !HAVE_DECL_TZNAME
+ extern char *tzname[];
+# endif
+ int i;
+ for (i = 0; i < 2; i++) {
+ pc.local_time_zone_table[i].name = tzname[i];
+ pc.local_time_zone_table[i].type = tLOCAL_ZONE;
+ pc.local_time_zone_table[i].value = i;
+ }
+ pc.local_time_zone_table[i].name = NULL;
+ }
+#else
+ pc.local_time_zone_table[0].name = NULL;
+#endif
+#endif
+
+ if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
+ && ! strcmp (pc.local_time_zone_table[0].name,
+ pc.local_time_zone_table[1].name)) {
+ /**
+ * This locale uses the same abbreviation for standard and
+ * daylight times. So if we see that abbreviation, we don't
+ * know whether it's daylight time.
+ */
+ pc.local_time_zone_table[0].value = -1;
+ pc.local_time_zone_table[1].name = NULL;
+ }
+
+ if (yyparse (&pc) != 0) {
+ goto fail;
+ }
+
+ if (pc.timespec_seen)
+ *result = pc.seconds;
+ else {
+ if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen
+ | pc.dsts_seen
+ | (pc.local_zones_seen + pc.zones_seen))) {
+ goto fail;
+ }
+
+ tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
+ tm.tm_mon = pc.month - 1;
+ tm.tm_mday = pc.day;
+ if (pc.times_seen || (pc.rels_seen &&
+ ! pc.dates_seen && ! pc.days_seen)) {
+ tm.tm_hour = to_hour (pc.hour, pc.meridian);
+ if (tm.tm_hour < 0) {
+ goto fail;
+ }
+ tm.tm_min = pc.minutes;
+ tm.tm_sec = pc.seconds.tv_sec;
+ } else {
+ tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
+ pc.seconds.tv_nsec = 0;
+ }
+
+ /**
+ * Let mktime deduce tm_isdst if we have an absolute timestamp.
+ */
+ if (pc.dates_seen | pc.days_seen | pc.times_seen)
+ tm.tm_isdst = -1;
+
+ /**
+ * But if the input explicitly specifies local time with or
+ * without DST, give mktime that information.
+ */
+ if (pc.local_zones_seen)
+ tm.tm_isdst = pc.local_isdst;
+
+ tm0 = tm;
+
+ Start = mktime (&tm);
+
+ if (! mktime_ok (&tm0, &tm, Start)) {
+ if (! pc.zones_seen) {
+ goto fail;
+ } else {
+ /** Guard against falsely reporting errors near
+ * the time_t boundaries when parsing times in
+ * other time zones. For example, suppose the
+ * input string "1969-12-31 23:00:00 -0100", the
+ * current time zone is 8 hours ahead of UTC,
+ * and the min time_t value is 1970-01-01
+ * 00:00:00 UTC. Then the min localtime value
+ * is 1970-01-01 08:00:00, and mktime will
+ * therefore fail on 1969-12-31 23:00:00. To
+ * work around the problem, set the time zone to
+ * 1 hour behind UTC temporarily by setting
+ * TZ="XXX1:00" and try mktime again.
+ */
+
+ intmax_t time_zone = pc.time_zone;
+
+ intmax_t abs_time_zone = time_zone < 0
+ ? - time_zone : time_zone;
+
+ intmax_t abs_time_zone_hour
+ = abs_time_zone / 60;
+
+ int abs_time_zone_min = abs_time_zone % 60;
+
+ char tz1buf[sizeof "XXX+0:00"
+ + sizeof pc.time_zone
+ * CHAR_BIT / 3];
+
+ if (!tz_was_altered)
+ tz0 = get_tz (tz0buf);
+ sprintf (tz1buf, "XXX%s%jd:%02d",
+ &"-"[time_zone < 0],
+ abs_time_zone_hour,
+ abs_time_zone_min);
+ if (setenv ("TZ", tz1buf, 1) != 0) {
+ goto fail;
+ }
+ tz_was_altered = 1;
+ tm = tm0;
+ Start = mktime (&tm);
+ if (! mktime_ok (&tm0, &tm, Start)) {
+ goto fail;
+ }
+ }
+ }
+
+ if (pc.days_seen && ! pc.dates_seen) {
+ tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7 + 7
+ * (pc.day_ordinal
+ - (0 < pc.day_ordinal
+ && tm.tm_wday != pc.day_number)));
+ tm.tm_isdst = -1;
+ Start = mktime (&tm);
+ if (Start == (time_t) -1) {
+ goto fail;
+ }
+ }
+ /* Add relative date. */
+ if (pc.rel.year | pc.rel.month | pc.rel.day) {
+ int year = tm.tm_year + pc.rel.year;
+ int month = tm.tm_mon + pc.rel.month;
+ int day = tm.tm_mday + pc.rel.day;
+ if (((year < tm.tm_year) ^ (pc.rel.year < 0))
+ | ((month < tm.tm_mon) ^ (pc.rel.month < 0))
+ | ((day < tm.tm_mday) ^ (pc.rel.day < 0))) {
+ goto fail;
+ }
+ tm.tm_year = year;
+ tm.tm_mon = month;
+ tm.tm_mday = day;
+ tm.tm_hour = tm0.tm_hour;
+ tm.tm_min = tm0.tm_min;
+ tm.tm_sec = tm0.tm_sec;
+ tm.tm_isdst = tm0.tm_isdst;
+ Start = mktime (&tm);
+ if (Start == (time_t) -1) {
+ goto fail;
+ }
+ }
+
+ /**
+ * The only "output" of this if-block is an updated Start value,
+ * so this block must follow others that clobber Start.
+ */
+ if (pc.zones_seen) {
+ intmax_t delta = pc.time_zone * 60;
+ time_t t1;
+#ifdef HAVE_TM_GMTOFF
+ delta -= tm.tm_gmtoff;
+#else
+ time_t t = Start;
+ struct tm const *gmt = gmtime (&t);
+ if (! gmt) {
+ goto fail;
+ }
+ delta -= tm_diff (&tm, gmt);
+#endif
+ t1 = Start - delta;
+ if ((Start < t1) != (delta < 0)) {
+ goto fail; /* time_t overflow */
+ }
+ Start = t1;
+ }
+
+ /**
+ * Add relative hours, minutes, and seconds. On hosts that
+ * support leap seconds, ignore the possibility of leap seconds;
+ * e.g., "+ 10 minutes" adds 600 seconds, even if one of them is
+ * a leap second. Typically this is not what the user wants,
+ * but it's too hard to do it the other way, because the time
+ * zone indicator must be applied before relative times, and if
+ * mktime is applied again the time zone will be lost.
+ */
+ intmax_t sum_ns = pc.seconds.tv_nsec + pc.rel.ns;
+ intmax_t normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
+ time_t t0 = Start;
+ intmax_t d1 = 60 * 60 * pc.rel.hour;
+ time_t t1 = t0 + d1;
+ intmax_t d2 = 60 * pc.rel.minutes;
+ time_t t2 = t1 + d2;
+ time_t d3 = pc.rel.seconds;
+ time_t t3 = t2 + d3;
+ intmax_t d4 = (sum_ns - normalized_ns) / BILLION;
+ time_t t4 = t3 + d4;
+ time_t t5 = t4;
+
+ if ((d1 / (60 * 60) ^ pc.rel.hour)
+ | (d2 / 60 ^ pc.rel.minutes)
+ | ((t1 < t0) ^ (d1 < 0))
+ | ((t2 < t1) ^ (d2 < 0))
+ | ((t3 < t2) ^ (d3 < 0))
+ | ((t4 < t3) ^ (d4 < 0))
+ | (t5 != t4)) {
+ goto fail;
+ }
+ result->tv_sec = t5;
+ result->tv_nsec = normalized_ns;
+ }
+
+ goto done;
+
+ fail:
+ ok = 0;
+ done:
+ if (tz_was_altered)
+ ok &= (tz0 ? setenv ("TZ", tz0, 1)
+ : unsetenv ("TZ")) == 0;
+ if (tz0 != tz0buf)
+ free (tz0);
+ return ok;
+}
diff --git a/lib/parse-date.y b/lib/parse-date.y
new file mode 100644
index 0000000..bb5abb5
--- /dev/null
+++ b/lib/parse-date.y
@@ -0,0 +1,1626 @@
+%{
+/**
+ * Parse a string into an internal timestamp.
+ *
+ * This file is based on gnulib parse-datetime.y-dd7a871 with
+ * the other gnulib dependencies removed for use in util-linux.
+ *
+ * Copyright (C) 1999-2000, 2002-2017 Free Software Foundation, Inc.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Originally written by Steven M. Bellovin <smb@research.att.com> while
+ * at the University of North Carolina at Chapel Hill. Later tweaked by
+ * a couple of people on Usenet. Completely overhauled by Rich $alz
+ * <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
+ *
+ * Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
+ * the right thing about local DST. Also modified by Paul Eggert
+ * <eggert@cs.ucla.edu> in February 2004 to support
+ * nanosecond-resolution timestamps, and in October 2004 to support
+ * TZ strings in dates.
+ */
+
+/**
+ * FIXME: Check for arithmetic overflow in all cases, not just
+ * some of them.
+ */
+
+#include <sys/time.h>
+#include <time.h>
+
+#include "c.h"
+#include "timeutils.h"
+
+/**
+ * There's no need to extend the stack, so there's no need to involve
+ * alloca.
+ */
+#define YYSTACK_USE_ALLOCA 0
+
+/**
+ * Tell Bison how much stack space is needed. 20 should be plenty for
+ * this grammar, which is not right recursive. Beware setting it too
+ * high, since that might cause problems on machines whose
+ * implementations have lame stack-overflow checking.
+ */
+#define YYMAXDEPTH 20
+#define YYINITDEPTH YYMAXDEPTH
+
+/**
+ * Since the code of parse-datetime.y is not included in the Emacs executable
+ * itself, there is no need to #define static in this file. Even if
+ * the code were included in the Emacs executable, it probably
+ * wouldn't do any harm to #undef it here; this will only cause
+ * problems if we try to write to a static variable, which I don't
+ * think this code needs to do.
+ */
+#ifdef emacs
+# undef static
+#endif
+
+#include <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+#include <stdarg.h>
+#include "cctype.h"
+#include "nls.h"
+
+/**
+ * Bison's skeleton tests _STDLIB_H, while some stdlib.h headers
+ * use _STDLIB_H_ as witness. Map the latter to the one bison uses.
+ * FIXME: this is temporary. Remove when we have a mechanism to ensure
+ * that the version we're using is fixed, too.
+ */
+#ifdef _STDLIB_H_
+# undef _STDLIB_H
+# define _STDLIB_H 1
+#endif
+
+/**
+ * Shift A right by B bits portably, by dividing A by 2**B and
+ * truncating towards minus infinity. A and B should be free of side
+ * effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
+ * INT_BITS is the number of useful bits in an int. GNU code can
+ * assume that INT_BITS is at least 32.
+ *
+ * ISO C99 says that A >> B is implementation-defined if A < 0. Some
+ * implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
+ * right in the usual way when A < 0, so SHR falls back on division if
+ * ordinary A >> B doesn't seem to be the usual signed shift.
+ */
+#define SHR(a, b) \
+ (-1 >> 1 == -1 \
+ ? (a) >> (b) \
+ : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
+
+#define TM_YEAR_BASE 1900
+
+#define HOUR(x) ((x) * 60)
+
+#define STREQ(a, b) (strcmp (a, b) == 0)
+
+/**
+ * Convert a possibly-signed character to an unsigned character. This is
+ * a bit safer than casting to unsigned char, since it catches some type
+ * errors that the cast doesn't.
+ */
+static unsigned char to_uchar (char ch) { return ch; }
+
+/**
+ * FIXME: It also assumes that signed integer overflow silently wraps around,
+ * but this is not true any more with recent versions of GCC 4.
+ */
+
+/**
+ * An integer value, and the number of digits in its textual
+ * representation.
+ */
+typedef struct {
+ int negative;
+ intmax_t value;
+ size_t digits;
+} textint;
+
+/* An entry in the lexical lookup table. */
+typedef struct {
+ char const *name;
+ int type;
+ int value;
+} table;
+
+/* Meridian: am, pm, or 24-hour style. */
+enum { MERam, MERpm, MER24 };
+
+enum { BILLION = 1000000000, LOG10_BILLION = 9 };
+
+/* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
+typedef struct {
+ intmax_t year;
+ intmax_t month;
+ intmax_t day;
+ intmax_t hour;
+ intmax_t minutes;
+ time_t seconds;
+ int ns;
+} relative_time;
+
+#if HAVE_COMPOUND_LITERALS
+# define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
+#else
+static relative_time const RELATIVE_TIME_0;
+#endif
+
+/* Information passed to and from the parser. */
+typedef struct {
+ /* The input string remaining to be parsed. */
+ const char *input;
+
+ /* N, if this is the Nth Tuesday. */
+ intmax_t day_ordinal;
+
+ /* Day of week; Sunday is 0. */
+ int day_number;
+
+ /* tm_isdst flag for the local zone. */
+ int local_isdst;
+
+ /* Time zone, in minutes east of UTC. */
+ int time_zone;
+
+ /* Style used for time. */
+ int meridian;
+
+ /* Gregorian year, month, day, hour, minutes, seconds, and ns. */
+ textint year;
+ intmax_t month;
+ intmax_t day;
+ intmax_t hour;
+ intmax_t minutes;
+ struct timespec seconds; /* includes nanoseconds */
+
+ /* Relative year, month, day, hour, minutes, seconds, and ns. */
+ relative_time rel;
+
+ /* Presence or counts of some nonterminals parsed so far. */
+ int timespec_seen;
+ int rels_seen;
+ size_t dates_seen;
+ size_t days_seen;
+ size_t local_zones_seen;
+ size_t dsts_seen;
+ size_t times_seen;
+ size_t zones_seen;
+
+ /* Table of local time zone abbreviations, null terminated. */
+ table local_time_zone_table[3];
+} parser_control;
+
+union YYSTYPE;
+static int yylex (union YYSTYPE *, parser_control *);
+static int yyerror (parser_control const *, char const *);
+static int time_zone_hhmm (parser_control *, textint, textint);
+
+/**
+ * Extract into *PC any date and time info from a string of digits
+ * of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
+ * YYYY, ...).
+ */
+static void digits_to_date_time(parser_control *pc, textint text_int)
+{
+ if (pc->dates_seen && ! pc->year.digits
+ && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits)) {
+ pc->year = text_int;
+ } else {
+ if (4 < text_int.digits) {
+ pc->dates_seen++;
+ pc->day = text_int.value % 100;
+ pc->month = (text_int.value / 100) % 100;
+ pc->year.value = text_int.value / 10000;
+ pc->year.digits = text_int.digits - 4;
+ } else {
+ pc->times_seen++;
+ if (text_int.digits <= 2) {
+ pc->hour = text_int.value;
+ pc->minutes = 0;
+ }
+ else {
+ pc->hour = text_int.value / 100;
+ pc->minutes = text_int.value % 100;
+ }
+ pc->seconds.tv_sec = 0;
+ pc->seconds.tv_nsec = 0;
+ pc->meridian = MER24;
+ }
+ }
+}
+
+/* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). */
+static void apply_relative_time(parser_control *pc, relative_time rel,
+ int factor)
+{
+ pc->rel.ns += factor * rel.ns;
+ pc->rel.seconds += factor * rel.seconds;
+ pc->rel.minutes += factor * rel.minutes;
+ pc->rel.hour += factor * rel.hour;
+ pc->rel.day += factor * rel.day;
+ pc->rel.month += factor * rel.month;
+ pc->rel.year += factor * rel.year;
+ pc->rels_seen = 1;
+}
+
+/* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
+static void
+set_hhmmss(parser_control *pc, intmax_t hour, intmax_t minutes,
+ time_t sec, int nsec)
+{
+ pc->hour = hour;
+ pc->minutes = minutes;
+ pc->seconds.tv_sec = sec;
+ pc->seconds.tv_nsec = nsec;
+}
+
+%}
+
+/**
+ * We want a reentrant parser, even if the TZ manipulation and the calls to
+ * localtime and gmtime are not reentrant.
+ */
+%pure-parser
+%parse-param { parser_control *pc }
+%lex-param { parser_control *pc }
+
+/* This grammar has 31 shift/reduce conflicts. */
+%expect 31
+
+%union {
+ intmax_t intval;
+ textint textintval;
+ struct timespec timespec;
+ relative_time rel;
+}
+
+%token <intval> tAGO
+%token tDST
+
+%token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
+%token <intval> tDAY_UNIT tDAY_SHIFT
+
+%token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
+%token <intval> tMONTH tORDINAL tZONE
+
+%token <textintval> tSNUMBER tUNUMBER
+%token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
+
+%type <textintval> o_colon_minutes
+%type <timespec> seconds signed_seconds unsigned_seconds
+
+%type <rel> relunit relunit_snumber dayshift
+
+%%
+
+spec:
+ timespec
+ | items
+;
+
+timespec:
+ '@' seconds {
+ pc->seconds = $2;
+ pc->timespec_seen = 1;
+ }
+;
+
+items:
+ /* empty */
+ | items item
+;
+
+item:
+ datetime {
+ pc->times_seen++; pc->dates_seen++;
+ }
+ | time {
+ pc->times_seen++;
+ }
+ | local_zone {
+ pc->local_zones_seen++;
+ }
+ | zone {
+ pc->zones_seen++;
+ }
+ | date {
+ pc->dates_seen++;
+ }
+ | day {
+ pc->days_seen++;
+ }
+ | rel
+ | number
+ | hybrid
+;
+
+datetime:
+ iso_8601_datetime
+;
+
+iso_8601_datetime:
+ iso_8601_date 'T' iso_8601_time
+;
+
+time:
+ tUNUMBER tMERIDIAN {
+ set_hhmmss (pc, $1.value, 0, 0, 0);
+ pc->meridian = $2;
+ }
+ | tUNUMBER ':' tUNUMBER tMERIDIAN {
+ set_hhmmss (pc, $1.value, $3.value, 0, 0);
+ pc->meridian = $4;
+ }
+ | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tMERIDIAN {
+ set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
+ pc->meridian = $6;
+ }
+ | iso_8601_time
+;
+
+iso_8601_time:
+ tUNUMBER zone_offset {
+ set_hhmmss (pc, $1.value, 0, 0, 0);
+ pc->meridian = MER24;
+ }
+ | tUNUMBER ':' tUNUMBER o_zone_offset {
+ set_hhmmss (pc, $1.value, $3.value, 0, 0);
+ pc->meridian = MER24;
+ }
+ | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_zone_offset {
+ set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
+ pc->meridian = MER24;
+ }
+;
+
+o_zone_offset:
+ /* empty */
+ | zone_offset
+;
+
+zone_offset:
+ tSNUMBER o_colon_minutes {
+ pc->zones_seen++;
+ if (! time_zone_hhmm (pc, $1, $2)) YYABORT;
+ }
+;
+
+/**
+ * Local zone strings only affect DST setting,
+ * and only take affect if the current TZ setting is relevant.
+ *
+ * Example 1:
+ * 'EEST' is parsed as tLOCAL_ZONE, as it relates to the effective TZ:
+ * TZ=Europe/Helsinki date -d '2016-12-30 EEST'
+ *
+ * Example 2:
+ * 'EEST' is parsed as 'zone' (TZ=+03:00):
+ * TZ=Asia/Tokyo ./src/date --debug -d '2011-06-11 EEST'
+ *
+ * This is implemented by probing the next three calendar quarters
+ * of the effective timezone and looking for DST changes -
+ * if found, the timezone name (EEST) is inserted into
+ * the lexical lookup table with type tLOCAL_ZONE.
+ * (Search for 'quarter' comment in 'parse_date').
+ */
+local_zone:
+ tLOCAL_ZONE {
+ pc->local_isdst = $1;
+ pc->dsts_seen += (0 < $1);
+ }
+ | tLOCAL_ZONE tDST {
+ pc->local_isdst = 1;
+ pc->dsts_seen += (0 < $1) + 1;
+ }
+;
+
+/**
+ * Note 'T' is a special case, as it is used as the separator in ISO
+ * 8601 date and time of day representation.
+ */
+zone:
+ tZONE {
+ pc->time_zone = $1;
+ }
+ | 'T' {
+ pc->time_zone = HOUR(7);
+ }
+ | tZONE relunit_snumber {
+ pc->time_zone = $1;
+ apply_relative_time (pc, $2, 1);
+ }
+ | 'T' relunit_snumber {
+ pc->time_zone = HOUR(7);
+ apply_relative_time (pc, $2, 1);
+ }
+ | tZONE tSNUMBER o_colon_minutes {
+ if (! time_zone_hhmm (pc, $2, $3)) YYABORT;
+ pc->time_zone += $1;
+ }
+ | tDAYZONE {
+ pc->time_zone = $1 + 60;
+ }
+ | tZONE tDST {
+ pc->time_zone = $1 + 60;
+ }
+;
+
+day:
+ tDAY {
+ pc->day_ordinal = 0;
+ pc->day_number = $1;
+ }
+ | tDAY ',' {
+ pc->day_ordinal = 0;
+ pc->day_number = $1;
+ }
+ | tORDINAL tDAY {
+ pc->day_ordinal = $1;
+ pc->day_number = $2;
+ }
+ | tUNUMBER tDAY {
+ pc->day_ordinal = $1.value;
+ pc->day_number = $2;
+ }
+;
+
+date:
+ tUNUMBER '/' tUNUMBER {
+ pc->month = $1.value;
+ pc->day = $3.value;
+ }
+ | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
+ /**
+ * Interpret as YYYY/MM/DD if the first value has 4 or more digits,
+ * otherwise as MM/DD/YY.
+ * The goal in recognizing YYYY/MM/DD is solely to support legacy
+ * machine-generated dates like those in an RCS log listing. If
+ * you want portability, use the ISO 8601 format.
+ */
+ if (4 <= $1.digits) {
+ pc->year = $1;
+ pc->month = $3.value;
+ pc->day = $5.value;
+ } else {
+ pc->month = $1.value;
+ pc->day = $3.value;
+ pc->year = $5;
+ }
+ }
+ | tUNUMBER tMONTH tSNUMBER {
+ /* e.g. 17-JUN-1992. */
+ pc->day = $1.value;
+ pc->month = $2;
+ pc->year.value = -$3.value;
+ pc->year.digits = $3.digits;
+ }
+ | tMONTH tSNUMBER tSNUMBER {
+ /* e.g. JUN-17-1992. */
+ pc->month = $1;
+ pc->day = -$2.value;
+ pc->year.value = -$3.value;
+ pc->year.digits = $3.digits;
+ }
+ | tMONTH tUNUMBER {
+ pc->month = $1;
+ pc->day = $2.value;
+ }
+ | tMONTH tUNUMBER ',' tUNUMBER {
+ pc->month = $1;
+ pc->day = $2.value;
+ pc->year = $4;
+ }
+ | tUNUMBER tMONTH {
+ pc->day = $1.value;
+ pc->month = $2;
+ }
+ | tUNUMBER tMONTH tUNUMBER {
+ pc->day = $1.value;
+ pc->month = $2;
+ pc->year = $3;
+ }
+ | iso_8601_date
+;
+
+iso_8601_date:
+ tUNUMBER tSNUMBER tSNUMBER {
+ /* ISO 8601 format.YYYY-MM-DD. */
+ pc->year = $1;
+ pc->month = -$2.value;
+ pc->day = -$3.value;
+ }
+;
+
+rel:
+ relunit tAGO
+ { apply_relative_time (pc, $1, $2); }
+ | relunit
+ { apply_relative_time (pc, $1, 1); }
+ | dayshift
+ { apply_relative_time (pc, $1, 1); }
+;
+
+relunit:
+ tORDINAL tYEAR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.year = $1; }
+ | tUNUMBER tYEAR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
+ | tYEAR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.year = 1; }
+ | tORDINAL tMONTH_UNIT
+ { $$ = RELATIVE_TIME_0; $$.month = $1; }
+ | tUNUMBER tMONTH_UNIT
+ { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
+ | tMONTH_UNIT
+ { $$ = RELATIVE_TIME_0; $$.month = 1; }
+ | tORDINAL tDAY_UNIT
+ { $$ = RELATIVE_TIME_0; $$.day = $1 * $2; }
+ | tUNUMBER tDAY_UNIT
+ { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
+ | tDAY_UNIT
+ { $$ = RELATIVE_TIME_0; $$.day = $1; }
+ | tORDINAL tHOUR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.hour = $1; }
+ | tUNUMBER tHOUR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
+ | tHOUR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.hour = 1; }
+ | tORDINAL tMINUTE_UNIT
+ { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
+ | tUNUMBER tMINUTE_UNIT
+ { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
+ | tMINUTE_UNIT
+ { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
+ | tORDINAL tSEC_UNIT
+ { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
+ | tUNUMBER tSEC_UNIT
+ { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
+ | tSDECIMAL_NUMBER tSEC_UNIT {
+ $$ = RELATIVE_TIME_0;
+ $$.seconds = $1.tv_sec;
+ $$.ns = $1.tv_nsec;
+ }
+ | tUDECIMAL_NUMBER tSEC_UNIT {
+ $$ = RELATIVE_TIME_0;
+ $$.seconds = $1.tv_sec;
+ $$.ns = $1.tv_nsec;
+ }
+ | tSEC_UNIT
+ { $$ = RELATIVE_TIME_0; $$.seconds = 1; }
+ | relunit_snumber
+;
+
+relunit_snumber:
+ tSNUMBER tYEAR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
+ | tSNUMBER tMONTH_UNIT
+ { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
+ | tSNUMBER tDAY_UNIT
+ { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
+ | tSNUMBER tHOUR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
+ | tSNUMBER tMINUTE_UNIT
+ { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
+ | tSNUMBER tSEC_UNIT
+ { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
+;
+
+dayshift:
+ tDAY_SHIFT
+ { $$ = RELATIVE_TIME_0; $$.day = $1; }
+;
+
+seconds: signed_seconds | unsigned_seconds;
+
+signed_seconds:
+ tSDECIMAL_NUMBER
+ | tSNUMBER
+ { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
+;
+
+unsigned_seconds:
+ tUDECIMAL_NUMBER
+ | tUNUMBER
+ { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
+;
+
+number:
+ tUNUMBER
+ { digits_to_date_time (pc, $1); }
+;
+
+hybrid:
+ tUNUMBER relunit_snumber {
+ /**
+ * Hybrid all-digit and relative offset, so that we accept e.g.,
+ * "YYYYMMDD +N days" as well as "YYYYMMDD N days".
+ */
+ digits_to_date_time (pc, $1);
+ apply_relative_time (pc, $2, 1);
+ }
+;
+
+o_colon_minutes:
+ /* empty */
+ { $$.value = $$.digits = 0; }
+ | ':' tUNUMBER {
+ $$ = $2;
+ }
+;
+
+%%
+
+static table const meridian_table[] = {
+ { "AM", tMERIDIAN, MERam },
+ { "A.M.", tMERIDIAN, MERam },
+ { "PM", tMERIDIAN, MERpm },
+ { "P.M.", tMERIDIAN, MERpm },
+ { NULL, 0, 0 }
+};
+
+static table const dst_table[] = {
+ { "DST", tDST, 0 }
+};
+
+static table const month_and_day_table[] = {
+ { "JANUARY", tMONTH, 1 },
+ { "FEBRUARY", tMONTH, 2 },
+ { "MARCH", tMONTH, 3 },
+ { "APRIL", tMONTH, 4 },
+ { "MAY", tMONTH, 5 },
+ { "JUNE", tMONTH, 6 },
+ { "JULY", tMONTH, 7 },
+ { "AUGUST", tMONTH, 8 },
+ { "SEPTEMBER",tMONTH, 9 },
+ { "SEPT", tMONTH, 9 },
+ { "OCTOBER", tMONTH, 10 },
+ { "NOVEMBER", tMONTH, 11 },
+ { "DECEMBER", tMONTH, 12 },
+ { "SUNDAY", tDAY, 0 },
+ { "MONDAY", tDAY, 1 },
+ { "TUESDAY", tDAY, 2 },
+ { "TUES", tDAY, 2 },
+ { "WEDNESDAY",tDAY, 3 },
+ { "WEDNES", tDAY, 3 },
+ { "THURSDAY", tDAY, 4 },
+ { "THUR", tDAY, 4 },
+ { "THURS", tDAY, 4 },
+ { "FRIDAY", tDAY, 5 },
+ { "SATURDAY", tDAY, 6 },
+ { NULL, 0, 0 }
+};
+
+static table const time_units_table[] = {
+ { "YEAR", tYEAR_UNIT, 1 },
+ { "MONTH", tMONTH_UNIT, 1 },
+ { "FORTNIGHT",tDAY_UNIT, 14 },
+ { "WEEK", tDAY_UNIT, 7 },
+ { "DAY", tDAY_UNIT, 1 },
+ { "HOUR", tHOUR_UNIT, 1 },
+ { "MINUTE", tMINUTE_UNIT, 1 },
+ { "MIN", tMINUTE_UNIT, 1 },
+ { "SECOND", tSEC_UNIT, 1 },
+ { "SEC", tSEC_UNIT, 1 },
+ { NULL, 0, 0 }
+};
+
+/* Assorted relative-time words. */
+static table const relative_time_table[] = {
+ { "TOMORROW", tDAY_SHIFT, 1 },
+ { "YESTERDAY",tDAY_SHIFT, -1 },
+ { "TODAY", tDAY_SHIFT, 0 },
+ { "NOW", tDAY_SHIFT, 0 },
+ { "LAST", tORDINAL, -1 },
+ { "THIS", tORDINAL, 0 },
+ { "NEXT", tORDINAL, 1 },
+ { "FIRST", tORDINAL, 1 },
+ /*{ "SECOND", tORDINAL, 2 }, */
+ { "THIRD", tORDINAL, 3 },
+ { "FOURTH", tORDINAL, 4 },
+ { "FIFTH", tORDINAL, 5 },
+ { "SIXTH", tORDINAL, 6 },
+ { "SEVENTH", tORDINAL, 7 },
+ { "EIGHTH", tORDINAL, 8 },
+ { "NINTH", tORDINAL, 9 },
+ { "TENTH", tORDINAL, 10 },
+ { "ELEVENTH", tORDINAL, 11 },
+ { "TWELFTH", tORDINAL, 12 },
+ { "AGO", tAGO, -1 },
+ { "HENCE", tAGO, 1 },
+ { NULL, 0, 0 }
+};
+
+/**
+ * The universal time zone table. These labels can be used even for
+ * timestamps that would not otherwise be valid, e.g., GMT timestamps
+ * in London during summer.
+ */
+static table const universal_time_zone_table[] = {
+ { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
+ { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
+ { "UTC", tZONE, HOUR ( 0) },
+ { NULL, 0, 0 }
+};
+
+/**
+ * The time zone table. This table is necessarily incomplete, as time
+ * zone abbreviations are ambiguous; e.g. Australians interpret "EST"
+ * as Eastern time in Australia, not as US Eastern Standard Time.
+ * You cannot rely on parse_date to handle arbitrary time zone
+ * abbreviations; use numeric abbreviations like "-0500" instead.
+ */
+static table const time_zone_table[] = {
+ { "WET", tZONE, HOUR ( 0) }, /* Western European */
+ { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
+ { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
+ { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
+ { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
+ { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
+ { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
+ { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
+ { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
+ { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
+ { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
+ { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
+ { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
+ { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
+ { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
+ { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
+ { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
+ { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
+ { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
+ { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
+ { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
+ { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
+ { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
+ { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
+ { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
+ { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
+ { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
+ { "CET", tZONE, HOUR ( 1) }, /* Central European */
+ { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
+ { "MET", tZONE, HOUR ( 1) }, /* Middle European */
+ { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
+ { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
+ { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
+ { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
+ { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
+ { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
+ { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
+ { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
+ { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
+ { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
+ { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
+ { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
+ { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
+ { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
+ { "GST", tZONE, HOUR (10) }, /* Guam Standard */
+ { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
+ { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
+ { NULL, 0, 0 }
+};
+
+/**
+ * Military time zone table.
+ *
+ * Note 'T' is a special case, as it is used as the separator in ISO
+ * 8601 date and time of day representation.
+ */
+static table const military_table[] = {
+ { "A", tZONE, -HOUR ( 1) },
+ { "B", tZONE, -HOUR ( 2) },
+ { "C", tZONE, -HOUR ( 3) },
+ { "D", tZONE, -HOUR ( 4) },
+ { "E", tZONE, -HOUR ( 5) },
+ { "F", tZONE, -HOUR ( 6) },
+ { "G", tZONE, -HOUR ( 7) },
+ { "H", tZONE, -HOUR ( 8) },
+ { "I", tZONE, -HOUR ( 9) },
+ { "K", tZONE, -HOUR (10) },
+ { "L", tZONE, -HOUR (11) },
+ { "M", tZONE, -HOUR (12) },
+ { "N", tZONE, HOUR ( 1) },
+ { "O", tZONE, HOUR ( 2) },
+ { "P", tZONE, HOUR ( 3) },
+ { "Q", tZONE, HOUR ( 4) },
+ { "R", tZONE, HOUR ( 5) },
+ { "S", tZONE, HOUR ( 6) },
+ { "T", 'T', 0 },
+ { "U", tZONE, HOUR ( 8) },
+ { "V", tZONE, HOUR ( 9) },
+ { "W", tZONE, HOUR (10) },
+ { "X", tZONE, HOUR (11) },
+ { "Y", tZONE, HOUR (12) },
+ { "Z", tZONE, HOUR ( 0) },
+ { NULL, 0, 0 }
+};
+
+/**
+ * Convert a time offset expressed as HH:MM or HHMM into an integer count of
+ * minutes. If hh is more than 2 digits then it is of the form HHMM and must be
+ * delimited; in that case 'mm' is required to be absent. Otherwise, hh and mm
+ * are used ('mm' contains digits that were prefixed with a colon).
+ *
+ * POSIX TZ and ISO 8601 both define the maximum offset as 24:59. POSIX also
+ * allows seconds, but currently the parser rejects them. Both require minutes
+ * to be zero padded (2 digits). ISO requires hours to be zero padded, POSIX
+ * does not, either is accepted; which means an invalid ISO offset could pass.
+ */
+
+static int time_zone_hhmm(parser_control *pc, textint hh, textint mm)
+{
+ int h, m;
+
+ if (hh.digits > 2 && hh.digits < 5 && mm.digits == 0) {
+ h = hh.value / 100;
+ m = hh.value % 100;
+ } else if (hh.digits < 3 && (mm.digits == 0 || mm.digits == 2)) {
+ h = hh.value;
+ m = hh.negative ? -mm.value : mm.value;
+ } else
+ return 0;
+
+ if (abs(h) > 24 || abs(m) > 59)
+ return 0;
+
+ pc->time_zone = h * 60 + m;
+ return 1;
+}
+
+static int to_hour(intmax_t hours, int meridian)
+{
+ switch (meridian) {
+ default: /* Pacify GCC. */
+ case MER24:
+ return 0 <= hours && hours < 24 ? hours : -1;
+ case MERam:
+ return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
+ case MERpm:
+ return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
+ }
+}
+
+static long int to_year(textint textyear)
+{
+ intmax_t year = textyear.value;
+
+ if (year < 0)
+ year = -year;
+
+ /**
+ * XPG4 suggests that years 00-68 map to 2000-2068, and
+ * years 69-99 map to 1969-1999.
+ */
+ else if (textyear.digits == 2)
+ year += year < 69 ? 2000 : 1900;
+
+ return year;
+}
+
+static table const * lookup_zone(parser_control const *pc, char const *name)
+{
+ table const *tp;
+
+ for (tp = universal_time_zone_table; tp->name; tp++)
+ if (strcmp (name, tp->name) == 0)
+ return tp;
+
+ /**
+ * Try local zone abbreviations before those in time_zone_table, as
+ * the local ones are more likely to be right.
+ */
+ for (tp = pc->local_time_zone_table; tp->name; tp++)
+ if (strcmp (name, tp->name) == 0)
+ return tp;
+
+ for (tp = time_zone_table; tp->name; tp++)
+ if (strcmp (name, tp->name) == 0)
+ return tp;
+
+ return NULL;
+}
+
+#if ! HAVE_TM_GMTOFF
+/**
+ * Yield the difference between *A and *B,
+ * measured in seconds, ignoring leap seconds.
+ * The body of this function is taken directly from the GNU C Library;
+ * see src/strftime.c.
+ */
+static int tm_diff(struct tm const *a, struct tm const *b)
+{
+ /**
+ * Compute intervening leap days correctly even if year is negative.
+ * Take care to avoid int overflow in leap day calculations.
+ */
+ int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
+ int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
+ int a100 = a4 / 25 - (a4 % 25 < 0);
+ int b100 = b4 / 25 - (b4 % 25 < 0);
+ int a400 = SHR (a100, 2);
+ int b400 = SHR (b100, 2);
+ int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
+ int years = a->tm_year - b->tm_year;
+ int days = (365 * years + intervening_leap_days
+ + (a->tm_yday - b->tm_yday));
+ return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
+ + (a->tm_min - b->tm_min))
+ + (a->tm_sec - b->tm_sec));
+}
+#endif /* ! HAVE_TM_GMTOFF */
+
+static table const * lookup_word(parser_control const *pc, char *word)
+{
+ char *p;
+ char *q;
+ size_t wordlen;
+ table const *tp;
+ int period_found;
+ int abbrev;
+
+ /* Make it uppercase. */
+ for (p = word; *p; p++)
+ *p = c_toupper (to_uchar (*p));
+
+ for (tp = meridian_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* See if we have an abbreviation for a month. */
+ wordlen = strlen (word);
+ abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
+
+ for (tp = month_and_day_table; tp->name; tp++)
+ if ((abbrev ? strncmp (word, tp->name, 3) :
+ strcmp (word, tp->name)) == 0)
+ return tp;
+
+ if ((tp = lookup_zone (pc, word)))
+ return tp;
+
+ if (strcmp (word, dst_table[0].name) == 0)
+ return dst_table;
+
+ for (tp = time_units_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* Strip off any plural and try the units table again. */
+ if (word[wordlen - 1] == 'S') {
+ word[wordlen - 1] = '\0';
+ for (tp = time_units_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+ word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
+ }
+
+ for (tp = relative_time_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* Military time zones. */
+ if (wordlen == 1)
+ for (tp = military_table; tp->name; tp++)
+ if (word[0] == tp->name[0])
+ return tp;
+
+ /* Drop out any periods and try the time zone table again. */
+ for (period_found = 0, p = q = word; (*p = *q); q++)
+ if (*q == '.')
+ period_found = 1;
+ else
+ p++;
+ if (period_found && (tp = lookup_zone (pc, word)))
+ return tp;
+
+ return NULL;
+}
+
+static int yylex (union YYSTYPE *lvalp, parser_control *pc)
+{
+ unsigned char c;
+ size_t count;
+
+ for (;;) {
+ while (c = *pc->input, c_isspace (c))
+ pc->input++;
+
+ if (c_isdigit (c) || c == '-' || c == '+') {
+ char const *p;
+ int sign;
+ uintmax_t value;
+ if (c == '-' || c == '+') {
+ sign = c == '-' ? -1 : 1;
+ while (c = *++pc->input, c_isspace (c))
+ continue;
+ if (! c_isdigit (c))
+ /* skip the '-' sign */
+ continue;
+ } else
+ sign = 0;
+ p = pc->input;
+ for (value = 0; ; value *= 10) {
+ uintmax_t value1 = value + (c - '0');
+ if (value1 < value)
+ return '?';
+ value = value1;
+ c = *++p;
+ if (! c_isdigit (c))
+ break;
+ if (UINTMAX_MAX / 10 < value)
+ return '?';
+ }
+ if ((c == '.' || c == ',') && c_isdigit (p[1])) {
+ time_t s;
+ int ns;
+ int digits;
+ uintmax_t value1;
+
+ /* Check for overflow when converting value to
+ * time_t.
+ */
+ if (sign < 0) {
+ s = - value;
+ if (0 < s)
+ return '?';
+ value1 = -s;
+ } else {
+ s = value;
+ if (s < 0)
+ return '?';
+ value1 = s;
+ }
+ if (value != value1)
+ return '?';
+
+ /* Accumulate fraction, to ns precision. */
+ p++;
+ ns = *p++ - '0';
+ for (digits = 2;
+ digits <= LOG10_BILLION; digits++) {
+ ns *= 10;
+ if (c_isdigit (*p))
+ ns += *p++ - '0';
+ }
+
+ /* Skip excess digits, truncating toward
+ * -Infinity.
+ */
+ if (sign < 0)
+ for (; c_isdigit (*p); p++)
+ if (*p != '0') {
+ ns++;
+ break;
+ }
+ while (c_isdigit (*p))
+ p++;
+
+ /* Adjust to the timespec convention, which is
+ * that tv_nsec is always a positive offset even
+ * if tv_sec is negative.
+ */
+ if (sign < 0 && ns) {
+ s--;
+ if (! (s < 0))
+ return '?';
+ ns = BILLION - ns;
+ }
+
+ lvalp->timespec.tv_sec = s;
+ lvalp->timespec.tv_nsec = ns;
+ pc->input = p;
+ return
+ sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
+ } else {
+ lvalp->textintval.negative = sign < 0;
+ if (sign < 0) {
+ lvalp->textintval.value = - value;
+ if (0 < lvalp->textintval.value)
+ return '?';
+ } else {
+ lvalp->textintval.value = value;
+ if (lvalp->textintval.value < 0)
+ return '?';
+ }
+ lvalp->textintval.digits = p - pc->input;
+ pc->input = p;
+ return sign ? tSNUMBER : tUNUMBER;
+ }
+ }
+
+ if (c_isalpha (c)) {
+ char buff[20];
+ char *p = buff;
+ table const *tp;
+
+ do {
+ if (p < buff + sizeof buff - 1)
+ *p++ = c;
+ c = *++pc->input;
+ }
+ while (c_isalpha (c) || c == '.');
+
+ *p = '\0';
+ tp = lookup_word (pc, buff);
+ if (! tp) {
+ return '?';
+ }
+ lvalp->intval = tp->value;
+ return tp->type;
+ }
+
+ if (c != '(')
+ return to_uchar (*pc->input++);
+
+ count = 0;
+ do {
+ c = *pc->input++;
+ if (c == '\0')
+ return c;
+ if (c == '(')
+ count++;
+ else if (c == ')')
+ count--;
+ }
+ while (count != 0);
+ }
+}
+
+/* Do nothing if the parser reports an error. */
+static int yyerror(parser_control const *pc __attribute__((__unused__)),
+ char const *s __attribute__((__unused__)))
+{
+ return 0;
+}
+
+/**
+ * If *TM0 is the old and *TM1 is the new value of a struct tm after
+ * passing it to mktime, return 1 if it's OK that mktime returned T.
+ * It's not OK if *TM0 has out-of-range members.
+ */
+
+static int mktime_ok(struct tm const *tm0, struct tm const *tm1, time_t t)
+{
+ if (t == (time_t) -1) {
+ /**
+ * Guard against falsely reporting an error when parsing a
+ * timestamp that happens to equal (time_t) -1, on a host that
+ * supports such a timestamp.
+ */
+ tm1 = localtime (&t);
+ if (!tm1)
+ return 0;
+ }
+
+ return ! ((tm0->tm_sec ^ tm1->tm_sec)
+ | (tm0->tm_min ^ tm1->tm_min)
+ | (tm0->tm_hour ^ tm1->tm_hour)
+ | (tm0->tm_mday ^ tm1->tm_mday)
+ | (tm0->tm_mon ^ tm1->tm_mon)
+ | (tm0->tm_year ^ tm1->tm_year));
+}
+
+/**
+ * A reasonable upper bound for the size of ordinary TZ strings.
+ * Use heap allocation if TZ's length exceeds this.
+ */
+enum { TZBUFSIZE = 100 };
+
+/**
+ * Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
+ * otherwise.
+ */
+static char * get_tz(char tzbuf[TZBUFSIZE])
+{
+ char *tz = getenv ("TZ");
+ if (tz) {
+ size_t tzsize = strlen (tz) + 1;
+ tz = (tzsize <= TZBUFSIZE
+ ? memcpy (tzbuf, tz, tzsize)
+ : strdup (tz));
+ }
+ return tz;
+}
+
+/**
+ * Parse a date/time string, storing the resulting time value into *result.
+ * The string itself is pointed to by *p. Return 1 if successful.
+ * *p can be an incomplete or relative time specification; if so, use
+ * *now as the basis for the returned time.
+ */
+int parse_date(struct timespec *result, char const *p,
+ struct timespec const *now)
+{
+ time_t Start;
+ intmax_t Start_ns;
+ struct tm const *tmp;
+ struct tm tm;
+ struct tm tm0;
+ parser_control pc;
+ struct timespec gettime_buffer;
+ unsigned char c;
+ int tz_was_altered = 0;
+ char *tz0 = NULL;
+ char tz0buf[TZBUFSIZE];
+ int ok = 1;
+ struct timeval tv;
+
+ if (! now) {
+ gettimeofday (&tv, NULL);
+ gettime_buffer.tv_sec = tv.tv_sec;
+ gettime_buffer.tv_nsec = tv.tv_usec * 1000;
+ now = &gettime_buffer;
+ }
+
+ Start = now->tv_sec;
+ Start_ns = now->tv_nsec;
+
+ tmp = localtime (&now->tv_sec);
+ if (! tmp)
+ return 0;
+
+ while (c = *p, c_isspace (c))
+ p++;
+
+ if (strncmp (p, "TZ=\"", 4) == 0) {
+ char const *tzbase = p + 4;
+ size_t tzsize = 1;
+ char const *s;
+
+ for (s = tzbase; *s; s++, tzsize++)
+ if (*s == '\\') {
+ s++;
+ if (! (*s == '\\' || *s == '"'))
+ break;
+ } else if (*s == '"') {
+ char *z;
+ char *tz1;
+ char tz1buf[TZBUFSIZE];
+ int large_tz = TZBUFSIZE < tzsize;
+ int setenv_ok;
+
+ tz0 = get_tz (tz0buf);
+ if (!tz0)
+ goto fail;
+
+ if (large_tz) {
+ z = tz1 = malloc (tzsize);
+ if (!tz1)
+ goto fail;
+ } else
+ z = tz1 = tz1buf;
+
+ for (s = tzbase; *s != '"'; s++)
+ *z++ = *(s += *s == '\\');
+ *z = '\0';
+ setenv_ok = setenv ("TZ", tz1, 1) == 0;
+ if (large_tz)
+ free (tz1);
+ if (!setenv_ok)
+ goto fail;
+ tz_was_altered = 1;
+
+ p = s + 1;
+ while (c = *p, c_isspace (c))
+ p++;
+
+ break;
+ }
+ }
+
+ /**
+ * As documented, be careful to treat the empty string just like
+ * a date string of "0". Without this, an empty string would be
+ * declared invalid when parsed during a DST transition.
+ */
+ if (*p == '\0')
+ p = "0";
+
+ pc.input = p;
+ pc.year.value = tmp->tm_year;
+ pc.year.value += TM_YEAR_BASE;
+ pc.year.digits = 0;
+ pc.month = tmp->tm_mon + 1;
+ pc.day = tmp->tm_mday;
+ pc.hour = tmp->tm_hour;
+ pc.minutes = tmp->tm_min;
+ pc.seconds.tv_sec = tmp->tm_sec;
+ pc.seconds.tv_nsec = Start_ns;
+ tm.tm_isdst = tmp->tm_isdst;
+
+ pc.meridian = MER24;
+ pc.rel = RELATIVE_TIME_0;
+ pc.timespec_seen = 0;
+ pc.rels_seen = 0;
+ pc.dates_seen = 0;
+ pc.days_seen = 0;
+ pc.times_seen = 0;
+ pc.local_zones_seen = 0;
+ pc.dsts_seen = 0;
+ pc.zones_seen = 0;
+
+#if HAVE_STRUCT_TM_TM_ZONE
+ pc.local_time_zone_table[0].name = tmp->tm_zone;
+ pc.local_time_zone_table[0].type = tLOCAL_ZONE;
+ pc.local_time_zone_table[0].value = tmp->tm_isdst;
+ pc.local_time_zone_table[1].name = NULL;
+
+ /**
+ * Probe the names used in the next three calendar quarters, looking
+ * for a tm_isdst different from the one we already have.
+ */
+ {
+ int quarter;
+ for (quarter = 1; quarter <= 3; quarter++) {
+ time_t probe = Start + quarter * (90 * 24 * 60 * 60);
+ struct tm const *probe_tm = localtime (&probe);
+ if (probe_tm && probe_tm->tm_zone
+ && probe_tm->tm_isdst
+ != pc.local_time_zone_table[0].value) {
+ {
+ pc.local_time_zone_table[1].name
+ = probe_tm->tm_zone;
+ pc.local_time_zone_table[1].type
+ = tLOCAL_ZONE;
+ pc.local_time_zone_table[1].value
+ = probe_tm->tm_isdst;
+ pc.local_time_zone_table[2].name
+ = NULL;
+ }
+ break;
+ }
+ }
+ }
+#else
+#if HAVE_TZNAME
+ {
+# if !HAVE_DECL_TZNAME
+ extern char *tzname[];
+# endif
+ int i;
+ for (i = 0; i < 2; i++) {
+ pc.local_time_zone_table[i].name = tzname[i];
+ pc.local_time_zone_table[i].type = tLOCAL_ZONE;
+ pc.local_time_zone_table[i].value = i;
+ }
+ pc.local_time_zone_table[i].name = NULL;
+ }
+#else
+ pc.local_time_zone_table[0].name = NULL;
+#endif
+#endif
+
+ if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
+ && ! strcmp (pc.local_time_zone_table[0].name,
+ pc.local_time_zone_table[1].name)) {
+ /**
+ * This locale uses the same abbreviation for standard and
+ * daylight times. So if we see that abbreviation, we don't
+ * know whether it's daylight time.
+ */
+ pc.local_time_zone_table[0].value = -1;
+ pc.local_time_zone_table[1].name = NULL;
+ }
+
+ if (yyparse (&pc) != 0) {
+ goto fail;
+ }
+
+ if (pc.timespec_seen)
+ *result = pc.seconds;
+ else {
+ if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen
+ | pc.dsts_seen
+ | (pc.local_zones_seen + pc.zones_seen))) {
+ goto fail;
+ }
+
+ tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
+ tm.tm_mon = pc.month - 1;
+ tm.tm_mday = pc.day;
+ if (pc.times_seen || (pc.rels_seen &&
+ ! pc.dates_seen && ! pc.days_seen)) {
+ tm.tm_hour = to_hour (pc.hour, pc.meridian);
+ if (tm.tm_hour < 0) {
+ goto fail;
+ }
+ tm.tm_min = pc.minutes;
+ tm.tm_sec = pc.seconds.tv_sec;
+ } else {
+ tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
+ pc.seconds.tv_nsec = 0;
+ }
+
+ /**
+ * Let mktime deduce tm_isdst if we have an absolute timestamp.
+ */
+ if (pc.dates_seen | pc.days_seen | pc.times_seen)
+ tm.tm_isdst = -1;
+
+ /**
+ * But if the input explicitly specifies local time with or
+ * without DST, give mktime that information.
+ */
+ if (pc.local_zones_seen)
+ tm.tm_isdst = pc.local_isdst;
+
+ tm0 = tm;
+
+ Start = mktime (&tm);
+
+ if (! mktime_ok (&tm0, &tm, Start)) {
+ if (! pc.zones_seen) {
+ goto fail;
+ } else {
+ /** Guard against falsely reporting errors near
+ * the time_t boundaries when parsing times in
+ * other time zones. For example, suppose the
+ * input string "1969-12-31 23:00:00 -0100", the
+ * current time zone is 8 hours ahead of UTC,
+ * and the min time_t value is 1970-01-01
+ * 00:00:00 UTC. Then the min localtime value
+ * is 1970-01-01 08:00:00, and mktime will
+ * therefore fail on 1969-12-31 23:00:00. To
+ * work around the problem, set the time zone to
+ * 1 hour behind UTC temporarily by setting
+ * TZ="XXX1:00" and try mktime again.
+ */
+
+ intmax_t time_zone = pc.time_zone;
+
+ intmax_t abs_time_zone = time_zone < 0
+ ? - time_zone : time_zone;
+
+ intmax_t abs_time_zone_hour
+ = abs_time_zone / 60;
+
+ int abs_time_zone_min = abs_time_zone % 60;
+
+ char tz1buf[sizeof "XXX+0:00"
+ + sizeof pc.time_zone
+ * CHAR_BIT / 3];
+
+ if (!tz_was_altered)
+ tz0 = get_tz (tz0buf);
+ sprintf (tz1buf, "XXX%s%jd:%02d",
+ &"-"[time_zone < 0],
+ abs_time_zone_hour,
+ abs_time_zone_min);
+ if (setenv ("TZ", tz1buf, 1) != 0) {
+ goto fail;
+ }
+ tz_was_altered = 1;
+ tm = tm0;
+ Start = mktime (&tm);
+ if (! mktime_ok (&tm0, &tm, Start)) {
+ goto fail;
+ }
+ }
+ }
+
+ if (pc.days_seen && ! pc.dates_seen) {
+ tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7 + 7
+ * (pc.day_ordinal
+ - (0 < pc.day_ordinal
+ && tm.tm_wday != pc.day_number)));
+ tm.tm_isdst = -1;
+ Start = mktime (&tm);
+ if (Start == (time_t) -1) {
+ goto fail;
+ }
+ }
+ /* Add relative date. */
+ if (pc.rel.year | pc.rel.month | pc.rel.day) {
+ int year = tm.tm_year + pc.rel.year;
+ int month = tm.tm_mon + pc.rel.month;
+ int day = tm.tm_mday + pc.rel.day;
+ if (((year < tm.tm_year) ^ (pc.rel.year < 0))
+ | ((month < tm.tm_mon) ^ (pc.rel.month < 0))
+ | ((day < tm.tm_mday) ^ (pc.rel.day < 0))) {
+ goto fail;
+ }
+ tm.tm_year = year;
+ tm.tm_mon = month;
+ tm.tm_mday = day;
+ tm.tm_hour = tm0.tm_hour;
+ tm.tm_min = tm0.tm_min;
+ tm.tm_sec = tm0.tm_sec;
+ tm.tm_isdst = tm0.tm_isdst;
+ Start = mktime (&tm);
+ if (Start == (time_t) -1) {
+ goto fail;
+ }
+ }
+
+ /**
+ * The only "output" of this if-block is an updated Start value,
+ * so this block must follow others that clobber Start.
+ */
+ if (pc.zones_seen) {
+ intmax_t delta = pc.time_zone * 60;
+ time_t t1;
+#ifdef HAVE_TM_GMTOFF
+ delta -= tm.tm_gmtoff;
+#else
+ time_t t = Start;
+ struct tm const *gmt = gmtime (&t);
+ if (! gmt) {
+ goto fail;
+ }
+ delta -= tm_diff (&tm, gmt);
+#endif
+ t1 = Start - delta;
+ if ((Start < t1) != (delta < 0)) {
+ goto fail; /* time_t overflow */
+ }
+ Start = t1;
+ }
+
+ /**
+ * Add relative hours, minutes, and seconds. On hosts that
+ * support leap seconds, ignore the possibility of leap seconds;
+ * e.g., "+ 10 minutes" adds 600 seconds, even if one of them is
+ * a leap second. Typically this is not what the user wants,
+ * but it's too hard to do it the other way, because the time
+ * zone indicator must be applied before relative times, and if
+ * mktime is applied again the time zone will be lost.
+ */
+ intmax_t sum_ns = pc.seconds.tv_nsec + pc.rel.ns;
+ intmax_t normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
+ time_t t0 = Start;
+ intmax_t d1 = 60 * 60 * pc.rel.hour;
+ time_t t1 = t0 + d1;
+ intmax_t d2 = 60 * pc.rel.minutes;
+ time_t t2 = t1 + d2;
+ time_t d3 = pc.rel.seconds;
+ time_t t3 = t2 + d3;
+ intmax_t d4 = (sum_ns - normalized_ns) / BILLION;
+ time_t t4 = t3 + d4;
+ time_t t5 = t4;
+
+ if ((d1 / (60 * 60) ^ pc.rel.hour)
+ | (d2 / 60 ^ pc.rel.minutes)
+ | ((t1 < t0) ^ (d1 < 0))
+ | ((t2 < t1) ^ (d2 < 0))
+ | ((t3 < t2) ^ (d3 < 0))
+ | ((t4 < t3) ^ (d4 < 0))
+ | (t5 != t4)) {
+ goto fail;
+ }
+ result->tv_sec = t5;
+ result->tv_nsec = normalized_ns;
+ }
+
+ goto done;
+
+ fail:
+ ok = 0;
+ done:
+ if (tz_was_altered)
+ ok &= (tz0 ? setenv ("TZ", tz0, 1)
+ : unsetenv ("TZ")) == 0;
+ if (tz0 != tz0buf)
+ free (tz0);
+ return ok;
+}
diff --git a/lib/path.c b/lib/path.c
new file mode 100644
index 0000000..48fb505
--- /dev/null
+++ b/lib/path.c
@@ -0,0 +1,1226 @@
+/*
+ * Simple functions to access files. Paths can be globally prefixed to read
+ * data from an alternative source (e.g. a /proc dump for regression tests).
+ *
+ * The paths is possible to format by printf-like way for functions with "f"
+ * postfix in the name (e.g. readf, openf, ... ul_path_readf_u64()).
+ *
+ * The ul_path_read_* API is possible to use without path_cxt handler. In this
+ * case is not possible to use global prefix and printf-like formatting.
+ *
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com> [February 2018]
+ */
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <errno.h>
+
+#include "c.h"
+#include "fileutils.h"
+#include "all-io.h"
+#include "path.h"
+#include "debug.h"
+
+/*
+ * Debug stuff (based on include/debug.h)
+ */
+static UL_DEBUG_DEFINE_MASK(ulpath);
+UL_DEBUG_DEFINE_MASKNAMES(ulpath) = UL_DEBUG_EMPTY_MASKNAMES;
+
+#define ULPATH_DEBUG_INIT (1 << 1)
+#define ULPATH_DEBUG_CXT (1 << 2)
+
+#define DBG(m, x) __UL_DBG(ulpath, ULPATH_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(ulpath, ULPATH_DEBUG_, m, x)
+
+#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulpath)
+#include "debugobj.h"
+
+void ul_path_init_debug(void)
+{
+ if (ulpath_debug_mask)
+ return;
+ __UL_INIT_DEBUG_FROM_ENV(ulpath, ULPATH_DEBUG_, 0, ULPATH_DEBUG);
+}
+
+struct path_cxt *ul_new_path(const char *dir, ...)
+{
+ struct path_cxt *pc = calloc(1, sizeof(*pc));
+
+ if (!pc)
+ return NULL;
+
+ DBG(CXT, ul_debugobj(pc, "alloc"));
+
+ pc->refcount = 1;
+ pc->dir_fd = -1;
+
+ if (dir) {
+ int rc;
+ va_list ap;
+
+ va_start(ap, dir);
+ rc = vasprintf(&pc->dir_path, dir, ap);
+ va_end(ap);
+
+ if (rc < 0 || !pc->dir_path)
+ goto fail;
+ }
+ return pc;
+fail:
+ ul_unref_path(pc);
+ return NULL;
+}
+
+void ul_ref_path(struct path_cxt *pc)
+{
+ if (pc)
+ pc->refcount++;
+}
+
+void ul_unref_path(struct path_cxt *pc)
+{
+ if (!pc)
+ return;
+
+ pc->refcount--;
+
+ if (pc->refcount <= 0) {
+ DBG(CXT, ul_debugobj(pc, "dealloc"));
+ if (pc->dialect)
+ pc->free_dialect(pc);
+ if (pc->dir_fd >= 0)
+ close(pc->dir_fd);
+ free(pc->dir_path);
+ free(pc->prefix);
+ free(pc);
+ }
+}
+
+int ul_path_set_prefix(struct path_cxt *pc, const char *prefix)
+{
+ char *p = NULL;
+
+ assert(pc->dir_fd < 0);
+
+ if (prefix) {
+ p = strdup(prefix);
+ if (!p)
+ return -ENOMEM;
+ }
+
+ free(pc->prefix);
+ pc->prefix = p;
+ DBG(CXT, ul_debugobj(pc, "new prefix: '%s'", p));
+ return 0;
+}
+
+const char *ul_path_get_prefix(struct path_cxt *pc)
+{
+ return pc ? pc->prefix : NULL;
+}
+
+int ul_path_set_dir(struct path_cxt *pc, const char *dir)
+{
+ char *p = NULL;
+
+ if (dir) {
+ p = strdup(dir);
+ if (!p)
+ return -ENOMEM;
+ }
+
+ if (pc->dir_fd >= 0) {
+ close(pc->dir_fd);
+ pc->dir_fd = -1;
+ }
+
+ free(pc->dir_path);
+ pc->dir_path = p;
+ DBG(CXT, ul_debugobj(pc, "new dir: '%s'", p));
+ return 0;
+}
+
+const char *ul_path_get_dir(struct path_cxt *pc)
+{
+ return pc ? pc->dir_path : NULL;
+}
+
+int ul_path_set_dialect(struct path_cxt *pc, void *data, void free_data(struct path_cxt *))
+{
+ pc->dialect = data;
+ pc->free_dialect = free_data;
+ DBG(CXT, ul_debugobj(pc, "(re)set dialect"));
+ return 0;
+}
+
+void *ul_path_get_dialect(struct path_cxt *pc)
+{
+ return pc ? pc->dialect : NULL;
+}
+
+int ul_path_set_enoent_redirect(struct path_cxt *pc, int (*func)(struct path_cxt *, const char *, int *))
+{
+ pc->redirect_on_enoent = func;
+ return 0;
+}
+
+static const char *get_absdir(struct path_cxt *pc)
+{
+ int rc;
+ const char *dirpath;
+
+ if (!pc->prefix)
+ return pc->dir_path;
+
+ dirpath = pc->dir_path;
+ if (!dirpath)
+ return pc->prefix;
+ if (*dirpath == '/')
+ dirpath++;
+
+ rc = snprintf(pc->path_buffer, sizeof(pc->path_buffer), "%s/%s", pc->prefix, dirpath);
+ if (rc < 0)
+ return NULL;
+ if ((size_t)rc >= sizeof(pc->path_buffer)) {
+ errno = ENAMETOOLONG;
+ return NULL;
+ }
+
+ return pc->path_buffer;
+}
+
+int ul_path_get_dirfd(struct path_cxt *pc)
+{
+ assert(pc);
+ assert(pc->dir_path);
+
+ if (pc->dir_fd < 0) {
+ const char *path = get_absdir(pc);
+ if (!path)
+ return -errno;
+
+ DBG(CXT, ul_debugobj(pc, "opening dir: '%s'", path));
+ pc->dir_fd = open(path, O_RDONLY|O_CLOEXEC);
+ }
+
+ return pc->dir_fd;
+}
+
+static const char *ul_path_mkpath(struct path_cxt *pc, const char *path, va_list ap)
+{
+ int rc = vsnprintf(pc->path_buffer, sizeof(pc->path_buffer), path, ap);
+
+ if (rc < 0)
+ return NULL;
+
+ if ((size_t)rc >= sizeof(pc->path_buffer)) {
+ errno = ENAMETOOLONG;
+ return NULL;
+ }
+
+ return pc->path_buffer;
+}
+
+char *ul_path_get_abspath(struct path_cxt *pc, char *buf, size_t bufsz, const char *path, ...)
+{
+ if (path) {
+ int rc;
+ va_list ap;
+ const char *tail = NULL, *dirpath = pc->dir_path;
+
+ va_start(ap, path);
+ tail = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ if (dirpath && *dirpath == '/')
+ dirpath++;
+ if (tail && *tail == '/')
+ tail++;
+
+ rc = snprintf(buf, bufsz, "%s/%s/%s",
+ pc->prefix ? pc->prefix : "",
+ dirpath ? dirpath : "",
+ tail ? tail : "");
+
+ if ((size_t)rc >= bufsz) {
+ errno = ENAMETOOLONG;
+ return NULL;
+ }
+ } else {
+ const char *tmp = get_absdir(pc);
+
+ if (!tmp)
+ return NULL;
+ strncpy(buf, tmp, bufsz);
+ buf[bufsz - 1] = '\0';
+ }
+
+ return buf;
+}
+
+
+int ul_path_access(struct path_cxt *pc, int mode, const char *path)
+{
+ int dir, rc;
+
+ dir = ul_path_get_dirfd(pc);
+ if (dir < 0)
+ return dir;
+
+ DBG(CXT, ul_debugobj(pc, "access: '%s'", path));
+ rc = faccessat(dir, path, mode, 0);
+
+ if (rc && errno == ENOENT
+ && pc->redirect_on_enoent
+ && pc->redirect_on_enoent(pc, path, &dir) == 0)
+ rc = faccessat(dir, path, mode, 0);
+ return rc;
+}
+
+int ul_path_accessf(struct path_cxt *pc, int mode, const char *path, ...)
+{
+ va_list ap;
+ const char *p;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ return ul_path_access(pc, mode, p);
+}
+
+int ul_path_open(struct path_cxt *pc, int flags, const char *path)
+{
+ int fd;
+
+ if (!pc) {
+ fd = open(path, flags);
+ DBG(CXT, ul_debug("opening '%s'", path));
+ } else {
+ int fdx;
+ int dir = ul_path_get_dirfd(pc);
+ if (dir < 0)
+ return dir;
+
+ fdx = fd = openat(dir, path, flags);
+
+ if (fd < 0 && errno == ENOENT
+ && pc->redirect_on_enoent
+ && pc->redirect_on_enoent(pc, path, &dir) == 0)
+ fd = openat(dir, path, flags);
+
+ DBG(CXT, ul_debugobj(pc, "opening '%s'%s", path, fdx != fd ? " [redirected]" : ""));
+ }
+ return fd;
+}
+
+int ul_path_vopenf(struct path_cxt *pc, int flags, const char *path, va_list ap)
+{
+ const char *p;
+
+ p = ul_path_mkpath(pc, path, ap);
+ if (!p)
+ return -errno;
+
+ return ul_path_open(pc, flags, p);
+}
+
+int ul_path_openf(struct path_cxt *pc, int flags, const char *path, ...)
+{
+ va_list ap;
+ int rc;
+
+ va_start(ap, path);
+ rc = ul_path_vopenf(pc, flags, path, ap);
+ va_end(ap);
+
+ return rc;
+}
+
+/*
+ * Maybe stupid, but good enough ;-)
+ */
+static int mode2flags(const char *mode)
+{
+ int flags = 0;
+ const char *p;
+
+ for (p = mode; p && *p; p++) {
+ if (*p == 'r' && *(p + 1) == '+')
+ flags |= O_RDWR;
+ else if (*p == 'r')
+ flags |= O_RDONLY;
+
+ else if (*p == 'w' && *(p + 1) == '+')
+ flags |= O_RDWR | O_TRUNC;
+ else if (*p == 'w')
+ flags |= O_WRONLY | O_TRUNC;
+
+ else if (*p == 'a' && *(p + 1) == '+')
+ flags |= O_RDWR | O_APPEND;
+ else if (*p == 'a')
+ flags |= O_WRONLY | O_APPEND;
+#ifdef O_CLOEXEC
+ else if (*p == *UL_CLOEXECSTR)
+ flags |= O_CLOEXEC;
+#endif
+ }
+
+ return flags;
+}
+
+FILE *ul_path_fopen(struct path_cxt *pc, const char *mode, const char *path)
+{
+ int flags = mode2flags(mode);
+ int fd = ul_path_open(pc, flags, path);
+
+ if (fd < 0)
+ return NULL;
+
+ return fdopen(fd, mode);
+}
+
+
+FILE *ul_path_vfopenf(struct path_cxt *pc, const char *mode, const char *path, va_list ap)
+{
+ const char *p;
+
+ p = ul_path_mkpath(pc, path, ap);
+ if (!p)
+ return NULL;
+
+ return ul_path_fopen(pc, mode, p);
+}
+
+FILE *ul_path_fopenf(struct path_cxt *pc, const char *mode, const char *path, ...)
+{
+ FILE *f;
+ va_list ap;
+
+ va_start(ap, path);
+ f = ul_path_vfopenf(pc, mode, path, ap);
+ va_end(ap);
+
+ return f;
+}
+
+/*
+ * Open directory @path in read-onl mode. If the path is NULL then duplicate FD
+ * to the directory addressed by @pc.
+ */
+DIR *ul_path_opendir(struct path_cxt *pc, const char *path)
+{
+ DIR *dir;
+ int fd = -1;
+
+ if (path)
+ fd = ul_path_open(pc, O_RDONLY|O_CLOEXEC, path);
+ else if (pc->dir_path) {
+ DBG(CXT, ul_debugobj(pc, "duplicate dir path"));
+ fd = dup_fd_cloexec(ul_path_get_dirfd(pc), STDERR_FILENO + 1);
+ }
+
+ if (fd < 0)
+ return NULL;
+
+ dir = fdopendir(fd);
+ if (!dir) {
+ close(fd);
+ return NULL;
+ }
+ if (!path)
+ rewinddir(dir);
+ return dir;
+}
+
+
+/*
+ * Open directory @path in read-onl mode. If the path is NULL then duplicate FD
+ * to the directory addressed by @pc.
+ */
+DIR *ul_path_vopendirf(struct path_cxt *pc, const char *path, va_list ap)
+{
+ const char *p;
+
+ p = ul_path_mkpath(pc, path, ap);
+ if (!p)
+ return NULL;
+
+ return ul_path_opendir(pc, p);
+}
+
+/*
+ * Open directory @path in read-onl mode. If the path is NULL then duplicate FD
+ * to the directory addressed by @pc.
+ */
+DIR *ul_path_opendirf(struct path_cxt *pc, const char *path, ...)
+{
+ va_list ap;
+ DIR *dir;
+
+ va_start(ap, path);
+ dir = ul_path_vopendirf(pc, path, ap);
+ va_end(ap);
+
+ return dir;
+}
+
+/*
+ * If @path is NULL then readlink is called on @pc directory.
+ */
+ssize_t ul_path_readlink(struct path_cxt *pc, char *buf, size_t bufsiz, const char *path)
+{
+ int dirfd;
+
+ if (!path) {
+ const char *p = get_absdir(pc);
+ if (!p)
+ return -errno;
+ return readlink(p, buf, bufsiz);
+ }
+
+ dirfd = ul_path_get_dirfd(pc);
+ if (dirfd < 0)
+ return dirfd;
+
+ return readlinkat(dirfd, path, buf, bufsiz);
+}
+
+/*
+ * If @path is NULL then readlink is called on @pc directory.
+ */
+ssize_t ul_path_readlinkf(struct path_cxt *pc, char *buf, size_t bufsiz, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ if (!p)
+ return -errno;
+
+ return ul_path_readlink(pc, buf, bufsiz, p);
+}
+
+int ul_path_read(struct path_cxt *pc, char *buf, size_t len, const char *path)
+{
+ int rc, errsv;
+ int fd;
+
+ fd = ul_path_open(pc, O_RDONLY|O_CLOEXEC, path);
+ if (fd < 0)
+ return -errno;
+
+ DBG(CXT, ul_debug(" reading '%s'", path));
+ rc = read_all(fd, buf, len);
+
+ errsv = errno;
+ close(fd);
+ errno = errsv;
+ return rc;
+}
+
+int ul_path_vreadf(struct path_cxt *pc, char *buf, size_t len, const char *path, va_list ap)
+{
+ const char *p;
+
+ p = ul_path_mkpath(pc, path, ap);
+ if (!p)
+ return -EINVAL;
+
+ return ul_path_read(pc, buf, len, p);
+}
+
+int ul_path_readf(struct path_cxt *pc, char *buf, size_t len, const char *path, ...)
+{
+ va_list ap;
+ int rc;
+
+ va_start(ap, path);
+ rc = ul_path_vreadf(pc, buf, len, path, ap);
+ va_end(ap);
+
+ return rc;
+}
+
+
+/*
+ * Returns newly allocated buffer with data from file. Maximal size is BUFSIZ
+ * (send patch if you need something bigger;-)
+ *
+ * Returns size of the string!
+ */
+int ul_path_read_string(struct path_cxt *pc, char **str, const char *path)
+{
+ char buf[BUFSIZ];
+ int rc;
+
+ *str = NULL;
+
+ rc = ul_path_read(pc, buf, sizeof(buf) - 1, path);
+ if (rc < 0 || !str)
+ return rc;;
+
+ /* Remove tailing newline (usuall in sysfs) */
+ if (rc > 0 && *(buf + rc - 1) == '\n')
+ --rc;
+
+ buf[rc] = '\0';
+ *str = strdup(buf);
+ if (!*str)
+ rc = -ENOMEM;
+
+ return rc;
+}
+
+int ul_path_readf_string(struct path_cxt *pc, char **str, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ if (!p)
+ return -EINVAL;
+
+ return ul_path_read_string(pc, str, p);
+}
+
+int ul_path_read_buffer(struct path_cxt *pc, char *buf, size_t bufsz, const char *path)
+{
+ int rc = ul_path_read(pc, buf, bufsz - 1, path);
+ if (rc < 0)
+ return rc;;
+
+ /* Remove tailing newline (usuall in sysfs) */
+ if (rc > 0 && *(buf + rc - 1) == '\n')
+ --rc;
+
+ buf[rc] = '\0';
+ return rc;
+}
+
+int ul_path_readf_buffer(struct path_cxt *pc, char *buf, size_t bufsz, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ if (!p)
+ return -EINVAL;
+
+ return ul_path_read_buffer(pc, buf, bufsz, p);
+}
+
+int ul_path_scanf(struct path_cxt *pc, const char *path, const char *fmt, ...)
+{
+ FILE *f;
+ va_list fmt_ap;
+ int rc;
+
+ f = ul_path_fopen(pc, "r" UL_CLOEXECSTR, path);
+ if (!f)
+ return -EINVAL;
+
+ DBG(CXT, ul_debug(" fscanf [%s] '%s'", fmt, path));
+
+ va_start(fmt_ap, fmt);
+ rc = vfscanf(f, fmt, fmt_ap);
+ va_end(fmt_ap);
+
+ fclose(f);
+ return rc;
+}
+
+int ul_path_scanff(struct path_cxt *pc, const char *path, va_list ap, const char *fmt, ...)
+{
+ FILE *f;
+ va_list fmt_ap;
+ int rc;
+
+ f = ul_path_vfopenf(pc, "r" UL_CLOEXECSTR, path, ap);
+ if (!f)
+ return -EINVAL;
+
+ va_start(fmt_ap, fmt);
+ rc = vfscanf(f, fmt, fmt_ap);
+ va_end(fmt_ap);
+
+ fclose(f);
+ return rc;
+}
+
+
+int ul_path_read_s64(struct path_cxt *pc, int64_t *res, const char *path)
+{
+ int64_t x = 0;
+ int rc;
+
+ rc = ul_path_scanf(pc, path, "%"SCNd64, &x);
+ if (rc != 1)
+ return -1;
+ if (res)
+ *res = x;
+ return 0;
+}
+
+int ul_path_readf_s64(struct path_cxt *pc, int64_t *res, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ if (!p)
+ return -EINVAL;;
+
+ return ul_path_read_s64(pc, res, p);
+}
+
+int ul_path_read_u64(struct path_cxt *pc, uint64_t *res, const char *path)
+{
+ uint64_t x = 0;
+ int rc;
+
+ rc = ul_path_scanf(pc, path, "%"SCNu64, &x);
+ if (rc != 1)
+ return -1;
+ if (res)
+ *res = x;
+ return 0;
+}
+
+int ul_path_readf_u64(struct path_cxt *pc, uint64_t *res, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ if (!p)
+ return -EINVAL;
+
+ return ul_path_read_u64(pc, res, p);
+}
+
+int ul_path_read_s32(struct path_cxt *pc, int *res, const char *path)
+{
+ int rc, x = 0;
+
+ rc = ul_path_scanf(pc, path, "%d", &x);
+ if (rc != 1)
+ return -1;
+ if (res)
+ *res = x;
+ return 0;
+}
+
+int ul_path_readf_s32(struct path_cxt *pc, int *res, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ if (!p)
+ return -EINVAL;
+
+ return ul_path_read_s32(pc, res, p);
+}
+
+int ul_path_read_u32(struct path_cxt *pc, unsigned int *res, const char *path)
+{
+ int rc;
+ unsigned int x;
+
+ rc = ul_path_scanf(pc, path, "%u", &x);
+ if (rc != 1)
+ return -1;
+ if (res)
+ *res = x;
+ return 0;
+}
+
+int ul_path_readf_u32(struct path_cxt *pc, unsigned int *res, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ if (!p)
+ return -EINVAL;
+
+ return ul_path_read_u32(pc, res, p);
+}
+
+int ul_path_read_majmin(struct path_cxt *pc, dev_t *res, const char *path)
+{
+ int rc, maj, min;
+
+ rc = ul_path_scanf(pc, path, "%d:%d", &maj, &min);
+ if (rc != 2)
+ return -1;
+ if (res)
+ *res = makedev(maj, min);
+ return 0;
+}
+
+int ul_path_readf_majmin(struct path_cxt *pc, dev_t *res, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ if (!p)
+ return -EINVAL;
+
+ return ul_path_read_majmin(pc, res, p);
+}
+
+int ul_path_write_string(struct path_cxt *pc, const char *str, const char *path)
+{
+ int rc, errsv;
+ int fd;
+
+ fd = ul_path_open(pc, O_WRONLY|O_CLOEXEC, path);
+ if (fd < 0)
+ return -errno;
+
+ rc = write_all(fd, str, strlen(str));
+
+ errsv = errno;
+ close(fd);
+ errno = errsv;
+ return rc;
+}
+
+int ul_path_writef_string(struct path_cxt *pc, const char *str, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ if (!p)
+ return -EINVAL;
+
+ return ul_path_write_string(pc, str, p);
+}
+
+int ul_path_write_s64(struct path_cxt *pc, int64_t num, const char *path)
+{
+ char buf[sizeof(stringify_value(LLONG_MAX))];
+ int rc, errsv;
+ int fd, len;
+
+ fd = ul_path_open(pc, O_WRONLY|O_CLOEXEC, path);
+ if (fd < 0)
+ return -errno;
+
+ len = snprintf(buf, sizeof(buf), "%" PRId64, num);
+ if (len < 0 || (size_t) len >= sizeof(buf))
+ rc = len < 0 ? -errno : -E2BIG;
+ else
+ rc = write_all(fd, buf, len);
+
+ errsv = errno;
+ close(fd);
+ errno = errsv;
+ return rc;
+}
+
+int ul_path_write_u64(struct path_cxt *pc, uint64_t num, const char *path)
+{
+ char buf[sizeof(stringify_value(ULLONG_MAX))];
+ int rc, errsv;
+ int fd, len;
+
+ fd = ul_path_open(pc, O_WRONLY|O_CLOEXEC, path);
+ if (fd < 0)
+ return -errno;
+
+ len = snprintf(buf, sizeof(buf), "%" PRIu64, num);
+ if (len < 0 || (size_t) len >= sizeof(buf))
+ rc = len < 0 ? -errno : -E2BIG;
+ else
+ rc = write_all(fd, buf, len);
+
+ errsv = errno;
+ close(fd);
+ errno = errsv;
+ return rc;
+}
+
+int ul_path_writef_u64(struct path_cxt *pc, uint64_t num, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ if (!p)
+ return -EINVAL;
+
+ return ul_path_write_u64(pc, num, p);
+
+}
+
+static struct dirent *xreaddir(DIR *dp)
+{
+ struct dirent *d;
+
+ while ((d = readdir(dp))) {
+ if (!strcmp(d->d_name, ".") ||
+ !strcmp(d->d_name, ".."))
+ continue;
+
+ /* blacklist here? */
+ break;
+ }
+ return d;
+}
+
+int ul_path_count_dirents(struct path_cxt *pc, const char *path)
+{
+ DIR *dir;
+ int r = 0;
+
+ dir = ul_path_opendir(pc, path);
+ if (!dir)
+ return 0;
+
+ while (xreaddir(dir)) r++;
+
+ closedir(dir);
+ return r;
+}
+
+int ul_path_countf_dirents(struct path_cxt *pc, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ if (!p)
+ return 0;
+
+ return ul_path_count_dirents(pc, p);
+}
+
+/*
+ * Like fopen() but, @path is always prefixed by @prefix. This function is
+ * useful in case when ul_path_* API is overkill.
+ */
+FILE *ul_prefix_fopen(const char *prefix, const char *path, const char *mode)
+{
+ char buf[PATH_MAX];
+
+ if (!path)
+ return NULL;
+ if (!prefix)
+ return fopen(path, mode);
+ if (*path == '/')
+ path++;
+
+ snprintf(buf, sizeof(buf), "%s/%s", prefix, path);
+ return fopen(buf, mode);
+}
+
+#ifdef HAVE_CPU_SET_T
+static int ul_path_cpuparse(struct path_cxt *pc, cpu_set_t **set, int maxcpus, int islist, const char *path, va_list ap)
+{
+ FILE *f;
+ size_t setsize, len = maxcpus * 7;
+ char buf[len];
+
+ *set = NULL;
+
+ f = ul_path_vfopenf(pc, "r" UL_CLOEXECSTR, path, ap);
+ if (!f)
+ return -errno;
+
+ if (!fgets(buf, len, f))
+ return -errno;
+ fclose(f);
+
+ len = strlen(buf);
+ if (buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+
+ *set = cpuset_alloc(maxcpus, &setsize, NULL);
+ if (!*set)
+ return -ENOMEM;
+
+ if (islist) {
+ if (cpulist_parse(buf, *set, setsize, 0)) {
+ cpuset_free(*set);
+ return -EINVAL;
+ }
+ } else {
+ if (cpumask_parse(buf, *set, setsize)) {
+ cpuset_free(*set);
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+int ul_path_readf_cpuset(struct path_cxt *pc, cpu_set_t **set, int maxcpus, const char *path, ...)
+{
+ va_list ap;
+ int rc = 0;
+
+ va_start(ap, path);
+ rc = ul_path_cpuparse(pc, set, maxcpus, 0, path, ap);
+ va_end(ap);
+
+ return rc;
+}
+
+int ul_path_readf_cpulist(struct path_cxt *pc, cpu_set_t **set, int maxcpus, const char *path, ...)
+{
+ va_list ap;
+ int rc = 0;
+
+ va_start(ap, path);
+ rc = ul_path_cpuparse(pc, set, maxcpus, 1, path, ap);
+ va_end(ap);
+
+ return rc;
+}
+
+#endif /* HAVE_CPU_SET_T */
+
+
+#ifdef TEST_PROGRAM_PATH
+#include <getopt.h>
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ fprintf(stdout, " %s [options] <dir> <command>\n\n", program_invocation_short_name);
+ fputs(" -p, --prefix <dir> redirect hardcoded paths to <dir>\n", stdout);
+
+ fputs(" Commands:\n", stdout);
+ fputs(" read-u64 <file> read uint64_t from file\n", stdout);
+ fputs(" read-s64 <file> read int64_t from file\n", stdout);
+ fputs(" read-u32 <file> read uint32_t from file\n", stdout);
+ fputs(" read-s32 <file> read int32_t from file\n", stdout);
+ fputs(" read-string <file> read string from file\n", stdout);
+ fputs(" read-majmin <file> read devno from file\n", stdout);
+ fputs(" read-link <file> read symlink\n", stdout);
+ fputs(" write-string <file> <str> write string from file\n", stdout);
+ fputs(" write-u64 <file> <str> write uint64_t from file\n", stdout);
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+ const char *prefix = NULL, *dir, *file, *command;
+ struct path_cxt *pc = NULL;
+
+ static const struct option longopts[] = {
+ { "prefix", 1, NULL, 'p' },
+ { "help", 0, NULL, 'h' },
+ { NULL, 0, NULL, 0 },
+ };
+
+ while((c = getopt_long(argc, argv, "p:h", longopts, NULL)) != -1) {
+ switch(c) {
+ case 'p':
+ prefix = optarg;
+ break;
+ case 'h':
+ usage();
+ break;
+ default:
+ err(EXIT_FAILURE, "try --help");
+ }
+ }
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, "<dir> not defined");
+ dir = argv[optind++];
+
+ ul_path_init_debug();
+
+ pc = ul_new_path(dir);
+ if (!pc)
+ err(EXIT_FAILURE, "failed to initialize path context");
+ if (prefix)
+ ul_path_set_prefix(pc, prefix);
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, "<command> not defined");
+ command = argv[optind++];
+
+ if (strcmp(command, "read-u32") == 0) {
+ uint32_t res;
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, "<file> not defined");
+ file = argv[optind++];
+
+ if (ul_path_read_u32(pc, &res, file) != 0)
+ err(EXIT_FAILURE, "read u64 failed");
+ printf("read: %s: %u\n", file, res);
+
+ if (ul_path_readf_u32(pc, &res, "%s", file) != 0)
+ err(EXIT_FAILURE, "readf u64 failed");
+ printf("readf: %s: %u\n", file, res);
+
+ } else if (strcmp(command, "read-s32") == 0) {
+ int32_t res;
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, "<file> not defined");
+ file = argv[optind++];
+
+ if (ul_path_read_s32(pc, &res, file) != 0)
+ err(EXIT_FAILURE, "read u64 failed");
+ printf("read: %s: %d\n", file, res);
+
+ if (ul_path_readf_s32(pc, &res, "%s", file) != 0)
+ err(EXIT_FAILURE, "readf u64 failed");
+ printf("readf: %s: %d\n", file, res);
+
+ } else if (strcmp(command, "read-u64") == 0) {
+ uint64_t res;
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, "<file> not defined");
+ file = argv[optind++];
+
+ if (ul_path_read_u64(pc, &res, file) != 0)
+ err(EXIT_FAILURE, "read u64 failed");
+ printf("read: %s: %" PRIu64 "\n", file, res);
+
+ if (ul_path_readf_u64(pc, &res, "%s", file) != 0)
+ err(EXIT_FAILURE, "readf u64 failed");
+ printf("readf: %s: %" PRIu64 "\n", file, res);
+
+ } else if (strcmp(command, "read-s64") == 0) {
+ int64_t res;
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, "<file> not defined");
+ file = argv[optind++];
+
+ if (ul_path_read_s64(pc, &res, file) != 0)
+ err(EXIT_FAILURE, "read u64 failed");
+ printf("read: %s: %" PRIu64 "\n", file, res);
+
+ if (ul_path_readf_s64(pc, &res, "%s", file) != 0)
+ err(EXIT_FAILURE, "readf u64 failed");
+ printf("readf: %s: %" PRIu64 "\n", file, res);
+
+ } else if (strcmp(command, "read-majmin") == 0) {
+ dev_t res;
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, "<file> not defined");
+ file = argv[optind++];
+
+ if (ul_path_read_majmin(pc, &res, file) != 0)
+ err(EXIT_FAILURE, "read maj:min failed");
+ printf("read: %s: %d\n", file, (int) res);
+
+ if (ul_path_readf_majmin(pc, &res, "%s", file) != 0)
+ err(EXIT_FAILURE, "readf maj:min failed");
+ printf("readf: %s: %d\n", file, (int) res);
+
+ } else if (strcmp(command, "read-string") == 0) {
+ char *res;
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, "<file> not defined");
+ file = argv[optind++];
+
+ if (ul_path_read_string(pc, &res, file) < 0)
+ err(EXIT_FAILURE, "read string failed");
+ printf("read: %s: %s\n", file, res);
+
+ if (ul_path_readf_string(pc, &res, "%s", file) < 0)
+ err(EXIT_FAILURE, "readf string failed");
+ printf("readf: %s: %s\n", file, res);
+
+ } else if (strcmp(command, "read-link") == 0) {
+ char res[PATH_MAX];
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, "<file> not defined");
+ file = argv[optind++];
+
+ if (ul_path_readlink(pc, res, sizeof(res), file) < 0)
+ err(EXIT_FAILURE, "read symlink failed");
+ printf("read: %s: %s\n", file, res);
+
+ if (ul_path_readlinkf(pc, res, sizeof(res), "%s", file) < 0)
+ err(EXIT_FAILURE, "readf symlink failed");
+ printf("readf: %s: %s\n", file, res);
+
+ } else if (strcmp(command, "write-string") == 0) {
+ char *str;
+
+ if (optind + 1 == argc)
+ errx(EXIT_FAILURE, "<file> <string> not defined");
+ file = argv[optind++];
+ str = argv[optind++];
+
+ if (ul_path_write_string(pc, str, file) != 0)
+ err(EXIT_FAILURE, "write string failed");
+ if (ul_path_writef_string(pc, str, "%s", file) != 0)
+ err(EXIT_FAILURE, "writef string failed");
+
+ } else if (strcmp(command, "write-u64") == 0) {
+ uint64_t num;
+
+ if (optind + 1 == argc)
+ errx(EXIT_FAILURE, "<file> <num> not defined");
+ file = argv[optind++];
+ num = strtoumax(argv[optind++], NULL, 0);
+
+ if (ul_path_write_u64(pc, num, file) != 0)
+ err(EXIT_FAILURE, "write u64 failed");
+ if (ul_path_writef_u64(pc, num, "%s", file) != 0)
+ err(EXIT_FAILURE, "writef u64 failed");
+ }
+
+ ul_unref_path(pc);
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM_PATH */
+
diff --git a/lib/plymouth-ctrl.c b/lib/plymouth-ctrl.c
new file mode 100644
index 0000000..2d3deda
--- /dev/null
+++ b/lib/plymouth-ctrl.c
@@ -0,0 +1,144 @@
+/*
+ * plymouth-ctrl.c Simply communications with plymouthd
+ * to avoid forked sub processes and/or
+ * missed plymouth send commands tool
+ * due a plymouthd replacement.
+ *
+ * Copyright (c) 2016 SUSE Linux GmbH, All rights reserved.
+ * Copyright (c) 2016 Werner Fink <werner@suse.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, 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 (see the file COPYING); if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ * Author: Werner Fink <werner@suse.de>
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "all-io.h"
+#include "c.h"
+#include "nls.h"
+#include "plymouth-ctrl.h"
+
+static int can_read(int fd, const long timeout)
+{
+ struct pollfd fds = {
+ .fd = fd,
+ .events = POLLIN|POLLPRI,
+ .revents = 0,
+ };
+ int ret;
+
+ do {
+ ret = poll(&fds, 1, timeout);
+ } while ((ret < 0) && (errno == EINTR));
+
+ return (ret == 1) && (fds.revents & (POLLIN|POLLPRI));
+}
+
+static int open_un_socket_and_connect(void)
+{
+ /* The abstract UNIX socket of plymouth */
+ struct sockaddr_un su = {
+ .sun_family = AF_UNIX,
+ .sun_path = PLYMOUTH_SOCKET_PATH,
+ };
+ const int one = 1;
+ int fd, ret;
+
+ fd = socket(PF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (fd < 0) {
+ warnx(_("cannot open UNIX socket"));
+ goto err;
+ }
+
+ ret = setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
+ if (ret < 0) {
+ warnx(_("cannot set option for UNIX socket"));
+ close(fd);
+ fd = -1;
+ goto err;
+ }
+
+ /* Note, the abstract PLYMOUTH_SOCKET_PATH has a leading NULL byte */
+ ret = connect(fd, (struct sockaddr *) &su,
+ offsetof(struct sockaddr_un, sun_path) + 1 + strlen(su.sun_path+1));
+ if (ret < 0) {
+ if (errno != ECONNREFUSED)
+ warnx(_("cannot connect on UNIX socket"));
+ close(fd);
+ fd = -1;
+ goto err;
+ }
+err:
+ return fd;
+}
+
+int plymouth_command(int cmd, ...)
+{
+ uint8_t answer[2], command[2];
+ struct sigaction sp, op;
+ int fdsock = -1, ret = 0;
+
+ sigemptyset (&sp.sa_mask);
+ sp.sa_handler = SIG_IGN;
+ sp.sa_flags = SA_RESTART;
+ sigaction(SIGPIPE, &sp, &op);
+
+ /* The plymouthd does read at least two bytes. */
+ command[1] = '\0';
+ switch (cmd) {
+ case MAGIC_PING:
+ fdsock = open_un_socket_and_connect();
+ if (fdsock >= 0) {
+ command[0] = cmd;
+ write_all(fdsock, command, sizeof(command));
+ }
+ break;
+ case MAGIC_QUIT:
+ fdsock = open_un_socket_and_connect();
+ if (fdsock >= 0) {
+ command[0] = cmd;
+ write_all(fdsock, command, sizeof(command));
+ }
+ break;
+ default:
+ warnx(_("the plymouth request %c is not implemented"), cmd);
+ case '?':
+ goto err;
+ }
+
+ answer[0] = '\0';
+ if (fdsock >= 0) {
+ if (can_read(fdsock, 1000))
+ read_all(fdsock, (char *) &answer[0], sizeof(answer));
+ close(fdsock);
+ }
+ sigaction(SIGPIPE, &op, NULL);
+ ret = (answer[0] == ANSWER_ACK) ? 1 : 0;
+err:
+ return ret;
+}
+
diff --git a/lib/procutils.c b/lib/procutils.c
new file mode 100644
index 0000000..8fb5d5c
--- /dev/null
+++ b/lib/procutils.c
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2011 Davidlohr Bueso <dave@gnu.org>
+ *
+ * procutils.c: General purpose procfs parsing utilities
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library Public License as published by
+ * the Free Software Foundation; either version 2, 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 Library Public License for more details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <ctype.h>
+
+#include "procutils.h"
+#include "fileutils.h"
+#include "all-io.h"
+#include "c.h"
+
+/*
+ * @pid: process ID for which we want to obtain the threads group
+ *
+ * Returns: newly allocated tasks structure
+ */
+struct proc_tasks *proc_open_tasks(pid_t pid)
+{
+ struct proc_tasks *tasks;
+ char path[PATH_MAX];
+
+ sprintf(path, "/proc/%d/task/", pid);
+
+ tasks = malloc(sizeof(struct proc_tasks));
+ if (tasks) {
+ tasks->dir = opendir(path);
+ if (tasks->dir)
+ return tasks;
+ }
+
+ free(tasks);
+ return NULL;
+}
+
+/*
+ * @tasks: allocated tasks structure
+ *
+ * Returns: nothing
+ */
+void proc_close_tasks(struct proc_tasks *tasks)
+{
+ if (tasks && tasks->dir)
+ closedir(tasks->dir);
+ free(tasks);
+}
+
+/*
+ * @tasks: allocated task structure
+ * @tid: [output] one of the thread IDs belonging to the thread group
+ * If when an error occurs, it is set to 0.
+ *
+ * Returns: 0 on success, 1 on end, -1 on failure or no more threads
+ */
+int proc_next_tid(struct proc_tasks *tasks, pid_t *tid)
+{
+ struct dirent *d;
+ char *end;
+
+ if (!tasks || !tid)
+ return -EINVAL;
+
+ *tid = 0;
+ errno = 0;
+
+ do {
+ d = readdir(tasks->dir);
+ if (!d)
+ return errno ? -1 : 1; /* error or end-of-dir */
+
+ if (!isdigit((unsigned char) *d->d_name))
+ continue;
+ errno = 0;
+ *tid = (pid_t) strtol(d->d_name, &end, 10);
+ if (errno || d->d_name == end || (end && *end))
+ return -1;
+
+ } while (!*tid);
+
+ return 0;
+}
+
+/* returns process command path, use free() for result */
+static char *proc_file_strdup(pid_t pid, const char *name)
+{
+ char buf[BUFSIZ], *res = NULL;
+ ssize_t sz = 0;
+ size_t i;
+ int fd;
+
+ snprintf(buf, sizeof(buf), "/proc/%d/%s", (int) pid, name);
+ fd = open(buf, O_RDONLY);
+ if (fd < 0)
+ goto done;
+
+ sz = read_all(fd, buf, sizeof(buf));
+ if (sz <= 0)
+ goto done;
+
+ for (i = 0; i < (size_t) sz; i++) {
+
+ if (buf[i] == '\0')
+ buf[i] = ' ';
+ }
+ buf[sz - 1] = '\0';
+ res = strdup(buf);
+done:
+ if (fd >= 0)
+ close(fd);
+ return res;
+}
+
+/* returns process command path, use free() for result */
+char *proc_get_command(pid_t pid)
+{
+ return proc_file_strdup(pid, "cmdline");
+}
+
+/* returns process command name, use free() for result */
+char *proc_get_command_name(pid_t pid)
+{
+ return proc_file_strdup(pid, "comm");
+}
+
+struct proc_processes *proc_open_processes(void)
+{
+ struct proc_processes *ps;
+
+ ps = calloc(1, sizeof(struct proc_processes));
+ if (ps) {
+ ps->dir = opendir("/proc");
+ if (ps->dir)
+ return ps;
+ }
+
+ free(ps);
+ return NULL;
+}
+
+void proc_close_processes(struct proc_processes *ps)
+{
+ if (ps && ps->dir)
+ closedir(ps->dir);
+ free(ps);
+}
+
+void proc_processes_filter_by_name(struct proc_processes *ps, const char *name)
+{
+ ps->fltr_name = name;
+ ps->has_fltr_name = name ? 1 : 0;
+}
+
+void proc_processes_filter_by_uid(struct proc_processes *ps, uid_t uid)
+{
+ ps->fltr_uid = uid;
+ ps->has_fltr_uid = 1;
+}
+
+int proc_next_pid(struct proc_processes *ps, pid_t *pid)
+{
+ struct dirent *d;
+
+ if (!ps || !pid)
+ return -EINVAL;
+
+ *pid = 0;
+ errno = 0;
+
+ do {
+ char buf[BUFSIZ], *p;
+
+ errno = 0;
+ d = readdir(ps->dir);
+ if (!d)
+ return errno ? -1 : 1; /* error or end-of-dir */
+
+
+ if (!isdigit((unsigned char) *d->d_name))
+ continue;
+
+ /* filter out by UID */
+ if (ps->has_fltr_uid) {
+ struct stat st;
+
+ if (fstatat(dirfd(ps->dir), d->d_name, &st, 0))
+ continue;
+ if (ps->fltr_uid != st.st_uid)
+ continue;
+ }
+
+ /* filter out by NAME */
+ if (ps->has_fltr_name) {
+ char procname[256];
+ FILE *f;
+
+ snprintf(buf, sizeof(buf), "%s/stat", d->d_name);
+ f = fopen_at(dirfd(ps->dir), buf, O_CLOEXEC|O_RDONLY, "r");
+ if (!f)
+ continue;
+
+ p = fgets(buf, sizeof(buf), f);
+ fclose(f);
+ if (!p)
+ continue;
+
+ if (sscanf(buf, "%*d (%255[^)])", procname) != 1)
+ continue;
+
+ /* ok, we got the process name. */
+ if (strcmp(procname, ps->fltr_name) != 0)
+ continue;
+ }
+
+ p = NULL;
+ errno = 0;
+ *pid = (pid_t) strtol(d->d_name, &p, 10);
+ if (errno || d->d_name == p || (p && *p))
+ return errno ? -errno : -1;
+
+ return 0;
+ } while (1);
+
+ return 0;
+}
+
+#ifdef TEST_PROGRAM_PROCUTILS
+
+static int test_tasks(int argc, char *argv[])
+{
+ pid_t tid, pid;
+ struct proc_tasks *ts;
+
+ if (argc != 2)
+ return EXIT_FAILURE;
+
+ pid = strtol(argv[1], (char **) NULL, 10);
+ printf("PID=%d, TIDs:", pid);
+
+ ts = proc_open_tasks(pid);
+ if (!ts)
+ err(EXIT_FAILURE, "open list of tasks failed");
+
+ while (proc_next_tid(ts, &tid) == 0)
+ printf(" %d", tid);
+
+ printf("\n");
+ proc_close_tasks(ts);
+ return EXIT_SUCCESS;
+}
+
+static int test_processes(int argc, char *argv[])
+{
+ pid_t pid;
+ struct proc_processes *ps;
+
+ ps = proc_open_processes();
+ if (!ps)
+ err(EXIT_FAILURE, "open list of processes failed");
+
+ if (argc >= 3 && strcmp(argv[1], "--name") == 0)
+ proc_processes_filter_by_name(ps, argv[2]);
+
+ if (argc >= 3 && strcmp(argv[1], "--uid") == 0)
+ proc_processes_filter_by_uid(ps, (uid_t) atol(argv[2]));
+
+ while (proc_next_pid(ps, &pid) == 0)
+ printf(" %d", pid);
+
+ printf("\n");
+ proc_close_processes(ps);
+ return EXIT_SUCCESS;
+}
+
+int main(int argc, char *argv[])
+{
+ if (argc < 2) {
+ fprintf(stderr, "usage: %1$s --tasks <pid>\n"
+ " %1$s --processes [---name <name>] [--uid <uid>]\n",
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+
+ if (strcmp(argv[1], "--tasks") == 0)
+ return test_tasks(argc - 1, argv + 1);
+ if (strcmp(argv[1], "--processes") == 0)
+ return test_processes(argc - 1, argv + 1);
+
+ return EXIT_FAILURE;
+}
+#endif /* TEST_PROGRAM_PROCUTILS */
diff --git a/lib/pwdutils.c b/lib/pwdutils.c
new file mode 100644
index 0000000..25b4dae
--- /dev/null
+++ b/lib/pwdutils.c
@@ -0,0 +1,96 @@
+#include <stdlib.h>
+
+#include "c.h"
+#include "pwdutils.h"
+#include "xalloc.h"
+
+/* Returns allocated passwd and allocated pwdbuf to store passwd strings
+ * fields. In case of error returns NULL and set errno, for unknown user set
+ * errno to EINVAL
+ */
+struct passwd *xgetpwnam(const char *username, char **pwdbuf)
+{
+ struct passwd *pwd = NULL, *res = NULL;
+ int rc;
+
+ if (!pwdbuf || !username)
+ return NULL;
+
+ *pwdbuf = xmalloc(UL_GETPW_BUFSIZ);
+ pwd = xcalloc(1, sizeof(struct passwd));
+
+ errno = 0;
+ rc = getpwnam_r(username, pwd, *pwdbuf, UL_GETPW_BUFSIZ, &res);
+ if (rc != 0) {
+ errno = rc;
+ goto failed;
+ }
+ if (!res) {
+ errno = EINVAL;
+ goto failed;
+ }
+ return pwd;
+failed:
+ free(pwd);
+ free(*pwdbuf);
+ return NULL;
+}
+
+char *xgetlogin(void)
+{
+ struct passwd *pw = NULL;
+ uid_t ruid;
+ char *user;
+
+ user = getlogin();
+ if (user)
+ return xstrdup(user);
+
+ /* GNU Hurd implementation has an extension where a process can exist in a
+ * non-conforming environment, and thus be outside the realms of POSIX
+ * process identifiers; on this platform, getuid() fails with a status of
+ * (uid_t)(-1) and sets errno if a program is run from a non-conforming
+ * environment.
+ *
+ * http://austingroupbugs.net/view.php?id=511
+ */
+ errno = 0;
+ ruid = getuid();
+
+ if (errno == 0)
+ pw = getpwuid(ruid);
+ if (pw && pw->pw_name && *pw->pw_name)
+ return xstrdup(pw->pw_name);
+
+ return NULL;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char *argv[])
+{
+ char *buf = NULL;
+ struct passwd *pwd = NULL;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s <username>\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ pwd = xgetpwnam(argv[1], &buf);
+ if (!pwd)
+ err(EXIT_FAILURE, "failed to get %s pwd entry", argv[1]);
+
+ printf("Username: %s\n", pwd->pw_name);
+ printf("UID: %d\n", pwd->pw_uid);
+ printf("HOME: %s\n", pwd->pw_dir);
+ printf("GECO: %s\n", pwd->pw_gecos);
+
+ free(pwd);
+ free(buf);
+
+ printf("Current: %s\n", (buf = xgetlogin()));
+ free(buf);
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM */
diff --git a/lib/randutils.c b/lib/randutils.c
new file mode 100644
index 0000000..de42795
--- /dev/null
+++ b/lib/randutils.c
@@ -0,0 +1,235 @@
+/*
+ * General purpose random utilities
+ *
+ * Based on libuuid code.
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include <sys/syscall.h>
+
+#include "c.h"
+#include "randutils.h"
+#include "nls.h"
+
+#ifdef HAVE_TLS
+#define THREAD_LOCAL static __thread
+#else
+#define THREAD_LOCAL static
+#endif
+
+#ifdef HAVE_GETRANDOM
+# include <sys/random.h>
+#elif defined (__linux__)
+# if !defined(SYS_getrandom) && defined(__NR_getrandom)
+ /* usable kernel-headers, but old glibc-headers */
+# define SYS_getrandom __NR_getrandom
+# endif
+#endif
+
+#if !defined(HAVE_GETRANDOM) && defined(SYS_getrandom)
+/* libc without function, but we have syscal */
+#define GRND_NONBLOCK 0x01
+#define GRND_RANDOM 0x02
+static int getrandom(void *buf, size_t buflen, unsigned int flags)
+{
+ return (syscall(SYS_getrandom, buf, buflen, flags));
+}
+# define HAVE_GETRANDOM
+#endif
+
+#if defined(__linux__) && defined(__NR_gettid) && defined(HAVE_JRAND48)
+#define DO_JRAND_MIX
+THREAD_LOCAL unsigned short ul_jrand_seed[3];
+#endif
+
+int rand_get_number(int low_n, int high_n)
+{
+ return rand() % (high_n - low_n + 1) + low_n;
+}
+
+static void crank_random(void)
+{
+ int i;
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ srand((getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec);
+
+#ifdef DO_JRAND_MIX
+ ul_jrand_seed[0] = getpid() ^ (tv.tv_sec & 0xFFFF);
+ ul_jrand_seed[1] = getppid() ^ (tv.tv_usec & 0xFFFF);
+ ul_jrand_seed[2] = (tv.tv_sec ^ tv.tv_usec) >> 16;
+#endif
+ /* Crank the random number generator a few times */
+ gettimeofday(&tv, NULL);
+ for (i = (tv.tv_sec ^ tv.tv_usec) & 0x1F; i > 0; i--)
+ rand();
+}
+
+int random_get_fd(void)
+{
+ int i, fd;
+
+ fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
+ if (fd == -1)
+ fd = open("/dev/random", O_RDONLY | O_NONBLOCK | O_CLOEXEC);
+ if (fd >= 0) {
+ i = fcntl(fd, F_GETFD);
+ if (i >= 0)
+ fcntl(fd, F_SETFD, i | FD_CLOEXEC);
+ }
+ crank_random();
+ return fd;
+}
+
+/*
+ * Generate a stream of random nbytes into buf.
+ * Use /dev/urandom if possible, and if not,
+ * use glibc pseudo-random functions.
+ */
+#define UL_RAND_READ_ATTEMPTS 8
+#define UL_RAND_READ_DELAY 125000 /* microseconds */
+
+void random_get_bytes(void *buf, size_t nbytes)
+{
+ unsigned char *cp = (unsigned char *)buf;
+ size_t i, n = nbytes;
+ int lose_counter = 0;
+
+#ifdef HAVE_GETRANDOM
+ while (n > 0) {
+ int x;
+
+ errno = 0;
+ x = getrandom(cp, n, GRND_NONBLOCK);
+ if (x > 0) { /* success */
+ n -= x;
+ cp += x;
+ lose_counter = 0;
+
+ } else if (errno == ENOSYS) { /* kernel without getrandom() */
+ break;
+
+ } else if (errno == EAGAIN && lose_counter < UL_RAND_READ_ATTEMPTS) {
+ xusleep(UL_RAND_READ_DELAY); /* no etropy, wait and try again */
+ lose_counter++;
+ } else
+ break;
+ }
+
+ if (errno == ENOSYS)
+#endif
+ /*
+ * We've been built against headers that support getrandom, but the
+ * running kernel does not. Fallback to reading from /dev/{u,}random
+ * as before
+ */
+ {
+ int fd = random_get_fd();
+
+ lose_counter = 0;
+ if (fd >= 0) {
+ while (n > 0) {
+ ssize_t x = read(fd, cp, n);
+ if (x <= 0) {
+ if (lose_counter++ > UL_RAND_READ_ATTEMPTS)
+ break;
+ xusleep(UL_RAND_READ_DELAY);
+ continue;
+ }
+ n -= x;
+ cp += x;
+ lose_counter = 0;
+ }
+
+ close(fd);
+ }
+ }
+ /*
+ * We do this all the time, but this is the only source of
+ * randomness if /dev/random/urandom is out to lunch.
+ */
+ crank_random();
+ for (cp = buf, i = 0; i < nbytes; i++)
+ *cp++ ^= (rand() >> 7) & 0xFF;
+
+#ifdef DO_JRAND_MIX
+ {
+ unsigned short tmp_seed[3];
+
+ memcpy(tmp_seed, ul_jrand_seed, sizeof(tmp_seed));
+ ul_jrand_seed[2] = ul_jrand_seed[2] ^ syscall(__NR_gettid);
+ for (cp = buf, i = 0; i < nbytes; i++)
+ *cp++ ^= (jrand48(tmp_seed) >> 7) & 0xFF;
+ memcpy(ul_jrand_seed, tmp_seed,
+ sizeof(ul_jrand_seed)-sizeof(unsigned short));
+ }
+#endif
+ return;
+}
+
+
+/*
+ * Tell source of randomness.
+ */
+const char *random_tell_source(void)
+{
+#ifdef HAVE_GETRANDOM
+ return _("getrandom() function");
+#else
+ size_t i;
+ static const char *random_sources[] = {
+ "/dev/urandom",
+ "/dev/random"
+ };
+
+ for (i = 0; i < ARRAY_SIZE(random_sources); i++) {
+ if (!access(random_sources[i], R_OK))
+ return random_sources[i];
+ }
+#endif
+ return _("libc pseudo-random functions");
+}
+
+#ifdef TEST_PROGRAM_RANDUTILS
+#include <inttypes.h>
+
+int main(int argc, char *argv[])
+{
+ size_t i, n;
+ int64_t *vp, v;
+ char *buf;
+ size_t bufsz;
+
+ n = argc == 1 ? 16 : atoi(argv[1]);
+
+ printf("Multiple random calls:\n");
+ for (i = 0; i < n; i++) {
+ random_get_bytes(&v, sizeof(v));
+ printf("#%02zu: %25"PRIu64"\n", i, v);
+ }
+
+
+ printf("One random call:\n");
+ bufsz = n * sizeof(*vp);
+ buf = malloc(bufsz);
+ if (!buf)
+ err(EXIT_FAILURE, "failed to allocate buffer");
+
+ random_get_bytes(buf, bufsz);
+ for (i = 0; i < n; i++) {
+ vp = (int64_t *) (buf + (i * sizeof(*vp)));
+ printf("#%02zu: %25"PRIu64"\n", i, *vp);
+ }
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM_RANDUTILS */
diff --git a/lib/setproctitle.c b/lib/setproctitle.c
new file mode 100644
index 0000000..7168e46
--- /dev/null
+++ b/lib/setproctitle.c
@@ -0,0 +1,75 @@
+/*
+ * set process title for ps (from sendmail)
+ *
+ * Clobbers argv of our main procedure so ps(1) will display the title.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "setproctitle.h"
+
+#ifndef SPT_BUFSIZE
+# define SPT_BUFSIZE 2048
+#endif
+
+extern char **environ;
+
+static char **argv0;
+static size_t argv_lth;
+
+void initproctitle (int argc, char **argv)
+{
+ int i;
+ char **envp = environ;
+
+ /*
+ * Move the environment so we can reuse the memory.
+ * (Code borrowed from sendmail.)
+ * WARNING: ugly assumptions on memory layout here;
+ * if this ever causes problems, #undef DO_PS_FIDDLING
+ */
+ for (i = 0; envp[i] != NULL; i++)
+ continue;
+
+ environ = malloc(sizeof(char *) * (i + 1));
+ if (environ == NULL)
+ return;
+
+ for (i = 0; envp[i] != NULL; i++)
+ if ((environ[i] = strdup(envp[i])) == NULL)
+ return;
+ environ[i] = NULL;
+
+ if (i > 0)
+ argv_lth = envp[i-1] + strlen(envp[i-1]) - argv[0];
+ else
+ argv_lth = argv[argc-1] + strlen(argv[argc-1]) - argv[0];
+ if (argv_lth > 1)
+ argv0 = argv;
+}
+
+void setproctitle (const char *prog, const char *txt)
+{
+ size_t i;
+ char buf[SPT_BUFSIZE];
+
+ if (!argv0)
+ return;
+
+ if (strlen(prog) + strlen(txt) + 5 > SPT_BUFSIZE)
+ return;
+
+ sprintf(buf, "%s -- %s", prog, txt);
+
+ i = strlen(buf);
+ if (i > argv_lth - 2) {
+ i = argv_lth - 2;
+ buf[i] = '\0';
+ }
+ memset(argv0[0], '\0', argv_lth); /* clear the memory area */
+ strcpy(argv0[0], buf);
+
+ argv0[1] = NULL;
+}
diff --git a/lib/sha1.c b/lib/sha1.c
new file mode 100644
index 0000000..22d33b3
--- /dev/null
+++ b/lib/sha1.c
@@ -0,0 +1,256 @@
+/*
+ * SHA-1 in C by Steve Reid <steve@edmweb.com>
+ * 100% Public Domain
+ *
+ * Test Vectors (from FIPS PUB 180-1)
+ * 1) "abc": A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
+ * 2) "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq": 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
+ * 3) A million repetitions of "a": 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
+ */
+
+#define UL_SHA1HANDSOFF
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "sha1.h"
+
+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+/* blk0() and blk() perform the initial expand. */
+#ifdef WORDS_BIGENDIAN
+# define blk0(i) block->l[i]
+#else
+# define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \
+ |(rol(block->l[i],8)&0x00FF00FF))
+#endif
+
+#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
+ ^block->l[(i+2)&15]^block->l[i&15],1))
+
+/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
+#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
+#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
+#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
+
+/* Hash a single 512-bit block. This is the core of the algorithm. */
+
+void ul_SHA1Transform(uint32_t state[5], const unsigned char buffer[64])
+{
+ uint32_t a, b, c, d, e;
+
+ typedef union {
+ unsigned char c[64];
+ uint32_t l[16];
+ } CHAR64LONG16;
+
+#ifdef UL_SHA1HANDSOFF
+ CHAR64LONG16 block[1]; /* use array to appear as a pointer */
+
+ memcpy(block, buffer, 64);
+#else
+ /* The following had better never be used because it causes the
+ * pointer-to-const buffer to be cast into a pointer to non-const.
+ * And the result is written through. I threw a "const" in, hoping
+ * this will cause a diagnostic.
+ */
+ CHAR64LONG16 *block = (const CHAR64LONG16 *)buffer;
+#endif
+ /* Copy context->state[] to working vars */
+ a = state[0];
+ b = state[1];
+ c = state[2];
+ d = state[3];
+ e = state[4];
+ /* 4 rounds of 20 operations each. Loop unrolled. */
+ R0(a, b, c, d, e, 0);
+ R0(e, a, b, c, d, 1);
+ R0(d, e, a, b, c, 2);
+ R0(c, d, e, a, b, 3);
+ R0(b, c, d, e, a, 4);
+ R0(a, b, c, d, e, 5);
+ R0(e, a, b, c, d, 6);
+ R0(d, e, a, b, c, 7);
+ R0(c, d, e, a, b, 8);
+ R0(b, c, d, e, a, 9);
+ R0(a, b, c, d, e, 10);
+ R0(e, a, b, c, d, 11);
+ R0(d, e, a, b, c, 12);
+ R0(c, d, e, a, b, 13);
+ R0(b, c, d, e, a, 14);
+ R0(a, b, c, d, e, 15);
+ R1(e, a, b, c, d, 16);
+ R1(d, e, a, b, c, 17);
+ R1(c, d, e, a, b, 18);
+ R1(b, c, d, e, a, 19);
+ R2(a, b, c, d, e, 20);
+ R2(e, a, b, c, d, 21);
+ R2(d, e, a, b, c, 22);
+ R2(c, d, e, a, b, 23);
+ R2(b, c, d, e, a, 24);
+ R2(a, b, c, d, e, 25);
+ R2(e, a, b, c, d, 26);
+ R2(d, e, a, b, c, 27);
+ R2(c, d, e, a, b, 28);
+ R2(b, c, d, e, a, 29);
+ R2(a, b, c, d, e, 30);
+ R2(e, a, b, c, d, 31);
+ R2(d, e, a, b, c, 32);
+ R2(c, d, e, a, b, 33);
+ R2(b, c, d, e, a, 34);
+ R2(a, b, c, d, e, 35);
+ R2(e, a, b, c, d, 36);
+ R2(d, e, a, b, c, 37);
+ R2(c, d, e, a, b, 38);
+ R2(b, c, d, e, a, 39);
+ R3(a, b, c, d, e, 40);
+ R3(e, a, b, c, d, 41);
+ R3(d, e, a, b, c, 42);
+ R3(c, d, e, a, b, 43);
+ R3(b, c, d, e, a, 44);
+ R3(a, b, c, d, e, 45);
+ R3(e, a, b, c, d, 46);
+ R3(d, e, a, b, c, 47);
+ R3(c, d, e, a, b, 48);
+ R3(b, c, d, e, a, 49);
+ R3(a, b, c, d, e, 50);
+ R3(e, a, b, c, d, 51);
+ R3(d, e, a, b, c, 52);
+ R3(c, d, e, a, b, 53);
+ R3(b, c, d, e, a, 54);
+ R3(a, b, c, d, e, 55);
+ R3(e, a, b, c, d, 56);
+ R3(d, e, a, b, c, 57);
+ R3(c, d, e, a, b, 58);
+ R3(b, c, d, e, a, 59);
+ R4(a, b, c, d, e, 60);
+ R4(e, a, b, c, d, 61);
+ R4(d, e, a, b, c, 62);
+ R4(c, d, e, a, b, 63);
+ R4(b, c, d, e, a, 64);
+ R4(a, b, c, d, e, 65);
+ R4(e, a, b, c, d, 66);
+ R4(d, e, a, b, c, 67);
+ R4(c, d, e, a, b, 68);
+ R4(b, c, d, e, a, 69);
+ R4(a, b, c, d, e, 70);
+ R4(e, a, b, c, d, 71);
+ R4(d, e, a, b, c, 72);
+ R4(c, d, e, a, b, 73);
+ R4(b, c, d, e, a, 74);
+ R4(a, b, c, d, e, 75);
+ R4(e, a, b, c, d, 76);
+ R4(d, e, a, b, c, 77);
+ R4(c, d, e, a, b, 78);
+ R4(b, c, d, e, a, 79);
+ /* Add the working vars back into context.state[] */
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+ state[4] += e;
+ /* Wipe variables */
+ a = b = c = d = e = 0;
+#ifdef UL_SHA1HANDSOFF
+ memset(block, '\0', sizeof(block));
+#endif
+}
+
+/* SHA1Init - Initialize new context */
+
+void ul_SHA1Init(UL_SHA1_CTX *context)
+{
+ /* SHA1 initialization constants */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xEFCDAB89;
+ context->state[2] = 0x98BADCFE;
+ context->state[3] = 0x10325476;
+ context->state[4] = 0xC3D2E1F0;
+ context->count[0] = context->count[1] = 0;
+}
+
+/* Run your data through this. */
+
+void ul_SHA1Update(UL_SHA1_CTX *context, const unsigned char *data, uint32_t len)
+{
+ uint32_t i;
+
+ uint32_t j;
+
+ j = context->count[0];
+ if ((context->count[0] += len << 3) < j)
+ context->count[1]++;
+ context->count[1] += (len >> 29);
+ j = (j >> 3) & 63;
+ if ((j + len) > 63) {
+ memcpy(&context->buffer[j], data, (i = 64 - j));
+ ul_SHA1Transform(context->state, context->buffer);
+ for (; i + 63 < len; i += 64) {
+ ul_SHA1Transform(context->state, &data[i]);
+ }
+ j = 0;
+ } else
+ i = 0;
+ memcpy(&context->buffer[j], &data[i], len - i);
+}
+
+/* Add padding and return the message digest. */
+
+void ul_SHA1Final(unsigned char digest[20], UL_SHA1_CTX *context)
+{
+ unsigned i;
+
+ unsigned char finalcount[8];
+
+ unsigned char c;
+
+#if 0 /* untested "improvement" by DHR */
+ /* Convert context->count to a sequence of bytes
+ * in finalcount. Second element first, but
+ * big-endian order within element.
+ * But we do it all backwards.
+ */
+ unsigned char *fcp = &finalcount[8];
+
+ for (i = 0; i < 2; i++) {
+ uint32_t t = context->count[i];
+
+ int j;
+
+ for (j = 0; j < 4; t >>= 8, j++)
+ *--fcp = (unsigned char)t}
+#else
+ for (i = 0; i < 8; i++) {
+ finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */
+ }
+#endif
+ c = 0200;
+ ul_SHA1Update(context, &c, 1);
+ while ((context->count[0] & 504) != 448) {
+ c = 0000;
+ ul_SHA1Update(context, &c, 1);
+ }
+ ul_SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */
+ for (i = 0; i < 20; i++) {
+ digest[i] = (unsigned char)
+ ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
+ }
+ /* Wipe variables */
+ memset(context, '\0', sizeof(*context));
+ memset(&finalcount, '\0', sizeof(finalcount));
+}
+
+void ul_SHA1(char *hash_out, const char *str, unsigned len)
+{
+ UL_SHA1_CTX ctx;
+ unsigned int ii;
+
+ ul_SHA1Init(&ctx);
+ for (ii = 0; ii < len; ii += 1)
+ ul_SHA1Update(&ctx, (const unsigned char *)str + ii, 1);
+ ul_SHA1Final((unsigned char *)hash_out, &ctx);
+ hash_out[20] = '\0';
+}
diff --git a/lib/signames.c b/lib/signames.c
new file mode 100644
index 0000000..316eec5
--- /dev/null
+++ b/lib/signames.c
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 1988, 1993, 1994, 2017
+ * 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.
+ */
+/*
+ * 2017-10-14 Niklas Hambüchen <mail@nh2.me>
+ * - Extracted signal names mapping from kill.c
+ *
+ * Copyright (C) 2014 Sami Kerola <kerolasa@iki.fi>
+ * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2017 Niklas Hambüchen <mail@nh2.me>
+ */
+
+#include <ctype.h> /* for isdigit() */
+#include <signal.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include "c.h"
+#include "strutils.h"
+#include "signames.h"
+
+static const struct ul_signal_name {
+ const char *name;
+ int val;
+} ul_signames[] = {
+ /* POSIX signals */
+ { "HUP", SIGHUP }, /* 1 */
+ { "INT", SIGINT }, /* 2 */
+ { "QUIT", SIGQUIT }, /* 3 */
+ { "ILL", SIGILL }, /* 4 */
+#ifdef SIGTRAP
+ { "TRAP", SIGTRAP }, /* 5 */
+#endif
+ { "ABRT", SIGABRT }, /* 6 */
+#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
+ { "FPE", SIGFPE }, /* 8 */
+ { "KILL", SIGKILL }, /* 9 */
+ { "USR1", SIGUSR1 }, /* 10 (arm,i386,m68k,ppc), 30 (alpha,sparc*), 16 (mips) */
+ { "SEGV", SIGSEGV }, /* 11 */
+ { "USR2", SIGUSR2 }, /* 12 (arm,i386,m68k,ppc), 31 (alpha,sparc*), 17 (mips) */
+ { "PIPE", SIGPIPE }, /* 13 */
+ { "ALRM", SIGALRM }, /* 14 */
+ { "TERM", SIGTERM }, /* 15 */
+#ifdef SIGSTKFLT
+ { "STKFLT", SIGSTKFLT }, /* 16 (arm,i386,m68k,ppc) */
+#endif
+ { "CHLD", SIGCHLD }, /* 17 (arm,i386,m68k,ppc), 20 (alpha,sparc*), 18 (mips) */
+#ifdef SIGCLD
+ { "CLD", SIGCLD }, /* same as SIGCHLD (mips) */
+#endif
+ { "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) */
+#ifdef SIGURG
+ { "URG", SIGURG }, /* 23 (arm,i386,m68k,ppc), 16 (alpha,sparc*), 21 (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 SIGWINCH
+ { "WINCH", SIGWINCH }, /* 28 (arm,i386,m68k,ppc,alpha,sparc*), 20 (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 SIGINFO
+ { "INFO", SIGINFO }, /* 29 (alpha) */
+#endif
+#ifdef SIGLOST
+ { "LOST", SIGLOST }, /* 29 (arm,i386,m68k,ppc,sparc*) */
+#endif
+#ifdef SIGPWR
+ { "PWR", SIGPWR }, /* 30 (arm,i386,m68k,ppc), 29 (alpha,sparc*), 19 (mips) */
+#endif
+#ifdef SIGUNUSED
+ { "UNUSED", SIGUNUSED }, /* 31 (arm,i386,m68k,ppc) */
+#endif
+#ifdef SIGSYS
+ { "SYS", SIGSYS }, /* 31 (mips,alpha,sparc*) */
+#endif
+};
+
+#ifdef SIGRTMIN
+static int rtsig_to_signum(const 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 || SIGRTMAX < num)
+ return -1;
+ return num;
+}
+#endif
+
+int signame_to_signum(const 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 signals */
+ for (n = 0; n < ARRAY_SIZE(ul_signames); n++) {
+ if (!strcasecmp(ul_signames[n].name, sig))
+ return ul_signames[n].val;
+ }
+ return -1;
+}
+
+const char *signum_to_signame(int signum)
+{
+ size_t n;
+
+ for (n = 0; n < ARRAY_SIZE(ul_signames); n++) {
+ if (ul_signames[n].val == signum) {
+ return ul_signames[n].name;
+ }
+ }
+
+ return NULL;
+}
+
+int get_signame_by_idx(size_t idx, const char **signame, int *signum)
+{
+ if (idx >= ARRAY_SIZE(ul_signames))
+ return -1;
+
+ if (signame)
+ *signame = ul_signames[idx].name;
+ if (signum)
+ *signum = ul_signames[idx].val;
+ return 0;
+
+}
+
diff --git a/lib/strutils.c b/lib/strutils.c
new file mode 100644
index 0000000..b71dde5
--- /dev/null
+++ b/lib/strutils.c
@@ -0,0 +1,1029 @@
+/*
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2010 Davidlohr Bueso <dave@gnu.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <assert.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "bitops.h"
+
+static int STRTOXX_EXIT_CODE = EXIT_FAILURE;
+
+void strutils_set_exitcode(int ex) {
+ STRTOXX_EXIT_CODE = ex;
+}
+
+static int do_scale_by_power (uintmax_t *x, int base, int power)
+{
+ while (power--) {
+ if (UINTMAX_MAX / base < *x)
+ return -ERANGE;
+ *x *= base;
+ }
+ return 0;
+}
+
+/*
+ * strtosize() - convert string to size (uintmax_t).
+ *
+ * Supported suffixes:
+ *
+ * XiB or X for 2^N
+ * where X = {K,M,G,T,P,E,Z,Y}
+ * or X = {k,m,g,t,p,e} (undocumented for backward compatibility only)
+ * for example:
+ * 10KiB = 10240
+ * 10K = 10240
+ *
+ * XB for 10^N
+ * where X = {K,M,G,T,P,E,Z,Y}
+ * for example:
+ * 10KB = 10000
+ *
+ * The optional 'power' variable returns number associated with used suffix
+ * {K,M,G,T,P,E,Z,Y} = {1,2,3,4,5,6,7,8}.
+ *
+ * The function also supports decimal point, for example:
+ * 0.5MB = 500000
+ * 0.5MiB = 512000
+ *
+ * Note that the function does not accept numbers with '-' (negative sign)
+ * prefix.
+ */
+int parse_size(const char *str, uintmax_t *res, int *power)
+{
+ const char *p;
+ char *end;
+ uintmax_t x, frac = 0;
+ int base = 1024, rc = 0, pwr = 0, frac_zeros = 0;
+
+ static const char *suf = "KMGTPEZY";
+ static const char *suf2 = "kmgtpezy";
+ const char *sp;
+
+ *res = 0;
+
+ if (!str || !*str) {
+ rc = -EINVAL;
+ goto err;
+ }
+
+ /* Only positive numbers are acceptable
+ *
+ * Note that this check is not perfect, it would be better to
+ * use lconv->negative_sign. But coreutils use the same solution,
+ * so it's probably good enough...
+ */
+ p = str;
+ while (isspace((unsigned char) *p))
+ p++;
+ if (*p == '-') {
+ rc = -EINVAL;
+ goto err;
+ }
+
+ errno = 0, end = NULL;
+ x = strtoumax(str, &end, 0);
+
+ if (end == str ||
+ (errno != 0 && (x == UINTMAX_MAX || x == 0))) {
+ rc = errno ? -errno : -EINVAL;
+ goto err;
+ }
+ if (!end || !*end)
+ goto done; /* without suffix */
+ p = end;
+
+ /*
+ * Check size suffixes
+ */
+check_suffix:
+ if (*(p + 1) == 'i' && (*(p + 2) == 'B' || *(p + 2) == 'b') && !*(p + 3))
+ base = 1024; /* XiB, 2^N */
+ else if ((*(p + 1) == 'B' || *(p + 1) == 'b') && !*(p + 2))
+ base = 1000; /* XB, 10^N */
+ else if (*(p + 1)) {
+ struct lconv const *l = localeconv();
+ const char *dp = l ? l->decimal_point : NULL;
+ size_t dpsz = dp ? strlen(dp) : 0;
+
+ if (frac == 0 && *p && dp && strncmp(dp, p, dpsz) == 0) {
+ const char *fstr = p + dpsz;
+
+ for (p = fstr; *p == '0'; p++)
+ frac_zeros++;
+ errno = 0, end = NULL;
+ frac = strtoumax(fstr, &end, 0);
+ if (end == fstr ||
+ (errno != 0 && (frac == UINTMAX_MAX || frac == 0))) {
+ rc = errno ? -errno : -EINVAL;
+ goto err;
+ }
+ if (frac && (!end || !*end)) {
+ rc = -EINVAL;
+ goto err; /* without suffix, but with frac */
+ }
+ p = end;
+ goto check_suffix;
+ }
+ rc = -EINVAL;
+ goto err; /* unexpected suffix */
+ }
+
+ sp = strchr(suf, *p);
+ if (sp)
+ pwr = (sp - suf) + 1;
+ else {
+ sp = strchr(suf2, *p);
+ if (sp)
+ pwr = (sp - suf2) + 1;
+ else {
+ rc = -EINVAL;
+ goto err;
+ }
+ }
+
+ rc = do_scale_by_power(&x, base, pwr);
+ if (power)
+ *power = pwr;
+ if (frac && pwr) {
+ int zeros_in_pwr = frac_zeros % 3;
+ int frac_pwr = pwr - (frac_zeros / 3) - 1;
+ uintmax_t y = frac * (zeros_in_pwr == 0 ? 100 :
+ zeros_in_pwr == 1 ? 10 : 1);
+
+ if (frac_pwr < 0) {
+ rc = -EINVAL;
+ goto err;
+ }
+ do_scale_by_power(&y, base, frac_pwr);
+ x += y;
+ }
+done:
+ *res = x;
+err:
+ if (rc < 0)
+ errno = -rc;
+ return rc;
+}
+
+int strtosize(const char *str, uintmax_t *res)
+{
+ return parse_size(str, res, NULL);
+}
+
+int isdigit_strend(const char *str, const char **end)
+{
+ const char *p;
+
+ for (p = str; p && *p && isdigit((unsigned char) *p); p++);
+
+ if (end)
+ *end = p;
+ return p && p > str && !*p;
+}
+
+int isxdigit_strend(const char *str, const char **end)
+{
+ const char *p;
+
+ for (p = str; p && *p && isxdigit((unsigned char) *p); p++);
+
+ if (end)
+ *end = p;
+
+ return p && p > str && !*p;
+}
+
+/*
+ * parse_switch(argv[i], "on", "off", "yes", "no", NULL);
+ */
+int parse_switch(const char *arg, const char *errmesg, ...)
+{
+ const char *a, *b;
+ va_list ap;
+
+ va_start(ap, errmesg);
+ do {
+ a = va_arg(ap, char *);
+ if (!a)
+ break;
+ b = va_arg(ap, char *);
+ if (!b)
+ break;
+
+ if (strcmp(arg, a) == 0) {
+ va_end(ap);
+ return 1;
+ } else if (strcmp(arg, b) == 0) {
+ va_end(ap);
+ return 0;
+ }
+ } while (1);
+ va_end(ap);
+
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, arg);
+}
+
+#ifndef HAVE_MEMPCPY
+void *mempcpy(void *restrict dest, const void *restrict src, size_t n)
+{
+ return ((char *)memcpy(dest, src, n)) + n;
+}
+#endif
+
+#ifndef HAVE_STRNLEN
+size_t strnlen(const char *s, size_t maxlen)
+{
+ size_t i;
+
+ for (i = 0; i < maxlen; i++) {
+ if (s[i] == '\0')
+ return i;
+ }
+ return maxlen;
+}
+#endif
+
+#ifndef HAVE_STRNCHR
+char *strnchr(const char *s, size_t maxlen, int c)
+{
+ for (; maxlen-- && *s != '\0'; ++s)
+ if (*s == (char)c)
+ return (char *)s;
+ return NULL;
+}
+#endif
+
+#ifndef HAVE_STRNDUP
+char *strndup(const char *s, size_t n)
+{
+ size_t len = strnlen(s, n);
+ char *new = malloc((len + 1) * sizeof(char));
+ if (!new)
+ return NULL;
+ new[len] = '\0';
+ return (char *) memcpy(new, s, len);
+}
+#endif
+
+static uint32_t _strtou32_or_err(const char *str, const char *errmesg, int base);
+static uint64_t _strtou64_or_err(const char *str, const char *errmesg, int base);
+
+int16_t strtos16_or_err(const char *str, const char *errmesg)
+{
+ int32_t num = strtos32_or_err(str, errmesg);
+
+ if (num < INT16_MIN || num > INT16_MAX) {
+ errno = ERANGE;
+ err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+ }
+ return num;
+}
+
+static uint16_t _strtou16_or_err(const char *str, const char *errmesg, int base)
+{
+ uint32_t num = _strtou32_or_err(str, errmesg, base);
+
+ if (num > UINT16_MAX) {
+ errno = ERANGE;
+ err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+ }
+ return num;
+}
+
+uint16_t strtou16_or_err(const char *str, const char *errmesg)
+{
+ return _strtou16_or_err(str, errmesg, 10);
+}
+
+uint16_t strtox16_or_err(const char *str, const char *errmesg)
+{
+ return _strtou16_or_err(str, errmesg, 16);
+}
+
+int32_t strtos32_or_err(const char *str, const char *errmesg)
+{
+ int64_t num = strtos64_or_err(str, errmesg);
+
+ if (num < INT32_MIN || num > INT32_MAX) {
+ errno = ERANGE;
+ err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+ }
+ return num;
+}
+
+static uint32_t _strtou32_or_err(const char *str, const char *errmesg, int base)
+{
+ uint64_t num = _strtou64_or_err(str, errmesg, base);
+
+ if (num > UINT32_MAX) {
+ errno = ERANGE;
+ err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+ }
+ return num;
+}
+
+uint32_t strtou32_or_err(const char *str, const char *errmesg)
+{
+ return _strtou32_or_err(str, errmesg, 10);
+}
+
+uint32_t strtox32_or_err(const char *str, const char *errmesg)
+{
+ return _strtou32_or_err(str, errmesg, 16);
+}
+
+int64_t strtos64_or_err(const char *str, const char *errmesg)
+{
+ int64_t num;
+ char *end = NULL;
+
+ errno = 0;
+ if (str == NULL || *str == '\0')
+ goto err;
+ num = strtoimax(str, &end, 10);
+
+ if (errno || str == end || (end && *end))
+ goto err;
+
+ return num;
+err:
+ if (errno == ERANGE)
+ err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+}
+
+static uint64_t _strtou64_or_err(const char *str, const char *errmesg, int base)
+{
+ uintmax_t num;
+ char *end = NULL;
+
+ errno = 0;
+ if (str == NULL || *str == '\0')
+ goto err;
+ num = strtoumax(str, &end, base);
+
+ if (errno || str == end || (end && *end))
+ goto err;
+
+ return num;
+err:
+ if (errno == ERANGE)
+ err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+}
+
+uint64_t strtou64_or_err(const char *str, const char *errmesg)
+{
+ return _strtou64_or_err(str, errmesg, 10);
+}
+
+uint64_t strtox64_or_err(const char *str, const char *errmesg)
+{
+ return _strtou64_or_err(str, errmesg, 16);
+}
+
+double strtod_or_err(const char *str, const char *errmesg)
+{
+ double num;
+ char *end = NULL;
+
+ errno = 0;
+ if (str == NULL || *str == '\0')
+ goto err;
+ num = strtod(str, &end);
+
+ if (errno || str == end || (end && *end))
+ goto err;
+
+ return num;
+err:
+ if (errno == ERANGE)
+ err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+}
+
+long strtol_or_err(const char *str, const char *errmesg)
+{
+ long num;
+ char *end = NULL;
+
+ errno = 0;
+ if (str == NULL || *str == '\0')
+ goto err;
+ num = strtol(str, &end, 10);
+
+ if (errno || str == end || (end && *end))
+ goto err;
+
+ return num;
+err:
+ if (errno == ERANGE)
+ err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+}
+
+unsigned long strtoul_or_err(const char *str, const char *errmesg)
+{
+ unsigned long num;
+ char *end = NULL;
+
+ errno = 0;
+ if (str == NULL || *str == '\0')
+ goto err;
+ num = strtoul(str, &end, 10);
+
+ if (errno || str == end || (end && *end))
+ goto err;
+
+ return num;
+err:
+ if (errno == ERANGE)
+ err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+}
+
+uintmax_t strtosize_or_err(const char *str, const char *errmesg)
+{
+ uintmax_t num;
+
+ if (strtosize(str, &num) == 0)
+ return num;
+
+ if (errno)
+ err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+}
+
+
+void strtotimeval_or_err(const char *str, struct timeval *tv, const char *errmesg)
+{
+ double user_input;
+
+ user_input = strtod_or_err(str, errmesg);
+ tv->tv_sec = (time_t) user_input;
+ tv->tv_usec = (long)((user_input - tv->tv_sec) * 1000000);
+}
+
+/*
+ * Converts stat->st_mode to ls(1)-like mode string. The size of "str" must
+ * be 11 bytes.
+ */
+char *xstrmode(mode_t mode, char *str)
+{
+ unsigned short i = 0;
+
+ if (S_ISDIR(mode))
+ str[i++] = 'd';
+ else if (S_ISLNK(mode))
+ str[i++] = 'l';
+ else if (S_ISCHR(mode))
+ str[i++] = 'c';
+ else if (S_ISBLK(mode))
+ str[i++] = 'b';
+ else if (S_ISSOCK(mode))
+ str[i++] = 's';
+ else if (S_ISFIFO(mode))
+ str[i++] = 'p';
+ else if (S_ISREG(mode))
+ str[i++] = '-';
+
+ str[i++] = mode & S_IRUSR ? 'r' : '-';
+ str[i++] = mode & S_IWUSR ? 'w' : '-';
+ str[i++] = (mode & S_ISUID
+ ? (mode & S_IXUSR ? 's' : 'S')
+ : (mode & S_IXUSR ? 'x' : '-'));
+ str[i++] = mode & S_IRGRP ? 'r' : '-';
+ str[i++] = mode & S_IWGRP ? 'w' : '-';
+ str[i++] = (mode & S_ISGID
+ ? (mode & S_IXGRP ? 's' : 'S')
+ : (mode & S_IXGRP ? 'x' : '-'));
+ str[i++] = mode & S_IROTH ? 'r' : '-';
+ str[i++] = mode & S_IWOTH ? 'w' : '-';
+ str[i++] = (mode & S_ISVTX
+ ? (mode & S_IXOTH ? 't' : 'T')
+ : (mode & S_IXOTH ? 'x' : '-'));
+ str[i] = '\0';
+
+ return str;
+}
+
+/*
+ * returns exponent (2^x=n) in range KiB..EiB (2^10..2^60)
+ */
+static int get_exp(uint64_t n)
+{
+ int shft;
+
+ for (shft = 10; shft <= 60; shft += 10) {
+ if (n < (1ULL << shft))
+ break;
+ }
+ return shft - 10;
+}
+
+char *size_to_human_string(int options, uint64_t bytes)
+{
+ char buf[32];
+ int dec, exp;
+ uint64_t frac;
+ const char *letters = "BKMGTPE";
+ char suffix[sizeof(" KiB")], *psuf = suffix;
+ char c;
+
+ if (options & SIZE_SUFFIX_SPACE)
+ *psuf++ = ' ';
+
+ exp = get_exp(bytes);
+ c = *(letters + (exp ? exp / 10 : 0));
+ dec = exp ? bytes / (1ULL << exp) : bytes;
+ frac = exp ? bytes % (1ULL << exp) : 0;
+
+ *psuf++ = c;
+
+ if ((options & SIZE_SUFFIX_3LETTER) && (c != 'B')) {
+ *psuf++ = 'i';
+ *psuf++ = 'B';
+ }
+
+ *psuf = '\0';
+
+ /* fprintf(stderr, "exp: %d, unit: %c, dec: %d, frac: %jd\n",
+ * exp, suffix[0], dec, frac);
+ */
+
+ if (frac) {
+ /* round */
+ frac = (frac / (1ULL << (exp - 10)) + 50) / 100;
+ if (frac == 10)
+ dec++, frac = 0;
+ }
+
+ if (frac) {
+ struct lconv const *l = localeconv();
+ char *dp = l ? l->decimal_point : NULL;
+
+ if (!dp || !*dp)
+ dp = ".";
+ snprintf(buf, sizeof(buf), "%d%s%" PRIu64 "%s", dec, dp, frac, suffix);
+ } else
+ snprintf(buf, sizeof(buf), "%d%s", dec, suffix);
+
+ return strdup(buf);
+}
+
+/*
+ * Parses comma delimited list to array with IDs, for example:
+ *
+ * "aaa,bbb,ccc" --> ary[0] = FOO_AAA;
+ * ary[1] = FOO_BBB;
+ * ary[3] = FOO_CCC;
+ *
+ * The function name2id() provides conversion from string to ID.
+ *
+ * Returns: >= 0 : number of items added to ary[]
+ * -1 : parse error or unknown item
+ * -2 : arysz reached
+ */
+int string_to_idarray(const char *list, int ary[], size_t arysz,
+ int (name2id)(const char *, size_t))
+{
+ const char *begin = NULL, *p;
+ size_t n = 0;
+
+ if (!list || !*list || !ary || !arysz || !name2id)
+ return -1;
+
+ for (p = list; p && *p; p++) {
+ const char *end = NULL;
+ int id;
+
+ if (n >= arysz)
+ return -2;
+ if (!begin)
+ begin = p; /* begin of the column name */
+ if (*p == ',')
+ end = p; /* terminate the name */
+ if (*(p + 1) == '\0')
+ end = p + 1; /* end of string */
+ if (!begin || !end)
+ continue;
+ if (end <= begin)
+ return -1;
+
+ id = name2id(begin, end - begin);
+ if (id == -1)
+ return -1;
+ ary[ n++ ] = id;
+ begin = NULL;
+ if (end && !*end)
+ break;
+ }
+ return n;
+}
+
+/*
+ * Parses the array like string_to_idarray but if format is "+aaa,bbb"
+ * it adds fields to array instead of replacing them.
+ */
+int string_add_to_idarray(const char *list, int ary[], size_t arysz,
+ size_t *ary_pos, int (name2id)(const char *, size_t))
+{
+ const char *list_add;
+ int r;
+
+ if (!list || !*list || !ary_pos || *ary_pos > arysz)
+ return -1;
+
+ if (list[0] == '+')
+ list_add = &list[1];
+ else {
+ list_add = list;
+ *ary_pos = 0;
+ }
+
+ r = string_to_idarray(list_add, &ary[*ary_pos], arysz - *ary_pos, name2id);
+ if (r > 0)
+ *ary_pos += r;
+ return r;
+}
+
+/*
+ * LIST ::= <item> [, <item>]
+ *
+ * The <item> is translated to 'id' by name2id() function and the 'id' is used
+ * as a position in the 'ary' bit array. It means that the 'id' has to be in
+ * range <0..N> where N < sizeof(ary) * NBBY.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int string_to_bitarray(const char *list,
+ char *ary,
+ int (*name2bit)(const char *, size_t))
+{
+ const char *begin = NULL, *p;
+
+ if (!list || !name2bit || !ary)
+ return -EINVAL;
+
+ for (p = list; p && *p; p++) {
+ const char *end = NULL;
+ int bit;
+
+ if (!begin)
+ begin = p; /* begin of the level name */
+ if (*p == ',')
+ end = p; /* terminate the name */
+ if (*(p + 1) == '\0')
+ end = p + 1; /* end of string */
+ if (!begin || !end)
+ continue;
+ if (end <= begin)
+ return -1;
+
+ bit = name2bit(begin, end - begin);
+ if (bit < 0)
+ return bit;
+ setbit(ary, bit);
+ begin = NULL;
+ if (end && !*end)
+ break;
+ }
+ return 0;
+}
+
+/*
+ * LIST ::= <item> [, <item>]
+ *
+ * The <item> is translated to 'id' by name2flag() function and the flags is
+ * set to the 'mask'
+*
+ * Returns: 0 on success, <0 on error.
+ */
+int string_to_bitmask(const char *list,
+ unsigned long *mask,
+ long (*name2flag)(const char *, size_t))
+{
+ const char *begin = NULL, *p;
+
+ if (!list || !name2flag || !mask)
+ return -EINVAL;
+
+ for (p = list; p && *p; p++) {
+ const char *end = NULL;
+ long flag;
+
+ if (!begin)
+ begin = p; /* begin of the level name */
+ if (*p == ',')
+ end = p; /* terminate the name */
+ if (*(p + 1) == '\0')
+ end = p + 1; /* end of string */
+ if (!begin || !end)
+ continue;
+ if (end <= begin)
+ return -1;
+
+ flag = name2flag(begin, end - begin);
+ if (flag < 0)
+ return flag; /* error */
+ *mask |= flag;
+ begin = NULL;
+ if (end && !*end)
+ break;
+ }
+ return 0;
+}
+
+/*
+ * Parse the lower and higher values in a string containing
+ * "lower:higher" or "lower-higher" format. Note that either
+ * the lower or the higher values may be missing, and the def
+ * value will be assigned to it by default.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int parse_range(const char *str, int *lower, int *upper, int def)
+{
+ char *end = NULL;
+
+ if (!str)
+ return 0;
+
+ *upper = *lower = def;
+ errno = 0;
+
+ if (*str == ':') { /* <:N> */
+ str++;
+ *upper = strtol(str, &end, 10);
+ if (errno || !end || *end || end == str)
+ return -1;
+ } else {
+ *upper = *lower = strtol(str, &end, 10);
+ if (errno || !end || end == str)
+ return -1;
+
+ if (*end == ':' && !*(end + 1)) /* <M:> */
+ *upper = def;
+ else if (*end == '-' || *end == ':') { /* <M:N> <M-N> */
+ str = end + 1;
+ end = NULL;
+ errno = 0;
+ *upper = strtol(str, &end, 10);
+
+ if (errno || !end || *end || end == str)
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static const char *next_path_segment(const char *str, size_t *sz)
+{
+ const char *start, *p;
+
+ start = str;
+ *sz = 0;
+ while (start && *start == '/' && *(start + 1) == '/')
+ start++;
+
+ if (!start || !*start)
+ return NULL;
+
+ for (*sz = 1, p = start + 1; *p && *p != '/'; p++) {
+ (*sz)++;
+ }
+
+ return start;
+}
+
+int streq_paths(const char *a, const char *b)
+{
+ while (a && b) {
+ size_t a_sz, b_sz;
+ const char *a_seg = next_path_segment(a, &a_sz);
+ const char *b_seg = next_path_segment(b, &b_sz);
+
+ /*
+ fprintf(stderr, "A>>>(%zu) '%s'\n", a_sz, a_seg);
+ fprintf(stderr, "B>>>(%zu) '%s'\n", b_sz, b_seg);
+ */
+
+ /* end of the path */
+ if (a_sz + b_sz == 0)
+ return 1;
+
+ /* ignore tailing slash */
+ if (a_sz + b_sz == 1 &&
+ ((a_seg && *a_seg == '/') || (b_seg && *b_seg == '/')))
+ return 1;
+
+ if (a_sz != b_sz || strncmp(a_seg, b_seg, a_sz) != 0)
+ return 0;
+
+ a = a_seg + a_sz;
+ b = b_seg + b_sz;
+ };
+
+ return 0;
+}
+
+char *strnappend(const char *s, const char *suffix, size_t b)
+{
+ size_t a;
+ char *r;
+
+ if (!s && !suffix)
+ return strdup("");
+ if (!s)
+ return strndup(suffix, b);
+ if (!suffix)
+ return strdup(s);
+
+ assert(s);
+ assert(suffix);
+
+ a = strlen(s);
+ if (b > ((size_t) -1) - a)
+ return NULL;
+
+ r = malloc(a + b + 1);
+ if (!r)
+ return NULL;
+
+ memcpy(r, s, a);
+ memcpy(r + a, suffix, b);
+ r[a+b] = 0;
+
+ return r;
+}
+
+char *strappend(const char *s, const char *suffix)
+{
+ return strnappend(s, suffix, suffix ? strlen(suffix) : 0);
+}
+
+char *strfappend(const char *s, const char *format, ...)
+{
+ va_list ap;
+ char *val, *res;
+ int sz;
+
+ va_start(ap, format);
+ sz = vasprintf(&val, format, ap);
+ va_end(ap);
+
+ if (sz < 0)
+ return NULL;
+
+ res = strnappend(s, val, sz);
+ free(val);
+ return res;
+}
+
+static size_t strcspn_escaped(const char *s, const char *reject)
+{
+ int escaped = 0;
+ int n;
+
+ for (n=0; s[n]; n++) {
+ if (escaped)
+ escaped = 0;
+ else if (s[n] == '\\')
+ escaped = 1;
+ else if (strchr(reject, s[n]))
+ break;
+ }
+
+ /* if s ends in \, return index of previous char */
+ return n - escaped;
+}
+
+/* Split a string into words. */
+const char *split(const char **state, size_t *l, const char *separator, int quoted)
+{
+ const char *current;
+
+ current = *state;
+
+ if (!*current) {
+ assert(**state == '\0');
+ return NULL;
+ }
+
+ current += strspn(current, separator);
+ if (!*current) {
+ *state = current;
+ return NULL;
+ }
+
+ if (quoted && strchr("\'\"", *current)) {
+ char quotechars[2] = {*current, '\0'};
+
+ *l = strcspn_escaped(current + 1, quotechars);
+ if (current[*l + 1] == '\0' || current[*l + 1] != quotechars[0] ||
+ (current[*l + 2] && !strchr(separator, current[*l + 2]))) {
+ /* right quote missing or garbage at the end */
+ *state = current;
+ return NULL;
+ }
+ *state = current++ + *l + 2;
+ } else if (quoted) {
+ *l = strcspn_escaped(current, separator);
+ if (current[*l] && !strchr(separator, current[*l])) {
+ /* unfinished escape */
+ *state = current;
+ return NULL;
+ }
+ *state = current + *l;
+ } else {
+ *l = strcspn(current, separator);
+ *state = current + *l;
+ }
+
+ return current;
+}
+
+/* Rewind file pointer forward to new line. */
+int skip_fline(FILE *fp)
+{
+ int ch;
+
+ do {
+ if ((ch = fgetc(fp)) == EOF)
+ return 1;
+ if (ch == '\n')
+ return 0;
+ } while (1);
+}
+
+#ifdef TEST_PROGRAM_STRUTILS
+
+static int test_strutils_sizes(int argc, char *argv[])
+{
+ uintmax_t size = 0;
+ char *hum, *hum2;
+
+ if (argc < 2)
+ return EXIT_FAILURE;
+
+ if (strtosize(argv[1], &size))
+ errx(EXIT_FAILURE, "invalid size '%s' value", argv[1]);
+
+ hum = size_to_human_string(SIZE_SUFFIX_1LETTER, size);
+ hum2 = size_to_human_string(SIZE_SUFFIX_3LETTER |
+ SIZE_SUFFIX_SPACE, size);
+
+ printf("%25s : %20ju : %8s : %12s\n", argv[1], size, hum, hum2);
+ free(hum);
+ free(hum2);
+
+ return EXIT_SUCCESS;
+}
+
+static int test_strutils_cmp_paths(int argc, char *argv[])
+{
+ int rc = streq_paths(argv[1], argv[2]);
+
+ if (argc < 3)
+ return EXIT_FAILURE;
+
+ printf("%s: '%s' '%s'\n", rc == 1 ? "YES" : "NOT", argv[1], argv[2]);
+ return EXIT_SUCCESS;
+}
+
+int main(int argc, char *argv[])
+{
+ if (argc == 3 && strcmp(argv[1], "--size") == 0)
+ return test_strutils_sizes(argc - 1, argv + 1);
+
+ else if (argc == 4 && strcmp(argv[1], "--cmp-paths") == 0)
+ return test_strutils_cmp_paths(argc - 1, argv + 1);
+
+ else {
+ fprintf(stderr, "usage: %1$s --size <number>[suffix]\n"
+ " %1$s --cmp-paths <path> <path>\n",
+ argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ return EXIT_FAILURE;
+}
+#endif /* TEST_PROGRAM_STRUTILS */
diff --git a/lib/strv.c b/lib/strv.c
new file mode 100644
index 0000000..ddc2a0c
--- /dev/null
+++ b/lib/strv.c
@@ -0,0 +1,403 @@
+/*
+ *
+ * Copyright 2010 Lennart Poettering
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ *
+ * Copyright (C) 2015 Karel Zak <kzak@redhat.com>
+ * Modified the original version from systemd project for util-linux.
+ */
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <assert.h>
+
+#include "strutils.h"
+#include "strv.h"
+
+void strv_clear(char **l) {
+ char **k;
+
+ if (!l)
+ return;
+
+ for (k = l; *k; k++)
+ free(*k);
+
+ *l = NULL;
+}
+
+char **strv_free(char **l) {
+ strv_clear(l);
+ free(l);
+ return NULL;
+}
+
+char **strv_copy(char * const *l) {
+ char **r, **k;
+
+ k = r = malloc(sizeof(char *) * (strv_length(l) + 1));
+ if (!r)
+ return NULL;
+
+ if (l)
+ for (; *l; k++, l++) {
+ *k = strdup(*l);
+ if (!*k) {
+ strv_free(r);
+ return NULL;
+ }
+ }
+
+ *k = NULL;
+ return r;
+}
+
+unsigned strv_length(char * const *l) {
+ unsigned n = 0;
+
+ if (!l)
+ return 0;
+
+ for (; *l; l++)
+ n++;
+
+ return n;
+}
+
+char **strv_new_ap(const char *x, va_list ap) {
+ const char *s;
+ char **a;
+ unsigned n = 0, i = 0;
+ va_list aq;
+
+ /* As a special trick we ignore all listed strings that equal
+ * (const char*) -1. This is supposed to be used with the
+ * STRV_IFNOTNULL() macro to include possibly NULL strings in
+ * the string list. */
+
+ if (x) {
+ n = x == (const char*) -1 ? 0 : 1;
+
+ va_copy(aq, ap);
+ while ((s = va_arg(aq, const char*))) {
+ if (s == (const char*) -1)
+ continue;
+
+ n++;
+ }
+
+ va_end(aq);
+ }
+
+ a = malloc(sizeof(char *) * (n + 1));
+ if (!a)
+ return NULL;
+
+ if (x) {
+ if (x != (const char*) -1) {
+ a[i] = strdup(x);
+ if (!a[i])
+ goto fail;
+ i++;
+ }
+
+ while ((s = va_arg(ap, const char*))) {
+
+ if (s == (const char*) -1)
+ continue;
+
+ a[i] = strdup(s);
+ if (!a[i])
+ goto fail;
+
+ i++;
+ }
+ }
+
+ a[i] = NULL;
+
+ return a;
+
+fail:
+ strv_free(a);
+ return NULL;
+}
+
+char **strv_new(const char *x, ...) {
+ char **r;
+ va_list ap;
+
+ va_start(ap, x);
+ r = strv_new_ap(x, ap);
+ va_end(ap);
+
+ return r;
+}
+
+int strv_extend_strv(char ***a, char **b) {
+ int r;
+ char **s;
+
+ STRV_FOREACH(s, b) {
+ r = strv_extend(a, *s);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int strv_extend_strv_concat(char ***a, char **b, const char *suffix) {
+ int r;
+ char **s;
+
+ STRV_FOREACH(s, b) {
+ char *v;
+
+ v = strappend(*s, suffix);
+ if (!v)
+ return -ENOMEM;
+
+ r = strv_push(a, v);
+ if (r < 0) {
+ free(v);
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+
+#define _FOREACH_WORD(word, length, s, separator, quoted, state) \
+ for ((state) = (s), (word) = split(&(state), &(length), (separator), (quoted)); (word); (word) = split(&(state), &(length), (separator), (quoted)))
+
+#define FOREACH_WORD_SEPARATOR(word, length, s, separator, state) \
+ _FOREACH_WORD(word, length, s, separator, false, state)
+
+
+char **strv_split(const char *s, const char *separator) {
+ const char *word, *state;
+ size_t l;
+ unsigned n, i;
+ char **r;
+
+ assert(s);
+
+ n = 0;
+ FOREACH_WORD_SEPARATOR(word, l, s, separator, state)
+ n++;
+
+ r = malloc(sizeof(char *) * (n + 1));
+ if (!r)
+ return NULL;
+
+ i = 0;
+ FOREACH_WORD_SEPARATOR(word, l, s, separator, state) {
+ r[i] = strndup(word, l);
+ if (!r[i]) {
+ strv_free(r);
+ return NULL;
+ }
+
+ i++;
+ }
+
+ r[i] = NULL;
+ return r;
+}
+
+char *strv_join(char **l, const char *separator) {
+ char *r, *e;
+ char **s;
+ size_t n, k;
+
+ if (!separator)
+ separator = " ";
+
+ k = strlen(separator);
+
+ n = 0;
+ STRV_FOREACH(s, l) {
+ if (n != 0)
+ n += k;
+ n += strlen(*s);
+ }
+
+ r = malloc(n + 1);
+ if (!r)
+ return NULL;
+
+ e = r;
+ STRV_FOREACH(s, l) {
+ if (e != r)
+ e = stpcpy(e, separator);
+
+ e = stpcpy(e, *s);
+ }
+
+ *e = 0;
+
+ return r;
+}
+
+int strv_push(char ***l, char *value) {
+ char **c;
+ unsigned n, m;
+
+ if (!value)
+ return 0;
+
+ n = strv_length(*l);
+
+ /* Increase and check for overflow */
+ m = n + 2;
+ if (m < n)
+ return -ENOMEM;
+
+ c = realloc(*l, sizeof(char *) * m);
+ if (!c)
+ return -ENOMEM;
+
+ c[n] = value;
+ c[n+1] = NULL;
+
+ *l = c;
+ return 0;
+}
+
+int strv_push_prepend(char ***l, char *value) {
+ char **c;
+ unsigned n, m, i;
+
+ if (!value)
+ return 0;
+
+ n = strv_length(*l);
+
+ /* increase and check for overflow */
+ m = n + 2;
+ if (m < n)
+ return -ENOMEM;
+
+ c = malloc(sizeof(char *) * m);
+ if (!c)
+ return -ENOMEM;
+
+ for (i = 0; i < n; i++)
+ c[i+1] = (*l)[i];
+
+ c[0] = value;
+ c[n+1] = NULL;
+
+ free(*l);
+ *l = c;
+
+ return 0;
+}
+
+int strv_consume(char ***l, char *value) {
+ int r;
+
+ r = strv_push(l, value);
+ if (r < 0)
+ free(value);
+
+ return r;
+}
+
+int strv_consume_prepend(char ***l, char *value) {
+ int r;
+
+ r = strv_push_prepend(l, value);
+ if (r < 0)
+ free(value);
+
+ return r;
+}
+
+int strv_extend(char ***l, const char *value) {
+ char *v;
+
+ if (!value)
+ return 0;
+
+ v = strdup(value);
+ if (!v)
+ return -ENOMEM;
+
+ return strv_consume(l, v);
+}
+
+char **strv_remove(char **l, const char *s) {
+ char **f, **t;
+
+ if (!l)
+ return NULL;
+
+ assert(s);
+
+ /* Drops every occurrence of s in the string list, edits
+ * in-place. */
+
+ for (f = t = l; *f; f++)
+ if (strcmp(*f, s) == 0)
+ free(*f);
+ else
+ *(t++) = *f;
+
+ *t = NULL;
+ return l;
+}
+
+int strv_extendf(char ***l, const char *format, ...) {
+ va_list ap;
+ char *x;
+ int r;
+
+ va_start(ap, format);
+ r = vasprintf(&x, format, ap);
+ va_end(ap);
+
+ if (r < 0)
+ return -ENOMEM;
+
+ return strv_consume(l, x);
+}
+
+int strv_extendv(char ***l, const char *format, va_list ap) {
+ char *x;
+ int r;
+
+ r = vasprintf(&x, format, ap);
+ if (r < 0)
+ return -ENOMEM;
+
+ return strv_consume(l, x);
+}
+
+char **strv_reverse(char **l) {
+ unsigned n, i;
+
+ n = strv_length(l);
+ if (n <= 1)
+ return l;
+
+ for (i = 0; i < n / 2; i++) {
+ char *t;
+
+ t = l[i];
+ l[i] = l[n-1-i];
+ l[n-1-i] = t;
+ }
+
+ return l;
+}
diff --git a/lib/swapprober.c b/lib/swapprober.c
new file mode 100644
index 0000000..5a4b112
--- /dev/null
+++ b/lib/swapprober.c
@@ -0,0 +1,49 @@
+
+#include "c.h"
+#include "nls.h"
+
+#include "swapheader.h"
+#include "swapprober.h"
+
+blkid_probe get_swap_prober(const char *devname)
+{
+ blkid_probe pr;
+ int rc;
+ const char *version = NULL;
+ char *swap_filter[] = { "swap", NULL };
+
+ pr = blkid_new_probe_from_filename(devname);
+ if (!pr) {
+ warn(_("%s: unable to probe device"), devname);
+ return NULL;
+ }
+
+ blkid_probe_enable_superblocks(pr, TRUE);
+ blkid_probe_set_superblocks_flags(pr,
+ BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID |
+ BLKID_SUBLKS_VERSION);
+
+ blkid_probe_filter_superblocks_type(pr, BLKID_FLTR_ONLYIN, swap_filter);
+
+ rc = blkid_do_safeprobe(pr);
+ if (rc == -1)
+ warn(_("%s: unable to probe device"), devname);
+ else if (rc == -2)
+ warnx(_("%s: ambiguous probing result; use wipefs(8)"), devname);
+ else if (rc == 1)
+ warnx(_("%s: not a valid swap partition"), devname);
+
+ if (rc == 0) {
+ /* Only the SWAPSPACE2 is supported. */
+ if (blkid_probe_lookup_value(pr, "VERSION", &version, NULL) == 0
+ && version
+ && strcmp(version, stringify_value(SWAP_VERSION)))
+ warnx(_("%s: unsupported swap version '%s'"),
+ devname, version);
+ else
+ return pr;
+ }
+
+ blkid_free_probe(pr);
+ return NULL;
+}
diff --git a/lib/sysfs.c b/lib/sysfs.c
new file mode 100644
index 0000000..626451b
--- /dev/null
+++ b/lib/sysfs.c
@@ -0,0 +1,1099 @@
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#include <ctype.h>
+#include <libgen.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "pathnames.h"
+#include "sysfs.h"
+#include "fileutils.h"
+#include "all-io.h"
+#include "debug.h"
+
+static void sysfs_blkdev_deinit_path(struct path_cxt *pc);
+static int sysfs_blkdev_enoent_redirect(struct path_cxt *pc, const char *path, int *dirfd);
+
+/*
+ * Debug stuff (based on include/debug.h)
+ */
+static UL_DEBUG_DEFINE_MASK(ulsysfs);
+UL_DEBUG_DEFINE_MASKNAMES(ulsysfs) = UL_DEBUG_EMPTY_MASKNAMES;
+
+#define ULSYSFS_DEBUG_INIT (1 << 1)
+#define ULSYSFS_DEBUG_CXT (1 << 2)
+
+#define DBG(m, x) __UL_DBG(ulsysfs, ULSYSFS_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(ulsysfs, ULSYSFS_DEBUG_, m, x)
+
+#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulsysfs)
+#include "debugobj.h"
+
+void ul_sysfs_init_debug(void)
+{
+ if (ulsysfs_debug_mask)
+ return;
+ __UL_INIT_DEBUG_FROM_ENV(ulsysfs, ULSYSFS_DEBUG_, 0, ULSYSFS_DEBUG);
+}
+
+struct path_cxt *ul_new_sysfs_path(dev_t devno, struct path_cxt *parent, const char *prefix)
+{
+ struct path_cxt *pc = ul_new_path(NULL);
+
+ if (!pc)
+ return NULL;
+ if (prefix)
+ ul_path_set_prefix(pc, prefix);
+
+ if (sysfs_blkdev_init_path(pc, devno, parent) != 0) {
+ ul_unref_path(pc);
+ return NULL;
+ }
+
+ DBG(CXT, ul_debugobj(pc, "alloc"));
+ return pc;
+}
+
+/*
+ * sysfs_blkdev_* is sysfs extension to ul_path_* API for block devices.
+ *
+ * The function is possible to call in loop and without sysfs_blkdev_deinit_path().
+ * The sysfs_blkdev_deinit_path() is automatically called by ul_unref_path().
+ *
+ */
+int sysfs_blkdev_init_path(struct path_cxt *pc, dev_t devno, struct path_cxt *parent)
+{
+ struct sysfs_blkdev *blk;
+ int rc;
+ char buf[sizeof(_PATH_SYS_DEVBLOCK)
+ + sizeof(stringify_value(UINT32_MAX)) * 2
+ + 3];
+
+ /* define path to devno stuff */
+ snprintf(buf, sizeof(buf), _PATH_SYS_DEVBLOCK "/%d:%d", major(devno), minor(devno));
+ rc = ul_path_set_dir(pc, buf);
+ if (rc)
+ return rc;
+
+ /* make sure path exists */
+ rc = ul_path_get_dirfd(pc);
+ if (rc < 0)
+ return rc;
+
+ /* initialize sysfs blkdev specific stuff */
+ blk = ul_path_get_dialect(pc);
+ if (!blk) {
+ DBG(CXT, ul_debugobj(pc, "alloc new sysfs handler"));
+ blk = calloc(1, sizeof(struct sysfs_blkdev));
+ if (!blk)
+ return -ENOMEM;
+
+ ul_path_set_dialect(pc, blk, sysfs_blkdev_deinit_path);
+ ul_path_set_enoent_redirect(pc, sysfs_blkdev_enoent_redirect);
+ }
+
+ DBG(CXT, ul_debugobj(pc, "init sysfs stuff"));
+
+ blk->devno = devno;
+ sysfs_blkdev_set_parent(pc, parent);
+
+ return 0;
+}
+
+static void sysfs_blkdev_deinit_path(struct path_cxt *pc)
+{
+ struct sysfs_blkdev *blk;
+
+ if (!pc)
+ return;
+
+ DBG(CXT, ul_debugobj(pc, "deinit"));
+
+ blk = ul_path_get_dialect(pc);
+ if (!blk)
+ return;
+
+ ul_ref_path(blk->parent);
+ free(blk);
+
+ ul_path_set_dialect(pc, NULL, NULL);
+}
+
+int sysfs_blkdev_set_parent(struct path_cxt *pc, struct path_cxt *parent)
+{
+ struct sysfs_blkdev *blk = ul_path_get_dialect(pc);
+
+ if (!pc || !blk)
+ return -EINVAL;
+
+ if (blk->parent) {
+ ul_unref_path(blk->parent);
+ blk->parent = NULL;
+ }
+
+ if (parent) {
+ ul_ref_path(parent);
+ blk->parent = parent;
+ } else
+ blk->parent = NULL;
+
+ DBG(CXT, ul_debugobj(pc, "new parent"));
+ return 0;
+}
+
+struct path_cxt *sysfs_blkdev_get_parent(struct path_cxt *pc)
+{
+ struct sysfs_blkdev *blk = ul_path_get_dialect(pc);
+ return blk ? blk->parent : NULL;
+}
+
+/*
+ * Redirects ENOENT errors to the parent, if the path is to the queue/
+ * sysfs directory. For example
+ *
+ * /sys/dev/block/8:1/queue/logical_block_size redirects to
+ * /sys/dev/block/8:0/queue/logical_block_size
+ */
+static int sysfs_blkdev_enoent_redirect(struct path_cxt *pc, const char *path, int *dirfd)
+{
+ struct sysfs_blkdev *blk = ul_path_get_dialect(pc);
+
+ if (blk && blk->parent && strncmp(path, "queue/", 6) == 0) {
+ *dirfd = ul_path_get_dirfd(blk->parent);
+ if (*dirfd >= 0) {
+ DBG(CXT, ul_debugobj(pc, "%s redirected to parent", path));
+ return 0;
+ }
+ }
+ return 1; /* no redirect */
+}
+
+char *sysfs_blkdev_get_name(struct path_cxt *pc, char *buf, size_t bufsiz)
+{
+ char link[PATH_MAX];
+ char *name;
+ ssize_t sz;
+
+ /* read /sys/dev/block/<maj:min> link */
+ sz = ul_path_readlink(pc, link, sizeof(link) - 1, NULL);
+ if (sz < 0)
+ return NULL;
+ link[sz] = '\0';
+
+ name = strrchr(link, '/');
+ if (!name)
+ return NULL;
+
+ name++;
+ sz = strlen(name);
+ if ((size_t) sz + 1 > bufsiz)
+ return NULL;
+
+ memcpy(buf, name, sz + 1);
+ sysfs_devname_sys_to_dev(buf);
+ return buf;
+}
+
+static struct dirent *xreaddir(DIR *dp)
+{
+ struct dirent *d;
+
+ while ((d = readdir(dp))) {
+ if (!strcmp(d->d_name, ".") ||
+ !strcmp(d->d_name, ".."))
+ continue;
+
+ /* blacklist here? */
+ break;
+ }
+ return d;
+}
+
+int sysfs_blkdev_is_partition_dirent(DIR *dir, struct dirent *d, const char *parent_name)
+{
+ char path[NAME_MAX + 6 + 1];
+
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (d->d_type != DT_DIR &&
+ d->d_type != DT_LNK &&
+ d->d_type != DT_UNKNOWN)
+ return 0;
+#endif
+ if (parent_name) {
+ const char *p = parent_name;
+ size_t len;
+
+ /* /dev/sda --> "sda" */
+ if (*parent_name == '/') {
+ p = strrchr(parent_name, '/');
+ if (!p)
+ return 0;
+ p++;
+ }
+
+ len = strlen(p);
+ if (strlen(d->d_name) <= len)
+ return 0;
+
+ /* partitions subdir name is
+ * "<parent>[:digit:]" or "<parent>p[:digit:]"
+ */
+ return strncmp(p, d->d_name, len) == 0 &&
+ ((*(d->d_name + len) == 'p' && isdigit(*(d->d_name + len + 1)))
+ || isdigit(*(d->d_name + len)));
+ }
+
+ /* Cannot use /partition file, not supported on old sysfs */
+ snprintf(path, sizeof(path), "%s/start", d->d_name);
+
+ return faccessat(dirfd(dir), path, R_OK, 0) == 0;
+}
+
+int sysfs_blkdev_count_partitions(struct path_cxt *pc, const char *devname)
+{
+ DIR *dir;
+ struct dirent *d;
+ int r = 0;
+
+ dir = ul_path_opendir(pc, NULL);
+ if (!dir)
+ return 0;
+
+ while ((d = xreaddir(dir))) {
+ if (sysfs_blkdev_is_partition_dirent(dir, d, devname))
+ r++;
+ }
+
+ closedir(dir);
+ return r;
+}
+
+/*
+ * Converts @partno (partition number) to devno of the partition.
+ * The @pc handles wholedisk device.
+ *
+ * Note that this code does not expect any special format of the
+ * partitions devnames.
+ */
+dev_t sysfs_blkdev_partno_to_devno(struct path_cxt *pc, int partno)
+{
+ DIR *dir;
+ struct dirent *d;
+ dev_t devno = 0;
+
+ dir = ul_path_opendir(pc, NULL);
+ if (!dir)
+ return 0;
+
+ while ((d = xreaddir(dir))) {
+ int n;
+
+ if (!sysfs_blkdev_is_partition_dirent(dir, d, NULL))
+ continue;
+
+ if (ul_path_readf_s32(pc, &n, "%s/partition", d->d_name))
+ continue;
+
+ if (n == partno) {
+ if (ul_path_readf_majmin(pc, &devno, "%s/dev", d->d_name) == 0)
+ break;
+ }
+ }
+
+ closedir(dir);
+ DBG(CXT, ul_debugobj(pc, "partno (%d) -> devno (%d)", (int) partno, (int) devno));
+ return devno;
+}
+
+
+/*
+ * Returns slave name if there is only one slave, otherwise returns NULL.
+ * The result should be deallocated by free().
+ */
+char *sysfs_blkdev_get_slave(struct path_cxt *pc)
+{
+ DIR *dir;
+ struct dirent *d;
+ char *name = NULL;
+
+ dir = ul_path_opendir(pc, "slaves");
+ if (!dir)
+ return NULL;
+
+ while ((d = xreaddir(dir))) {
+ if (name)
+ goto err; /* more slaves */
+ name = strdup(d->d_name);
+ }
+
+ closedir(dir);
+ return name;
+err:
+ free(name);
+ closedir(dir);
+ return NULL;
+}
+
+
+#define SUBSYSTEM_LINKNAME "/subsystem"
+
+/*
+ * For example:
+ *
+ * chain: /sys/dev/block/../../devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/ \
+ * 1-1.2:1.0/host65/target65:0:0/65:0:0:0/block/sdb
+ *
+ * The function check if <chain>/subsystem symlink exists, if yes then returns
+ * basename of the readlink result, and remove the last subdirectory from the
+ * <chain> path.
+ */
+static char *get_subsystem(char *chain, char *buf, size_t bufsz)
+{
+ size_t len;
+ char *p;
+
+ if (!chain || !*chain)
+ return NULL;
+
+ len = strlen(chain);
+ if (len + sizeof(SUBSYSTEM_LINKNAME) > PATH_MAX)
+ return NULL;
+
+ do {
+ ssize_t sz;
+
+ /* append "/subsystem" to the path */
+ memcpy(chain + len, SUBSYSTEM_LINKNAME, sizeof(SUBSYSTEM_LINKNAME));
+
+ /* try if subsystem symlink exists */
+ sz = readlink(chain, buf, bufsz - 1);
+
+ /* remove last subsystem from chain */
+ chain[len] = '\0';
+ p = strrchr(chain, '/');
+ if (p) {
+ *p = '\0';
+ len = p - chain;
+ }
+
+ if (sz > 0) {
+ /* we found symlink to subsystem, return basename */
+ buf[sz] = '\0';
+ return basename(buf);
+ }
+
+ } while (p);
+
+ return NULL;
+}
+
+/*
+ * Returns complete path to the device, the patch contains all subsystems
+ * used for the device.
+ */
+char *sysfs_blkdev_get_devchain(struct path_cxt *pc, char *buf, size_t bufsz)
+{
+ /* read /sys/dev/block/<maj>:<min> symlink */
+ ssize_t sz = ul_path_readlink(pc, buf, bufsz, NULL);
+ const char *prefix;
+ size_t psz = 0;
+
+ if (sz <= 0 || sz + sizeof(_PATH_SYS_DEVBLOCK "/") > bufsz)
+ return NULL;
+
+ buf[sz++] = '\0';
+ prefix = ul_path_get_prefix(pc);
+ if (prefix)
+ psz = strlen(prefix);
+
+ /* create absolute patch from the link */
+ memmove(buf + psz + sizeof(_PATH_SYS_DEVBLOCK "/") - 1, buf, sz);
+ if (prefix)
+ memcpy(buf, prefix, psz);
+
+ memcpy(buf + psz, _PATH_SYS_DEVBLOCK "/", sizeof(_PATH_SYS_DEVBLOCK "/") - 1);
+ return buf;
+}
+
+/*
+ * The @subsys returns the next subsystem in the chain. Function modifies
+ * @devchain string.
+ *
+ * Returns: 0 in success, <0 on error, 1 on end of chain
+ */
+int sysfs_blkdev_next_subsystem(struct path_cxt *pc __attribute__((unused)),
+ char *devchain, char **subsys)
+{
+ char subbuf[PATH_MAX];
+ char *sub;
+
+ if (!subsys || !devchain)
+ return -EINVAL;
+
+ *subsys = NULL;
+
+ while ((sub = get_subsystem(devchain, subbuf, sizeof(subbuf)))) {
+ *subsys = strdup(sub);
+ if (!*subsys)
+ return -ENOMEM;
+ return 0;
+ }
+
+ return 1;
+}
+
+
+static int is_hotpluggable_subsystem(const char *name)
+{
+ static const char * const hotplug_subsystems[] = {
+ "usb",
+ "ieee1394",
+ "pcmcia",
+ "mmc",
+ "ccw"
+ };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(hotplug_subsystems); i++)
+ if (strcmp(name, hotplug_subsystems[i]) == 0)
+ return 1;
+
+ return 0;
+}
+
+int sysfs_blkdev_is_hotpluggable(struct path_cxt *pc)
+{
+ char buf[PATH_MAX], *chain, *sub;
+ int rc = 0;
+
+
+ /* check /sys/dev/block/<maj>:<min>/removable attribute */
+ if (ul_path_read_s32(pc, &rc, "removable") == 0 && rc == 1)
+ return 1;
+
+ chain = sysfs_blkdev_get_devchain(pc, buf, sizeof(buf));
+
+ while (chain && sysfs_blkdev_next_subsystem(pc, chain, &sub) == 0) {
+ rc = is_hotpluggable_subsystem(sub);
+ if (rc) {
+ free(sub);
+ break;
+ }
+ free(sub);
+ }
+
+ return rc;
+}
+
+static int get_dm_wholedisk(struct path_cxt *pc, char *diskname,
+ size_t len, dev_t *diskdevno)
+{
+ int rc = 0;
+ char *name;
+
+ /* Note, sysfs_blkdev_get_slave() returns the first slave only,
+ * if there is more slaves, then return NULL
+ */
+ name = sysfs_blkdev_get_slave(pc);
+ if (!name)
+ return -1;
+
+ if (diskname && len) {
+ strncpy(diskname, name, len);
+ diskname[len - 1] = '\0';
+ }
+
+ if (diskdevno) {
+ *diskdevno = __sysfs_devname_to_devno(ul_path_get_prefix(pc), name, NULL);
+ if (!*diskdevno)
+ rc = -1;
+ }
+
+ free(name);
+ return rc;
+}
+
+/*
+ * Returns by @diskdevno whole disk device devno and (optionally) by
+ * @diskname the whole disk device name.
+ */
+int sysfs_blkdev_get_wholedisk( struct path_cxt *pc,
+ char *diskname,
+ size_t len,
+ dev_t *diskdevno)
+{
+ int is_part = 0;
+
+ if (!pc)
+ return -1;
+
+ is_part = ul_path_access(pc, F_OK, "partition") == 0;
+ if (!is_part) {
+ /*
+ * Extra case for partitions mapped by device-mapper.
+ *
+ * All regular partitions (added by BLKPG ioctl or kernel PT
+ * parser) have the /sys/.../partition file. The partitions
+ * mapped by DM don't have such file, but they have "part"
+ * prefix in DM UUID.
+ */
+ char *uuid = NULL, *tmp, *prefix;
+
+ ul_path_read_string(pc, &uuid, "dm/uuid");
+ tmp = uuid;
+ prefix = uuid ? strsep(&tmp, "-") : NULL;
+
+ if (prefix && strncasecmp(prefix, "part", 4) == 0)
+ is_part = 1;
+ free(uuid);
+
+ if (is_part &&
+ get_dm_wholedisk(pc, diskname, len, diskdevno) == 0)
+ /*
+ * partitioned device, mapped by DM
+ */
+ goto done;
+
+ is_part = 0;
+ }
+
+ if (!is_part) {
+ /*
+ * unpartitioned device
+ */
+ if (diskname && !sysfs_blkdev_get_name(pc, diskname, len))
+ goto err;
+ if (diskdevno)
+ *diskdevno = sysfs_blkdev_get_devno(pc);
+
+ } else {
+ /*
+ * partitioned device
+ * - readlink /sys/dev/block/8:1 = ../../block/sda/sda1
+ * - dirname ../../block/sda/sda1 = ../../block/sda
+ * - basename ../../block/sda = sda
+ */
+ char linkpath[PATH_MAX];
+ char *name;
+ ssize_t linklen;
+
+ linklen = ul_path_readlink(pc, linkpath, sizeof(linkpath) - 1, NULL);
+ if (linklen < 0)
+ goto err;
+ linkpath[linklen] = '\0';
+
+ stripoff_last_component(linkpath); /* dirname */
+ name = stripoff_last_component(linkpath); /* basename */
+ if (!name)
+ goto err;
+
+ sysfs_devname_sys_to_dev(name);
+ if (diskname && len) {
+ strncpy(diskname, name, len);
+ diskname[len - 1] = '\0';
+ }
+
+ if (diskdevno) {
+ *diskdevno = __sysfs_devname_to_devno(ul_path_get_prefix(pc), name, NULL);
+ if (!*diskdevno)
+ goto err;
+ }
+ }
+
+done:
+ return 0;
+err:
+ return -1;
+}
+
+int sysfs_devno_to_wholedisk(dev_t devno, char *diskname,
+ size_t len, dev_t *diskdevno)
+{
+ struct path_cxt *pc;
+ int rc = 0;
+
+ if (!devno)
+ return -EINVAL;
+ pc = ul_new_sysfs_path(devno, NULL, NULL);
+ if (!pc)
+ return -ENOMEM;
+
+ rc = sysfs_blkdev_get_wholedisk(pc, diskname, len, diskdevno);
+ ul_unref_path(pc);
+ return rc;
+}
+
+/*
+ * Returns 1 if the device is private device mapper device. The @uuid
+ * (if not NULL) returns DM device UUID, use free() to deallocate.
+ */
+int sysfs_devno_is_dm_private(dev_t devno, char **uuid)
+{
+ struct path_cxt *pc = NULL;
+ char *id = NULL;
+ int rc = 0;
+
+ pc = ul_new_sysfs_path(devno, NULL, NULL);
+ if (!pc)
+ goto done;
+ if (ul_path_read_string(pc, &id, "dm/uuid") <= 0 || !id)
+ goto done;
+
+ /* Private LVM devices use "LVM-<uuid>-<name>" uuid format (important
+ * is the "LVM" prefix and "-<name>" postfix).
+ */
+ if (strncmp(id, "LVM-", 4) == 0) {
+ char *p = strrchr(id + 4, '-');
+
+ if (p && *(p + 1))
+ rc = 1;
+
+ /* Private Stratis devices prefix the UUID with "stratis-1-private"
+ */
+ } else if (strncmp(id, "stratis-1-private", 17) == 0) {
+ rc = 1;
+ }
+done:
+ ul_unref_path(pc);
+ if (uuid)
+ *uuid = id;
+ else
+ free(id);
+ return rc;
+}
+
+/*
+ * Return 0 or 1, or < 0 in case of error
+ */
+int sysfs_devno_is_wholedisk(dev_t devno)
+{
+ dev_t disk;
+
+ if (sysfs_devno_to_wholedisk(devno, NULL, 0, &disk) != 0)
+ return -1;
+
+ return devno == disk;
+}
+
+
+int sysfs_blkdev_scsi_get_hctl(struct path_cxt *pc, int *h, int *c, int *t, int *l)
+{
+ char buf[PATH_MAX], *hctl;
+ struct sysfs_blkdev *blk;
+ ssize_t len;
+
+ blk = ul_path_get_dialect(pc);
+
+ if (!blk || blk->hctl_error)
+ return -EINVAL;
+ if (blk->has_hctl)
+ goto done;
+
+ blk->hctl_error = 1;
+ len = ul_path_readlink(pc, buf, sizeof(buf) - 1, "device");
+ if (len < 0)
+ return len;
+
+ buf[len] = '\0';
+ hctl = strrchr(buf, '/');
+ if (!hctl)
+ return -1;
+ hctl++;
+
+ if (sscanf(hctl, "%u:%u:%u:%u", &blk->scsi_host, &blk->scsi_channel,
+ &blk->scsi_target, &blk->scsi_lun) != 4)
+ return -1;
+
+ blk->has_hctl = 1;
+done:
+ if (h)
+ *h = blk->scsi_host;
+ if (c)
+ *c = blk->scsi_channel;
+ if (t)
+ *t = blk->scsi_target;
+ if (l)
+ *l = blk->scsi_lun;
+
+ blk->hctl_error = 0;
+ return 0;
+}
+
+
+static char *scsi_host_attribute_path(
+ struct path_cxt *pc,
+ const char *type,
+ char *buf,
+ size_t bufsz,
+ const char *attr)
+{
+ int len;
+ int host;
+ const char *prefix;
+
+ if (sysfs_blkdev_scsi_get_hctl(pc, &host, NULL, NULL, NULL))
+ return NULL;
+
+ prefix = ul_path_get_prefix(pc);
+ if (!prefix)
+ prefix = "";
+
+ if (attr)
+ len = snprintf(buf, bufsz, "%s%s/%s_host/host%d/%s",
+ prefix, _PATH_SYS_CLASS, type, host, attr);
+ else
+ len = snprintf(buf, bufsz, "%s%s/%s_host/host%d",
+ prefix, _PATH_SYS_CLASS, type, host);
+
+ return (len < 0 || (size_t) len >= bufsz) ? NULL : buf;
+}
+
+char *sysfs_blkdev_scsi_host_strdup_attribute(struct path_cxt *pc,
+ const char *type, const char *attr)
+{
+ char buf[1024];
+ int rc;
+ FILE *f;
+
+ if (!attr || !type ||
+ !scsi_host_attribute_path(pc, type, buf, sizeof(buf), attr))
+ return NULL;
+
+ if (!(f = fopen(buf, "r" UL_CLOEXECSTR)))
+ return NULL;
+
+ rc = fscanf(f, "%1023[^\n]", buf);
+ fclose(f);
+
+ return rc == 1 ? strdup(buf) : NULL;
+}
+
+int sysfs_blkdev_scsi_host_is(struct path_cxt *pc, const char *type)
+{
+ char buf[PATH_MAX];
+ struct stat st;
+
+ if (!type || !scsi_host_attribute_path(pc, type,
+ buf, sizeof(buf), NULL))
+ return 0;
+
+ return stat(buf, &st) == 0 && S_ISDIR(st.st_mode);
+}
+
+static char *scsi_attribute_path(struct path_cxt *pc,
+ char *buf, size_t bufsz, const char *attr)
+{
+ int len, h, c, t, l;
+ const char *prefix;
+
+ if (sysfs_blkdev_scsi_get_hctl(pc, &h, &c, &t, &l) != 0)
+ return NULL;
+
+ prefix = ul_path_get_prefix(pc);
+ if (!prefix)
+ prefix = "";
+
+ if (attr)
+ len = snprintf(buf, bufsz, "%s%s/devices/%d:%d:%d:%d/%s",
+ prefix, _PATH_SYS_SCSI,
+ h,c,t,l, attr);
+ else
+ len = snprintf(buf, bufsz, "%s%s/devices/%d:%d:%d:%d",
+ prefix, _PATH_SYS_SCSI,
+ h,c,t,l);
+ return (len < 0 || (size_t) len >= bufsz) ? NULL : buf;
+}
+
+int sysfs_blkdev_scsi_has_attribute(struct path_cxt *pc, const char *attr)
+{
+ char path[PATH_MAX];
+ struct stat st;
+
+ if (!scsi_attribute_path(pc, path, sizeof(path), attr))
+ return 0;
+
+ return stat(path, &st) == 0;
+}
+
+int sysfs_blkdev_scsi_path_contains(struct path_cxt *pc, const char *pattern)
+{
+ char path[PATH_MAX], linkc[PATH_MAX];
+ struct stat st;
+ ssize_t len;
+
+ if (!scsi_attribute_path(pc, path, sizeof(path), NULL))
+ return 0;
+
+ if (stat(path, &st) != 0)
+ return 0;
+
+ len = readlink(path, linkc, sizeof(linkc) - 1);
+ if (len < 0)
+ return 0;
+
+ linkc[len] = '\0';
+ return strstr(linkc, pattern) != NULL;
+}
+
+static dev_t read_devno(const char *path)
+{
+ FILE *f;
+ int maj = 0, min = 0;
+ dev_t dev = 0;
+
+ f = fopen(path, "r" UL_CLOEXECSTR);
+ if (!f)
+ return 0;
+
+ if (fscanf(f, "%d:%d", &maj, &min) == 2)
+ dev = makedev(maj, min);
+ fclose(f);
+ return dev;
+}
+
+dev_t __sysfs_devname_to_devno(const char *prefix, const char *name, const char *parent)
+{
+ char buf[PATH_MAX];
+ char *_name = NULL; /* name as encoded in sysfs */
+ dev_t dev = 0;
+ int len;
+
+ if (!prefix)
+ prefix = "";
+
+ assert(name);
+
+ if (strncmp("/dev/", name, 5) == 0) {
+ /*
+ * Read from /dev
+ */
+ struct stat st;
+
+ if (stat(name, &st) == 0) {
+ dev = st.st_rdev;
+ goto done;
+ }
+ name += 5; /* unaccesible, or not node in /dev */
+ }
+
+ _name = strdup(name);
+ if (!_name)
+ goto done;
+ sysfs_devname_dev_to_sys(_name);
+
+ if (parent && strncmp("dm-", name, 3)) {
+ /*
+ * Create path to /sys/block/<parent>/<name>/dev
+ */
+ char *_parent = strdup(parent);
+
+ if (!_parent) {
+ free(_parent);
+ goto done;
+ }
+ sysfs_devname_dev_to_sys(_parent);
+ len = snprintf(buf, sizeof(buf),
+ "%s" _PATH_SYS_BLOCK "/%s/%s/dev",
+ prefix, _parent, _name);
+ free(_parent);
+ if (len < 0 || (size_t) len >= sizeof(buf))
+ goto done;
+
+ /* don't try anything else for dm-* */
+ dev = read_devno(buf);
+ goto done;
+ }
+
+ /*
+ * Read from /sys/block/<sysname>/dev
+ */
+ len = snprintf(buf, sizeof(buf),
+ "%s" _PATH_SYS_BLOCK "/%s/dev",
+ prefix, _name);
+ if (len < 0 || (size_t) len >= sizeof(buf))
+ goto done;
+ dev = read_devno(buf);
+
+ if (!dev) {
+ /*
+ * Read from /sys/block/<sysname>/device/dev
+ */
+ len = snprintf(buf, sizeof(buf),
+ "%s" _PATH_SYS_BLOCK "/%s/device/dev",
+ prefix, _name);
+ if (len < 0 || (size_t) len >= sizeof(buf))
+ goto done;
+ dev = read_devno(buf);
+ }
+done:
+ free(_name);
+ return dev;
+}
+
+dev_t sysfs_devname_to_devno(const char *name)
+{
+ return __sysfs_devname_to_devno(NULL, name, NULL);
+}
+
+char *sysfs_blkdev_get_path(struct path_cxt *pc, char *buf, size_t bufsiz)
+{
+ const char *name = sysfs_blkdev_get_name(pc, buf, bufsiz);
+ char *res = NULL;
+ size_t sz;
+ struct stat st;
+
+ if (!name)
+ goto done;
+
+ sz = strlen(name);
+ if (sz + sizeof("/dev/") > bufsiz)
+ goto done;
+
+ /* create the final "/dev/<name>" string */
+ memmove(buf + 5, name, sz + 1);
+ memcpy(buf, "/dev/", 5);
+
+ if (!stat(buf, &st) && S_ISBLK(st.st_mode) && st.st_rdev == sysfs_blkdev_get_devno(pc))
+ res = buf;
+done:
+ return res;
+}
+
+dev_t sysfs_blkdev_get_devno(struct path_cxt *pc)
+{
+ return ((struct sysfs_blkdev *) ul_path_get_dialect(pc))->devno;
+}
+
+/*
+ * Returns devname (e.g. "/dev/sda1") for the given devno.
+ *
+ * Please, use more robust blkid_devno_to_devname() in your applications.
+ */
+char *sysfs_devno_to_devpath(dev_t devno, char *buf, size_t bufsiz)
+{
+ struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL);
+ char *res = NULL;
+
+ if (pc) {
+ res = sysfs_blkdev_get_path(pc, buf, bufsiz);
+ ul_unref_path(pc);
+ }
+ return res;
+}
+
+char *sysfs_devno_to_devname(dev_t devno, char *buf, size_t bufsiz)
+{
+ struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL);
+ char *res = NULL;
+
+ if (pc) {
+ res = sysfs_blkdev_get_name(pc, buf, bufsiz);
+ ul_unref_path(pc);
+ }
+ return res;
+}
+
+
+
+#ifdef TEST_PROGRAM_SYSFS
+#include <errno.h>
+#include <err.h>
+#include <stdlib.h>
+
+int main(int argc, char *argv[])
+{
+ struct path_cxt *pc;
+ char *devname;
+ dev_t devno, disk_devno;
+ char path[PATH_MAX], *sub, *chain;
+ char diskname[32];
+ int i, is_part, rc = EXIT_SUCCESS;
+ uint64_t u64;
+
+ if (argc != 2)
+ errx(EXIT_FAILURE, "usage: %s <devname>", argv[0]);
+
+ ul_sysfs_init_debug();
+
+ devname = argv[1];
+ devno = sysfs_devname_to_devno(devname);
+
+ if (!devno)
+ err(EXIT_FAILURE, "failed to read devno");
+
+ printf("non-context:\n");
+ printf(" DEVNO: %u (%d:%d)\n", (unsigned int) devno, major(devno), minor(devno));
+ printf(" DEVNAME: %s\n", sysfs_devno_to_devname(devno, path, sizeof(path)));
+ printf(" DEVPATH: %s\n", sysfs_devno_to_devpath(devno, path, sizeof(path)));
+
+ sysfs_devno_to_wholedisk(devno, diskname, sizeof(diskname), &disk_devno);
+ printf(" WHOLEDISK-DEVNO: %u (%d:%d)\n", (unsigned int) disk_devno, major(disk_devno), minor(disk_devno));
+ printf(" WHOLEDISK-DEVNAME: %s\n", diskname);
+
+ pc = ul_new_sysfs_path(devno, NULL, NULL);
+ if (!pc)
+ goto done;
+
+ printf("context based:\n");
+ devno = sysfs_blkdev_get_devno(pc);
+ printf(" DEVNO: %u (%d:%d)\n", (unsigned int) devno, major(devno), minor(devno));
+ printf(" DEVNAME: %s\n", sysfs_blkdev_get_name(pc, path, sizeof(path)));
+ printf(" DEVPATH: %s\n", sysfs_blkdev_get_path(pc, path, sizeof(path)));
+
+ sysfs_devno_to_wholedisk(devno, diskname, sizeof(diskname), &disk_devno);
+ printf(" WHOLEDISK-DEVNO: %u (%d:%d)\n", (unsigned int) disk_devno, major(disk_devno), minor(disk_devno));
+ printf(" WHOLEDISK-DEVNAME: %s\n", diskname);
+
+ is_part = ul_path_access(pc, F_OK, "partition") == 0;
+ printf(" PARTITION: %s\n", is_part ? "YES" : "NOT");
+
+ if (is_part && disk_devno) {
+ struct path_cxt *disk_pc = ul_new_sysfs_path(disk_devno, NULL, NULL);
+ sysfs_blkdev_set_parent(pc, disk_pc);
+
+ ul_unref_path(disk_pc);
+ }
+
+ printf(" HOTPLUG: %s\n", sysfs_blkdev_is_hotpluggable(pc) ? "yes" : "no");
+ printf(" SLAVES: %d\n", ul_path_count_dirents(pc, "slaves"));
+
+ if (!is_part) {
+ printf("First 5 partitions:\n");
+ for (i = 1; i <= 5; i++) {
+ dev_t dev = sysfs_blkdev_partno_to_devno(pc, i);
+ if (dev)
+ printf("\t#%d %d:%d\n", i, major(dev), minor(dev));
+ }
+ }
+
+ if (ul_path_read_u64(pc, &u64, "size") != 0)
+ printf(" (!) read SIZE failed\n");
+ else
+ printf(" SIZE: %jd\n", u64);
+
+ if (ul_path_read_s32(pc, &i, "queue/hw_sector_size"))
+ printf(" (!) read SECTOR failed\n");
+ else
+ printf(" SECTOR: %d\n", i);
+
+
+ chain = sysfs_blkdev_get_devchain(pc, path, sizeof(path));
+ printf(" SUBSUSTEMS:\n");
+
+ while (chain && sysfs_blkdev_next_subsystem(pc, chain, &sub) == 0) {
+ printf("\t%s\n", sub);
+ free(sub);
+ }
+
+ rc = EXIT_SUCCESS;
+done:
+ ul_unref_path(pc);
+ return rc;
+}
+#endif /* TEST_PROGRAM_SYSFS */
diff --git a/lib/terminal-colors.d.5 b/lib/terminal-colors.d.5
new file mode 100644
index 0000000..e0bec23
--- /dev/null
+++ b/lib/terminal-colors.d.5
@@ -0,0 +1,192 @@
+.\" terminal-colors.d.5 --
+.\" Copyright 2014 Ondrej Oprala <ooprala@redhat.com>
+.\" Copyright (C) 2014 Karel Zak <kzak@redhat.com>
+.\" Copyright 2014 Red Hat, Inc.
+.\" May be distributed under the GNU General Public License
+.TH "TERMINAL_COLORS.D" "5" "January 2014" "util-linux" "terminal-colors.d"
+.SH "NAME"
+terminal-colors.d \- Configure output colorization for various utilities
+.SH "SYNOPSIS"
+/etc/terminal-colors\&.d/[[\fIname\fR][@\fIterm\fR]\&.][\fItype\fR]
+.SH "DESCRIPTION"
+Files in this directory determine the default behavior for utilities
+when coloring output.
+
+The
+.I name
+is a utility name. The name is optional and when none is specified then the
+file is used for all unspecified utilities.
+
+The
+.I term
+is a terminal identifier (the TERM environment variable).
+The terminal identifier is optional and when none is specified then the file
+is used for all unspecified terminals.
+
+The
+.I type
+is a file type. Supported file types are:
+.TP
+.B disable
+Turns off output colorization for all compatible utilities.
+.TP
+.B enable
+Turns on output colorization; any matching
+.B disable
+files are ignored.
+.TP
+.B scheme
+Specifies colors used for output. The file format may be specific to the utility,
+the default format is described below.
+.PP
+If there are more files that match for a utility, then the file with the more
+specific filename wins. For example, the filename "@xterm.scheme" has less
+priority than "dmesg@xterm.scheme". The lowest priority are those files without a
+utility name and terminal identifier (e.g. "disable").
+
+The user-specific
+.I $XDG_CONFIG_HOME/terminal-colors.d
+or
+.I $HOME/.config/terminal-colors.d
+overrides the global setting.
+
+.SH EXAMPLES
+Disable colors for all compatible utilities:
+.RS
+.br
+.B "touch /etc/terminal-colors.d/disable"
+.br
+.RE
+
+Disable colors for all compatible utils on a vt100 terminal:
+.RS
+.br
+.B "touch /etc/terminal-colors.d/@vt100.disable"
+.br
+.RE
+
+Disable colors for all compatible utils except dmesg(1):
+.RS
+.br
+.B "touch /etc/terminal-colors.d/disable"
+.sp
+.B "touch /etc/terminal-colors.d/dmesg.enable"
+.br
+.RE
+
+.SH DEFAULT SCHEME FILES FORMAT
+The following statement is recognized:
+
+.RS
+.br
+.B "name color-sequence"
+.br
+.RE
+
+The
+.B name
+is a logical name of color sequence (for example "error"). The names are
+specific to the utilities. For more details always see the COLORS section
+in the man page for the utility.
+
+The
+.B color-sequence
+is a color name, ASCII color sequences or escape sequences.
+
+.SS Color names
+black, blink, blue, bold, brown, cyan, darkgray, gray, green, halfbright,
+lightblue, lightcyan, lightgray, lightgreen, lightmagenta, lightred, magenta,
+red, reset, reverse, and yellow.
+
+.SS ANSI color sequences
+The color sequences are composed of sequences of numbers
+separated by semicolons. The most common codes are:
+.sp
+.RS
+.TS
+l l.
+ 0 to restore default color
+ 1 for brighter colors
+ 4 for underlined text
+ 5 for flashing text
+30 for black foreground
+31 for red foreground
+32 for green foreground
+33 for yellow (or brown) foreground
+34 for blue foreground
+35 for purple foreground
+36 for cyan foreground
+37 for white (or gray) foreground
+40 for black background
+41 for red background
+42 for green background
+43 for yellow (or brown) background
+44 for blue background
+45 for purple background
+46 for cyan background
+47 for white (or gray) background
+.TE
+.RE
+.SS Escape sequences
+To specify control or blank characters in the color sequences,
+C-style \e-escaped notation can be used:
+.sp
+.RS
+.TS
+lb l.
+\ea Bell (ASCII 7)
+\eb Backspace (ASCII 8)
+\ee Escape (ASCII 27)
+\ef Form feed (ASCII 12)
+\en Newline (ASCII 10)
+\er Carriage Return (ASCII 13)
+\et Tab (ASCII 9)
+\ev Vertical Tab (ASCII 11)
+\e? Delete (ASCII 127)
+\e_ Space
+\e\e Backslash (\e)
+\e^ Caret (^)
+\e# Hash mark (#)
+.TE
+.RE
+.sp
+Please note that escapes are necessary to enter a space, backslash,
+caret, or any control character anywhere in the string, as well as a
+hash mark as the first character.
+
+For example, to use a red background for alert messages in the output of
+.BR dmesg (1),
+use:
+
+.RS
+.br
+.B "echo 'alert 37;41' >> /etc/terminal-colors.d/dmesg.scheme"
+.br
+.RE
+
+.SS Comments
+Lines where the first non-blank character is a # (hash) are ignored.
+Any other use of the hash character is not interpreted as introducing
+a comment.
+
+.SH FILES
+.I $XDG_CONFIG_HOME/terminal-colors.d
+.br
+.I $HOME/.config/terminal-colors.d
+.br
+.I /etc/terminal-colors.d
+
+.SH ENVIRONMENT
+.IP TERMINAL_COLORS_DEBUG=all
+enables debug output.
+
+.SH COMPATIBILITY
+The terminal-colors.d functionality is currently supported by all util-linux
+utilities which provides colorized output. For more details always see the
+COLORS section in the man page for the utility.
+
+.SH AVAILABILITY
+terminal-colors.d is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/lib/timer.c b/lib/timer.c
new file mode 100644
index 0000000..210c726
--- /dev/null
+++ b/lib/timer.c
@@ -0,0 +1,65 @@
+/*
+ * Please, don't add this file to libcommon because timers requires
+ * -lrt on systems with old libc (and probably also -lpthread for static
+ * build).
+ */
+#include <time.h>
+#include <signal.h>
+#include <sys/time.h>
+
+#include "c.h"
+#include "timer.h"
+
+/*
+ * Note the timeout is used for the first signal, then the signal is send
+ * repeatedly in interval ~1% of the original timeout to avoid race in signal
+ * handling -- for example you want to use timer to define timeout for a
+ * syscall:
+ *
+ * setup_timer()
+ * syscall()
+ * cancel_timer()
+ *
+ * if the timeout is too short than it's possible that the signal is delivered
+ * before application enter the syscall function. For this reason timer send
+ * the signal repeatedly.
+ *
+ * The applications need to ensure that they can tolerate multiple signal
+ * deliveries.
+ */
+int setup_timer(timer_t * t_id, struct itimerval *timeout,
+ void (*timeout_handler)(int, siginfo_t *, void *))
+{
+ time_t sec = timeout->it_value.tv_sec;
+ long usec = timeout->it_value.tv_usec;
+ struct sigaction sig_a;
+ static struct sigevent sig_e = {
+ .sigev_notify = SIGEV_SIGNAL,
+ .sigev_signo = SIGALRM
+ };
+ struct itimerspec val = {
+ .it_value.tv_sec = sec,
+ .it_value.tv_nsec = usec * 1000,
+ .it_interval.tv_sec = sec / 100,
+ .it_interval.tv_nsec = (sec ? sec % 100 : 1) * 10*1000*1000
+ };
+
+ if (sigemptyset(&sig_a.sa_mask))
+ return 1;
+
+ sig_a.sa_flags = SA_SIGINFO;
+ sig_a.sa_sigaction = timeout_handler;
+
+ if (sigaction(SIGALRM, &sig_a, NULL))
+ return 1;
+ if (timer_create(CLOCK_MONOTONIC, &sig_e, t_id))
+ return 1;
+ if (timer_settime(*t_id, 0, &val, NULL))
+ return 1;
+ return 0;
+}
+
+void cancel_timer(timer_t *t_id)
+{
+ timer_delete(*t_id);
+}
diff --git a/lib/timeutils.c b/lib/timeutils.c
new file mode 100644
index 0000000..9c286ae
--- /dev/null
+++ b/lib/timeutils.c
@@ -0,0 +1,594 @@
+/***
+ First set of functions in this file are part of systemd, and were
+ copied to util-linux at August 2013.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with util-linux; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "timeutils.h"
+
+#define WHITESPACE " \t\n\r"
+
+#define streq(a,b) (strcmp((a),(b)) == 0)
+
+static int parse_sec(const char *t, usec_t *usec)
+{
+ static const struct {
+ const char *suffix;
+ usec_t usec;
+ } table[] = {
+ { "seconds", USEC_PER_SEC },
+ { "second", USEC_PER_SEC },
+ { "sec", USEC_PER_SEC },
+ { "s", USEC_PER_SEC },
+ { "minutes", USEC_PER_MINUTE },
+ { "minute", USEC_PER_MINUTE },
+ { "min", USEC_PER_MINUTE },
+ { "months", USEC_PER_MONTH },
+ { "month", USEC_PER_MONTH },
+ { "msec", USEC_PER_MSEC },
+ { "ms", USEC_PER_MSEC },
+ { "m", USEC_PER_MINUTE },
+ { "hours", USEC_PER_HOUR },
+ { "hour", USEC_PER_HOUR },
+ { "hr", USEC_PER_HOUR },
+ { "h", USEC_PER_HOUR },
+ { "days", USEC_PER_DAY },
+ { "day", USEC_PER_DAY },
+ { "d", USEC_PER_DAY },
+ { "weeks", USEC_PER_WEEK },
+ { "week", USEC_PER_WEEK },
+ { "w", USEC_PER_WEEK },
+ { "years", USEC_PER_YEAR },
+ { "year", USEC_PER_YEAR },
+ { "y", USEC_PER_YEAR },
+ { "usec", 1ULL },
+ { "us", 1ULL },
+ { "", USEC_PER_SEC }, /* default is sec */
+ };
+
+ const char *p;
+ usec_t r = 0;
+ int something = FALSE;
+
+ assert(t);
+ assert(usec);
+
+ p = t;
+ for (;;) {
+ long long l, z = 0;
+ char *e;
+ unsigned i, n = 0;
+
+ p += strspn(p, WHITESPACE);
+
+ if (*p == 0) {
+ if (!something)
+ return -EINVAL;
+
+ break;
+ }
+
+ errno = 0;
+ l = strtoll(p, &e, 10);
+
+ if (errno > 0)
+ return -errno;
+
+ if (l < 0)
+ return -ERANGE;
+
+ if (*e == '.') {
+ char *b = e + 1;
+
+ errno = 0;
+ z = strtoll(b, &e, 10);
+ if (errno > 0)
+ return -errno;
+
+ if (z < 0)
+ return -ERANGE;
+
+ if (e == b)
+ return -EINVAL;
+
+ n = e - b;
+
+ } else if (e == p)
+ return -EINVAL;
+
+ e += strspn(e, WHITESPACE);
+
+ for (i = 0; i < ARRAY_SIZE(table); i++)
+ if (startswith(e, table[i].suffix)) {
+ usec_t k = (usec_t) z * table[i].usec;
+
+ for (; n > 0; n--)
+ k /= 10;
+
+ r += (usec_t) l *table[i].usec + k;
+ p = e + strlen(table[i].suffix);
+
+ something = TRUE;
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(table))
+ return -EINVAL;
+
+ }
+
+ *usec = r;
+
+ return 0;
+}
+
+int parse_timestamp(const char *t, usec_t *usec)
+{
+ static const struct {
+ const char *name;
+ const int nr;
+ } day_nr[] = {
+ { "Sunday", 0 },
+ { "Sun", 0 },
+ { "Monday", 1 },
+ { "Mon", 1 },
+ { "Tuesday", 2 },
+ { "Tue", 2 },
+ { "Wednesday", 3 },
+ { "Wed", 3 },
+ { "Thursday", 4 },
+ { "Thu", 4 },
+ { "Friday", 5 },
+ { "Fri", 5 },
+ { "Saturday", 6 },
+ { "Sat", 6 },
+ };
+
+ const char *k;
+ struct tm tm, copy;
+ time_t x;
+ usec_t plus = 0, minus = 0, ret;
+ int r, weekday = -1;
+ unsigned i;
+
+ /*
+ * Allowed syntaxes:
+ *
+ * 2012-09-22 16:34:22
+ * 2012-09-22 16:34 (seconds will be set to 0)
+ * 2012-09-22 (time will be set to 00:00:00)
+ * 16:34:22 (date will be set to today)
+ * 16:34 (date will be set to today, seconds to 0)
+ * now
+ * yesterday (time is set to 00:00:00)
+ * today (time is set to 00:00:00)
+ * tomorrow (time is set to 00:00:00)
+ * +5min
+ * -5days
+ *
+ */
+
+ assert(t);
+ assert(usec);
+
+ x = time(NULL);
+ localtime_r(&x, &tm);
+ tm.tm_isdst = -1;
+
+ if (streq(t, "now"))
+ goto finish;
+
+ else if (streq(t, "today")) {
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+
+ } else if (streq(t, "yesterday")) {
+ tm.tm_mday--;
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+
+ } else if (streq(t, "tomorrow")) {
+ tm.tm_mday++;
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+
+ } else if (t[0] == '+') {
+
+ r = parse_sec(t + 1, &plus);
+ if (r < 0)
+ return r;
+
+ goto finish;
+ } else if (t[0] == '-') {
+
+ r = parse_sec(t + 1, &minus);
+ if (r < 0)
+ return r;
+
+ goto finish;
+
+ } else if (endswith(t, " ago")) {
+ char *z;
+
+ z = strndup(t, strlen(t) - 4);
+ if (!z)
+ return -ENOMEM;
+
+ r = parse_sec(z, &minus);
+ free(z);
+ if (r < 0)
+ return r;
+
+ goto finish;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(day_nr); i++) {
+ size_t skip;
+
+ if (!startswith_no_case(t, day_nr[i].name))
+ continue;
+
+ skip = strlen(day_nr[i].name);
+ if (t[skip] != ' ')
+ continue;
+
+ weekday = day_nr[i].nr;
+ t += skip + 1;
+ break;
+ }
+
+ copy = tm;
+ k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
+ if (k && *k == 0)
+ goto finish;
+
+ tm = copy;
+ k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
+ if (k && *k == 0)
+ goto finish;
+
+ tm = copy;
+ k = strptime(t, "%y-%m-%d %H:%M", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = 0;
+ goto finish;
+ }
+
+ tm = copy;
+ k = strptime(t, "%Y-%m-%d %H:%M", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = 0;
+ goto finish;
+ }
+
+ tm = copy;
+ k = strptime(t, "%y-%m-%d", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+ }
+
+ tm = copy;
+ k = strptime(t, "%Y-%m-%d", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+ }
+
+ tm = copy;
+ k = strptime(t, "%H:%M:%S", &tm);
+ if (k && *k == 0)
+ goto finish;
+
+ tm = copy;
+ k = strptime(t, "%H:%M", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = 0;
+ goto finish;
+ }
+
+ tm = copy;
+ k = strptime(t, "%Y%m%d%H%M%S", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = 0;
+ goto finish;
+ }
+
+ return -EINVAL;
+
+ finish:
+ x = mktime(&tm);
+ if (x == (time_t)-1)
+ return -EINVAL;
+
+ if (weekday >= 0 && tm.tm_wday != weekday)
+ return -EINVAL;
+
+ ret = (usec_t) x *USEC_PER_SEC;
+
+ ret += plus;
+ if (ret > minus)
+ ret -= minus;
+ else
+ ret = 0;
+
+ *usec = ret;
+
+ return 0;
+}
+
+/* Returns the difference in seconds between its argument and GMT. If if TP is
+ * invalid or no DST information is available default to UTC, that is, zero.
+ * tzset is called so, for example, 'TZ="UTC" hwclock' will work as expected.
+ * Derived from glibc/time/strftime_l.c
+ */
+int get_gmtoff(const struct tm *tp)
+{
+ if (tp->tm_isdst < 0)
+ return 0;
+
+#if HAVE_TM_GMTOFF
+ return tp->tm_gmtoff;
+#else
+ struct tm tm;
+ struct tm gtm;
+ struct tm ltm = *tp;
+ time_t lt;
+
+ tzset();
+ lt = mktime(&ltm);
+ /* Check if mktime returning -1 is an error or a valid time_t */
+ if (lt == (time_t) -1) {
+ if (! localtime_r(&lt, &tm)
+ || ((ltm.tm_sec ^ tm.tm_sec)
+ | (ltm.tm_min ^ tm.tm_min)
+ | (ltm.tm_hour ^ tm.tm_hour)
+ | (ltm.tm_mday ^ tm.tm_mday)
+ | (ltm.tm_mon ^ tm.tm_mon)
+ | (ltm.tm_year ^ tm.tm_year)))
+ return 0;
+ }
+
+ if (! gmtime_r(&lt, &gtm))
+ return 0;
+
+ /* Calculate the GMT offset, that is, the difference between the
+ * TP argument (ltm) and GMT (gtm).
+ *
+ * Compute intervening leap days correctly even if year is negative.
+ * Take care to avoid int overflow in leap day calculations, but it's OK
+ * to assume that A and B are close to each other.
+ */
+ int a4 = (ltm.tm_year >> 2) + (1900 >> 2) - ! (ltm.tm_year & 3);
+ int b4 = (gtm.tm_year >> 2) + (1900 >> 2) - ! (gtm.tm_year & 3);
+ int a100 = a4 / 25 - (a4 % 25 < 0);
+ int b100 = b4 / 25 - (b4 % 25 < 0);
+ int a400 = a100 >> 2;
+ int b400 = b100 >> 2;
+ int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
+
+ int years = ltm.tm_year - gtm.tm_year;
+ int days = (365 * years + intervening_leap_days
+ + (ltm.tm_yday - gtm.tm_yday));
+
+ return (60 * (60 * (24 * days + (ltm.tm_hour - gtm.tm_hour))
+ + (ltm.tm_min - gtm.tm_min)) + (ltm.tm_sec - gtm.tm_sec));
+#endif
+}
+
+static int format_iso_time(struct tm *tm, suseconds_t usec, int flags, char *buf, size_t bufsz)
+{
+ char *p = buf;
+ int len;
+
+ if (flags & ISO_DATE) {
+ len = snprintf(p, bufsz, "%4ld-%.2d-%.2d",
+ tm->tm_year + (long) 1900,
+ tm->tm_mon + 1, tm->tm_mday);
+ if (len < 0 || (size_t) len > bufsz)
+ goto err;
+ bufsz -= len;
+ p += len;
+ }
+
+ if ((flags & ISO_DATE) && (flags & ISO_TIME)) {
+ if (bufsz < 1)
+ goto err;
+ *p++ = (flags & ISO_T) ? 'T' : ' ';
+ bufsz--;
+ }
+
+ if (flags & ISO_TIME) {
+ len = snprintf(p, bufsz, "%02d:%02d:%02d", tm->tm_hour,
+ tm->tm_min, tm->tm_sec);
+ if (len < 0 || (size_t) len > bufsz)
+ goto err;
+ bufsz -= len;
+ p += len;
+ }
+
+ if (flags & ISO_DOTUSEC) {
+ len = snprintf(p, bufsz, ".%06ld", (long) usec);
+ if (len < 0 || (size_t) len > bufsz)
+ goto err;
+ bufsz -= len;
+ p += len;
+
+ } else if (flags & ISO_COMMAUSEC) {
+ len = snprintf(p, bufsz, ",%06ld", (long) usec);
+ if (len < 0 || (size_t) len > bufsz)
+ goto err;
+ bufsz -= len;
+ p += len;
+ }
+
+ if (flags & ISO_TIMEZONE) {
+ int tmin = get_gmtoff(tm) / 60;
+ int zhour = tmin / 60;
+ int zmin = abs(tmin % 60);
+ len = snprintf(p, bufsz, "%+03d:%02d", zhour,zmin);
+ if (len < 0 || (size_t) len > bufsz)
+ goto err;
+ }
+ return 0;
+ err:
+ warnx(_("format_iso_time: buffer overflow."));
+ return -1;
+}
+
+/* timeval to ISO 8601 */
+int strtimeval_iso(struct timeval *tv, int flags, char *buf, size_t bufsz)
+{
+ struct tm tm;
+ struct tm *rc;
+
+ if (flags & ISO_GMTIME)
+ rc = gmtime_r(&tv->tv_sec, &tm);
+ else
+ rc = localtime_r(&tv->tv_sec, &tm);
+
+ if (rc)
+ return format_iso_time(&tm, tv->tv_usec, flags, buf, bufsz);
+
+ warnx(_("time %ld is out of range."), tv->tv_sec);
+ return -1;
+}
+
+/* struct tm to ISO 8601 */
+int strtm_iso(struct tm *tm, int flags, char *buf, size_t bufsz)
+{
+ return format_iso_time(tm, 0, flags, buf, bufsz);
+}
+
+/* time_t to ISO 8601 */
+int strtime_iso(const time_t *t, int flags, char *buf, size_t bufsz)
+{
+ struct tm tm;
+ struct tm *rc;
+
+ if (flags & ISO_GMTIME)
+ rc = gmtime_r(t, &tm);
+ else
+ rc = localtime_r(t, &tm);
+
+ if (rc)
+ return format_iso_time(&tm, 0, flags, buf, bufsz);
+
+ warnx(_("time %ld is out of range."), (long)t);
+ return -1;
+}
+
+/* relative time functions */
+int time_is_today(const time_t *t, struct timeval *now)
+{
+ if (now->tv_sec == 0)
+ gettimeofday(now, NULL);
+ return *t / (3600 * 24) == now->tv_sec / (3600 * 24);
+}
+
+int time_is_thisyear(const time_t *t, struct timeval *now)
+{
+ if (now->tv_sec == 0)
+ gettimeofday(now, NULL);
+ return *t / (3600 * 24 * 365) == now->tv_sec / (3600 * 24 * 365);
+}
+
+int strtime_short(const time_t *t, struct timeval *now, int flags, char *buf, size_t bufsz)
+{
+ struct tm tm;
+ int rc = 0;
+
+ localtime_r(t, &tm);
+
+ if (time_is_today(t, now)) {
+ rc = snprintf(buf, bufsz, "%02d:%02d", tm.tm_hour, tm.tm_min);
+ if (rc < 0 || (size_t) rc > bufsz)
+ return -1;
+ rc = 1;
+
+ } else if (time_is_thisyear(t, now)) {
+ if (flags & UL_SHORTTIME_THISYEAR_HHMM)
+ rc = strftime(buf, bufsz, "%b%d/%H:%M", &tm);
+ else
+ rc = strftime(buf, bufsz, "%b%d", &tm);
+ } else
+ rc = strftime(buf, bufsz, "%Y-%b%d", &tm);
+
+ return rc <= 0 ? -1 : 0;
+}
+
+#ifndef HAVE_TIMEGM
+time_t timegm(struct tm *tm)
+{
+ const char *zone = getenv("TZ");
+ time_t ret;
+
+ setenv("TZ", "", 1);
+ tzset();
+ ret = mktime(tm);
+ if (zone)
+ setenv("TZ", zone, 1);
+ else
+ unsetenv("TZ");
+ tzset();
+ return ret;
+}
+#endif /* HAVE_TIMEGM */
+
+#ifdef TEST_PROGRAM_TIMEUTILS
+
+int main(int argc, char *argv[])
+{
+ struct timeval tv = { 0 };
+ char buf[ISO_BUFSIZ];
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <time> [<usec>]\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ tv.tv_sec = strtos64_or_err(argv[1], "failed to parse <time>");
+ if (argc == 3)
+ tv.tv_usec = strtos64_or_err(argv[2], "failed to parse <usec>");
+
+ strtimeval_iso(&tv, ISO_DATE, buf, sizeof(buf));
+ printf("Date: '%s'\n", buf);
+
+ strtimeval_iso(&tv, ISO_TIME, buf, sizeof(buf));
+ printf("Time: '%s'\n", buf);
+
+ strtimeval_iso(&tv, ISO_DATE | ISO_TIME | ISO_COMMAUSEC | ISO_T,
+ buf, sizeof(buf));
+ printf("Full: '%s'\n", buf);
+
+ strtimeval_iso(&tv, ISO_TIMESTAMP_DOT, buf, sizeof(buf));
+ printf("Zone: '%s'\n", buf);
+
+ return EXIT_SUCCESS;
+}
+
+#endif /* TEST_PROGRAM_TIMEUTILS */
diff --git a/lib/ttyutils.c b/lib/ttyutils.c
new file mode 100644
index 0000000..00a7903
--- /dev/null
+++ b/lib/ttyutils.c
@@ -0,0 +1,143 @@
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#include <ctype.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "ttyutils.h"
+
+
+static int get_env_int(const char *name)
+{
+ const char *cp = getenv(name);
+
+ if (cp) {
+ char *end = NULL;
+ long x;
+
+ errno = 0;
+ x = strtol(cp, &end, 10);
+
+ if (errno == 0 && end && *end == '\0' && end > cp &&
+ x > 0 && x <= INT_MAX)
+ return x;
+ }
+
+ return -1;
+}
+
+int get_terminal_dimension(int *cols, int *lines)
+{
+ int c = 0, l = 0;
+
+#if defined(TIOCGWINSZ)
+ struct winsize w_win;
+ if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &w_win) == 0) {
+ c = w_win.ws_col;
+ l = w_win.ws_row;
+ }
+#elif defined(TIOCGSIZE)
+ struct ttysize t_win;
+ if (ioctl (STDOUT_FILENO, TIOCGSIZE, &t_win) == 0) {
+ c = t_win.ts_cols;
+ l = t_win.ts_lines;
+ }
+#endif
+
+ if (cols && c <= 0)
+ c = get_env_int("COLUMNS");
+ if (lines && l <= 0)
+ l = get_env_int("LINES");
+
+ if (cols)
+ *cols = c;
+ if (lines)
+ *lines = l;
+ return 0;
+}
+
+int get_terminal_width(int default_width)
+{
+ int width = 0;
+
+ get_terminal_dimension(&width, NULL);
+
+ return width > 0 ? width : default_width;
+}
+
+int get_terminal_name(const char **path,
+ const char **name,
+ const char **number)
+{
+ const char *tty;
+ const char *p;
+ int fd;
+
+
+ if (name)
+ *name = NULL;
+ if (path)
+ *path = NULL;
+ if (number)
+ *number = NULL;
+
+ if (isatty(STDIN_FILENO))
+ fd = STDIN_FILENO;
+ else if (isatty(STDOUT_FILENO))
+ fd = STDOUT_FILENO;
+ else if (isatty(STDERR_FILENO))
+ fd = STDERR_FILENO;
+ else
+ return -1;
+
+ tty = ttyname(fd);
+ if (!tty)
+ return -1;
+ if (path)
+ *path = tty;
+ tty = strncmp(tty, "/dev/", 5) == 0 ? tty + 5 : tty;
+ if (name)
+ *name = tty;
+ if (number) {
+ for (p = tty; p && *p; p++) {
+ if (isdigit(*p)) {
+ *number = p;
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+int get_terminal_type(const char **type)
+{
+ *type = getenv("TERM");
+ if (*type)
+ return -EINVAL;
+ return 0;
+}
+
+#ifdef TEST_PROGRAM_TTYUTILS
+# include <stdlib.h>
+int main(void)
+{
+ const char *path, *name, *num;
+ int c, l;
+
+ if (get_terminal_name(&path, &name, &num) == 0) {
+ fprintf(stderr, "tty path: %s\n", path);
+ fprintf(stderr, "tty name: %s\n", name);
+ fprintf(stderr, "tty number: %s\n", num);
+ }
+ get_terminal_dimension(&c, &l);
+ fprintf(stderr, "tty cols: %d\n", c);
+ fprintf(stderr, "tty lines: %d\n", l);
+
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM_TTYUTILS */