mirror of
https://codeberg.org/libreboot/lbmk.git
synced 2026-07-11 05:52:36 +02:00
f90af15502
Signed-off-by: Leah Rowe <leah@libreboot.org>
896 lines
17 KiB
C
896 lines
17 KiB
C
/* SPDX-License-Identifier: MIT
|
|
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
|
|
*
|
|
* Pathless i/o, and some stuff you
|
|
* probably never saw in userspace.
|
|
*
|
|
* Be nice to the demon.
|
|
*/
|
|
|
|
/*
|
|
TODO: putting it here just so it's somewhere:
|
|
PATH_MAX is not reliable as a limit for paths,
|
|
because the real length depends on mount point,
|
|
and specific file systems.
|
|
more correct usage example:
|
|
long max = pathconf("/", _PC_PATH_MAX);
|
|
*/
|
|
|
|
|
|
#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;
|
|
int rval = 0;
|
|
errno = 0;
|
|
|
|
if (if_err(st_old == NULL, EFAULT) ||
|
|
if_err(fd < 0, EBADF) ||
|
|
(rval = fstat(fd, &st)) < 0 ||
|
|
(rval = fd_verify_regular(fd, st_old, &st)) < 0 ||
|
|
if_err(check_size && st.st_size != st_old->st_size, ESTALE))
|
|
return with_fallback_errno(ESTALE);
|
|
|
|
reset_caller_errno(rval);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
fsync_dir(const char *path)
|
|
{
|
|
int saved_errno = errno;
|
|
size_t pathlen = 0;
|
|
char *dirbuf = NULL;
|
|
int dirfd = -1;
|
|
char *slash = NULL;
|
|
struct stat st = {0};
|
|
int rval = 0;
|
|
errno = 0;
|
|
|
|
if (if_err(slen(path, PATH_MAX, &pathlen) == 0, EINVAL))
|
|
goto err_fsync_dir;
|
|
|
|
memcpy(smalloc(&dirbuf, pathlen + 1),
|
|
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 (if_err_sys(dirfd < 0) ||
|
|
if_err_sys((rval = fstat(dirfd, &st)) < 0) ||
|
|
if_err(!S_ISDIR(st.st_mode), ENOTDIR)
|
|
||
|
|
if_err_sys((rval = fsync_on_eintr(dirfd)) == -1))
|
|
goto err_fsync_dir;
|
|
|
|
close_on_eintr(&dirfd);
|
|
free_and_set_null(&dirbuf);
|
|
|
|
reset_caller_errno(rval);
|
|
return 0;
|
|
|
|
err_fsync_dir:
|
|
free_and_set_null(&dirbuf);
|
|
close_on_eintr(&dirfd);
|
|
|
|
return with_fallback_errno(EIO);
|
|
}
|
|
|
|
/* 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, size_t max_retries, int off_reset)
|
|
{
|
|
int saved_errno = errno;
|
|
ssize_t rval = 0;
|
|
ssize_t rc = 0;
|
|
size_t nrw_cur;
|
|
off_t off_cur;
|
|
void *mem_cur;
|
|
size_t retries_on_zero = 0;
|
|
errno = 0;
|
|
|
|
if (io_args(fd, mem, nrw, off, rw_type) == -1)
|
|
goto err_rw_file_exact;
|
|
|
|
while (1) {
|
|
|
|
/* Prevent theoretical overflow */
|
|
if (if_err(rval >= 0 && (size_t)rval > (nrw - rc), 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 (if_err(off < 0, EOVERFLOW))
|
|
goto err_rw_file_exact;
|
|
|
|
off_cur = off + (off_t)rc;
|
|
|
|
if ((rval = rw(fd, mem_cur, nrw_cur, off_cur, rw_type)) < 0)
|
|
goto err_rw_file_exact;
|
|
|
|
if (rval == 0) {
|
|
if (retries_on_zero++ < max_retries)
|
|
continue;
|
|
|
|
goto err_rw_file_exact;
|
|
}
|
|
|
|
retries_on_zero = 0;
|
|
}
|
|
|
|
if (if_err((size_t)rc != nrw, EIO) ||
|
|
(rval = rw_over_nrw(rc, nrw)) < 0)
|
|
goto err_rw_file_exact;
|
|
|
|
reset_caller_errno(rval);
|
|
return rval;
|
|
|
|
err_rw_file_exact:
|
|
return with_fallback_errno(EIO);
|
|
}
|
|
|
|
/* rw() - read-write but with more
|
|
* safety checks than barebones libc
|
|
*
|
|
* A fallback is provided for regular read/write.
|
|
* rw_type can be IO_READ (read), IO_WRITE (write),
|
|
* IO_PREAD (pread) or IO_PWRITE
|
|
*/
|
|
|
|
ssize_t
|
|
rw(int fd, void *mem, size_t nrw,
|
|
off_t off, int rw_type)
|
|
{
|
|
ssize_t rval = 0;
|
|
ssize_t r = -1;
|
|
struct stat st;
|
|
int saved_errno = errno;
|
|
errno = 0;
|
|
|
|
if (io_args(fd, mem, nrw, off, rw_type) == -1)
|
|
return with_fallback_errno(EINVAL);
|
|
|
|
switch (rw_type) {
|
|
case IO_WRITE:
|
|
r = write_on_eintr(fd, mem, nrw);
|
|
break;
|
|
case IO_READ:
|
|
r = read_on_eintr(fd, mem, nrw);
|
|
break;
|
|
case IO_PWRITE:
|
|
r = pwrite_on_eintr(fd, mem, nrw, off);
|
|
break;
|
|
case IO_PREAD:
|
|
r = pread_on_eintr(fd, mem, nrw, off);
|
|
break;
|
|
default:
|
|
errno = EINVAL;
|
|
break;
|
|
}
|
|
|
|
if ((rval = rw_over_nrw(r, nrw)) < 0)
|
|
return with_fallback_errno(EIO);
|
|
|
|
reset_caller_errno(rval);
|
|
return rval;
|
|
}
|
|
|
|
int
|
|
io_args(int fd, void *mem, size_t nrw,
|
|
off_t off, int rw_type)
|
|
{
|
|
int saved_errno = errno;
|
|
errno = 0;
|
|
|
|
if (if_err(mem == NULL, EFAULT) ||
|
|
if_err(fd < 0, EBADF) ||
|
|
if_err(off < 0, ERANGE) ||
|
|
if_err(!nrw, EPERM) || /* TODO: toggle zero-byte check */
|
|
if_err(nrw > (size_t)SSIZE_MAX, ERANGE) ||
|
|
if_err(((size_t)off + nrw) < (size_t)off, ERANGE) ||
|
|
if_err(rw_type > IO_PWRITE, EINVAL))
|
|
goto err_io_args;
|
|
|
|
reset_caller_errno(0);
|
|
return 0;
|
|
|
|
err_io_args:
|
|
return with_fallback_errno(EINVAL);
|
|
}
|
|
|
|
int
|
|
check_file(int fd, struct stat *st)
|
|
{
|
|
int saved_errno = errno;
|
|
int rval = 0;
|
|
errno = 0;
|
|
|
|
if (if_err(fd < 0, EBADF) ||
|
|
if_err(st == NULL, EFAULT) ||
|
|
((rval = fstat(fd, st)) == -1) ||
|
|
if_err(!S_ISREG(st->st_mode), EBADF))
|
|
goto err_is_file;
|
|
|
|
reset_caller_errno(rval);
|
|
return 0;
|
|
|
|
err_is_file:
|
|
return with_fallback_errno(EINVAL);
|
|
}
|
|
|
|
/* POSIX can say whatever it wants.
|
|
* specification != implementation
|
|
*/
|
|
ssize_t
|
|
rw_over_nrw(ssize_t r, size_t nrw)
|
|
{
|
|
if (if_err(!nrw, EIO) ||
|
|
(r == -1) ||
|
|
if_err((size_t)r > SSIZE_MAX, ERANGE) ||
|
|
if_err((size_t)r > nrw, ERANGE))
|
|
return with_fallback_errno(EIO);
|
|
|
|
return r;
|
|
}
|
|
|
|
/* two functions that reduce sloccount by
|
|
* two hundred lines */
|
|
int
|
|
if_err(int condition, int errval)
|
|
{
|
|
if (!condition)
|
|
return 0;
|
|
if (errval)
|
|
errno = errval;
|
|
return 1;
|
|
}
|
|
int
|
|
if_err_sys(int condition)
|
|
{
|
|
if (!condition)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
fs_rename_at(int olddirfd, const char *old,
|
|
int newdirfd, const char *new)
|
|
{
|
|
if (if_err(new == NULL || old == NULL, EFAULT) ||
|
|
if_err(olddirfd < 0 || newdirfd < 0, EBADF))
|
|
return -1;
|
|
|
|
return renameat(olddirfd, old, newdirfd, new);
|
|
}
|
|
|
|
/* secure open, based on relative path to root
|
|
*
|
|
* always a fixed fd for / see: rootfs()
|
|
* and fs_resolve_at()
|
|
*/
|
|
int
|
|
fs_open(const char *path, int flags)
|
|
{
|
|
struct filesystem *fs;
|
|
|
|
if (if_err(path == NULL, EFAULT) ||
|
|
if_err(path[0] != '/', EINVAL) ||
|
|
if_err_sys((fs = rootfs()) == NULL))
|
|
return -1;
|
|
|
|
return fs_resolve_at(fs->rootfd, path + 1, 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 = -1;
|
|
|
|
open_file_on_eintr("/", &global_fs.rootfd,
|
|
O_RDONLY | O_DIRECTORY | O_CLOEXEC, 0400, NULL);
|
|
|
|
if (global_fs.rootfd < 0)
|
|
return NULL;
|
|
|
|
fs_initialised = 1;
|
|
}
|
|
|
|
return &global_fs;
|
|
}
|
|
|
|
/* filesystem sandboxing in userspace
|
|
* TODO:
|
|
missing length bound check.
|
|
potential CPU DoS on very long paths, spammed repeatedly.
|
|
perhaps cap at MAX_PATH?
|
|
*/
|
|
int
|
|
fs_resolve_at(int dirfd, const char *path, int flags)
|
|
{
|
|
int nextfd = -1;
|
|
int curfd;
|
|
const char *p;
|
|
char name[PATH_MAX];
|
|
int saved_errno = errno;
|
|
int r;
|
|
int is_last;
|
|
errno = 0;
|
|
|
|
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 not the original input */
|
|
if (curfd != dirfd)
|
|
close_on_eintr(&curfd);
|
|
|
|
curfd = nextfd;
|
|
nextfd = -1;
|
|
}
|
|
|
|
reset_caller_errno(0);
|
|
return curfd;
|
|
|
|
err:
|
|
saved_errno = errno;
|
|
|
|
if (nextfd >= 0)
|
|
close_on_eintr(&nextfd);
|
|
|
|
/* close curfd only if it's not the original */
|
|
if (curfd != dirfd && curfd >= 0)
|
|
close_on_eintr(&curfd);
|
|
|
|
errno = saved_errno;
|
|
return with_fallback_errno(EIO);
|
|
}
|
|
|
|
/* NOTE:
|
|
rejects . and .. but not empty strings
|
|
after normalisation. edge case:
|
|
//////
|
|
|
|
normalised implicitly, but might be good
|
|
to add a defensive check regardless. code
|
|
probably not exploitable in current state.
|
|
*/
|
|
int
|
|
fs_next_component(const char **p,
|
|
char *name, size_t namesz)
|
|
{
|
|
const char *s = *p;
|
|
size_t len = 0;
|
|
|
|
while (*s == '/')
|
|
s++;
|
|
|
|
if (*s == '\0') {
|
|
*p = s;
|
|
return 0;
|
|
}
|
|
|
|
while (s[len] != '/' && s[len] != '\0')
|
|
len++;
|
|
|
|
if (len == 0 || len >= namesz ||
|
|
len >= PATH_MAX) {
|
|
errno = ENAMETOOLONG;
|
|
return -1;
|
|
}
|
|
|
|
memcpy(name, s, len);
|
|
name[len] = '\0';
|
|
|
|
/* reject . and .. */
|
|
if (if_err((name[0] == '.' && name[1] == '\0') ||
|
|
(name[0] == '.' && name[1] == '.' && name[2] == '\0'), EPERM))
|
|
goto err;
|
|
|
|
*p = s + len;
|
|
return 1;
|
|
err:
|
|
return with_fallback_errno(EPERM);
|
|
}
|
|
|
|
int
|
|
fs_open_component(int dirfd, const char *name,
|
|
int flags, int is_last)
|
|
{
|
|
int saved_errno = errno;
|
|
int fd;
|
|
struct stat st;
|
|
errno = 0;
|
|
|
|
fd = openat_on_eintr(dirfd, name,
|
|
(is_last ? flags : (O_RDONLY | O_DIRECTORY)) |
|
|
O_NOFOLLOW | O_CLOEXEC, (flags & O_CREAT) ? 0600 : 0);
|
|
|
|
if (!is_last &&
|
|
(if_err(fd < 0, EBADF) ||
|
|
if_err_sys(fstat(fd, &st) < 0) ||
|
|
if_err(!S_ISDIR(st.st_mode), ENOTDIR)))
|
|
return with_fallback_errno(EIO);
|
|
|
|
reset_caller_errno(fd);
|
|
return fd;
|
|
}
|
|
|
|
int
|
|
fs_dirname_basename(const char *path,
|
|
char **dir, char **base,
|
|
int allow_relative)
|
|
{
|
|
int saved_errno = errno;
|
|
char *buf = NULL;
|
|
char *slash;
|
|
size_t len;
|
|
errno = 0;
|
|
|
|
if (if_err(path == NULL || dir == NULL || base == NULL, EFAULT))
|
|
goto err;
|
|
|
|
slen(path, PATH_MAX, &len);
|
|
memcpy(smalloc(&buf, len + 1),
|
|
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) {
|
|
|
|
sdup(".", PATH_MAX, dir);
|
|
*base = buf;
|
|
} else {
|
|
free_and_set_null(&buf);
|
|
goto err;
|
|
}
|
|
|
|
reset_caller_errno(0);
|
|
return 0;
|
|
err:
|
|
return with_fallback_errno(EINVAL);
|
|
}
|
|
|
|
/* TODO: why does this abort, but others
|
|
e.g. open_file_on_eintr, don't???
|
|
*/
|
|
void
|
|
open_file_on_eintr(const char *path,
|
|
int *fd, int flags, mode_t mode,
|
|
struct stat *st)
|
|
{
|
|
int saved_errno = errno;
|
|
int rval = 0;
|
|
errno = 0;
|
|
|
|
if (path == NULL)
|
|
err_exit(EINVAL, "open_file_on_eintr: null path");
|
|
if (fd == NULL)
|
|
err_exit(EFAULT, "%s: open_file_on_eintr: null fd ptr", path);
|
|
if (*fd >= 0)
|
|
err_exit(EBADF,
|
|
"%s: open_file_on_eintr: file already open", path);
|
|
|
|
errno = 0;
|
|
while (fs_retry(saved_errno,
|
|
rval = open(path, flags, mode)));
|
|
|
|
if (rval < 0)
|
|
err_exit(errno,
|
|
"%s: open_file_on_eintr: could not close", path);
|
|
|
|
reset_caller_errno(rval);
|
|
*fd = rval;
|
|
|
|
/* we don't care about edge case behaviour here,
|
|
even if the next operation sets errno on success,
|
|
because the open() call is our main concern.
|
|
however, we also must preserve the new errno,
|
|
assuming it changed above under the same edge case */
|
|
|
|
saved_errno = errno;
|
|
|
|
if (st != NULL) {
|
|
if (fstat(*fd, st) < 0)
|
|
err_exit(errno, "%s: stat", path);
|
|
|
|
if (!S_ISREG(st->st_mode))
|
|
err_exit(errno, "%s: not a regular file", path);
|
|
}
|
|
|
|
if (lseek_on_eintr(*fd, 0, SEEK_CUR, 1, 1) == (off_t)-1)
|
|
err_exit(errno, "%s: file not seekable", path);
|
|
|
|
errno = saved_errno; /* see previous comment */
|
|
}
|
|
|
|
|
|
#ifdef __linux__ /* we use openat2 on linux */
|
|
int
|
|
openat_on_eintr(int dirfd, const char *path,
|
|
int flags, mode_t mode)
|
|
{
|
|
struct open_how how = {
|
|
.flags = flags,
|
|
.mode = mode,
|
|
.resolve =
|
|
RESOLVE_BENEATH |
|
|
RESOLVE_NO_SYMLINKS |
|
|
RESOLVE_NO_MAGICLINKS
|
|
};
|
|
int saved_errno = errno;
|
|
long rval = 0;
|
|
errno = 0;
|
|
|
|
if (if_err(dirfd < 0, EBADF) ||
|
|
if_err(path == NULL, EFAULT))
|
|
goto err;
|
|
|
|
errno = 0;
|
|
while (sys_retry(saved_errno,
|
|
rval = syscall(SYS_openat2, dirfd, path, &how, sizeof(how))));
|
|
|
|
if (rval == -1) /* avoid long->int UB for -1 */
|
|
goto err;
|
|
|
|
reset_caller_errno(rval);
|
|
return (int)rval;
|
|
err:
|
|
return with_fallback_errno(EIO); /* -1 */
|
|
}
|
|
#else /* regular openat on non-linux e.g. openbsd */
|
|
int
|
|
openat_on_eintr(int dirfd, const char *path,
|
|
int flags, mode_t mode)
|
|
{
|
|
int saved_errno = errno;
|
|
int rval = 0;
|
|
errno = 0;
|
|
|
|
if (if_err(dirfd < 0, EBADF) ||
|
|
if_err(path == NULL, EFAULT))
|
|
return with_fallback_errno(EIO);
|
|
|
|
while (fs_retry(saved_errno,
|
|
rval = openat(dirfd, path, flags, mode)));
|
|
|
|
reset_caller_errno(rval);
|
|
return rval;
|
|
}
|
|
#endif
|
|
|
|
off_t
|
|
lseek_on_eintr(int fd, off_t off, int whence,
|
|
int loop_eagain, int loop_eintr)
|
|
{
|
|
int saved_errno = errno;
|
|
off_t rval = 0;
|
|
errno = 0;
|
|
|
|
while (off_retry(saved_errno,
|
|
rval = lseek(fd, off, whence)));
|
|
|
|
reset_caller_errno(rval);
|
|
return rval;
|
|
}
|
|
|
|
int
|
|
mkdirat_on_eintr(int dirfd,
|
|
const char *path, mode_t mode)
|
|
{
|
|
int saved_errno = errno;
|
|
int rval = 0;
|
|
errno = 0;
|
|
|
|
if (if_err(dirfd < 0, EBADF) ||
|
|
if_err(path == NULL, EFAULT))
|
|
return with_fallback_errno(EIO);
|
|
|
|
while (fs_retry(saved_errno,
|
|
rval = mkdirat(dirfd, path, mode)));
|
|
|
|
reset_caller_errno(rval);
|
|
return rval;
|
|
}
|
|
|
|
ssize_t
|
|
read_on_eintr(int fd,
|
|
void *buf, size_t count)
|
|
{
|
|
int saved_errno = errno;
|
|
ssize_t rval = 0;
|
|
errno = 0;
|
|
|
|
if (if_err(buf == NULL, EFAULT) ||
|
|
if_err(fd < 0, EBADF) ||
|
|
if_err(count == 0, EINVAL))
|
|
return with_fallback_errno(EIO);
|
|
|
|
while (rw_retry(saved_errno,
|
|
rval = read(fd, buf, count)));
|
|
|
|
reset_caller_errno(rval);
|
|
return rval;
|
|
}
|
|
|
|
ssize_t
|
|
pread_on_eintr(int fd,
|
|
void *buf, size_t count,
|
|
off_t off)
|
|
{
|
|
int saved_errno = errno;
|
|
ssize_t rval = 0;
|
|
errno = 0;
|
|
|
|
if (if_err(buf == NULL, EFAULT) ||
|
|
if_err(fd < 0, EBADF) ||
|
|
if_err(off < 0, EFAULT) ||
|
|
if_err(count == 0, EINVAL))
|
|
return with_fallback_errno(EIO);
|
|
|
|
while (rw_retry(saved_errno,
|
|
rval = pread(fd, buf, count, off)));
|
|
|
|
reset_caller_errno(rval);
|
|
return rval;
|
|
}
|
|
|
|
ssize_t
|
|
write_on_eintr(int fd,
|
|
void *buf, size_t count)
|
|
{
|
|
int saved_errno = errno;
|
|
ssize_t rval = 0;
|
|
errno = 0;
|
|
|
|
if (if_err(buf == NULL, EFAULT) ||
|
|
if_err(fd < 0, EBADF) ||
|
|
if_err(count == 0, EINVAL))
|
|
return with_fallback_errno(EIO);
|
|
|
|
while (rw_retry(saved_errno,
|
|
rval = write(fd, buf, count)));
|
|
|
|
reset_caller_errno(rval);
|
|
return rval;
|
|
}
|
|
|
|
ssize_t
|
|
pwrite_on_eintr(int fd,
|
|
void *buf, size_t count,
|
|
off_t off)
|
|
{
|
|
int saved_errno = errno;
|
|
ssize_t rval = 0;
|
|
errno = 0;
|
|
|
|
if (if_err(buf == NULL, EFAULT) ||
|
|
if_err(fd < 0, EBADF) ||
|
|
if_err(off < 0, EFAULT) ||
|
|
if_err(count == 0, EINVAL))
|
|
return with_fallback_errno(EIO);
|
|
|
|
while (rw_retry(saved_errno,
|
|
rval = pwrite(fd, buf, count, off)));
|
|
|
|
reset_caller_errno(rval);
|
|
return rval;
|
|
}
|
|
|
|
int
|
|
fsync_on_eintr(int fd)
|
|
{
|
|
int saved_errno = errno;
|
|
int rval = 0;
|
|
errno = 0;
|
|
|
|
if (if_err(fd < 0, EBADF))
|
|
return with_fallback_errno(EIO);
|
|
|
|
while (fs_retry(saved_errno,
|
|
rval = fsync(fd)));
|
|
|
|
reset_caller_errno(rval);
|
|
return rval;
|
|
}
|
|
|
|
void
|
|
close_on_eintr(int *fd)
|
|
{
|
|
int saved_errno = errno;
|
|
int rval = 0;
|
|
|
|
if (fd == NULL)
|
|
err_exit(EINVAL, "close_on_eintr: null pointer");
|
|
if (*fd < 0)
|
|
return;
|
|
|
|
errno = 0;
|
|
while (fs_retry(saved_errno,
|
|
rval = close(*fd)));
|
|
|
|
if (rval < 0)
|
|
err_exit(errno, "close_on_eintr: could not close");
|
|
|
|
*fd = -1;
|
|
|
|
reset_caller_errno(rval);
|
|
}
|
|
|
|
/* unified eintr looping.
|
|
* differently typed functions
|
|
* to avoid potential UB
|
|
*
|
|
* ONE MACRO TO RULE THEM ALL:
|
|
*/
|
|
#define fs_err_retry() \
|
|
do { \
|
|
if ((rval == -1) && \
|
|
(errno == EINTR)) \
|
|
return 1; \
|
|
if (rval >= 0 && !errno) \
|
|
errno = saved_errno; \
|
|
return 0; \
|
|
} while(0)
|
|
/*
|
|
* Regarding the errno logic above:
|
|
* on success, it is permitted that
|
|
* a syscall could still set errno.
|
|
* We reset errno after storingit
|
|
* for later preservation, in functions
|
|
* that call *_retry() functions.
|
|
*
|
|
* They rely ultimately on this
|
|
* macro for errno restoration. We
|
|
* assume therefore that errno was
|
|
* reset to zero before the retry
|
|
* loop. If errno is then *set* on
|
|
* success, we leave it alone. Otherwise,
|
|
* we restore the caller's saved errno.
|
|
*
|
|
* This offers some consistency, while
|
|
* complying with POSIX specification.
|
|
*/
|
|
|
|
|
|
/* retry switch for offset-based
|
|
* functions e.g. lseek
|
|
*/
|
|
/* retry switch for functions that
|
|
return long status e.g. linux syscall
|
|
*/
|
|
int
|
|
off_retry(int saved_errno, off_t rval)
|
|
{
|
|
fs_err_retry();
|
|
}
|
|
|
|
/* retry switch for functions that
|
|
return long status e.g. linux syscall
|
|
*/
|
|
int
|
|
sys_retry(int saved_errno, long rval)
|
|
{
|
|
fs_err_retry();
|
|
}
|
|
|
|
/* retry switch for functions that
|
|
return int status e.g. mkdirat
|
|
*/
|
|
int
|
|
fs_retry(int saved_errno, int rval)
|
|
{
|
|
fs_err_retry();
|
|
}
|
|
|
|
/* retry switch for functions that
|
|
return rw count in ssize_t e.g. read()
|
|
*/
|
|
int
|
|
rw_retry(int saved_errno, ssize_t rval)
|
|
{
|
|
fs_err_retry();
|
|
}
|