diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 14:30:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 14:30:35 +0000 |
commit | 378c18e5f024ac5a8aef4cb40d7c9aa9633d144c (patch) | |
tree | 44dfb6ca500d32cabd450649b322a42e70a30683 /lib | |
parent | Initial commit. (diff) | |
download | util-linux-upstream.tar.xz util-linux-upstream.zip |
Adding upstream version 2.38.1.upstream/2.38.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib')
48 files changed, 17159 insertions, 0 deletions
diff --git a/lib/Makemodule.am b/lib/Makemodule.am new file mode 100644 index 0000000..d38c21e --- /dev/null +++ b/lib/Makemodule.am @@ -0,0 +1,224 @@ +# +# Use only LGPL or Public domain (preferred) code in libcommon, otherwise add +# your lib/file.c directly to the _SOURCES= of the target binary. +# +# THIS LIBRARY IS NOT DISTRIBUTED! +# +# It's just ar(1) archive used by build-system to keep things simple. +# +# Note that you need "make install-strip" (or proper rpm / Debian build) +# to generate binaries with only relevant stuff. +# +EXTRA_LTLIBRARIES += libcommon.la +libcommon_la_CFLAGS = $(AM_CFLAGS) +libcommon_la_SOURCES = \ + lib/blkdev.c \ + lib/buffer.c \ + lib/canonicalize.c \ + lib/color-names.c \ + lib/crc32.c \ + lib/crc32c.c \ + lib/c_strtod.c \ + lib/encode.c \ + lib/env.c \ + lib/fileutils.c \ + lib/idcache.c \ + lib/jsonwrt.c \ + lib/mangle.c \ + lib/match.c \ + lib/mbsalign.c \ + lib/mbsedit.c\ + lib/md5.c \ + lib/pager.c \ + lib/pwdutils.c \ + lib/randutils.c \ + lib/sha1.c \ + lib/signames.c \ + lib/strutils.c \ + lib/strv.c \ + lib/timeutils.c \ + lib/ttyutils.c + +if LINUX +libcommon_la_SOURCES += \ + lib/linux_version.c \ + lib/loopdev.c +endif + +if !HAVE_LANGINFO_H +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/sysfs.c +libcommon_la_SOURCES += lib/procfs.c +endif +endif + +EXTRA_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 + +MANPAGES += lib/terminal-colors.d.5 +dist_noinst_DATA += lib/terminal-colors.d.5.adoc + +check_PROGRAMS += \ + test_blkdev \ + test_buffer \ + test_canonicalize \ + test_colors \ + test_fileeq \ + test_fileutils \ + test_ismounted \ + test_pwdutils \ + test_mangle \ + test_randutils \ + test_remove_env \ + test_strutils \ + test_ttyutils \ + test_timeutils \ + test_c_strtod + + +if LINUX +if HAVE_CPU_SET_T +check_PROGRAMS += test_cpuset +endif +check_PROGRAMS += \ + test_sysfs \ + test_procfs \ + test_pager \ + test_caputils \ + test_loopdev \ + test_linux_version +endif + +if HAVE_OPENAT +if HAVE_DIRFD +check_PROGRAMS += test_path +endif +endif + +test_caputils_SOURCES = lib/caputils.c +test_caputils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_CAPUTILS +test_caputils_LDADD = $(LDADD) libcommon.la + +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_c_strtod_SOURCES = lib/c_strtod.c +test_c_strtod_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM + +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_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 HAVE_PTY +check_PROGRAMS += test_pty +test_pty_SOURCES = lib/pty-session.c \ + include/pty-session.h \ + lib/monotonic.c +test_pty_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PTY +test_pty_LDADD = $(LDADD) libcommon.la $(MATH_LIBS) $(REALTIME_LIBS) -lutil +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_procfs_SOURCES = lib/procfs.c lib/path.c lib/fileutils.c lib/strutils.c +if HAVE_CPU_SET_T +test_procfs_SOURCES += lib/cpuset.c +endif +test_procfs_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PROCFS +test_procfs_LDADD = $(LDADD) + +test_pager_SOURCES = lib/pager.c +test_pager_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PAGER + +test_linux_version_SOURCES = lib/linux_version.c +test_linux_version_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_LINUXVERSION +endif + +test_fileeq_SOURCES = lib/fileeq.c +test_fileeq_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_FILEEQ + +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 + +test_remove_env_SOURCES = lib/env.c +test_remove_env_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM + +test_buffer_SOURCES = lib/buffer.c lib/mbsalign.c +test_buffer_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_BUFFER + +if LINUX +test_loopdev_SOURCES = lib/loopdev.c \ + lib/blkdev.c \ + lib/linux_version.c \ + $(test_sysfs_SOURCES) \ + $(test_canonicalize_SOURCES) +test_loopdev_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_LOOPDEV +endif diff --git a/lib/blkdev.c b/lib/blkdev.c new file mode 100644 index 0000000..cc1eea3 --- /dev/null +++ b/lib/blkdev.c @@ -0,0 +1,483 @@ +/* + * 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/file.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <stdint.h> + +#ifdef HAVE_LINUX_FD_H +#include <linux/fd.h> +#endif + +#ifdef HAVE_LINUX_BLKZONED_H +#include <linux/blkzoned.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; + + 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; +} + +/* return 0 on success */ +int blkdev_lock(int fd, const char *devname, const char *lockmode) +{ + int oper, rc, msg = 0; + + if (!lockmode) + lockmode = getenv("LOCK_BLOCK_DEVICE"); + if (!lockmode) + return 0; + + if (strcasecmp(lockmode, "yes") == 0 || + strcmp(lockmode, "1") == 0) + oper = LOCK_EX; + + else if (strcasecmp(lockmode, "nonblock") == 0) + oper = LOCK_EX | LOCK_NB; + + else if (strcasecmp(lockmode, "no") == 0 || + strcmp(lockmode, "0") == 0) + return 0; + else { + warnx(_("unsupported lock mode: %s"), lockmode); + return -EINVAL; + } + + if (!(oper & LOCK_NB)) { + /* Try non-block first to provide message */ + rc = flock(fd, oper | LOCK_NB); + if (rc == 0) + return 0; + if (rc != 0 && errno == EWOULDBLOCK) { + fprintf(stderr, _("%s: %s: device already locked, waiting to get lock ... "), + program_invocation_short_name, devname); + msg = 1; + } + } + rc = flock(fd, oper); + if (rc != 0) { + switch (errno) { + case EWOULDBLOCK: /* LOCK_NB */ + warnx(_("%s: device already locked"), devname); + break; + default: + warn(_("%s: failed to get lock"), devname); + } + } else if (msg) + fprintf(stderr, _("OK\n")); + return rc; +} + +#ifdef HAVE_LINUX_BLKZONED_H +struct blk_zone_report *blkdev_get_zonereport(int fd, uint64_t sector, uint32_t nzones) +{ + struct blk_zone_report *rep; + size_t rep_size; + int ret; + + rep_size = sizeof(struct blk_zone_report) + sizeof(struct blk_zone) * 2; + rep = calloc(1, rep_size); + if (!rep) + return NULL; + + rep->sector = sector; + rep->nr_zones = nzones; + + ret = ioctl(fd, BLKREPORTZONE, rep); + if (ret || rep->nr_zones != nzones) { + free(rep); + return NULL; + } + + return rep; +} +#endif + + +#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, §ors) < 0) + err(EXIT_FAILURE, "blkdev_get_sectors() failed"); + if (blkdev_get_sector_size(fd, §or_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/buffer.c b/lib/buffer.c new file mode 100644 index 0000000..1dba7dd --- /dev/null +++ b/lib/buffer.c @@ -0,0 +1,289 @@ +/* + * 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 "buffer.h" +#include "mbsalign.h" + +void ul_buffer_reset_data(struct ul_buffer *buf) +{ + if (buf->begin) + buf->begin[0] = '\0'; + buf->end = buf->begin; + + if (buf->ptrs && buf->nptrs) + memset(buf->ptrs, 0, buf->nptrs * sizeof(char *)); +} + +void ul_buffer_free_data(struct ul_buffer *buf) +{ + assert(buf); + + free(buf->begin); + buf->begin = NULL; + buf->end = NULL; + buf->sz = 0; + + free(buf->ptrs); + buf->ptrs = NULL; + buf->nptrs = 0; + + free(buf->encoded); + buf->encoded = NULL; + buf->encoded_sz = 0; +} + +void ul_buffer_set_chunksize(struct ul_buffer *buf, size_t sz) +{ + buf->chunksize = sz; +} + +int ul_buffer_is_empty(struct ul_buffer *buf) +{ + return buf->begin == buf->end; +} + +int ul_buffer_save_pointer(struct ul_buffer *buf, unsigned short ptr_idx) +{ + if (ptr_idx >= buf->nptrs) { + char **tmp = realloc(buf->ptrs, (ptr_idx + 1) * sizeof(char *)); + + if (!tmp) + return -EINVAL; + buf->ptrs = tmp; + buf->nptrs = ptr_idx + 1; + } + + buf->ptrs[ptr_idx] = buf->end; + return 0; +} + + +char *ul_buffer_get_pointer(struct ul_buffer *buf, unsigned short ptr_idx) +{ + if (ptr_idx < buf->nptrs) + return buf->ptrs[ptr_idx]; + return NULL; +} + +/* returns length from begin to the pointer */ +size_t ul_buffer_get_pointer_length(struct ul_buffer *buf, unsigned short ptr_idx) +{ + char *ptr = ul_buffer_get_pointer(buf, ptr_idx); + + if (ptr && ptr > buf->begin) + return ptr - buf->begin; + return 0; +} + +/* returns width of data in safe encoding (from the begin to the pointer) */ +size_t ul_buffer_get_safe_pointer_width(struct ul_buffer *buf, unsigned short ptr_idx) +{ + size_t len = ul_buffer_get_pointer_length(buf, ptr_idx); + + if (!len) + return 0; + + return mbs_safe_nwidth(buf->begin, len, NULL); +} + +void ul_buffer_refer_string(struct ul_buffer *buf, char *str) +{ + if (buf->sz) + ul_buffer_free_data(buf); + buf->begin = str; + buf->sz = str ? strlen(str) : 0; + buf->end = buf->begin ? buf->begin + buf->sz : buf->begin; +} + +int ul_buffer_alloc_data(struct ul_buffer *buf, size_t sz) +{ + char *tmp; + size_t len = 0; + + assert(buf); + + if (sz <= buf->sz) + return 0; + + if (buf->end && buf->begin) + len = buf->end - buf->begin; + + if (buf->chunksize) + sz = ((sz + buf->chunksize) / buf->chunksize) * buf->chunksize + 1; + + tmp = realloc(buf->begin, sz); + if (!tmp) + return -ENOMEM; + + buf->begin = tmp; + buf->end = buf->begin + len; + buf->sz = sz; + + memset(buf->end, '\0', sz - len); + + return 0; +} + +int ul_buffer_append_data(struct ul_buffer *buf, const char *data, size_t sz) +{ + size_t maxsz = 0; + + if (!buf) + return -EINVAL; + if (!data || !*data) + return 0; + + if (buf->begin && buf->end) + maxsz = buf->sz - (buf->end - buf->begin); + + if (maxsz <= sz + 1) { + int rc = ul_buffer_alloc_data(buf, buf->sz + sz + 1); + if (rc) + return rc; + } + if (!buf->end) + return -EINVAL; /* make static analyzers happy */ + + memcpy(buf->end, data, sz); + buf->end += sz; + *buf->end = '\0'; /* make sure it's terminated */ + return 0; +} + +int ul_buffer_append_string(struct ul_buffer *buf, const char *str) +{ + if (!str) + return 0; + + return ul_buffer_append_data(buf, str, strlen(str)); +} + +int ul_buffer_append_ntimes(struct ul_buffer *buf, size_t n, const char *str) +{ + size_t i; + size_t len = strlen(str); + + for (i = 0; len && i < n; i++) { + int rc = ul_buffer_append_data(buf, str, len); + if (rc) + return rc; + } + return 0; +} + +int ul_buffer_set_data(struct ul_buffer *buf, const char *data, size_t sz) +{ + ul_buffer_reset_data(buf); + return ul_buffer_append_data(buf, data, sz); +} + +char *ul_buffer_get_data(struct ul_buffer *buf, size_t *sz, size_t *width) +{ + if (sz) + *sz = buf->end - buf->begin; + if (width) + *width = buf->begin && *buf->begin ? mbs_width(buf->begin) : 0; + return buf->begin; +} + +/* size of allocated area (!= size of stored data */ +size_t ul_buffer_get_bufsiz(struct ul_buffer *buf) +{ + return buf->sz; +} + +/* encode data by mbs_safe_encode() to avoid control and non-printable chars */ +char *ul_buffer_get_safe_data(struct ul_buffer *buf, size_t *sz, size_t *width, const char *safechars) +{ + char *data = ul_buffer_get_data(buf, NULL, NULL); + size_t encsz, wsz = 0; + char *res = NULL; + + if (!data) + goto nothing; + + encsz = mbs_safe_encode_size(buf->sz) + 1; + if (encsz > buf->encoded_sz) { + char *tmp = realloc(buf->encoded, encsz); + if (!tmp) + goto nothing; + buf->encoded = tmp; + buf->encoded_sz = encsz; + } + + res = mbs_safe_encode_to_buffer(data, &wsz, buf->encoded, safechars); + if (!res || !wsz || wsz == (size_t) -1) + goto nothing; + + if (width) + *width = wsz; + if (sz) + *sz = strlen(res); + return res; +nothing: + if (width) + *width = 0; + if (sz) + *sz = 0; + return NULL; +} + + +#ifdef TEST_PROGRAM_BUFFER + +enum { + PTR_AAA = 0, + PTR_BBB, +}; + +int main(void) +{ + struct ul_buffer buf = UL_INIT_BUFFER; + char *str; + size_t sz = 0; + + ul_buffer_set_chunksize(&buf, 16); + + ul_buffer_append_string(&buf, "AAA"); + ul_buffer_append_data(&buf, "=", 1); + ul_buffer_append_string(&buf, "aaa"); + ul_buffer_save_pointer(&buf, PTR_AAA); + + ul_buffer_append_data(&buf, ",", 1); + ul_buffer_append_string(&buf, "BBB"); + ul_buffer_append_string(&buf, "="); + ul_buffer_append_string(&buf, "bbb"); + ul_buffer_save_pointer(&buf, PTR_BBB); + + str = ul_buffer_get_data(&buf, &sz, NULL); + printf("data [%zu] '%s'\n", sz, str); + + printf(" pointer data len: AAA=%zu, BBB=%zu\n", + ul_buffer_get_pointer_length(&buf, PTR_AAA), + ul_buffer_get_pointer_length(&buf, PTR_BBB)); + printf(" pointer data width: AAA=%zu, BBB=%zu\n", + ul_buffer_get_safe_pointer_width(&buf, PTR_AAA), + ul_buffer_get_safe_pointer_width(&buf, PTR_BBB)); + + ul_buffer_reset_data(&buf); + ul_buffer_append_string(&buf, "This is really long string to test the buffer function."); + ul_buffer_save_pointer(&buf, PTR_AAA); + ul_buffer_append_string(&buf, " YES!"); + str = ul_buffer_get_data(&buf, &sz, NULL); + printf("data [%zu] '%s'\n", sz, str); + printf(" pointer data len: AAA=%zu\n", ul_buffer_get_pointer_length(&buf, PTR_AAA)); + + ul_buffer_free_data(&buf); + str = strdup("foo"); + ul_buffer_refer_string(&buf, str); + ul_buffer_append_data(&buf, ",", 1); + ul_buffer_append_string(&buf, "bar"); + str = ul_buffer_get_data(&buf, &sz, NULL); + printf("data [%zu] '%s'\n", sz, str); + + ul_buffer_free_data(&buf); +} +#endif /* TEST_PROGRAM_BUFFER */ diff --git a/lib/c_strtod.c b/lib/c_strtod.c new file mode 100644 index 0000000..d25ee27 --- /dev/null +++ b/lib/c_strtod.c @@ -0,0 +1,109 @@ +/* + * Locale-independent strtod(). + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * + * Copyright (C) 2021 Karel Zak <kzak@redhat.com> + */ +#include "c.h" + +#include <locale.h> +#include <stdlib.h> +#include <string.h> + +#include "c_strtod.h" + +#ifdef __APPLE__ +# include <xlocale.h> +#endif + +#if defined(HAVE_NEWLOCALE) && (defined(HAVE_STRTOD_L) || defined(HAVE_USELOCALE)) +# define USE_CLOCALE +#endif + +#if defined(USE_CLOCALE) +static volatile locale_t c_locale; + +static locale_t get_c_locale(void) +{ + if (!c_locale) + c_locale = newlocale(LC_ALL_MASK, "C", (locale_t) 0); + return c_locale; +} +#endif + + +double c_strtod(char const *str, char **end) +{ + double res; + int errsv; + +#if defined(USE_CLOCALE) + locale_t cl = get_c_locale(); + +#if defined(HAVE_STRTOD_L) + /* + * A) try strtod_l() for "C" locale + */ + if (cl) + return strtod_l(str, end, cl); +#elif defined(HAVE_USELOCALE) + /* + * B) classic strtod(), but switch to "C" locale by uselocal() + */ + if (cl) { + locale_t org_cl = uselocale(locale); + if (!org_cl) + return 0; + + res = strtod(str, end); + errsv = errno; + + uselocale(org_cl); + errno = errsv; + return res; + } +#endif /* HAVE_USELOCALE */ +#endif /* USE_CLOCALE */ + /* + * C) classic strtod(), but switch to "C" locale by setlocale() + */ + char *org_locale = setlocale(LC_NUMERIC, NULL); + + if (org_locale) { + org_locale = strdup(org_locale); + if (!org_locale) + return 0; + + setlocale(LC_NUMERIC, "C"); + } + res = strtod(str, end); + errsv = errno; + + if (org_locale) { + setlocale(LC_NUMERIC, org_locale); + free(org_locale); + } + errno = errsv; + return res; +} + +#ifdef TEST_PROGRAM +int main(int argc, char *argv[]) +{ + double res; + char *end; + + if (argc < 2) { + fprintf(stderr, "usage: %s decimal.number\n", + program_invocation_short_name); + return EXIT_FAILURE; + } + + res = c_strtod(argv[1], &end); + printf("Result: %g, errno: %d, endptr: '%s'\n", res, errno, end); + + return errno ? EXIT_FAILURE : EXIT_SUCCESS; +} +#endif diff --git a/lib/canonicalize.c b/lib/canonicalize.c new file mode 100644 index 0000000..6f85b47 --- /dev/null +++ b/lib/canonicalize.c @@ -0,0 +1,249 @@ +/* + * 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 <sys/wait.h> + +#include "canonicalize.h" +#include "pathnames.h" +#include "all-io.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 && *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 = NULL; + int errsv = 0; + int pipes[2]; + ssize_t len; + pid_t pid; + + if (!path || !*path) + return NULL; + + if (pipe(pipes) != 0) + return NULL; + + /* + * To accurately assume identity of getuid() we must use setuid() + * but if we do that, we lose ability to reassume euid of 0, so + * we fork to do the check to keep euid intact. + */ + pid = fork(); + switch (pid) { + case -1: + close(pipes[0]); + close(pipes[1]); + return NULL; /* fork error */ + case 0: + close(pipes[0]); /* close unused end */ + pipes[0] = -1; + errno = 0; + + if (drop_permissions() != 0) + canonical = NULL; /* failed */ + else { + char *dmname = NULL; + + canonical = realpath(path, NULL); + if (canonical && is_dm_devname(canonical, &dmname)) { + char *dm = canonicalize_dm_name(dmname); + if (dm) { + free(canonical); + canonical = dm; + } + } + } + + len = canonical ? (ssize_t) strlen(canonical) : + errno ? -errno : -EINVAL; + + /* send length or errno */ + write_all(pipes[1], (char *) &len, sizeof(len)); + if (canonical) + write_all(pipes[1], canonical, len); + exit(0); + default: + break; + } + + close(pipes[1]); /* close unused end */ + pipes[1] = -1; + + /* read size or -errno */ + if (read_all(pipes[0], (char *) &len, sizeof(len)) != sizeof(len)) + goto done; + if (len < 0) { + errsv = -len; + goto done; + } + + canonical = malloc(len + 1); + if (!canonical) { + errsv = ENOMEM; + goto done; + } + /* read path */ + if (read_all(pipes[0], canonical, len) != len) { + errsv = errno; + goto done; + } + canonical[len] = '\0'; +done: + if (errsv) { + free(canonical); + canonical = NULL; + } + close(pipes[0]); + + /* We make a best effort to reap child */ + waitpid(pid, NULL, 0); + + 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/caputils.c b/lib/caputils.c new file mode 100644 index 0000000..987533a --- /dev/null +++ b/lib/caputils.c @@ -0,0 +1,118 @@ +/* + * 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 <sys/prctl.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <errno.h> + +#include "c.h" +#include "caputils.h" +#include "pathnames.h" +#include "procfs.h" + +static int test_cap(unsigned int cap) +{ + /* prctl returns 0 or 1 for valid caps, -1 otherwise */ + return prctl(PR_CAPBSET_READ, cap, 0, 0, 0) >= 0; +} + +static int cap_last_by_bsearch(int *ret) +{ + /* starting with cap=INT_MAX means we always know + * that cap1 is invalid after the first iteration */ + int cap = INT_MAX; + unsigned int cap0 = 0, cap1 = INT_MAX; + + while ((int)cap0 < cap) { + if (test_cap(cap)) + cap0 = cap; + else + cap1 = cap; + + cap = (cap0 + cap1) / 2U; + } + + *ret = cap; + return 0; +} + +static int cap_last_by_procfs(int *ret) +{ + FILE *f = fopen(_PATH_PROC_CAPLASTCAP, "r"); + int rc = -EINVAL; + + *ret = 0; + + if (f && fd_is_procfs(fileno(f))) { + int cap; + + /* we check if the cap after this one really isn't valid */ + if (fscanf(f, "%d", &cap) == 1 && + cap < INT_MAX && !test_cap(cap + 1)) { + + *ret = cap; + rc = 0; + } + } + + if (f) + fclose(f); + return rc; +} + +int cap_last_cap(void) +{ + static int cap = -1; + + if (cap != -1) + return cap; + if (cap_last_by_procfs(&cap) < 0) + cap_last_by_bsearch(&cap); + + return cap; +} + +#ifdef TEST_PROGRAM_CAPUTILS +int main(int argc, char *argv[]) +{ + int rc = 0, cap; + + if (argc < 2) { + fprintf(stderr, "usage: %1$s --last-by-procfs\n" + " %1$s --last-by-bsearch\n" + " %1$s --last\n", + program_invocation_short_name); + return EXIT_FAILURE; + } + + if (strcmp(argv[1], "--last-by-procfs") == 0) { + rc = cap_last_by_procfs(&cap); + if (rc == 0) + printf("last cap: %d\n", cap); + + } else if (strcmp(argv[1], "--last-by-bsearch") == 0) { + rc = cap_last_by_bsearch(&cap); + if (rc == 0) + printf("last cap: %d\n", cap); + + } else if (strcmp(argv[1], "--last") == 0) + printf("last cap: %d\n", cap_last_cap()); + + return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} +#endif diff --git a/lib/color-names.c b/lib/color-names.c new file mode 100644 index 0000000..9b1505e --- /dev/null +++ b/lib/color-names.c @@ -0,0 +1,64 @@ +/* + * 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 "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 }, + { "white", UL_COLOR_WHITE } + }; + 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..e317519 --- /dev/null +++ b/lib/colors.c @@ -0,0 +1,907 @@ +/* + * 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, may be overridden 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++; + } + + if (out) { + 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 defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) + { + int ret; + + if (setupterm(NULL, STDOUT_FILENO, &ret) == 0 && ret == 1) + ncolors = tigetnum("colors"); + } +#endif + if (1 < ncolors) { + DBG(CONF, ul_debug("terminal is ready (supports %d colors)", ncolors)); + return 1; + } + + DBG(CONF, ul_debug("terminal is NOT ready (no colors)")); + 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; + + termcolors_init_debug(); + + if (mode != UL_COLORMODE_ALWAYS && !isatty(STDOUT_FILENO)) + cc->mode = UL_COLORMODE_NEVER; + else + cc->mode = mode; + + if (cc->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; +} + +/* + * Returns mode + */ +int colors_mode(void) +{ + return ul_colors.mode; +} + +/* + * 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..9c3284c --- /dev/null +++ b/lib/cpuset.c @@ -0,0 +1,415 @@ +/* + * 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> +#ifdef HAVE_SYS_SYSCALL_H +#include <sys/syscall.h> +#endif + +#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; +} + +static int nextnumber(const char *str, char **end, unsigned int *result) +{ + errno = 0; + if (str == NULL || *str == '\0' || !isdigit(*str)) + return -EINVAL; + *result = (unsigned int) strtoul(str, end, 10); + if (errno) + return -errno; + if (str == *end) + return -EINVAL; + 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; + char *end = NULL; + + 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; + + if (nextnumber(p, &end, &a) != 0) + return 1; + b = a; + s = 1; + p = end; + + c1 = nexttoken(p, '-'); + c2 = nexttoken(p, ','); + + if (c1 != NULL && (c2 == NULL || c1 < c2)) { + if (nextnumber(c1, &end, &b) != 0) + return 1; + + c1 = end && *end ? nexttoken(end, ':') : NULL; + + if (c1 != NULL && (c2 == NULL || c1 < c2)) { + if (nextnumber(c1, &end, &s) != 0) + 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 (end && *end) + 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>\n", + 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/encode.c b/lib/encode.c new file mode 100644 index 0000000..10b5971 --- /dev/null +++ b/lib/encode.c @@ -0,0 +1,79 @@ +/* + * Based on code from libblkid, + * + * Copyright (C) 2008 Kay Sievers <kay.sievers@vrfy.org> + * Copyright (C) 2009 Karel Zak <kzak@redhat.com> + * Copyright (C) 2020 Pali Rohár <pali.rohar@gmail.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#include "c.h" +#include "encode.h" + +size_t ul_encode_to_utf8(int enc, unsigned char *dest, size_t len, + const unsigned char *src, size_t count) +{ + size_t i, j; + uint32_t c; + uint16_t c2; + + for (j = i = 0; i < count; i++) { + if (enc == UL_ENCODE_UTF16LE) { + if (i+2 > count) + break; + c = (src[i+1] << 8) | src[i]; + i++; + } else if (enc == UL_ENCODE_UTF16BE) { + if (i+2 > count) + break; + c = (src[i] << 8) | src[i+1]; + i++; + } else if (enc == UL_ENCODE_LATIN1) { + c = src[i]; + } else { + return 0; + } + if ((enc == UL_ENCODE_UTF16LE || enc == UL_ENCODE_UTF16BE) && + c >= 0xD800 && c <= 0xDBFF && i+2 < count) { + if (enc == UL_ENCODE_UTF16LE) + c2 = (src[i+2] << 8) | src[i+1]; + else + c2 = (src[i+1] << 8) | src[i+2]; + if (c2 >= 0xDC00 && c2 <= 0xDFFF) { + c = 0x10000 + ((c - 0xD800) << 10) + (c2 - 0xDC00); + i += 2; + } + } + if (c == 0) { + dest[j] = '\0'; + break; + } + + if (c < 0x80) { + if (j+1 >= len) + break; + dest[j++] = (uint8_t) c; + } else if (c < 0x800) { + if (j+2 >= len) + break; + dest[j++] = (uint8_t) (0xc0 | (c >> 6)); + dest[j++] = (uint8_t) (0x80 | (c & 0x3f)); + } else if (c < 0x10000) { + if (j+3 >= len) + break; + dest[j++] = (uint8_t) (0xe0 | (c >> 12)); + dest[j++] = (uint8_t) (0x80 | ((c >> 6) & 0x3f)); + dest[j++] = (uint8_t) (0x80 | (c & 0x3f)); + } else { + if (j+4 >= len) + break; + dest[j++] = (uint8_t) (0xf0 | (c >> 18)); + dest[j++] = (uint8_t) (0x80 | ((c >> 12) & 0x3f)); + dest[j++] = (uint8_t) (0x80 | ((c >> 6) & 0x3f)); + dest[j++] = (uint8_t) (0x80 | (c & 0x3f)); + } + } + dest[j] = '\0'; + return j; +} diff --git a/lib/env.c b/lib/env.c new file mode 100644 index 0000000..a3dd335 --- /dev/null +++ b/lib/env.c @@ -0,0 +1,239 @@ +/* + * environ[] array cleanup code and getenv() wrappers + * + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + */ +#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 +}; + + +struct ul_env_list { + char *env; + struct ul_env_list *next; +}; + +/* + * Saves @name env.variable to @ls, returns pointer to the new head of the list. + */ +static struct ul_env_list *env_list_add(struct ul_env_list *ls0, const char *str) +{ + struct ul_env_list *ls; + char *p; + size_t sz = 0; + + if (!str || !*str) + return ls0; + + sz = strlen(str) + 1; + p = malloc(sizeof(struct ul_env_list) + sz); + if (!p) + return ls0; + + ls = (struct ul_env_list *) p; + p += sizeof(struct ul_env_list); + memcpy(p, str, sz); + ls->env = p; + + ls->next = ls0; + return ls; +} + +/* + * Use setenv() for all stuff in @ls. + * + * It would be possible to use putenv(), but we want to keep @ls free()-able. + */ +int env_list_setenv(struct ul_env_list *ls) +{ + int rc = 0; + + while (ls && rc == 0) { + if (ls->env) { + char *val = strchr(ls->env, '='); + if (!val) + continue; + *val = '\0'; + rc = setenv(ls->env, val + 1, 0); + *val = '='; + } + ls = ls->next; + } + return rc; +} + +void env_list_free(struct ul_env_list *ls) +{ + while (ls) { + struct ul_env_list *x = ls; + ls = ls->next; + free(x); + } +} + +/* + * Removes unwanted variables from environ[]. If @org is not NULL than stores + * unwnated variables to the list. + */ +void __sanitize_env(struct ul_env_list **org) +{ + char **envp = environ; + char * const *bad; + char **cur; + int last = 0; + + for (cur = envp; *cur; cur++) + last++; + + for (cur = envp; *cur; cur++) { + for (bad = forbid; *bad; bad++) { + if (strncmp(*cur, *bad, strlen(*bad)) == 0) { + if (org) + *org = env_list_add(*org, *cur); + last = remote_entry(envp, cur - envp, last); + 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 */ + if (org) + *org = env_list_add(*org, *cur); + last = remote_entry(envp, cur - envp, last); + cur--; + break; + } + } +} + +void sanitize_env(void) +{ + __sanitize_env(NULL); +} + +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 +} + +#ifdef TEST_PROGRAM +int main(void) +{ + char *const *bad; + char copy[32]; + char *p; + int retval = EXIT_SUCCESS; + struct ul_env_list *removed = NULL; + + for (bad = forbid; *bad; bad++) { + strcpy(copy, *bad); + p = strchr(copy, '='); + if (p) + *p = '\0'; + setenv(copy, copy, 1); + } + + /* removed */ + __sanitize_env(&removed); + + /* check removal */ + for (bad = forbid; *bad; bad++) { + strcpy(copy, *bad); + p = strchr(copy, '='); + if (p) + *p = '\0'; + p = getenv(copy); + if (p) { + warnx("%s was not removed", copy); + retval = EXIT_FAILURE; + } + } + + /* restore removed */ + env_list_setenv(removed); + + /* check restore */ + for (bad = forbid; *bad; bad++) { + strcpy(copy, *bad); + p = strchr(copy, '='); + if (p) + *p = '\0'; + p = getenv(copy); + if (!p) { + warnx("%s was not restored", copy); + retval = EXIT_FAILURE; + } + } + + env_list_free(removed); + + return retval; +} +#endif diff --git a/lib/exec_shell.c b/lib/exec_shell.c new file mode 100644 index 0000000..6fef6c7 --- /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, (char *)NULL); + errexec(shell); +} diff --git a/lib/fileeq.c b/lib/fileeq.c new file mode 100644 index 0000000..d3d9aa3 --- /dev/null +++ b/lib/fileeq.c @@ -0,0 +1,641 @@ +/* + * compare files content + * + * The goal is to minimize number of data we need to read from the files and be + * ready to compare large set of files, it means reuse the previous data if + * possible. It never read entire file if not necessary. + * + * The another goal is to minimize number of open files (imagine "hardlink /"), + * the code can open only two files and reopen the file next time if + * necessary. + * + * This code supports multiple comparison methods. The very basic step which is + * generic for all methods is to read and compare an "intro" (a few bytes from + * the begging of the file). This intro buffer is always cached in 'struct + * ul_fileeq_data', this intro buffer is addresses as block=0. This primitive + * thing can reduce a lot ... + * + * The next steps depend on selected method: + * + * * memcmp method: always read data to userspace, nothing is cached, compare + * directly files content; fast for small sets of the small files. + * + * * Linux crypto API: zero-copy method based on sendfile(), data blocks are + * send to the kernel hash functions (sha1, ...), and only hash digest is read + * and cached in usersapce. Fast for large set of (large) files. + * + * + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * Written by Karel Zak <kzak@redhat.com> [October 2021] + */ + +#include <inttypes.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +/* Linux crypto */ +#ifdef HAVE_LINUX_IF_ALG_H +# include <sys/socket.h> +# include <linux/if_alg.h> +# include <sys/param.h> +# include <sys/sendfile.h> +# define USE_HARDLINK_CRYPTOAPI 1 +#endif + +#include "c.h" +#include "all-io.h" +#include "fileeq.h" +#include "debug.h" + +static UL_DEBUG_DEFINE_MASK(ulfileeq); +UL_DEBUG_DEFINE_MASKNAMES(ulfileeq) = UL_DEBUG_EMPTY_MASKNAMES; + +#define ULFILEEQ_DEBUG_INIT (1 << 1) +#define ULFILEEQ_DEBUG_CRYPTO (1 << 2) +#define ULFILEEQ_DEBUG_DATA (1 << 3) +#define ULFILEEQ_DEBUG_EQ (1 << 4) + +#define DBG(m, x) __UL_DBG(ulfileeq, ULFILEEQ_DEBUG_, m, x) +#define ON_DBG(m, x) __UL_DBG_CALL(ulfileeq, ULFILEEQ_DEBUG_, m, x) + +#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulfileeq) +#include "debugobj.h" + +static void ul_fileeq_init_debug(void) +{ + if (ulfileeq_debug_mask) + return; + __UL_INIT_DEBUG_FROM_ENV(ulfileeq, ULFILEEQ_DEBUG_, 0, ULFILEEQ_DEBUG); +} + +enum { + UL_FILEEQ_MEMCMP, + UL_FILEEQ_SHA1, + UL_FILEEQ_SHA256, + UL_FILEEQ_CRC32 +}; + +struct ul_fileeq_method { + int id; + const char *name; /* name used by applications */ + const char *kname; /* name used by kernel crypto */ + short digsiz; +}; + +static const struct ul_fileeq_method ul_eq_methods[] = { + [UL_FILEEQ_MEMCMP] = { + .id = UL_FILEEQ_MEMCMP, .name = "memcmp" + }, +#ifdef USE_HARDLINK_CRYPTOAPI + [UL_FILEEQ_SHA1] = { + .id = UL_FILEEQ_SHA1, .name = "sha1", + .digsiz = 20, .kname = "sha1" + }, + [UL_FILEEQ_SHA256] = { + .id = UL_FILEEQ_SHA256, .name = "sha256", + .digsiz = 32, .kname = "sha256" + }, + + [UL_FILEEQ_CRC32] = { + .id = UL_FILEEQ_CRC32, .name = "crc32", + .digsiz = 4, .kname = "crc32c" + } +#endif +}; + +#ifdef USE_HARDLINK_CRYPTOAPI +static void deinit_crypto_api(struct ul_fileeq *eq) +{ + if (!eq) + return; + + DBG(CRYPTO, ul_debugobj(eq, "deinit")); + + if (eq->fd_cip >= 0) + close(eq->fd_cip); + if (eq->fd_api >= 0) + close(eq->fd_api); + + eq->fd_cip = eq->fd_api = -1; + +} + +static int init_crypto_api(struct ul_fileeq *eq) +{ + struct sockaddr_alg sa = { + .salg_family = AF_ALG, + .salg_type = "hash", + }; + + assert(eq->method); + assert(eq->method->kname); + assert(eq->fd_api == -1); + assert(eq->fd_cip == -1); + + DBG(CRYPTO, ul_debugobj(eq, "init [%s]", eq->method->kname)); + + assert(sizeof(sa.salg_name) > strlen(eq->method->kname) + 1); + memcpy(&sa.salg_name, eq->method->kname, strlen(eq->method->kname) + 1); + + if ((eq->fd_api = socket(AF_ALG, SOCK_SEQPACKET, 0)) < 0) + goto fail; + if (bind(eq->fd_api, (struct sockaddr *) &sa, sizeof(sa)) != 0) + goto fail; + if ((eq->fd_cip = accept(eq->fd_api, NULL, 0)) < 0) + goto fail; + return 0; +fail: + deinit_crypto_api(eq); + return -1; +} +#endif + +int ul_fileeq_init(struct ul_fileeq *eq, const char *method) +{ + size_t i; + + ul_fileeq_init_debug(); + DBG(EQ, ul_debugobj(eq, "init [%s]", method)); + + memset(eq, 0, sizeof(*eq)); + eq->fd_api = -1; + eq->fd_cip = -1; + + for (i = 0; i < ARRAY_SIZE(ul_eq_methods); i++) { + const struct ul_fileeq_method *m = &ul_eq_methods[i]; + + if (strcmp(m->name, method) == 0) { + eq->method = m; + break; + } + } + + if (!eq->method) + return -1; +#ifdef USE_HARDLINK_CRYPTOAPI + if (eq->method->id != UL_FILEEQ_MEMCMP + && init_crypto_api(eq) != 0) + return -1; +#endif + return 0; +} + +void ul_fileeq_deinit(struct ul_fileeq *eq) +{ + if (!eq) + return; + + DBG(EQ, ul_debugobj(eq, "deinit")); +#ifdef USE_HARDLINK_CRYPTOAPI + deinit_crypto_api(eq); +#endif + free(eq->buf_a); + free(eq->buf_b); +} + +void ul_fileeq_data_close_file(struct ul_fileeq_data *data) +{ + assert(data); + + if (data->fd >= 0) { + DBG(DATA, ul_debugobj(data, "close")); + close(data->fd); + } + data->fd = -1; +} + +void ul_fileeq_data_init(struct ul_fileeq_data *data) +{ + DBG(DATA, ul_debugobj(data, "init")); + memset(data, 0, sizeof(*data)); + data->fd = -1; +} + +void ul_fileeq_data_deinit(struct ul_fileeq_data *data) +{ + assert(data); + + DBG(DATA, ul_debugobj(data, "deinit")); + free(data->blocks); + data->blocks = NULL; + data->nblocks = 0; + data->maxblocks = 0; + data->is_eof = 0; + data->name = NULL; + + ul_fileeq_data_close_file(data); +} + +int ul_fileeq_data_associated(struct ul_fileeq_data *data) +{ + return data->name != NULL; +} + +void ul_fileeq_data_set_file(struct ul_fileeq_data *data, const char *name) +{ + assert(data); + assert(name); + + DBG(DATA, ul_debugobj(data, "set file: %s", name)); + ul_fileeq_data_init(data); + data->name = name; +} + +size_t ul_fileeq_set_size(struct ul_fileeq *eq, uint64_t filesiz, + size_t readsiz, size_t memsiz) +{ + uint64_t nreads, maxdigs; + size_t digsiz; + + assert(eq); + + eq->filesiz = filesiz; + + switch (eq->method->id) { + case UL_FILEEQ_MEMCMP: + /* align file size */ + filesiz = (filesiz + readsiz) / readsiz * readsiz; + break; + default: + digsiz = eq->method->digsiz; + if (readsiz < digsiz) + readsiz = digsiz; + /* align file size */ + filesiz = (filesiz + readsiz) / readsiz * readsiz; + /* calculate limits */ + maxdigs = memsiz / digsiz; + if (maxdigs == 0) + maxdigs = 1; + nreads = filesiz / readsiz; + /* enlarge readsize for large files */ + if (nreads > maxdigs) + readsiz = filesiz / maxdigs; + break; + } + + eq->readsiz = readsiz; + eq->blocksmax = filesiz / readsiz; + + DBG(EQ, ul_debugobj(eq, "set sizes: filesiz=%ju, maxblocks=%" PRIu64 ", readsiz=%zu", + eq->filesiz, eq->blocksmax, eq->readsiz)); + return eq->blocksmax; +} + +static unsigned char *get_buffer(struct ul_fileeq *eq) +{ + if (!eq->buf_a) + eq->buf_a = malloc(eq->readsiz); + if (!eq->buf_b) + eq->buf_b = malloc(eq->readsiz); + + if (!eq->buf_a || !eq->buf_b) + return NULL; + + if (eq->buf_last == eq->buf_b) + eq->buf_last = eq->buf_a; + else + eq->buf_last = eq->buf_b; + + return eq->buf_last; +} + +#define get_cached_nblocks(_d) \ + ((_d)->nblocks ? (_d)->nblocks - 1 : 0) + +#define get_cached_offset(_e, _d) \ + ((_d)->nblocks == 0 ? 0 : \ + sizeof((_d)->intro) \ + + (get_cached_nblocks(_d) * (_e)->readsiz)) + + +static int get_fd(struct ul_fileeq *eq, struct ul_fileeq_data *data, off_t *off) +{ + off_t o = get_cached_offset(eq, data); + + assert(eq); + assert(data); + + + if (data->fd < 0) { + DBG(DATA, ul_debugobj(data, "open: %s", data->name)); + data->fd = open(data->name, O_RDONLY); + if (data->fd < 0) + return data->fd; + +#if defined(POSIX_FADV_SEQUENTIAL) && defined(HAVE_POSIX_FADVISE) + ignore_result( posix_fadvise(data->fd, o, 0, POSIX_FADV_SEQUENTIAL) ); +#endif + if (o) { + DBG(DATA, ul_debugobj(data, "lseek off=%ju", (uintmax_t) o)); + lseek(data->fd, o, SEEK_SET); + } + } + + if (off) + *off = o; + + return data->fd; +} + +static void memcmp_reset(struct ul_fileeq *eq, struct ul_fileeq_data *data) +{ + /* only intro[] is cached */ + if (data->nblocks) + data->nblocks = 1; + /* reset file possition */ + if (data->fd >= 0) + lseek(data->fd, get_cached_offset(eq, data), SEEK_SET); + data->is_eof = 0; +} + +static ssize_t read_block(struct ul_fileeq *eq, struct ul_fileeq_data *data, + size_t n, unsigned char **block) +{ + int fd; + off_t off = 0; + ssize_t rsz; + + if (data->is_eof || n > eq->blocksmax) + return 0; + + fd = get_fd(eq, data, &off); + if (fd < 0) + return fd; + + DBG(DATA, ul_debugobj(data, " read block off=%ju", (uintmax_t) off)); + + *block = get_buffer(eq); + if (!*block) + return -ENOMEM; + + rsz = read_all(data->fd, (char *) *block, eq->readsiz); + if (rsz < 0) { + DBG(DATA, ul_debugobj(data, " read failed")); + return rsz; + } + off += rsz; + data->nblocks++; + + if (rsz == 0 || (uint64_t) off >= eq->filesiz) { + data->is_eof = 1; + ul_fileeq_data_close_file(data); + } + + DBG(DATA, ul_debugobj(data, " read sz=%zu", rsz)); + return rsz; +} + +#ifdef USE_HARDLINK_CRYPTOAPI +static ssize_t get_digest(struct ul_fileeq *eq, struct ul_fileeq_data *data, + size_t n, unsigned char **block) +{ + off_t off = 0; + ssize_t rsz; + size_t sz; + int fd; + + if (n > eq->blocksmax) + return 0; + + /* return already cached if alvalable */ + if (n < get_cached_nblocks(data)) { + DBG(DATA, ul_debugobj(data, " digest cached")); + assert(data->blocks); + *block = data->blocks + (n * eq->method->digsiz); + return eq->method->digsiz; + } + + if (data->is_eof) { + DBG(DATA, ul_debugobj(data, " file EOF")); + return 0; + } + + /* read new block */ + fd = get_fd(eq, data, &off); + if (fd < 0) + return fd; + + DBG(DATA, ul_debugobj(data, " read digest off=%ju", (uintmax_t) off)); + + sz = eq->method->digsiz; + + if (!data->blocks) { + DBG(DATA, ul_debugobj(data, " alloc cache %zu", eq->blocksmax * sz)); + data->blocks = malloc(eq->blocksmax * sz); + if (!data->blocks) + return -ENOMEM; + } + + assert(n <= eq->blocksmax); + + rsz = sendfile(eq->fd_cip, data->fd, NULL, eq->readsiz); + DBG(DATA, ul_debugobj(data, " sent %zu [%zu wanted] to cipher", rsz, eq->readsiz)); + + if (rsz < 0) + return rsz; + + off += rsz; + + /* get block digest (note 1st block is data->intro */ + *block = data->blocks + (n * eq->method->digsiz); + rsz = read_all(eq->fd_cip, (char *) *block, sz); + + if (rsz > 0) + data->nblocks++; + if (rsz == 0 || (uint64_t) off >= eq->filesiz) { + data->is_eof = 1; + ul_fileeq_data_close_file(data); + } + DBG(DATA, ul_debugobj(data, " get %zuB digest", rsz)); + return rsz; +} +#endif + +static ssize_t get_intro(struct ul_fileeq *eq, struct ul_fileeq_data *data, + unsigned char **block) +{ + if (data->nblocks == 0) { + int fd = get_fd(eq, data, NULL); + ssize_t rsz; + + if (fd < 0) + return -1; + rsz = read_all(fd, (char *) data->intro, sizeof(data->intro)); + DBG(DATA, ul_debugobj(data, " read %zu bytes intro", sizeof(data->intro))); + if (rsz <= 0) + return -1; + data->nblocks = 1; + } + + DBG(DATA, ul_debugobj(data, " return intro")); + *block = data->intro; + return sizeof(data->intro); +} + +static ssize_t get_cmp_data(struct ul_fileeq *eq, struct ul_fileeq_data *data, + size_t blockno, unsigned char **block) +{ + if (blockno == 0) + return get_intro(eq, data, block); + + blockno--; + + switch (eq->method->id) { + case UL_FILEEQ_MEMCMP: + return read_block(eq, data, blockno, block); + default: + break; + } +#ifdef USE_HARDLINK_CRYPTOAPI + return get_digest(eq, data, blockno, block); +#else + return -1; +#endif +} + +#define CMP(a, b) ((a) > (b) ? 1 : ((a) < (b) ? -1 : 0)) + +int ul_fileeq(struct ul_fileeq *eq, + struct ul_fileeq_data *a, struct ul_fileeq_data *b) +{ + int cmp; + size_t n = 0; + + DBG(EQ, ul_debugobj(eq, "--> compare %s %s", a->name, b->name)); + + if (eq->method->id == UL_FILEEQ_MEMCMP) { + memcmp_reset(eq, a); + memcmp_reset(eq, b); + } + + do { + unsigned char *da, *db; + ssize_t ca, cb; + + DBG(EQ, ul_debugobj(eq, "compare block #%zu", n)); + + ca = get_cmp_data(eq, a, n, &da); + if (ca < 0) + goto done; + cb = get_cmp_data(eq, b, n, &db); + if (cb < 0) + goto done; + if (ca != cb || ca == 0) { + cmp = CMP(ca, cb); + break; + + } + cmp = memcmp(da, db, ca); + DBG(EQ, ul_debugobj(eq, "#%zu=%s", n, cmp == 0 ? "match" : "not-match")); + n++; + } while (cmp == 0); + + if (cmp == 0) { + if (!a->is_eof || !b->is_eof) + goto done; /* filesize chnaged? */ + + DBG(EQ, ul_debugobj(eq, "<-- MATCH")); + return 1; + } +done: + DBG(EQ, ul_debugobj(eq, " <-- NOT-MATCH")); + return 0; +} + +#ifdef TEST_PROGRAM_FILEEQ +# include <getopt.h> +# include <err.h> + +int main(int argc, char *argv[]) +{ + struct ul_fileeq eq; + struct ul_fileeq_data a, b, c; + const char *method = "sha1"; + static const struct option longopts[] = { + { "method", required_argument, NULL, 'm' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + int ch, rc; + const char *file_a = NULL, *file_b = NULL, *file_c = NULL; + struct stat st_a, st_b, st_c; + + while ((ch = getopt_long(argc, argv, "m:", longopts, NULL)) != -1) { + switch (ch) { + case 'm': + method = optarg; + break; + case 'h': + printf("usage: %s [options] <file> <file>\n" + " -m, --method <memcmp|sha1|crc32> compare method\n", + program_invocation_short_name); + return EXIT_FAILURE; + } + } + + if (optind < argc) + file_a = argv[optind++]; + if (optind < argc) + file_b = argv[optind++]; + if (optind < argc) + file_c = argv[optind++]; + + if (!file_a || !file_b) + errx(EXIT_FAILURE, "no files specified, see --help"); + + if (stat(file_a, &st_a) != 0 || !S_ISREG(st_a.st_mode)) + errx(EXIT_FAILURE, "%s: wrong file", file_a); + if (stat(file_b, &st_b) != 0 || !S_ISREG(st_b.st_mode)) + errx(EXIT_FAILURE, "%s: wrong file", file_b); + if (file_c && (stat(file_c, &st_c) != 0 || !S_ISREG(st_c.st_mode))) + errx(EXIT_FAILURE, "%s: wrong file", file_c); + + + if (st_a.st_size != st_b.st_size || + (file_c && st_a.st_size != st_c.st_size)) + errx(EXIT_FAILURE, "size of the files does not match"); + + + rc = ul_fileeq_init(&eq, method); + if (rc != 0 && strcmp(method, "memcmp") != 0) { + method = "memcmp"; + rc = ul_fileeq_init(&eq, method); + } + if (rc < 0) + err(EXIT_FAILURE, "failed to initialize files comparior"); + + ul_fileeq_data_set_file(&a, file_a); + ul_fileeq_data_set_file(&b, file_b); + + /* 3rd is optional */ + if (file_c) + ul_fileeq_data_set_file(&c, file_c); + + /* filesiz, readsiz, memsiz */ + ul_fileeq_set_size(&eq, st_a.st_size, 1024*1024, 4*1024); + + rc = ul_fileeq(&eq, &a, &b); + + printf("1st vs. 2nd: %s\n", rc == 1 ? "MATCH" : "NOT-MATCH"); + if (file_c) { + rc = ul_fileeq(&eq, &a, &c); + printf("1st vs. 3rd: %s\n", rc == 1 ? "MATCH" : "NOT-MATCH"); + + rc = ul_fileeq(&eq, &b, &c); + printf("2st vs. 3rd: %s\n", rc == 1 ? "MATCH" : "NOT-MATCH"); + } + + ul_fileeq_data_deinit(&a); + ul_fileeq_data_deinit(&b); + + if (file_c) + ul_fileeq_data_deinit(&c); + + ul_fileeq_deinit(&eq); + return EXIT_FAILURE; +} +#endif /* TEST_PROGRAM_FILEEQ */ diff --git a/lib/fileutils.c b/lib/fileutils.c new file mode 100644 index 0000000..7779e10 --- /dev/null +++ b/lib/fileutils.c @@ -0,0 +1,313 @@ +/* + * This code is in the public domain; do with it what you wish. + * + * Copyright (C) 2012 Sami Kerola <kerolasa@iki.fi> + * Copyright (C) 2012-2020 Karel Zak <kzak@redhat.com> + */ +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <string.h> + +#include "c.h" +#include "all-io.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; +} + +#ifdef F_DUPFD_CLOEXEC +int dup_fd_cloexec(int oldfd, int lowfd) +#else +int dup_fd_cloexec(int oldfd, int lowfd __attribute__((__unused__))) +#endif +{ + 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() + */ +unsigned 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; +} + +void ul_close_all_fds(unsigned int first, unsigned int last) +{ + struct dirent *d; + DIR *dir; + + dir = opendir(_PATH_PROC_FDDIR); + if (dir) { + while ((d = xreaddir(dir))) { + char *end; + unsigned int fd; + int dfd; + + errno = 0; + fd = strtoul(d->d_name, &end, 10); + + if (errno || end == d->d_name || !end || *end) + continue; + dfd = dirfd(dir); + if (dfd < 0) + continue; + if ((unsigned int)dfd == fd) + continue; + if (fd < first || last < fd) + continue; + close(fd); + } + closedir(dir); + } else { + unsigned fd, tbsz = get_fd_tabsize(); + + for (fd = 0; fd < tbsz; fd++) { + if (first <= fd && fd <= last) + close(fd); + } + } +} + +#ifdef TEST_PROGRAM_FILEUTILS +int main(int argc, char *argv[]) +{ + if (argc < 2) + errx(EXIT_FAILURE, "Usage %s --{mkstemp,close-fds,copy-file}", argv[0]); + + if (strcmp(argv[1], "--mkstemp") == 0) { + FILE *f; + char *tmpname = NULL; + + f = xfmkstemp(&tmpname, NULL, "test"); + unlink(tmpname); + free(tmpname); + fclose(f); + + } else if (strcmp(argv[1], "--close-fds") == 0) { + ignore_result( dup(STDIN_FILENO) ); + ignore_result( dup(STDIN_FILENO) ); + ignore_result( dup(STDIN_FILENO) ); + +# ifdef HAVE_CLOSE_RANGE + if (close_range(STDERR_FILENO + 1, ~0U, 0) < 0) +# endif + ul_close_all_fds(STDERR_FILENO + 1, ~0U); + + } else if (strcmp(argv[1], "--copy-file") == 0) { + int ret = ul_copy_file(STDIN_FILENO, STDOUT_FILENO); + if (ret == UL_COPY_READ_ERROR) + err(EXIT_FAILURE, "read"); + else if (ret == UL_COPY_WRITE_ERROR) + err(EXIT_FAILURE, "write"); + } + return EXIT_SUCCESS; +} +#endif + + +int ul_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; +} + +static int copy_file_simple(int from, int to) +{ + ssize_t nr; + char buf[BUFSIZ]; + + while ((nr = read_all(from, buf, sizeof(buf))) > 0) + if (write_all(to, buf, nr) == -1) + return UL_COPY_WRITE_ERROR; + if (nr < 0) + return UL_COPY_READ_ERROR; +#ifdef HAVE_EXPLICIT_BZERO + explicit_bzero(buf, sizeof(buf)); +#endif + return 0; +} + +/* Copies the contents of a file. Returns -1 on read error, -2 on write error. */ +int ul_copy_file(int from, int to) +{ +#ifdef HAVE_SENDFILE + struct stat st; + ssize_t nw; + + if (fstat(from, &st) == -1) + return UL_COPY_READ_ERROR; + if (!S_ISREG(st.st_mode)) + return copy_file_simple(from, to); + if (sendfile_all(to, from, NULL, st.st_size) < 0) + return copy_file_simple(from, to); + /* ensure we either get an EOF or an error */ + while ((nw = sendfile_all(to, from, NULL, 16*1024*1024)) != 0) + if (nw < 0) + return copy_file_simple(from, to); + return 0; +#else + return copy_file_simple(from, to); +#endif +} + +int ul_reopen(int fd, int flags) +{ + ssize_t ssz; + char buf[PATH_MAX]; + char fdpath[ sizeof(_PATH_PROC_FDDIR) + sizeof(stringify_value(INT_MAX)) ]; + + snprintf(fdpath, sizeof(fdpath), _PATH_PROC_FDDIR "/%d", fd); + + ssz = readlink(fdpath, buf, sizeof(buf) - 1); + if (ssz < 0) + return -errno; + + assert(ssz > 0); + + buf[ssz] = '\0'; + + return open(buf, flags); +} diff --git a/lib/idcache.c b/lib/idcache.c new file mode 100644 index 0000000..5550223 --- /dev/null +++ b/lib/idcache.c @@ -0,0 +1,117 @@ +/* + * 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 <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; +} + +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..31be71a --- /dev/null +++ b/lib/ismounted.c @@ -0,0 +1,396 @@ +/* + * 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 "strutils.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) + xstrncpy(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) + xstrncpy(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 && n >= 0) + xstrncpy(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) != 0) + /* 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() 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) + xstrncpy(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|O_NONBLOCK); + 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/jsonwrt.c b/lib/jsonwrt.c new file mode 100644 index 0000000..8ca1d4d --- /dev/null +++ b/lib/jsonwrt.c @@ -0,0 +1,229 @@ +/* + * JSON output formatting functions. + * + * 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 <stdio.h> +#include <inttypes.h> +#include <ctype.h> +#include <cctype.h> + +#include "c.h" +#include "jsonwrt.h" + +/* + * Requirements enumerated via testing (V8, Firefox, IE11): + * + * var charsToEscape = []; + * for (var i = 0; i < 65535; i += 1) { + * try { + * JSON.parse('{"sample": "' + String.fromCodePoint(i) + '"}'); + * } catch (e) { + * charsToEscape.push(i); + * } + * } + */ +static void fputs_quoted_case_json(const char *data, FILE *out, int dir) +{ + const char *p; + + fputc('"', out); + for (p = data; p && *p; p++) { + + const unsigned int c = (unsigned int) *p; + + /* From http://www.json.org + * + * The double-quote and backslashes would break out a string or + * init an escape sequence if not escaped. + * + * Note that single-quotes and forward slashes, while they're + * in the JSON spec, don't break double-quoted strings. + */ + if (c == '"' || c == '\\') { + fputc('\\', out); + fputc(c, out); + continue; + } + + /* All non-control characters OK; do the case swap as required. */ + if (c >= 0x20) { + /* + * Don't use locale sensitive ctype.h functions for regular + * ASCII chars, because for example with Turkish locale + * (aka LANG=tr_TR.UTF-8) toupper('I') returns 'I'. + */ + if (c <= 127) + fputc(dir == 1 ? c_toupper(c) : + dir == -1 ? c_tolower(c) : *p, out); + else + fputc(dir == 1 ? toupper(c) : + dir == -1 ? tolower(c) : *p, out); + continue; + } + + /* In addition, all chars under ' ' break Node's/V8/Chrome's, and + * Firefox's JSON.parse function + */ + switch (c) { + /* Handle short-hand cases to reduce output size. C + * has most of the same stuff here, so if there's an + * "Escape for C" function somewhere in the STL, we + * should probably be using it. + */ + case '\b': + fputs("\\b", out); + break; + case '\t': + fputs("\\t", out); + break; + case '\n': + fputs("\\n", out); + break; + case '\f': + fputs("\\f", out); + break; + case '\r': + fputs("\\r", out); + break; + default: + /* Other assorted control characters */ + fprintf(out, "\\u00%02x", c); + break; + } + } + fputc('"', out); +} + +#define fputs_quoted_json(_d, _o) fputs_quoted_case_json(_d, _o, 0) +#define fputs_quoted_json_upper(_d, _o) fputs_quoted_case_json(_d, _o, 1) +#define fputs_quoted_json_lower(_d, _o) fputs_quoted_case_json(_d, _o, -1) + +void ul_jsonwrt_init(struct ul_jsonwrt *fmt, FILE *out, int indent) +{ + fmt->out = out; + fmt->indent = indent; + fmt->after_close = 0; +} + +int ul_jsonwrt_is_ready(struct ul_jsonwrt *fmt) +{ + return fmt->out == NULL ? 0 : 1; +} + +void ul_jsonwrt_indent(struct ul_jsonwrt *fmt) +{ + int i; + + for (i = 0; i < fmt->indent; i++) + fputs(" ", fmt->out); +} + +void ul_jsonwrt_open(struct ul_jsonwrt *fmt, const char *name, int type) +{ + if (name) { + if (fmt->after_close) + fputs(",\n", fmt->out); + ul_jsonwrt_indent(fmt); + fputs_quoted_json_lower(name, fmt->out); + } else { + if (fmt->after_close) + fputs(",", fmt->out); + else + ul_jsonwrt_indent(fmt); + } + + switch (type) { + case UL_JSON_OBJECT: + fputs(name ? ": {\n" : "{\n", fmt->out); + fmt->indent++; + break; + case UL_JSON_ARRAY: + fputs(name ? ": [\n" : "[\n", fmt->out); + fmt->indent++; + break; + case UL_JSON_VALUE: + fputs(name ? ": " : " ", fmt->out); + break; + } + fmt->after_close = 0; +} + +void ul_jsonwrt_close(struct ul_jsonwrt *fmt, int type) +{ + if (fmt->indent == 1) { + fputs("\n}\n", fmt->out); + fmt->indent--; + fmt->after_close = 1; + return; + } + assert(fmt->indent > 0); + + switch (type) { + case UL_JSON_OBJECT: + fmt->indent--; + fputc('\n', fmt->out); + ul_jsonwrt_indent(fmt); + fputs("}", fmt->out); + break; + case UL_JSON_ARRAY: + fmt->indent--; + fputc('\n', fmt->out); + ul_jsonwrt_indent(fmt); + fputs("]", fmt->out); + break; + case UL_JSON_VALUE: + break; + } + + fmt->after_close = 1; +} + +void ul_jsonwrt_value_raw(struct ul_jsonwrt *fmt, + const char *name, const char *data) +{ + ul_jsonwrt_value_open(fmt, name); + if (data && *data) + fputs(data, fmt->out); + else + fputs("null", fmt->out); + ul_jsonwrt_value_close(fmt); +} + +void ul_jsonwrt_value_s(struct ul_jsonwrt *fmt, + const char *name, const char *data) +{ + ul_jsonwrt_value_open(fmt, name); + if (data && *data) + fputs_quoted_json(data, fmt->out); + else + fputs("null", fmt->out); + ul_jsonwrt_value_close(fmt); +} + +void ul_jsonwrt_value_u64(struct ul_jsonwrt *fmt, + const char *name, uint64_t data) +{ + ul_jsonwrt_value_open(fmt, name); + fprintf(fmt->out, "%"PRIu64, data); + ul_jsonwrt_value_close(fmt); +} + +void ul_jsonwrt_value_boolean(struct ul_jsonwrt *fmt, + const char *name, int data) +{ + ul_jsonwrt_value_open(fmt, name); + fputs(data ? "true" : "false", fmt->out); + ul_jsonwrt_value_close(fmt); +} + +void ul_jsonwrt_value_null(struct ul_jsonwrt *fmt, + const char *name) +{ + ul_jsonwrt_value_open(fmt, name); + fputs("null", fmt->out); + ul_jsonwrt_value_close(fmt); +} diff --git a/lib/langinfo.c b/lib/langinfo.c new file mode 100644 index 0000000..a200085 --- /dev/null +++ b/lib/langinfo.c @@ -0,0 +1,124 @@ +/* + * 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. + * + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * 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..119869e --- /dev/null +++ b/lib/linux_version.c @@ -0,0 +1,75 @@ +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + */ +#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..05376dd --- /dev/null +++ b/lib/loopdev.c @@ -0,0 +1,1924 @@ + +/* + * 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" +#include "fileutils.h" + + +#define LOOPDEV_MAX_TRIES 10 + +/* + * 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->blocksize = 0; + lc->has_info = 0; + lc->info_failed = 0; + *lc->device = '\0'; + memset(&lc->config, 0, sizeof(lc->config)); + + /* 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 + xstrncpy(lc->device, device, sizeof(lc->device)); + + 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 can be used 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 + * associated 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; + int rc = 0; + + if (!device || stat(device, &st) != 0 || !S_ISBLK(st.st_mode)) + rc = 0; + else if (major(st.st_rdev) == LOOPDEV_MAJOR) + rc = 1; + else if (sysfs_devno_is_wholedisk(st.st_rdev)) { + /* It's possible that kernel creates a device with a different + * major number ... check by /sys it's really loop device. + */ + char name[PATH_MAX], *cn, *p = NULL; + + snprintf(name, sizeof(name), _PATH_SYS_DEVBLOCK "/%d:%d", + major(st.st_rdev), minor(st.st_rdev)); + cn = canonicalize_path(name); + if (cn) + p = stripoff_last_component(cn); + rc = p && startswith(p, "loop"); + free(cn); + } + + if (rc == 0) + errno = ENODEV; + return rc; +} + +/* + * @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->config.info; + + fd = loopcxt_get_fd(lc); + if (fd < 0) + return NULL; + + if (ioctl(fd, LOOP_GET_STATUS64, &lc->config.info) == 0) { + lc->has_info = 1; + lc->info_failed = 0; + DBG(CXT, ul_debugobj(lc, "reading loop_info64 OK")); + return &lc->config.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 associated + * 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 preferred, 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) + if (ul_path_read_u64(sysfs, offset, "loop/offset") == 0) + rc = 0; + + 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) + if (ul_path_read_u64(sysfs, blocksize, "queue/logical_block_size") == 0) + rc = 0; + + /* 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) + if (ul_path_read_u64(sysfs, size, "loop/sizelimit") == 0) + rc = 0; + + 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 = 0; + dev_t dev = 0; + + 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 = 0; + + int rc = loopcxt_get_offset(lc, &off) == 0 && off == offset; + + if (rc && flags & LOOPDEV_FL_SIZELIMIT) { + uint64_t sz = 0; + + return loopcxt_get_sizelimit(lc, &sz) == 0 && sz == sizelimit; + } + 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->config.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->config.info.lo_sizelimit = sizelimit; + + DBG(CXT, ul_debugobj(lc, "set sizelimit=%jd", sizelimit)); + return 0; +} + +/* + * The blocksize will be used by loopcxt_set_device(). For already exiting + * devices use loopcxt_ioctl_blocksize(). + * + * The setting is removed by loopcxt_set_device() loopcxt_next()! + */ +int loopcxt_set_blocksize(struct loopdev_cxt *lc, uint64_t blocksize) +{ + if (!lc) + return -EINVAL; + lc->blocksize = blocksize; + + DBG(CXT, ul_debugobj(lc, "set blocksize=%jd", blocksize)); + 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->config.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; + + xstrncpy((char *)lc->config.info.lo_file_name, lc->filename, LO_NAME_SIZE); + + DBG(CXT, ul_debugobj(lc, "set backing file=%s", lc->config.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->config.info.lo_offset && !lc->config.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->config.info.lo_offset) { + DBG(CXT, ul_debugobj(lc, "failed to determine expected size")); + return 0; /* ignore this error */ + } + + if (lc->config.info.lo_offset > 0) + expected_size -= lc->config.info.lo_offset; + + if (lc->config.info.lo_sizelimit > 0 && lc->config.info.lo_sizelimit < expected_size) + expected_size = lc->config.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_ioctl_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, flags = O_CLOEXEC; + int rc = -1, cnt = 0, err, again; + int errsv = 0; + int fallback = 0; + + if (!lc || !*lc->device || !lc->filename) + return -EINVAL; + + DBG(SETUP, ul_debugobj(lc, "device setup requested")); + + /* + * Open backing file and device + */ + if (lc->config.info.lo_flags & LO_FLAGS_READ_ONLY) + mode = O_RDONLY; + + if (lc->config.info.lo_flags & LO_FLAGS_DIRECT_IO) + flags |= O_DIRECT; + + if ((file_fd = open(lc->filename, mode | flags)) < 0) { + if (mode != O_RDONLY && (errno == EROFS || errno == EACCES)) + file_fd = open(lc->filename, (mode = O_RDONLY) | flags); + + 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->config.info.lo_flags |= LO_FLAGS_READ_ONLY; /* kernel loopdev mode */ + } else { + lc->flags |= LOOPDEV_FL_RDWR; /* open() mode */ + lc->config.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")); + + /* + * Atomic way to configure all by one ioctl call + * -- since Linux v5.8-rc1, commit 3448914e8cc550ba792d4ccc74471d1ca4293aae + */ + lc->config.fd = file_fd; + if (lc->blocksize > 0) + lc->config.block_size = lc->blocksize; + + if (ioctl(dev_fd, LOOP_CONFIGURE, &lc->config) < 0) { + rc = -errno; + errsv = errno; + if (errno != EINVAL && errno != ENOTTY && errno != ENOSYS) { + DBG(SETUP, ul_debugobj(lc, "LOOP_CONFIGURE failed: %m")); + goto err; + } + fallback = 1; + } else { + DBG(SETUP, ul_debugobj(lc, "LOOP_CONFIGURE: OK")); + } + + /* + * Old deprecated way; first assign backing file FD and then in the + * second step set loop device properties. + */ + if (fallback) { + 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 (lc->blocksize > 0 + && (rc = loopcxt_ioctl_blocksize(lc, lc->blocksize)) < 0) { + errsv = -rc; + goto err; + } + + do { + err = ioctl(dev_fd, LOOP_SET_STATUS64, &lc->config.info); + again = err && errno == EAGAIN; + if (again) + xusleep(250000); + } while (again); + + if (err) { + 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->config, 0, sizeof(lc->config)); + 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_ioctl_status(struct loopdev_cxt *lc) +{ + int dev_fd, rc = -1, err, again, tries = 0; + + errno = 0; + dev_fd = loopcxt_get_fd(lc); + + if (dev_fd < 0) { + rc = -errno; + return rc; + } + DBG(SETUP, ul_debugobj(lc, "device open: OK")); + + do { + err = ioctl(dev_fd, LOOP_SET_STATUS64, &lc->config.info); + again = err && errno == EAGAIN; + if (again) { + xusleep(250000); + tries++; + } + } while (again && tries <= LOOPDEV_MAX_TRIES); + + if (err) { + 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_ioctl_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_ioctl_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_ioctl_blocksize(struct loopdev_cxt *lc, uint64_t blocksize) +{ + int fd = loopcxt_get_fd(lc); + int err, again, tries = 0; + + if (fd < 0) + return -EINVAL; + + do { + /* Kernels prior to v4.14 don't support this ioctl */ + err = ioctl(fd, LOOP_SET_BLOCK_SIZE, (unsigned long) blocksize); + again = err && errno == EAGAIN; + if (again) { + xusleep(250000); + tries++; + } else if (err) { + int rc = -errno; + DBG(CXT, ul_debugobj(lc, "LOOP_SET_BLOCK_SIZE failed: %m")); + return rc; + } + } while (again && tries <= LOOPDEV_MAX_TRIES); + + 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; +} + +int loopdev_has_backing_file(const char *device) +{ + char *tmp = loopdev_get_backing_file(device); + + if (tmp) { + free(tmp); + return 1; + } + return 0; +} + +/* + * 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); + /* + * Either the loopdev is unused or we've got an error which can + * happen when we are racing with device autoclear. Just ignore + * this loopdev... + */ + if (rc <= 0) + continue; + + 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))); + continue; + } + 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))); + continue; + } + + /* 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 associated 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) != 0) { + 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; +} + +#ifdef TEST_PROGRAM_LOOPDEV +int main(int argc, char *argv[]) +{ + if (argc < 2) + goto usage; + + if (strcmp(argv[1], "--is-loopdev") == 0 && argc == 3) + printf("%s: %s\n", argv[2], is_loopdev(argv[2]) ? "OK" : "FAIL"); + else + goto usage; + + return EXIT_SUCCESS; +usage: + fprintf(stderr, "usage: %1$s --is-loopdev <dev>\n", + program_invocation_short_name); + return EXIT_FAILURE; +} +#endif + diff --git a/lib/mangle.c b/lib/mangle.c new file mode 100644 index 0000000..1a3b89a --- /dev/null +++ b/lib/mangle.c @@ -0,0 +1,169 @@ +/* + * Functions for \oct encoding used in mtab/fstab/swaps/etc. + * + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * 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 == ' ' || *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]); + if (x) { + unmangle_to_buffer(x, x, strlen(x) + 1); + + 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..e251202 --- /dev/null +++ b/lib/mbsalign.c @@ -0,0 +1,627 @@ +/* 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 <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" + +/* + * Counts number of cells in multibyte string. All control and + * non-printable chars are ignored. + * + * Returns: number of cells. + */ +size_t mbs_nwidth(const char *buf, size_t bufsz) +{ + const char *p = buf, *last = buf; + size_t width = 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 (iscntrl((unsigned char) *p)) { + p++; + + /* try detect "\e[x;ym" and skip on success */ + if (*p && *p == '[') { + const char *e = p; + while (*e && e < last && *e != 'm') + e++; + if (*e == 'm') + p = e + 1; + } + continue; + } +#ifdef HAVE_WIDECHAR + wchar_t wc; + size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st); + + if (len == 0) + break; + if (len > 0 && iswprint(wc)) { + int x = wcwidth(wc); + if (x > 0) + width += x; + } else if (len == (size_t) -1 || len == (size_t) -2) + len = 1; + p += len; +#else + if (isprint((unsigned char) *p)) + width++; + p++; +#endif + } + + return width; +} + +size_t mbs_width(const char *s) +{ + if (!s || !*s) + return 0; + return mbs_nwidth(s, strlen(s)); +} + +/* + * 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; +} + +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) +{ + 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/meson.build b/lib/meson.build new file mode 100644 index 0000000..0847d59 --- /dev/null +++ b/lib/meson.build @@ -0,0 +1,99 @@ +lib_common_sources = ''' + blkdev.c + buffer.c + canonicalize.c + color-names.c + crc32.c + crc32c.c + c_strtod.c + encode.c + env.c + fileutils.c + idcache.c + jsonwrt.c + mangle.c + match.c + mbsalign.c + mbsedit.c + md5.c + pager.c + procfs.c + pwdutils.c + randutils.c + sha1.c + signames.c + strutils.c + strv.c + timeutils.c + ttyutils.c +'''.split() + +idcache_c = files('idcache.c') +randutils_c = files('randutils.c') +md5_c = files('md5.c') +sha1_c = files('sha1.c') +strutils_c = files('strutils.c') +strv_c = files('strv.c') + +lib_common_sources += [idcache_c, + randutils_c, + md5_c, + sha1_c, + strutils_c, + strv_c] + +monotonic_c = files('monotonic.c') +timer_c = files('timer.c') +swapprober_c = files('swapprober.c') +pty_session_c = files('pty-session.c') +ismounted_c = files('ismounted.c') +exec_shell_c = files('exec_shell.c') +fileeq_c = files('fileeq.c') + +if LINUX + lib_common_sources += ''' + caputils.c + linux_version.c + loopdev.c +'''.split() +endif + +if build_plymouth_support + lib_common_sources += ''' + plymouth-ctrl.c +'''.split() +endif + +if conf.get('HAVE_LANGINFO_H') in [1] + lib_common_sources += 'langinfo.c' +endif + +if conf.get('HAVE_CPU_SET_T') in [1] + lib_common_sources += 'cpuset.c' +endif + +if conf.get('HAVE_OPENAT') in [1] and conf.get('HAVE_DIRFD') in [1] + lib_common_sources += ''' + path.c + procfs.c + sysfs.c +'''.split() +endif + +lib_common = static_library( + 'common', + lib_common_sources, + include_directories : dir_include) + + +lib_color_sources = files(''' + colors.c + color-names.c +'''.split()) +# colors.h include/color-names.h + +lib_tcolors = static_library( + 'tcolors', + lib_color_sources, + include_directories : dir_include, + dependencies : curses_libs) diff --git a/lib/monotonic.c b/lib/monotonic.c new file mode 100644 index 0000000..f0aeba6 --- /dev/null +++ b/lib/monotonic.c @@ -0,0 +1,81 @@ +/* + * Please, don't add this file to libcommon because clock_gettime() requires + * -lrt on systems with old libc. + * + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + */ +#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 +} + +time_t get_suspended_time(void) +{ +#if defined(CLOCK_BOOTTIME) && defined(CLOCK_MONOTONIC) + struct timespec boot, mono; + + if (clock_gettime(CLOCK_BOOTTIME, &boot) == 0 && + clock_gettime(CLOCK_MONOTONIC, &mono) == 0) + return boot.tv_sec - mono.tv_sec; +#endif + return 0; +} + +int gettime_monotonic(struct timeval *tv) +{ +#ifdef CLOCK_MONOTONIC + /* Can slew only by ntp and adjtime */ + int ret; + struct timespec ts; + + /* Linux specific, can't slew */ + if (!(ret = clock_gettime(UL_CLOCK_MONOTONIC, &ts))) { + 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..747521e --- /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 (0 <= cmd->in) + close(cmd->in); + return -1; + } + + if (need_in) + close(fdin[0]); + else if (0 <= 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/path.c b/lib/path.c new file mode 100644 index 0000000..42b4ead --- /dev/null +++ b/lib/path.c @@ -0,0 +1,1285 @@ +/* + * 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" +#include "strutils.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); + ul_path_close_dirfd(pc); + 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_is_accessible(struct path_cxt *pc) +{ + const char *path; + assert(pc); + + if (pc->dir_fd >= 0) + return 1; + + path = get_absdir(pc); + if (!path) + return 0; + return access(path, F_OK) == 0; +} + +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; +} + +/* Note that next ul_path_get_dirfd() will reopen the directory */ +void ul_path_close_dirfd(struct path_cxt *pc) +{ + assert(pc); + + if (pc->dir_fd >= 0) { + DBG(CXT, ul_debugobj(pc, "closing dir")); + close(pc->dir_fd); + pc->dir_fd = -1; + } +} + +int ul_path_isopen_dirfd(struct path_cxt *pc) +{ + return pc && pc->dir_fd >= 0; +} + +static const char *ul_path_mkpath(struct path_cxt *pc, const char *path, va_list ap) +{ + int rc; + + errno = 0; + + rc = vsnprintf(pc->path_buffer, sizeof(pc->path_buffer), path, ap); + if (rc < 0) { + if (!errno) + errno = EINVAL; + 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; + xstrncpy(buf, tmp, bufsz); + } + + return buf; +} + + +int ul_path_access(struct path_cxt *pc, int mode, const char *path) +{ + int rc; + + if (!pc) { + rc = access(path, mode); + DBG(CXT, ul_debug("access '%s' [no context, rc=%d]", path, rc)); + } else { + int dir = ul_path_get_dirfd(pc); + if (dir < 0) + return dir; + if (*path == '/') + 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); + + DBG(CXT, ul_debugobj(pc, "access: '%s' [rc=%d]", path, rc)); + } + 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 !p ? -errno : ul_path_access(pc, mode, p); +} + +int ul_path_stat(struct path_cxt *pc, struct stat *sb, int flags, const char *path) +{ + int rc; + + if (!pc) { + rc = path ? stat(path, sb) : -EINVAL; + DBG(CXT, ul_debug("stat '%s' [no context, rc=%d]", path, rc)); + } else { + int dir = ul_path_get_dirfd(pc); + if (dir < 0) + return dir; + if (path) { + if (*path == '/') + path++; + rc = fstatat(dir, path, sb, flags); + + } else + rc = fstat(dir, sb); /* dir itself */ + + if (rc && errno == ENOENT + && path + && pc->redirect_on_enoent + && pc->redirect_on_enoent(pc, path, &dir) == 0) + rc = fstatat(dir, path, sb, 0); + + DBG(CXT, ul_debugobj(pc, "stat '%s' [rc=%d]", path, rc)); + } + return rc; +} + +int ul_path_open(struct path_cxt *pc, int flags, const char *path) +{ + int fd; + + if (!path) + return -EINVAL; + if (!pc) { + fd = open(path, flags); + DBG(CXT, ul_debug("opening '%s' [no context]", path)); + } else { + int fdx; + int dir = ul_path_get_dirfd(pc); + if (dir < 0) + return dir; + + if (*path == '/') + path++; + + 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 = ul_path_mkpath(pc, path, ap); + + return !p ? -errno : 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 = ul_path_mkpath(pc, path, ap); + + return !p ? NULL : 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) { + int dirfd; + + DBG(CXT, ul_debugobj(pc, "duplicate dir path")); + dirfd = ul_path_get_dirfd(pc); + if (dirfd >= 0) + fd = dup_fd_cloexec(dirfd, 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 = ul_path_mkpath(pc, path, ap); + + return !p ? NULL : 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; + ssize_t ssz; + + if (!path) { + const char *p = get_absdir(pc); + if (!p) + return -errno; + ssz = readlink(p, buf, bufsiz - 1); + } else { + dirfd = ul_path_get_dirfd(pc); + if (dirfd < 0) + return dirfd; + + if (*path == '/') + path++; + + ssz = readlinkat(dirfd, path, buf, bufsiz - 1); + } + + if (ssz >= 0) + buf[ssz] = '\0'; + return ssz; +} + +/* + * 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); + + return !p ? -errno : 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 = ul_path_mkpath(pc, path, ap); + + return !p ? -errno : 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 without \0, nothing is allocated if returns <= 0. + */ +int ul_path_read_string(struct path_cxt *pc, char **str, const char *path) +{ + char buf[BUFSIZ]; + int rc; + + if (!str) + return -EINVAL; + + *str = NULL; + rc = ul_path_read(pc, buf, sizeof(buf) - 1, path); + if (rc < 0) + return rc; + + /* Remove tailing newline (usual in sysfs) */ + if (rc > 0 && *(buf + rc - 1) == '\n') + --rc; + if (rc == 0) + return 0; + + 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); + + return !p ? -errno : 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) + buf[0] = '\0'; + + else if (rc > 0) { + /* Remove tailing newline (usual in sysfs) */ + if (*(buf + rc - 1) == '\n') + buf[--rc] = '\0'; + else + buf[rc - 1] = '\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); + + return !p ? -errno : 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); + + return !p ? -errno : 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); + + return !p ? -errno : 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); + + return !p ? -errno : 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 = 0; + + 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); + + return !p ? -errno : 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 = 0, min = 0; + + 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); + + return !p ? -errno : 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); + + return !p ? -errno : 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); + + return !p ? -errno : ul_path_write_u64(pc, num, p); + +} + +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); + + return !p ? -errno : ul_path_count_dirents(pc, p); +} + +/* first call (when @sub is NULL) opens the directory, last call closes the diretory */ +int ul_path_next_dirent(struct path_cxt *pc, DIR **sub, const char *dirname, struct dirent **d) +{ + if (!pc || !sub || !d) + return -EINVAL; + + if (!*sub) { + *sub = ul_path_opendir(pc, dirname); + if (!*sub) + return -errno; + } + + *d = xreaddir(*sub); + if (*d) + return 0; + + closedir(*sub); + *sub = NULL; + return 1; +} + +/* + * 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]; + int rc; + + *set = NULL; + + f = ul_path_vfopenf(pc, "r" UL_CLOEXECSTR, path, ap); + if (!f) + return -errno; + + rc = fgets(buf, len, f) == NULL ? -errno : 0; + fclose(f); + + if (rc) + return rc; + + 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("%s", 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/procfs.c b/lib/procfs.c new file mode 100644 index 0000000..4d6d25b --- /dev/null +++ b/lib/procfs.c @@ -0,0 +1,564 @@ +/* + * 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 <sys/vfs.h> +#include <errno.h> + +#include "c.h" +#include "pathnames.h" +#include "procfs.h" +#include "fileutils.h" +#include "all-io.h" +#include "debug.h" +#include "strutils.h" +#include "statfs_magic.h" + +static void procfs_process_deinit_path(struct path_cxt *pc); + +/* + * Debug stuff (based on include/debug.h) + */ +static UL_DEBUG_DEFINE_MASK(ulprocfs); +UL_DEBUG_DEFINE_MASKNAMES(ulprocfs) = UL_DEBUG_EMPTY_MASKNAMES; + +#define ULPROCFS_DEBUG_INIT (1 << 1) +#define ULPROCFS_DEBUG_CXT (1 << 2) + +#define DBG(m, x) __UL_DBG(ulprocfs, ULPROCFS_DEBUG_, m, x) +#define ON_DBG(m, x) __UL_DBG_CALL(ulprocfs, ULPROCFS_DEBUG_, m, x) + +#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulprocfs) +#include "debugobj.h" + +void ul_procfs_init_debug(void) +{ + if (ulprocfs_debug_mask) + return; + __UL_INIT_DEBUG_FROM_ENV(ulprocfs, ULPROCFS_DEBUG_, 0, ULPROCFS_DEBUG); +} + +struct path_cxt *ul_new_procfs_path(pid_t pid, const char *prefix) +{ + struct path_cxt *pc = ul_new_path(NULL); + + if (!pc) + return NULL; + if (prefix) + ul_path_set_prefix(pc, prefix); + + if (procfs_process_init_path(pc, pid) != 0) { + ul_unref_path(pc); + return NULL; + } + + DBG(CXT, ul_debugobj(pc, "alloc")); + return pc; +} + +/* + * procfs_blkdev_* is procfs extension to ul_path_* API to read info about process. + * + * The function is possible to call in loop and without sysfs_procfs_deinit_path(). + * The procfs_process_deinit_path() is automatically called by ul_unref_path(). + * + */ +int procfs_process_init_path(struct path_cxt *pc, pid_t pid) +{ + struct procfs_process *prc; + int rc; + char buf[sizeof(_PATH_PROC) + sizeof(stringify_value(UINT32_MAX)) + 2]; + + /* define path to pid stuff */ + snprintf(buf, sizeof(buf), _PATH_PROC "/%zu", (size_t) pid); + 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 procfs specific stuff */ + prc = ul_path_get_dialect(pc); + if (!prc) { + DBG(CXT, ul_debugobj(pc, "alloc new procfs handler")); + prc = calloc(1, sizeof(struct procfs_process)); + if (!prc) + return -ENOMEM; + + ul_path_set_dialect(pc, prc, procfs_process_deinit_path); + } + + DBG(CXT, ul_debugobj(pc, "init procfs stuff")); + + prc->pid = pid; + return 0; +} + +static void procfs_process_deinit_path(struct path_cxt *pc) +{ + struct procfs_process *prc; + + if (!pc) + return; + + DBG(CXT, ul_debugobj(pc, "deinit")); + + prc = ul_path_get_dialect(pc); + if (!prc) + return; + + free(prc); + ul_path_set_dialect(pc, NULL, NULL); +} + +static ssize_t read_procfs_file(int fd, char *buf, size_t bufsz) +{ + ssize_t sz = 0; + size_t i; + + if (fd < 0) + return -EINVAL; + + sz = read_all(fd, buf, bufsz); + if (sz <= 0) + return sz; + + for (i = 0; i < (size_t) sz; i++) { + if (buf[i] == '\0') + buf[i] = ' '; + } + buf[sz - 1] = '\0'; + return sz; +} + +static ssize_t procfs_process_get_line_for(struct path_cxt *pc, char *buf, size_t bufsz, + const char *fname) +{ + int fd = ul_path_open(pc, O_RDONLY|O_CLOEXEC, fname); + + if (fd >= 0) { + ssize_t sz = read_procfs_file(fd, buf, bufsz); + close(fd); + return sz; + } + return -errno; +} + +ssize_t procfs_process_get_cmdline(struct path_cxt *pc, char *buf, size_t bufsz) +{ + return procfs_process_get_line_for(pc, buf, bufsz, "cmdline"); +} + +ssize_t procfs_process_get_cmdname(struct path_cxt *pc, char *buf, size_t bufsz) +{ + return procfs_process_get_line_for(pc, buf, bufsz, "comm"); +} + +ssize_t procfs_process_get_stat(struct path_cxt *pc, char *buf, size_t bufsz) +{ + return procfs_process_get_line_for(pc, buf, bufsz, "stat"); +} + +int procfs_process_get_uid(struct path_cxt *pc, uid_t *uid) +{ + struct stat sb; + int rc; + + if ((rc = ul_path_stat(pc, &sb, 0, NULL)) == 0) + *uid = sb.st_uid; + return rc; +} + +/* + * returns the next task TID, the @sub is automatically initialized + * when called first time and closed after last call or you can + * call closedir()* when you need to break the loop. + * + * Returns: <0 on error, 0 on success, >1 done + * + * Example: + * + * pid_t tid; + * DIR *sub = NULL; + * path_cxt *pc = ul_new_procfs_path(123, NULL); + * + * while (procfs_process_next_tid(pc, &sub, &tid) == 0) + * printf("task: %d", (int) tid); + * + */ +int procfs_process_next_tid(struct path_cxt *pc, DIR **sub, pid_t *tid) +{ + struct dirent *d; + + if (!pc || !sub || !tid) + return -EINVAL; + + if (!*sub) { + *sub = ul_path_opendir(pc, "task"); + if (!*sub) + return -errno; + } + + while ((d = xreaddir(*sub))) { + if (procfs_dirent_get_pid(d, tid) == 0) + return 0; + } + + closedir(*sub); + *sub = NULL; + return 1; +} + +int procfs_process_next_fd(struct path_cxt *pc, DIR **sub, int *fd) +{ + struct dirent *d; + + if (!pc || !sub || !fd) + return -EINVAL; + + if (!*sub) { + *sub = ul_path_opendir(pc, "fd"); + if (!*sub) + return -errno; + } + + while ((d = xreaddir(*sub))) { + uint64_t num; +#ifdef _DIRENT_HAVE_D_TYPE + if (d->d_type != DT_LNK && d->d_type != DT_UNKNOWN) + continue; +#endif + if (ul_strtou64(d->d_name, &num, 10) < 0) + continue; + *fd = num; + return 0; + } + + closedir(*sub); + *sub = NULL; + return 1; +} + +/* + * Simple 'dirent' based stuff for use-cases where procfs_process_* API is overkill + */ + +/* stupid, but good enough as a basic filter */ +int procfs_dirent_is_process(struct dirent *d) +{ +#ifdef _DIRENT_HAVE_D_TYPE + if (d->d_type != DT_DIR && d->d_type != DT_UNKNOWN) + return 0; +#endif + if (!isdigit((unsigned char) *d->d_name)) + return 0; + + return 1; +} + +int procfs_dirent_get_pid(struct dirent *d, pid_t *pid) +{ + uint64_t num; + + if (!procfs_dirent_is_process(d)) + return -EINVAL; + + if (ul_strtou64(d->d_name, &num, 10) < 0) + return -EINVAL; + + *pid = (pid_t) num; + return 0; +} + +int procfs_dirent_get_uid(DIR *procfs, struct dirent *d, uid_t *uid) +{ + struct stat st; + + if (!procfs_dirent_is_process(d)) + return -EINVAL; + + if (fstatat(dirfd(procfs), d->d_name, &st, 0)) + return -EINVAL; + + *uid = st.st_uid; + return 0; +} + +int procfs_dirent_match_uid(DIR *procfs, struct dirent *d, uid_t uid) +{ + uid_t x; + + if (procfs_dirent_get_uid(procfs, d, &x) == 0) + return x == uid; + + return 0; +} + +/* "name" of process; may be truncated, see prctl(2) and PR_SET_NAME. + * The minimal of the @buf has to be 32 bytes. */ +int procfs_dirent_get_name(DIR *procfs, struct dirent *d, char *buf, size_t bufsz) +{ + FILE *f; + size_t sz; + char tmp[1024], *p, *end = NULL; + + if (bufsz < 32) + return -EINVAL; + if (!procfs_dirent_is_process(d)) + return -EINVAL; + + snprintf(tmp, sizeof(tmp), "%s/stat", d->d_name); + f = fopen_at(dirfd(procfs), tmp, O_CLOEXEC|O_RDONLY, "r"); + if (!f) + return -errno; + + p = fgets(tmp, sizeof(tmp), f); + fclose(f); + if (!p) + return -errno; + + /* skip PID */ + while (*p && *p != '(') + p++; + + /* skip extra '(' */ + while (*p && *p == '(') + p++; + + end = p; + while (*end && *end != ')') + end++; + + sz = end - p; + if (sz > bufsz) + sz = bufsz - 1; + + memcpy(buf, p, sz); + buf[sz] = '\0'; + + return 0; +} + +int procfs_dirent_match_name(DIR *procfs, struct dirent *d, const char *name) +{ + char buf[33]; + + if (procfs_dirent_get_name(procfs, d, buf, sizeof(buf)) == 0) + return strcmp(name, buf) == 0; + + return 0; +} + +/* checks if fd is file in a procfs; + * returns 1 if true, 0 if false or couldn't determine */ +int fd_is_procfs(int fd) +{ + struct statfs st; + int ret; + + do { + errno = 0; + ret = fstatfs(fd, &st); + + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN) + return 0; + xusleep(250000); + } + } while (ret != 0); + + return st.f_type == STATFS_PROC_MAGIC; +} + +static char *strdup_procfs_file(pid_t pid, const char *name) +{ + char buf[BUFSIZ]; + char *re = NULL; + int fd; + + snprintf(buf, sizeof(buf), _PATH_PROC "/%d/%s", (int) pid, name); + fd = open(buf, O_CLOEXEC|O_RDONLY); + if (fd < 0) + return NULL; + + if (read_procfs_file(fd, buf, sizeof(buf)) > 0) + re = strdup(buf); + close(fd); + return re; +} + +char *pid_get_cmdname(pid_t pid) +{ + return strdup_procfs_file(pid, "comm"); +} + +char *pid_get_cmdline(pid_t pid) +{ + return strdup_procfs_file(pid, "cmdline"); +} + +#ifdef TEST_PROGRAM_PROCFS + +static int test_tasks(int argc, char *argv[]) +{ + DIR *sub = NULL; + struct path_cxt *pc; + pid_t tid = 0, pid; + + if (argc != 2) + return EXIT_FAILURE; + + pid = strtol(argv[1], (char **) NULL, 10); + printf("PID=%d, TIDs:", pid); + + pc = ul_new_procfs_path(pid, NULL); + if (!pc) + err(EXIT_FAILURE, "alloc procfs handler failed"); + + while (procfs_process_next_tid(pc, &sub, &tid) == 0) + printf(" %d", tid); + + printf("\n"); + ul_unref_path(pc); + return EXIT_SUCCESS; +} + +static int test_fds(int argc, char *argv[]) +{ + DIR *sub = NULL; + struct path_cxt *pc; + pid_t pid; + int fd = -1; + + if (argc != 2) + return EXIT_FAILURE; + + pid = strtol(argv[1], (char **) NULL, 10); + printf("PID=%d, FDs:", pid); + + pc = ul_new_procfs_path(pid, NULL); + if (!pc) + err(EXIT_FAILURE, "alloc procfs handler failed"); + + while (procfs_process_next_fd(pc, &sub, &fd) == 0) + printf(" %d", fd); + + fputc('\n', stdout); + ul_unref_path(pc); + return EXIT_SUCCESS; +} + +static int test_processes(int argc, char *argv[]) +{ + DIR *dir; + struct dirent *d; + char *name = NULL; + uid_t uid = (uid_t) -1; + char buf[128]; + + if (argc >= 3 && strcmp(argv[1], "--name") == 0) + name = argv[2]; + if (argc >= 3 && strcmp(argv[1], "--uid") == 0) + uid = (uid_t) atol(argv[2]); + + dir = opendir(_PATH_PROC); + if (!dir) + err(EXIT_FAILURE, "cannot open proc"); + + while ((d = xreaddir(dir))) { + pid_t pid = 0; + + if (procfs_dirent_get_pid(d, &pid) != 0) + continue; + if (name && !procfs_dirent_match_name(dir, d, name)) + continue; + if (uid != (uid_t) -1 && !procfs_dirent_match_uid(dir, d, uid)) + continue; + procfs_dirent_get_name(dir, d, buf, sizeof(buf)); + printf(" %d [%s]", pid, buf); + } + + fputc('\n', stdout); + closedir(dir); + return EXIT_SUCCESS; +} + +static int test_one_process(int argc, char *argv[]) +{ + pid_t pid; + struct path_cxt *pc; + char buf[BUFSIZ]; + uid_t uid = (uid_t) -1; + + if (argc != 2) + return EXIT_FAILURE; + pid = strtol(argv[1], (char **) NULL, 10); + + pc = ul_new_procfs_path(pid, NULL); + if (!pc) + err(EXIT_FAILURE, "cannot alloc procfs handler"); + + printf("%d\n", (int) pid); + + procfs_process_get_uid(pc, &uid); + printf(" UID: %zu\n", (size_t) uid); + + procfs_process_get_cmdline(pc, buf, sizeof(buf)); + printf(" CMDLINE: '%s'\n", buf); + + procfs_process_get_cmdname(pc, buf, sizeof(buf)); + printf(" COMM: '%s'\n", buf); + + ul_unref_path(pc); + return EXIT_SUCCESS; +} + +static int test_isprocfs(int argc, char *argv[]) +{ + const char *name = argc > 1 ? argv[1] : "/proc"; + int fd = open(name, O_RDONLY); + int is = 0; + + if (fd >= 0) { + is = fd_is_procfs(fd); + close(fd); + } else + err(EXIT_FAILURE, "cannot open %s", name); + + printf("%s: %s procfs\n", name, is ? "is" : "is NOT"); + return is ? EXIT_SUCCESS : EXIT_FAILURE; +} + +int main(int argc, char *argv[]) +{ + if (argc < 2) { + fprintf(stderr, "usage: %1$s --tasks <pid>\n" + " %1$s --fds <pid>\n" + " %1$s --is-procfs [<dir>]\n" + " %1$s --processes [---name <name>] [--uid <uid>]\n" + " %1$s --one <pid>\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], "--fds") == 0) + return test_fds(argc - 1, argv + 1); + if (strcmp(argv[1], "--processes") == 0) + return test_processes(argc - 1, argv + 1); + if (strcmp(argv[1], "--is-procfs") == 0) + return test_isprocfs(argc - 1, argv + 1); + if (strcmp(argv[1], "--one") == 0) + return test_one_process(argc - 1, argv + 1); + + return EXIT_FAILURE; +} +#endif /* TEST_PROGRAM_PROCUTILS */ diff --git a/lib/pty-session.c b/lib/pty-session.c new file mode 100644 index 0000000..6f038e1 --- /dev/null +++ b/lib/pty-session.c @@ -0,0 +1,749 @@ +/* + * This is pseudo-terminal container for child process where parent creates a + * proxy between the current std{in,out,etrr} and the child's pty. Advantages: + * + * - child has no access to parent's terminal (e.g. su --pty) + * - parent can log all traffic between user and child's terminal (e.g. script(1)) + * - it's possible to start commands on terminal although parent has no terminal + * + * This code is in the public domain; do with it what you wish. + * + * Written by Karel Zak <kzak@redhat.com> in Jul 2019 + */ +#include <stdio.h> +#include <stdlib.h> +#include <pty.h> +#include <poll.h> +#include <sys/signalfd.h> +#include <paths.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <inttypes.h> + +#include "c.h" +#include "all-io.h" +#include "ttyutils.h" +#include "pty-session.h" +#include "monotonic.h" +#include "debug.h" + +static UL_DEBUG_DEFINE_MASK(ulpty); +UL_DEBUG_DEFINE_MASKNAMES(ulpty) = UL_DEBUG_EMPTY_MASKNAMES; + +#define ULPTY_DEBUG_INIT (1 << 1) +#define ULPTY_DEBUG_SETUP (1 << 2) +#define ULPTY_DEBUG_SIG (1 << 3) +#define ULPTY_DEBUG_IO (1 << 4) +#define ULPTY_DEBUG_DONE (1 << 5) +#define ULPTY_DEBUG_ALL 0xFFFF + +#define DBG(m, x) __UL_DBG(ulpty, ULPTY_DEBUG_, m, x) +#define ON_DBG(m, x) __UL_DBG_CALL(ulpty, ULPTY_DEBUG_, m, x) + +#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulpty) +#include "debugobj.h" + +void ul_pty_init_debug(int mask) +{ + if (ulpty_debug_mask) + return; + __UL_INIT_DEBUG_FROM_ENV(ulpty, ULPTY_DEBUG_, mask, ULPTY_DEBUG); +} + +struct ul_pty *ul_new_pty(int is_stdin_tty) +{ + struct ul_pty *pty = calloc(1, sizeof(*pty)); + + if (!pty) + return NULL; + + DBG(SETUP, ul_debugobj(pty, "alloc handler")); + pty->isterm = is_stdin_tty; + pty->master = -1; + pty->slave = -1; + pty->sigfd = -1; + pty->child = (pid_t) -1; + + return pty; +} + +void ul_free_pty(struct ul_pty *pty) +{ + free(pty); +} + +void ul_pty_slave_echo(struct ul_pty *pty, int enable) +{ + assert(pty); + pty->slave_echo = enable ? 1 : 0; +} + +int ul_pty_get_delivered_signal(struct ul_pty *pty) +{ + assert(pty); + return pty->delivered_signal; +} + +struct ul_pty_callbacks *ul_pty_get_callbacks(struct ul_pty *pty) +{ + assert(pty); + return &pty->callbacks; +} + +void ul_pty_set_callback_data(struct ul_pty *pty, void *data) +{ + assert(pty); + pty->callback_data = data; +} + +void ul_pty_set_child(struct ul_pty *pty, pid_t child) +{ + assert(pty); + pty->child = child; +} + +int ul_pty_get_childfd(struct ul_pty *pty) +{ + assert(pty); + return pty->master; +} + +pid_t ul_pty_get_child(struct ul_pty *pty) +{ + assert(pty); + return pty->child; +} + +/* it's active when signals are redirected to sigfd */ +int ul_pty_is_running(struct ul_pty *pty) +{ + assert(pty); + return pty->sigfd >= 0; +} + +void ul_pty_set_mainloop_time(struct ul_pty *pty, struct timeval *tv) +{ + assert(pty); + if (!tv) { + DBG(IO, ul_debugobj(pty, "mainloop time: clear")); + timerclear(&pty->next_callback_time); + } else { + pty->next_callback_time.tv_sec = tv->tv_sec; + pty->next_callback_time.tv_usec = tv->tv_usec; + DBG(IO, ul_debugobj(pty, "mainloop time: %"PRId64".%06"PRId64, + (int64_t) tv->tv_sec, (int64_t) tv->tv_usec)); + } +} + +static void pty_signals_cleanup(struct ul_pty *pty) +{ + if (pty->sigfd != -1) + close(pty->sigfd); + pty->sigfd = -1; + + /* restore original setting */ + sigprocmask(SIG_SETMASK, &pty->orgsig, NULL); +} + +/* call me before fork() */ +int ul_pty_setup(struct ul_pty *pty) +{ + struct termios attrs; + sigset_t ourset; + int rc = 0; + + assert(pty->sigfd == -1); + + /* save the current signals setting */ + sigprocmask(0, NULL, &pty->orgsig); + + if (pty->isterm) { + DBG(SETUP, ul_debugobj(pty, "create for terminal")); + + /* original setting of the current terminal */ + if (tcgetattr(STDIN_FILENO, &pty->stdin_attrs) != 0) { + rc = -errno; + goto done; + } + + attrs = pty->stdin_attrs; + if (pty->slave_echo) + attrs.c_lflag |= ECHO; + else + attrs.c_lflag &= ~ECHO; + + ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&pty->win); + /* create master+slave */ + rc = openpty(&pty->master, &pty->slave, NULL, &attrs, &pty->win); + if (rc) + goto done; + + /* set the current terminal to raw mode; pty_cleanup() reverses this change on exit */ + cfmakeraw(&attrs); + tcsetattr(STDIN_FILENO, TCSANOW, &attrs); + } else { + DBG(SETUP, ul_debugobj(pty, "create for non-terminal")); + + rc = openpty(&pty->master, &pty->slave, NULL, NULL, NULL); + if (rc) + goto done; + + tcgetattr(pty->slave, &attrs); + + if (pty->slave_echo) + attrs.c_lflag |= ECHO; + else + attrs.c_lflag &= ~ECHO; + + tcsetattr(pty->slave, TCSANOW, &attrs); + } + + sigfillset(&ourset); + if (sigprocmask(SIG_BLOCK, &ourset, NULL)) { + rc = -errno; + goto done; + } + + sigemptyset(&ourset); + sigaddset(&ourset, SIGCHLD); + sigaddset(&ourset, SIGWINCH); + sigaddset(&ourset, SIGALRM); + sigaddset(&ourset, SIGTERM); + sigaddset(&ourset, SIGINT); + sigaddset(&ourset, SIGQUIT); + + if (pty->callbacks.flush_logs) + sigaddset(&ourset, SIGUSR1); + + if ((pty->sigfd = signalfd(-1, &ourset, SFD_CLOEXEC)) < 0) + rc = -errno; +done: + if (rc) + ul_pty_cleanup(pty); + + DBG(SETUP, ul_debugobj(pty, "pty setup done [master=%d, slave=%d, rc=%d]", + pty->master, pty->slave, rc)); + return rc; +} + +/* cleanup in parent process */ +void ul_pty_cleanup(struct ul_pty *pty) +{ + struct termios rtt; + + pty_signals_cleanup(pty); + + if (pty->master == -1 || !pty->isterm) + return; + + DBG(DONE, ul_debugobj(pty, "cleanup")); + rtt = pty->stdin_attrs; + tcsetattr(STDIN_FILENO, TCSADRAIN, &rtt); +} + +int ul_pty_chownmod_slave(struct ul_pty *pty, uid_t uid, gid_t gid, mode_t mode) +{ + if (fchown(pty->slave, uid, gid)) + return -errno; + if (fchmod(pty->slave, mode)) + return -errno; + return 0; +} + +/* call me in child process */ +void ul_pty_init_slave(struct ul_pty *pty) +{ + DBG(SETUP, ul_debugobj(pty, "initialize slave")); + + setsid(); + + ioctl(pty->slave, TIOCSCTTY, 1); + close(pty->master); + + dup2(pty->slave, STDIN_FILENO); + dup2(pty->slave, STDOUT_FILENO); + dup2(pty->slave, STDERR_FILENO); + + close(pty->slave); + + if (pty->sigfd >= 0) + close(pty->sigfd); + + pty->slave = -1; + pty->master = -1; + pty->sigfd = -1; + + sigprocmask(SIG_SETMASK, &pty->orgsig, NULL); + + DBG(SETUP, ul_debugobj(pty, "... initialize slave done")); +} + +static int write_output(char *obuf, ssize_t bytes) +{ + DBG(IO, ul_debug(" writing output")); + + if (write_all(STDOUT_FILENO, obuf, bytes)) { + DBG(IO, ul_debug(" writing output *failed*")); + return -errno; + } + + return 0; +} + +static int write_to_child(struct ul_pty *pty, char *buf, size_t bufsz) +{ + return write_all(pty->master, buf, bufsz); +} + +/* + * The pty is usually faster than shell, so it's a good idea to wait until + * the previous message has been already read by shell from slave before we + * write to master. This is necessary especially for EOF situation when we can + * send EOF to master before shell is fully initialized, to workaround this + * problem we wait until slave is empty. For example: + * + * echo "date" | su --pty + * + * Unfortunately, the child (usually shell) can ignore stdin at all, so we + * don't wait forever to avoid dead locks... + * + * Note that su --pty is primarily designed for interactive sessions as it + * maintains master+slave tty stuff within the session. Use pipe to write to + * pty and assume non-interactive (tee-like) behavior is NOT well supported. + */ +void ul_pty_write_eof_to_child(struct ul_pty *pty) +{ + unsigned int tries = 0; + struct pollfd fds[] = { + { .fd = pty->slave, .events = POLLIN } + }; + char c = DEF_EOF; + + DBG(IO, ul_debugobj(pty, " waiting for empty slave")); + while (poll(fds, 1, 10) == 1 && tries < 8) { + DBG(IO, ul_debugobj(pty, " slave is not empty")); + xusleep(250000); + tries++; + } + if (tries < 8) + DBG(IO, ul_debugobj(pty, " slave is empty now")); + + DBG(IO, ul_debugobj(pty, " sending EOF to master")); + write_to_child(pty, &c, sizeof(char)); +} + +static int mainloop_callback(struct ul_pty *pty) +{ + int rc; + + if (!pty->callbacks.mainloop) + return 0; + + DBG(IO, ul_debugobj(pty, "calling mainloop callback")); + rc = pty->callbacks.mainloop(pty->callback_data); + + DBG(IO, ul_debugobj(pty, " callback done [rc=%d]", rc)); + return rc; +} + +static int handle_io(struct ul_pty *pty, int fd, int *eof) +{ + char buf[BUFSIZ]; + ssize_t bytes; + int rc = 0; + sigset_t set; + + DBG(IO, ul_debugobj(pty, " handle I/O on fd=%d", fd)); + *eof = 0; + + sigemptyset(&set); + sigaddset(&set, SIGTTIN); + sigprocmask(SIG_UNBLOCK, &set, NULL); + /* read from active FD */ + bytes = read(fd, buf, sizeof(buf)); + sigprocmask(SIG_BLOCK, &set, NULL); + if (bytes < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + return -errno; + } + + if (bytes == 0) { + *eof = 1; + return 0; + } + + /* from stdin (user) to command */ + if (fd == STDIN_FILENO) { + DBG(IO, ul_debugobj(pty, " stdin --> master %zd bytes", bytes)); + + if (write_to_child(pty, buf, bytes)) + return -errno; + + /* without sync write_output() will write both input & + * shell output that looks like double echoing */ + fdatasync(pty->master); + + /* from command (master) to stdout */ + } else if (fd == pty->master) { + DBG(IO, ul_debugobj(pty, " master --> stdout %zd bytes", bytes)); + write_output(buf, bytes); + } + + if (pty->callbacks.log_stream_activity) + rc = pty->callbacks.log_stream_activity( + pty->callback_data, fd, buf, bytes); + + return rc; +} + +void ul_pty_wait_for_child(struct ul_pty *pty) +{ + int status; + pid_t pid; + int options = 0; + + if (pty->child == (pid_t) -1) + return; + + DBG(SIG, ul_debug("waiting for child [child=%d]", (int) pty->child)); + + if (ul_pty_is_running(pty)) { + /* wait for specific child */ + options = WNOHANG; + for (;;) { + pid = waitpid(pty->child, &status, options); + DBG(SIG, ul_debug(" waitpid done [rc=%d]", (int) pid)); + if (pid != (pid_t) - 1) { + if (pty->callbacks.child_die) + pty->callbacks.child_die( + pty->callback_data, + pty->child, status); + ul_pty_set_child(pty, (pid_t) -1); + } else + break; + } + } else { + /* final wait */ + while ((pid = waitpid(-1, &status, options)) > 0) { + DBG(SIG, ul_debug(" waitpid done [rc=%d]", (int) pid)); + if (pid == pty->child) { + if (pty->callbacks.child_die) + pty->callbacks.child_die( + pty->callback_data, + pty->child, status); + ul_pty_set_child(pty, (pid_t) -1); + } + } + } +} + +static int handle_signal(struct ul_pty *pty, int fd) +{ + struct signalfd_siginfo info; + ssize_t bytes; + int rc = 0; + + DBG(SIG, ul_debugobj(pty, " handle signal on fd=%d", fd)); + + bytes = read(fd, &info, sizeof(info)); + if (bytes != sizeof(info)) { + if (bytes < 0 && (errno == EAGAIN || errno == EINTR)) + return 0; + return -errno; + } + + switch (info.ssi_signo) { + case SIGCHLD: + DBG(SIG, ul_debugobj(pty, " get signal SIGCHLD")); + + if (info.ssi_code == CLD_EXITED + || info.ssi_code == CLD_KILLED + || info.ssi_code == CLD_DUMPED) { + + if (pty->callbacks.child_wait) + pty->callbacks.child_wait(pty->callback_data, + pty->child); + else + ul_pty_wait_for_child(pty); + + } else if (info.ssi_status == SIGSTOP && pty->child > 0) { + pty->callbacks.child_sigstop(pty->callback_data, + pty->child); + } + + if (pty->child <= 0) { + DBG(SIG, ul_debugobj(pty, " no child, setting leaving timeout")); + pty->poll_timeout = 10; + timerclear(&pty->next_callback_time); + } + return 0; + case SIGWINCH: + DBG(SIG, ul_debugobj(pty, " get signal SIGWINCH")); + if (pty->isterm) { + ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&pty->win); + ioctl(pty->slave, TIOCSWINSZ, (char *)&pty->win); + + if (pty->callbacks.log_signal) + rc = pty->callbacks.log_signal(pty->callback_data, + &info, (void *) &pty->win); + } + break; + case SIGTERM: + /* fallthrough */ + case SIGINT: + /* fallthrough */ + case SIGQUIT: + DBG(SIG, ul_debugobj(pty, " get signal SIG{TERM,INT,QUIT}")); + pty->delivered_signal = info.ssi_signo; + /* Child termination is going to generate SIGCHLD (see above) */ + if (pty->child > 0) + kill(pty->child, SIGTERM); + + if (pty->callbacks.log_signal) + rc = pty->callbacks.log_signal(pty->callback_data, + &info, (void *) &pty->win); + break; + case SIGUSR1: + DBG(SIG, ul_debugobj(pty, " get signal SIGUSR1")); + if (pty->callbacks.flush_logs) + rc = pty->callbacks.flush_logs(pty->callback_data); + break; + default: + abort(); + } + + return rc; +} + +/* loop in parent */ +int ul_pty_proxy_master(struct ul_pty *pty) +{ + int rc = 0, ret, eof = 0; + enum { + POLLFD_SIGNAL = 0, + POLLFD_MASTER, + POLLFD_STDIN + + }; + struct pollfd pfd[] = { + [POLLFD_SIGNAL] = { .fd = -1, .events = POLLIN | POLLERR | POLLHUP }, + [POLLFD_MASTER] = { .fd = pty->master, .events = POLLIN | POLLERR | POLLHUP }, + [POLLFD_STDIN] = { .fd = STDIN_FILENO, .events = POLLIN | POLLERR | POLLHUP } + }; + + /* We use signalfd, and standard signals by handlers are completely blocked */ + assert(pty->sigfd >= 0); + + pfd[POLLFD_SIGNAL].fd = pty->sigfd; + pty->poll_timeout = -1; + + while (!pty->delivered_signal) { + size_t i; + int errsv, timeout; + + DBG(IO, ul_debugobj(pty, "--poll() loop--")); + + /* note, callback usually updates @next_callback_time */ + if (timerisset(&pty->next_callback_time)) { + struct timeval now; + + DBG(IO, ul_debugobj(pty, " callback requested")); + gettime_monotonic(&now); + if (timercmp(&now, &pty->next_callback_time, >)) { + rc = mainloop_callback(pty); + if (rc) + break; + } + } + + /* set timeout */ + if (timerisset(&pty->next_callback_time)) { + struct timeval now, rest; + + gettime_monotonic(&now); + timersub(&pty->next_callback_time, &now, &rest); + timeout = (rest.tv_sec * 1000) + (rest.tv_usec / 1000); + } else + timeout = pty->poll_timeout; + + /* wait for input, signal or timeout */ + DBG(IO, ul_debugobj(pty, "calling poll() [timeout=%dms]", timeout)); + ret = poll(pfd, ARRAY_SIZE(pfd), timeout); + + errsv = errno; + DBG(IO, ul_debugobj(pty, "poll() rc=%d", ret)); + + /* error */ + if (ret < 0) { + if (errsv == EAGAIN) + continue; + rc = -errno; + break; + } + + /* timeout */ + if (ret == 0) { + if (timerisset(&pty->next_callback_time)) { + rc = mainloop_callback(pty); + if (rc == 0) + continue; + } else { + rc = 0; + } + + DBG(IO, ul_debugobj(pty, "leaving poll() loop [timeout=%d, rc=%d]", timeout, rc)); + break; + } + /* event */ + for (i = 0; i < ARRAY_SIZE(pfd); i++) { + if (pfd[i].revents == 0) + continue; + + DBG(IO, ul_debugobj(pty, " active pfd[%s].fd=%d %s %s %s %s", + i == POLLFD_STDIN ? "stdin" : + i == POLLFD_MASTER ? "master" : + i == POLLFD_SIGNAL ? "signal" : "???", + pfd[i].fd, + pfd[i].revents & POLLIN ? "POLLIN" : "", + pfd[i].revents & POLLHUP ? "POLLHUP" : "", + pfd[i].revents & POLLERR ? "POLLERR" : "", + pfd[i].revents & POLLNVAL ? "POLLNVAL" : "")); + + if (i == POLLFD_SIGNAL) + rc = handle_signal(pty, pfd[i].fd); + else if (pfd[i].revents & POLLIN) + rc = handle_io(pty, pfd[i].fd, &eof); /* data */ + + if (rc) { + ul_pty_write_eof_to_child(pty); + break; + } + + if (i == POLLFD_SIGNAL) + continue; + + /* EOF maybe detected in two ways; they are as follows: + * A) poll() return POLLHUP event after close() + * B) read() returns 0 (no data) + * + * POLLNVAL means that fd is closed. + */ + if ((pfd[i].revents & POLLHUP) || (pfd[i].revents & POLLNVAL) || eof) { + DBG(IO, ul_debugobj(pty, " ignore FD")); + pfd[i].fd = -1; + if (i == POLLFD_STDIN) { + ul_pty_write_eof_to_child(pty); + DBG(IO, ul_debugobj(pty, " ignore STDIN")); + } + } + } + if (rc) + break; + } + + if (rc && pty->child && pty->child != (pid_t) -1 && !pty->delivered_signal) { + kill(pty->child, SIGTERM); + sleep(2); + kill(pty->child, SIGKILL); + } + + pty_signals_cleanup(pty); + + DBG(IO, ul_debug("poll() done [signal=%d, rc=%d]", pty->delivered_signal, rc)); + return rc; +} + +#ifdef TEST_PROGRAM_PTY +/* + * $ make test_pty + * $ ./test_pty + * + * ... and see for example tty(1) or "ps afu" + */ +static void child_sigstop(void *data __attribute__((__unused__)), pid_t child) +{ + kill(getpid(), SIGSTOP); + kill(child, SIGCONT); +} + +int main(int argc, char *argv[]) +{ + struct ul_pty_callbacks *cb; + const char *shell, *command = NULL, *shname = NULL; + int caught_signal = 0; + pid_t child; + struct ul_pty *pty; + + shell = getenv("SHELL"); + if (shell == NULL) + shell = _PATH_BSHELL; + if (argc == 2) + command = argv[1]; + + ul_pty_init_debug(0); + + pty = ul_new_pty(isatty(STDIN_FILENO)); + if (!pty) + err(EXIT_FAILURE, "failed to allocate PTY handler"); + + cb = ul_pty_get_callbacks(pty); + cb->child_sigstop = child_sigstop; + + if (ul_pty_setup(pty)) + err(EXIT_FAILURE, "failed to create pseudo-terminal"); + + fflush(stdout); /* ??? */ + + switch ((int) (child = fork())) { + case -1: /* error */ + ul_pty_cleanup(pty); + err(EXIT_FAILURE, "cannot create child process"); + break; + + case 0: /* child */ + ul_pty_init_slave(pty); + + signal(SIGTERM, SIG_DFL); /* because /etc/csh.login */ + + shname = strrchr(shell, '/'); + shname = shname ? shname + 1 : shell; + + if (command) + execl(shell, shname, "-c", command, (char *)NULL); + else + execl(shell, shname, "-i", (char *)NULL); + err(EXIT_FAILURE, "failed to execute %s", shell); + break; + + default: + break; + } + + /* parent */ + ul_pty_set_child(pty, child); + + /* this is the main loop */ + ul_pty_proxy_master(pty); + + /* all done; cleanup and kill */ + caught_signal = ul_pty_get_delivered_signal(pty); + + if (!caught_signal && ul_pty_get_child(pty) != (pid_t)-1) + ul_pty_wait_for_child(pty); /* final wait */ + + if (caught_signal && ul_pty_get_child(pty) != (pid_t)-1) { + fprintf(stderr, "\nSession terminated, killing shell..."); + kill(child, SIGTERM); + sleep(2); + kill(child, SIGKILL); + fprintf(stderr, " ...killed.\n"); + } + + ul_pty_cleanup(pty); + ul_free_pty(pty); + return EXIT_SUCCESS; +} + +#endif /* TEST_PROGRAM */ + diff --git a/lib/pwdutils.c b/lib/pwdutils.c new file mode 100644 index 0000000..1c1f13e --- /dev/null +++ b/lib/pwdutils.c @@ -0,0 +1,158 @@ +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + */ +#include <stdlib.h> +#include <assert.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; + + assert(pwdbuf); + assert(username); + + *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; +} + +/* Returns allocated group and allocated grpbuf to store group strings + * fields. In case of error returns NULL and set errno, for unknown group set + * errno to EINVAL + */ +struct group *xgetgrnam(const char *groupname, char **grpbuf) +{ + struct group *grp = NULL, *res = NULL; + int rc; + + assert(grpbuf); + assert(groupname); + + *grpbuf = xmalloc(UL_GETPW_BUFSIZ); + grp = xcalloc(1, sizeof(struct group)); + + errno = 0; + rc = getgrnam_r(groupname, grp, *grpbuf, UL_GETPW_BUFSIZ, &res); + if (rc != 0) { + errno = rc; + goto failed; + } + if (!res) { + errno = EINVAL; + goto failed; + } + return grp; +failed: + free(grp); + free(*grpbuf); + return NULL; +} + +struct passwd *xgetpwuid(uid_t uid, char **pwdbuf) +{ + struct passwd *pwd = NULL, *res = NULL; + int rc; + + assert(pwdbuf); + + *pwdbuf = xmalloc(UL_GETPW_BUFSIZ); + pwd = xcalloc(1, sizeof(struct passwd)); + + errno = 0; + rc = getpwuid_r(uid, 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; + + /* 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 + * + * The same implementation is useful for other systems, since getlogin(3) + * shouldn't be used as actual identification. + */ + 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..2ffe9b4 --- /dev/null +++ b/lib/randutils.c @@ -0,0 +1,245 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause + * + * General purpose random utilities. Based on libuuid code. + * + * This code is free software; you can redistribute it and/or modify it under + * the terms of the Modified BSD License. The complete text of the license is + * available in the Documentation/licenses/COPYING.BSD-3-Clause file. + */ +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> +#ifdef HAVE_SYS_SYSCALL_H +#include <sys/syscall.h> +#endif +#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 syscall */ +#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; + unsigned int n_pid, n_uid; + + gettimeofday(&tv, NULL); + n_pid = getpid(); + n_uid = getuid(); + srand((n_pid << 16) ^ n_uid ^ 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 */ + +/* + * Write @nbytes random bytes into @buf. + * + * Returns 0 for good quality of random bytes or 1 for weak quality. + */ +int ul_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; + errno = 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 entropy, 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 n != 0; +} + + +/* + * 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++) { + ul_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"); + + ul_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/selinux-utils.c b/lib/selinux-utils.c new file mode 100644 index 0000000..c0df891 --- /dev/null +++ b/lib/selinux-utils.c @@ -0,0 +1,85 @@ +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * Written by Karel Zak <kzak@redhat.com> [January 2021] + */ +#include <selinux/context.h> +#include <selinux/selinux.h> +#include <selinux/label.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <errno.h> + +#include "selinux-utils.h" + +/* set the SELinux security context used for _creating_ a new file system object + * + * returns 0 on success, + * or <0 on error + */ +int ul_setfscreatecon_from_file(char *orig_file) +{ + if (is_selinux_enabled() > 0) { + char *scontext = NULL; + + if (getfilecon(orig_file, &scontext) < 0) + return -1; + if (setfscreatecon(scontext) < 0) { + freecon(scontext); + return -1; + } + freecon(scontext); + } + return 0; +} + +/* returns 1 if user has access to @class and @perm ("passwd", "chfn") + * or 0 on error, + * or 0 if has no access -- in this case sets @user_cxt to user-context + */ +int ul_selinux_has_access(const char *classstr, const char *perm, char **user_cxt) +{ + char *user; + int rc; + + if (user_cxt) + *user_cxt = NULL; + + if (getprevcon(&user) != 0) + return 0; + + rc = selinux_check_access(user, user, classstr, perm, NULL); + if (rc != 0 && user_cxt) + *user_cxt = user; + else + freecon(user); + + return rc == 0 ? 1 : 0; +} + +/* Gets the default context for @path and @st_mode. + * + * returns 0 on success, + * or <0 on error + */ +int ul_selinux_get_default_context(const char *path, int st_mode, char **cxt) +{ + struct selabel_handle *hnd; + struct selinux_opt options[SELABEL_NOPT] = {}; + int rc = 0; + + *cxt = NULL; + + hnd = selabel_open(SELABEL_CTX_FILE, options, SELABEL_NOPT); + if (!hnd) + return -errno; + + if (selabel_lookup(hnd, cxt, path, st_mode) != 0) + rc = -errno + ; + selabel_close(hnd); + + return rc; +} 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..064776a --- /dev/null +++ b/lib/signames.c @@ -0,0 +1,173 @@ +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + + * Written by: + * Sami Kerola <kerolasa@iki.fi> + * Karel Zak <kzak@redhat.com> + * 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..104eb38 --- /dev/null +++ b/lib/strutils.c @@ -0,0 +1,1313 @@ +/* + * Copyright (C) 2010 Karel Zak <kzak@redhat.com> + * Copyright (C) 2010 Davidlohr Bueso <dave@gnu.org> + * + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + */ +#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" +#include "pathnames.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++; + fstr = p; + if (isdigit(*fstr)) { + 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; + } + } else + end = (char *) p; + + 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 i; + uintmax_t frac_div = 10, frac_poz = 1, frac_base = 1; + + /* mega, giga, ... */ + do_scale_by_power(&frac_base, base, pwr); + + /* maximal divisor for last digit (e.g. for 0.05 is + * frac_div=100, for 0.054 is frac_div=1000, etc.) + * + * Reduce frac if too large. + */ + while (frac_div < frac) { + if (frac_div <= UINTMAX_MAX/10) + frac_div *= 10; + else + frac /= 10; + } + + /* 'frac' is without zeros (5 means 0.5 as well as 0.05) */ + for (i = 0; i < frac_zeros; i++) { + if (frac_div <= UINTMAX_MAX/10) + frac_div *= 10; + else + frac /= 10; + } + + /* + * Go backwardly from last digit and add to result what the + * digit represents in the frac_base. For example 0.25G + * + * 5 means 1GiB / (100/5) + * 2 means 1GiB / (10/2) + */ + do { + unsigned int seg = frac % 10; /* last digit of the frac */ + uintmax_t seg_div = frac_div / frac_poz; /* what represents the segment 1000, 100, .. */ + + frac /= 10; /* remove last digit from frac */ + frac_poz *= 10; + + if (seg && seg_div / seg) + x += frac_base / (seg_div / seg); + } while (frac); + } +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; + } + + 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 + +/* + * convert strings to numbers; returns <0 on error, and 0 on success + */ +int ul_strtos64(const char *str, int64_t *num, int base) +{ + char *end = NULL; + + errno = 0; + if (str == NULL || *str == '\0') + return -EINVAL; + *num = (int64_t) strtoimax(str, &end, base); + + if (errno || str == end || (end && *end)) + return -EINVAL; + return 0; +} + +int ul_strtou64(const char *str, uint64_t *num, int base) +{ + char *end = NULL; + int64_t tmp; + + errno = 0; + if (str == NULL || *str == '\0') + return -EINVAL; + + /* we need to ignore negative numbers, note that for invalid negative + * number strtoimax() returns negative number too, so we do not + * need to check errno here */ + tmp = (int64_t) strtoimax(str, &end, base); + if (tmp < 0) + errno = ERANGE; + else { + errno = 0; + *num = strtoumax(str, &end, base); + } + + if (errno || str == end || (end && *end)) + return -EINVAL; + return 0; +} + +int ul_strtos32(const char *str, int32_t *num, int base) +{ + int64_t tmp; + int rc; + + rc = ul_strtos64(str, &tmp, base); + if (rc == 0 && (tmp < INT32_MIN || tmp > INT32_MAX)) + rc = -(errno = ERANGE); + if (rc == 0) + *num = (int32_t) tmp; + return rc; +} + +int ul_strtou32(const char *str, uint32_t *num, int base) +{ + uint64_t tmp; + int rc; + + rc = ul_strtou64(str, &tmp, base); + if (rc == 0 && tmp > UINT32_MAX) + rc = -(errno = ERANGE); + if (rc == 0) + *num = (uint32_t) tmp; + return rc; +} + +/* + * Convert strings to numbers in defined range and print message on error. + * + * These functions are used when we read input from users (getopt() etc.). It's + * better to consolidate the code and keep it all based on 64-bit numbers then + * implement it for 32 and 16-bit numbers too. + */ +int64_t str2num_or_err(const char *str, int base, const char *errmesg, + int64_t low, int64_t up) +{ + int64_t num = 0; + int rc; + + rc = ul_strtos64(str, &num, base); + if (rc == 0 && ((low && num < low) || (up && num > up))) + rc = -(errno = ERANGE); + + if (rc) { + if (errno == ERANGE) + err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); + errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); + } + return num; +} + +uint64_t str2unum_or_err(const char *str, int base, const char *errmesg, uint64_t up) +{ + uint64_t num = 0; + int rc; + + rc = ul_strtou64(str, &num, base); + if (rc == 0 && (up && num > up)) + rc = -(errno = ERANGE); + + if (rc) { + if (errno == ERANGE) + err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); + errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); + } + return num; +} + +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 double strtold_or_err(const char *str, const char *errmesg) +{ + double num; + char *end = NULL; + + errno = 0; + if (str == NULL || *str == '\0') + goto err; + num = strtold(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) +{ + long double user_input; + + user_input = strtold_or_err(str, errmesg); + tv->tv_sec = (time_t) user_input; + tv->tv_usec = (suseconds_t)((user_input - tv->tv_sec) * 1000000); +} + +time_t strtotime_or_err(const char *str, const char *errmesg) +{ + int64_t user_input; + + user_input = strtos64_or_err(str, errmesg); + return (time_t) user_input; +} + +/* + * 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); + */ + + /* round */ + if (frac) { + /* get 3 digits after decimal point */ + if (frac >= UINT64_MAX / 1000) + frac = ((frac / 1024) * 1000) / (1ULL << (exp - 10)) ; + else + frac = (frac * 1000) / (1ULL << (exp)) ; + + if (options & SIZE_DECIMAL_2DIGITS) { + /* round 4/5 and keep 2 digits after decimal point */ + frac = (frac + 5) / 10 ; + } else { + /* round 4/5 and keep 1 digit after decimal point */ + frac = ((frac + 50) / 100) * 10 ; + } + + /* rounding could have overflowed */ + if (frac == 100) { + dec++; + frac = 0; + } + } + + if (frac) { + struct lconv const *l = localeconv(); + char *dp = l ? l->decimal_point : NULL; + int len; + + if (!dp || !*dp) + dp = "."; + + len = snprintf(buf, sizeof(buf), "%d%s%02" PRIu64, dec, dp, frac); + if (len > 0 && (size_t) len < sizeof(buf)) { + /* remove potential extraneous zero */ + if (buf[len - 1] == '0') + buf[len--] = '\0'; + /* append suffix */ + xstrncpy(buf+len, suffix, sizeof(buf) - len); + } else + *buf = '\0'; /* snprintf error */ + } 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_seg || !b_seg) + break; + if (a_sz != b_sz || strncmp(a_seg, b_seg, a_sz) != 0) + break; + + a = a_seg + a_sz; + b = b_seg + b_sz; + }; + + return 0; +} + +/* concatenate two strings to a new string, the size of the second string is limited by @b */ +char *strnconcat(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; +} + +/* concatenate two strings to a new string */ +char *strconcat(const char *s, const char *suffix) +{ + return strnconcat(s, suffix, suffix ? strlen(suffix) : 0); +} + +/* concatenate @s and string defined by @format to a new string */ +char *strfconcat(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 = strnconcat(s, val, sz); + free(val); + return res; +} + +int strappend(char **a, const char *b) +{ + size_t al, bl; + char *tmp; + + if (!a) + return -EINVAL; + if (!b || !*b) + return 0; + if (!*a) { + *a = strdup(b); + return !*a ? -ENOMEM : 0; + } + + al = strlen(*a); + bl = strlen(b); + + tmp = realloc(*a, al + bl + 1); + if (!tmp) + return -ENOMEM; + *a = tmp; + memcpy((*a) + al, b, bl + 1); + return 0; +} + +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; +} + +/* + * Like strchr() but ignores @c if escaped by '\', '\\' is interpreted like '\'. + * + * For example for @c='X': + * + * "abcdXefgXh" --> "XefgXh" + * "abcd\XefgXh" --> "Xh" + * "abcd\\XefgXh" --> "XefgXh" + * "abcd\\\XefgXh" --> "Xh" + * "abcd\Xefg\Xh" --> (null) + * + * "abcd\\XefgXh" --> "\XefgXh" for @c='\\' + */ +char *ul_strchr_escaped(const char *s, int c) +{ + char *p; + int esc = 0; + + for (p = (char *) s; p && *p; p++) { + if (!esc && *p == '\\') { + esc = 1; + continue; + } + if (*p == c && (!esc || c == '\\')) + return p; + esc = 0; + } + + return NULL; +} + +/* 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); +} + + +/* compare two strings, but ignoring non-alnum and case of the characters, for example + * "Hello (123)!" is the same as "hello123". + */ +int ul_stralnumcmp(const char *p1, const char *p2) +{ + const unsigned char *s1 = (const unsigned char *) p1; + const unsigned char *s2 = (const unsigned char *) p2; + unsigned char c1, c2; + + do { + do { + c1 = (unsigned char) *s1++; + } while (c1 != '\0' && !isalnum((unsigned int) c1)); + + do { + c2 = (unsigned char) *s2++; + } while (c2 != '\0' && !isalnum((unsigned int) c2)); + + if (c1 != '\0') + c1 = tolower(c1); + if (c2 != '\0') + c2 = tolower(c2); + if (c1 == '\0') + return c1 - c2; + } while (c1 == c2); + + return c1 - c2; +} + +#ifdef TEST_PROGRAM_STRUTILS +struct testS { + char *name; + char *value; +}; + +static int test_strdup_to_member(int argc, char *argv[]) +{ + struct testS *xx; + + if (argc < 3) + return EXIT_FAILURE; + + xx = calloc(1, sizeof(*xx)); + if (!xx) + err(EXIT_FAILURE, "calloc() failed"); + + strdup_to_struct_member(xx, name, argv[1]); + strdup_to_struct_member(xx, value, argv[2]); + + if (strcmp(xx->name, argv[1]) != 0 && + strcmp(xx->value, argv[2]) != 0) + errx(EXIT_FAILURE, "strdup_to_struct_member() failed"); + + printf("1: '%s', 2: '%s'\n", xx->name, xx->value); + + free(xx->name); + free(xx->value); + free(xx); + return EXIT_SUCCESS; +} + +static int test_strutils_sizes(int argc, char *argv[]) +{ + uintmax_t size = 0; + char *hum1, *hum2, *hum3; + + if (argc < 2) + return EXIT_FAILURE; + + if (strtosize(argv[1], &size)) + errx(EXIT_FAILURE, "invalid size '%s' value", argv[1]); + + hum1 = size_to_human_string(SIZE_SUFFIX_1LETTER, size); + hum2 = size_to_human_string(SIZE_SUFFIX_3LETTER | + SIZE_SUFFIX_SPACE, size); + hum3 = size_to_human_string(SIZE_SUFFIX_3LETTER | + SIZE_SUFFIX_SPACE | + SIZE_DECIMAL_2DIGITS, size); + + printf("%25s : %20ju : %8s : %12s : %13s\n", argv[1], size, hum1, hum2, hum3); + free(hum1); + free(hum2); + free(hum3); + + 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; +} + +static int test_strutils_normalize(int argc, char *argv[]) +{ + unsigned char *src, *dst, *org; + size_t sz, len; + + if (argc < 2) + return EXIT_FAILURE; + + org = (unsigned char *) strdup(argv[1]); + src = (unsigned char *) strdup((char *) org); + len = strlen((char *) src); + dst = malloc(len + 1); + + if (!org || !src || !dst) + goto done; + + /* two buffers */ + sz = __normalize_whitespace(src, len, dst, len + 1); + printf("1: '%s' --> '%s' [sz=%zu]\n", src, dst, sz); + + /* one buffer */ + sz = normalize_whitespace(src); + printf("2: '%s' --> '%s' [sz=%zu]\n", org, src, sz); + +done: + free(src); + free(dst); + free(org); + + 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 if (argc == 4 && strcmp(argv[1], "--strdup-member") == 0) { + return test_strdup_to_member(argc - 1, argv + 1); + + } else if (argc == 4 && strcmp(argv[1], "--stralnumcmp") == 0) { + printf("%s\n", ul_stralnumcmp(argv[2], argv[3]) == 0 ? + "match" : "dismatch"); + return EXIT_SUCCESS; + } else if (argc == 3 && strcmp(argv[1], "--normalize") == 0) { + return test_strutils_normalize(argc - 1, argv + 1); + + + } else if (argc == 3 && strcmp(argv[1], "--strtos64") == 0) { + printf("'%s'-->%jd\n", argv[2], strtos64_or_err(argv[2], "strtos64 failed")); + return EXIT_SUCCESS; + } else if (argc == 3 && strcmp(argv[1], "--strtou64") == 0) { + printf("'%s'-->%ju\n", argv[2], strtou64_or_err(argv[2], "strtou64 failed")); + return EXIT_SUCCESS; + } else if (argc == 3 && strcmp(argv[1], "--strtos32") == 0) { + printf("'%s'-->%d\n", argv[2], strtos32_or_err(argv[2], "strtos32 failed")); + return EXIT_SUCCESS; + } else if (argc == 3 && strcmp(argv[1], "--strtou32") == 0) { + printf("'%s'-->%u\n", argv[2], strtou32_or_err(argv[2], "strtou32 failed")); + return EXIT_SUCCESS; + } else if (argc == 3 && strcmp(argv[1], "--strtos16") == 0) { + printf("'%s'-->%hd\n", argv[2], strtos16_or_err(argv[2], "strtos16 failed")); + return EXIT_SUCCESS; + } else if (argc == 3 && strcmp(argv[1], "--strtou16") == 0) { + printf("'%s'-->%hu\n", argv[2], strtou16_or_err(argv[2], "strtou16 failed")); + return EXIT_SUCCESS; + + } else if (argc == 4 && strcmp(argv[1], "--strchr-escaped") == 0) { + printf("\"%s\" --> \"%s\"\n", argv[2], ul_strchr_escaped(argv[2], *argv[3])); + return EXIT_SUCCESS; + + } else { + fprintf(stderr, "usage: %1$s --size <number>[suffix]\n" + " %1$s --cmp-paths <path> <path>\n" + " %1$s --strdup-member <str> <str>\n" + " %1$s --stralnumcmp <str> <str>\n" + " %1$s --normalize <str>\n" + " %1$s --strto{s,u}{16,32,64} <str>\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..58a4c97 --- /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 = strconcat(*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..594496f --- /dev/null +++ b/lib/swapprober.c @@ -0,0 +1,54 @@ +/* + * 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 "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)) != 0) + 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..84af4fe --- /dev/null +++ b/lib/sysfs.c @@ -0,0 +1,1172 @@ +/* + * 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" +#include "strutils.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_unref_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 && path && 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), NULL); + if (sz < 0) + return NULL; + + 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; +} + +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 + size_t len = 0; + + if (parent_name) { + const char *p = parent_name; + + /* /dev/sda --> "sda" */ + if (*parent_name == '/') { + p = strrchr(parent_name, '/'); + if (!p) + return 0; + p++; + } + + len = strlen(p); + if ((strlen(d->d_name) <= len) || (strncmp(p, d->d_name, len) != 0)) + len = 0; + } + + if (len > 0) { + /* partitions subdir name is + * "<parent>[:digit:]" or "<parent>p[:digit:]" + */ + return ((*(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; + + sz++; + 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) + xstrncpy(diskname, name, len); + + 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), NULL); + if (linklen < 0) + goto err; + + stripoff_last_component(linkpath); /* dirname */ + name = stripoff_last_component(linkpath); /* basename */ + if (!name) + goto err; + + sysfs_devname_sys_to_dev(name); + if (diskname && len) + xstrncpy(diskname, name, len); + + 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), "device"); + if (len < 0) + return len; + + 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; +} + +int sysfs_devname_is_hidden(const char *prefix, const char *name) +{ + char buf[PATH_MAX]; + int rc = 0, hidden = 0, len; + FILE *f; + + if (strncmp("/dev/", name, 5) == 0) + return 0; + + if (!prefix) + prefix = ""; + /* + * Create path to /sys/block/<name>/hidden + */ + len = snprintf(buf, sizeof(buf), + "%s" _PATH_SYS_BLOCK "/%s/hidden", + prefix, name); + + if (len < 0 || (size_t) len + 1 > sizeof(buf)) + return 0; + + f = fopen(buf, "r" UL_CLOEXECSTR); + if (!f) + return 0; + + rc = fscanf(f, "%d", &hidden); + fclose(f); + + return rc == 1 ? hidden : 0; +} + + +dev_t __sysfs_devname_to_devno(const char *prefix, const char *name, const char *parent) +{ + char buf[PATH_MAX]; + char *_name = NULL, *_parent = 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; /* unaccessible, or not node in /dev */ + } + + _name = strdup(name); + if (!_name) + goto done; + sysfs_devname_dev_to_sys(_name); + + if (parent) { + _parent = strdup(parent); + if (!_parent) + goto done; + } + + if (parent && strncmp("dm-", name, 3) != 0) { + /* + * Create path to /sys/block/<parent>/<name>/dev + */ + sysfs_devname_dev_to_sys(_parent); + len = snprintf(buf, sizeof(buf), + "%s" _PATH_SYS_BLOCK "/%s/%s/dev", + prefix, _parent, _name); + 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); + + /* + * Read from /sys/block/<parent>/<partition>/dev + */ + if (!dev && parent && startswith(name, parent)) { + len = snprintf(buf, sizeof(buf), + "%s" _PATH_SYS_BLOCK "/%s/%s/dev", + prefix, _parent, _name); + if (len < 0 || (size_t) len >= sizeof(buf)) + goto done; + dev = read_devno(buf); + } + + /* + * Read from /sys/block/<sysname>/device/dev + */ + if (!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); + free(_parent); + 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; +} + +int sysfs_devno_count_partitions(dev_t devno) +{ + struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL); + int n = 0; + + if (pc) { + char buf[PATH_MAX + 1]; + char *name = sysfs_blkdev_get_name(pc, buf, sizeof(buf)); + + n = sysfs_blkdev_count_partitions(pc, name); + ul_unref_path(pc); + } + return n; +} + +char *sysfs_chrdev_devno_to_devname(dev_t devno, char *buf, size_t bufsiz) +{ + char link[PATH_MAX]; + struct path_cxt *pc; + char *name; + ssize_t sz; + + pc = ul_new_path(_PATH_SYS_DEVCHAR "/%u:%u", major(devno), minor(devno)); + if (!pc) + return NULL; + + /* read /sys/dev/char/<maj:min> link */ + sz = ul_path_readlink(pc, link, sizeof(link), NULL); + ul_unref_path(pc); + + if (sz < 0) + return NULL; + + 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; + +} + + + +#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..7bea79c --- /dev/null +++ b/lib/terminal-colors.d.5 @@ -0,0 +1,404 @@ +'\" t +.\" Title: terminal-colors.d +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.15 +.\" Date: 2022-05-11 +.\" Manual: File formats +.\" Source: util-linux 2.38.1 +.\" Language: English +.\" +.TH "TERMINAL\-COLORS.D" "5" "2022-05-11" "util\-linux 2.38.1" "File formats" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +terminal-colors.d \- configure output colorization for various utilities +.SH "SYNOPSIS" +.sp +/etc/terminal\-colors.d/\fI[[name][@term].][type]\fP +.SH "DESCRIPTION" +.sp +Files in this directory determine the default behavior for utilities when coloring output. +.sp +The \fIname\fP is a utility name. The name is optional and when none is specified then the file is used for all unspecified utilities. +.sp +The \fIterm\fP is a terminal identifier (the \fBTERM\fP environment variable). The terminal identifier is optional and when none is specified then the file is used for all unspecified terminals. +.sp +The \fItype\fP is a file type. Supported file types are: +.sp +\fBdisable\fP +.RS 4 +Turns off output colorization for all compatible utilities. +.RE +.sp +\fBenable\fP +.RS 4 +Turns on output colorization; any matching \fBdisable\fP files are ignored. +.RE +.sp +\fBscheme\fP +.RS 4 +Specifies colors used for output. The file format may be specific to the utility, the default format is described below. +.RE +.sp +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"). +.sp +The user\-specific \fI$XDG_CONFIG_HOME/terminal\-colors.d\fP or \fI$HOME/.config/terminal\-colors.d\fP overrides the global setting. +.SH "DEFAULT SCHEME FILES FORMAT" +.sp +The following statement is recognized: +.RS 3 +.ll -.6i +.sp +\fBname color\-sequence\fP +.br +.RE +.ll +.sp +The \fBname\fP is a logical name of color sequence (for example "error"). The names are specific to the utilities. For more details always see the \fBCOLORS\fP section in the man page for the utility. +.sp +The \fBcolor\-sequence\fP is a color name, ASCII color sequences or escape sequences. +.SS "Color names" +.sp +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" +.sp +The color sequences are composed of sequences of numbers separated by semicolons. The most common codes are: +.RS 3 +.ll -.6i +.TS +allbox tab(:); +lt lt. +T{ +.sp +0 +T}:T{ +.sp +to restore default color +T} +T{ +.sp +1 +T}:T{ +.sp +for brighter colors +T} +T{ +.sp +4 +T}:T{ +.sp +for underlined text +T} +T{ +.sp +5 +T}:T{ +.sp +for flashing text +T} +T{ +.sp +30 +T}:T{ +.sp +for black foreground +T} +T{ +.sp +31 +T}:T{ +.sp +for red foreground +T} +T{ +.sp +32 +T}:T{ +.sp +for green foreground +T} +T{ +.sp +33 +T}:T{ +.sp +for yellow (or brown) foreground +T} +T{ +.sp +34 +T}:T{ +.sp +for blue foreground +T} +T{ +.sp +35 +T}:T{ +.sp +for purple foreground +T} +T{ +.sp +36 +T}:T{ +.sp +for cyan foreground +T} +T{ +.sp +37 +T}:T{ +.sp +for white (or gray) foreground +T} +T{ +.sp +40 +T}:T{ +.sp +for black background +T} +T{ +.sp +41 +T}:T{ +.sp +for red background +T} +T{ +.sp +42 +T}:T{ +.sp +for green background +T} +T{ +.sp +43 +T}:T{ +.sp +for yellow (or brown) background +T} +T{ +.sp +44 +T}:T{ +.sp +for blue background +T} +T{ +.sp +45 +T}:T{ +.sp +for purple background +T} +T{ +.sp +46 +T}:T{ +.sp +for cyan background +T} +T{ +.sp +47 +T}:T{ +.sp +for white (or gray) background +T} +.TE +.sp +.br +.RE +.ll +.SS "Escape sequences" +.sp +To specify control or blank characters in the color sequences, C\-style \(rs\-escaped notation can be used: +.RS 3 +.ll -.6i +.TS +allbox tab(:); +lt lt. +T{ +.sp +\fB\(rsa\fP +T}:T{ +.sp +Bell (ASCII 7) +T} +T{ +.sp +\fB\(rsb\fP +T}:T{ +.sp +Backspace (ASCII 8) +T} +T{ +.sp +\fB\(rse\fP +T}:T{ +.sp +Escape (ASCII 27) +T} +T{ +.sp +\fB\(rsf\fP +T}:T{ +.sp +Form feed (ASCII 12) +T} +T{ +.sp +\fB\(rsn\fP +T}:T{ +.sp +Newline (ASCII 10) +T} +T{ +.sp +\fB\(rsr\fP +T}:T{ +.sp +Carriage Return (ASCII 13) +T} +T{ +.sp +\fB\(rst\fP +T}:T{ +.sp +Tab (ASCII 9) +T} +T{ +.sp +\fB\(rsv\fP +T}:T{ +.sp +Vertical Tab (ASCII 11) +T} +T{ +.sp +\fB\(rs?\fP +T}:T{ +.sp +Delete (ASCII 127) +T} +T{ +.sp +\fB\(rs_\fP +T}:T{ +.sp +Space +T} +T{ +.sp +\fB\(rs\(rs\fP +T}:T{ +.sp +Backslash (\(rs) +T} +T{ +.sp +\fB\(rs^\fP +T}:T{ +.sp +Caret (^) +T} +T{ +.sp +\fB\(rs#\fP +T}:T{ +.sp +Hash mark (#) +T} +.TE +.sp +.br +.RE +.ll +.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. +.sp +For example, to use a red background for alert messages in the output of \fBdmesg\fP(1), use: +.RS 3 +.ll -.6i +.sp +\fBecho \(aqalert 37;41\(aq >> /etc/terminal\-colors.d/dmesg.scheme\fP +.br +.RE +.ll +.SS "Comments" +.sp +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 "ENVIRONMENT" +.sp +\fBTERMINAL_COLORS_DEBUG\fP=all +.RS 4 +enables debug output. +.RE +.SH "FILES" +.sp +\fI$XDG_CONFIG_HOME/terminal\-colors.d\fP +.sp +\fI$HOME/.config/terminal\-colors.d\fP +.sp +\fI/etc/terminal\-colors.d\fP +.SH "EXAMPLE" +.sp +Disable colors for all compatible utilities: +.RS 3 +.ll -.6i +.sp +\fBtouch /etc/terminal\-colors.d/disable\fP +.br +.RE +.ll +.sp +Disable colors for all compatible utils on a vt100 terminal: +.RS 3 +.ll -.6i +.sp +\fBtouch /etc/terminal\-colors.d/@vt100.disable\fP +.br +.RE +.ll +.sp +Disable colors for all compatible utils except \fBdmesg\fP(1): +.RS 3 +.ll -.6i +.sp +\fBtouch /etc/terminal\-colors.d/disable\fP +.sp +\fBtouch /etc/terminal\-colors.d/dmesg.enable\fP +.br +.RE +.ll +.SH "COMPATIBILITY" +.sp +The \fBterminal\-colors.d\fP functionality is currently supported by all util\-linux utilities which provides colorized output. For more details always see the \fBCOLORS\fP section in the man page for the utility. +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +\fBterminal\-colors.d\fP is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/lib/terminal-colors.d.5.adoc b/lib/terminal-colors.d.5.adoc new file mode 100644 index 0000000..f09cce1 --- /dev/null +++ b/lib/terminal-colors.d.5.adoc @@ -0,0 +1,174 @@ +//po4a: entry man manual +//// +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 +//// + += terminal-colors.d(5) +:doctype: manpage +:man manual: File formats +:man source: util-linux {release-version} +:page-layout: base +:configfile: terminal-colors.d + +== NAME + +terminal-colors.d - configure output colorization for various utilities + +== SYNOPSIS + +/etc/terminal-colors.d/_[[name][@term].][type]_ + +== DESCRIPTION + +Files in this directory determine the default behavior for utilities when coloring output. + +The _name_ is a utility name. The name is optional and when none is specified then the file is used for all unspecified utilities. + +The _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 _type_ is a file type. Supported file types are: + +*disable*:: +Turns off output colorization for all compatible utilities. + +*enable*:: +Turns on output colorization; any matching *disable* files are ignored. + +*scheme*:: +Specifies colors used for output. The file format may be specific to the utility, the default format is described below. + +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 _$XDG_CONFIG_HOME/terminal-colors.d_ or _$HOME/.config/terminal-colors.d_ overrides the global setting. + +== DEFAULT SCHEME FILES FORMAT + +The following statement is recognized: + +____ +*name color-sequence* +____ + +The *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 *color-sequence* is a color name, ASCII color sequences or escape sequences. + +=== Color names + +black, blink, blue, bold, brown, cyan, darkgray, gray, green, halfbright, lightblue, lightcyan, lightgray, lightgreen, lightmagenta, lightred, magenta, red, reset, reverse, and yellow. + +=== ANSI color sequences + +The color sequences are composed of sequences of numbers separated by semicolons. The most common codes are: + +____ +[cols=",",] +|=== +|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 +|=== +____ + +=== Escape sequences + +To specify control or blank characters in the color sequences, C-style \-escaped notation can be used: + +____ +[cols=",",] +|=== +|*\a* |Bell (ASCII 7) +|*\b* |Backspace (ASCII 8) +|*\e* |Escape (ASCII 27) +|*\f* |Form feed (ASCII 12) +|*\n* |Newline (ASCII 10) +|*\r* |Carriage Return (ASCII 13) +|*\t* |Tab (ASCII 9) +|*\v* |Vertical Tab (ASCII 11) +|*\?* |Delete (ASCII 127) +|*\_* |Space +|*\\* |Backslash (\) +|*\^* |Caret (^) +|*\#* |Hash mark (#) +|=== +____ + +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 *dmesg*(1), use: + +____ +*echo 'alert 37;41' >> /etc/terminal-colors.d/dmesg.scheme* +____ + +=== 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. + +== ENVIRONMENT + +*TERMINAL_COLORS_DEBUG*=all:: +enables debug output. + +== FILES + +_$XDG_CONFIG_HOME/terminal-colors.d_ + +_$HOME/.config/terminal-colors.d_ + +_/etc/terminal-colors.d_ + +== EXAMPLE + +Disable colors for all compatible utilities: + +____ +*touch /etc/terminal-colors.d/disable* +____ + +Disable colors for all compatible utils on a vt100 terminal: + +____ +*touch /etc/terminal-colors.d/@vt100.disable* +____ + +Disable colors for all compatible utils except *dmesg*(1): + +____ +*touch /etc/terminal-colors.d/disable* + +*touch /etc/terminal-colors.d/dmesg.enable* +____ + +== 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. + +include::man-common/bugreports.adoc[] + +include::man-common/footer-config.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/lib/timer.c b/lib/timer.c new file mode 100644 index 0000000..cfa18f6 --- /dev/null +++ b/lib/timer.c @@ -0,0 +1,98 @@ +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * 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. + */ +#ifdef HAVE_TIMER_CREATE +int setup_timer(struct ul_timer *timer, + 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, &timer->t_id)) + return 1; + if (timer_settime(timer->t_id, 0, &val, NULL)) + return 1; + return 0; +} +void cancel_timer(struct ul_timer *timer) +{ + timer_delete(timer->t_id); +} + +#else /* !HAVE_TIMER_CREATE */ + +int setup_timer(struct ul_timer *timer, + struct itimerval *timeout, + void (*timeout_handler)(int, siginfo_t *, void *)) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof sa); + memset(timer, 0, sizeof(*timer)); + + sa.sa_flags = SA_SIGINFO | SA_RESETHAND; + sa.sa_sigaction = timeout_handler; + + if (sigaction(SIGALRM, &sa, &timer->old_sa)) + return 1; + if (setitimer(ITIMER_REAL, timeout, &timer->old_timer) != 0) + return 1; + return 0; +} + +void cancel_timer(struct ul_timer *timer) +{ + setitimer(ITIMER_REAL, &timer->old_timer, NULL); + sigaction(SIGALRM, &timer->old_sa, NULL); + +} +#endif /* !HAVE_TIMER_CREATE */ diff --git a/lib/timeutils.c b/lib/timeutils.c new file mode 100644 index 0000000..2e28ada --- /dev/null +++ b/lib/timeutils.c @@ -0,0 +1,612 @@ +/*** + 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 <inttypes.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-22T16: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-%dT%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(<m); + /* Check if mktime returning -1 is an error or a valid time_t */ + if (lt == (time_t) -1) { + if (! localtime_r(<, &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(<, >m)) + 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, ".%06"PRId64, (int64_t) usec); + if (len < 0 || (size_t) len > bufsz) + goto err; + bufsz -= len; + p += len; + + } else if (flags & ISO_COMMAUSEC) { + len = snprintf(p, bufsz, ",%06"PRId64, (int64_t) 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 %"PRId64" is out of range."), (int64_t)(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 %"PRId64" is out of range."), (int64_t)*t); + return -1; +} + +/* relative time functions */ +static inline int time_is_thisyear(struct tm const *const tm, + struct tm const *const tmnow) +{ + return tm->tm_year == tmnow->tm_year; +} + +static inline int time_is_today(struct tm const *const tm, + struct tm const *const tmnow) +{ + return (tm->tm_yday == tmnow->tm_yday && + time_is_thisyear(tm, tmnow)); +} + +int strtime_short(const time_t *t, struct timeval *now, int flags, char *buf, size_t bufsz) +{ + struct tm tm, tmnow; + int rc = 0; + + if (now->tv_sec == 0) + gettimeofday(now, NULL); + + localtime_r(t, &tm); + localtime_r(&now->tv_sec, &tmnow); + + if (time_is_today(&tm, &tmnow)) { + 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(&tm, &tmnow)) { + 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>]] | [--timestamp <str>]\n", argv[0]); + exit(EXIT_FAILURE); + } + + if (strcmp(argv[1], "--timestamp") == 0) { + usec_t usec = 0; + + parse_timestamp(argv[2], &usec); + tv.tv_sec = (time_t) (usec / 1000000); + tv.tv_usec = usec % 1000000; + } else { + 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..7064565 --- /dev/null +++ b/lib/ttyutils.c @@ -0,0 +1,152 @@ +/* + * 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) { + if (c <= 0) + c = get_env_int("COLUMNS"); + *cols = c; + } + if (lines) { + if (l <= 0) + l = get_env_int("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_stdfd(void) +{ + if (isatty(STDIN_FILENO)) + return STDIN_FILENO; + if (isatty(STDOUT_FILENO)) + return STDOUT_FILENO; + if (isatty(STDERR_FILENO)) + return STDERR_FILENO; + + return -EINVAL; +} + +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; + + fd = get_terminal_stdfd(); + if (fd < 0) + return fd; /* error */ + + tty = ttyname(fd); + if (!tty) + return -1; + + if (path) + *path = tty; + if (name || number) + 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 */ |