diff options
Diffstat (limited to 'util/nvmutil/nvmutil.c')
| -rw-r--r-- | util/nvmutil/nvmutil.c | 191 |
1 files changed, 149 insertions, 42 deletions
diff --git a/util/nvmutil/nvmutil.c b/util/nvmutil/nvmutil.c index b0719f99..d605ac79 100644 --- a/util/nvmutil/nvmutil.c +++ b/util/nvmutil/nvmutil.c @@ -16,6 +16,48 @@ */ /* + * I/O config (build-time) + * + * Regarding: + * Retries on zero-return. + * + * 10 retries is generous, + * but also conservative. + * This is enough for e.g. + * slow USB flash drives, + * busy NFS servers, etc. + * Any more is too much + * and not of much benefit. + * + * At least 3-5 recommended. + * Pass this at build time. + */ +#ifndef MAX_ZERO_RW_RETRY +#define MAX_ZERO_RW_RETRY 10 +#endif +/* + * 0: portable pread/pwrite + * 1: real pread/pwrite (thread-safe) + * Pass this at build-time + */ +#ifndef HAVE_REAL_PREAD_PWRITE +#define HAVE_REAL_PREAD_PWRITE 0 +#endif +/* + * Configure whether to wait on + * EINTR on files, or EAGAIN on + * cmd cat (stdout). + * + * Pass these at build time. + */ +#ifndef LOOP_EAGAIN +#define LOOP_EAGAIN 1 +#endif +#ifndef LOOP_EINTR +#define LOOP_EINTR 1 +#endif + +/* * Major TODO: split this into multiple files. * This program has become quite large now, mostly * due to all the extra sanity checks / portability. @@ -341,10 +383,11 @@ static off_t gbe_x_offset(size_t part, const char *f_op, 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); + 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); + int loop_eintr, size_t max_retries); static ssize_t prw(int fd, void *mem, size_t nrw, off_t off, int rw_type, int loop_eagain, int loop_eintr); static int rw_over_nrw(ssize_t r, size_t nrw); @@ -444,6 +487,9 @@ static const char *argv0; #define ARGC_3 3 #define ARGC_4 4 +#define NO_LOOP_EAGAIN 0 +#define NO_LOOP_EINTR 0 + enum { IO_READ, IO_WRITE, @@ -603,6 +649,10 @@ typedef char bool_skip_checksum_write[(SKIP_CHECKSUM_WRITE==0)?1:-1]; typedef char bool_checksum_write[(CHECKSUM_WRITE==1)?1:-1]; typedef char bool_no_invert[(NO_INVERT==0)?1:-1]; typedef char bool_part_invert[(PART_INVERT==1)?1:-1]; +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]; static int use_prng = 0; @@ -899,10 +949,24 @@ static void open_gbe_file(void) { struct stat gbe_st; + int flags; xopen(&gbe_fd, fname, command[cmd_index].flags | O_BINARY | O_NOFOLLOW, &gbe_st); + flags = fcntl(gbe_fd, F_GETFL); + if (flags == -1) + err(errno, "%s: fcntl(F_GETFL)", fname); + + /* + * O_APPEND must not be used, because this + * allows POSIX write() to ignore the + * current write offset and write at EOF, + * which would therefore break pread/pwrite + */ + if (flags & O_APPEND) + err(EIO, "%s: O_APPEND flag"); + gbe_file_size = gbe_st.st_size; switch (gbe_file_size) { @@ -1192,7 +1256,8 @@ rhex(void) if (!n) { n = sizeof(rnum); - if (rw_file_exact(urandom_fd, rnum, n, 0, IO_READ, 0, 1) == -1) + if (rw_file_exact(urandom_fd, rnum, n, 0, IO_READ, + NO_LOOP_EAGAIN, LOOP_EINTR, MAX_ZERO_RW_RETRY) == -1) err(errno, "Randomisation failed"); } @@ -1361,7 +1426,8 @@ static void gbe_cat_buf(u8 *b) { if (rw_file_exact(STDOUT_FILENO, b, - GBE_PART_SIZE, 0, IO_WRITE, 1, 1) < 0) + GBE_PART_SIZE, 0, IO_WRITE, LOOP_EAGAIN, LOOP_EINTR, + MAX_ZERO_RW_RETRY) < 0) err(errno, "stdout: cat"); } @@ -1721,7 +1787,8 @@ rw_gbe_file_exact(int fd, u8 *mem, size_t nrw, if (nrw > GBE_PART_SIZE) goto err_rw_gbe_file_exact; - return rw_file_exact(fd, mem, nrw, off, rw_type, 0, 1); + return rw_file_exact(fd, mem, nrw, off, rw_type, + NO_LOOP_EAGAIN, LOOP_EINTR, MAX_ZERO_RW_RETRY); err_rw_gbe_file_exact: errno = EIO; @@ -1729,33 +1796,47 @@ err_rw_gbe_file_exact: } /* - * Read or write the exact contents of a file, - * along with a buffer, (if applicable) offset, - * and number of bytes to be read. It unifies - * the functionality of read(), pread(), write() - * and pwrite(), with retry-on-EINTR and also - * prevents infinite loop on zero-reads. + * 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 * - * The pread() and pwrite() functionality are - * provided by yet another portable function, - * prw() - see notes below. + * Read/write, and absolutely insist on an + * absolute read; e.g. if 100 bytes are + * requested, this MUST return 100. * - * This must only be used on files. It cannot - * be used on sockets or pipes, because 0-byte - * reads are treated like fatal errors. This - * means that EOF is also considered fatal. + * 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 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. */ 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) + int loop_eintr, size_t max_retries) { ssize_t rv; size_t rc; for (rc = 0, rv = 0; rc < nrw; ) { if ((rv = rw_file_once(fd, mem, nrw, off, rw_type, rc, - loop_eagain, loop_eintr)) < 0) + loop_eagain, loop_eintr, max_retries)) < 0) return -1; /* rw_file_once never returns @@ -1774,26 +1855,28 @@ rw_file_exact(int fd, u8 *mem, size_t nrw, } /* - * Helper function for rw_file_exact, that - * also does extra error handling pertaining - * to GbE file offsets. + * rw_file_once() - Read less than perfectly + * (and possibly die) * - * May not return all requested bytes (nrw). - * Use rw_file_exact for guaranteed length. + * 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) + int loop_eagain, int loop_eintr, + size_t max_retries) { ssize_t rv; size_t retries_on_zero = 0; - size_t max_retries = 10; if (mem == NULL) goto err_rw_file_once; @@ -1830,6 +1913,9 @@ err_rw_file_once: * 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 @@ -1852,7 +1938,6 @@ prw(int fd, void *mem, size_t nrw, off_t off_orig; ssize_t r; int saved_errno; - int flags; int positional_rw; if (mem == NULL) @@ -1875,10 +1960,21 @@ prw(int fd, void *mem, size_t nrw, 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))) @@ -1887,19 +1983,10 @@ try_rw_again: return rw_over_nrw(r, nrw); } - flags = fcntl(fd, F_GETFL); - if (flags == -1) - return -1; - - /* - * O_APPEND must not be used, because this - * allows POSIX write() to ignore the - * current write offset and write at EOF, - * which would therefore break pread/pwrite - */ - if (flags & O_APPEND) - goto err_prw; - +#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; @@ -1927,6 +2014,7 @@ try_rw_again: errno = saved_errno; return rw_over_nrw(r, nrw); +#endif err_prw: errno = EIO; @@ -1934,6 +2022,8 @@ err_prw: } /* + * Check overflows caused by buggy libc. + * * POSIX can say whatever it wants. * specification != implementation */ @@ -1975,6 +2065,13 @@ err_rw_over_nrw: 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. + */ static off_t lseek_loop(int fd, off_t off, int whence, int loop_eagain, int loop_eintr) @@ -1989,7 +2086,14 @@ lseek_loop(int fd, off_t off, int whence, 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 + */ static int try_err(int loop_err, int errval) { @@ -2024,7 +2128,10 @@ close_files(void) if (saved_errno) errno = saved_errno; - return -(close_err_gbe | close_err_rand); + if (close_err_gbe || close_err_rand) + return -1; + + return 0; } static void |
