mirror of
https://codeberg.org/libreboot/lbmk.git
synced 2026-03-25 13:29:03 +02:00
988 lines
18 KiB
C
988 lines
18 KiB
C
/* SPDX-License-Identifier: MIT
|
|
*
|
|
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
|
|
*
|
|
* Safe file handling.
|
|
*/
|
|
|
|
#ifdef __OpenBSD__
|
|
#include <sys/param.h>
|
|
#endif
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <stdarg.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "include/common.h"
|
|
|
|
/*
|
|
* TODO: make generic. S_ISREG: check every other
|
|
* type, erring only if it doesn't match what was
|
|
* passed as type requested.
|
|
* also:
|
|
* have variable need_seek, only err on seek if
|
|
* need_seek is set.
|
|
* also consider the stat check in this generic
|
|
* context
|
|
* make tthe return type an int, not a void.
|
|
* return -1 with errno set to indicate error,
|
|
* though the syscalls mostly handle that.
|
|
* save errno before lseek, resetting it after
|
|
* the check if return >-1
|
|
*/
|
|
void
|
|
xopen(int *fd_ptr, const char *path, int flags, struct stat *st)
|
|
{
|
|
if ((*fd_ptr = open(path, flags)) == -1)
|
|
err(errno, "%s", path);
|
|
|
|
if (fstat(*fd_ptr, st) == -1)
|
|
err(errno, "%s: stat", path);
|
|
|
|
if (!S_ISREG(st->st_mode))
|
|
err(errno, "%s: not a regular file", path);
|
|
|
|
if (lseek(*fd_ptr, 0, SEEK_CUR) == (off_t)-1)
|
|
err(errno, "%s: file not seekable", path);
|
|
}
|
|
|
|
/*
|
|
* Ensure rename() is durable by syncing the
|
|
* directory containing the target file.
|
|
*/
|
|
int
|
|
fsync_dir(const char *path)
|
|
{
|
|
int saved_errno = errno;
|
|
|
|
unsigned long pathlen;
|
|
unsigned long maxlen;
|
|
|
|
char *dirbuf;
|
|
int dirfd;
|
|
|
|
char *slash;
|
|
|
|
struct stat st;
|
|
|
|
#if defined(PATH_LEN) && \
|
|
(PATH_LEN) >= 256
|
|
maxlen = PATH_LEN;
|
|
#else
|
|
maxlen = 1024;
|
|
#endif
|
|
|
|
dirbuf = NULL;
|
|
dirfd = -1;
|
|
|
|
pathlen = xstrxlen(path, maxlen);
|
|
|
|
if (pathlen >= maxlen) {
|
|
fprintf(stderr, "Path too long for fsync_parent_dir\n");
|
|
goto err_fsync_dir;
|
|
}
|
|
|
|
if (pathlen == 0)
|
|
{
|
|
errno = EINVAL;
|
|
goto err_fsync_dir;
|
|
}
|
|
|
|
dirbuf = malloc(pathlen + 1);
|
|
if (dirbuf == NULL)
|
|
goto err_fsync_dir;
|
|
|
|
x_v_memcpy(dirbuf, path, pathlen + 1);
|
|
slash = x_c_strrchr(dirbuf, '/');
|
|
|
|
if (slash != NULL) {
|
|
*slash = '\0';
|
|
if (*dirbuf == '\0') {
|
|
dirbuf[0] = '/';
|
|
dirbuf[1] = '\0';
|
|
}
|
|
} else {
|
|
dirbuf[0] = '.';
|
|
dirbuf[1] = '\0';
|
|
}
|
|
|
|
dirfd = open(dirbuf, O_RDONLY
|
|
#ifdef O_DIRECTORY
|
|
| O_DIRECTORY
|
|
#endif
|
|
#ifdef O_NOFOLLOW
|
|
| O_NOFOLLOW
|
|
#endif
|
|
);
|
|
if (dirfd == -1)
|
|
goto err_fsync_dir;
|
|
|
|
if (fstat(dirfd, &st) < 0)
|
|
goto err_fsync_dir;
|
|
|
|
if (!S_ISDIR(st.st_mode)) {
|
|
fprintf(stderr, "%s: not a directory\n", dirbuf);
|
|
goto err_fsync_dir;
|
|
}
|
|
|
|
/* sync file on disk */
|
|
if (x_i_fsync(dirfd) == -1)
|
|
goto err_fsync_dir;
|
|
|
|
if (x_i_close(dirfd) == -1)
|
|
goto err_fsync_dir;
|
|
|
|
if (dirbuf != NULL)
|
|
free(dirbuf);
|
|
|
|
errno = saved_errno;
|
|
return 0;
|
|
|
|
err_fsync_dir:
|
|
if (!errno)
|
|
errno = EIO;
|
|
|
|
if (errno != saved_errno)
|
|
fprintf(stderr, "%s: %s\n", path, strerror(errno));
|
|
|
|
if (dirbuf != NULL)
|
|
free(dirbuf);
|
|
|
|
if (dirfd > -1)
|
|
x_i_close(dirfd);
|
|
|
|
errno = saved_errno;
|
|
|
|
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)
|
|
*
|
|
* 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
|
|
*/
|
|
char *
|
|
new_tmpfile(int *fd, int local, const char *path)
|
|
{
|
|
unsigned long maxlen;
|
|
struct stat st;
|
|
|
|
/*
|
|
* please do not modify the
|
|
* strings or I will get mad
|
|
*/
|
|
char tmp_none[] = "";
|
|
char tmp_default[] = "/tmp";
|
|
char default_tmpname[] = "tmpXXXXXX";
|
|
char *tmpname;
|
|
|
|
char *base = NULL;
|
|
char *dest = NULL;
|
|
|
|
unsigned long tmpdir_len = 0;
|
|
unsigned long tmpname_len = 0;
|
|
unsigned long tmppath_len = 0;
|
|
|
|
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;
|
|
#else
|
|
maxlen = 1024;
|
|
#endif
|
|
|
|
tmpname = default_tmpname;
|
|
if (local) {
|
|
if (path == NULL)
|
|
goto err_new_tmpfile;
|
|
if (*path == '\0')
|
|
goto err_new_tmpfile;
|
|
|
|
if (stat(path, &st) == -1)
|
|
goto err_new_tmpfile;
|
|
|
|
if (!S_ISREG(st.st_mode))
|
|
goto err_new_tmpfile;
|
|
|
|
tmpname = (char *)path;
|
|
}
|
|
|
|
if (local) {
|
|
base = tmp_none;
|
|
|
|
/*
|
|
* appended to filename for tmp:
|
|
*/
|
|
tmpdir_len = xstrxlen(default_tmpname, maxlen);
|
|
} else {
|
|
base = x_c_tmpdir();
|
|
|
|
if (base == NULL)
|
|
base = tmp_default;
|
|
if (*base == '\0')
|
|
base = tmp_default;
|
|
|
|
tmpdir_len = xstrxlen(base, maxlen);
|
|
}
|
|
|
|
tmpname_len = xstrxlen(tmpname, maxlen);
|
|
|
|
tmppath_len = tmpdir_len + tmpname_len;
|
|
++tmppath_len; /* for '/' or '.' */
|
|
|
|
/*
|
|
* max length -1 of maxlen
|
|
* for termination
|
|
*/
|
|
if (tmpdir_len > maxlen - tmpname_len - 1)
|
|
goto err_new_tmpfile;
|
|
|
|
/* +1 for NULL */
|
|
dest = malloc(tmppath_len + 1);
|
|
if (dest == NULL)
|
|
goto err_new_tmpfile;
|
|
|
|
if (local) {
|
|
|
|
*dest = '.'; /* hidden file */
|
|
|
|
x_v_memcpy(dest + (unsigned long)1, tmpname, tmpname_len);
|
|
|
|
x_v_memcpy(dest + (unsigned long)1 + tmpname_len,
|
|
default_tmpname, tmpdir_len);
|
|
} else {
|
|
|
|
x_v_memcpy(dest, base, tmpdir_len);
|
|
|
|
dest[tmpdir_len] = '/';
|
|
|
|
x_v_memcpy(dest + tmpdir_len + 1, tmpname, tmpname_len);
|
|
}
|
|
|
|
dest[tmppath_len] = '\0';
|
|
|
|
fd_tmp = x_i_mkstemp(dest);
|
|
if (fd_tmp == -1)
|
|
goto err_new_tmpfile;
|
|
|
|
if (fchmod(fd_tmp, 0600) == -1)
|
|
goto err_new_tmpfile;
|
|
|
|
flags = fcntl(fd_tmp, F_GETFL);
|
|
|
|
if (flags == -1)
|
|
goto err_new_tmpfile;
|
|
|
|
/*
|
|
* O_APPEND would permit offsets
|
|
* to be ignored, which breaks
|
|
* positional read/write
|
|
*/
|
|
if (flags & O_APPEND)
|
|
goto err_new_tmpfile;
|
|
|
|
if (lock_file(fd_tmp, flags) == -1)
|
|
goto err_new_tmpfile;
|
|
|
|
if (fstat(fd_tmp, &st) == -1)
|
|
goto err_new_tmpfile;
|
|
|
|
/*
|
|
* Extremely defensive
|
|
* likely pointless checks
|
|
*/
|
|
|
|
/* check if it's a file */
|
|
if (!S_ISREG(st.st_mode))
|
|
goto err_new_tmpfile;
|
|
|
|
/* check if it's seekable */
|
|
if (lseek(fd_tmp, 0, SEEK_CUR) == (off_t)-1)
|
|
goto err_new_tmpfile;
|
|
|
|
/* tmpfile has >1 hardlinks */
|
|
if (st.st_nlink > 1)
|
|
goto err_new_tmpfile;
|
|
|
|
/* tmpfile unlinked while opened */
|
|
if (st.st_nlink == 0)
|
|
goto err_new_tmpfile;
|
|
|
|
*fd = fd_tmp;
|
|
|
|
return dest;
|
|
|
|
err_new_tmpfile:
|
|
|
|
if (dest != NULL)
|
|
free(dest);
|
|
|
|
if (fd_tmp > -1)
|
|
x_i_close(fd_tmp);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
lock_file(int fd, int flags)
|
|
{
|
|
struct flock fl;
|
|
|
|
memset(&fl, 0, sizeof(fl));
|
|
|
|
if ((flags & O_ACCMODE) == O_RDONLY)
|
|
fl.l_type = F_RDLCK;
|
|
else
|
|
fl.l_type = F_WRLCK;
|
|
|
|
fl.l_whence = SEEK_SET;
|
|
|
|
if (fcntl(fd, F_SETLK, &fl) == -1)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
char *
|
|
x_c_tmpdir(void)
|
|
{
|
|
char *t;
|
|
struct stat st;
|
|
|
|
t = getenv("TMPDIR");
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (stat("/tmp", &st) == 0 && S_ISDIR(st.st_mode))
|
|
return "/tmp";
|
|
|
|
if (stat("/var/tmp", &st) == 0 && S_ISDIR(st.st_mode))
|
|
return "/var/tmp";
|
|
|
|
return ".";
|
|
}
|
|
|
|
/*
|
|
* portable mkstemp
|
|
*/
|
|
int
|
|
x_i_mkstemp(char *template)
|
|
{
|
|
int fd;
|
|
int i, j;
|
|
unsigned long len;
|
|
char *p;
|
|
|
|
char ch[] =
|
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
|
|
unsigned long r;
|
|
|
|
len = xstrxlen(template, PATH_LEN);
|
|
|
|
/* find trailing XXXXXX */
|
|
if (len < 6)
|
|
return -1;
|
|
|
|
p = template + len - 6;
|
|
|
|
for (i = 0; i < 100; i++) {
|
|
|
|
for (j = 0; j < 6; j++) {
|
|
r = rlong();
|
|
p[j] = ch[(unsigned long)(r >> 1) % (sizeof(ch) - 1)];
|
|
}
|
|
|
|
fd = open(template, O_RDWR | O_CREAT | O_EXCL, 0600);
|
|
|
|
if (fd >= 0)
|
|
return fd;
|
|
|
|
if (errno != EEXIST)
|
|
return -1;
|
|
}
|
|
|
|
errno = EEXIST;
|
|
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.
|
|
*/
|
|
long
|
|
rw_file_exact(int fd, unsigned char *mem, unsigned long nrw,
|
|
off_t off, int rw_type, int loop_eagain,
|
|
int loop_eintr, unsigned long max_retries,
|
|
int off_reset)
|
|
{
|
|
long rval;
|
|
long rc;
|
|
|
|
unsigned long nrw_cur;
|
|
|
|
off_t off_cur;
|
|
void *mem_cur;
|
|
|
|
unsigned long retries_on_zero;
|
|
|
|
rval = 0;
|
|
|
|
rc = 0;
|
|
retries_on_zero = 0;
|
|
|
|
if (io_args(fd, mem, nrw, off, rw_type) == -1)
|
|
return -1;
|
|
|
|
while (1) {
|
|
|
|
/* Prevent theoretical overflow */
|
|
if (rval >= 0 && (unsigned long)rval > (nrw - rc))
|
|
goto err_rw_file_exact;
|
|
|
|
rc += rval;
|
|
if ((unsigned long)rc >= nrw)
|
|
break;
|
|
|
|
mem_cur = (void *)(mem + (unsigned long)rc);
|
|
nrw_cur = (unsigned long)(nrw - (unsigned long)rc);
|
|
if (off < 0)
|
|
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)
|
|
return -1;
|
|
|
|
if (rval == 0) {
|
|
if (retries_on_zero++ < max_retries)
|
|
continue;
|
|
goto err_rw_file_exact;
|
|
}
|
|
|
|
retries_on_zero = 0;
|
|
}
|
|
|
|
if ((unsigned long)rc != nrw)
|
|
goto err_rw_file_exact;
|
|
|
|
return rw_over_nrw(rc, nrw);
|
|
|
|
err_rw_file_exact:
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* prw() - portable read-write
|
|
*
|
|
* 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.
|
|
*
|
|
* 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
|
|
*
|
|
* 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).
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
long
|
|
prw(int fd, void *mem, unsigned long nrw,
|
|
off_t off, int rw_type,
|
|
int loop_eagain, int loop_eintr,
|
|
int off_reset)
|
|
{
|
|
#ifndef MAX_EAGAIN_RETRIES
|
|
unsigned long retries = 100000;
|
|
#else
|
|
unsigned long retries = MAX_EAGAIN_RETRIES;
|
|
#endif
|
|
|
|
long r;
|
|
int positional_rw;
|
|
struct stat st;
|
|
#if !defined(HAVE_REAL_PREAD_PWRITE) || \
|
|
HAVE_REAL_PREAD_PWRITE < 1
|
|
int saved_errno;
|
|
off_t verified;
|
|
off_t off_orig;
|
|
off_t off_last;
|
|
#endif
|
|
|
|
if (io_args(fd, mem, nrw, off, rw_type)
|
|
== -1) {
|
|
return -1;
|
|
}
|
|
|
|
r = -1;
|
|
|
|
/* Programs like cat can use this,
|
|
so we only check if it's a normal
|
|
file if not looping EAGAIN */
|
|
if (!loop_eagain) {
|
|
/*
|
|
* Checking on every run of prw()
|
|
* is expensive if called many
|
|
* times, but is defensive in
|
|
* case the status changes.
|
|
*/
|
|
if (check_file(fd, &st) == -1)
|
|
return -1;
|
|
}
|
|
|
|
if (rw_type >= IO_PREAD)
|
|
positional_rw = 1; /* pread/pwrite */
|
|
else
|
|
positional_rw = 0; /* read/write */
|
|
|
|
try_rw_again:
|
|
|
|
if (!positional_rw) {
|
|
#if defined(HAVE_REAL_PREAD_PWRITE) && \
|
|
HAVE_REAL_PREAD_PWRITE > 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(HAVE_REAL_PREAD_PWRITE) && \
|
|
HAVE_REAL_PREAD_PWRITE > 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;
|
|
|
|
return rw_over_nrw(r, nrw);
|
|
}
|
|
|
|
#if defined(HAVE_REAL_PREAD_PWRITE) && \
|
|
HAVE_REAL_PREAD_PWRITE > 0
|
|
goto real_pread_pwrite;
|
|
#else
|
|
if ((off_orig = lseek_loop(fd, (off_t)0, SEEK_CUR,
|
|
loop_eagain, loop_eintr)) == (off_t)-1) {
|
|
r = -1;
|
|
} else if (lseek_loop(fd, off, SEEK_SET,
|
|
loop_eagain, loop_eintr) == (off_t)-1) {
|
|
r = -1;
|
|
} else {
|
|
verified = lseek_loop(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.
|
|
*/
|
|
if (off_reset && off != verified)
|
|
lseek_loop(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.
|
|
*/
|
|
verified = lseek_loop(fd, (off_t)0, SEEK_CUR,
|
|
loop_eagain, loop_eintr);
|
|
|
|
if (off != verified)
|
|
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) {
|
|
errno = EIO;
|
|
break;
|
|
}
|
|
|
|
} while (r == -1 &&
|
|
(errno == try_err(loop_eintr, EINTR) ||
|
|
errno == try_err(loop_eagain, EAGAIN)) &&
|
|
retries++ < MAX_EAGAIN_RETRIES);
|
|
}
|
|
|
|
saved_errno = errno;
|
|
|
|
off_last = lseek_loop(fd, off_orig, SEEK_SET,
|
|
loop_eagain, loop_eintr);
|
|
|
|
if (off_last != off_orig) {
|
|
errno = saved_errno;
|
|
goto err_prw;
|
|
}
|
|
|
|
errno = saved_errno;
|
|
|
|
return rw_over_nrw(r, nrw);
|
|
#endif
|
|
|
|
err_prw:
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
io_args(int fd, void *mem, unsigned long nrw,
|
|
off_t off, int rw_type)
|
|
{
|
|
/* obviously */
|
|
if (mem == NULL)
|
|
goto err_io_args;
|
|
|
|
/* uninitialised fd */
|
|
if (fd < 0)
|
|
goto err_io_args;
|
|
|
|
/* negative offset */
|
|
if (off < 0)
|
|
goto err_io_args;
|
|
|
|
/* prevent zero-byte rw */
|
|
if (!nrw)
|
|
goto err_io_args;
|
|
|
|
/* prevent overflow */
|
|
if (nrw > (unsigned long)X_LONG_MAX)
|
|
goto err_io_args;
|
|
|
|
/* prevent overflow */
|
|
if (((unsigned long)off + nrw) < (unsigned long)off)
|
|
goto err_io_args;
|
|
|
|
if (rw_type > IO_PWRITE)
|
|
goto err_io_args;
|
|
|
|
return 0;
|
|
|
|
err_io_args:
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
check_file(int fd, struct stat *st)
|
|
{
|
|
if (fstat(fd, st) == -1)
|
|
goto err_is_file;
|
|
|
|
if (!S_ISREG(st->st_mode))
|
|
goto err_is_file;
|
|
|
|
return 0;
|
|
|
|
err_is_file:
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Check overflows caused by buggy libc.
|
|
*
|
|
* POSIX can say whatever it wants.
|
|
* specification != implementation
|
|
*/
|
|
long
|
|
rw_over_nrw(long r, unsigned long nrw)
|
|
{
|
|
/*
|
|
* If a byte length of zero
|
|
* was requested, that is
|
|
* clearly a bug. No way.
|
|
*/
|
|
if (!nrw)
|
|
goto err_rw_over_nrw;
|
|
|
|
if (r == -1)
|
|
return r;
|
|
|
|
if ((unsigned long)
|
|
r > X_LONG_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()
|
|
*/
|
|
goto err_rw_over_nrw;
|
|
}
|
|
|
|
/*
|
|
* Theoretical buggy libc:
|
|
* Should never return a number of
|
|
* bytes above the requested length.
|
|
*/
|
|
if ((unsigned long)r > nrw)
|
|
goto err_rw_over_nrw;
|
|
|
|
return r;
|
|
|
|
err_rw_over_nrw:
|
|
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
#if !defined(HAVE_REAL_PREAD_PWRITE) || \
|
|
HAVE_REAL_PREAD_PWRITE < 1
|
|
/*
|
|
* lseek_loop() does lseek() but optionally
|
|
* on an EINTR/EAGAIN wait loop. Used by prw()
|
|
* for setting offsets for positional I/O.
|
|
*/
|
|
off_t
|
|
lseek_loop(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_eagain, EAGAIN)));
|
|
|
|
return old;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* If a given error loop is enabled,
|
|
* e.g. EINTR or EAGAIN, an I/O operation
|
|
* will loop until errno isn't -1 and one
|
|
* of these, e.g. -1 and EINTR
|
|
*/
|
|
int
|
|
try_err(int loop_err, int errval)
|
|
{
|
|
if (loop_err)
|
|
return errval;
|
|
|
|
/* errno is never negative,
|
|
so functions checking it
|
|
can use it accordingly */
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* non-atomic rename
|
|
*
|
|
* commented because i can't sacrifice
|
|
* exactly this property. nvmutil tries
|
|
* to protect files against e.g. power loss
|
|
*/
|
|
/*
|
|
int
|
|
x_i_rename(const char *src, const char *dst)
|
|
{
|
|
int sfd, dirfd;
|
|
ssize_t r;
|
|
char buf[8192];
|
|
|
|
sfd = open(src, O_RDONLY);
|
|
if (sfd < 0)
|
|
return -1;
|
|
|
|
dirfd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0600);
|
|
if (dirfd < 0) {
|
|
x_i_close(sfd);
|
|
return -1;
|
|
}
|
|
|
|
while ((r = read(sfd, buf, sizeof(buf))) > 0) {
|
|
ssize_t w = write(dirfd, buf, r);
|
|
if (w != r) {
|
|
x_i_close(sfd);
|
|
x_i_close(dirfd);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (r < 0) {
|
|
x_i_close(sfd);
|
|
x_i_close(dirfd);
|
|
return -1;
|
|
}
|
|
|
|
x_i_fsync(dirfd);
|
|
|
|
x_i_close(sfd);
|
|
x_i_close(dirfd);
|
|
|
|
if (unlink(src) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
*/
|
|
|
|
int
|
|
x_i_close(int fd)
|
|
{
|
|
int r;
|
|
int saved_errno = errno;
|
|
|
|
do {
|
|
r = close(fd);
|
|
} while (r == -1 && errno == EINTR);
|
|
|
|
if (r > -1)
|
|
errno = saved_errno;
|
|
|
|
return r;
|
|
}
|
|
|
|
int
|
|
x_i_fsync(int fd)
|
|
{
|
|
int r;
|
|
|
|
do {
|
|
r = fsync(fd);
|
|
} while (r == -1 && errno == EINTR);
|
|
|
|
return r;
|
|
}
|