diff options
Diffstat (limited to 'util/libreboot-utils/lib/file.c')
| -rw-r--r-- | util/libreboot-utils/lib/file.c | 830 |
1 files changed, 339 insertions, 491 deletions
diff --git a/util/libreboot-utils/lib/file.c b/util/libreboot-utils/lib/file.c index 1d2de9b8..805db726 100644 --- a/util/libreboot-utils/lib/file.c +++ b/util/libreboot-utils/lib/file.c @@ -7,6 +7,16 @@ * 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> @@ -34,51 +44,31 @@ same_file(int fd, struct stat *st_old, { struct stat st; int saved_errno = errno; + int rval = 0; + errno = 0; - /* 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; + 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); - errno = saved_errno; + reset_caller_errno(rval); return 0; - -err_same_file: - return set_errno(saved_errno, ESTALE); } 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 close_errno; + int rval = 0; + errno = 0; if (if_err(slen(path, PATH_MAX, &pathlen) == 0, EINVAL)) goto err_fsync_dir; @@ -109,26 +99,23 @@ fsync_dir(const char *path) ); if (if_err_sys(dirfd < 0) || - if_err_sys(fstat(dirfd, &st) < 0) || + if_err_sys((rval = fstat(dirfd, &st)) < 0) || if_err(!S_ISDIR(st.st_mode), ENOTDIR) || - if_err_sys(fsync_on_eintr(dirfd) == -1)) + if_err_sys((rval = fsync_on_eintr(dirfd)) == -1)) goto err_fsync_dir; close_on_eintr(&dirfd); - free_and_set_null(&dirbuf); - errno = saved_errno; + reset_caller_errno(rval); return 0; err_fsync_dir: - - free_and_set_null(&dirbuf); close_on_eintr(&dirfd); - return set_errno(saved_errno, EIO); + return with_fallback_errno(EIO); } /* rw_file_exact() - Read perfectly or die @@ -150,38 +137,25 @@ err_fsync_dir: 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) + off_t off, int rw_type, size_t max_retries, int off_reset) { - ssize_t rval; - ssize_t rc; - + 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; - - int saved_errno = errno; + size_t retries_on_zero = 0; errno = 0; - 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; + if (if_err(rval >= 0 && (size_t)rval > (nrw - rc), EOVERFLOW)) goto err_rw_file_exact; - } rc += rval; if ((size_t)rc >= nrw) @@ -190,227 +164,79 @@ rw_file_exact(int fd, unsigned char *mem, size_t nrw, mem_cur = (void *)(mem + (size_t)rc); nrw_cur = (size_t)(nrw - (size_t)rc); - if (off < 0) { - errno = EOVERFLOW; + if (if_err(off < 0, 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) + 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; - errno = EIO; goto err_rw_file_exact; } retries_on_zero = 0; } - if ((size_t)rc != nrw) { - - errno = EIO; + if (if_err((size_t)rc != nrw, EIO) || + (rval = rw_over_nrw(rc, nrw)) < 0) goto err_rw_file_exact; - } - - rval = rw_over_nrw(rc, nrw); - if (rval < 0) - goto err_rw_file_exact; - - errno = saved_errno; + reset_caller_errno(rval); return rval; err_rw_file_exact: - - return set_errno(saved_errno, EIO); + return with_fallback_errno(EIO); } -/* prw() - portable read-write with more +/* rw() - read-write but with more * safety checks than barebones libc * - * 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 - * - * REAL_POS_IO is enabled by default in common.h - * and the fallback version was written for fun. - * You should just use the real one (REAL_POS_IO 1), - * since it is generally more reliable. */ 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) +rw(int fd, void *mem, size_t nrw, + off_t off, int rw_type) { - ssize_t rval; - ssize_t r; - int positional_rw; + ssize_t rval = 0; + ssize_t r = -1; 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; errno = 0; - 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_on_eintr(fd, mem, nrw); - else if (rw_type == IO_READ) - r = read_on_eintr(fd, mem, nrw); -#if defined(REAL_POS_IO) && \ - REAL_POS_IO > 0 - else if (rw_type == IO_PWRITE) - r = pwrite_on_eintr(fd, mem, nrw, off); - else if (rw_type == IO_PREAD) - r = pread_on_eintr(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_on_eintr(fd, mem, nrw); - else if (rw_type == IO_PWRITE) - r = write_on_eintr(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; + 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; } - errno = saved_errno; - - rval = rw_over_nrw(r, nrw); - if (rval < 0) - goto err_prw; - - errno = saved_errno; + if ((rval = rw_over_nrw(r, nrw)) < 0) + return with_fallback_errno(EIO); + reset_caller_errno(rval); return rval; - -#endif - -err_prw: - return set_errno(saved_errno, EIO); } int @@ -418,6 +244,7 @@ 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) || @@ -428,70 +255,46 @@ io_args(int fd, void *mem, size_t nrw, if_err(rw_type > IO_PWRITE, EINVAL)) goto err_io_args; - errno = saved_errno; + reset_caller_errno(0); return 0; err_io_args: - return set_errno(saved_errno, EINVAL); + 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) || - if_err(fstat(fd, st) == -1, 0) || + ((rval = fstat(fd, st)) == -1) || if_err(!S_ISREG(st->st_mode), EBADF)) goto err_is_file; - errno = saved_errno; + reset_caller_errno(rval); return 0; err_is_file: - return set_errno(saved_errno, EINVAL); + return with_fallback_errno(EINVAL); } /* POSIX can say whatever it wants. * specification != implementation */ - ssize_t rw_over_nrw(ssize_t r, size_t nrw) { - int saved_errno = errno; - - if (if_err(!nrw, 0) || - if_err(r == -1, 0) || + if (if_err(!nrw, EIO) || + (r == -1) || if_err((size_t)r > SSIZE_MAX, ERANGE) || if_err((size_t)r > nrw, ERANGE)) - goto err_rw_over_nrw; + return with_fallback_errno(EIO); - errno = saved_errno; return r; - -err_rw_over_nrw: - return set_errno(saved_errno, EIO); -} - -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; } /* two functions that reduce sloccount by @@ -501,10 +304,8 @@ if_err(int condition, int errval) { if (!condition) return 0; - if (errval) errno = errval; - return 1; } int @@ -514,102 +315,6 @@ if_err_sys(int condition) return 0; return 1; } -/* errno can never be -1, so you can - * use this to conditionally set an integer - * for comparison. see example in lseek_on_eintr - */ -int -try_err(int loop_err, int errval) -{ - if (loop_err) - return errval; - return -1; -} - -void -open_on_eintr(const char *path, - int *fd, int flags, mode_t mode, - struct stat *st) -{ - int r = -1; - int saved_errno = errno; - - if (path == NULL) - err_exit(EINVAL, "open_on_eintr: null path"); - - if (fd == NULL) - err_exit(EFAULT, "%s: open_on_eintr: null fd ptr", path); - - if (*fd >= 0) - err_exit(EBADF, "%s: open_on_eintr: file already open", path); - - do { - r = open(path, flags, mode); - } while (r == -1 && ( - errno == EINTR || errno == EAGAIN || - errno == EWOULDBLOCK || errno == ETXTBSY)); - - if (r < 0) - err_exit(errno, "%s: open_on_eintr: could not close", path); - - *fd = r; - - 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; -} - -void -close_on_eintr(int *fd) -{ - int r; - int saved_errno = errno; - - if (fd == NULL) - err_exit(EINVAL, "close_on_eintr: null pointer"); - - if (*fd < 0) - return; - - do { - r = close(*fd); - } while (r == -1 && ( - errno == EINTR || errno == EAGAIN || - errno == EWOULDBLOCK || errno == ETXTBSY)); - - if (r < 0) - err_exit(errno, "close_on_eintr: could not close"); - - *fd = -1; - - errno = saved_errno; -} - -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, @@ -653,7 +358,7 @@ rootfs(void) global_fs.rootfd = -1; - open_on_eintr("/", &global_fs.rootfd, + open_file_on_eintr("/", &global_fs.rootfd, O_RDONLY | O_DIRECTORY | O_CLOEXEC, 0400, NULL); if (global_fs.rootfd < 0) @@ -681,6 +386,7 @@ fs_resolve_at(int dirfd, const char *path, int flags) int saved_errno = errno; int r; int is_last; + errno = 0; if (dirfd < 0 || path == NULL || *path == '\0') { errno = EINVAL; @@ -711,7 +417,7 @@ fs_resolve_at(int dirfd, const char *path, int flags) nextfd = -1; } - errno = saved_errno; + reset_caller_errno(0); return curfd; err: @@ -725,7 +431,7 @@ err: close_on_eintr(&curfd); errno = saved_errno; - return -1; + return with_fallback_errno(EIO); } /* NOTE: @@ -765,41 +471,36 @@ fs_next_component(const char **p, name[len] = '\0'; /* reject . and .. */ - if ((name[0] == '.' && name[1] == '\0') || - (name[0] == '.' && name[1] == '.' && name[2] == '\0')) { - errno = EPERM; - return -1; - } + 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 = openat2p(dirfd, name, + 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 (if_err(fd < 0, EBADF) || - if_err_sys(fstat(fd, &st) < 0)) - return -1; - - if (!S_ISDIR(st.st_mode)) { - - close_on_eintr(&fd); - errno = ENOTDIR; - return -1; - } - } + 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; } @@ -808,13 +509,14 @@ 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; - int rval; + errno = 0; if (if_err(path == NULL || dir == NULL || base == NULL, EFAULT)) - return -1; + goto err; slen(path, PATH_MAX, &len); memcpy(smalloc(&buf, len + 1), @@ -841,22 +543,75 @@ fs_dirname_basename(const char *path, sdup(".", PATH_MAX, dir); *base = buf; } else { - errno = EINVAL; free_and_set_null(&buf); - return -1; + goto err; } + reset_caller_errno(0); return 0; +err: + return with_fallback_errno(EINVAL); } -/* portable wrapper for use of openat2 on linux, - * with fallback for others e.g. openbsd +/* 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 -openat2p(int dirfd, const char *path, +openat_on_eintr(int dirfd, const char *path, int flags, mode_t mode) { -#ifdef __linux__ struct open_how how = { .flags = flags, .mode = mode, @@ -866,37 +621,58 @@ openat2p(int dirfd, const char *path, RESOLVE_NO_MAGICLINKS }; int saved_errno = errno; - int rval; -#endif + long rval = 0; + errno = 0; if (if_err(dirfd < 0, EBADF) || if_err(path == NULL, EFAULT)) - return -1; + goto err; -retry: errno = 0; + while (sys_retry(saved_errno, + rval = syscall(SYS_openat2, dirfd, path, &how, sizeof(how)))); -#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); + 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 - if (rval == -1 && ( - errno == EINTR || - errno == EAGAIN || - errno == EWOULDBLOCK || - errno == ETXTBSY)) - goto retry; - if (rval >= 0) - errno = saved_errno; +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; } @@ -905,26 +681,17 @@ mkdirat_on_eintr(int dirfd, const char *path, mode_t mode) { int saved_errno = errno; - int rval; + int rval = 0; + errno = 0; if (if_err(dirfd < 0, EBADF) || if_err(path == NULL, EFAULT)) - return -1; - -retry: - errno = 0; - rval = mkdirat(dirfd, path, mode); - - if (rval == -1 && ( - errno == EINTR || - errno == EAGAIN || - errno == EWOULDBLOCK || - errno == ETXTBSY)) - goto retry; + return with_fallback_errno(EIO); - if (rval >= 0) - errno = saved_errno; + while (fs_retry(saved_errno, + rval = mkdirat(dirfd, path, mode))); + reset_caller_errno(rval); return rval; } @@ -933,27 +700,19 @@ read_on_eintr(int fd, void *buf, size_t count) { int saved_errno = errno; - int rval; + ssize_t rval = 0; + errno = 0; if (if_err(buf == NULL, EFAULT) || if_err(fd < 0, EBADF) || if_err(count == 0, EINVAL)) - goto err; + return with_fallback_errno(EIO); -retry: - errno = 0; + while (rw_retry(saved_errno, + rval = read(fd, buf, count))); - if ((rval = read(fd, buf, count)) == -1 && ( - errno == EINTR || - errno == EAGAIN || - errno == EWOULDBLOCK || - errno == ETXTBSY)) - goto retry; - - errno = saved_errno; + reset_caller_errno(rval); return rval; -err: - return set_errno(saved_errno, EIO); } ssize_t @@ -962,28 +721,20 @@ pread_on_eintr(int fd, off_t off) { int saved_errno = errno; - int rval; + 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)) - goto err; - -retry: - errno = 0; + return with_fallback_errno(EIO); - if ((rval = pread(fd, buf, count, off)) == -1 && ( - errno == EINTR || - errno == EAGAIN || - errno == EWOULDBLOCK || - errno == ETXTBSY)) - goto retry; + while (rw_retry(saved_errno, + rval = pread(fd, buf, count, off))); - errno = saved_errno; + reset_caller_errno(rval); return rval; -err: - return set_errno(saved_errno, EIO); } ssize_t @@ -991,27 +742,19 @@ write_on_eintr(int fd, void *buf, size_t count) { int saved_errno = errno; - int rval; + ssize_t rval = 0; + errno = 0; if (if_err(buf == NULL, EFAULT) || if_err(fd < 0, EBADF) || if_err(count == 0, EINVAL)) - goto err; - -retry: - errno = 0; + return with_fallback_errno(EIO); - if ((rval = write(fd, buf, count)) == -1 && ( - errno == EINTR || - errno == EAGAIN || - errno == EWOULDBLOCK || - errno == ETXTBSY)) - goto retry; + while (rw_retry(saved_errno, + rval = write(fd, buf, count))); - errno = saved_errno; + reset_caller_errno(rval); return rval; -err: - return set_errno(saved_errno, EIO); } ssize_t @@ -1020,26 +763,131 @@ pwrite_on_eintr(int fd, off_t off) { int saved_errno = errno; - int rval; + 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)) - goto err; + return with_fallback_errno(EIO); + + while (rw_retry(saved_errno, + rval = pwrite(fd, buf, count, off))); -retry: + reset_caller_errno(rval); + return rval; +} + +int +fsync_on_eintr(int fd) +{ + int saved_errno = errno; + int rval = 0; errno = 0; - if ((rval = pwrite(fd, buf, count, off)) == -1 && ( - errno == EINTR || - errno == EAGAIN || - errno == EWOULDBLOCK || - errno == ETXTBSY)) - goto retry; + if (if_err(fd < 0, EBADF)) + return with_fallback_errno(EIO); - errno = saved_errno; + while (fs_retry(saved_errno, + rval = fsync(fd))); + + reset_caller_errno(rval); return rval; -err: - return set_errno(saved_errno, EIO); +} + +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() \ + if ((rval == -1) && \ + (errno == EINTR)) \ + return 1; \ + if (rval >= 0 && !errno) \ + errno = saved_errno; \ + return 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(); } |
