summaryrefslogtreecommitdiff
path: root/util/nvmutil/lib/file.c
diff options
context:
space:
mode:
Diffstat (limited to 'util/nvmutil/lib/file.c')
-rw-r--r--util/nvmutil/lib/file.c165
1 files changed, 53 insertions, 112 deletions
diff --git a/util/nvmutil/lib/file.c b/util/nvmutil/lib/file.c
index f90ecdba..b4925ccd 100644
--- a/util/nvmutil/lib/file.c
+++ b/util/nvmutil/lib/file.c
@@ -1,8 +1,5 @@
/* SPDX-License-Identifier: MIT
- *
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
- *
- * Safe file handling.
*/
#include <sys/types.h>
@@ -51,6 +48,9 @@ err_same_file:
return -1;
}
+/* open() but with abort traps
+ */
+
void
xopen(int *fd_ptr, const char *path, int flags, struct stat *st)
{
@@ -67,8 +67,8 @@ xopen(int *fd_ptr, const char *path, int flags, struct stat *st)
err(errno, "%s: file not seekable", path);
}
-/* Ensure rename() is durable by syncing the
- * directory containing the target file.
+/* fsync() the directory of a file,
+ * useful for atomic writes
*/
int
@@ -177,28 +177,11 @@ err_fsync_dir:
return -1;
}
-/* create new tmpfile path
- *
- * ON SUCCESS:
- *
- * returns ptr to path string on success
- * ALSO: the int at *fd will be set,
- * indicating the file descriptor
- *
- * ON ERROR:
- *
- * return NULL (*fd not touched)
+/* returns ptr to path (string). if local>0:
+ * make tmpfile in the same directory as the
+ * file. if local==0, use TMPDIR
*
- * malloc() may set errno, but you should
- * not rely on errno from this function
- *
- * local: if non-zero, then only a file
- * name will be given, relative to
- * the current file name. for this,
- * the 3rd argument (path) must be non-null
- *
- * if local is zero, then 3rd arg (path)
- * is irrelevant and can be NULL
+ * if local==0, the 3rd argument is ignored
*/
char *
@@ -225,16 +208,6 @@ new_tmpfile(int *fd, int local, const char *path)
int fd_tmp = -1;
int flags;
- /*
- * 256 is the most
- * conservative path
- * size limit (posix),
- * but 4096 is modern
- *
- * set PATH_LEN as you
- * wish, at build time
- */
-
#if defined(PATH_LEN) && \
(PATH_LEN) >= 256
maxlen = PATH_LEN;
@@ -265,7 +238,7 @@ new_tmpfile(int *fd, int local, const char *path)
*/
tmpdir_len = xstrxlen(default_tmpname, maxlen);
} else {
- base = x_c_tmpdir();
+ base = get_tmpdir();
if (base == NULL)
base = tmp_default;
@@ -392,8 +365,12 @@ lock_file(int fd, int flags)
return 0;
}
+/* return TMPDIR, or fall back
+ * to portable defaults
+ */
+
char *
-x_c_tmpdir(void)
+get_tmpdir(void)
{
char *t;
struct stat st;
@@ -401,9 +378,12 @@ x_c_tmpdir(void)
t = getenv("TMPDIR");
if (t && *t) {
+
if (stat(t, &st) == 0 && S_ISDIR(st.st_mode)) {
+
if ((st.st_mode & S_IWOTH) && !(st.st_mode & S_ISVTX))
return NULL;
+
return t;
}
}
@@ -434,11 +414,11 @@ mkstemp_n(char *template)
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
unsigned long r;
- unsigned long max_len =
-#ifndef PATH_LEN
- 4096;
+#if defined(PATH_LEN) && \
+ (PATH_LEN) >= 256
+ unsigned long max_len = PATH_LEN;
#else
- (PATH_LEN);
+ unsigned long max_len = 4096;
#endif
len = xstrxlen(template, max_len);
@@ -579,45 +559,32 @@ err_rw_file_exact:
return -1;
}
-/* prw() - portable read-write
+/* prw() - portable read-write with more
+ * safety checks than barebones libc
*
- * This implements a portable analog of pwrite()
- * and pread() - note that this version is not
- * thread-safe (race conditions are possible on
- * shared file descriptors).
- *
- * This limitation is acceptable, since nvmutil is
- * single-threaded. Portability is the main goal.
+ * 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: HAVE_REAL_PREAD_PWRITE=1
*
* A fallback is provided for regular read/write.
- * rw_type can be IO_READ, IO_WRITE, IO_PREAD
- * or IO_PWRITE
+ * 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
*
- * Unlike the bare syscalls, prw() does security
- * checks e.g. checks NULL strings, checks bounds,
- * also mitigates a few theoretical libc bugs.
- * It is designed for extremely safe single-threaded
- * I/O on applications that need it.
- *
- * NOTE: If you use loop_eagain (1), you enable wait
- * loop on EAGAIN. Beware if using this on a non-blocking
- * pipe (it could spin indefinitely).
+ * race conditions on non-libc pread/pwrite:
+ * if a file offset changes, abort, to mitage.
*
- * off_reset: if zero, and using fallback pwrite/pread
- * analogs, we check if a file offset changed,
- * which would indicate another thread changed
- * it, and return error, without resetting the
- * file - this would allow that thread to keep
- * running, but we could then cause a whole
- * program exit if we wanted to.
- * if not zero:
- * we reset and continue, and pray for the worst.
+ * 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
*/
long
@@ -644,16 +611,15 @@ prw(int fd, void *mem, unsigned long nrw,
r = -1;
- /* Programs like cat can use this,
- so we only check if it's a normal
- file if not looping EAGAIN */
+ /* do not use loop_eagain on
+ * normal files
+ */
+
if (!loop_eagain) {
- /*
- * Checking on every run of prw()
- * is expensive if called many
- * times, but is defensive in
- * case the status changes.
+ /* check whether the file
+ * changed
*/
+
if (check_file(fd, &st) == -1)
return -1;
}
@@ -703,39 +669,21 @@ real_pread_pwrite:
verified = lseek_on_eintr(fd, (off_t)0, SEEK_CUR,
loop_eagain, loop_eintr);
- /*
- * Partial thread-safety: detect
- * if the offset changed to what
- * we previously got. If it did,
- * then another thread may have
- * changed it. Enabled if
- * off_reset is OFF_RESET.
- *
- * We do this *once*, on the theory
- * that nothing is touching it now.
+ /* 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 {
- /*
- * Verify again before I/O
- * (even with OFF_ERR)
- *
- * This implements the first check
- * even with OFF_ERR, but without
- * the recovery. On ERR_RESET, if
- * the check fails again, then we
- * know something else is touching
- * the file, so it's best that we
- * probably leave it alone and err.
- *
- * In other words, ERR_RESET only
- * tolerates one change. Any more
- * will cause an exit, including
- * per EINTR/EAGAIN re-spin.
+ /* 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);
@@ -831,9 +779,7 @@ err_is_file:
return -1;
}
-/* Check weirdness on buggy libc.
- *
- * POSIX can say whatever it wants.
+/* POSIX can say whatever it wants.
* specification != implementation
*/
@@ -888,11 +834,6 @@ err_rw_over_nrw:
#if !defined(HAVE_REAL_PREAD_PWRITE) || \
HAVE_REAL_PREAD_PWRITE < 1
-/*
- * lseek_on_eintr() does lseek() but optionally
- * on an EINTR/EAGAIN wait loop. Used by prw()
- * for setting offsets for positional I/O.
- */
off_t
lseek_on_eintr(int fd, off_t off, int whence,
int loop_eagain, int loop_eintr)