summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorIgor Pashev <pashev.igor@gmail.com>2012-11-02 20:15:39 +0400
committerIgor Pashev <pashev.igor@gmail.com>2012-11-02 20:15:39 +0400
commitb13154de3eca5ba28fbb4854d916cd0be5febeed (patch)
tree30f2e9e89ab71a2df837076ac68c3ba770230294 /lib
downloadutil-linux-upstream.tar.gz
Imported Upstream version 2.22upstream/2.22upstream
Diffstat (limited to 'lib')
-rw-r--r--lib/Makemodule.am111
-rw-r--r--lib/at.c140
-rw-r--r--lib/blkdev.c370
-rw-r--r--lib/canonicalize.c205
-rw-r--r--lib/cpuset.c399
-rw-r--r--lib/crc32.c116
-rw-r--r--lib/env.c109
-rw-r--r--lib/fileutils.c83
-rw-r--r--lib/ismounted.c372
-rw-r--r--lib/langinfo.c121
-rw-r--r--lib/linux_version.c25
-rw-r--r--lib/loopdev.c1592
-rw-r--r--lib/mangle.c166
-rw-r--r--lib/match.c53
-rw-r--r--lib/mbsalign.c290
-rw-r--r--lib/md5.c254
-rw-r--r--lib/pager.c215
-rw-r--r--lib/path.c218
-rw-r--r--lib/procutils.c124
-rw-r--r--lib/randutils.c121
-rw-r--r--lib/setproctitle.c74
-rw-r--r--lib/strutils.c696
-rw-r--r--lib/sysfs.c705
-rw-r--r--lib/tt.c998
-rw-r--r--lib/wholedisk.c57
-rw-r--r--lib/xgetpass.c46
26 files changed, 7660 insertions, 0 deletions
diff --git a/lib/Makemodule.am b/lib/Makemodule.am
new file mode 100644
index 0000000..33a3eb8
--- /dev/null
+++ b/lib/Makemodule.am
@@ -0,0 +1,111 @@
+
+noinst_LTLIBRARIES += libcommon.la
+libcommon_la_CFLAGS = $(AM_CFLAGS)
+libcommon_la_SOURCES = \
+ lib/at.c \
+ lib/blkdev.c \
+ lib/canonicalize.c \
+ lib/cpuset.c \
+ lib/crc32.c \
+ lib/env.c \
+ lib/fileutils.c \
+ lib/ismounted.c \
+ lib/mangle.c \
+ lib/match.c \
+ lib/mbsalign.c \
+ lib/md5.c \
+ lib/pager.c \
+ lib/path.c \
+ lib/procutils.c \
+ lib/randutils.c \
+ lib/setproctitle.c \
+ lib/strutils.c \
+ lib/sysfs.c \
+ lib/tt.c \
+ lib/wholedisk.c \
+ lib/xgetpass.c
+
+if LINUX
+libcommon_la_SOURCES += \
+ lib/linux_version.c \
+ lib/loopdev.c
+endif
+
+if !HAVE_LANGINFO
+libcommon_la_SOURCES += lib/langinfo.c
+endif
+
+check_PROGRAMS += \
+ test_at \
+ test_blkdev \
+ test_canonicalize \
+ test_fileutils \
+ test_ismounted \
+ test_mangle \
+ test_procutils \
+ test_randutils \
+ test_strutils \
+ test_tt \
+ test_wholedisk
+
+if LINUX
+if HAVE_CPU_SET_T
+check_PROGRAMS += test_cpuset
+endif
+check_PROGRAMS += \
+ test_sysfs \
+ test_loopdev \
+ test_pager
+endif
+
+test_blkdev_SOURCES = lib/blkdev.c
+test_blkdev_CFLAGS = -DTEST_PROGRAM
+test_blkdev_LDADD = libcommon.la
+
+test_ismounted_SOURCES = lib/ismounted.c
+test_ismounted_CFLAGS = -DTEST_PROGRAM
+
+test_wholedisk_SOURCES = lib/wholedisk.c
+test_wholedisk_CFLAGS = -DTEST_PROGRAM
+
+test_mangle_SOURCES = lib/mangle.c
+test_mangle_CFLAGS = -DTEST_PROGRAM
+
+test_at_SOURCES = lib/at.c
+test_at_CFLAGS = -DTEST_PROGRAM_AT
+
+test_strutils_SOURCES = lib/strutils.c
+test_strutils_CFLAGS = -DTEST_PROGRAM
+
+test_randutils_SOURCES = lib/randutils.c
+test_randutils_CFLAGS = -DTEST_PROGRAM
+
+test_procutils_SOURCES = lib/procutils.c
+test_procutils_CFLAGS = -DTEST_PROGRAM
+
+if LINUX
+test_cpuset_SOURCES = lib/cpuset.c
+test_cpuset_CFLAGS = -DTEST_PROGRAM
+
+test_sysfs_SOURCES = lib/sysfs.c
+test_sysfs_CFLAGS = -DTEST_PROGRAM_SYSFS
+test_sysfs_LDADD = libcommon.la
+
+test_pager_SOURCES = lib/pager.c
+test_pager_CFLAGS = -DTEST_PROGRAM
+
+test_loopdev_SOURCES = lib/loopdev.c
+test_loopdev_CFLAGS = -DTEST_PROGRAM_LOOPDEV
+test_loopdev_LDADD = libcommon.la
+endif
+
+test_fileutils_SOURCES = lib/fileutils.c
+test_fileutils_CFLAGS = -DTEST_PROGRAM
+
+test_tt_SOURCES = lib/tt.c
+test_tt_CFLAGS = -DTEST_PROGRAM
+test_tt_LDADD = libcommon.la
+
+test_canonicalize_SOURCES = lib/canonicalize.c
+test_canonicalize_CFLAGS = -DTEST_PROGRAM_CANONICALIZE
+
diff --git a/lib/at.c b/lib/at.c
new file mode 100644
index 0000000..bbce516
--- /dev/null
+++ b/lib/at.c
@@ -0,0 +1,140 @@
+/*
+ * Portable xxxat() functions.
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "at.h"
+#include "c.h"
+
+#ifdef HAVE_FSTATAT
+int fstat_at(int dir, const char *dirname __attribute__ ((__unused__)),
+ const char *filename, struct stat *st, int nofollow)
+{
+ return fstatat(dir, filename, st,
+ nofollow ? AT_SYMLINK_NOFOLLOW : 0);
+}
+#else
+int fstat_at(int dir, const char *dirname, const char *filename,
+ struct stat *st, int nofollow)
+{
+
+ if (*filename != '/') {
+ char path[PATH_MAX];
+ int len;
+
+ len = snprintf(path, sizeof(path), "%s/%s", dirname, filename);
+ if (len < 0 || len + 1 > sizeof(path))
+ return -1;
+
+ return nofollow ? lstat(path, st) : stat(path, st);
+ }
+
+ return nofollow ? lstat(filename, st) : stat(filename, st);
+}
+#endif
+
+#ifdef HAVE_FSTATAT
+int open_at(int dir, const char *dirname __attribute__ ((__unused__)),
+ const char *filename, int flags)
+{
+ return openat(dir, filename, flags);
+}
+#else
+int open_at(int dir, const char *dirname, const char *filename, int flags)
+{
+ if (*filename != '/') {
+ char path[PATH_MAX];
+ int len;
+
+ len = snprintf(path, sizeof(path), "%s/%s", dirname, filename);
+ if (len < 0 || len + 1 > sizeof(path))
+ return -1;
+
+ return open(path, flags);
+ }
+ return open(filename, flags);
+}
+#endif
+
+FILE *fopen_at(int dir, const char *dirname, const char *filename, int flags,
+ const char *mode)
+{
+ int fd = open_at(dir, dirname, filename, flags);
+
+ if (fd < 0)
+ return NULL;
+
+ return fdopen(fd, mode);
+}
+
+#ifdef HAVE_FSTATAT
+ssize_t readlink_at(int dir, const char *dirname __attribute__ ((__unused__)),
+ const char *pathname, char *buf, size_t bufsiz)
+{
+ return readlinkat(dir, pathname, buf, bufsiz);
+}
+#else
+ssize_t readlink_at(int dir, const char *dirname, const char *pathname,
+ char *buf, size_t bufsiz)
+{
+ if (*pathname != '/') {
+ char path[PATH_MAX];
+ int len;
+
+ len = snprintf(path, sizeof(path), "%s/%s", dirname, pathname);
+ if (len < 0 || len + 1 > sizeof(path))
+ return -1;
+
+ return readlink(path, buf, bufsiz);
+ }
+ return readlink(pathname, buf, bufsiz);
+}
+#endif
+
+#ifdef TEST_PROGRAM_AT
+#include <errno.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <string.h>
+
+int main(int argc, char *argv[])
+{
+ DIR *dir;
+ struct dirent *d;
+ char *dirname;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s <directory>\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ dirname = argv[1];
+
+ dir = opendir(dirname);
+ if (!dir)
+ err(EXIT_FAILURE, "cannot open %s", dirname);
+
+ while ((d = readdir(dir))) {
+ struct stat st;
+ FILE *f;
+
+ printf("%32s ", d->d_name);
+
+ if (fstat_at(dirfd(dir), dirname, d->d_name, &st, 0) == 0)
+ printf("%16jd bytes ", st.st_size);
+ else
+ printf("%16s bytes ", "???");
+
+ f = fopen_at(dirfd(dir), dirname, d->d_name, O_RDONLY, "r");
+ printf(" %s\n", f ? "OK" : strerror(errno));
+ if (f)
+ fclose(f);
+ }
+ closedir(dir);
+ return EXIT_SUCCESS;
+}
+#endif
diff --git a/lib/blkdev.c b/lib/blkdev.c
new file mode 100644
index 0000000..9193b64
--- /dev/null
+++ b/lib/blkdev.c
@@ -0,0 +1,370 @@
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <stdint.h>
+
+#ifdef HAVE_LINUX_FD_H
+#include <linux/fd.h>
+#endif
+
+#ifdef HAVE_SYS_DISKLABEL_H
+#include <sys/disklabel.h>
+#endif
+
+#ifdef HAVE_SYS_DISK_H
+#ifdef HAVE_SYS_QUEUE_H
+#include <sys/queue.h> /* for LIST_HEAD */
+#endif
+#include <sys/disk.h>
+#endif
+
+#include "blkdev.h"
+#include "c.h"
+#include "linux_version.h"
+#include "xalloc.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
+ {
+#ifdef __linux__
+ int ver = get_linux_version();
+
+ /* kernels 2.4.15-2.4.17, had a broken BLKGETSIZE64 */
+ if (ver >= KERNEL_VERSION (2,6,0) ||
+ (ver >= KERNEL_VERSION (2,4,18) && ver < KERNEL_VERSION (2,5,0)))
+#endif
+ if (ioctl(fd, BLKGETSIZE64, bytes) >= 0)
+ return 0;
+ }
+#endif /* BLKGETSIZE64 */
+
+#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 = this_floppy.size << 9;
+ return 0;
+ }
+ }
+#endif /* FDGETPRM */
+
+#ifdef HAVE_SYS_DISKLABEL_H
+ {
+ /*
+ * 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;
+ char ch;
+ 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 /* HAVE_SYS_DISKLABEL_H */
+
+ {
+ 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.
+ */
+int blkdev_get_sector_size(int fd, int *sector_size)
+{
+#ifdef BLKSSZGET
+ if (ioctl(fd, BLKSSZGET, sector_size) >= 0)
+ return 0;
+ return -1;
+#else
+ *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;
+ * }
+ */
+int blkdev_get_physector_size(int fd, int *sector_size)
+{
+#ifdef BLKPBSZGET
+ if (ioctl(fd, BLKPBSZGET, &sector_size) >= 0)
+ return 0;
+ return -1;
+#else
+ *sector_size = DEFAULT_SECTOR_SIZE;
+ return 0;
+#endif
+}
+
+/*
+ * Return the alignment status of a device
+ */
+int blkdev_is_misaligned(int fd)
+{
+#ifdef BLKALIGNOFF
+ int aligned;
+
+ if (ioctl(fd, BLKALIGNOFF, &aligned) < 0)
+ return 0; /* probably kernel < 2.6.32 */
+ /*
+ * Note that kernel returns -1 as alignement offset if no compatible
+ * sizes and alignments exist for stacked devices
+ */
+ return aligned != 0 ? 1 : 0;
+#else
+ return 0;
+#endif
+}
+
+int blkdev_is_cdrom(int fd)
+{
+#ifdef CDROM_GET_CAPABILITY
+ int ret;
+
+ if ((ret = ioctl(fd, CDROM_GET_CAPABILITY, NULL)) < 0)
+ return 0;
+ else
+ return ret;
+#else
+ 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.
+ */
+int blkdev_get_geometry(int fd, unsigned int *h, unsigned int *s)
+{
+#ifdef HDIO_GETGEO
+ struct hd_geometry geometry;
+
+ if (ioctl(fd, HDIO_GETGEO, &geometry) == 0) {
+ *h = geometry.heads;
+ *s = geometry.sectors;
+ return 0;
+ }
+#else
+ *h = 0;
+ *s = 0;
+#endif
+ return -1;
+}
+
+/*
+ * Convert scsi type to human readable string.
+ */
+const char *blkdev_scsi_type_to_name(int type)
+{
+ switch (type) {
+ case SCSI_TYPE_DISK:
+ return "disk";
+ case SCSI_TYPE_TAPE:
+ return "tape";
+ case SCSI_TYPE_PRINTER:
+ return "printer";
+ case SCSI_TYPE_PROCESSOR:
+ return "processor";
+ case SCSI_TYPE_WORM:
+ return "worm";
+ case SCSI_TYPE_ROM:
+ return "rom";
+ case SCSI_TYPE_SCANNER:
+ return "scanner";
+ case SCSI_TYPE_MOD:
+ return "mo-disk";
+ case SCSI_TYPE_MEDIUM_CHANGER:
+ return "changer";
+ case SCSI_TYPE_COMM:
+ return "comm";
+ case SCSI_TYPE_RAID:
+ return "raid";
+ case SCSI_TYPE_ENCLOSURE:
+ return "enclosure";
+ case SCSI_TYPE_RBC:
+ return "rbc";
+ case SCSI_TYPE_OSD:
+ return "osd";
+ case SCSI_TYPE_NO_LUN:
+ return "no-lun";
+ default:
+ break;
+ }
+ return NULL;
+}
+
+#ifdef TEST_PROGRAM
+#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)) < 0)
+ err(EXIT_FAILURE, "open %s failed", argv[1]);
+
+ if (blkdev_get_size(fd, &bytes) < 0)
+ err(EXIT_FAILURE, "blkdev_get_size() failed");
+ if (blkdev_get_sectors(fd, &sectors) < 0)
+ err(EXIT_FAILURE, "blkdev_get_sectors() failed");
+ if (blkdev_get_sector_size(fd, &sector_size) < 0)
+ err(EXIT_FAILURE, "blkdev_get_sector_size() failed");
+ if (blkdev_get_physector_size(fd, &phy_sector_size) < 0)
+ err(EXIT_FAILURE, "blkdev_get_physector_size() failed");
+
+ printf(" bytes: %llu\n", bytes);
+ printf(" sectors: %llu\n", sectors);
+ printf(" sector size: %d\n", sector_size);
+ printf("phy-sector size: %d\n", phy_sector_size);
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM */
diff --git a/lib/canonicalize.c b/lib/canonicalize.c
new file mode 100644
index 0000000..ab32c10
--- /dev/null
+++ b/lib/canonicalize.c
@@ -0,0 +1,205 @@
+/*
+ * canonicalize.c -- canonicalize pathname by removing symlinks
+ * Copyright (C) 1993 Rick Sladkey <jrs@world.std.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Library Public License for more details.
+ *
+ */
+
+/*
+ * This routine is part of libc. We include it nevertheless,
+ * since the libc version has some security flaws.
+ *
+ * TODO: use canonicalize_file_name() when exist in glibc
+ */
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "canonicalize.h"
+
+#ifndef MAXSYMLINKS
+# define MAXSYMLINKS 256
+#endif
+
+static char *
+myrealpath(const char *path, char *resolved_path, int maxreslth) {
+ int readlinks = 0;
+ char *npath;
+ char link_path[PATH_MAX+1];
+ int n;
+ char *buf = NULL;
+
+ npath = resolved_path;
+
+ /* If it's a relative pathname use getcwd for starters. */
+ if (*path != '/') {
+ if (!getcwd(npath, maxreslth-2))
+ return NULL;
+ npath += strlen(npath);
+ if (npath[-1] != '/')
+ *npath++ = '/';
+ } else {
+ *npath++ = '/';
+ path++;
+ }
+
+ /* Expand each slash-separated pathname component. */
+ while (*path != '\0') {
+ /* Ignore stray "/" */
+ if (*path == '/') {
+ path++;
+ continue;
+ }
+ if (*path == '.' && (path[1] == '\0' || path[1] == '/')) {
+ /* Ignore "." */
+ path++;
+ continue;
+ }
+ if (*path == '.' && path[1] == '.' &&
+ (path[2] == '\0' || path[2] == '/')) {
+ /* Backup for ".." */
+ path += 2;
+ while (npath > resolved_path+1 &&
+ (--npath)[-1] != '/')
+ ;
+ continue;
+ }
+ /* Safely copy the next pathname component. */
+ while (*path != '\0' && *path != '/') {
+ if (npath-resolved_path > maxreslth-2) {
+ errno = ENAMETOOLONG;
+ goto err;
+ }
+ *npath++ = *path++;
+ }
+
+ /* Protect against infinite loops. */
+ if (readlinks++ > MAXSYMLINKS) {
+ errno = ELOOP;
+ goto err;
+ }
+
+ /* See if last pathname component is a symlink. */
+ *npath = '\0';
+ n = readlink(resolved_path, link_path, PATH_MAX);
+ if (n < 0) {
+ /* EINVAL means the file exists but isn't a symlink. */
+ if (errno != EINVAL)
+ goto err;
+ } else {
+ int m;
+ char *newbuf;
+
+ /* Note: readlink doesn't add the null byte. */
+ link_path[n] = '\0';
+ if (*link_path == '/')
+ /* Start over for an absolute symlink. */
+ npath = resolved_path;
+ else
+ /* Otherwise back up over this component. */
+ while (*(--npath) != '/')
+ ;
+
+ /* Insert symlink contents into path. */
+ m = strlen(path);
+ newbuf = malloc(m + n + 1);
+ if (!newbuf)
+ goto err;
+ memcpy(newbuf, link_path, n);
+ memcpy(newbuf + n, path, m + 1);
+ free(buf);
+ path = buf = newbuf;
+ }
+ *npath++ = '/';
+ }
+ /* Delete trailing slash but don't whomp a lone slash. */
+ if (npath != resolved_path+1 && npath[-1] == '/')
+ npath--;
+ /* Make sure it's null terminated. */
+ *npath = '\0';
+
+ free(buf);
+ return resolved_path;
+
+ err:
+ free(buf);
+ return NULL;
+}
+
+/*
+ * 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 *ptname)
+{
+ FILE *f;
+ size_t sz;
+ char path[256], name[256], *res = NULL;
+
+ snprintf(path, sizeof(path), "/sys/block/%s/dm/name", ptname);
+ if (!(f = fopen(path, "r")))
+ 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), "/dev/mapper/%s", name);
+ res = strdup(path);
+ }
+ fclose(f);
+ return res;
+}
+
+char *
+canonicalize_path(const char *path)
+{
+ char canonical[PATH_MAX+2];
+ char *p;
+
+ if (path == NULL)
+ return NULL;
+
+ if (!myrealpath(path, canonical, PATH_MAX+1))
+ return strdup(path);
+
+
+ p = strrchr(canonical, '/');
+ if (p && strncmp(p, "/dm-", 4) == 0 && isdigit(*(p + 4))) {
+ p = canonicalize_dm_name(p+1);
+ if (p)
+ return p;
+ }
+
+ return strdup(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/cpuset.c b/lib/cpuset.c
new file mode 100644
index 0000000..26b0a90
--- /dev/null
+++ b/lib/cpuset.c
@@ -0,0 +1,399 @@
+/*
+ * 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.
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sched.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/syscall.h>
+
+#include "cpuset.h"
+#include "c.h"
+
+static inline int val_to_char(int v)
+{
+ if (v >= 0 && v < 10)
+ return '0' + v;
+ else if (v >= 10 && v < 16)
+ return ('a' - 10) + v;
+ else
+ return -1;
+}
+
+static inline int char_to_val(int c)
+{
+ int cl;
+
+ cl = tolower(c);
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ else if (cl >= 'a' && cl <= 'f')
+ return cl + (10 - 'a');
+ else
+ 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, "%zd,", i);
+ else if (run == 1) {
+ rlen = snprintf(ptr, len, "%zd,%zd,", i, i + 1);
+ i++;
+ } else {
+ rlen = snprintf(ptr, len, "%zd-%zd,", i, i + run);
+ i += run;
+ }
+ if (rlen < 0 || (size_t) rlen + 1 > len)
+ return NULL;
+ ptr += rlen;
+ if (rlen > 0 && len > (size_t) rlen)
+ len -= rlen;
+ else
+ len = 0;
+ }
+ }
+ 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);
+ len--;
+ ptr--;
+ cpu += 4;
+ }
+
+ return 0;
+}
+
+/*
+ * Parses string with list of CPU ranges.
+ * Returns 0 on success.
+ * Returns 1 on error.
+ * Returns 2 if fail is set and a cpu number passed in the list doesn't fit
+ * into the cpu_set. If fail is not set cpu numbers that do not fit are
+ * ignored and 0 is returned instead.
+ */
+int cpulist_parse(const char *str, cpu_set_t *set, size_t setsize, int fail)
+{
+ size_t max = cpuset_nbits(setsize);
+ const char *p, *q;
+ int r = 0;
+
+ q = str;
+ CPU_ZERO_S(setsize, set);
+
+ while (p = q, q = nexttoken(q, ','), p) {
+ unsigned int a; /* beginning of range */
+ unsigned int b; /* end of range */
+ unsigned int s; /* stride */
+ const char *c1, *c2;
+ char c;
+
+ if ((r = sscanf(p, "%u%c", &a, &c)) < 1)
+ return 1;
+ b = a;
+ s = 1;
+
+ c1 = nexttoken(p, '-');
+ c2 = nexttoken(p, ',');
+ if (c1 != NULL && (c2 == NULL || c1 < c2)) {
+ if ((r = sscanf(c1, "%u%c", &b, &c)) < 1)
+ return 1;
+ c1 = nexttoken(c1, ':');
+ if (c1 != NULL && (c2 == NULL || c1 < c2)) {
+ if ((r = sscanf(c1, "%u%c", &s, &c)) < 1)
+ return 1;
+ if (s == 0)
+ return 1;
+ }
+ }
+
+ if (!(a <= b))
+ return 1;
+ while (a <= b) {
+ if (fail && (a >= max))
+ return 2;
+ CPU_SET_S(a, setsize, set);
+ a += s;
+ }
+ }
+
+ if (r == 2)
+ return 1;
+ return 0;
+}
+
+#ifdef TEST_PROGRAM
+
+#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, 0, 'n' },
+ { "mask", 1, 0, 'm' },
+ { "range", 1, 0, 'r' },
+ { NULL, 0, 0, 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(range);
+ cpuset_free(set);
+
+ return EXIT_SUCCESS;
+
+usage_err:
+ fprintf(stderr,
+ "usage: %s [--ncpus <num>] --mask <mask> | --range <list>",
+ program_invocation_short_name);
+ exit(EXIT_FAILURE);
+}
+#endif
diff --git a/lib/crc32.c b/lib/crc32.c
new file mode 100644
index 0000000..eaaa06a
--- /dev/null
+++ b/lib/crc32.c
@@ -0,0 +1,116 @@
+/*
+ * 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 hight-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
+};
+
+/*
+ * 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 crc32(uint32_t seed, const unsigned char *buf, size_t len)
+{
+ uint32_t crc = seed;
+ const unsigned char *p = buf;
+
+ while(len-- > 0)
+ crc = crc32_tab[(crc ^ *p++) & 0xff] ^ (crc >> 8);
+
+ return crc;
+}
+
diff --git a/lib/env.c b/lib/env.c
new file mode 100644
index 0000000..04e0f0b
--- /dev/null
+++ b/lib/env.c
@@ -0,0 +1,109 @@
+/*
+ * Security checks of environment
+ * Added from shadow-utils package
+ * by Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_SYS_PRCTL_H
+#include <sys/prctl.h>
+#else
+#define PR_GET_DUMPABLE 3
+#endif
+#if (!defined(HAVE_PRCTL) && defined(linux))
+#include <sys/syscall.h>
+#endif
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "env.h"
+
+#ifndef HAVE_ENVIRON_DECL
+extern char **environ;
+#endif
+
+static char * const forbid[] = {
+ "_RLD_=",
+ "BASH_ENV=", /* GNU creeping featurism strikes again... */
+ "ENV=",
+ "HOME=",
+ "IFS=",
+ "KRB_CONF=",
+ "LD_", /* anything with the LD_ prefix */
+ "LIBPATH=",
+ "MAIL=",
+ "NLSPATH=",
+ "PATH=",
+ "SHELL=",
+ "SHLIB_PATH=",
+ (char *) 0
+};
+
+/* these are allowed, but with no slashes inside
+ (to work around security problems in GNU gettext) */
+static char * const noslash[] = {
+ "LANG=",
+ "LANGUAGE=",
+ "LC_", /* anything with the LC_ prefix */
+ (char *) 0
+};
+
+void
+sanitize_env(void)
+{
+ char **envp = environ;
+ char * const *bad;
+ char **cur;
+ char **move;
+
+ for (cur = envp; *cur; cur++) {
+ for (bad = forbid; *bad; bad++) {
+ if (strncmp(*cur, *bad, strlen(*bad)) == 0) {
+ for (move = cur; *move; move++)
+ *move = *(move + 1);
+ cur--;
+ break;
+ }
+ }
+ }
+
+ for (cur = envp; *cur; cur++) {
+ for (bad = noslash; *bad; bad++) {
+ if (strncmp(*cur, *bad, strlen(*bad)) != 0)
+ continue;
+ if (!strchr(*cur, '/'))
+ continue; /* OK */
+ for (move = cur; *move; move++)
+ *move = *(move + 1);
+ cur--;
+ break;
+ }
+ }
+}
+
+
+char *safe_getenv(const char *arg)
+{
+ uid_t ruid = getuid();
+
+ if (ruid != 0 || (ruid != geteuid()) || (getgid() != getegid()))
+ return NULL;
+#ifdef HAVE_PRCTL
+ if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 0)
+ return NULL;
+#else
+#if (defined(linux) && defined(SYS_prctl))
+ if (syscall(SYS_prctl, PR_GET_DUMPABLE, 0, 0, 0, 0) == 0)
+ return NULL;
+#endif
+#endif
+
+#ifdef HAVE___SECURE_GETENV
+ return __secure_getenv(arg);
+#else
+ return getenv(arg);
+#endif
+}
diff --git a/lib/fileutils.c b/lib/fileutils.c
new file mode 100644
index 0000000..ff8bb86
--- /dev/null
+++ b/lib/fileutils.c
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2012 Sami Kerola <kerolasa@iki.fi>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include "c.h"
+#include "fileutils.h"
+#include "pathnames.h"
+#include "xalloc.h"
+
+/* Create open temporary file in safe way. Please notice that the
+ * file permissions are -rw------- by default. */
+int xmkstemp(char **tmpname, char *dir)
+{
+ char *localtmp;
+ char *tmpenv;
+ mode_t old_mode;
+ int fd;
+
+ /* Some use cases must be capable of being moved atomically
+ * with rename(2), which is the reason why dir is here. */
+ if (dir != NULL)
+ tmpenv = dir;
+ else
+ tmpenv = getenv("TMPDIR");
+
+ if (tmpenv)
+ xasprintf(&localtmp, "%s/%s.XXXXXX", tmpenv,
+ program_invocation_short_name);
+ else
+ xasprintf(&localtmp, "%s/%s.XXXXXX", _PATH_TMP,
+ program_invocation_short_name);
+ old_mode = umask(077);
+ fd = mkstemp(localtmp);
+ umask(old_mode);
+ if (fd == -1) {
+ free(localtmp);
+ localtmp = NULL;
+ }
+ *tmpname = localtmp;
+ return fd;
+}
+
+/*
+ * portable getdtablesize()
+ */
+int get_fd_tabsize(void)
+{
+ int m;
+
+#if defined(HAVE_GETDTABLESIZE)
+ m = getdtablesize();
+#elif defined(HAVE_GETRLIMIT) && defined(RLIMIT_NOFILE)
+ struct rlimit rl;
+
+ getrlimit(RLIMIT_NOFILE, &rl);
+ m = rl.rlim_cur;
+#elif defined(HAVE_SYSCONF) && defined(_SC_OPEN_MAX)
+ m = sysconf(_SC_OPEN_MAX);
+#else
+ m = OPEN_MAX;
+#endif
+ return m;
+}
+
+#ifdef TEST_PROGRAM
+int main(void)
+{
+ FILE *f;
+ char *tmpname;
+ f = xfmkstemp(&tmpname, NULL);
+ unlink(tmpname);
+ free(tmpname);
+ fclose(f);
+ return EXIT_FAILURE;
+}
+#endif
diff --git a/lib/ismounted.c b/lib/ismounted.c
new file mode 100644
index 0000000..273a7d9
--- /dev/null
+++ b/lib/ismounted.c
@@ -0,0 +1,372 @@
+/*
+ * 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>
+#ifdef __APPLE__
+#include <sys/ucred.h>
+#include <sys/mount.h>
+#endif
+
+#include "pathnames.h"
+#include "ismounted.h"
+#include "c.h"
+
+#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) {
+ if (S_ISBLK(st_buf.st_mode)) {
+#ifndef __GNU__
+ if (file_rdev && (file_rdev == st_buf.st_rdev))
+ break;
+#endif /* __GNU__ */
+ } else {
+ if (file_dev && ((file_dev == st_buf.st_dev) &&
+ (file_ino == st_buf.st_ino)))
+ break;
+ }
+ }
+ }
+
+ if (mnt == 0) {
+#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */
+ /*
+ * Do an extra check to see if this is the root device. We
+ * can't trust /etc/mtab, and /proc/mounts will only list
+ * /dev/root for the root filesystem. Argh. Instead we
+ * check if the given device has the same major/minor number
+ * as the device that the root directory is on.
+ */
+ if (file_rdev && stat("/", &st_buf) == 0 &&
+ st_buf.st_dev == file_rdev) {
+ *mount_flags = MF_MOUNTED;
+ if (mtpt)
+ strncpy(mtpt, "/", mtlen);
+ goto is_root;
+ }
+#endif /* __GNU__ */
+ goto errout;
+ }
+#ifndef __GNU__ /* The GNU hurd is deficient; what else is new? */
+ /* Validate the entry in case /etc/mtab is out of date */
+ /*
+ * We need to be paranoid, because some broken distributions
+ * (read: Slackware) don't initialize /etc/mtab before checking
+ * all of the non-root filesystems on the disk.
+ */
+ if (stat(mnt->mnt_dir, &st_buf) < 0) {
+ retval = errno;
+ if (retval == ENOENT) {
+#ifdef DEBUG
+ printf("Bogus entry in %s! (%s does not exist)\n",
+ mtab_file, mnt->mnt_dir);
+#endif /* DEBUG */
+ retval = 0;
+ }
+ goto errout;
+ }
+ if (file_rdev && (st_buf.st_dev != file_rdev)) {
+#ifdef DEBUG
+ printf("Bogus entry in %s! (%s not mounted on %s)\n",
+ mtab_file, file, mnt->mnt_dir);
+#endif /* DEBUG */
+ goto errout;
+ }
+#endif /* __GNU__ */
+ *mount_flags = MF_MOUNTED;
+
+#ifdef MNTOPT_RO
+ /* Check to see if the ro option is set */
+ if (hasmntopt(mnt, MNTOPT_RO))
+ *mount_flags |= MF_READONLY;
+#endif
+
+ if (mtpt)
+ strncpy(mtpt, mnt->mnt_dir, mtlen);
+ /*
+ * Check to see if we're referring to the root filesystem.
+ * If so, do a manual check to see if we can open /etc/mtab
+ * read/write, since if the root is mounted read/only, the
+ * contents of /etc/mtab may not be accurate.
+ */
+ if (!strcmp(mnt->mnt_dir, "/")) {
+is_root:
+#define TEST_FILE "/.ismount-test-file"
+ *mount_flags |= MF_ISROOT;
+ fd = open(TEST_FILE, O_RDWR|O_CREAT, 0600);
+ if (fd < 0) {
+ if (errno == EROFS)
+ *mount_flags |= MF_READONLY;
+ } else
+ close(fd);
+ (void) unlink(TEST_FILE);
+ }
+ retval = 0;
+errout:
+ endmntent (f);
+ return retval;
+}
+
+static int check_mntent(const char *file, int *mount_flags,
+ char *mtpt, int mtlen)
+{
+ int retval;
+
+#ifdef DEBUG
+ retval = check_mntent_file("/tmp/mtab", file, mount_flags,
+ mtpt, mtlen);
+ if (retval == 0)
+ return 0;
+#endif /* DEBUG */
+#ifdef __linux__
+ retval = check_mntent_file("/proc/mounts", file, mount_flags,
+ mtpt, mtlen);
+ if (retval == 0 && (*mount_flags != 0))
+ return 0;
+ if (access("/proc/mounts", R_OK) == 0) {
+ *mount_flags = 0;
+ return retval;
+ }
+#endif /* __linux__ */
+#if defined(MOUNTED) || defined(_PATH_MOUNTED)
+#ifndef MOUNTED
+#define MOUNTED _PATH_MOUNTED
+#endif /* MOUNTED */
+ retval = check_mntent_file(MOUNTED, file, mount_flags, mtpt, mtlen);
+ return retval;
+#else
+ *mount_flags = 0;
+ return 0;
+#endif /* defined(MOUNTED) || defined(_PATH_MOUNTED) */
+}
+
+#else
+#if defined(HAVE_GETMNTINFO)
+
+static int check_getmntinfo(const char *file, int *mount_flags,
+ char *mtpt, int mtlen)
+{
+ struct statfs *mp;
+ int len, n;
+ const char *s1;
+ char *s2;
+
+ n = getmntinfo(&mp, MNT_NOWAIT);
+ if (n == 0)
+ return errno;
+
+ len = sizeof(_PATH_DEV) - 1;
+ s1 = file;
+ if (strncmp(_PATH_DEV, s1, len) == 0)
+ s1 += len;
+
+ *mount_flags = 0;
+ while (--n >= 0) {
+ s2 = mp->f_mntfromname;
+ if (strncmp(_PATH_DEV, s2, len) == 0) {
+ s2 += len - 1;
+ *s2 = 'r';
+ }
+ if (strcmp(s1, s2) == 0 || strcmp(s1, &s2[1]) == 0) {
+ *mount_flags = MF_MOUNTED;
+ break;
+ }
+ ++mp;
+ }
+ if (mtpt)
+ strncpy(mtpt, mp->f_mntonname, mtlen);
+ return 0;
+}
+#endif /* HAVE_GETMNTINFO */
+#endif /* HAVE_MNTENT_H */
+
+/*
+ * Check to see if we're dealing with the swap device.
+ */
+static int is_swap_device(const char *file)
+{
+ FILE *f;
+ char buf[1024], *cp;
+ dev_t file_dev;
+ struct stat st_buf;
+ int ret = 0;
+
+ file_dev = 0;
+#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */
+ if ((stat(file, &st_buf) == 0) &&
+ S_ISBLK(st_buf.st_mode))
+ file_dev = st_buf.st_rdev;
+#endif /* __GNU__ */
+
+ if (!(f = fopen("/proc/swaps", "r")))
+ return 0;
+ /* Skip the first line */
+ if (!fgets(buf, sizeof(buf), f))
+ goto leave;
+ if (*buf && strncmp(buf, "Filename\t", 9))
+ /* Linux <=2.6.19 contained a bug in the /proc/swaps
+ * code where the header would not be displayed
+ */
+ goto valid_first_line;
+
+ while (fgets(buf, sizeof(buf), f)) {
+valid_first_line:
+ if ((cp = strchr(buf, ' ')) != NULL)
+ *cp = 0;
+ if ((cp = strchr(buf, '\t')) != NULL)
+ *cp = 0;
+ if (strcmp(buf, file) == 0) {
+ ret++;
+ break;
+ }
+#ifndef __GNU__
+ if (file_dev && (stat(buf, &st_buf) == 0) &&
+ S_ISBLK(st_buf.st_mode) &&
+ file_dev == st_buf.st_rdev) {
+ ret++;
+ break;
+ }
+#endif /* __GNU__ */
+ }
+
+leave:
+ fclose(f);
+ return ret;
+}
+
+
+/*
+ * check_mount_point() fills determines if the device is mounted or otherwise
+ * busy, and fills in mount_flags with one or more of the following flags:
+ * MF_MOUNTED, MF_ISROOT, MF_READONLY, MF_SWAP, and MF_BUSY. If mtpt is
+ * non-NULL, the directory where the device is mounted is copied to where mtpt
+ * is pointing, up to mtlen characters.
+ */
+#ifdef __TURBOC__
+ #pragma argsused
+#endif
+int check_mount_point(const char *device, int *mount_flags,
+ char *mtpt, int mtlen)
+{
+ struct stat st_buf;
+ int retval = 0;
+ int fd;
+
+ if (is_swap_device(device)) {
+ *mount_flags = MF_MOUNTED | MF_SWAP;
+ strncpy(mtpt, "<swap>", mtlen);
+ } else {
+#ifdef HAVE_MNTENT_H
+ retval = check_mntent(device, mount_flags, mtpt, mtlen);
+#else
+#ifdef HAVE_GETMNTINFO
+ retval = check_getmntinfo(device, mount_flags, mtpt, mtlen);
+#else
+#ifdef __GNUC__
+ #warning "Can't use getmntent or getmntinfo to check for mounted filesystems!"
+#endif
+ *mount_flags = 0;
+#endif /* HAVE_GETMNTINFO */
+#endif /* HAVE_MNTENT_H */
+ }
+ if (retval)
+ return retval;
+
+#ifdef __linux__ /* This only works on Linux 2.6+ systems */
+ if ((stat(device, &st_buf) != 0) ||
+ !S_ISBLK(st_buf.st_mode))
+ return 0;
+ fd = open(device, O_RDONLY | O_EXCL);
+ 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
+int main(int argc, char **argv)
+{
+ int flags = 0;
+ char devname[PATH_MAX];
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s device\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (check_mount_point(argv[1], &flags, devname, sizeof(devname)) == 0 &&
+ (flags & MF_MOUNTED)) {
+ if (flags & MF_SWAP)
+ printf("used swap device\n");
+ else
+ printf("mounted on %s\n", devname);
+ return EXIT_SUCCESS;
+ }
+
+ printf("not mounted\n");
+ return EXIT_FAILURE;
+}
+#endif /* DEBUG */
diff --git a/lib/langinfo.c b/lib/langinfo.c
new file mode 100644
index 0000000..deeab9b
--- /dev/null
+++ b/lib/langinfo.c
@@ -0,0 +1,121 @@
+/*
+ * This is callback solution for systems without nl_langinfo(), this function
+ * returns hardcoded and on locale setting independed value.
+ *
+ * See langinfo.h man page for more details.
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ */
+#include "nls.h"
+
+char *langinfo_fallback(nl_item item)
+{
+ switch (item) {
+ case CODESET:
+ return "ISO-8859-1";
+ case THOUSEP:
+ return ",";
+ case D_T_FMT:
+ case ERA_D_T_FMT:
+ return "%a %b %e %H:%M:%S %Y";
+ case D_FMT:
+ case ERA_D_FMT:
+ return "%m/%d/%y";
+ case T_FMT:
+ case ERA_T_FMT:
+ return "%H:%M:%S";
+ case T_FMT_AMPM:
+ return "%I:%M:%S %p";
+ case AM_STR:
+ return "AM";
+ case PM_STR:
+ return "PM";
+ case DAY_1:
+ return "Sunday";
+ case DAY_2:
+ return "Monday";
+ case DAY_3:
+ return "Tuesday";
+ case DAY_4:
+ return "Wednesday";
+ case DAY_5:
+ return "Thursday";
+ case DAY_6:
+ return "Friday";
+ case DAY_7:
+ return "Saturday";
+ case ABDAY_1:
+ return "Sun";
+ case ABDAY_2:
+ return "Mon";
+ case ABDAY_3:
+ return "Tue";
+ case ABDAY_4:
+ return "Wed";
+ case ABDAY_5:
+ return "Thu";
+ case ABDAY_6:
+ return "Fri";
+ case ABDAY_7:
+ return "Sat";
+ case MON_1:
+ return "January";
+ case MON_2:
+ return "February";
+ case MON_3:
+ return "March";
+ case MON_4:
+ return "April";
+ case MON_5:
+ return "May";
+ case MON_6:
+ return "June";
+ case MON_7:
+ return "July";
+ case MON_8:
+ return "August";
+ case MON_9:
+ return "September";
+ case MON_10:
+ return "October";
+ case MON_11:
+ return "November";
+ case MON_12:
+ return "December";
+ case ABMON_1:
+ return "Jan";
+ case ABMON_2:
+ return "Feb";
+ case ABMON_3:
+ return "Mar";
+ case ABMON_4:
+ return "Apr";
+ case ABMON_5:
+ return "May";
+ case ABMON_6:
+ return "Jun";
+ case ABMON_7:
+ return "Jul";
+ case ABMON_8:
+ return "Aug";
+ case ABMON_9:
+ return "Sep";
+ case ABMON_10:
+ return "Oct";
+ case ABMON_11:
+ return "Nov";
+ case ABMON_12:
+ return "Dec";
+ case ALT_DIGITS:
+ return "\0\0\0\0\0\0\0\0\0\0";
+ case CRNCYSTR:
+ return "-";
+ case YESEXPR:
+ return "^[yY]";
+ case NOEXPR:
+ return "^[nN]";
+ default:
+ return "";
+ }
+}
+
diff --git a/lib/linux_version.c b/lib/linux_version.c
new file mode 100644
index 0000000..2bcc2cc
--- /dev/null
+++ b/lib/linux_version.c
@@ -0,0 +1,25 @@
+#include <stdio.h>
+#include <sys/utsname.h>
+
+#include "linux_version.h"
+
+int get_linux_version (void)
+{
+ static int kver = -1;
+ struct utsname uts;
+ int major = 0;
+ int minor = 0;
+ int teeny = 0;
+ int n;
+
+ if (kver != -1)
+ return kver;
+ if (uname (&uts))
+ return kver = 0;
+
+ n = sscanf(uts.release, "%d.%d.%d", &major, &minor, &teeny);
+ if (n < 1 || n > 3)
+ return kver = 0;
+
+ return kver = KERNEL_VERSION(major, minor, teeny);
+}
diff --git a/lib/loopdev.c b/lib/loopdev.c
new file mode 100644
index 0000000..a9f6df2
--- /dev/null
+++ b/lib/loopdev.c
@@ -0,0 +1,1592 @@
+/*
+ * Copyright (C) 2011 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 <sys/sysmacros.h>
+#include <inttypes.h>
+#include <dirent.h>
+#include <linux/posix_types.h>
+
+#include "linux_version.h"
+#include "c.h"
+#include "sysfs.h"
+#include "pathnames.h"
+#include "loopdev.h"
+#include "canonicalize.h"
+#include "at.h"
+
+#define CONFIG_LOOPDEV_DEBUG
+
+#ifdef CONFIG_LOOPDEV_DEBUG
+# include <stdarg.h>
+
+# define DBG(l,x) do { \
+ if ((l)->debug) {\
+ fprintf(stderr, "loopdev: [%p]: ", (l)); \
+ x; \
+ } \
+ } while(0)
+
+static inline void __attribute__ ((__format__ (__printf__, 1, 2)))
+loopdev_debug(const char *mesg, ...)
+{
+ va_list ap;
+ va_start(ap, mesg);
+ vfprintf(stderr, mesg, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+}
+
+#else /* !CONFIG_LOOPDEV_DEBUG */
+# define DBG(m,x) do { ; } while(0)
+#endif
+
+/*
+ * 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>)
+ *
+ * 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);
+ lc->fd = -1;
+ lc->mode = 0;
+ lc->has_info = 0;
+ lc->info_failed = 0;
+ *lc->device = '\0';
+ memset(&lc->info, 0, sizeof(lc->info));
+
+ /* set new */
+ if (device) {
+ if (*device != '/') {
+ const char *dir = _PATH_DEV;
+
+ /* compose device name for /dev/loop<n> or /dev/loop/<n> */
+ if (lc->flags & LOOPDEV_FL_DEVSUBDIR) {
+ if (strlen(device) < 5)
+ return -1;
+ device += 4;
+ dir = _PATH_DEV_LOOP "/"; /* _PATH_DEV uses tailing slash */
+ }
+ snprintf(lc->device, sizeof(lc->device), "%s%s",
+ dir, device);
+ } else {
+ strncpy(lc->device, device, sizeof(lc->device));
+ lc->device[sizeof(lc->device) - 1] = '\0';
+ }
+ DBG(lc, loopdev_debug("%s successfully assigned", device));
+ }
+
+ sysfs_deinit(&lc->sysfs);
+ return 0;
+}
+
+int loopcxt_has_device(struct loopdev_cxt *lc)
+{
+ return lc && *lc->device;
+}
+
+/*
+ * @lc: context
+ * @flags: LOOPDEV_FL_* flags
+ *
+ * Initilize 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 expection 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;
+
+ memcpy(lc, &dummy, sizeof(dummy));
+ lc->flags = flags;
+
+ rc = loopcxt_set_device(lc, NULL);
+ if (rc)
+ return rc;
+
+ 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;
+
+ if (!(lc->flags & LOOPDEV_FL_CONTROL) && !stat(_PATH_DEV_LOOPCTL, &st))
+ lc->flags |= LOOPDEV_FL_CONTROL;
+
+ return 0;
+}
+
+/*
+ * @lc: context
+ *
+ * Deinitialize loop context
+ */
+void loopcxt_deinit(struct loopdev_cxt *lc)
+{
+ int errsv = errno;
+
+ if (!lc)
+ return;
+
+ DBG(lc, loopdev_debug("de-initialize"));
+
+ free(lc->filename);
+ lc->filename = NULL;
+
+ ignore_result( loopcxt_set_device(lc, NULL) );
+ loopcxt_deinit_iterator(lc);
+
+ errno = errsv;
+}
+
+/*
+ * @lc: context
+ * @enable: TRUE/FALSE
+ *
+ * Enabled/disables debug messages
+ */
+void loopcxt_enable_debug(struct loopdev_cxt *lc, int enable)
+{
+ if (lc)
+ lc->debug = enable ? 1 : 0;
+}
+
+/*
+ * @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 : NULL;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns pointer to the sysfs context (see lib/sysfs.c)
+ */
+struct sysfs_cxt *loopcxt_get_sysfs(struct loopdev_cxt *lc)
+{
+ if (!lc || !*lc->device || (lc->flags & LOOPDEV_FL_NOSYSFS))
+ return NULL;
+
+ if (!lc->sysfs.devno) {
+ dev_t devno = sysfs_devname_to_devno(lc->device, NULL);
+ if (!devno) {
+ DBG(lc, loopdev_debug("sysfs: failed devname to devno"));
+ return NULL;
+ }
+ if (sysfs_init(&lc->sysfs, devno, NULL)) {
+ DBG(lc, loopdev_debug("sysfs: init failed"));
+ return NULL;
+ }
+ }
+
+ 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);
+ DBG(lc, loopdev_debug("open %s", lc->fd < 0 ? "failed" : "ok"));
+ }
+ return lc->fd;
+}
+
+int loopcxt_set_fd(struct loopdev_cxt *lc, int fd, int mode)
+{
+ if (!lc)
+ return -EINVAL;
+
+ lc->fd = fd;
+ lc->mode = mode;
+ return 0;
+}
+
+/*
+ * @lc: context
+ * @flags: LOOPITER_FL_* flags
+ *
+ * Iterator allows to scan list of the free or used loop devices.
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_init_iterator(struct loopdev_cxt *lc, int flags)
+{
+ struct loopdev_iter *iter;
+ struct stat st;
+
+ if (!lc)
+ return -EINVAL;
+
+ DBG(lc, loopdev_debug("iter: initialize"));
+
+ iter = &lc->iter;
+
+ /* 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;
+
+ DBG(lc, loopdev_debug("iter: de-initialize"));
+
+ iter = &lc->iter;
+
+ free(iter->minors);
+ if (iter->proc)
+ fclose(iter->proc);
+ if (iter->sysblock)
+ closedir(iter->sysblock);
+ iter->minors = NULL;
+ iter->proc = NULL;
+ iter->sysblock = NULL;
+ iter->done = 1;
+ return 0;
+}
+
+/*
+ * Same as loopcxt_set_device, but also checks if the device is
+ * associeted with any file.
+ *
+ * Returns: <0 on error, 0 on success, 1 device does not match with
+ * LOOPITER_FL_{USED,FREE} flags.
+ */
+static int loopiter_set_device(struct loopdev_cxt *lc, const char *device)
+{
+ int rc = loopcxt_set_device(lc, device);
+ int used;
+
+ if (rc)
+ return rc;
+
+ if (!(lc->iter.flags & LOOPITER_FL_USED) &&
+ !(lc->iter.flags & LOOPITER_FL_FREE))
+ return 0; /* caller does not care about device status */
+
+ 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(lc, loopdev_debug("iter: unset device"));
+ ignore_result( loopcxt_set_device(lc, NULL) );
+ return 1;
+}
+
+static int cmpnum(const void *p1, const void *p2)
+{
+ return (((* (int *) p1) > (* (int *) p2)) -
+ ((* (int *) p1) < (* (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;
+ 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;
+
+ 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);
+ 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(lc, loopdev_debug("iter: scan /proc/partitions"));
+
+ if (!iter->proc)
+ iter->proc = fopen(_PATH_PROC_PARTITIONS, "r");
+ 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(lc, loopdev_debug("iter: check %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(lc, loopdev_debug("iter: scan /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[256];
+ struct stat st;
+
+ DBG(lc, loopdev_debug("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 (fstat_at(fd, _PATH_SYS_BLOCK, 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;
+
+ DBG(lc, loopdev_debug("iter: next"));
+
+ iter = &lc->iter;
+ if (iter->done)
+ return 1;
+
+ /* 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) {
+ 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) {
+ iter->nminors = (lc->flags & LOOPDEV_FL_DEVSUBDIR) ?
+ loop_scandir(_PATH_DEV_LOOP, &iter->minors, 0) :
+ loop_scandir(_PATH_DEV, &iter->minors, 1);
+ iter->ncur = -1;
+ }
+ for (++iter->ncur; iter->ncur < iter->nminors; iter->ncur++) {
+ char name[16];
+ snprintf(name, sizeof(name), "loop%d", iter->minors[iter->ncur]);
+
+ if (loopiter_set_device(lc, name) == 0)
+ return 0;
+ }
+done:
+ loopcxt_deinit_iterator(lc);
+ return 1;
+}
+
+/*
+ * @device: path to device
+ */
+int is_loopdev(const char *device)
+{
+ struct stat st;
+
+ if (!device)
+ return 0;
+
+ return (stat(device, &st) == 0 &&
+ S_ISBLK(st.st_mode) &&
+ major(st.st_rdev) == LOOPDEV_MAJOR);
+}
+
+/*
+ * @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)
+ return NULL;
+ if (lc->has_info)
+ return &lc->info;
+
+ fd = loopcxt_get_fd(lc);
+ if (fd < 0)
+ return NULL;
+
+ if (ioctl(fd, LOOP_GET_STATUS64, &lc->info) == 0) {
+ lc->has_info = 1;
+ lc->info_failed = 0;
+ DBG(lc, loopdev_debug("reading loop_info64 OK"));
+ return &lc->info;
+ } else {
+ lc->info_failed = 1;
+ DBG(lc, loopdev_debug("reading loop_info64 FAILED"));
+ }
+
+ return NULL;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns (allocated) string with path to the file assicieted
+ * with the current loop device.
+ */
+char *loopcxt_get_backing_file(struct loopdev_cxt *lc)
+{
+ struct sysfs_cxt *sysfs = loopcxt_get_sysfs(lc);
+ char *res = NULL;
+
+ if (sysfs)
+ /*
+ * This is always preffered, the loop_info64
+ * has too small buffer for the filename.
+ */
+ res = sysfs_strdup(sysfs, "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(lc, loopdev_debug("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 sysfs_cxt *sysfs = loopcxt_get_sysfs(lc);
+ int rc = -EINVAL;
+
+ if (sysfs)
+ rc = sysfs_read_u64(sysfs, "loop/offset", offset);
+
+ if (rc && loopcxt_ioctl_enabled(lc)) {
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+ if (lo) {
+ if (offset)
+ *offset = lo->lo_offset;
+ rc = 0;
+ }
+ }
+
+ DBG(lc, loopdev_debug("get_offset [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 sysfs_cxt *sysfs = loopcxt_get_sysfs(lc);
+ int rc = -EINVAL;
+
+ if (sysfs)
+ rc = sysfs_read_u64(sysfs, "loop/sizelimit", size);
+
+ if (rc && loopcxt_ioctl_enabled(lc)) {
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+ if (lo) {
+ if (size)
+ *size = lo->lo_sizelimit;
+ rc = 0;
+ }
+ }
+
+ DBG(lc, loopdev_debug("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 = -EINVAL;
+
+ if (lo) {
+ if (type)
+ *type = lo->lo_encrypt_type;
+ rc = 0;
+ }
+ DBG(lc, loopdev_debug("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(lc, loopdev_debug("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 = -EINVAL;
+
+ if (lo) {
+ if (devno)
+ *devno = lo->lo_device;
+ rc = 0;
+ }
+ DBG(lc, loopdev_debug("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 = -EINVAL;
+
+ if (lo) {
+ if (ino)
+ *ino = lo->lo_inode;
+ rc = 0;
+ }
+ DBG(lc, loopdev_debug("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 paremeter 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");
+ 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
+ * scannig is enabled for all loop devices.
+ */
+int loopcxt_is_partscan(struct loopdev_cxt *lc)
+{
+ struct sysfs_cxt *sysfs = loopcxt_get_sysfs(lc);
+
+ if (sysfs) {
+ /* kernel >= 3.2 */
+ int fl;
+ if (sysfs_read_int(sysfs, "loop/partscan", &fl) == 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 sysfs_cxt *sysfs = loopcxt_get_sysfs(lc);
+
+ if (sysfs) {
+ int fl;
+ if (sysfs_read_int(sysfs, "loop/autoclear", &fl) == 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 sysfs_cxt *sysfs = loopcxt_get_sysfs(lc);
+
+ if (sysfs) {
+ int fl;
+ if (sysfs_read_int(sysfs, "ro", &fl) == 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
+ * @st: backing file stat or NULL
+ * @backing_file: filename
+ * @offset: offset
+ * @flags: LOOPDEV_FL_OFFSET if @offset should not be ignored
+ *
+ * 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().
+ *
+ * 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,
+ int flags)
+{
+ ino_t ino;
+ dev_t dev;
+
+ if (!lc)
+ return 0;
+
+ DBG(lc, loopdev_debug("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;
+
+ return loopcxt_get_offset(lc, &off) == 0 && off == offset;
+ }
+ return 1;
+}
+
+/*
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ */
+int loopcxt_set_offset(struct loopdev_cxt *lc, uint64_t offset)
+{
+ if (!lc)
+ return -EINVAL;
+ lc->info.lo_offset = offset;
+
+ DBG(lc, loopdev_debug("set offset=%jd", offset));
+ return 0;
+}
+
+/*
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ */
+int loopcxt_set_sizelimit(struct loopdev_cxt *lc, uint64_t sizelimit)
+{
+ if (!lc)
+ return -EINVAL;
+ lc->info.lo_sizelimit = sizelimit;
+
+ DBG(lc, loopdev_debug("set sizelimit=%jd", sizelimit));
+ return 0;
+}
+
+/*
+ * @lc: context
+ * @flags: kernel LO_FLAGS_{READ_ONLY,USE_AOPS,AUTOCLEAR} flags
+ *
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int loopcxt_set_flags(struct loopdev_cxt *lc, uint32_t flags)
+{
+ if (!lc)
+ return -EINVAL;
+ lc->info.lo_flags = flags;
+
+ DBG(lc, loopdev_debug("set flags=%u", (unsigned) flags));
+ return 0;
+}
+
+/*
+ * @lc: context
+ * @filename: backing file path (the path will be canonicalized)
+ *
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int loopcxt_set_backing_file(struct loopdev_cxt *lc, const char *filename)
+{
+ if (!lc)
+ return -EINVAL;
+
+ lc->filename = canonicalize_path(filename);
+ if (!lc->filename)
+ return -errno;
+
+ strncpy((char *)lc->info.lo_file_name, lc->filename, LO_NAME_SIZE);
+ lc->info.lo_file_name[LO_NAME_SIZE- 1] = '\0';
+
+ DBG(lc, loopdev_debug("set backing file=%s", lc->info.lo_file_name));
+ return 0;
+}
+
+static int digits_only(const char *s)
+{
+ while (*s)
+ if (!isdigit(*s++))
+ return 0;
+ return 1;
+}
+
+/*
+ * @lc: context
+ * @encryption: encryption name / type (see lopsetup man page)
+ * @password
+ *
+ * Note that the encryption functionality is deprecated an unmaintained. Use
+ * cryptsetup (it also supports AES-loops).
+ *
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int loopcxt_set_encryption(struct loopdev_cxt *lc,
+ const char *encryption,
+ const char *password)
+{
+ if (!lc)
+ return -EINVAL;
+
+ DBG(lc, loopdev_debug("setting encryption '%s'", encryption));
+
+ if (encryption && *encryption) {
+ if (digits_only(encryption)) {
+ lc->info.lo_encrypt_type = atoi(encryption);
+ } else {
+ lc->info.lo_encrypt_type = LO_CRYPT_CRYPTOAPI;
+ snprintf((char *)lc->info.lo_crypt_name, LO_NAME_SIZE,
+ "%s", encryption);
+ }
+ }
+
+ switch (lc->info.lo_encrypt_type) {
+ case LO_CRYPT_NONE:
+ lc->info.lo_encrypt_key_size = 0;
+ break;
+ default:
+ DBG(lc, loopdev_debug("setting encryption key"));
+ memset(lc->info.lo_encrypt_key, 0, LO_KEY_SIZE);
+ strncpy((char *)lc->info.lo_encrypt_key, password, LO_KEY_SIZE);
+ lc->info.lo_encrypt_key[LO_KEY_SIZE - 1] = '\0';
+ lc->info.lo_encrypt_key_size = LO_KEY_SIZE;
+ break;
+ }
+
+ DBG(lc, loopdev_debug("encryption successfully set"));
+ return 0;
+}
+
+/*
+ * @cl: context
+ *
+ * Associate the current device (see loopcxt_{set,get}_device()) with
+ * a file (see loopcxt_set_backing_file()).
+ *
+ * The device is initialized read-write by default. If you want read-only
+ * device then set LO_FLAGS_READ_ONLY by loopcxt_set_flags(). The LOOPDEV_FL_*
+ * flags are ignored and modified according to LO_FLAGS_*.
+ *
+ * If the device is already open by loopcxt_get_fd() then this setup device
+ * function will re-open the device to fix read/write mode.
+ *
+ * The device is also initialized read-only if the backing file is not
+ * possible to open read-write (e.g. read-only FS).
+ *
+ * Returns: <0 on error, 0 on success.
+ */
+int loopcxt_setup_device(struct loopdev_cxt *lc)
+{
+ int file_fd, dev_fd, mode = O_RDWR, rc = -1;
+
+ if (!lc || !*lc->device || !lc->filename)
+ return -EINVAL;
+
+ DBG(lc, loopdev_debug("device setup requested"));
+
+ /*
+ * Open backing file and device
+ */
+ if (lc->info.lo_flags & LO_FLAGS_READ_ONLY)
+ mode = O_RDONLY;
+
+ if ((file_fd = open(lc->filename, mode)) < 0) {
+ if (mode != O_RDONLY && (errno == EROFS || errno == EACCES))
+ file_fd = open(lc->filename, mode = O_RDONLY);
+
+ if (file_fd < 0) {
+ DBG(lc, loopdev_debug("open backing file failed: %m"));
+ return -errno;
+ }
+ }
+ DBG(lc, loopdev_debug("setup: backing file open: OK"));
+
+ if (lc->fd != -1 && lc->mode != mode) {
+ close(lc->fd);
+ lc->fd = -1;
+ lc->mode = 0;
+ }
+
+ if (mode == O_RDONLY) {
+ lc->flags |= LOOPDEV_FL_RDONLY; /* open() mode */
+ lc->info.lo_flags |= LO_FLAGS_READ_ONLY; /* kernel loopdev mode */
+ } else {
+ lc->flags |= LOOPDEV_FL_RDWR; /* open() mode */
+ lc->info.lo_flags &= ~LO_FLAGS_READ_ONLY;
+ lc->flags &= ~LOOPDEV_FL_RDONLY;
+ }
+
+ dev_fd = loopcxt_get_fd(lc);
+ if (dev_fd < 0) {
+ rc = -errno;
+ goto err;
+ }
+
+ DBG(lc, loopdev_debug("setup: device open: OK"));
+
+ /*
+ * Set FD
+ */
+ if (ioctl(dev_fd, LOOP_SET_FD, file_fd) < 0) {
+ rc = -errno;
+ DBG(lc, loopdev_debug("LOOP_SET_FD failed: %m"));
+ goto err;
+ }
+
+ DBG(lc, loopdev_debug("setup: LOOP_SET_FD: OK"));
+
+ close(file_fd);
+ file_fd = -1;
+
+ if (ioctl(dev_fd, LOOP_SET_STATUS64, &lc->info)) {
+ DBG(lc, loopdev_debug("LOOP_SET_STATUS64 failed: %m"));
+ goto err;
+ }
+
+ DBG(lc, loopdev_debug("setup: LOOP_SET_STATUS64: OK"));
+
+ memset(&lc->info, 0, sizeof(lc->info));
+ lc->has_info = 0;
+ lc->info_failed = 0;
+
+ DBG(lc, loopdev_debug("setup success [rc=0]"));
+ return 0;
+err:
+ if (file_fd >= 0)
+ close(file_fd);
+ if (dev_fd >= 0)
+ ioctl(dev_fd, LOOP_CLR_FD, 0);
+
+ DBG(lc, loopdev_debug("setup failed [rc=%d]", rc));
+ return rc;
+}
+
+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(lc, loopdev_debug("LOOP_CLR_FD failed: %m"));
+ return -errno;
+ }
+
+ DBG(lc, loopdev_debug("device removed"));
+ return 0;
+}
+
+/*
+ * 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(lc, loopdev_debug("find_unused requested"));
+
+ if (lc->flags & LOOPDEV_FL_CONTROL) {
+ int ctl = open(_PATH_DEV_LOOPCTL, O_RDWR);
+
+ 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);
+ }
+ if (ctl >= 0)
+ close(ctl);
+ DBG(lc, loopdev_debug("find_unused by loop-control [rc=%d]", rc));
+ }
+
+ if (rc < 0) {
+ rc = loopcxt_init_iterator(lc, LOOPITER_FL_FREE);
+ if (rc)
+ return rc;
+
+ rc = loopcxt_next(lc);
+ loopcxt_deinit_iterator(lc);
+ DBG(lc, loopdev_debug("find_unused by scan [rc=%d]", rc));
+ }
+ return rc;
+}
+
+
+
+/*
+ * Return: TRUE/FALSE
+ */
+int loopdev_is_autoclear(const char *device)
+{
+ struct loopdev_cxt lc;
+ int rc;
+
+ if (!device)
+ return 0;
+
+ rc = loopcxt_init(&lc, 0);
+ if (!rc)
+ rc = loopcxt_set_device(&lc, device);
+ if (!rc)
+ rc = loopcxt_is_autoclear(&lc);
+
+ loopcxt_deinit(&lc);
+ return rc;
+}
+
+char *loopdev_get_backing_file(const char *device)
+{
+ struct loopdev_cxt lc;
+ char *res = NULL;
+
+ if (!device)
+ return NULL;
+ if (loopcxt_init(&lc, 0))
+ return NULL;
+ if (loopcxt_set_device(&lc, device) == 0)
+ res = loopcxt_get_backing_file(&lc);
+
+ loopcxt_deinit(&lc);
+ return res;
+}
+
+/*
+ * Returns: TRUE/FALSE
+ */
+int loopdev_is_used(const char *device, const char *filename,
+ uint64_t offset, 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, 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, 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, flags))
+ break;
+ }
+
+ loopcxt_deinit_iterator(lc);
+ return rc;
+}
+
+/*
+ * Returns allocated string with device name
+ */
+char *loopdev_find_by_backing_file(const char *filename, uint64_t offset, 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, flags))
+ res = loopcxt_strdup_device(&lc);
+ loopcxt_deinit(&lc);
+
+ return res;
+}
+
+/*
+ * Returns number of loop devices associated with @file, if only one loop
+ * device is associeted with the given @filename and @loopdev is not NULL then
+ * @loopdev returns name of the device.
+ */
+int loopdev_count_by_backing_file(const char *filename, char **loopdev)
+{
+ struct loopdev_cxt lc;
+ int count = 0, rc;
+
+ if (!filename)
+ return -1;
+
+ rc = loopcxt_init(&lc, 0);
+ if (rc)
+ return rc;
+ if (loopcxt_init_iterator(&lc, LOOPITER_FL_USED))
+ return -1;
+
+ while(loopcxt_next(&lc) == 0) {
+ char *backing = loopcxt_get_backing_file(&lc);
+
+ if (!backing || strcmp(backing, filename)) {
+ free(backing);
+ continue;
+ }
+
+ free(backing);
+ if (loopdev && count == 0)
+ *loopdev = loopcxt_strdup_device(&lc);
+ count++;
+ }
+
+ loopcxt_deinit(&lc);
+
+ if (loopdev && count > 1) {
+ free(*loopdev);
+ *loopdev = NULL;
+ }
+ return count;
+}
+
+
+#ifdef TEST_PROGRAM_LOOPDEV
+#include <errno.h>
+#include <err.h>
+
+static void test_loop_info(const char *device, int flags, int debug)
+{
+ struct loopdev_cxt lc;
+ char *p;
+ uint64_t u64;
+
+ if (loopcxt_init(&lc, flags))
+ return;
+ loopcxt_enable_debug(&lc, debug);
+
+ if (loopcxt_set_device(&lc, device))
+ err(EXIT_FAILURE, "failed to set device");
+
+ p = loopcxt_get_backing_file(&lc);
+ printf("\tBACKING FILE: %s\n", p);
+ free(p);
+
+ if (loopcxt_get_offset(&lc, &u64) == 0)
+ printf("\tOFFSET: %jd\n", u64);
+
+ if (loopcxt_get_sizelimit(&lc, &u64) == 0)
+ printf("\tSIZE LIMIT: %jd\n", u64);
+
+ printf("\tAUTOCLEAR: %s\n", loopcxt_is_autoclear(&lc) ? "YES" : "NOT");
+
+ loopcxt_deinit(&lc);
+}
+
+static void test_loop_scan(int flags, int debug)
+{
+ struct loopdev_cxt lc;
+ int rc;
+
+ if (loopcxt_init(&lc, 0))
+ return;
+ loopcxt_enable_debug(&lc, debug);
+
+ if (loopcxt_init_iterator(&lc, flags))
+ err(EXIT_FAILURE, "iterator initlization failed");
+
+ while((rc = loopcxt_next(&lc)) == 0) {
+ const char *device = loopcxt_get_device(&lc);
+
+ if (flags & LOOPITER_FL_USED) {
+ char *backing = loopcxt_get_backing_file(&lc);
+ printf("\t%s: %s\n", device, backing);
+ free(backing);
+ } else
+ printf("\t%s\n", device);
+ }
+
+ if (rc < 0)
+ err(EXIT_FAILURE, "loopdevs scanning failed");
+
+ loopcxt_deinit(&lc);
+}
+
+static int test_loop_setup(const char *filename, const char *device, int debug)
+{
+ struct loopdev_cxt lc;
+ int rc;
+
+ rc = loopcxt_init(&lc, 0);
+ if (rc)
+ return rc;
+ loopcxt_enable_debug(&lc, debug);
+
+ if (device) {
+ rc = loopcxt_set_device(&lc, device);
+ if (rc)
+ err(EXIT_FAILURE, "failed to set device: %s", device);
+ }
+
+ do {
+ if (!device) {
+ rc = loopcxt_find_unused(&lc);
+ if (rc)
+ err(EXIT_FAILURE, "failed to find unused device");
+ printf("Trying to use '%s'\n", loopcxt_get_device(&lc));
+ }
+
+ if (loopcxt_set_backing_file(&lc, filename))
+ err(EXIT_FAILURE, "failed to set backing file");
+
+ rc = loopcxt_setup_device(&lc);
+ if (rc == 0)
+ break; /* success */
+
+ if (device || rc != -EBUSY)
+ err(EXIT_FAILURE, "failed to setup device for %s",
+ lc.filename);
+
+ printf("device stolen...trying again\n");
+ } while (1);
+
+ loopcxt_deinit(&lc);
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ int dbg;
+
+ if (argc < 2)
+ goto usage;
+
+ dbg = getenv("LOOPDEV_DEBUG") == NULL ? 0 : 1;
+
+ if (argc == 3 && strcmp(argv[1], "--info") == 0) {
+ printf("---sysfs & ioctl:---\n");
+ test_loop_info(argv[2], 0, dbg);
+ printf("---sysfs only:---\n");
+ test_loop_info(argv[2], LOOPDEV_FL_NOIOCTL, dbg);
+ printf("---ioctl only:---\n");
+ test_loop_info(argv[2], LOOPDEV_FL_NOSYSFS, dbg);
+
+ } else if (argc == 2 && strcmp(argv[1], "--used") == 0) {
+ printf("---all used devices---\n");
+ test_loop_scan(LOOPITER_FL_USED, dbg);
+
+ } else if (argc == 2 && strcmp(argv[1], "--free") == 0) {
+ printf("---all free devices---\n");
+ test_loop_scan(LOOPITER_FL_FREE, dbg);
+
+ } else if (argc >= 3 && strcmp(argv[1], "--setup") == 0) {
+ test_loop_setup(argv[2], argv[3], dbg);
+
+ } else if (argc == 3 && strcmp(argv[1], "--delete") == 0) {
+ if (loopdev_delete(argv[2]))
+ errx(EXIT_FAILURE, "failed to deinitialize device %s", argv[2]);
+ } else
+ goto usage;
+
+ return EXIT_SUCCESS;
+
+usage:
+ errx(EXIT_FAILURE, "usage: \n"
+ " %1$s --info <device>\n"
+ " %1$s --free\n"
+ " %1$s --used\n"
+ " %1$s --setup <filename> [<device>]\n"
+ " %1$s --delete\n",
+ argv[0]);
+}
+
+#endif /* TEST_PROGRAM */
diff --git a/lib/mangle.c b/lib/mangle.c
new file mode 100644
index 0000000..5236e97
--- /dev/null
+++ b/lib/mangle.c
@@ -0,0 +1,166 @@
+/*
+ * Functions for \oct encoding used in mtab/fstab/swaps/etc.
+ *
+ * Based on code from mount(8).
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "mangle.h"
+#include "c.h"
+
+#define isoctal(a) (((a) & ~7) == '0')
+
+#define from_hex(c) (isdigit(c) ? c - '0' : tolower(c) - 'a' + 10)
+
+#define is_unwanted_char(x) (strchr(" \t\n\\", (unsigned int) x) != NULL)
+
+
+char *mangle(const char *s)
+{
+ char *ss, *sp;
+
+ if (!s)
+ return NULL;
+
+ ss = sp = malloc(4 * strlen(s) + 1);
+ if (!sp)
+ return NULL;
+ while(1) {
+ if (!*s) {
+ *sp = '\0';
+ break;
+ }
+ if (is_unwanted_char(*s)) {
+ *sp++ = '\\';
+ *sp++ = '0' + ((*s & 0300) >> 6);
+ *sp++ = '0' + ((*s & 070) >> 3);
+ *sp++ = '0' + (*s & 07);
+ } else
+ *sp++ = *s;
+ s++;
+ }
+ return ss;
+}
+
+
+void unmangle_to_buffer(const char *s, char *buf, size_t len)
+{
+ size_t sz = 0;
+
+ if (!s)
+ return;
+
+ while(*s && sz < len - 1) {
+ if (*s == '\\' && sz + 3 < len - 1 && isoctal(s[1]) &&
+ isoctal(s[2]) && isoctal(s[3])) {
+
+ *buf++ = 64*(s[1] & 7) + 8*(s[2] & 7) + (s[3] & 7);
+ s += 4;
+ sz += 4;
+ } else {
+ *buf++ = *s++;
+ sz++;
+ }
+ }
+ *buf = '\0';
+}
+
+void unhexmangle_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 && 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';
+}
+
+static inline char *skip_nonspaces(const char *s)
+{
+ while (*s && !(*s == ' ' || *s == '\t'))
+ s++;
+ return (char *) s;
+}
+
+/*
+ * Returns mallocated buffer or NULL in case of error.
+ */
+char *unmangle(const char *s, char **end)
+{
+ char *buf;
+ 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
+#include <errno.h>
+int main(int argc, char *argv[])
+{
+ char *p = NULL;
+ if (argc < 3) {
+ fprintf(stderr, "usage: %s --mangle|unmangle <string>\n",
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+
+ if (!strcmp(argv[1], "--mangle")) {
+ p = mangle(argv[2]);
+ printf("mangled: '%s'\n", p);
+ free(p);
+ }
+
+ else if (!strcmp(argv[1], "--unmangle")) {
+ char *x = unmangle(argv[2], NULL);
+
+ if (x) {
+ printf("unmangled: '%s'\n", x);
+ free(x);
+ }
+
+ x = strdup(argv[2]);
+ unmangle_to_buffer(x, x, strlen(x) + 1);
+
+ if (x) {
+ printf("self-unmangled: '%s'\n", x);
+ free(x);
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM */
diff --git a/lib/match.c b/lib/match.c
new file mode 100644
index 0000000..9be82b0
--- /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) && !strncmp(p+2, type, len) &&
+ (p[len+2] == 0 || p[len+2] == ','))
+ return 0;
+ if (strncmp(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..d97bbd5
--- /dev/null
+++ b/lib/mbsalign.c
@@ -0,0 +1,290 @@
+/* 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 General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Pádraig Brady. */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <limits.h>
+
+#include "c.h"
+#include "mbsalign.h"
+#include "widechar.h"
+
+
+#ifdef HAVE_WIDECHAR
+/* Replace non printable chars.
+ Note \t and \n etc. are non printable.
+ Return 1 if replacement made, 0 otherwise. */
+
+static bool
+wc_ensure_printable (wchar_t *wchars)
+{
+ bool replaced = false;
+ wchar_t *wc = wchars;
+ while (*wc)
+ {
+ if (!iswprint ((wint_t) *wc))
+ {
+ *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
+ replaced = true;
+ }
+ wc++;
+ }
+ return replaced;
+}
+
+/* Truncate wchar string to width cells.
+ * Returns number of cells used. */
+
+static size_t
+wc_truncate (wchar_t *wc, size_t width)
+{
+ size_t cells = 0;
+ int next_cells = 0;
+
+ while (*wc)
+ {
+ next_cells = wcwidth (*wc);
+ if (next_cells == -1) /* non printable */
+ {
+ *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
+ next_cells = 1;
+ }
+ if (cells + next_cells > width)
+ break;
+ cells += next_cells;
+ wc++;
+ }
+ *wc = L'\0';
+ return cells;
+}
+
+/* FIXME: move this function to gnulib as it's missing on:
+ OpenBSD 3.8, IRIX 5.3, Solaris 2.5.1, mingw, BeOS */
+
+static int
+rpl_wcswidth (const wchar_t *s, size_t n)
+{
+ int ret = 0;
+
+ while (n-- > 0 && *s != L'\0')
+ {
+ int nwidth = wcwidth (*s++);
+ if (nwidth == -1) /* non printable */
+ return -1;
+ if (ret > (INT_MAX - nwidth)) /* overflow */
+ return -1;
+ ret += nwidth;
+ }
+
+ return ret;
+}
+#endif
+
+/* 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 = malloc((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 (*width < 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)
+{
+ /* FIXME: Should we pad with "figure space" (\u2007)
+ if non ascii data present? */
+ while (n_spaces-- && (dest < dest_end))
+ *dest++ = ' ';
+ *dest = '\0';
+ return dest;
+}
+
+/* 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 (const char *src, char *dest, size_t dest_size,
+ size_t *width, mbs_align_t align, int flags)
+{
+ 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;
+ bool conversion = false;
+ bool wc_enabled = false;
+
+#ifdef HAVE_WIDECHAR
+ /* 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);
+ }
+#endif
+
+mbsalign_unibyte:
+
+ 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 = n_spaces / 2 + n_spaces % 2;
+ size_t end_spaces = n_spaces / 2;
+
+ 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);
+ size_t 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);
+ }
+
+mbsalign_cleanup:
+
+ free (str_wc);
+ free (newstr);
+
+ return ret;
+}
diff --git a/lib/md5.c b/lib/md5.c
new file mode 100644
index 0000000..26ec4bb
--- /dev/null
+++ b/lib/md5.c
@@ -0,0 +1,254 @@
+/*
+ * 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
+void byteReverse(unsigned char *buf, unsigned longs);
+
+#ifndef ASM_MD5
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+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
+#endif
+
+/*
+ * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void MD5Init(struct 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 MD5Update(struct 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);
+ 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);
+ 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 MD5Final(unsigned char digest[MD5LENGTH], struct 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);
+ 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 */
+ ((uint32_t *) ctx->in)[14] = ctx->bits[0];
+ ((uint32_t *) ctx->in)[15] = ctx->bits[1];
+
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ byteReverse((unsigned char *) ctx->buf, 4);
+ memcpy(digest, ctx->buf, 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 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/pager.c b/lib/pager.c
new file mode 100644
index 0000000..1fce5bf
--- /dev/null
+++ b/lib/pager.c
@@ -0,0 +1,215 @@
+/*
+ * 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 "c.h"
+#include "xalloc.h"
+#include "nls.h"
+
+void setup_pager(void);
+
+static const char *pager_argv[] = { "sh", "-c", NULL, NULL };
+
+struct child_process {
+ const char **argv;
+ pid_t pid;
+ int in;
+ int out;
+ int err;
+ 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 inline void dup_devnull(int to)
+{
+ int fd = open("/dev/null", O_RDWR);
+ dup2(fd, to);
+ close(fd);
+}
+
+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], 0);
+ close_pair(fdin);
+ } else if (cmd->in) {
+ dup2(cmd->in, 0);
+ close(cmd->in);
+ }
+
+ cmd->preexec_cb();
+ execvp(cmd->argv[0], (char *const*) cmd->argv);
+ exit(127); /* cmd not found */
+ }
+
+ if (cmd->pid < 0) {
+ if (need_in)
+ close_pair(fdin);
+ else if (cmd->in)
+ close(cmd->in);
+ return -1;
+ }
+
+ if (need_in)
+ close(fdin[0]);
+ else if (cmd->in)
+ close(cmd->in);
+ return 0;
+}
+
+static int wait_or_whine(pid_t pid)
+{
+ for (;;) {
+ int status, code;
+ pid_t waiting = waitpid(pid, &status, 0);
+
+ if (waiting < 0) {
+ if (errno == EINTR)
+ continue;
+ err(EXIT_FAILURE, _("waitpid failed (%s)"), strerror(errno));
+ }
+ if (waiting != pid)
+ return -1;
+ if (WIFSIGNALED(status))
+ return -1;
+
+ if (!WIFEXITED(status))
+ return -1;
+ code = WEXITSTATUS(status);
+ switch (code) {
+ case 127:
+ return -1;
+ case 0:
+ return 0;
+ default:
+ return -1;
+ }
+ }
+}
+
+static int finish_command(struct child_process *cmd)
+{
+ return wait_or_whine(cmd->pid);
+}
+
+static void pager_preexec(void)
+{
+ /*
+ * Work around bug in "less" by not starting it until we
+ * have real input
+ */
+ fd_set in;
+
+ FD_ZERO(&in);
+ FD_SET(0, &in);
+ select(1, &in, NULL, &in, NULL);
+
+ setenv("LESS", "FRSX", 0);
+}
+
+static void wait_for_pager(void)
+{
+ fflush(stdout);
+ fflush(stderr);
+ /* signal EOF to pager */
+ close(1);
+ close(2);
+ finish_command(&pager_process);
+}
+
+static void wait_for_pager_signal(int signo)
+{
+ wait_for_pager();
+ raise(signo);
+}
+
+void setup_pager(void)
+{
+ const char *pager = getenv("PAGER");
+
+ if (!isatty(1))
+ return;
+
+ if (!pager)
+ pager = "less";
+ else if (!*pager || !strcmp(pager, "cat"))
+ 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, 1);
+ if (isatty(2))
+ dup2(pager_process.in, 2);
+ close(pager_process.in);
+
+ /* this makes sure that the parent terminates after the pager */
+ signal(SIGINT, wait_for_pager_signal);
+ signal(SIGHUP, wait_for_pager_signal);
+ signal(SIGTERM, wait_for_pager_signal);
+ signal(SIGQUIT, wait_for_pager_signal);
+ signal(SIGPIPE, wait_for_pager_signal);
+
+ atexit(wait_for_pager);
+}
+
+#ifdef TEST_PROGRAM
+
+#define MAX 255
+
+int main(int argc __attribute__ ((__unused__)),
+ char *argv[] __attribute__ ((__unused__)))
+{
+ int i;
+
+ setup_pager();
+ for (i = 0; i < MAX; i++)
+ printf("%d\n", i);
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM */
diff --git a/lib/path.c b/lib/path.c
new file mode 100644
index 0000000..8437c02
--- /dev/null
+++ b/lib/path.c
@@ -0,0 +1,218 @@
+/*
+ * Simple functions to access files.
+ *
+ * Taken from lscpu.c
+ *
+ * Copyright (C) 2008 Cai Qian <qcai@redhat.com>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "all-io.h"
+#include "cpuset.h"
+#include "path.h"
+#include "nls.h"
+#include "c.h"
+
+static size_t prefixlen;
+static char pathbuf[PATH_MAX];
+
+static const char *
+path_vcreate(const char *path, va_list ap)
+{
+ if (prefixlen)
+ vsnprintf(pathbuf + prefixlen,
+ sizeof(pathbuf) - prefixlen, path, ap);
+ else
+ vsnprintf(pathbuf, sizeof(pathbuf), path, ap);
+ return pathbuf;
+}
+
+static FILE *
+path_vfopen(const char *mode, int exit_on_error, const char *path, va_list ap)
+{
+ FILE *f;
+ const char *p = path_vcreate(path, ap);
+
+ f = fopen(p, mode);
+ if (!f && exit_on_error)
+ err(EXIT_FAILURE, _("cannot open %s"), p);
+ return f;
+}
+
+static int
+path_vopen(int flags, const char *path, va_list ap)
+{
+ int fd;
+ const char *p = path_vcreate(path, ap);
+
+ fd = open(p, flags);
+ if (fd == -1)
+ err(EXIT_FAILURE, _("cannot open %s"), p);
+ return fd;
+}
+
+FILE *
+path_fopen(const char *mode, int exit_on_error, const char *path, ...)
+{
+ FILE *fd;
+ va_list ap;
+
+ va_start(ap, path);
+ fd = path_vfopen(mode, exit_on_error, path, ap);
+ va_end(ap);
+
+ return fd;
+}
+
+void
+path_getstr(char *result, size_t len, const char *path, ...)
+{
+ FILE *fd;
+ va_list ap;
+
+ va_start(ap, path);
+ fd = path_vfopen("r", 1, path, ap);
+ va_end(ap);
+
+ if (!fgets(result, len, fd))
+ err(EXIT_FAILURE, _("failed to read: %s"), pathbuf);
+ fclose(fd);
+
+ len = strlen(result);
+ if (result[len - 1] == '\n')
+ result[len - 1] = '\0';
+}
+
+int
+path_getnum(const char *path, ...)
+{
+ FILE *fd;
+ va_list ap;
+ int result;
+
+ va_start(ap, path);
+ fd = path_vfopen("r", 1, path, ap);
+ va_end(ap);
+
+ if (fscanf(fd, "%d", &result) != 1) {
+ if (ferror(fd))
+ err(EXIT_FAILURE, _("failed to read: %s"), pathbuf);
+ else
+ errx(EXIT_FAILURE, _("parse error: %s"), pathbuf);
+ }
+ fclose(fd);
+ return result;
+}
+
+int
+path_writestr(const char *str, const char *path, ...)
+{
+ int fd, result;
+ va_list ap;
+
+ va_start(ap, path);
+ fd = path_vopen(O_WRONLY, path, ap);
+ va_end(ap);
+ result = write_all(fd, str, strlen(str));
+ close(fd);
+ return result;
+}
+
+int
+path_exist(const char *path, ...)
+{
+ va_list ap;
+ const char *p;
+
+ va_start(ap, path);
+ p = path_vcreate(path, ap);
+ va_end(ap);
+
+ return access(p, F_OK) == 0;
+}
+
+static cpu_set_t *
+path_cpuparse(int maxcpus, int islist, const char *path, va_list ap)
+{
+ FILE *fd;
+ cpu_set_t *set;
+ size_t setsize, len = maxcpus * 7;
+ char buf[len];
+
+ fd = path_vfopen("r", 1, path, ap);
+
+ if (!fgets(buf, len, fd))
+ err(EXIT_FAILURE, _("failed to read: %s"), pathbuf);
+ fclose(fd);
+
+ len = strlen(buf);
+ if (buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+
+ set = cpuset_alloc(maxcpus, &setsize, NULL);
+ if (!set)
+ err(EXIT_FAILURE, _("failed to callocate cpu set"));
+
+ if (islist) {
+ if (cpulist_parse(buf, set, setsize, 0))
+ errx(EXIT_FAILURE, _("failed to parse CPU list %s"), buf);
+ } else {
+ if (cpumask_parse(buf, set, setsize))
+ errx(EXIT_FAILURE, _("failed to parse CPU mask %s"), buf);
+ }
+ return set;
+}
+
+cpu_set_t *
+path_cpuset(int maxcpus, const char *path, ...)
+{
+ va_list ap;
+ cpu_set_t *set;
+
+ va_start(ap, path);
+ set = path_cpuparse(maxcpus, 0, path, ap);
+ va_end(ap);
+
+ return set;
+}
+
+cpu_set_t *
+path_cpulist(int maxcpus, const char *path, ...)
+{
+ va_list ap;
+ cpu_set_t *set;
+
+ va_start(ap, path);
+ set = path_cpuparse(maxcpus, 1, path, ap);
+ va_end(ap);
+
+ return set;
+}
+
+void
+path_setprefix(const char *prefix)
+{
+ prefixlen = strlen(prefix);
+ strncpy(pathbuf, prefix, sizeof(pathbuf));
+ pathbuf[sizeof(pathbuf) - 1] = '\0';
+}
diff --git a/lib/procutils.c b/lib/procutils.c
new file mode 100644
index 0000000..52e9ee3
--- /dev/null
+++ b/lib/procutils.c
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2011 Davidlohr Bueso <dave@gnu.org>
+ *
+ * procutils.c: General purpose procfs parsing utilities
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Library Public License for more details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <ctype.h>
+
+#include "procutils.h"
+#include "c.h"
+
+/*
+ * @pid: process ID for which we want to obtain the threads group
+ *
+ * Returns: newly allocated tasks structure
+ */
+struct proc_tasks *proc_open_tasks(pid_t pid)
+{
+ struct proc_tasks *tasks;
+ char path[PATH_MAX];
+
+ sprintf(path, "/proc/%d/task/", pid);
+
+ tasks = malloc(sizeof(struct proc_tasks));
+ if (tasks) {
+ tasks->dir = opendir(path);
+ if (tasks->dir)
+ return tasks;
+ }
+
+ free(tasks);
+ return NULL;
+}
+
+/*
+ * @tasks: allocated tasks structure
+ *
+ * Returns: nothing
+ */
+void proc_close_tasks(struct proc_tasks *tasks)
+{
+ if (tasks && tasks->dir)
+ closedir(tasks->dir);
+ free(tasks);
+}
+
+/*
+ * @tasks: allocated task structure
+ * @tid: [output] one of the thread IDs belonging to the thread group
+ * If when an error occurs, it is set to 0.
+ *
+ * Returns: 0 on success, 1 on end, -1 on failure or no more threads
+ */
+int proc_next_tid(struct proc_tasks *tasks, pid_t *tid)
+{
+ struct dirent *d;
+ char *end;
+
+ if (!tasks || !tid)
+ return -1;
+
+ *tid = 0;
+ errno = 0;
+
+ do {
+ d = readdir(tasks->dir);
+ if (!d)
+ return errno ? -1 : 1; /* error or end-of-dir */
+
+ if (!isdigit((unsigned char) *d->d_name))
+ continue;
+
+ *tid = (pid_t) strtol(d->d_name, &end, 10);
+ if (errno || d->d_name == end || (end && *end))
+ return -1;
+
+ } while (!*tid);
+
+ return 0;
+}
+
+#ifdef TEST_PROGRAM
+
+int main(int argc, char *argv[])
+{
+ pid_t tid, pid;
+ struct proc_tasks *ts;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s <pid>\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ pid = strtol(argv[1], (char **) NULL, 10);
+ printf("PID=%d, TIDs:", pid);
+
+ ts = proc_open_tasks(pid);
+ if (!ts)
+ err(EXIT_FAILURE, "open list of tasks failed");
+
+ while (proc_next_tid(ts, &tid) == 0)
+ printf(" %d", tid);
+
+ printf("\n");
+ proc_close_tasks(ts);
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM */
diff --git a/lib/randutils.c b/lib/randutils.c
new file mode 100644
index 0000000..85cb1a9
--- /dev/null
+++ b/lib/randutils.c
@@ -0,0 +1,121 @@
+/*
+ * General purpose random utilities
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include <sys/syscall.h>
+
+#include "randutils.h"
+
+#ifdef HAVE_TLS
+#define THREAD_LOCAL static __thread
+#else
+#define THREAD_LOCAL static
+#endif
+
+#if defined(__linux__) && defined(__NR_gettid) && defined(HAVE_JRAND48)
+#define DO_JRAND_MIX
+THREAD_LOCAL unsigned short ul_jrand_seed[3];
+#endif
+
+int random_get_fd(void)
+{
+ int i, fd;
+ struct timeval tv;
+
+ gettimeofday(&tv, 0);
+ fd = open("/dev/urandom", O_RDONLY);
+ if (fd == -1)
+ fd = open("/dev/random", O_RDONLY | O_NONBLOCK);
+ if (fd >= 0) {
+ i = fcntl(fd, F_GETFD);
+ if (i >= 0)
+ fcntl(fd, F_SETFD, i | FD_CLOEXEC);
+ }
+ srand((getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec);
+
+#ifdef DO_JRAND_MIX
+ ul_jrand_seed[0] = getpid() ^ (tv.tv_sec & 0xFFFF);
+ ul_jrand_seed[1] = getppid() ^ (tv.tv_usec & 0xFFFF);
+ ul_jrand_seed[2] = (tv.tv_sec ^ tv.tv_usec) >> 16;
+#endif
+ /* Crank the random number generator a few times */
+ gettimeofday(&tv, 0);
+ for (i = (tv.tv_sec ^ tv.tv_usec) & 0x1F; i > 0; i--)
+ rand();
+ return fd;
+}
+
+
+/*
+ * Generate a stream of random nbytes into buf.
+ * Use /dev/urandom if possible, and if not,
+ * use glibc pseudo-random functions.
+ */
+void random_get_bytes(void *buf, size_t nbytes)
+{
+ size_t i, n = nbytes;
+ int fd = random_get_fd();
+ int lose_counter = 0;
+ unsigned char *cp = (unsigned char *) buf;
+
+ if (fd >= 0) {
+ while (n > 0) {
+ ssize_t x = read(fd, cp, n);
+ if (x <= 0) {
+ if (lose_counter++ > 16)
+ break;
+ 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.
+ */
+ 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;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc __attribute__ ((__unused__)),
+ char *argv[] __attribute__ ((__unused__)))
+{
+ unsigned int v, i;
+
+ /* generate and print 10 random numbers */
+ for (i = 0; i < 10; i++) {
+ random_get_bytes(&v, sizeof(v));
+ printf("%d\n", v);
+ }
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM */
diff --git a/lib/setproctitle.c b/lib/setproctitle.c
new file mode 100644
index 0000000..4bcf8c8
--- /dev/null
+++ b/lib/setproctitle.c
@@ -0,0 +1,74 @@
+/*
+ * set process title for ps (from sendmail)
+ *
+ * Clobbers argv of our main procedure so ps(1) will display the title.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "setproctitle.h"
+
+#ifndef SPT_BUFSIZE
+# define SPT_BUFSIZE 2048
+#endif
+
+extern char **environ;
+
+static char **argv0;
+static int argv_lth;
+
+void initproctitle (int argc, char **argv)
+{
+ int i;
+ char **envp = environ;
+
+ /*
+ * Move the environment so we can reuse the memory.
+ * (Code borrowed from sendmail.)
+ * WARNING: ugly assumptions on memory layout here;
+ * if this ever causes problems, #undef DO_PS_FIDDLING
+ */
+ for (i = 0; envp[i] != NULL; i++)
+ continue;
+
+ environ = (char **) malloc(sizeof(char *) * (i + 1));
+ if (environ == NULL)
+ return;
+
+ for (i = 0; envp[i] != NULL; i++)
+ if ((environ[i] = strdup(envp[i])) == NULL)
+ return;
+ environ[i] = NULL;
+
+ argv0 = argv;
+ if (i > 0)
+ argv_lth = envp[i-1] + strlen(envp[i-1]) - argv0[0];
+ else
+ argv_lth = argv0[argc-1] + strlen(argv0[argc-1]) - argv0[0];
+}
+
+void setproctitle (const char *prog, const char *txt)
+{
+ int i;
+ char buf[SPT_BUFSIZE];
+
+ if (!argv0)
+ return;
+
+ if (strlen(prog) + strlen(txt) + 5 > SPT_BUFSIZE)
+ return;
+
+ sprintf(buf, "%s -- %s", prog, txt);
+
+ i = strlen(buf);
+ if (i > argv_lth - 2) {
+ i = argv_lth - 2;
+ buf[i] = '\0';
+ }
+ memset(argv0[0], '\0', argv_lth); /* clear the memory area */
+ strcpy(argv0[0], buf);
+
+ argv0[1] = NULL;
+}
diff --git a/lib/strutils.c b/lib/strutils.c
new file mode 100644
index 0000000..5dda138
--- /dev/null
+++ b/lib/strutils.c
@@ -0,0 +1,696 @@
+/*
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2010 Davidlohr Bueso <dave@gnu.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <string.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "bitops.h"
+
+static int do_scale_by_power (uintmax_t *x, int base, int power)
+{
+ while (power--) {
+ if (UINTMAX_MAX / base < *x)
+ return -2;
+ *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,Y,Z}
+ * 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,Y,Z}
+ * for example:
+ * 10KB = 10000
+ *
+ * Note that the function does not accept numbers with '-' (negative sign)
+ * prefix.
+ */
+int strtosize(const char *str, uintmax_t *res)
+{
+ char *p;
+ uintmax_t x;
+ int base = 1024, rc = 0;
+
+ *res = 0;
+
+ if (!str || !*str)
+ 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 = (char *) str;
+ while (isspace((unsigned char) *p))
+ p++;
+ if (*p == '-')
+ goto err;
+ p = NULL;
+
+ errno = 0;
+ x = strtoumax(str, &p, 0);
+
+ if (p == str ||
+ (errno != 0 && (x == UINTMAX_MAX || x == 0)))
+ goto err;
+
+ if (!p || !*p)
+ goto done; /* without suffix */
+
+ /*
+ * Check size suffixes
+ */
+ if (*(p + 1) == 'i' && *(p + 2) == 'B' && !*(p + 3))
+ base = 1024; /* XiB, 2^N */
+ else if (*(p + 1) == 'B' && !*(p + 2))
+ base = 1000; /* XB, 10^N */
+ else if (*(p + 1))
+ goto err; /* unexpected suffix */
+
+ switch(*p) {
+ case 'K':
+ case 'k':
+ rc = do_scale_by_power(&x, base, 1);
+ break;
+ case 'M':
+ case 'm':
+ rc = do_scale_by_power(&x, base, 2);
+ break;
+ case 'G':
+ case 'g':
+ rc = do_scale_by_power(&x, base, 3);
+ break;
+ case 'T':
+ case 't':
+ rc = do_scale_by_power(&x, base, 4);
+ break;
+ case 'P':
+ case 'p':
+ rc = do_scale_by_power(&x, base, 5);
+ break;
+ case 'E':
+ case 'e':
+ rc = do_scale_by_power(&x, base, 6);
+ break;
+ case 'Z':
+ rc = do_scale_by_power(&x, base, 7);
+ break;
+ case 'Y':
+ rc = do_scale_by_power(&x, base, 8);
+ break;
+ default:
+ goto err;
+ }
+
+done:
+ *res = x;
+ return rc;
+err:
+ return -1;
+}
+
+#ifndef HAVE_STRNLEN
+size_t strnlen(const char *s, size_t maxlen)
+{
+ int i;
+
+ for (i = 0; i < maxlen; i++) {
+ if (s[i] == '\0')
+ return i + 1;
+ }
+ 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 = (char *) malloc((len + 1) * sizeof(char));
+ if (!new)
+ return NULL;
+ new[len] = '\0';
+ return (char *) memcpy(new, s, len);
+}
+#endif
+
+int16_t strtos16_or_err(const char *str, const char *errmesg)
+{
+ int32_t num = strtos32_or_err(str, errmesg);
+
+ if (num < INT16_MIN || num > INT16_MAX)
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+ return num;
+}
+
+uint16_t strtou16_or_err(const char *str, const char *errmesg)
+{
+ uint32_t num = strtou32_or_err(str, errmesg);
+
+ if (num > UINT16_MAX)
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+ return num;
+}
+
+int32_t strtos32_or_err(const char *str, const char *errmesg)
+{
+ int64_t num = strtos64_or_err(str, errmesg);
+
+ if (num < INT32_MIN || num > INT32_MAX)
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+ return num;
+}
+
+uint32_t strtou32_or_err(const char *str, const char *errmesg)
+{
+ uint64_t num = strtou64_or_err(str, errmesg);
+
+ if (num > UINT32_MAX)
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+ return num;
+}
+
+int64_t strtos64_or_err(const char *str, const char *errmesg)
+{
+ int64_t num;
+ char *end = NULL;
+
+ if (str == NULL || *str == '\0')
+ goto err;
+ errno = 0;
+ num = strtoimax(str, &end, 10);
+
+ if (errno || str == end || (end && *end))
+ goto err;
+
+ return num;
+err:
+ if (errno)
+ err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+}
+
+uint64_t strtou64_or_err(const char *str, const char *errmesg)
+{
+ uintmax_t num;
+ char *end = NULL;
+
+ if (str == NULL || *str == '\0')
+ goto err;
+ errno = 0;
+ num = strtoumax(str, &end, 10);
+
+ if (errno || str == end || (end && *end))
+ goto err;
+
+ return num;
+err:
+ if (errno)
+ err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+}
+
+
+double strtod_or_err(const char *str, const char *errmesg)
+{
+ double num;
+ char *end = NULL;
+
+ if (str == NULL || *str == '\0')
+ goto err;
+ errno = 0;
+ num = strtod(str, &end);
+
+ if (errno || str == end || (end && *end))
+ goto err;
+
+ return num;
+err:
+ if (errno)
+ 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;
+
+ if (str == NULL || *str == '\0')
+ goto err;
+ errno = 0;
+ num = strtol(str, &end, 10);
+
+ if (errno || str == end || (end && *end))
+ goto err;
+
+ return num;
+err:
+ if (errno)
+ 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;
+
+ if (str == NULL || *str == '\0')
+ goto err;
+ errno = 0;
+ num = strtoul(str, &end, 10);
+
+ if (errno || str == end || (end && *end))
+ goto err;
+
+ return num;
+err:
+ if (errno)
+ 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);
+}
+
+/*
+ * Converts stat->st_mode to ls(1)-like mode string. The size of "str" must
+ * be 10 bytes.
+ */
+void strmode(mode_t mode, char *str)
+{
+ if (S_ISDIR(mode))
+ str[0] = 'd';
+ else if (S_ISLNK(mode))
+ str[0] = 'l';
+ else if (S_ISCHR(mode))
+ str[0] = 'c';
+ else if (S_ISBLK(mode))
+ str[0] = 'b';
+ else if (S_ISSOCK(mode))
+ str[0] = 's';
+ else if (S_ISFIFO(mode))
+ str[0] = 'p';
+ else if (S_ISREG(mode))
+ str[0] = '-';
+
+ str[1] = mode & S_IRUSR ? 'r' : '-';
+ str[2] = mode & S_IWUSR ? 'w' : '-';
+ str[3] = (mode & S_ISUID
+ ? (mode & S_IXUSR ? 's' : 'S')
+ : (mode & S_IXUSR ? 'x' : '-'));
+ str[4] = mode & S_IRGRP ? 'r' : '-';
+ str[5] = mode & S_IWGRP ? 'w' : '-';
+ str[6] = (mode & S_ISGID
+ ? (mode & S_IXGRP ? 's' : 'S')
+ : (mode & S_IXGRP ? 'x' : '-'));
+ str[7] = mode & S_IROTH ? 'r' : '-';
+ str[8] = mode & S_IWOTH ? 'w' : '-';
+ str[9] = (mode & S_ISVTX
+ ? (mode & S_IXOTH ? 't' : 'T')
+ : (mode & S_IXOTH ? 'x' : '-'));
+ str[10] = '\0';
+}
+
+/*
+ * returns exponent (2^x=n) in range KiB..PiB
+ */
+static int get_exp(uint64_t n)
+{
+ int shft;
+
+ for (shft = 10; shft <= 60; shft += 10) {
+ if (n < (1ULL << shft))
+ break;
+ }
+ return shft - 10;
+}
+
+char *size_to_human_string(int options, uint64_t bytes)
+{
+ char buf[32];
+ int dec, exp;
+ uint64_t frac;
+ const char *letters = "BKMGTPE";
+ char suffix[sizeof(" KiB")], *psuf = suffix;
+ char c;
+
+ if (options & SIZE_SUFFIX_SPACE)
+ *psuf++ = ' ';
+
+ exp = get_exp(bytes);
+ c = *(letters + (exp ? exp / 10 : 0));
+ dec = exp ? bytes / (1ULL << exp) : bytes;
+ frac = exp ? bytes % (1ULL << exp) : 0;
+
+ *psuf++ = c;
+
+ if ((options & SIZE_SUFFIX_3LETTER) && (c != 'B')) {
+ *psuf++ = 'i';
+ *psuf++ = 'B';
+ }
+
+ *psuf = '\0';
+
+ /* fprintf(stderr, "exp: %d, unit: %c, dec: %d, frac: %jd\n",
+ * exp, suffix[0], dec, frac);
+ */
+
+ if (frac) {
+ /* round */
+ frac = (frac / (1ULL << (exp - 10)) + 50) / 100;
+ if (frac == 10)
+ dec++, frac = 0;
+ }
+
+ if (frac) {
+ struct lconv const *l = localeconv();
+ char *dp = l ? l->decimal_point : NULL;
+
+ if (!dp || !*dp)
+ dp = ".";
+ snprintf(buf, sizeof(buf), "%d%s%jd%s", dec, dp, frac, suffix);
+ } else
+ snprintf(buf, sizeof(buf), "%d%s", dec, suffix);
+
+ return strdup(buf);
+}
+
+/*
+ * Parses comma delimited list to array with IDs, for example:
+ *
+ * "aaa,bbb,ccc" --> ary[0] = FOO_AAA;
+ * ary[1] = FOO_BBB;
+ * ary[3] = FOO_CCC;
+ *
+ * The function name2id() provides conversion from string to ID.
+ *
+ * Returns: >= 0 : number of items added to ary[]
+ * -1 : parse error or unknown item
+ * -2 : arysz reached
+ */
+int string_to_idarray(const char *list, int ary[], size_t arysz,
+ int (name2id)(const char *, size_t))
+{
+ const char *begin = NULL, *p;
+ size_t n = 0;
+
+ if (!list || !*list || !ary || !arysz || !name2id)
+ return -1;
+
+ for (p = list; p && *p; p++) {
+ const char *end = NULL;
+ int id;
+
+ if (n >= arysz)
+ return -2;
+ if (!begin)
+ begin = p; /* begin of the column name */
+ if (*p == ',')
+ end = p; /* terminate the name */
+ if (*(p + 1) == '\0')
+ end = p + 1; /* end of string */
+ if (!begin || !end)
+ continue;
+ if (end <= begin)
+ return -1;
+
+ id = name2id(begin, end - begin);
+ if (id == -1)
+ return -1;
+ ary[ n++ ] = id;
+ begin = NULL;
+ if (end && !*end)
+ break;
+ }
+ return n;
+}
+
+/*
+ * Parses the array like string_to_idarray but if format is "+aaa,bbb"
+ * it adds fields to array instead of replacing them.
+ */
+int string_add_to_idarray(const char *list, int ary[], size_t arysz,
+ int *ary_pos, int (name2id)(const char *, size_t))
+{
+ const char *list_add;
+ int r;
+
+ if (!list || !*list || !ary_pos ||
+ *ary_pos < 0 || (size_t) *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 = 0;
+ 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;
+}
+
+/*
+ * Compare two strings for equality, ignoring at most one trailing
+ * slash.
+ */
+int streq_except_trailing_slash(const char *s1, const char *s2)
+{
+ int equal;
+
+ if (!s1 && !s2)
+ return 1;
+ if (!s1 || !s2)
+ return 0;
+
+ equal = !strcmp(s1, s2);
+
+ if (!equal) {
+ size_t len1 = strlen(s1);
+ size_t len2 = strlen(s2);
+
+ if (len1 && *(s1 + len1 - 1) == '/')
+ len1--;
+ if (len2 && *(s2 + len2 - 1) == '/')
+ len2--;
+ if (len1 != len2)
+ return 0;
+
+ equal = !strncmp(s1, s2, len1);
+ }
+
+ return equal;
+}
+
+
+#ifdef TEST_PROGRAM
+
+int main(int argc, char *argv[])
+{
+ uintmax_t size = 0;
+ char *hum, *hum2;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <number>[suffix]\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ if (strtosize(argv[1], &size))
+ errx(EXIT_FAILURE, "invalid size '%s' value", argv[1]);
+
+ hum = size_to_human_string(SIZE_SUFFIX_1LETTER, size);
+ hum2 = size_to_human_string(SIZE_SUFFIX_3LETTER |
+ SIZE_SUFFIX_SPACE, size);
+
+ printf("%25s : %20ju : %8s : %12s\n", argv[1], size, hum, hum2);
+ free(hum);
+ free(hum2);
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM */
diff --git a/lib/sysfs.c b/lib/sysfs.c
new file mode 100644
index 0000000..7b2c5f7
--- /dev/null
+++ b/lib/sysfs.c
@@ -0,0 +1,705 @@
+/*
+ * Copyright (C) 2011 Karel Zak <kzak@redhat.com>
+ */
+
+#include <ctype.h>
+
+#include "c.h"
+#include "at.h"
+#include "pathnames.h"
+#include "sysfs.h"
+
+char *sysfs_devno_attribute_path(dev_t devno, char *buf,
+ size_t bufsiz, const char *attr)
+{
+ int len;
+
+ if (attr)
+ len = snprintf(buf, bufsiz, _PATH_SYS_DEVBLOCK "/%d:%d/%s",
+ major(devno), minor(devno), attr);
+ else
+ len = snprintf(buf, bufsiz, _PATH_SYS_DEVBLOCK "/%d:%d",
+ major(devno), minor(devno));
+
+ return (len < 0 || (size_t) len + 1 > bufsiz) ? NULL : buf;
+}
+
+int sysfs_devno_has_attribute(dev_t devno, const char *attr)
+{
+ char path[PATH_MAX];
+ struct stat info;
+
+ if (!sysfs_devno_attribute_path(devno, path, sizeof(path), attr))
+ return 0;
+ if (stat(path, &info) == 0)
+ return 1;
+ return 0;
+}
+
+char *sysfs_devno_path(dev_t devno, char *buf, size_t bufsiz)
+{
+ return sysfs_devno_attribute_path(devno, buf, bufsiz, NULL);
+}
+
+dev_t sysfs_devname_to_devno(const char *name, const char *parent)
+{
+ char buf[PATH_MAX], *path = NULL;
+ dev_t dev = 0;
+
+ if (strncmp("/dev/", name, 5) == 0) {
+ /*
+ * Read from /dev
+ */
+ struct stat st;
+
+ if (stat(name, &st) == 0)
+ dev = st.st_rdev;
+ else
+ name += 5; /* unaccesible, or not node in /dev */
+ }
+
+ if (!dev && parent) {
+ /*
+ * Create path to /sys/block/<parent>/<name>/dev
+ */
+ int len = snprintf(buf, sizeof(buf),
+ _PATH_SYS_BLOCK "/%s/%s/dev", parent, name);
+ if (len < 0 || (size_t) len + 1 > sizeof(buf))
+ return 0;
+ path = buf;
+
+ } else if (!dev) {
+ /*
+ * Create path to /sys/block/<name>/dev
+ */
+ int len = snprintf(buf, sizeof(buf),
+ _PATH_SYS_BLOCK "/%s/dev", name);
+ if (len < 0 || (size_t) len + 1 > sizeof(buf))
+ return 0;
+ path = buf;
+ }
+
+ if (path) {
+ /*
+ * read devno from sysfs
+ */
+ FILE *f;
+ int maj = 0, min = 0;
+
+ f = fopen(path, "r");
+ if (!f)
+ return 0;
+
+ if (fscanf(f, "%d:%d", &maj, &min) == 2)
+ dev = makedev(maj, min);
+ fclose(f);
+ }
+ return dev;
+}
+
+/*
+ * Returns devname (e.g. "/dev/sda1") for the given devno.
+ *
+ * Note that the @buf has to be large enough to store /sys/dev/block/<maj:min>
+ * symlinks.
+ *
+ * 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 sysfs_cxt cxt;
+ char *name;
+ size_t sz;
+ struct stat st;
+
+ if (sysfs_init(&cxt, devno, NULL))
+ return NULL;
+
+ name = sysfs_get_devname(&cxt, buf, bufsiz);
+ sysfs_deinit(&cxt);
+
+ if (!name)
+ return NULL;
+
+ sz = strlen(name);
+
+ if (sz + sizeof("/dev/") > bufsiz)
+ return NULL;
+
+ /* 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 == devno)
+ return buf;
+
+ return NULL;
+}
+
+int sysfs_init(struct sysfs_cxt *cxt, dev_t devno, struct sysfs_cxt *parent)
+{
+ char path[PATH_MAX];
+ int fd, rc;
+
+ memset(cxt, 0, sizeof(*cxt));
+ cxt->dir_fd = -1;
+
+ if (!sysfs_devno_path(devno, path, sizeof(path)))
+ goto err;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ goto err;
+ cxt->dir_fd = fd;
+
+ cxt->dir_path = strdup(path);
+ if (!cxt->dir_path)
+ goto err;
+ cxt->devno = devno;
+ cxt->parent = parent;
+ return 0;
+err:
+ rc = errno > 0 ? -errno : -1;
+ sysfs_deinit(cxt);
+ return rc;
+}
+
+void sysfs_deinit(struct sysfs_cxt *cxt)
+{
+ if (!cxt)
+ return;
+
+ if (cxt->dir_fd >= 0)
+ close(cxt->dir_fd);
+ free(cxt->dir_path);
+
+ cxt->devno = 0;
+ cxt->dir_fd = -1;
+ cxt->parent = NULL;
+ cxt->dir_path = NULL;
+}
+
+int sysfs_stat(struct sysfs_cxt *cxt, const char *attr, struct stat *st)
+{
+ int rc = fstat_at(cxt->dir_fd, cxt->dir_path, attr, st, 0);
+
+ if (rc != 0 && errno == ENOENT &&
+ strncmp(attr, "queue/", 6) == 0 && cxt->parent) {
+
+ /* Exception for "queue/<attr>". These attributes are available
+ * for parental devices only
+ */
+ return fstat_at(cxt->parent->dir_fd,
+ cxt->parent->dir_path, attr, st, 0);
+ }
+ return rc;
+}
+
+int sysfs_has_attribute(struct sysfs_cxt *cxt, const char *attr)
+{
+ struct stat st;
+
+ return sysfs_stat(cxt, attr, &st) == 0;
+}
+
+static int sysfs_open(struct sysfs_cxt *cxt, const char *attr)
+{
+ int fd = open_at(cxt->dir_fd, cxt->dir_path, attr, O_RDONLY);
+
+ if (fd == -1 && errno == ENOENT &&
+ strncmp(attr, "queue/", 6) == 0 && cxt->parent) {
+
+ /* Exception for "queue/<attr>". These attributes are available
+ * for parental devices only
+ */
+ fd = open_at(cxt->parent->dir_fd, cxt->dir_path, attr, O_RDONLY);
+ }
+ return fd;
+}
+
+ssize_t sysfs_readlink(struct sysfs_cxt *cxt, const char *attr,
+ char *buf, size_t bufsiz)
+{
+ if (!cxt->dir_path)
+ return -1;
+
+ if (attr)
+ return readlink_at(cxt->dir_fd, cxt->dir_path, attr, buf, bufsiz);
+
+ /* read /sys/dev/block/<maj:min> link */
+ return readlink(cxt->dir_path, buf, bufsiz);
+}
+
+DIR *sysfs_opendir(struct sysfs_cxt *cxt, const char *attr)
+{
+ DIR *dir;
+ int fd;
+
+ if (attr)
+ fd = sysfs_open(cxt, attr);
+ else
+ /* request to open root of device in sysfs (/sys/block/<dev>)
+ * -- we cannot use cxt->sysfs_fd directly, because closedir()
+ * will close this our persistent file descriptor.
+ */
+ fd = dup(cxt->dir_fd);
+
+ if (fd < 0)
+ return NULL;
+
+ dir = fdopendir(fd);
+ if (!dir) {
+ close(fd);
+ return NULL;
+ }
+ if (!attr)
+ rewinddir(dir);
+ return dir;
+}
+
+
+static FILE *sysfs_fopen(struct sysfs_cxt *cxt, const char *attr)
+{
+ int fd = sysfs_open(cxt, attr);
+
+ return fd < 0 ? NULL : fdopen(fd, "r");
+}
+
+
+static struct dirent *xreaddir(DIR *dp)
+{
+ struct dirent *d;
+
+ while ((d = readdir(dp))) {
+ if (!strcmp(d->d_name, ".") ||
+ !strcmp(d->d_name, ".."))
+ continue;
+
+ /* blacklist here? */
+ break;
+ }
+ return d;
+}
+
+int sysfs_is_partition_dirent(DIR *dir, struct dirent *d, const char *parent_name)
+{
+ char path[256];
+
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (d->d_type != DT_DIR &&
+ d->d_type != DT_LNK)
+ return 0;
+#endif
+ if (parent_name) {
+ const char *p = parent_name;
+ size_t len;
+
+ /* /dev/sda --> "sda" */
+ if (*parent_name == '/') {
+ p = strrchr(parent_name, '/');
+ if (!p)
+ return 0;
+ p++;
+ }
+
+ len = strlen(p);
+ if (strlen(d->d_name) <= len)
+ return 0;
+
+ /* partitions subdir name is
+ * "<parent>[:digit:]" or "<parent>p[:digit:]"
+ */
+ return strncmp(p, d->d_name, len) == 0 &&
+ ((*(d->d_name + len) == 'p' && isdigit(*(d->d_name + len + 1)))
+ || isdigit(*(d->d_name + len)));
+ }
+
+ /* Cannot use /partition file, not supported on old sysfs */
+ snprintf(path, sizeof(path), "%s/start", d->d_name);
+
+ return faccessat(dirfd(dir), path, R_OK, 0) == 0;
+}
+
+/*
+ * Converts @partno (partition number) to devno of the partition.
+ * The @cxt handles wholedisk device.
+ *
+ * Note that this code does not expect any special format of the
+ * partitions devnames.
+ */
+dev_t sysfs_partno_to_devno(struct sysfs_cxt *cxt, int partno)
+{
+ DIR *dir;
+ struct dirent *d;
+ char path[256];
+ dev_t devno = 0;
+
+ dir = sysfs_opendir(cxt, NULL);
+ if (!dir)
+ return 0;
+
+ while ((d = xreaddir(dir))) {
+ int n, maj, min;
+
+ if (!sysfs_is_partition_dirent(dir, d, NULL))
+ continue;
+
+ snprintf(path, sizeof(path), "%s/partition", d->d_name);
+ if (sysfs_read_int(cxt, path, &n))
+ continue;
+
+ if (n == partno) {
+ snprintf(path, sizeof(path), "%s/dev", d->d_name);
+ if (sysfs_scanf(cxt, path, "%d:%d", &maj, &min) == 2)
+ devno = makedev(maj, min);
+ break;
+ }
+ }
+
+ closedir(dir);
+ return devno;
+}
+
+
+int sysfs_scanf(struct sysfs_cxt *cxt, const char *attr, const char *fmt, ...)
+{
+ FILE *f = sysfs_fopen(cxt, attr);
+ va_list ap;
+ int rc;
+
+ if (!f)
+ return -EINVAL;
+ va_start(ap, fmt);
+ rc = vfscanf(f, fmt, ap);
+ va_end(ap);
+
+ fclose(f);
+ return rc;
+}
+
+
+int sysfs_read_s64(struct sysfs_cxt *cxt, const char *attr, int64_t *res)
+{
+ int64_t x = 0;
+
+ if (sysfs_scanf(cxt, attr, "%"SCNd64, &x) == 1) {
+ if (res)
+ *res = x;
+ return 0;
+ }
+ return -1;
+}
+
+int sysfs_read_u64(struct sysfs_cxt *cxt, const char *attr, uint64_t *res)
+{
+ uint64_t x = 0;
+
+ if (sysfs_scanf(cxt, attr, "%"SCNu64, &x) == 1) {
+ if (res)
+ *res = x;
+ return 0;
+ }
+ return -1;
+}
+
+int sysfs_read_int(struct sysfs_cxt *cxt, const char *attr, int *res)
+{
+ int x = 0;
+
+ if (sysfs_scanf(cxt, attr, "%d", &x) == 1) {
+ if (res)
+ *res = x;
+ return 0;
+ }
+ return -1;
+}
+
+char *sysfs_strdup(struct sysfs_cxt *cxt, const char *attr)
+{
+ char buf[1024];
+ return sysfs_scanf(cxt, attr, "%1023[^\n]", buf) == 1 ?
+ strdup(buf) : NULL;
+}
+
+int sysfs_count_dirents(struct sysfs_cxt *cxt, const char *attr)
+{
+ DIR *dir;
+ int r = 0;
+
+ if (!(dir = sysfs_opendir(cxt, attr)))
+ return 0;
+
+ while (xreaddir(dir)) r++;
+
+ closedir(dir);
+ return r;
+}
+
+int sysfs_count_partitions(struct sysfs_cxt *cxt, const char *devname)
+{
+ DIR *dir;
+ struct dirent *d;
+ int r = 0;
+
+ if (!(dir = sysfs_opendir(cxt, NULL)))
+ return 0;
+
+ while ((d = xreaddir(dir))) {
+ if (sysfs_is_partition_dirent(dir, d, devname))
+ r++;
+ }
+
+ closedir(dir);
+ return r;
+}
+
+/*
+ * Returns slave name if there is only one slave, otherwise returns NULL.
+ * The result should be deallocated by free().
+ */
+char *sysfs_get_slave(struct sysfs_cxt *cxt)
+{
+ DIR *dir;
+ struct dirent *d;
+ char *name = NULL;
+
+ if (!(dir = sysfs_opendir(cxt, "slaves")))
+ 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;
+}
+
+/*
+ * Note that the @buf has to be large enough to store /sys/dev/block/<maj:min>
+ * symlinks.
+ */
+char *sysfs_get_devname(struct sysfs_cxt *cxt, char *buf, size_t bufsiz)
+{
+ char *name = NULL;
+ ssize_t sz;
+
+ sz = sysfs_readlink(cxt, NULL, buf, bufsiz - 1);
+ if (sz < 0)
+ return NULL;
+
+ buf[sz] = '\0';
+ name = strrchr(buf, '/');
+ if (!name)
+ return NULL;
+
+ name++;
+ sz = strlen(name);
+
+ memmove(buf, name, sz + 1);
+ return buf;
+}
+
+/* returns basename and keeps dirname in the @path */
+static char *stripoff_last_component(char *path)
+{
+ char *p = strrchr(path, '/');
+
+ if (!p)
+ return NULL;
+ *p = '\0';
+ return ++p;
+}
+
+static int get_dm_wholedisk(struct sysfs_cxt *cxt, char *diskname,
+ size_t len, dev_t *diskdevno)
+{
+ int rc = 0;
+ char *name;
+
+ /* Note, sysfs_get_slave() returns the first slave only,
+ * if there is more slaves, then return NULL
+ */
+ name = sysfs_get_slave(cxt);
+ if (!name)
+ return -1;
+
+ if (diskname && len) {
+ strncpy(diskname, name, len);
+ diskname[len - 1] = '\0';
+ }
+
+ if (diskdevno) {
+ *diskdevno = sysfs_devname_to_devno(name, NULL);
+ if (!*diskdevno)
+ rc = -1;
+ }
+
+ free(name);
+ return rc;
+}
+
+int sysfs_devno_to_wholedisk(dev_t dev, char *diskname,
+ size_t len, dev_t *diskdevno)
+{
+ struct sysfs_cxt cxt;
+ int is_part = 0;
+
+ if (!dev || sysfs_init(&cxt, dev, NULL) != 0)
+ return -1;
+
+ is_part = sysfs_has_attribute(&cxt, "partition");
+ if (!is_part) {
+ /*
+ * Extra case for partitions mapped by device-mapper.
+ *
+ * All regualar 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 = sysfs_strdup(&cxt, "dm/uuid");
+ char *tmp = uuid;
+ char *prefix = uuid ? strsep(&tmp, "-") : NULL;
+
+ if (prefix && strncasecmp(prefix, "part", 4) == 0)
+ is_part = 1;
+ free(uuid);
+
+ if (is_part &&
+ get_dm_wholedisk(&cxt, diskname, len, diskdevno) == 0)
+ /*
+ * partitioned device, mapped by DM
+ */
+ goto done;
+
+ is_part = 0;
+ }
+
+ if (!is_part) {
+ /*
+ * unpartitioned device
+ */
+ if (diskname && len) {
+ if (!sysfs_get_devname(&cxt, diskname, len))
+ goto err;
+ }
+ if (diskdevno)
+ *diskdevno = dev;
+
+ } 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;
+ int linklen;
+
+ linklen = sysfs_readlink(&cxt, NULL,
+ linkpath, sizeof(linkpath) - 1);
+ if (linklen < 0)
+ goto err;
+ linkpath[linklen] = '\0';
+
+ stripoff_last_component(linkpath); /* dirname */
+ name = stripoff_last_component(linkpath); /* basename */
+ if (!name)
+ goto err;
+
+ if (diskname && len) {
+ strncpy(diskname, name, len);
+ diskname[len - 1] = '\0';
+ }
+
+ if (diskdevno) {
+ *diskdevno = sysfs_devname_to_devno(name, NULL);
+ if (!*diskdevno)
+ goto err;
+ }
+ }
+
+done:
+ sysfs_deinit(&cxt);
+ return 0;
+err:
+ sysfs_deinit(&cxt);
+ return -1;
+}
+
+#ifdef TEST_PROGRAM_SYSFS
+#include <errno.h>
+#include <err.h>
+#include <stdlib.h>
+
+int main(int argc, char *argv[])
+{
+ struct sysfs_cxt cxt = UL_SYSFSCXT_EMPTY;
+ char *devname;
+ dev_t devno;
+ char path[PATH_MAX];
+ int i, is_part;
+ uint64_t u64;
+ ssize_t len;
+
+ if (argc != 2)
+ errx(EXIT_FAILURE, "usage: %s <devname>", argv[0]);
+
+ devname = argv[1];
+ devno = sysfs_devname_to_devno(devname, NULL);
+
+ if (!devno)
+ err(EXIT_FAILURE, "failed to read devno");
+
+ is_part = sysfs_devno_has_attribute(devno, "partition");
+
+ printf("NAME: %s\n", devname);
+ printf("DEVNO: %u (%d:%d)\n", (unsigned int) devno, major(devno), minor(devno));
+ printf("DEVNOPATH: %s\n", sysfs_devno_path(devno, path, sizeof(path)));
+ printf("DEVPATH: %s\n", sysfs_devno_to_devpath(devno, path, sizeof(path)));
+ printf("PARTITION: %s\n", is_part ? "YES" : "NOT");
+
+ if (sysfs_init(&cxt, devno, NULL))
+ return EXIT_FAILURE;
+
+ len = sysfs_readlink(&cxt, NULL, path, sizeof(path) - 1);
+ if (len > 0) {
+ path[len] = '\0';
+ printf("DEVNOLINK: %s\n", path);
+ }
+
+ if (!is_part) {
+ printf("First 5 partitions:\n");
+ for (i = 1; i <= 5; i++) {
+ dev_t dev = sysfs_partno_to_devno(&cxt, i);
+ if (dev)
+ printf("\t#%d %d:%d\n", i, major(dev), minor(dev));
+ }
+ }
+
+ printf("SLAVES: %d\n", sysfs_count_dirents(&cxt, "slaves"));
+
+ if (sysfs_read_u64(&cxt, "size", &u64))
+ printf("read SIZE failed\n");
+ else
+ printf("SIZE: %jd\n", u64);
+
+ if (sysfs_read_int(&cxt, "queue/hw_sector_size", &i))
+ printf("read SECTOR failed\n");
+ else
+ printf("SECTOR: %d\n", i);
+
+ printf("DEVNAME: %s\n", sysfs_get_devname(&cxt, path, sizeof(path)));
+
+ sysfs_deinit(&cxt);
+ return EXIT_SUCCESS;
+}
+#endif
diff --git a/lib/tt.c b/lib/tt.c
new file mode 100644
index 0000000..6ec967d
--- /dev/null
+++ b/lib/tt.c
@@ -0,0 +1,998 @@
+/*
+ * TT - Table or Tree, features:
+ * - column width could be defined as absolute or relative to the terminal width
+ * - allows to truncate or wrap data in columns
+ * - prints tree if parent->child relation is defined
+ * - draws the tree by ASCII or UTF8 lines (depends on terminal setting)
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <termios.h>
+#include <ctype.h>
+
+#include "c.h"
+#include "nls.h"
+#include "widechar.h"
+#include "tt.h"
+#include "mbsalign.h"
+#include "ttyutils.h"
+
+struct tt_symbols {
+ const char *branch;
+ const char *vert;
+ const char *right;
+};
+
+static const struct tt_symbols ascii_tt_symbols = {
+ .branch = "|-",
+ .vert = "| ",
+ .right = "`-",
+};
+
+#ifdef HAVE_WIDECHAR
+#define UTF_V "\342\224\202" /* U+2502, Vertical line drawing char */
+#define UTF_VR "\342\224\234" /* U+251C, Vertical and right */
+#define UTF_H "\342\224\200" /* U+2500, Horizontal */
+#define UTF_UR "\342\224\224" /* U+2514, Up and right */
+
+static const struct tt_symbols utf8_tt_symbols = {
+ .branch = UTF_VR UTF_H,
+ .vert = UTF_V " ",
+ .right = UTF_UR UTF_H,
+};
+#endif /* !HAVE_WIDECHAR */
+
+#define is_last_column(_tb, _cl) \
+ list_last_entry(&(_cl)->cl_columns, &(_tb)->tb_columns)
+
+/*
+ * 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().
+ */
+static size_t mbs_safe_width(const char *s)
+{
+ mbstate_t st;
+ const char *p = s;
+ size_t width = 0;
+
+ memset(&st, 0, sizeof(st));
+
+ while (p && *p) {
+ if (iscntrl((unsigned char) *p)) {
+ width += 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;
+ width += (isprint((unsigned char) *p) ? 1 : 4);
+
+ } if (!iswprint(wc))
+ width += len * 4; /* hex encode whole sequence */
+ else
+ width += wcwidth(wc); /* number of cells */
+ p += len;
+ }
+#else
+ else if (!isprint((unsigned char) *p)) {
+ width += 4; /* *p encoded to \x?? */
+ p++;
+ } else {
+ width++;
+ p++;
+ }
+#endif
+ }
+
+ return width;
+}
+
+/*
+ * Returns allocated string where all control and non-printable chars are
+ * replaced with \x?? hex sequence.
+ */
+static char *mbs_safe_encode(const char *s, size_t *width)
+{
+ mbstate_t st;
+ const char *p = s;
+ char *res, *r;
+ size_t sz = s ? strlen(s) : 0;
+
+
+ if (!sz)
+ return NULL;
+
+ memset(&st, 0, sizeof(st));
+
+ res = malloc((sz * 4) + 1);
+ if (!res)
+ return NULL;
+
+ r = res;
+ *width = 0;
+
+ while (p && *p) {
+ if (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);
+ 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 res;
+}
+
+/*
+ * @flags: TT_FL_* flags (usually TT_FL_{ASCII,RAW})
+ *
+ * Returns: newly allocated table
+ */
+struct tt *tt_new_table(int flags)
+{
+ struct tt *tb;
+
+ tb = calloc(1, sizeof(struct tt));
+ if (!tb)
+ return NULL;
+
+ tb->flags = flags;
+ INIT_LIST_HEAD(&tb->tb_lines);
+ INIT_LIST_HEAD(&tb->tb_columns);
+
+#if defined(HAVE_WIDECHAR)
+ if (!(flags & TT_FL_ASCII) && !strcmp(nl_langinfo(CODESET), "UTF-8"))
+ tb->symbols = &utf8_tt_symbols;
+ else
+#endif
+ tb->symbols = &ascii_tt_symbols;
+
+ tb->first_run = TRUE;
+ return tb;
+}
+
+void tt_remove_lines(struct tt *tb)
+{
+ if (!tb)
+ return;
+
+ while (!list_empty(&tb->tb_lines)) {
+ struct tt_line *ln = list_entry(tb->tb_lines.next,
+ struct tt_line, ln_lines);
+ list_del(&ln->ln_lines);
+ free(ln->data);
+ free(ln);
+ }
+}
+
+void tt_free_table(struct tt *tb)
+{
+ if (!tb)
+ return;
+
+ tt_remove_lines(tb);
+
+ while (!list_empty(&tb->tb_columns)) {
+ struct tt_column *cl = list_entry(tb->tb_columns.next,
+ struct tt_column, cl_columns);
+ list_del(&cl->cl_columns);
+ free(cl);
+ }
+ free(tb);
+}
+
+
+/*
+ * @tb: table
+ * @name: column header
+ * @whint: column width hint (absolute width: N > 1; relative width: N < 1)
+ * @flags: usually TT_FL_{TREE,TRUNCATE}
+ *
+ * The column width is possible to define by three ways:
+ *
+ * @whint = 0..1 : relative width, percent of terminal width
+ *
+ * @whint = 1..N : absolute width, empty colum will be truncated to
+ * the column header width
+ *
+ * @whint = 1..N
+ * @flags = TT_FL_STRICTWIDTH
+ * : absolute width, empty colum won't be truncated
+ *
+ * The column is necessary to address (for example for tt_line_set_data()) by
+ * sequential number. The first defined column has the colnum = 0. For example:
+ *
+ * tt_define_column(tab, "FOO", 0.5, 0); // colnum = 0
+ * tt_define_column(tab, "BAR", 0.5, 0); // colnum = 1
+ * .
+ * .
+ * tt_line_set_data(line, 0, "foo-data"); // FOO column
+ * tt_line_set_data(line, 1, "bar-data"); // BAR column
+ *
+ * Returns: newly allocated column definition
+ */
+struct tt_column *tt_define_column(struct tt *tb, const char *name,
+ double whint, int flags)
+{
+ struct tt_column *cl;
+
+ if (!tb)
+ return NULL;
+ cl = calloc(1, sizeof(*cl));
+ if (!cl)
+ return NULL;
+
+ cl->name = name;
+ cl->width_hint = whint;
+ cl->flags = flags;
+ cl->seqnum = tb->ncols++;
+
+ if (flags & TT_FL_TREE)
+ tb->flags |= TT_FL_TREE;
+
+ INIT_LIST_HEAD(&cl->cl_columns);
+ list_add_tail(&cl->cl_columns, &tb->tb_columns);
+ return cl;
+}
+
+/*
+ * @tb: table
+ * @parent: parental line or NULL
+ *
+ * Returns: newly allocate line
+ */
+struct tt_line *tt_add_line(struct tt *tb, struct tt_line *parent)
+{
+ struct tt_line *ln = NULL;
+
+ if (!tb || !tb->ncols)
+ goto err;
+ ln = calloc(1, sizeof(*ln));
+ if (!ln)
+ goto err;
+ ln->data = calloc(tb->ncols, sizeof(char *));
+ if (!ln->data)
+ goto err;
+
+ ln->table = tb;
+ ln->parent = parent;
+ INIT_LIST_HEAD(&ln->ln_lines);
+ INIT_LIST_HEAD(&ln->ln_children);
+ INIT_LIST_HEAD(&ln->ln_branch);
+
+ list_add_tail(&ln->ln_lines, &tb->tb_lines);
+
+ if (parent)
+ list_add_tail(&ln->ln_children, &parent->ln_branch);
+ return ln;
+err:
+ free(ln);
+ return NULL;
+}
+
+/*
+ * @tb: table
+ * @colnum: number of column (0..N)
+ *
+ * Returns: pointer to column or NULL
+ */
+struct tt_column *tt_get_column(struct tt *tb, size_t colnum)
+{
+ struct list_head *p;
+
+ list_for_each(p, &tb->tb_columns) {
+ struct tt_column *cl =
+ list_entry(p, struct tt_column, cl_columns);
+ if (cl->seqnum == colnum)
+ return cl;
+ }
+ return NULL;
+}
+
+/*
+ * @ln: line
+ * @colnum: number of column (0..N)
+ * @data: printable data
+ *
+ * Stores data that will be printed to the table cell.
+ */
+int tt_line_set_data(struct tt_line *ln, int colnum, const char *data)
+{
+ struct tt_column *cl;
+
+ if (!ln)
+ return -1;
+ cl = tt_get_column(ln->table, colnum);
+ if (!cl)
+ return -1;
+
+ if (ln->data[cl->seqnum]) {
+ size_t sz = strlen(ln->data[cl->seqnum]);;
+ ln->data_sz = ln->data_sz > sz ? ln->data_sz - sz : 0;
+ }
+
+ ln->data[cl->seqnum] = data;
+ if (data)
+ ln->data_sz += strlen(data);
+ return 0;
+}
+
+int tt_line_set_userdata(struct tt_line *ln, void *data)
+{
+ if (!ln)
+ return -1;
+ ln->userdata = data;
+ return 0;
+}
+
+static char *line_get_ascii_art(struct tt_line *ln, char *buf, size_t *bufsz)
+{
+ const char *art;
+ size_t len;
+
+ if (!ln->parent)
+ return buf;
+
+ buf = line_get_ascii_art(ln->parent, buf, bufsz);
+ if (!buf)
+ return NULL;
+
+ if (list_last_entry(&ln->ln_children, &ln->parent->ln_branch))
+ art = " ";
+ else
+ art = ln->table->symbols->vert;
+
+ len = strlen(art);
+ if (*bufsz < len)
+ return NULL; /* no space, internal error */
+
+ memcpy(buf, art, len);
+ *bufsz -= len;
+ return buf + len;
+}
+
+static char *line_get_data(struct tt_line *ln, struct tt_column *cl,
+ char *buf, size_t bufsz)
+{
+ const char *data = ln->data[cl->seqnum];
+ const struct tt_symbols *sym;
+ char *p = buf;
+
+ memset(buf, 0, bufsz);
+
+ if (!data)
+ return NULL;
+ if (!(cl->flags & TT_FL_TREE)) {
+ strncpy(buf, data, bufsz);
+ buf[bufsz - 1] = '\0';
+ return buf;
+ }
+
+ /*
+ * Tree stuff
+ */
+ if (ln->parent) {
+ p = line_get_ascii_art(ln->parent, buf, &bufsz);
+ if (!p)
+ return NULL;
+ }
+
+ sym = ln->table->symbols;
+
+ if (!ln->parent)
+ snprintf(p, bufsz, "%s", data); /* root node */
+ else if (list_last_entry(&ln->ln_children, &ln->parent->ln_branch))
+ snprintf(p, bufsz, "%s%s", sym->right, data); /* last chaild */
+ else
+ snprintf(p, bufsz, "%s%s", sym->branch, data); /* any child */
+
+ return buf;
+}
+
+/*
+ * This function counts column width.
+ *
+ * For the TT_FL_NOEXTREMES columns is possible to call this function two
+ * times. The first pass counts width and average width. If the column
+ * contains too large fields (width greater than 2 * average) then the column
+ * is marked as "extreme". In the second pass all extreme fields are ignored
+ * and column width is counted from non-extreme fields only.
+ */
+static void count_column_width(struct tt *tb, struct tt_column *cl,
+ char *buf, size_t bufsz)
+{
+ struct list_head *lp;
+ int count = 0;
+ size_t sum = 0;
+
+ cl->width = 0;
+
+ list_for_each(lp, &tb->tb_lines) {
+ struct tt_line *ln = list_entry(lp, struct tt_line, ln_lines);
+ char *data = line_get_data(ln, cl, buf, bufsz);
+ size_t len = data ? mbs_safe_width(data) : 0;
+
+ if (len == (size_t) -1) /* ignore broken multibyte strings */
+ len = 0;
+
+ if (len > cl->width_max)
+ cl->width_max = len;
+
+ if (cl->is_extreme && len > cl->width_avg * 2)
+ continue;
+ else if (cl->flags & TT_FL_NOEXTREMES) {
+ sum += len;
+ count++;
+ }
+ if (len > cl->width)
+ cl->width = len;
+ }
+
+ if (count && cl->width_avg == 0) {
+ cl->width_avg = sum / count;
+
+ if (cl->width_max > cl->width_avg * 2)
+ cl->is_extreme = 1;
+ }
+
+ /* check and set minimal column width */
+ if (cl->name)
+ cl->width_min = mbs_safe_width(cl->name);
+
+ /* enlarge to minimal width */
+ if (cl->width < cl->width_min && !(cl->flags & TT_FL_STRICTWIDTH))
+ cl->width = cl->width_min;
+
+ /* use relative size for large columns */
+ else if (cl->width_hint >= 1 && cl->width < (size_t) cl->width_hint &&
+ cl->width_min < (size_t) cl->width_hint)
+
+ cl->width = (size_t) cl->width_hint;
+}
+
+/*
+ * This is core of the tt_* voodo...
+ */
+static void recount_widths(struct tt *tb, char *buf, size_t bufsz)
+{
+ struct list_head *p;
+ size_t width = 0; /* output width */
+ int trunc_only;
+ int extremes = 0;
+
+ /* set basic columns width
+ */
+ list_for_each(p, &tb->tb_columns) {
+ struct tt_column *cl =
+ list_entry(p, struct tt_column, cl_columns);
+
+ count_column_width(tb, cl, buf, bufsz);
+ width += cl->width + (is_last_column(tb, cl) ? 0 : 1);
+ extremes += cl->is_extreme;
+ }
+
+ /* reduce columns with extreme fields
+ */
+ if (width > tb->termwidth && extremes) {
+ list_for_each(p, &tb->tb_columns) {
+ struct tt_column *cl = list_entry(p, struct tt_column, cl_columns);
+ size_t org_width;
+
+ if (!cl->is_extreme)
+ continue;
+
+ org_width = cl->width;
+ count_column_width(tb, cl, buf, bufsz);
+
+ if (org_width > cl->width)
+ width -= org_width - cl->width;
+ else
+ extremes--; /* hmm... nothing reduced */
+ }
+ }
+
+ if (width < tb->termwidth) {
+ /* try to found extreme column which fits into available space
+ */
+ if (extremes) {
+ /* enlarge the first extreme column */
+ list_for_each(p, &tb->tb_columns) {
+ struct tt_column *cl =
+ list_entry(p, struct tt_column, cl_columns);
+ size_t add;
+
+ if (!cl->is_extreme)
+ continue;
+
+ if (cl->width_max - cl->width >
+ (tb->termwidth - width))
+ /* this column is tooo large, ignore */
+ continue;
+
+ add = tb->termwidth - width;
+ if (add && cl->width + add > cl->width_max)
+ add = cl->width_max - cl->width;
+
+ cl->width += add;
+ width += add;
+
+ if (width == tb->termwidth)
+ break;
+ }
+ }
+ if (width < tb->termwidth) {
+ /* enalarge the last column */
+ struct tt_column *cl = list_entry(
+ tb->tb_columns.prev, struct tt_column, cl_columns);
+
+ if (!(cl->flags & TT_FL_RIGHT) && tb->termwidth - width > 0) {
+ cl->width += tb->termwidth - width;
+ width = tb->termwidth;
+ }
+ }
+ }
+
+ /* bad, we have to reduce output width, this is done in two steps:
+ * 1/ reduce columns with a relative width and with truncate flag
+ * 2) reduce columns with a relative width without truncate flag
+ */
+ trunc_only = 1;
+ while (width > tb->termwidth) {
+ size_t org = width;
+
+ list_for_each(p, &tb->tb_columns) {
+ struct tt_column *cl =
+ list_entry(p, struct tt_column, cl_columns);
+
+ if (width <= tb->termwidth)
+ break;
+ if (cl->width_hint > 1 && !(cl->flags & TT_FL_TRUNC))
+ continue; /* never truncate columns with absolute sizes */
+ if (cl->flags & TT_FL_TREE)
+ continue; /* never truncate the tree */
+ if (trunc_only && !(cl->flags & TT_FL_TRUNC))
+ continue;
+ if (cl->width == cl->width_min)
+ continue;
+
+ /* truncate column with relative sizes */
+ if (cl->width_hint < 1 && cl->width > 0 && width > 0 &&
+ cl->width > cl->width_hint * tb->termwidth) {
+ cl->width--;
+ width--;
+ }
+ /* truncate column with absolute size */
+ if (cl->width_hint > 1 && cl->width > 0 && width > 0 &&
+ !trunc_only) {
+ cl->width--;
+ width--;
+ }
+
+ }
+ if (org == width) {
+ if (trunc_only)
+ trunc_only = 0;
+ else
+ break;
+ }
+ }
+
+/*
+ fprintf(stderr, "terminal: %d, output: %d\n", tb->termwidth, width);
+
+ list_for_each(p, &tb->tb_columns) {
+ struct tt_column *cl =
+ list_entry(p, struct tt_column, cl_columns);
+
+ fprintf(stderr, "width: %s=%zd [hint=%d, avg=%zd, max=%zd, extreme=%s]\n",
+ cl->name, cl->width,
+ cl->width_hint > 1 ? (int) cl->width_hint :
+ (int) (cl->width_hint * tb->termwidth),
+ cl->width_avg,
+ cl->width_max,
+ cl->is_extreme ? "yes" : "not");
+ }
+*/
+ return;
+}
+
+void tt_fputs_quoted(const char *data, FILE *out)
+{
+ const char *p;
+
+ fputc('"', out);
+ for (p = data; p && *p; p++) {
+ if ((unsigned char) *p == 0x22 || /* " */
+ (unsigned char) *p == 0x5c || /* \ */
+ !isprint((unsigned char) *p) ||
+ iscntrl((unsigned char) *p)) {
+
+ fprintf(out, "\\x%02x", (unsigned char) *p);
+ } else
+ fputc(*p, out);
+ }
+ fputc('"', out);
+}
+
+void tt_fputs_nonblank(const char *data, FILE *out)
+{
+ const char *p;
+
+ for (p = data; p && *p; p++) {
+ if (isblank((unsigned char) *p) ||
+ (unsigned char) *p == 0x5c || /* \ */
+ !isprint((unsigned char) *p) ||
+ iscntrl((unsigned char) *p)) {
+
+ fprintf(out, "\\x%02x", (unsigned char) *p);
+
+ } else
+ fputc(*p, out);
+ }
+}
+
+/*
+ * Prints data, data maybe be printed in more formats (raw, NAME=xxx pairs) and
+ * control and non-printable chars maybe encoded in \x?? hex encoding.
+ */
+static void print_data(struct tt *tb, struct tt_column *cl, char *data)
+{
+ size_t len = 0, i, width;
+ char *buf;
+
+ if (!data)
+ data = "";
+
+ /* raw mode */
+ if (tb->flags & TT_FL_RAW) {
+ tt_fputs_nonblank(data, stdout);
+ if (!is_last_column(tb, cl))
+ fputc(' ', stdout);
+ return;
+ }
+
+ /* NAME=value mode */
+ if (tb->flags & TT_FL_EXPORT) {
+ fprintf(stdout, "%s=", cl->name);
+ tt_fputs_quoted(data, stdout);
+ if (!is_last_column(tb, cl))
+ fputc(' ', stdout);
+ return;
+ }
+
+ /* note that 'len' and 'width' are number of cells, not bytes */
+ buf = mbs_safe_encode(data, &len);
+ data = buf;
+ if (!data)
+ data = "";
+
+ if (!len || len == (size_t) -1) {
+ len = 0;
+ data = NULL;
+ }
+ width = cl->width;
+
+ if (is_last_column(tb, cl) && len < width)
+ width = len;
+
+ /* truncate data */
+ if (len > width && (cl->flags & TT_FL_TRUNC)) {
+ if (data)
+ len = mbs_truncate(data, &width);
+ if (!data || len == (size_t) -1) {
+ len = 0;
+ data = NULL;
+ }
+ }
+ if (data) {
+ if (!(tb->flags & TT_FL_RAW) && (cl->flags & TT_FL_RIGHT)) {
+ size_t xw = cl->width;
+ fprintf(stdout, "%*s", (int) xw, data);
+ if (len < xw)
+ len = xw;
+ }
+ else
+ fputs(data, stdout);
+ }
+ for (i = len; i < width; i++)
+ fputc(' ', stdout); /* padding */
+
+ if (!is_last_column(tb, cl)) {
+ if (len > width && !(cl->flags & TT_FL_TRUNC)) {
+ fputc('\n', stdout);
+ for (i = 0; i <= (size_t) cl->seqnum; i++) {
+ struct tt_column *x = tt_get_column(tb, i);
+ printf("%*s ", -((int)x->width), " ");
+ }
+ } else
+ fputc(' ', stdout); /* columns separator */
+ }
+
+ free(buf);
+}
+
+static void print_line(struct tt_line *ln, char *buf, size_t bufsz)
+{
+ struct list_head *p;
+
+ /* set width according to the size of data
+ */
+ list_for_each(p, &ln->table->tb_columns) {
+ struct tt_column *cl =
+ list_entry(p, struct tt_column, cl_columns);
+
+ print_data(ln->table, cl, line_get_data(ln, cl, buf, bufsz));
+ }
+ fputc('\n', stdout);
+}
+
+static void print_header(struct tt *tb, char *buf, size_t bufsz)
+{
+ struct list_head *p;
+
+ if (!tb->first_run ||
+ (tb->flags & TT_FL_NOHEADINGS) ||
+ (tb->flags & TT_FL_EXPORT) ||
+ list_empty(&tb->tb_lines))
+ return;
+
+ /* set width according to the size of data
+ */
+ list_for_each(p, &tb->tb_columns) {
+ struct tt_column *cl =
+ list_entry(p, struct tt_column, cl_columns);
+
+ strncpy(buf, cl->name, bufsz);
+ buf[bufsz - 1] = '\0';
+ print_data(tb, cl, buf);
+ }
+ fputc('\n', stdout);
+}
+
+static void print_table(struct tt *tb, char *buf, size_t bufsz)
+{
+ struct list_head *p;
+
+ print_header(tb, buf, bufsz);
+
+ list_for_each(p, &tb->tb_lines) {
+ struct tt_line *ln = list_entry(p, struct tt_line, ln_lines);
+
+ print_line(ln, buf, bufsz);
+ }
+}
+
+static void print_tree_line(struct tt_line *ln, char *buf, size_t bufsz)
+{
+ struct list_head *p;
+
+ print_line(ln, buf, bufsz);
+
+ if (list_empty(&ln->ln_branch))
+ return;
+
+ /* print all children */
+ list_for_each(p, &ln->ln_branch) {
+ struct tt_line *chld =
+ list_entry(p, struct tt_line, ln_children);
+ print_tree_line(chld, buf, bufsz);
+ }
+}
+
+static void print_tree(struct tt *tb, char *buf, size_t bufsz)
+{
+ struct list_head *p;
+
+ print_header(tb, buf, bufsz);
+
+ list_for_each(p, &tb->tb_lines) {
+ struct tt_line *ln = list_entry(p, struct tt_line, ln_lines);
+
+ if (ln->parent)
+ continue;
+
+ print_tree_line(ln, buf, bufsz);
+ }
+}
+
+/*
+ * @tb: table
+ *
+ * Prints the table to stdout
+ */
+int tt_print_table(struct tt *tb)
+{
+ char *line;
+ size_t line_sz;
+ struct list_head *p;
+
+ if (!tb)
+ return -1;
+
+ if (tb->first_run && !tb->termwidth) {
+ tb->termwidth = get_terminal_width();
+ if (tb->termwidth <= 0)
+ tb->termwidth = 80;
+ }
+
+ line_sz = tb->termwidth;
+
+ list_for_each(p, &tb->tb_lines) {
+ struct tt_line *ln = list_entry(p, struct tt_line, ln_lines);
+ if (ln->data_sz > line_sz)
+ line_sz = ln->data_sz;
+ }
+
+ line_sz++; /* make a space for \0 */
+ line = malloc(line_sz);
+ if (!line)
+ return -1;
+
+ if (tb->first_run &&
+ !((tb->flags & TT_FL_RAW) || (tb->flags & TT_FL_EXPORT)))
+ recount_widths(tb, line, line_sz);
+
+ if (tb->flags & TT_FL_TREE)
+ print_tree(tb, line, line_sz);
+ else
+ print_table(tb, line, line_sz);
+
+ free(line);
+
+ tb->first_run = FALSE;
+ return 0;
+}
+
+#ifdef TEST_PROGRAM
+#include <errno.h>
+
+enum { MYCOL_NAME, MYCOL_FOO, MYCOL_BAR, MYCOL_PATH };
+
+int main(int argc, char *argv[])
+{
+ struct tt *tb;
+ struct tt_line *ln, *pr, *root;
+ int flags = 0, notree = 0, i;
+
+ if (argc == 2 && !strcmp(argv[1], "--help")) {
+ printf("%s [--ascii | --raw | --list]\n",
+ program_invocation_short_name);
+ return EXIT_SUCCESS;
+ } else if (argc == 2 && !strcmp(argv[1], "--ascii")) {
+ flags |= TT_FL_ASCII;
+ } else if (argc == 2 && !strcmp(argv[1], "--raw")) {
+ flags |= TT_FL_RAW;
+ notree = 1;
+ } else if (argc == 2 && !strcmp(argv[1], "--export")) {
+ flags |= TT_FL_EXPORT;
+ notree = 1;
+ } else if (argc == 2 && !strcmp(argv[1], "--list"))
+ notree = 1;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+
+ tb = tt_new_table(flags);
+ if (!tb)
+ err(EXIT_FAILURE, "table initialization failed");
+
+ tt_define_column(tb, "NAME", 0.3, notree ? 0 : TT_FL_TREE);
+ tt_define_column(tb, "FOO", 0.3, TT_FL_TRUNC);
+ tt_define_column(tb, "BAR", 0.3, 0);
+ tt_define_column(tb, "PATH", 0.3, 0);
+
+ for (i = 0; i < 2; i++) {
+ root = ln = tt_add_line(tb, NULL);
+ tt_line_set_data(ln, MYCOL_NAME, "AAA");
+ tt_line_set_data(ln, MYCOL_FOO, "a-foo-foo");
+ tt_line_set_data(ln, MYCOL_BAR, "barBar-A");
+ tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA");
+
+ pr = ln = tt_add_line(tb, ln);
+ tt_line_set_data(ln, MYCOL_NAME, "AAA.A");
+ tt_line_set_data(ln, MYCOL_FOO, "a.a-foo-foo");
+ tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A");
+ tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A");
+
+ ln = tt_add_line(tb, pr);
+ tt_line_set_data(ln, MYCOL_NAME, "AAA.A.AAA");
+ tt_line_set_data(ln, MYCOL_FOO, "a.a.a-foo-foo");
+ tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A.A");
+ tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A/AAA");
+
+ ln = tt_add_line(tb, root);
+ tt_line_set_data(ln, MYCOL_NAME, "AAA.B");
+ tt_line_set_data(ln, MYCOL_FOO, "a.b-foo-foo");
+ tt_line_set_data(ln, MYCOL_BAR, "barBar-A.B");
+ tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/B");
+
+ ln = tt_add_line(tb, pr);
+ tt_line_set_data(ln, MYCOL_NAME, "AAA.A.BBB");
+ tt_line_set_data(ln, MYCOL_FOO, "a.a.b-foo-foo");
+ tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A.BBB");
+ tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A/BBB");
+
+ ln = tt_add_line(tb, pr);
+ tt_line_set_data(ln, MYCOL_NAME, "AAA.A.CCC");
+ tt_line_set_data(ln, MYCOL_FOO, "a.a.c-foo-foo");
+ tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A.CCC");
+ tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A/CCC");
+
+ ln = tt_add_line(tb, root);
+ tt_line_set_data(ln, MYCOL_NAME, "AAA.C");
+ tt_line_set_data(ln, MYCOL_FOO, "a.c-foo-foo");
+ tt_line_set_data(ln, MYCOL_BAR, "barBar-A.C");
+ tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/C");
+ }
+
+ tt_print_table(tb);
+ tt_free_table(tb);
+
+ return EXIT_SUCCESS;
+}
+#endif
diff --git a/lib/wholedisk.c b/lib/wholedisk.c
new file mode 100644
index 0000000..4a53052
--- /dev/null
+++ b/lib/wholedisk.c
@@ -0,0 +1,57 @@
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "blkdev.h"
+#include "wholedisk.h"
+
+int is_whole_disk_fd(int fd, const char *name)
+{
+#ifdef HDIO_GETGEO
+ if (fd != -1) {
+ struct hd_geometry geometry;
+ int i = ioctl(fd, HDIO_GETGEO, &geometry);
+ if (i == 0)
+ return geometry.start == 0;
+ }
+#endif
+ /*
+ * The "silly heuristic" is still sexy for us, because
+ * for example Xen doesn't implement HDIO_GETGEO for virtual
+ * block devices (/dev/xvda).
+ *
+ * -- kzak@redhat.com (23-Feb-2006)
+ */
+ while (*name)
+ name++;
+ return !isdigit(name[-1]);
+}
+
+int is_whole_disk(const char *name)
+{
+ int fd = -1, res = 0;
+#ifdef HDIO_GETGEO
+ fd = open(name, O_RDONLY);
+ if (fd != -1)
+#endif
+ res = is_whole_disk_fd(fd, name);
+
+ if (fd != -1)
+ close(fd);
+ return res;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char **argv)
+{
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <device>\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ printf("%s: is%s whole disk\n", argv[1],
+ is_whole_disk(argv[1]) ? "" : " NOT");
+ exit(EXIT_SUCCESS);
+}
+#endif
diff --git a/lib/xgetpass.c b/lib/xgetpass.c
new file mode 100644
index 0000000..ba20894
--- /dev/null
+++ b/lib/xgetpass.c
@@ -0,0 +1,46 @@
+/*
+ * A function to read the passphrase either from the terminal or from
+ * an open file descriptor.
+ *
+ * Public domain.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+
+#include "c.h"
+#include "xgetpass.h"
+
+char *xgetpass(int pfd, const char *prompt)
+{
+ char *pass = NULL;
+ int len = 0, i;
+
+ if (pfd < 0) /* terminal */
+ return getpass(prompt);
+
+ for (i=0; ; i++) {
+ if (i >= len-1) {
+ char *tmppass = pass;
+ len += 128;
+
+ pass = realloc(tmppass, len);
+ if (!pass) {
+ pass = tmppass; /* the old buffer hasn't changed */
+ break;
+ }
+ }
+ if (pass && (read(pfd, pass + i, 1) != 1 ||
+ pass[i] == '\n' || pass[i] == 0))
+ break;
+ }
+
+ if (pass)
+ pass[i] = '\0';
+ return pass;
+}
+