summaryrefslogtreecommitdiff
path: root/util/libreboot-utils/lib/file.c
diff options
context:
space:
mode:
authorLeah Rowe <leah@libreboot.org>2026-03-24 00:28:15 +0000
committerLeah Rowe <leah@libreboot.org>2026-03-24 01:25:53 +0000
commitf2544d094ba88e1cfbb7993ad67444852cfd5efd (patch)
tree252172594a1284e59e88c73c22f0201508809022 /util/libreboot-utils/lib/file.c
parentafcd535816b45fd7b0e0d07c1a8580f6f462f5e4 (diff)
util/mkhtemp: new utility (hardened mktemp)
part of the same code library as nvmutil. as part of this, i renamed util/nvmutil to util/libreboot-utils/ because it is now a multi-utility codebase. this is more efficient, since i also wish to use mkhtemp (function) in nvmutil. Signed-off-by: Leah Rowe <leah@libreboot.org>
Diffstat (limited to 'util/libreboot-utils/lib/file.c')
-rw-r--r--util/libreboot-utils/lib/file.c1136
1 files changed, 1136 insertions, 0 deletions
diff --git a/util/libreboot-utils/lib/file.c b/util/libreboot-utils/lib/file.c
new file mode 100644
index 00000000..ea2bcd0b
--- /dev/null
+++ b/util/libreboot-utils/lib/file.c
@@ -0,0 +1,1136 @@
+/* SPDX-License-Identifier: MIT
+ * Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
+ *
+ * Pathless i/o, and some stuff you probably never saw.
+ * Be nice to the demon.
+ */
+
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* for openat2: */
+#ifdef __linux__
+#include <linux/openat2.h>
+#include <sys/syscall.h>
+#endif
+
+#include "../include/common.h"
+
+/* check that a file changed
+ */
+
+int
+same_file(int fd, struct stat *st_old,
+ int check_size)
+{
+ struct stat st;
+ int saved_errno = errno;
+
+ /* TODO: null/-1 checks
+ * like this can be
+ * generalised
+ */
+ if (st_old == NULL) {
+ errno = EFAULT;
+ goto err_same_file;
+ }
+ if (fd < 0) {
+ errno = EBADF;
+ goto err_same_file;
+ }
+
+ if (fstat(fd, &st) == -1)
+ goto err_same_file;
+
+ if (fd_verify_regular(fd, st_old, &st) < 0)
+ goto err_same_file;
+
+ if (check_size &&
+ st.st_size != st_old->st_size)
+ goto err_same_file;
+
+ errno = saved_errno;
+ return 0;
+
+err_same_file:
+
+ if (errno == saved_errno)
+ errno = ESTALE;
+
+ return -1;
+}
+
+/* open() but with abort traps
+ */
+/* TODO: also support other things here than files.
+ and then use, throughout the program.
+ in particular, use of openat might help
+ (split the path)
+ (see: link attack mitigations throughout nvmutil)
+
+ make it return, and handle the return value/errno
+
+ (this could return e.g. EINTR)
+
+ TODO: this function is not used by mkhtemp, nor will
+ it probably be, it's currently used by nvmutil,
+ for opening intel gbe nvm config files. i can
+ probably remove it though and unify witth some
+ of the verification code now used for mkhtemp
+
+TODO: and don't abort. return -1. and handle in the caller.
+
+minor obstacle: the mkhtemp code always requires absolute
+paths, whereas the gbe editor takes relative paths.
+ */
+void
+xopen(int *fd_ptr, const char *path, int flags, struct stat *st)
+{
+ if ((*fd_ptr = open(path, flags)) < 0)
+ err(errno, "%s", path);
+
+ if (fstat(*fd_ptr, st) < 0)
+ err(errno, "%s: stat", path);
+
+ if (!S_ISREG(st->st_mode))
+ err(errno, "%s: not a regular file", path);
+
+ if (lseek_on_eintr(*fd_ptr, 0, SEEK_CUR, 1, 1) == (off_t)-1)
+ err(errno, "%s: file not seekable", path);
+}
+
+/* fsync() the directory of a file,
+ * useful for atomic writes
+ */
+
+int
+fsync_dir(const char *path)
+{
+ int saved_errno = errno;
+
+ size_t pathlen = 0;
+ size_t maxlen = 0;
+
+ char *dirbuf = NULL;
+ int dirfd = -1;
+
+ char *slash = NULL;
+ struct stat st = {0};
+
+ int close_errno;
+
+#if defined(PATH_LEN) && \
+ (PATH_LEN) >= 256
+ maxlen = PATH_LEN;
+#else
+ maxlen = 4096;
+#endif
+
+ if (path == NULL) {
+ errno = EFAULT;
+ goto err_fsync_dir;
+ }
+
+ if (slen(path, maxlen, &pathlen) < 0)
+ goto err_fsync_dir;
+
+ if (pathlen >= maxlen || pathlen < 0) {
+ errno = EMSGSIZE;
+ goto err_fsync_dir;
+ }
+
+ if (pathlen == 0)
+ {
+ errno = EINVAL;
+ goto err_fsync_dir;
+ }
+
+ dirbuf = malloc(pathlen + 1);
+ if (dirbuf == NULL) {
+
+ errno = ENOMEM;
+ goto err_fsync_dir;
+ }
+
+ memcpy(dirbuf, path, pathlen + 1);
+ slash = strrchr(dirbuf, '/');
+
+ if (slash != NULL) {
+ *slash = '\0';
+ if (*dirbuf == '\0') {
+ dirbuf[0] = '/';
+ dirbuf[1] = '\0';
+ }
+ } else {
+ dirbuf[0] = '.';
+ dirbuf[1] = '\0';
+ }
+
+ dirfd = fs_open(dirbuf,
+ O_RDONLY | O_CLOEXEC | O_NOCTTY
+#ifdef O_DIRECTORY
+ | O_DIRECTORY
+#endif
+#ifdef O_NOFOLLOW
+ | O_NOFOLLOW
+#endif
+);
+ if (dirfd < 0)
+ goto err_fsync_dir;
+
+ if (fstat(dirfd, &st) < 0)
+ goto err_fsync_dir;
+
+ if (!S_ISDIR(st.st_mode)) {
+
+ errno = ENOTDIR;
+ goto err_fsync_dir;
+ }
+
+ /* sync file on disk */
+ if (fsync_on_eintr(dirfd) == -1)
+ goto err_fsync_dir;
+
+ if (close_on_eintr(dirfd) == -1) {
+
+ dirfd = -1;
+ goto err_fsync_dir;
+ }
+
+ if (dirbuf != NULL) {
+
+ free(dirbuf);
+ dirbuf = NULL;
+ }
+
+ dirbuf = NULL;
+
+ errno = saved_errno;
+ return 0;
+
+err_fsync_dir:
+
+ if (errno == saved_errno)
+ errno = EIO;
+
+ if (dirbuf != NULL) {
+
+ free(dirbuf);
+ dirbuf = NULL;
+ }
+
+ if (dirfd >= 0) {
+
+ close_errno = errno;
+ (void) close_on_eintr(dirfd);
+ errno = close_errno;
+ dirfd = -1;
+ }
+
+ return -1;
+}
+
+/*
+ * Safe I/O functions wrapping around
+ * read(), write() and providing a portable
+ * analog of both pread() and pwrite().
+ * These functions are designed for maximum
+ * robustness, checking NULL inputs, overflowed
+ * outputs, and all kinds of errors that the
+ * standard libc functions don't.
+ *
+ * Looping on EINTR and EAGAIN is supported.
+ * EINTR/EAGAIN looping is done indefinitely.
+ */
+
+/* rw_file_exact() - Read perfectly or die
+ *
+ * Read/write, and absolutely insist on an
+ * absolute read; e.g. if 100 bytes are
+ * requested, this MUST return 100.
+ *
+ * This function will never return zero.
+ * It will only return below (error),
+ * or above (success). On error, -1 is
+ * returned and errno is set accordingly.
+ *
+ * Zero-byte returns are not allowed.
+ * It will re-spin a finite number of
+ * times upon zero-return, to recover,
+ * otherwise it will return an error.
+ */
+
+ssize_t
+rw_file_exact(int fd, unsigned char *mem, size_t nrw,
+ off_t off, int rw_type, int loop_eagain,
+ int loop_eintr, size_t max_retries,
+ int off_reset)
+{
+ ssize_t rval;
+ ssize_t rc;
+
+ size_t nrw_cur;
+
+ off_t off_cur;
+ void *mem_cur;
+
+ size_t retries_on_zero;
+
+ int saved_errno = errno;
+
+ rval = 0;
+
+ rc = 0;
+ retries_on_zero = 0;
+
+ if (io_args(fd, mem, nrw, off, rw_type) == -1)
+ goto err_rw_file_exact;
+
+ while (1) {
+
+ /* Prevent theoretical overflow */
+ if (rval >= 0 && (size_t)rval > (nrw - rc)) {
+ errno = EOVERFLOW;
+ goto err_rw_file_exact;
+ }
+
+ rc += rval;
+ if ((size_t)rc >= nrw)
+ break;
+
+ mem_cur = (void *)(mem + (size_t)rc);
+ nrw_cur = (size_t)(nrw - (size_t)rc);
+
+ if (off < 0) {
+ errno = EOVERFLOW;
+ goto err_rw_file_exact;
+ }
+
+ off_cur = off + (off_t)rc;
+
+ rval = prw(fd, mem_cur, nrw_cur, off_cur,
+ rw_type, loop_eagain, loop_eintr,
+ off_reset);
+
+ if (rval < 0)
+ goto err_rw_file_exact;
+
+ if (rval == 0) {
+ if (retries_on_zero++ < max_retries)
+ continue;
+
+ errno = EIO;
+ goto err_rw_file_exact;
+ }
+
+ retries_on_zero = 0;
+ }
+
+ if ((size_t)rc != nrw) {
+
+ errno = EIO;
+ goto err_rw_file_exact;
+ }
+
+ rval = rw_over_nrw(rc, nrw);
+ if (rval < 0)
+ goto err_rw_file_exact;
+
+ errno = saved_errno;
+
+ return rval;
+
+err_rw_file_exact:
+
+ if (errno == saved_errno)
+ errno = EIO;
+
+ return -1;
+}
+
+/* prw() - portable read-write with more
+ * safety checks than barebones libc
+ *
+ * portable pwrite/pread on request, or real
+ * pwrite/pread libc functions can be used.
+ * the portable (non-libc) pread/pwrite is not
+ * thread-safe, because it does not prevent or
+ * mitigate race conditions on file descriptors
+ *
+ * If you need real pwrite/pread, just compile
+ * with flag: REAL_POS_IO=1
+ *
+ * A fallback is provided for regular read/write.
+ * rw_type can be IO_READ (read), IO_WRITE (write),
+ * IO_PREAD (pread) or IO_PWRITE
+ *
+ * loop_eagain does a retry loop on EAGAIN if set
+ * loop_eintr does a retry loop on EINTR if set
+ *
+ * race conditions on non-libc pread/pwrite:
+ * if a file offset changes, abort, to mitage.
+ *
+ * off_reset 1: reset the file offset *once* if
+ * a change was detected, assuming
+ * nothing else is touching it now
+ * off_reset 0: never reset if changed
+ */
+
+ssize_t
+prw(int fd, void *mem, size_t nrw,
+ off_t off, int rw_type,
+ int loop_eagain, int loop_eintr,
+ int off_reset)
+{
+ ssize_t rval;
+ ssize_t r;
+ int positional_rw;
+ struct stat st;
+#if !defined(REAL_POS_IO) || \
+ REAL_POS_IO < 1
+ off_t verified;
+ off_t off_orig;
+ off_t off_last;
+#endif
+ int saved_errno = errno;
+
+ if (io_args(fd, mem, nrw, off, rw_type)
+ == -1)
+ goto err_prw;
+
+ r = -1;
+
+ /* do not use loop_eagain on
+ * normal files
+ */
+
+ if (!loop_eagain) {
+ /* check whether the file
+ * changed
+ */
+
+ if (check_file(fd, &st) == -1)
+ goto err_prw;
+ }
+
+ if (rw_type >= IO_PREAD)
+ positional_rw = 1; /* pread/pwrite */
+ else
+ positional_rw = 0; /* read/write */
+
+try_rw_again:
+
+ if (!positional_rw) {
+#if defined(REAL_POS_IO) && \
+ REAL_POS_IO > 0
+real_pread_pwrite:
+#endif
+ if (rw_type == IO_WRITE)
+ r = write(fd, mem, nrw);
+ else if (rw_type == IO_READ)
+ r = read(fd, mem, nrw);
+#if defined(REAL_POS_IO) && \
+ REAL_POS_IO > 0
+ else if (rw_type == IO_PWRITE)
+ r = pwrite(fd, mem, nrw, off);
+ else if (rw_type == IO_PREAD)
+ r = pread(fd, mem, nrw, off);
+#endif
+
+ if (r == -1 && (errno == try_err(loop_eintr, EINTR)
+ || errno == try_err(loop_eagain, EAGAIN)))
+ goto try_rw_again;
+
+ rval = rw_over_nrw(r, nrw);
+ if (rval < 0)
+ goto err_prw;
+
+ errno = saved_errno;
+
+ return rval;
+ }
+
+#if defined(REAL_POS_IO) && \
+ REAL_POS_IO > 0
+ goto real_pread_pwrite;
+#else
+ if ((off_orig = lseek_on_eintr(fd, (off_t)0, SEEK_CUR,
+ loop_eagain, loop_eintr)) == (off_t)-1) {
+ r = -1;
+ } else if (lseek_on_eintr(fd, off, SEEK_SET,
+ loop_eagain, loop_eintr) == (off_t)-1) {
+ r = -1;
+ } else {
+ verified = lseek_on_eintr(fd, (off_t)0, SEEK_CUR,
+ loop_eagain, loop_eintr);
+
+ /* abort if the offset changed,
+ * indicating race condition. if
+ * off_reset enabled, reset *ONCE*
+ */
+
+ if (off_reset && off != verified)
+ lseek_on_eintr(fd, off, SEEK_SET,
+ loop_eagain, loop_eintr);
+
+ do {
+ /* check offset again, repeatedly.
+ * even if off_reset is set, this
+ * aborts if offsets change again
+ */
+
+ verified = lseek_on_eintr(fd, (off_t)0, SEEK_CUR,
+ loop_eagain, loop_eintr);
+
+ if (off != verified) {
+
+ errno = EBUSY;
+ goto err_prw;
+ }
+
+ if (rw_type == IO_PREAD)
+ r = read(fd, mem, nrw);
+ else if (rw_type == IO_PWRITE)
+ r = write(fd, mem, nrw);
+
+ if (rw_over_nrw(r, nrw) == -1)
+ break;
+
+ } while (r == -1 &&
+ (errno == try_err(loop_eintr, EINTR) ||
+ errno == try_err(loop_eagain, EAGAIN)));
+ }
+
+ saved_errno = errno;
+
+ off_last = lseek_on_eintr(fd, off_orig, SEEK_SET,
+ loop_eagain, loop_eintr);
+
+ if (off_last != off_orig) {
+ errno = saved_errno;
+ goto err_prw;
+ }
+
+ errno = saved_errno;
+
+ rval = rw_over_nrw(r, nrw);
+ if (rval < 0)
+ goto err_prw;
+
+ errno = saved_errno;
+
+ return rval;
+
+#endif
+
+err_prw:
+
+ if (errno == saved_errno)
+ errno = EIO;
+
+ return -1;
+}
+
+int
+io_args(int fd, void *mem, size_t nrw,
+ off_t off, int rw_type)
+{
+ int saved_errno = errno;
+
+ /* obviously */
+ if (mem == NULL) {
+
+ errno = EFAULT;
+ goto err_io_args;
+ }
+
+ /* uninitialised fd */
+ if (fd < 0) {
+
+ errno = EBADF;
+ goto err_io_args;
+ }
+
+ /* negative offset */
+ if (off < 0) {
+
+ errno = ERANGE;
+ goto err_io_args;
+ }
+
+ /* prevent zero-byte rw */
+ if (!nrw)
+ goto err_io_args;
+
+ /* prevent overflow */
+ if (nrw > (size_t)SSIZE_MAX) {
+
+ errno = ERANGE;
+ goto err_io_args;
+ }
+
+ /* prevent overflow */
+ if (((size_t)off + nrw) < (size_t)off) {
+
+ errno = ERANGE;
+ goto err_io_args;
+ }
+
+ if (rw_type > IO_PWRITE) {
+
+ errno = EINVAL;
+ goto err_io_args;
+ }
+
+ errno = saved_errno;
+
+ return 0;
+
+err_io_args:
+
+ if (errno == saved_errno)
+ errno = EINVAL;
+
+ return -1;
+}
+
+int
+check_file(int fd, struct stat *st)
+{
+ int saved_errno = errno;
+
+ if (fd < 0) {
+ errno = EBADF;
+ goto err_is_file;
+ }
+
+ if (st == NULL) {
+ errno = EFAULT;
+ goto err_is_file;
+ }
+
+ if (fstat(fd, st) == -1)
+ goto err_is_file;
+
+ if (!S_ISREG(st->st_mode)) {
+
+ errno = EBADF;
+ goto err_is_file;
+ }
+
+ errno = saved_errno;
+
+ return 0;
+
+err_is_file:
+
+ if (errno == saved_errno)
+ errno = EINVAL;
+
+ return -1;
+}
+
+/* POSIX can say whatever it wants.
+ * specification != implementation
+ */
+
+ssize_t
+rw_over_nrw(ssize_t r, size_t nrw)
+{
+ int saved_errno = errno;
+
+ /* not a libc bug, but we
+ * don't like the number zero
+ */
+ if (!nrw)
+ goto err_rw_over_nrw;
+
+ if (r == -1)
+ return r;
+
+ if ((size_t)
+ r > SSIZE_MAX) {
+
+ /* Theoretical buggy libc
+ * check. Extremely academic.
+ *
+ * Specifications never
+ * allow this return value
+ * to exceed SSIZE_T, but
+ * spec != implementation
+ *
+ * Check this after using
+ * [p]read() or [p]write()
+ *
+ * NOTE: here, we assume
+ * ssize_t integers are the
+ * same size as SSIZE_T
+ */
+
+ errno = ERANGE;
+ goto err_rw_over_nrw;
+ }
+
+ /* Theoretical buggy libc:
+ * Should never return a number of
+ * bytes above the requested length.
+ */
+ if ((size_t)r > nrw) {
+
+ errno = ERANGE;
+ goto err_rw_over_nrw;
+ }
+
+ errno = saved_errno;
+
+ return r;
+
+err_rw_over_nrw:
+
+ if (errno == saved_errno)
+ errno = EIO;
+
+ return -1;
+}
+
+#if !defined(REAL_POS_IO) || \
+ REAL_POS_IO < 1
+off_t
+lseek_on_eintr(int fd, off_t off, int whence,
+ int loop_eagain, int loop_eintr)
+{
+ off_t old;
+
+ old = -1;
+
+ do {
+ old = lseek(fd, off, whence);
+ } while (old == (off_t)-1 && (
+ errno == try_err(loop_eintr, EINTR) ||
+ errno == try_err(loop_eintr, ETXTBSY) ||
+ errno == try_err(loop_eagain, EAGAIN) ||
+ errno == try_err(loop_eagain, EWOULDBLOCK)));
+
+ return old;
+}
+#endif
+
+int
+try_err(int loop_err, int errval)
+{
+ if (loop_err)
+ return errval;
+
+ return -1;
+}
+
+int
+close_on_eintr(int fd)
+{
+ int r;
+ int saved_errno = errno;
+
+ do {
+ r = close(fd);
+ } while (r == -1 && (
+ errno == EINTR || errno == EAGAIN ||
+ errno == EWOULDBLOCK || errno == ETXTBSY));
+
+ if (r >= 0)
+ errno = saved_errno;
+
+ return r;
+}
+
+int
+fsync_on_eintr(int fd)
+{
+ int r;
+ int saved_errno = errno;
+
+ do {
+ r = fsync(fd);
+ } while (r == -1 && (errno == EINTR || errno == EAGAIN ||
+ errno == ETXTBSY || errno == EWOULDBLOCK));
+
+ if (r >= 0)
+ errno = saved_errno;
+
+ return r;
+}
+
+int
+fs_rename_at(int olddirfd, const char *old,
+ int newdirfd, const char *new)
+{
+ if (new == NULL || old == NULL) {
+
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (olddirfd < 0 || newdirfd < 0) {
+
+ errno = EBADF;
+ return -1;
+ }
+
+ return renameat(olddirfd, old, newdirfd, new);
+}
+
+/* secure open, based on
+ * relative path to root
+ *
+ * always a fixed fd for /
+ * see: rootfs()
+ */
+int
+fs_open(const char *path, int flags)
+{
+ struct filesystem *fs;
+ const char *rel;
+
+ if (path == NULL) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (path[0] != '/') {
+ errno = EINVAL;
+ return -1;
+ }
+
+ fs = rootfs();
+ if (fs == NULL)
+ return -1;
+
+ rel = path + 1;
+
+ return fs_resolve_at(fs->rootfd, rel, flags);
+}
+
+/* singleton function
+ * that returns a fixed
+ * descriptor of /
+ *
+ * used throughout, for
+ * repeated integrity checks
+ */
+struct filesystem *
+rootfs(void)
+{
+ static struct filesystem global_fs;
+ static int fs_initialised = 0;
+
+ if (!fs_initialised) {
+
+ global_fs.rootfd =
+ open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+
+ if (global_fs.rootfd < 0)
+ return NULL;
+
+ fs_initialised = 1;
+ }
+
+ return &global_fs;
+}
+
+/* filesystem sandboxing.
+ * (in userspace)
+ */
+int
+fs_resolve_at(int dirfd, const char *path, int flags)
+{
+ int nextfd = -1;
+ int curfd;
+ const char *p;
+ char name[256];
+ int saved_errno = errno;
+ int r;
+ int is_last;
+
+ if (dirfd < 0 || path == NULL || *path == '\0') {
+ errno = EINVAL;
+ return -1;
+ }
+
+ p = path;
+ curfd = dirfd; /* start here */
+
+ for (;;) {
+ r = fs_next_component(&p, name, sizeof(name));
+ if (r < 0)
+ goto err;
+ if (r == 0)
+ break;
+
+ is_last = (*p == '\0');
+
+ nextfd = fs_open_component(curfd, name, flags, is_last);
+ if (nextfd < 0)
+ goto err;
+
+ /* close previous fd IF it is not the original input */
+ if (curfd != dirfd) {
+ (void) close_on_eintr(curfd);
+ }
+
+ curfd = nextfd;
+ nextfd = -1;
+ }
+
+ errno = saved_errno;
+ return curfd;
+
+err:
+ saved_errno = errno;
+
+ if (nextfd >= 0)
+ (void) close_on_eintr(nextfd);
+
+ /* close curfd only if it's not the original */
+ if (curfd != dirfd && curfd >= 0)
+ (void) close_on_eintr(curfd);
+
+ errno = saved_errno;
+ return -1;
+}
+
+int
+fs_next_component(const char **p,
+ char *name, size_t namesz)
+{
+ const char *s = *p;
+ size_t len = 0;
+#if defined(PATH_LEN) && \
+(PATH_LEN) >= 256
+ size_t maxlen = PATH_LEN;
+#else
+ size_t maxlen = 4096;
+#endif
+
+ while (*s == '/')
+ s++;
+
+ if (*s == '\0') {
+ *p = s;
+ return 0;
+ }
+
+ while (s[len] != '/' && s[len] != '\0')
+ len++;
+
+ if (len == 0 || len >= namesz ||
+ len >= maxlen) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ memcpy(name, s, len);
+ name[len] = '\0';
+
+ /* reject . and .. */
+ if ((name[0] == '.' && name[1] == '\0') ||
+ (name[0] == '.' && name[1] == '.' && name[2] == '\0')) {
+ errno = EPERM;
+ return -1;
+ }
+
+ *p = s + len;
+ return 1;
+}
+
+int
+fs_open_component(int dirfd, const char *name,
+ int flags, int is_last)
+{
+ int fd;
+ struct stat st;
+
+ fd = openat2p(dirfd, name,
+ (is_last ? flags : (O_RDONLY | O_DIRECTORY)) |
+ O_NOFOLLOW | O_CLOEXEC, (flags & O_CREAT) ? 0600 : 0);
+
+ /* the patient always lies
+ */
+ if (!is_last) {
+
+ if (fd < 0) {
+ errno = EBADF;
+ return -1;
+ }
+
+ if (fstat(fd, &st) < 0)
+ return -1;
+
+ if (!S_ISDIR(st.st_mode)) {
+
+ (void) close_on_eintr(fd);
+ errno = ENOTDIR;
+ return -1;
+ }
+ }
+
+ return fd;
+}
+
+int
+fs_dirname_basename(const char *path,
+ char **dir, char **base,
+ int allow_relative)
+{
+ char *buf;
+ char *slash;
+ size_t len;
+ int rval;
+#if defined(PATH_LEN) && \
+(PATH_LEN) >= 256
+ size_t maxlen = PATH_LEN;
+#else
+ size_t maxlen = 4096;
+#endif
+
+ if (path == NULL || dir == NULL || base == NULL)
+ return -1;
+
+ if (slen(path, maxlen, &len) < 0)
+ return -1;
+
+ buf = malloc(len + 1);
+ if (buf == NULL)
+ return -1;
+
+ memcpy(buf, path, len + 1);
+
+ /* strip trailing slashes */
+ while (len > 1 && buf[len - 1] == '/')
+ buf[--len] = '\0';
+
+ slash = strrchr(buf, '/');
+
+ if (slash) {
+
+ *slash = '\0';
+ *dir = buf;
+ *base = slash + 1;
+
+ if (**dir == '\0') {
+ (*dir)[0] = '/';
+ (*dir)[1] = '\0';
+ }
+ } else if (allow_relative) {
+
+ *dir = strdup(".");
+ *base = buf;
+ } else {
+ errno = EINVAL;
+ free(buf);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* portable wrapper for use of openat2 on linux,
+ * with fallback for others e.g. openbsd
+ *
+ * BONUS: arg checks
+ * TODO: consider EINTR/EAGAIN retry loop
+ */
+int
+openat2p(int dirfd, const char *path,
+ int flags, mode_t mode)
+{
+#ifdef __linux__
+ struct open_how how = {
+ .flags = flags,
+ .mode = mode,
+ .resolve =
+ RESOLVE_BENEATH |
+ RESOLVE_NO_SYMLINKS |
+ RESOLVE_NO_MAGICLINKS
+ };
+ int saved_errno = errno;
+ int rval;
+#endif
+
+ if (dirfd < 0) {
+ errno = EBADF;
+ return -1;
+ }
+
+ if (path == NULL) {
+ errno = EFAULT;
+ return -1;
+ }
+
+retry:
+ errno = 0;
+
+#ifdef __linux__
+ /* more secure than regular openat,
+ * but linux-only at the time of writing
+ */
+ rval = syscall(SYS_openat2, dirfd, path, &how, sizeof(how));
+#else
+ /* less secure, but e.g. openbsd
+ * doesn't have openat2 yet
+ */
+ rval = openat(dirfd, path, flags, mode);
+#endif
+ if (rval == -1 && (
+ errno == EINTR ||
+ errno == EAGAIN ||
+ errno == EWOULDBLOCK ||
+ errno == ETXTBSY))
+ goto retry;
+
+ if (rval >= 0)
+ errno = saved_errno;
+
+ return rval;
+}
+
+int
+mkdirat_on_eintr( /* <-- say that 10 times to please the demon */
+ int dirfd,
+ const char *path, mode_t mode)
+{
+ int saved_errno = errno;
+ int rval;
+
+ if (dirfd < 0) {
+ errno = EBADF;
+ return -1;
+ }
+
+ if (path == NULL) {
+ errno = EFAULT;
+ return -1;
+ }
+
+retry:
+ errno = 0;
+ rval = mkdirat(dirfd, path, mode);
+
+ if (rval == -1 && (
+ errno == EINTR ||
+ errno == EAGAIN ||
+ errno == EWOULDBLOCK ||
+ errno == ETXTBSY))
+ goto retry;
+
+ if (rval >= 0)
+ errno = saved_errno;
+
+ return rval;
+}