]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lib/path: new implementation
authorKarel Zak <kzak@redhat.com>
Wed, 9 May 2018 13:54:12 +0000 (15:54 +0200)
committerKarel Zak <kzak@redhat.com>
Thu, 21 Jun 2018 09:58:10 +0000 (11:58 +0200)
The goal is to avoid duplicate code in path.c and sysfs.c and make it
possible to define prefix for paths for all sysfs and procfs based
utils. Now we have /proc snapshots (for tests) for lscpu only. It
would be nice to have the same (for sysfs) for lsblk and another tools.

* very simple API to read numbers, strings and symlinks

* based on openat()

     pc = ul_new_path("/sys/block/sda");
     ul_path_read_u64(pc, &size, "size");
     ul_path_read_u64(pc, &lsz, "queue/logical_block_size");

* printf-like API to generate paths, for example:

     ul_path_readf_u64(pc, &num, "sda%d/size", partno)

* allow to define prefix to redirect hardcoded paths to another
  location, for example:

     pc = ul_new_path("/sys/block/sda");
     ul_path_set_prefix(pc, "/my/regression/dump");
     ul_path_read_u64(pc, &num, "size");

  to read /my/regression/dump/sys/block/sda/size

* allow to extend the API by "dialects", for example for sysfs:

     pc = ul_new_path(NULL);
     sysfs_blkdev_init_path(pc, devno, NULL);

  and use ul_path_* functions to read from @pc initialized by
  sysfs_blkdev_init_path()

* add test_path binary

Signed-off-by: Karel Zak <kzak@redhat.com>
include/path.h
lib/Makemodule.am
lib/path.c

index 4be01095cd778aca265ad16a064f7f37f8ebbd9c..8b000e63a140c99d82414210c38427414245d2a9 100644 (file)
 
 #include <stdio.h>
 #include <stdint.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+struct path_cxt {
+       int     dir_fd;
+       char    *dir_path;
+
+       int     refcount;
+
+       char *prefix;
+       char path_buffer[PATH_MAX];
+
+       void    *dialect;
+       void    (*free_dialect)(struct path_cxt *);
+       int     (*redirect_on_enoent)(struct path_cxt *, const char *, int *);
+};
+
+struct path_cxt *ul_new_path(const char *dir);
+void ul_unref_path(struct path_cxt *pc);
+void ul_ref_path(struct path_cxt *pc);
+
+int ul_path_set_prefix(struct path_cxt *pc, const char *prefix);
+const char *ul_path_get_prefix(struct path_cxt *pc);
+
+int ul_path_set_dir(struct path_cxt *pc, const char *dir);
+const char *ul_path_get_dir(struct path_cxt *pc);
+
+int ul_path_set_dialect(struct path_cxt *pc, void *data, void free_data(struct path_cxt *));
+void *ul_path_get_dialect(struct path_cxt *pc);
+
+int ul_path_set_enoent_redirect(struct path_cxt *pc, int (*func)(struct path_cxt *, const char *, int *));
+int ul_path_get_dirfd(struct path_cxt *pc);
+
+int ul_path_access(struct path_cxt *pc, int mode, const char *path);
+int ul_path_accessf(struct path_cxt *pc, int mode, const char *path, ...)
+                               __attribute__ ((__format__ (__printf__, 3, 4)));
+
+int ul_path_open(struct path_cxt *pc, int flags, const char *path);
+int ul_path_openf(struct path_cxt *pc, int flags, const char *path, ...)
+                               __attribute__ ((__format__ (__printf__, 3, 4)));
+int ul_path_vopenf(struct path_cxt *pc, int flags, const char *path, va_list ap);
+
+FILE *ul_path_fopen(struct path_cxt *pc, const char *mode, const char *path);
+FILE *ul_path_fopenf(struct path_cxt *pc, const char *mode, const char *path, ...)
+                               __attribute__ ((__format__ (__printf__, 3, 4)));
+FILE *ul_path_vfopenf(struct path_cxt *pc, const char *mode, const char *path, va_list ap);
+
+DIR *ul_path_opendir(struct path_cxt *pc, const char *path);
+DIR *ul_path_vopendirf(struct path_cxt *pc, const char *path, va_list ap);
+DIR *ul_path_opendirf(struct path_cxt *pc, const char *path, ...)
+                               __attribute__ ((__format__ (__printf__, 2, 3)));
+
+ssize_t ul_path_readlink(struct path_cxt *pc, char *buf, size_t bufsiz, const char *path);
+ssize_t ul_path_readlinkf(struct path_cxt *pc, char *buf, size_t bufsiz, const char *path, ...)
+                               __attribute__ ((__format__ (__printf__, 4, 5)));
+
+int ul_path_read(struct path_cxt *pc, char *buf, size_t len, const char *path);
+int ul_path_vreadf(struct path_cxt *pc, char *buf, size_t len, const char *path, va_list ap);
+int ul_path_readf(struct path_cxt *pc, char *buf, size_t len, const char *path, ...)
+                               __attribute__ ((__format__ (__printf__, 4, 5)));
+
+int ul_path_read_string(struct path_cxt *pc, char **str, const char *path);
+int ul_path_readf_string(struct path_cxt *pc, char **str, const char *path, ...)
+                               __attribute__ ((__format__ (__printf__, 3, 4)));
+
+int ul_path_scanf(struct path_cxt *pc, const char *path, const char *fmt, ...);
+int ul_path_scanff(struct path_cxt *pc, const char *path, va_list ap, const char *fmt, ...)
+                               __attribute__ ((__format__ (__scanf__, 4, 5)));
+
+int ul_path_read_majmin(struct path_cxt *pc, dev_t *res, const char *path);
+int ul_path_readf_majmin(struct path_cxt *pc, dev_t *res, const char *path, ...)
+                               __attribute__ ((__format__ (__printf__, 3, 4)));
+
+int ul_path_read_u32(struct path_cxt *pc, uint32_t *res, const char *path);
+int ul_path_readf_u32(struct path_cxt *pc, uint32_t *res, const char *path, ...)
+                               __attribute__ ((__format__ (__printf__, 3, 4)));
+
+int ul_path_read_s32(struct path_cxt *pc, int32_t *res, const char *path);
+int ul_path_readf_s32(struct path_cxt *pc, int32_t *res, const char *path, ...)
+                               __attribute__ ((__format__ (__printf__, 3, 4)));
+
+int ul_path_read_u64(struct path_cxt *pc, uint64_t *res, const char *path);
+int ul_path_readf_u64(struct path_cxt *pc, uint64_t *res, const char *path, ...)
+                               __attribute__ ((__format__ (__printf__, 3, 4)));
+
+int ul_path_read_s64(struct path_cxt *pc, int64_t *res, const char *path);
+int ul_path_readf_s64(struct path_cxt *pc, int64_t *res, const char *path, ...)
+                               __attribute__ ((__format__ (__printf__, 3, 4)));
+
+int ul_path_write_string(struct path_cxt *pc, const char *str, const char *path);
+int ul_path_writef_string(struct path_cxt *pc, const char *str, const char *path, ...)
+                               __attribute__ ((__format__ (__printf__, 3, 4)));
+
+int ul_path_write_u64(struct path_cxt *pc, uint64_t num, const char *path);
+int ul_path_writef_u64(struct path_cxt *pc, uint64_t num, const char *path, ...)
+                               __attribute__ ((__format__ (__printf__, 3, 4)));
+
+int ul_path_count_dirents(struct path_cxt *pc, const char *path);
+int ul_path_countf_dirents(struct path_cxt *pc, const char *path, ...)
+                               __attribute__ ((__format__ (__printf__, 2, 3)));
+
 
-/* Returns a pointer to a static buffer which may be destroyed by any later
-path_* function call. NULL means error and errno will be set. */
-/* Returns: 0 on success, sets errno on error. */
-extern int path_set_prefix(const char *)
-                       __attribute__((warn_unused_result));
-
-extern const char *path_get(const char *path, ...)
-                       __attribute__ ((__format__ (__printf__, 1, 2)));
-
-extern FILE *path_fopen(const char *mode, int exit_on_err, const char *path, ...)
-                       __attribute__ ((__format__ (__printf__, 3, 4)));
-extern void path_read_str(char *result, size_t len, const char *path, ...)
-                       __attribute__ ((__format__ (__printf__, 3, 4)));
-extern int path_write_str(const char *str, const char *path, ...)
-                        __attribute__ ((__format__ (__printf__, 2, 3)));
-extern int path_read_s32(const char *path, ...)
-                       __attribute__ ((__format__ (__printf__, 1, 2)));
-extern uint64_t path_read_u64(const char *path, ...)
-                       __attribute__ ((__format__ (__printf__, 1, 2)));
-
-extern int path_exist(const char *path, ...)
-                     __attribute__ ((__format__ (__printf__, 1, 2)));
 
 #ifdef HAVE_CPU_SET_T
 # include "cpuset.h"
+int ul_path_readf_cpuset(struct path_cxt *pc, cpu_set_t **set, int maxcpus, const char *path, ...)
+                               __attribute__ ((__format__ (__printf__, 4, 5)));
 
-extern cpu_set_t *path_read_cpuset(int, const char *path, ...)
-                             __attribute__ ((__format__ (__printf__, 2, 3)));
-extern cpu_set_t *path_read_cpulist(int, const char *path, ...)
-                              __attribute__ ((__format__ (__printf__, 2, 3)));
-
+int ul_path_readf_cpulist(struct path_cxt *pc, cpu_set_t **set, int maxcpus, const char *path, ...)
+                               __attribute__ ((__format__ (__printf__, 4, 5)));
 #endif /* HAVE_CPU_SET_T */
 #endif /* UTIL_LINUX_PATH_H */
index 2166d038423d6657597a8d6fbcc5198d2ecaadd6..2081ac927367ee130a0fccc9cfffede39422b104 100644 (file)
@@ -18,7 +18,6 @@ libcommon_la_SOURCES = \
        lib/md5.c \
        lib/pager.c \
        lib/parse-date.y \
-       lib/path.c \
        lib/pwdutils.c \
        lib/randutils.c \
        lib/setproctitle.c \
@@ -49,6 +48,7 @@ libcommon_la_SOURCES += lib/cpuset.c
 endif
 
 if HAVE_OPENAT
+libcommon_la_SOURCES += lib/path.c
 libcommon_la_SOURCES += lib/procutils.c
 libcommon_la_SOURCES += lib/sysfs.c
 endif
@@ -97,6 +97,7 @@ endif
 
 if HAVE_OPENAT
 check_PROGRAMS += test_procutils
+check_PROGRAMS += test_path
 endif
 
 test_ttyutils_SOURCES = lib/ttyutils.c
@@ -129,6 +130,15 @@ test_procutils_SOURCES = lib/procutils.c
 test_procutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PROCUTILS
 endif
 
+if HAVE_OPENAT
+test_path_SOURCES = lib/path.c lib/fileutils.c
+if HAVE_CPU_SET_T
+test_path_SOURCES += lib/cpuset.c
+endif
+test_path_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PATH
+test_path_LDADD = $(LDADD)
+endif
+
 if LINUX
 test_cpuset_SOURCES = lib/cpuset.c
 test_cpuset_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_CPUSET
index e8cfa557afec06bb3a5540bbd690999463355ab5..d6cfe1453d516dc054876389aba80569bf24a62f 100644 (file)
@@ -2,24 +2,11 @@
  * Simple functions to access files. Paths can be globally prefixed to read
  * data from an alternative source (e.g. a /proc dump for regression tests).
  *
- * Copyright (C) 2008 Cai Qian <qcai@redhat.com>
- * Copyright (C) 2008-2012 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.
- */
+ * No copyright is claimed.  This code is in the public domain; do with
+ * it what you wish.
 
+ * Written by Karel Zak <kzak@redhat.com> [February 2018]
+ */
 #include <stdarg.h>
 #include <string.h>
 #include <unistd.h>
 #include <inttypes.h>
 #include <errno.h>
 
+#include "c.h"
+#include "fileutils.h"
 #include "all-io.h"
 #include "path.h"
-#include "nls.h"
-#include "c.h"
 
-static size_t prefixlen;
-static char pathbuf[PATH_MAX];
+struct path_cxt *ul_new_path(const char *dir)
+{
+       struct path_cxt *pc = calloc(1, sizeof(*pc));
+
+       if (!pc)
+               return NULL;
+
+       pc->refcount = 1;
+       pc->dir_fd = -1;
+
+       if (dir) {
+               pc->dir_path = strdup(dir);
+               if (!pc->dir_path)
+                       goto fail;
+       }
+       return pc;
+fail:
+       ul_unref_path(pc);
+       return NULL;
+}
+
+void ul_ref_path(struct path_cxt *pc)
+{
+       if (pc)
+               pc->refcount++;
+}
 
-int
-path_set_prefix(const char *prefix)
+void ul_unref_path(struct path_cxt *pc)
 {
-       size_t len = strlen(prefix);
+       if (!pc)
+               return;
 
-       if (len >= sizeof(pathbuf) - 1) {
-               errno = ENAMETOOLONG;
-               return -1;
+       pc->refcount--;
+
+       if (pc->refcount <= 0) {
+               if (pc->dialect)
+                       pc->free_dialect(pc);
+               if (pc->dir_fd >= 0)
+                       close(pc->dir_fd);
+               free(pc->dir_path);
+               free(pc->prefix);
+               free(pc);
        }
-       prefixlen = len;
-       strcpy(pathbuf, prefix);
+}
+
+int ul_path_set_prefix(struct path_cxt *pc, const char *prefix)
+{
+       char *p = NULL;
+
+       assert(pc->dir_fd < 0);
+
+       if (prefix) {
+               p = strdup(prefix);
+               if (!p)
+                       return -ENOMEM;
+       }
+
+       free(pc->prefix);
+       pc->prefix = p;
        return 0;
 }
 
-static const char *
-path_vcreate(const char *path, va_list ap)
+const char *ul_path_get_prefix(struct path_cxt *pc)
 {
-       int rc = vsnprintf(
-               pathbuf + prefixlen, sizeof(pathbuf) - prefixlen, path, ap);
+       return pc ? pc->prefix : NULL;
+}
+
+int ul_path_set_dir(struct path_cxt *pc, const char *dir)
+{
+       char *p = NULL;
+
+       if (dir) {
+               p = strdup(dir);
+               if (!p)
+                       return -ENOMEM;
+       }
 
+       if (pc->dir_fd >= 0) {
+               close(pc->dir_fd);
+               pc->dir_fd = -1;
+       }
+
+       free(pc->dir_path);
+       pc->dir_path = p;
+       return 0;
+}
+
+const char *ul_path_get_dir(struct path_cxt *pc)
+{
+       return pc ? pc->dir_path : NULL;
+}
+
+int ul_path_set_dialect(struct path_cxt *pc, void *data, void free_data(struct path_cxt *))
+{
+       pc->dialect = data;
+       pc->free_dialect = free_data;
+       return 0;
+}
+
+void *ul_path_get_dialect(struct path_cxt *pc)
+{
+       return pc ? pc->dialect : NULL;
+}
+
+int ul_path_set_enoent_redirect(struct path_cxt *pc, int (*func)(struct path_cxt *, const char *, int *))
+{
+       pc->redirect_on_enoent = func;
+       return 0;
+}
+
+static const char *get_absdir(struct path_cxt *pc)
+{
+       int rc;
+
+       if (!pc->prefix)
+               return pc->dir_path;
+
+       rc = snprintf(pc->path_buffer, sizeof(pc->path_buffer), "%s/%s", pc->prefix, pc->dir_path);
        if (rc < 0)
                return NULL;
-       if ((size_t)rc >= sizeof(pathbuf)) {
+       if ((size_t)rc >= sizeof(pc->path_buffer)) {
                errno = ENAMETOOLONG;
                return NULL;
        }
-       return pathbuf;
+
+       return pc->path_buffer;
+}
+
+
+int ul_path_get_dirfd(struct path_cxt *pc)
+{
+       assert(pc);
+       assert(pc->dir_path);
+
+       if (pc->dir_fd < 0) {
+               const char *path = get_absdir(pc);
+               if (!path)
+                       return -errno;
+               pc->dir_fd = open(path, O_RDONLY|O_CLOEXEC);
+       }
+
+       return pc->dir_fd;
+}
+
+static const char *ul_path_mkpath(struct path_cxt *pc, const char *path, va_list ap)
+{
+       int rc = vsnprintf(pc->path_buffer, sizeof(pc->path_buffer), path, ap);
+
+       if (rc < 0)
+               return NULL;
+
+       if ((size_t)rc >= sizeof(pc->path_buffer)) {
+               errno = ENAMETOOLONG;
+               return NULL;
+       }
+
+       return pc->path_buffer;
+}
+
+int ul_path_access(struct path_cxt *pc, int mode, const char *path)
+{
+       int dir, rc;
+
+       dir = ul_path_get_dirfd(pc);
+       if (dir < 0)
+               return dir;
+
+       rc = faccessat(dir, path, mode, 0);
+
+       if (rc && errno == ENOENT
+           && pc->redirect_on_enoent
+           && pc->redirect_on_enoent(pc, path, &dir) == 0)
+               rc = faccessat(dir, path, mode, 0);
+
+       return rc;
 }
 
-const char *
-path_get(const char *path, ...)
+int ul_path_accessf(struct path_cxt *pc, int mode, const char *path, ...)
 {
+       va_list ap;
        const char *p;
+
+       va_start(ap, path);
+       p = ul_path_mkpath(pc, path, ap);
+       va_end(ap);
+
+       return ul_path_access(pc, mode, p);
+}
+
+int ul_path_open(struct path_cxt *pc, int flags, const char *path)
+{
+       int dir, fd;
+
+       dir = ul_path_get_dirfd(pc);
+       if (dir < 0)
+               return dir;
+
+       fd = openat(dir, path, flags);
+
+       if (fd < 0 && errno == ENOENT
+           && pc->redirect_on_enoent
+           && pc->redirect_on_enoent(pc, path, &dir) == 0)
+               fd = openat(dir, path, flags);
+
+       return fd;
+}
+
+int ul_path_vopenf(struct path_cxt *pc, int flags, const char *path, va_list ap)
+{
+       const char *p;
+
+       p = ul_path_mkpath(pc, path, ap);
+       if (!p)
+               return -errno;
+
+       return ul_path_open(pc, flags, p);
+}
+
+int ul_path_openf(struct path_cxt *pc, int flags, const char *path, ...)
+{
        va_list ap;
+       int rc;
 
        va_start(ap, path);
-       p = path_vcreate(path, ap);
+       rc = ul_path_vopenf(pc, flags, path, ap);
        va_end(ap);
 
-       return p;
+       return rc;
 }
 
-static FILE *
-path_vfopen(const char *mode, int exit_on_error, const char *path, va_list ap)
+/*
+ * Maybe stupid, but good enough ;-)
+ */
+static int mode2flags(const char *mode)
 {
-       FILE *f;
-       const char *p = path_vcreate(path, ap);
+       int flags = 0;
+       const char *p;
+
+       for (p = mode; p && *p; p++) {
+               if (*p == 'r' && *(p + 1) == '+')
+                       flags |= O_RDWR;
+               else if (*p == 'r')
+                       flags |= O_RDONLY;
+
+               else if (*p == 'w' && *(p + 1) == '+')
+                       flags |= O_RDWR | O_TRUNC;
+               else if (*p == 'w')
+                       flags |= O_WRONLY | O_TRUNC;
+
+               else if (*p == 'a' && *(p + 1) == '+')
+                       flags |= O_RDWR | O_APPEND;
+               else if (*p == 'a')
+                       flags |= O_WRONLY | O_APPEND;
+#ifdef O_CLOEXEC
+               else if (*p == *UL_CLOEXECSTR)
+                       flags |= O_CLOEXEC;
+#endif
+       }
+
+       return flags;
+}
+
+FILE *ul_path_fopen(struct path_cxt *pc, const char *mode, const char *path)
+{
+       int flags = mode2flags(mode);
+       int fd = ul_path_open(pc, flags, path);
+
+       if (fd < 0)
+               return NULL;
+
+       return fdopen(fd, mode);
+}
+
+
+FILE *ul_path_vfopenf(struct path_cxt *pc, const char *mode, const char *path, va_list ap)
+{
+       const char *p;
+
+       p = ul_path_mkpath(pc, path, ap);
        if (!p)
-               goto err;
+               return NULL;
 
-       f = fopen(p, mode);
-       if (!f)
-               goto err;
+       return ul_path_fopen(pc, mode, p);
+}
+
+FILE *ul_path_fopenf(struct path_cxt *pc, const char *mode, const char *path, ...)
+{
+       FILE *f;
+       va_list ap;
+
+       va_start(ap, path);
+       f = ul_path_vfopenf(pc, mode, path, ap);
+       va_end(ap);
 
        return f;
-err:
-       if (exit_on_error)
-               err(EXIT_FAILURE, _("cannot open %s"), p ? p : "path");
-       return NULL;
 }
 
-static int
-path_vopen(int flags, const char *path, va_list ap)
+/*
+ * Open directory @path in read-onl mode. If the path is NULL then duplicate FD
+ * to the directory addressed by @pc.
+ */
+DIR *ul_path_opendir(struct path_cxt *pc, const char *path)
 {
+       DIR *dir;
+       int fd = -1;
+
+       if (path)
+               fd = ul_path_open(pc, O_RDONLY|O_CLOEXEC, path);
+       else if (pc->dir_path)
+               fd = dup_fd_cloexec(ul_path_get_dirfd(pc), STDERR_FILENO + 1);
+
+       if (fd < 0)
+               return NULL;
+
+       dir = fdopendir(fd);
+       if (!dir) {
+               close(fd);
+               return NULL;
+       }
+       if (!path)
+                rewinddir(dir);
+       return dir;
+}
+
+
+/*
+ * Open directory @path in read-onl mode. If the path is NULL then duplicate FD
+ * to the directory addressed by @pc.
+ */
+DIR *ul_path_vopendirf(struct path_cxt *pc, const char *path, va_list ap)
+{
+       const char *p;
+
+       p = ul_path_mkpath(pc, path, ap);
+       if (!p)
+               return NULL;
+
+       return ul_path_opendir(pc, p);
+}
+
+/*
+ * Open directory @path in read-onl mode. If the path is NULL then duplicate FD
+ * to the directory addressed by @pc.
+ */
+DIR *ul_path_opendirf(struct path_cxt *pc, const char *path, ...)
+{
+       va_list ap;
+       DIR *dir;
+
+       va_start(ap, path);
+       dir = ul_path_vopendirf(pc, path, ap);
+       va_end(ap);
+
+       return dir;
+}
+
+/*
+ * If @path is NULL then readlink is called on @pc directory.
+ */
+ssize_t ul_path_readlink(struct path_cxt *pc, char *buf, size_t bufsiz, const char *path)
+{
+       int dirfd;
+
+       if (!path) {
+               const char *p = get_absdir(pc);
+               if (!p)
+                       return -errno;
+               return readlink(p, buf, bufsiz);
+       }
+
+       dirfd = ul_path_get_dirfd(pc);
+       if (dirfd < 0)
+               return dirfd;
+
+       return readlinkat(dirfd, path, buf, bufsiz);
+}
+
+/*
+ * If @path is NULL then readlink is called on @pc directory.
+ */
+ssize_t ul_path_readlinkf(struct path_cxt *pc, char *buf, size_t bufsiz, const char *path, ...)
+{
+       const char *p;
+       va_list ap;
+
+       va_start(ap, path);
+       p = ul_path_mkpath(pc, path, ap);
+       va_end(ap);
+
+       if (!p)
+               return -errno;
+
+       return ul_path_readlink(pc, buf, bufsiz, p);
+}
+
+int ul_path_read(struct path_cxt *pc, char *buf, size_t len, const char *path)
+{
+       int rc, errsv;
        int fd;
-       const char *p = path_vcreate(path, ap);
+
+       fd = ul_path_open(pc, O_RDONLY|O_CLOEXEC, path);
+       if (fd < 0)
+               return -errno;
+
+       rc = read_all(fd, buf, len);
+
+       errsv = errno;
+       close(fd);
+       errno = errsv;
+       return rc;
+}
+
+int ul_path_vreadf(struct path_cxt *pc, char *buf, size_t len, const char *path, va_list ap)
+{
+       const char *p;
+
+       p = ul_path_mkpath(pc, path, ap);
        if (!p)
-               goto err;
+               return -EINVAL;
 
-       fd = open(p, flags);
-       if (fd == -1)
-               goto err;
+       return ul_path_read(pc, buf, len, p);
+}
 
-       return fd;
-err:
-       err(EXIT_FAILURE, _("cannot open %s"), p ? p : "path");
+int ul_path_readf(struct path_cxt *pc, char *buf, size_t len, const char *path, ...)
+{
+       va_list ap;
+       int rc;
+
+       va_start(ap, path);
+       rc = ul_path_vreadf(pc, buf, len, path, ap);
+       va_end(ap);
+
+       return rc;
+}
+
+
+/*
+ * Returns newly allocated buffer with data from file. Maximal size is BUFSIZ
+ * (send patch if you need something bigger;-)
+ *
+ */
+int ul_path_read_string(struct path_cxt *pc, char **str, const char *path)
+{
+       char buf[BUFSIZ];
+       int rc;
+
+       *str = NULL;
+
+       rc = ul_path_read(pc, buf, sizeof(buf) - 1, path);
+       if (rc < 0 || !str)
+               return rc;;
+
+       buf[rc] = '\0';
+       *str = strdup(buf);
+       if (!*str)
+               rc = -ENOMEM;
+
+       return rc;
 }
 
-FILE *
-path_fopen(const char *mode, int exit_on_error, const char *path, ...)
+int ul_path_readf_string(struct path_cxt *pc, char **str, const char *path, ...)
 {
-       FILE *fd;
+       const char *p;
        va_list ap;
 
        va_start(ap, path);
-       fd = path_vfopen(mode, exit_on_error, path, ap);
+       p = ul_path_mkpath(pc, path, ap);
        va_end(ap);
 
-       return fd;
+       if (!p)
+               return -EINVAL;
+
+       return ul_path_read_string(pc, str, p);
 }
 
-void
-path_read_str(char *result, size_t len, const char *path, ...)
+int ul_path_scanf(struct path_cxt *pc, const char *path, const char *fmt, ...)
 {
-       FILE *fd;
+       FILE *f;
+       va_list fmt_ap;
+       int rc;
+
+       f = ul_path_fopen(pc, "r" UL_CLOEXECSTR, path);
+       if (!f)
+               return -EINVAL;
+
+       va_start(fmt_ap, fmt);
+       rc = vfscanf(f, fmt, fmt_ap);
+       va_end(fmt_ap);
+
+       fclose(f);
+       return rc;
+}
+
+int ul_path_scanff(struct path_cxt *pc, const char *path, va_list ap, const char *fmt, ...)
+{
+       FILE *f;
+       va_list fmt_ap;
+       int rc;
+
+       f = ul_path_vfopenf(pc, "r" UL_CLOEXECSTR, path, ap);
+       if (!f)
+               return -EINVAL;
+
+       va_start(fmt_ap, fmt);
+       rc = vfscanf(f, fmt, fmt_ap);
+       va_end(fmt_ap);
+
+       fclose(f);
+       return rc;
+}
+
+
+int ul_path_read_s64(struct path_cxt *pc, int64_t *res, const char *path)
+{
+       int64_t x = 0;
+       int rc;
+
+       rc = ul_path_scanf(pc, path, "%"SCNd64, &x);
+       if (rc != 1)
+               return -1;
+       if (res)
+               *res = x;
+       return 0;
+}
+
+int ul_path_readf_s64(struct path_cxt *pc, int64_t *res, const char *path, ...)
+{
+       const char *p;
        va_list ap;
 
        va_start(ap, path);
-       fd = path_vfopen("r" UL_CLOEXECSTR, 1, path, ap);
+       p = ul_path_mkpath(pc, path, ap);
        va_end(ap);
 
-       if (!fgets(result, len, fd))
-               err(EXIT_FAILURE, _("cannot read %s"), pathbuf);
-       fclose(fd);
+       if (!p)
+               return -EINVAL;;
+
+       return ul_path_read_s64(pc, res, p);
+}
+
+int ul_path_read_u64(struct path_cxt *pc, uint64_t *res, const char *path)
+{
+       uint64_t x = 0;
+       int rc;
 
-       len = strlen(result);
-       if (result[len - 1] == '\n')
-               result[len - 1] = '\0';
+       rc = ul_path_scanf(pc, path, "%"SCNu64, &x);
+       if (rc != 1)
+               return -1;
+       if (res)
+               *res = x;
+       return 0;
 }
 
-int
-path_read_s32(const char *path, ...)
+int ul_path_readf_u64(struct path_cxt *pc, uint64_t *res, const char *path, ...)
 {
-       FILE *fd;
+       const char *p;
        va_list ap;
-       int result;
 
        va_start(ap, path);
-       fd = path_vfopen("r" UL_CLOEXECSTR, 1, path, ap);
+       p = ul_path_mkpath(pc, path, ap);
        va_end(ap);
 
-       if (fscanf(fd, "%d", &result) != 1) {
-               if (ferror(fd))
-                       err(EXIT_FAILURE, _("cannot read %s"), pathbuf);
-               else
-                       errx(EXIT_FAILURE, _("parse error: %s"), pathbuf);
-       }
-       fclose(fd);
-       return result;
+       if (!p)
+               return -EINVAL;
+
+       return ul_path_read_u64(pc, res, p);
 }
 
-uint64_t
-path_read_u64(const char *path, ...)
+int ul_path_read_s32(struct path_cxt *pc, int *res, const char *path)
 {
-       FILE *fd;
+       int rc, x = 0;
+
+       rc = ul_path_scanf(pc, path, "%d", &x);
+       if (rc != 1)
+               return -1;
+       if (res)
+               *res = x;
+       return 0;
+}
+
+int ul_path_readf_s32(struct path_cxt *pc, int *res, const char *path, ...)
+{
+       const char *p;
        va_list ap;
-       uint64_t result;
 
        va_start(ap, path);
-       fd = path_vfopen("r", 1, path, ap);
+       p = ul_path_mkpath(pc, path, ap);
        va_end(ap);
 
-       if (fscanf(fd, "%"SCNu64, &result) != 1) {
-               if (ferror(fd))
-                       err(EXIT_FAILURE, _("cannot read %s"), pathbuf);
-               else
-                       errx(EXIT_FAILURE, _("parse error: %s"), pathbuf);
-       }
-       fclose(fd);
-       return result;
+       if (!p)
+               return -EINVAL;
+
+       return ul_path_read_s32(pc, res, p);
+}
+
+int ul_path_read_u32(struct path_cxt *pc, unsigned int *res, const char *path)
+{
+       int rc;
+       unsigned int x;
+
+       rc = ul_path_scanf(pc, path, "%u", &x);
+       if (rc != 1)
+               return -1;
+       if (res)
+               *res = x;
+       return 0;
+}
+
+int ul_path_readf_u32(struct path_cxt *pc, unsigned int *res, const char *path, ...)
+{
+       const char *p;
+       va_list ap;
+
+       va_start(ap, path);
+       p = ul_path_mkpath(pc, path, ap);
+       va_end(ap);
+
+       if (!p)
+               return -EINVAL;
+
+       return ul_path_read_u32(pc, res, p);
 }
 
-int
-path_write_str(const char *str, const char *path, ...)
+int ul_path_read_majmin(struct path_cxt *pc, dev_t *res, const char *path)
 {
-       int fd, result;
+       int rc, maj, min;
+
+       rc = ul_path_scanf(pc, path, "%d:%d", &maj, &min);
+       if (rc != 2)
+               return -1;
+       if (res)
+               *res = makedev(maj, min);
+       return 0;
+}
+
+int ul_path_readf_majmin(struct path_cxt *pc, dev_t *res, const char *path, ...)
+{
+       const char *p;
        va_list ap;
 
        va_start(ap, path);
-       fd = path_vopen(O_WRONLY|O_CLOEXEC, path, ap);
+       p = ul_path_mkpath(pc, path, ap);
        va_end(ap);
-       result = write_all(fd, str, strlen(str));
+
+       if (!p)
+               return -EINVAL;
+
+       return ul_path_read_majmin(pc, res, p);
+}
+
+int ul_path_write_string(struct path_cxt *pc, const char *str, const char *path)
+{
+       int rc, errsv;
+       int fd;
+
+       fd = ul_path_open(pc, O_WRONLY|O_CLOEXEC, path);
+       if (fd < 0)
+               return -errno;
+
+       rc = write_all(fd, str, strlen(str));
+
+       errsv = errno;
        close(fd);
-       return result;
+       errno = errsv;
+       return rc;
 }
 
-int
-path_exist(const char *path, ...)
+int ul_path_writef_string(struct path_cxt *pc, const char *str, const char *path, ...)
 {
+       const char *p;
        va_list ap;
+
+       va_start(ap, path);
+       p = ul_path_mkpath(pc, path, ap);
+       va_end(ap);
+
+       if (!p)
+               return -EINVAL;
+
+       return ul_path_write_string(pc, str, p);
+}
+
+int ul_path_write_u64(struct path_cxt *pc, uint64_t num, const char *path)
+{
+       char buf[sizeof(stringify_value(ULLONG_MAX))];
+       int rc, errsv;
+       int fd, len;
+
+       fd = ul_path_open(pc, O_WRONLY|O_CLOEXEC, path);
+       if (fd < 0)
+               return -errno;
+
+       len = snprintf(buf, sizeof(buf), "%" PRIu64, num);
+       if (len < 0 || (size_t) len >= sizeof(buf))
+               rc = len < 0 ? -errno : -E2BIG;
+       else
+               rc = write_all(fd, buf, len);
+
+       errsv = errno;
+       close(fd);
+       errno = errsv;
+       return rc;
+}
+
+int ul_path_writef_u64(struct path_cxt *pc, uint64_t num, const char *path, ...)
+{
        const char *p;
+       va_list ap;
 
        va_start(ap, path);
-       p = path_vcreate(path, ap);
+       p = ul_path_mkpath(pc, path, ap);
        va_end(ap);
 
-       return p && access(p, F_OK) == 0;
+       if (!p)
+               return -EINVAL;
+
+       return ul_path_write_u64(pc, num, p);
+
 }
 
-#ifdef HAVE_CPU_SET_T
+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;
+}
 
-static cpu_set_t *
-path_cpuparse(int maxcpus, int islist, const char *path, va_list ap)
+int ul_path_count_dirents(struct path_cxt *pc, const char *path)
 {
-       FILE *fd;
-       cpu_set_t *set;
+       DIR *dir;
+       int r = 0;
+
+       dir = ul_path_opendir(pc, path);
+       if (!dir)
+               return 0;
+
+       while (xreaddir(dir)) r++;
+
+       closedir(dir);
+       return r;
+}
+
+int ul_path_countf_dirents(struct path_cxt *pc, const char *path, ...)
+{
+       const char *p;
+       va_list ap;
+
+       va_start(ap, path);
+       p = ul_path_mkpath(pc, path, ap);
+       va_end(ap);
+
+       if (!p)
+               return 0;
+
+       return ul_path_count_dirents(pc, p);
+}
+
+#ifdef HAVE_CPU_SET_T
+static int ul_path_cpuparse(struct path_cxt *pc, cpu_set_t **set, int maxcpus, int islist, const char *path, va_list ap)
+{
+       FILE *f;
        size_t setsize, len = maxcpus * 7;
        char buf[len];
 
-       fd = path_vfopen("r" UL_CLOEXECSTR, 1, path, ap);
+       f = ul_path_vfopenf(pc, "r" UL_CLOEXECSTR, path, ap);
+       if (!f)
+               return -errno;
 
-       if (!fgets(buf, len, fd))
-               err(EXIT_FAILURE, _("cannot read %s"), pathbuf);
-       fclose(fd);
+       if (!fgets(buf, len, f))
+               return -errno;
+       fclose(f);
 
        len = strlen(buf);
        if (buf[len - 1] == '\n')
                buf[len - 1] = '\0';
 
-       set = cpuset_alloc(maxcpus, &setsize, NULL);
-       if (!set)
-               err(EXIT_FAILURE, _("failed to callocate cpu set"));
+       *set = cpuset_alloc(maxcpus, &setsize, NULL);
+       if (!*set)
+               return -ENOMEM;
 
        if (islist) {
-               if (cpulist_parse(buf, set, setsize, 0))
-                       errx(EXIT_FAILURE, _("failed to parse CPU list %s"), buf);
+               if (cpulist_parse(buf, *set, setsize, 0))
+                       return -EINVAL;
        } else {
-               if (cpumask_parse(buf, set, setsize))
-                       errx(EXIT_FAILURE, _("failed to parse CPU mask %s"), buf);
+               if (cpumask_parse(buf, *set, setsize))
+                       return -EINVAL;
        }
-       return set;
+       return 0;
 }
 
-cpu_set_t *
-path_read_cpuset(int maxcpus, const char *path, ...)
+int ul_path_readf_cpuset(struct path_cxt *pc, cpu_set_t **set, int maxcpus, const char *path, ...)
 {
        va_list ap;
-       cpu_set_t *set;
+       int rc = 0;
 
        va_start(ap, path);
-       set = path_cpuparse(maxcpus, 0, path, ap);
+       rc = ul_path_cpuparse(pc, set, maxcpus, 0, path, ap);
        va_end(ap);
 
-       return set;
+       return rc;
 }
 
-cpu_set_t *
-path_read_cpulist(int maxcpus, const char *path, ...)
+int ul_path_readf_cpulist(struct path_cxt *pc, cpu_set_t **set, int maxcpus, const char *path, ...)
 {
        va_list ap;
-       cpu_set_t *set;
+       int rc = 0;
 
        va_start(ap, path);
-       set = path_cpuparse(maxcpus, 1, path, ap);
+       rc = ul_path_cpuparse(pc, set, maxcpus, 1, path, ap);
        va_end(ap);
 
-       return set;
+       return rc;
 }
+
 #endif /* HAVE_CPU_SET_T */
+
+
+#ifdef TEST_PROGRAM_PATH
+#include <getopt.h>
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+       fprintf(stdout, " %s [options] <dir> <command>\n\n", program_invocation_short_name);
+       fputs(" -p, --prefix <dir>      redirect hardcoded paths to <dir>\n", stdout);
+
+       fputs(" Commands:\n", stdout);
+       fputs(" read-u64 <file>            read uint64_t from file\n", stdout);
+       fputs(" read-s64 <file>            read  int64_t from file\n", stdout);
+       fputs(" read-u32 <file>            read uint32_t from file\n", stdout);
+       fputs(" read-s32 <file>            read  int32_t from file\n", stdout);
+       fputs(" read-string <file>         read string  from file\n", stdout);
+       fputs(" read-majmin <file>         read devno from file\n", stdout);
+       fputs(" read-link <file>           read symlink\n", stdout);
+       fputs(" write-string <file> <str>  write string from file\n", stdout);
+       fputs(" write-u64 <file> <str>     write uint64_t from file\n", stdout);
+
+       exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+       int c;
+       const char *prefix = NULL, *dir, *file, *command;
+       struct path_cxt *pc = NULL;
+
+       static const struct option longopts[] = {
+               { "prefix",     1, NULL, 'p' },
+               { "help",       0, NULL, 'h' },
+               { NULL, 0, NULL, 0 },
+       };
+
+       while((c = getopt_long(argc, argv, "p:h", longopts, NULL)) != -1) {
+               switch(c) {
+               case 'p':
+                       prefix = optarg, "failed to parse range start";
+                       break;
+               case 'h':
+                       usage();
+                       break;
+               default:
+                       err(EXIT_FAILURE, "try --help");
+               }
+       }
+
+       if (optind == argc)
+               errx(EXIT_FAILURE, "<dir> not defined");
+       dir = argv[optind++];
+
+       pc = ul_new_path(dir);
+       if (!pc)
+               err(EXIT_FAILURE, "failed to initialize path context");
+       if (prefix)
+               ul_path_set_prefix(pc, prefix);
+
+       if (optind == argc)
+               errx(EXIT_FAILURE, "<command> not defined");
+       command = argv[optind++];
+
+       if (strcmp(command, "read-u32") == 0) {
+               uint32_t res;
+
+               if (optind == argc)
+                       errx(EXIT_FAILURE, "<file> not defined");
+               file = argv[optind++];
+
+               if (ul_path_read_u32(pc, &res, file) != 0)
+                       err(EXIT_FAILURE, "read u64 failed");
+               printf("read:  %s: %u\n", file, res);
+
+               if (ul_path_readf_u32(pc, &res, "%s", file) != 0)
+                       err(EXIT_FAILURE, "readf u64 failed");
+               printf("readf: %s: %u\n", file, res);
+
+       } else if (strcmp(command, "read-s32") == 0) {
+               int32_t res;
+
+               if (optind == argc)
+                       errx(EXIT_FAILURE, "<file> not defined");
+               file = argv[optind++];
+
+               if (ul_path_read_s32(pc, &res, file) != 0)
+                       err(EXIT_FAILURE, "read u64 failed");
+               printf("read:  %s: %d\n", file, res);
+
+               if (ul_path_readf_s32(pc, &res, "%s", file) != 0)
+                       err(EXIT_FAILURE, "readf u64 failed");
+               printf("readf: %s: %d\n", file, res);
+
+       } else if (strcmp(command, "read-u64") == 0) {
+               uint64_t res;
+
+               if (optind == argc)
+                       errx(EXIT_FAILURE, "<file> not defined");
+               file = argv[optind++];
+
+               if (ul_path_read_u64(pc, &res, file) != 0)
+                       err(EXIT_FAILURE, "read u64 failed");
+               printf("read:  %s: %" PRIu64 "\n", file, res);
+
+               if (ul_path_readf_u64(pc, &res, "%s", file) != 0)
+                       err(EXIT_FAILURE, "readf u64 failed");
+               printf("readf: %s: %" PRIu64 "\n", file, res);
+
+       } else if (strcmp(command, "read-s64") == 0) {
+               int64_t res;
+
+               if (optind == argc)
+                       errx(EXIT_FAILURE, "<file> not defined");
+               file = argv[optind++];
+
+               if (ul_path_read_s64(pc, &res, file) != 0)
+                       err(EXIT_FAILURE, "read u64 failed");
+               printf("read:  %s: %" PRIu64 "\n", file, res);
+
+               if (ul_path_readf_s64(pc, &res, "%s", file) != 0)
+                       err(EXIT_FAILURE, "readf u64 failed");
+               printf("readf: %s: %" PRIu64 "\n", file, res);
+
+       } else if (strcmp(command, "read-majmin") == 0) {
+               dev_t res;
+
+               if (optind == argc)
+                       errx(EXIT_FAILURE, "<file> not defined");
+               file = argv[optind++];
+
+               if (ul_path_read_majmin(pc, &res, file) != 0)
+                       err(EXIT_FAILURE, "read maj:min failed");
+               printf("read:  %s: %d\n", file, (int) res);
+
+               if (ul_path_readf_majmin(pc, &res, "%s", file) != 0)
+                       err(EXIT_FAILURE, "readf maj:min failed");
+               printf("readf: %s: %d\n", file, (int) res);
+
+       } else if (strcmp(command, "read-string") == 0) {
+               char *res;
+
+               if (optind == argc)
+                       errx(EXIT_FAILURE, "<file> not defined");
+               file = argv[optind++];
+
+               if (ul_path_read_string(pc, &res, file) != 0)
+                       err(EXIT_FAILURE, "read u64 failed");
+               printf("read:  %s: %s\n", file, res);
+
+               if (ul_path_readf_string(pc, &res, "%s", file) != 0)
+                       err(EXIT_FAILURE, "readf u64 failed");
+               printf("readf: %s: %s\n", file, res);
+
+       } else if (strcmp(command, "read-link") == 0) {
+               char res[PATH_MAX];
+
+               if (optind == argc)
+                       errx(EXIT_FAILURE, "<file> not defined");
+               file = argv[optind++];
+
+               if (ul_path_readlink(pc, res, sizeof(res), file) < 0)
+                       err(EXIT_FAILURE, "read symlink failed");
+               printf("read:  %s: %s\n", file, res);
+
+               if (ul_path_readlinkf(pc, res, sizeof(res), "%s", file) < 0)
+                       err(EXIT_FAILURE, "readf symlink failed");
+               printf("readf: %s: %s\n", file, res);
+
+       } else if (strcmp(command, "write-string") == 0) {
+               char *str;
+
+               if (optind + 1 == argc)
+                       errx(EXIT_FAILURE, "<file> <string> not defined");
+               file = argv[optind++];
+               str = argv[optind++];
+
+               if (ul_path_write_string(pc, str, file) != 0)
+                       err(EXIT_FAILURE, "write string failed");
+               if (ul_path_writef_string(pc, str, "%s", file) != 0)
+                       err(EXIT_FAILURE, "writef string failed");
+
+       } else if (strcmp(command, "write-u64") == 0) {
+               uint64_t num;
+
+               if (optind + 1 == argc)
+                       errx(EXIT_FAILURE, "<file> <num> not defined");
+               file = argv[optind++];
+               num = strtoumax(argv[optind++], NULL, 0);
+
+               if (ul_path_write_u64(pc, num, file) != 0)
+                       err(EXIT_FAILURE, "write u64 failed");
+               if (ul_path_writef_u64(pc, num, "%s", file) != 0)
+                       err(EXIT_FAILURE, "writef u64 failed");
+       }
+
+       ul_unref_path(pc);
+       return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM_PATH */
+