diff options
Diffstat (limited to 'util')
| -rw-r--r-- | util/nvmutil/nvmutil.c | 420 |
1 files changed, 259 insertions, 161 deletions
diff --git a/util/nvmutil/nvmutil.c b/util/nvmutil/nvmutil.c index 2b7a23c5..fe8364f7 100644 --- a/util/nvmutil/nvmutil.c +++ b/util/nvmutil/nvmutil.c @@ -15,6 +15,11 @@ * -Os -Wall -Wextra -Werror -pedantic -std=c90 */ +#define OFF_ERR 0 +#ifndef OFF_RESET +#define OFF_RESET 1 +#endif + /* * NOTE: older Linux lacked arc4random. * added in glibc 2.36. Just pass HAVE_ARC4RANDOM_BUF=0 @@ -249,6 +254,9 @@ typedef char static_assert_twos_complement[ typedef char assert_ulong_ptr[ (sizeof(ulong) >= sizeof(void *)) ? 1 : -1 ]; +typedef char assert_size_t_ptr[ + (sizeof(size_t) >= sizeof(void *)) ? 1 : -1 +]; /* * We set _FILE_OFFSET_BITS 64, but we only handle @@ -404,23 +412,26 @@ static ssize_t rw_gbe_file_exact(int fd, u8 *mem, size_t nrw, off_t off, int rw_type); static ssize_t rw_file_exact(int fd, u8 *mem, size_t len, off_t off, int rw_type, int loop_eagain, int loop_eintr, - size_t max_retries); -static ssize_t rw_file_once(int fd, u8 *mem, size_t len, - off_t off, int rw_type, size_t rc, int loop_eagain, - int loop_eintr, size_t max_retries); + size_t max_retries, int off_reset); static ssize_t prw(int fd, void *mem, size_t nrw, - off_t off, int rw_type, int loop_eagain, int loop_eintr); + off_t off, int rw_type, int loop_eagain, int loop_eintr, + int off_reset); +static int io_args(int fd, void *mem, size_t nrw, + off_t off, int rw_type); static int check_file(int fd, struct stat *st); -static int rw_over_nrw(ssize_t r, size_t nrw); +static ssize_t rw_over_nrw(ssize_t r, size_t nrw); +#if !defined(HAVE_REAL_PREAD_PWRITE) || \ + HAVE_REAL_PREAD_PWRITE < 1 static off_t lseek_loop(int fd, off_t off, int whence, int loop_eagain, int loop_eintr); +#endif static int try_err(int loop_err, int errval); /* * Error handling and cleanup */ -static int close_files(void); static void err(int nvm_errval, const char *msg, ...); +static int close_files(void); static const char *getnvmprogname(void); static void usage(int usage_exit); @@ -452,9 +463,6 @@ static void usage(int usage_exit); #define NVM_WORDS (NVM_SIZE >> 1) #define NVM_CHECKSUM_WORD (NVM_WORDS - 1) -#define NUM_RANDOM_BYTES 12 -static u8 rnum[NUM_RANDOM_BYTES]; - /* * Portable macro based on BSD nitems. * Used to count the number of commands (see below). @@ -641,8 +649,6 @@ typedef char assert_read[(IO_READ==0)?1:-1]; typedef char assert_write[(IO_WRITE==1)?1:-1]; typedef char assert_pread[(IO_PREAD==2)?1:-1]; typedef char assert_pwrite[(IO_PWRITE==3)?1:-1]; -typedef char assert_rand_byte[(NUM_RANDOM_BYTES>0)?1:-1]; -typedef char assert_rand_len[(NUM_RANDOM_BYTES<NVM_SIZE)?1:-1]; /* commands */ typedef char assert_cmd_dump[(CMD_DUMP==0)?1:-1]; typedef char assert_cmd_setmac[(CMD_SETMAC==1)?1:-1]; @@ -670,6 +676,8 @@ typedef char bool_loop_eintr[(LOOP_EINTR==1||LOOP_EINTR==0)?1:-1]; typedef char bool_loop_eagain[(LOOP_EAGAIN==1||LOOP_EAGAIN==0)?1:-1]; typedef char bool_no_loop_eintr[(NO_LOOP_EINTR==0)?1:-1]; typedef char bool_no_loop_eagain[(NO_LOOP_EAGAIN==0)?1:-1]; +typedef char bool_off_err[(OFF_ERR==0)?1:-1]; +typedef char bool_off_reset[(OFF_RESET==0||OFF_RESET==1)?1:-1]; static int io_err_gbe = 0; static int rw_check_err_read[] = {0, 0}; @@ -697,12 +705,12 @@ main(int argc, char *argv[]) #ifdef NVMUTIL_PLEDGE #ifdef NVMUTIL_UNVEIL - if (pledge("stdio rpath wpath unveil", NULL) == -1) + if (pledge("stdio flock rpath wpath unveil", NULL) == -1) err(errno, "pledge"); if (unveil("/dev/null", "r") == -1) err(errno, "unveil /dev/null"); #else - if (pledge("stdio rpath wpath", NULL) == -1) + if (pledge("stdio flock rpath wpath", NULL) == -1) err(errno, "pledge"); #endif #endif @@ -719,25 +727,28 @@ main(int argc, char *argv[]) err(errno, "%s: unveil ro", fname); if (unveil(NULL, NULL) == -1) err(errno, "unveil block (ro)"); - if (pledge("stdio rpath", NULL) == -1) + if (pledge("stdio flock rpath", NULL) == -1) err(errno, "pledge ro (kill unveil)"); } else { if (unveil(fname, "rw") == -1) err(errno, "%s: unveil rw", fname); if (unveil(NULL, NULL) == -1) err(errno, "unveil block (rw)"); - if (pledge("stdio rpath wpath", NULL) == -1) + if (pledge("stdio flock rpath wpath", NULL) == -1) err(errno, "pledge rw (kill unveil)"); } #else if (command[cmd_index].flags == O_RDONLY) { - if (pledge("stdio rpath", NULL) == -1) + if (pledge("stdio flock rpath", NULL) == -1) err(errno, "pledge ro"); } #endif #endif +#if !defined(HAVE_ARC4RANDOM_BUF) || \ + (HAVE_ARC4RANDOM_BUF) < 1 srand((uint)(time(NULL) ^ getpid())); +#endif open_gbe_file(); lock_gbe_file(); @@ -936,11 +947,17 @@ xstrxcmp(const char *a, const char *b, size_t maxlen) err(EINVAL, "Empty string in xstrxcmp"); for (i = 0; i < maxlen; i++) { - if (a[i] != b[i]) - return (u8)a[i] - (u8)b[i]; + u8 ac = (u8)a[i]; + u8 bc = (u8)b[i]; + + if (ac == '\0' || bc == '\0') { + if (ac == bc) + return 0; + return ac - bc; + } - if (a[i] == '\0') - return 0; + if (ac != bc) + return ac - bc; } /* @@ -1439,7 +1456,7 @@ cmd_helper_cat(void) fflush(NULL); for (p = 0; p < 2; p++) { - gbe_cat_buf(buf + (p * GBE_PART_SIZE)); + gbe_cat_buf(buf + (size_t)(p * GBE_PART_SIZE)); for (ff = 0; ff < n; ff++) gbe_cat_buf(pad); @@ -1451,7 +1468,7 @@ gbe_cat_buf(u8 *b) { if (rw_file_exact(STDOUT_FILENO, b, GBE_PART_SIZE, 0, IO_WRITE, LOOP_EAGAIN, LOOP_EINTR, - MAX_ZERO_RW_RETRY) < 0) + MAX_ZERO_RW_RETRY, OFF_ERR) < 0) err(errno, "stdout: cat"); } @@ -1760,7 +1777,7 @@ gbe_mem_offset(size_t p, const char *f_op) off_t gbe_off = gbe_x_offset(p, f_op, "mem", GBE_PART_SIZE, GBE_FILE_SIZE); - return (u8 *)(buf + gbe_off); + return (u8 *)(buf + (size_t)gbe_off); } /* @@ -1804,21 +1821,23 @@ static ssize_t rw_gbe_file_exact(int fd, u8 *mem, size_t nrw, off_t off, int rw_type) { - ulong mem_addr; - ulong buf_addr; - ulong buf_end; + size_t mem_addr; + size_t buf_addr; + ssize_t r; - if (mem == NULL) - goto err_rw_gbe_file_exact; + if (io_args(fd, mem, nrw, off, rw_type) == -1) + return -1; - mem_addr = (ulong)(void *)mem; - buf_addr = (ulong)(void *)buf; - buf_end = buf_addr + (ulong)GBE_FILE_SIZE; + mem_addr = (size_t)(void *)mem; + buf_addr = (size_t)(void *)buf; - if (mem != (void *)pad && - mem != (void *)rnum && - (mem_addr < buf_addr || mem_addr >= buf_end)) - goto err_rw_gbe_file_exact; + if (mem != (void *)pad) { + if (mem_addr < buf_addr) + goto err_rw_gbe_file_exact; + + if ((mem_addr - buf_addr) >= (size_t)GBE_FILE_SIZE) + goto err_rw_gbe_file_exact; + } if (off < 0 || off >= gbe_file_size) goto err_rw_gbe_file_exact; @@ -1826,11 +1845,14 @@ rw_gbe_file_exact(int fd, u8 *mem, size_t nrw, if (nrw > (size_t)(gbe_file_size - off)) goto err_rw_gbe_file_exact; - if (nrw > GBE_PART_SIZE) + if (nrw > (size_t)GBE_PART_SIZE) goto err_rw_gbe_file_exact; - return rw_file_exact(fd, mem, nrw, off, rw_type, - NO_LOOP_EAGAIN, LOOP_EINTR, MAX_ZERO_RW_RETRY); + r = rw_file_exact(fd, mem, nrw, off, rw_type, + NO_LOOP_EAGAIN, LOOP_EINTR, MAX_ZERO_RW_RETRY, + OFF_ERR); + + return rw_over_nrw(r, nrw); err_rw_gbe_file_exact: errno = EIO; @@ -1863,91 +1885,64 @@ err_rw_gbe_file_exact: * returned and errno is set accordingly. * * Zero-byte returns are not allowed. - * It calls rw_file_once(), which will - * re-try on zero-read a finite number - * of times, to prevent infinite loops - * while also having fault tolerance. + * It will re-spin a finite number of + * times upon zero-return, to recover, + * otherwise it will return an error. */ static ssize_t rw_file_exact(int fd, u8 *mem, size_t nrw, off_t off, int rw_type, int loop_eagain, - int loop_eintr, size_t max_retries) + int loop_eintr, size_t max_retries, + int off_reset) { - ssize_t rv; - size_t rc; + ssize_t rv = 0; + ssize_t rc = 0; + size_t retries_on_zero = 0; + off_t off_cur; + size_t nrw_cur; + void *mem_cur; - for (rc = 0, rv = 0; rc < nrw; ) { - if ((rv = rw_file_once(fd, mem, nrw, off, rw_type, rc, - loop_eagain, loop_eintr, max_retries)) < 0) - return -1; - - /* rw_file_once never returns - zero, but it's still logically - incorrect not to handle it here */ - - if (rv == 0) { - errno = EIO; - return -1; - } + if (io_args(fd, mem, nrw, off, rw_type) == -1) + return -1; + while (1) { + /* Prevent theoretical overflow */ - if ((size_t)rv > nrw - rc) + if (rv >= 0 && (size_t)rv > (nrw - rc)) goto err_rw_file_exact; - rc += (size_t)rv; - } - - return rc; - -err_rw_file_exact: - errno = EIO; - return -1; -} + rc += rv; + if ((size_t)rc >= nrw) + break; -/* - * rw_file_once() - Read less than perfectly - * (and possibly die) - * - * Read/write, but don't insist on an - * absolute read; e.g. if 100 bytes are - * requested, this may return 80 <-- fine - * - * 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. - */ -static ssize_t -rw_file_once(int fd, u8 *mem, size_t nrw, - off_t off, int rw_type, size_t rc, - int loop_eagain, int loop_eintr, - size_t max_retries) -{ - ssize_t rv; - size_t retries_on_zero = 0; + mem_cur = (void *)(mem + (size_t)rc); + nrw_cur = (size_t)(nrw - (size_t)rc); + if (off < 0) + goto err_rw_file_exact; + off_cur = (off_t)((size_t)off + (size_t)rc); - if (mem == NULL) - goto err_rw_file_once; + rv = prw(fd, mem_cur, nrw_cur, off_cur, + rw_type, loop_eagain, loop_eintr, + off_reset); -read_again: - rv = prw(fd, mem + rc, nrw - rc, off + rc, rw_type, - loop_eagain, loop_eintr); + if (rv < 0) + return -1; - if (rv < 0) - return -1; + if (rv == 0) { + if (retries_on_zero++ < max_retries) + continue; + goto err_rw_file_exact; + } - if ((size_t)rv > (nrw - rc))/* don't overflow */ - goto err_rw_file_once; + retries_on_zero = 0; + } - if (rv != 0) - return rv; + if ((size_t)rc != nrw) + goto err_rw_file_exact; - if (retries_on_zero++ < max_retries) - goto read_again; + return rw_over_nrw(rc, nrw); -err_rw_file_once: +err_rw_file_exact: errno = EIO; return -1; } @@ -1978,29 +1973,41 @@ err_rw_file_once: * 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. */ static ssize_t prw(int fd, void *mem, size_t nrw, off_t off, int rw_type, - int loop_eagain, int loop_eintr) + int loop_eagain, int loop_eintr, + int off_reset) { - off_t off_orig; - off_t off_last; ssize_t r; - int saved_errno; 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 (mem == NULL) - goto err_prw; - - if (fd < 0 - || off < 0 - || !nrw /* prevent zero read request */ - || nrw > (size_t)SSIZE_MAX /* prevent overflow */ - || (uint)rw_type > IO_PWRITE) - goto err_prw; + if (io_args(fd, mem, nrw, off, rw_type) == -1) + return -1; r = -1; @@ -2054,32 +2061,79 @@ real_pread_pwrite: goto real_pread_pwrite; #else if ((off_orig = lseek_loop(fd, (off_t)0, SEEK_CUR, - loop_eagain, loop_eintr)) == (off_t)-1) + loop_eagain, loop_eintr)) == (off_t)-1) { r = -1; - else if (lseek_loop(fd, off, SEEK_SET, - loop_eagain, loop_eintr) == (off_t)-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); - do { - if (rw_type == IO_PREAD) - r = read(fd, mem, nrw); - else if (rw_type == IO_PWRITE) - r = write(fd, mem, nrw); - - r = rw_over_nrw(r, nrw); - } while (r == -1 && - (errno == try_err(loop_eintr, EINTR) - || errno == try_err(loop_eagain, EAGAIN))); + /* + * 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))); + } saved_errno = errno; + off_last = lseek_loop(fd, off_orig, SEEK_SET, loop_eagain, loop_eintr); - if (off_last == (off_t)-1) { + + if (off_last != off_orig) { errno = saved_errno; - return -1; - } - if (off_last != off_orig) goto err_prw; + } + errno = saved_errno; return rw_over_nrw(r, nrw); @@ -2091,6 +2145,44 @@ err_prw: } static int +io_args(int fd, void *mem, size_t 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 > (size_t)SSIZE_MAX) + goto err_io_args; + + /* prevent overflow */ + if (((size_t)off + nrw) < (size_t)off) + goto err_io_args; + + if (rw_type > IO_PWRITE) + goto err_io_args; + + return 0; + +err_io_args: + errno = EIO; + return -1; +} + +static int check_file(int fd, struct stat *st) { if (fstat(fd, st) == -1) @@ -2112,9 +2204,17 @@ err_is_file: * POSIX can say whatever it wants. * specification != implementation */ -static int +static ssize_t rw_over_nrw(ssize_t r, size_t 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; @@ -2191,35 +2291,12 @@ try_err(int loop_err, int errval) return -1; } -static int -close_files(void) -{ - int close_err_gbe = 0; - int saved_errno = errno; - - if (gbe_fd > -1) { - if (close(gbe_fd) == -1) - close_err_gbe = errno; - gbe_fd = -1; - } - - if (saved_errno) - errno = saved_errno; - - if (close_err_gbe) - return -1; - - return 0; -} - static void err(int nvm_errval, const char *msg, ...) { va_list args; - if (errno <= 0) - errno = ECANCELED; - if (!errno) + if (errno == 0) errno = nvm_errval; (void)close_files(); @@ -2236,6 +2313,27 @@ err(int nvm_errval, const char *msg, ...) exit(EXIT_FAILURE); } +static int +close_files(void) +{ + int close_err_gbe = 0; + int saved_errno = errno; + + if (gbe_fd > -1) { + if (close(gbe_fd) == -1) + close_err_gbe = errno; + gbe_fd = -1; + } + + if (saved_errno) + errno = saved_errno; + + if (close_err_gbe) + return -1; + + return 0; +} + static const char * getnvmprogname(void) { |
