diff options
Diffstat (limited to 'util')
| -rw-r--r-- | util/nvmutil/nvmutil.c | 687 |
1 files changed, 486 insertions, 201 deletions
diff --git a/util/nvmutil/nvmutil.c b/util/nvmutil/nvmutil.c index b1d24bdc..493a88e3 100644 --- a/util/nvmutil/nvmutil.c +++ b/util/nvmutil/nvmutil.c @@ -163,17 +163,23 @@ also consider: #include <time.h> #include <unistd.h> -typedef unsigned char u8; +typedef unsigned char u8; typedef unsigned short ushort; -typedef unsigned int uint; +typedef unsigned int uint; +typedef unsigned long ulong; /* type asserts */ typedef char static_assert_char_is_8_bits[(CHAR_BIT == 8) ? 1 : -1]; typedef char static_assert_char_is_1[(sizeof(char) == 1) ? 1 : -1]; -typedef char static_assert_uint8_is_1[(sizeof(u8) == 1) ? 1 : -1]; -typedef char static_assert_uint16_is_2[(sizeof(ushort) >= 2) ? 1 : -1]; +typedef char static_assert_u8_is_1[ + (sizeof(u8) == 1) ? 1 : -1]; +typedef char static_assert_ushort_is_2[ + (sizeof(ushort) >= 2) ? 1 : -1]; typedef char static_assert_short_is_2[(sizeof(short) >= 2) ? 1 : -1]; -typedef char static_assert_uint32_is_4[(sizeof(uint) >= 4) ? 1 : -1]; +typedef char static_assert_uint_is_4[ + (sizeof(uint) >= 4) ? 1 : -1]; +typedef char static_assert_ulong_is_4[ + (sizeof(ulong) >= 4) ? 1 : -1]; typedef char static_assert_int_ge_32[(sizeof(int) >= 4) ? 1 : -1]; typedef char static_assert_twos_complement[ ((-1 & 3) == 3) ? 1 : -1 @@ -183,6 +189,9 @@ typedef char static_assert_twos_complement[ * We set _FILE_OFFSET_BITS 64, but we only handle * files that are 128KB in size at a maximum, so we * realistically only need 32-bit at a minimum. + * + * We set 64 anyway, because there's no reason not + * to, but some systems may ignore _FILE_OFFSET_BITS */ typedef char static_assert_off_t_is_32[(sizeof(off_t) >= 4) ? 1 : -1]; @@ -278,7 +287,7 @@ static void set_mac_nib(size_t mac_str_pos, static ushort hextonum(char ch_s); static ushort rhex(void); static ushort fallback_rand(void); -static unsigned long entropy_jitter(void); +static ulong entropy_jitter(void); static void write_mac_part(size_t partnum); /* @@ -323,29 +332,33 @@ static void check_bin(size_t a, const char *a_name); */ static void rw_gbe_file_part(size_t p, int rw_type, const char *rw_type_str); +static void check_written_part(size_t p); +static void report_io_err_rw(void); static u8 *gbe_mem_offset(size_t part, const char *f_op); static off_t gbe_file_offset(size_t part, const char *f_op); static off_t gbe_x_offset(size_t part, const char *f_op, const char *d_type, off_t nsize, off_t ncmp); -static ssize_t rw_file_exact(int fd, u8 *mem, size_t len, +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); static ssize_t rw_file_once(int fd, u8 *mem, size_t len, - off_t off, int rw_type, size_t rc); -static ssize_t do_rw(int fd, - u8 *mem, size_t len, off_t off, int rw_type); + off_t off, int rw_type, size_t rc, int loop_eagain, + int loop_eintr); static ssize_t prw(int fd, void *mem, size_t nrw, - off_t off, int rw_type); -static off_t lseek_eintr(int fd, off_t off, int whence); -static int io_args(int fd, void *mem, size_t nrw, - off_t off, int rw_type); + off_t off, int rw_type, int loop_eagain, int loop_eintr); +static int rw_over_nrw(ssize_t r, size_t nrw); +static off_t lseek_loop(int fd, off_t off, + int whence, int loop_eagain, int loop_eintr); +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 void close_files(void); static const char *getnvmprogname(void); -static void usage(u8 usage_exit); +static void usage(int usage_exit); /* * Sizes in bytes: @@ -396,8 +409,9 @@ static const char *rname = NULL; * * The code will handle this properly. */ -static u8 buf[GBE_FILE_SIZE]; -static u8 pad[GBE_PART_SIZE]; /* the file that wouldn't die */ +static u8 real_buf[GBE_FILE_SIZE]; +static u8 pad[GBE_FILE_SIZE]; /* the file that wouldn't die */ +static u8 *buf = real_buf; static ushort mac_buf[3]; static off_t gbe_file_size; @@ -430,6 +444,11 @@ static const char *argv0; #define ARGC_3 3 #define ARGC_4 4 +#define NO_LOOP_EAGAIN 0 +#define LOOP_EAGAIN 1 +#define NO_LOOP_EINTR 0 +#define LOOP_EINTR 1 + enum { IO_READ, IO_WRITE, @@ -589,9 +608,20 @@ 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)?1:-1]; +typedef char bool_loop_eagain[(LOOP_EAGAIN==1)?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; +static int io_err_gbe = 0; +static int rw_check_err_read[] = {0, 0}; +static int rw_check_partial_read[] = {0, 0}; +static int rw_check_bad_part[] = {0, 0}; + +static int post_rw_checksum[] = {0, 0}; + int main(int argc, char *argv[]) { @@ -663,10 +693,29 @@ main(int argc, char *argv[]) run_cmd(cmd_index); - if (command[cmd_index].flags == O_RDWR) + if (command[cmd_index].flags == O_RDWR) { + write_gbe_file(); - close_files(); + /* + * We may otherwise read from + * cache, so we must sync. + */ + if (fsync(gbe_fd) == -1) + err(errno, "%s: fsync (pre-verification)", + fname); + + check_written_part(0); + check_written_part(1); + + report_io_err_rw(); + + if (io_err_gbe) + err(EIO, "%s: bad write", fname); + } + + if (close_files() == -1) + err(EIO, "%s: close", fname); return EXIT_SUCCESS; } @@ -696,19 +745,19 @@ sanitize_command_index(size_t c) if (command[c].argc < 3) err(EINVAL, "cmd index %lu: argc below 3, %d", - (unsigned long)c, command[c].argc); + (ulong)c, command[c].argc); if (command[c].str == NULL) err(EINVAL, "cmd index %lu: NULL str", - (unsigned long)c); + (ulong)c); if (*command[c].str == '\0') err(EINVAL, "cmd index %lu: empty str", - (unsigned long)c); + (ulong)c); if (xstrxlen(command[c].str, MAX_CMD_LEN + 1) > MAX_CMD_LEN) { err(EINVAL, "cmd index %lu: str too long: %s", - (unsigned long)c, command[c].str); + (ulong)c, command[c].str); } mod_type = command[c].set_modified; @@ -736,12 +785,12 @@ sanitize_command_index(size_t c) break; default: err(EINVAL, "Unsupported rw_size: %lu", - (unsigned long)gbe_rw_size); + (ulong)gbe_rw_size); } if (gbe_rw_size > GBE_PART_SIZE) err(EINVAL, "rw_size larger than GbE part: %lu", - (unsigned long)gbe_rw_size); + (ulong)gbe_rw_size); if (command[c].flags != O_RDONLY && command[c].flags != O_RDWR) @@ -794,13 +843,13 @@ set_cmd_args(int argc, char *argv[]) static size_t conv_argv_part_num(const char *part_str) { - unsigned char ch; + u8 ch; if (part_str[0] == '\0' || part_str[1] != '\0') err(EINVAL, "Partnum string '%s' wrong length", part_str); /* char signedness is implementation-defined */ - ch = (unsigned char)part_str[0]; + ch = (u8)part_str[0]; if (ch < '0' || ch > '1') err(EINVAL, "Bad part number (%c)", ch); @@ -824,7 +873,7 @@ xstrxcmp(const char *a, const char *b, size_t maxlen) for (i = 0; i < maxlen; i++) { if (a[i] != b[i]) - return (unsigned char)a[i] - (unsigned char)b[i]; + return (u8)a[i] - (u8)b[i]; if (a[i] == '\0') return 0; @@ -852,7 +901,7 @@ open_dev_urandom(void) /* fallback on VERY VERY VERY old unix */ use_prng = 1; - srand((unsigned)(time(NULL) ^ getpid())); + srand((uint)(time(NULL) ^ getpid())); } static void @@ -970,7 +1019,7 @@ read_checksums(void) if (num_invalid >= max_invalid) { if (max_invalid == 1) err(ECANCELED, "%s: part %lu has a bad checksum", - fname, (unsigned long)part); + fname, (ulong)part); err(ECANCELED, "%s: No valid checksum found in file", fname); } @@ -1001,7 +1050,7 @@ check_command_num(size_t c) { if (!valid_command(c)) err(EINVAL, "Invalid run_cmd arg: %lu", - (unsigned long)c); + (ulong)c); } static u8 @@ -1012,7 +1061,7 @@ valid_command(size_t c) if (c != command[c].chk) err(EINVAL, "Invalid cmd chk value (%lu) vs arg: %lu", - (unsigned long)command[c].chk, (unsigned long)c); + (ulong)command[c].chk, (ulong)c); return 1; } @@ -1126,14 +1175,14 @@ set_mac_nib(size_t mac_str_pos, static ushort hextonum(char ch_s) { - unsigned char ch = (unsigned char)ch_s; + u8 ch = (u8)ch_s; - if ((unsigned)(ch - '0') <= 9) + if ((uint)(ch - '0') <= 9) return ch - '0'; ch |= 0x20; - if ((unsigned)(ch - 'a') <= 5) + if ((uint)(ch - 'a') <= 5) return ch - 'a' + 10; if (ch == '?' || ch == 'x') @@ -1152,7 +1201,8 @@ rhex(void) if (!n) { n = sizeof(rnum); - if (rw_file_exact(urandom_fd, rnum, n, 0, IO_READ) == -1) + if (rw_file_exact(urandom_fd, rnum, n, 0, IO_READ, + NO_LOOP_EAGAIN, LOOP_EINTR) == -1) err(errno, "Randomisation failed"); } @@ -1163,15 +1213,15 @@ static ushort fallback_rand(void) { struct timeval tv; - unsigned long mix; - static unsigned long counter = 0; + ulong mix; + static ulong counter = 0; gettimeofday(&tv, NULL); - mix = (unsigned long)tv.tv_sec - ^ (unsigned long)tv.tv_usec - ^ (unsigned long)getpid() - ^ (unsigned long)&mix + mix = (ulong)tv.tv_sec + ^ (ulong)tv.tv_usec + ^ (ulong)getpid() + ^ (ulong)&mix ^ counter++ ^ entropy_jitter(); @@ -1179,18 +1229,18 @@ fallback_rand(void) * Stack addresses can vary between * calls, thus increasing entropy. */ - mix ^= (unsigned long)&mix; - mix ^= (unsigned long)&tv; - mix ^= (unsigned long)&counter; + mix ^= (ulong)&mix; + mix ^= (ulong)&tv; + mix ^= (ulong)&counter; return (ushort)(mix & 0xf); } -static unsigned long +static ulong entropy_jitter(void) { struct timeval a, b; - unsigned long mix = 0; + ulong mix = 0; long mix_diff; int i; @@ -1207,8 +1257,8 @@ entropy_jitter(void) if (mix_diff < 0) mix_diff = -mix_diff; - mix ^= (unsigned long)(mix_diff); - mix ^= (unsigned long)&mix; + mix ^= (ulong)(mix_diff); + mix ^= (ulong)&mix; } return mix; @@ -1227,7 +1277,7 @@ write_mac_part(size_t partnum) set_nvm_word(w, partnum, mac_buf[w]); printf("Wrote MAC address to part %lu: ", - (unsigned long)partnum); + (ulong)partnum); print_mac_from_nvm(partnum); } @@ -1244,11 +1294,11 @@ cmd_helper_dump(void) fprintf(stderr, "BAD checksum %04x in part %lu (expected %04x)\n", nvm_word(NVM_CHECKSUM_WORD, partnum), - (unsigned long)partnum, + (ulong)partnum, calculated_checksum(partnum)); printf("MAC (part %lu): ", - (unsigned long)partnum); + (ulong)partnum); print_mac_from_nvm(partnum); hexdump(partnum); } @@ -1263,8 +1313,8 @@ print_mac_from_nvm(size_t partnum) for (c = 0; c < 3; c++) { val16 = nvm_word(c, partnum); printf("%02x:%02x", - (unsigned int)(val16 & 0xff), - (unsigned int)(val16 >> 8)); + (uint)(val16 & 0xff), + (uint)(val16 >> 8)); if (c == 2) printf("\n"); else @@ -1280,14 +1330,14 @@ hexdump(size_t partnum) ushort val16; for (row = 0; row < 8; row++) { - printf("%08lx ", (unsigned long)((size_t)row << 4)); + printf("%08lx ", (ulong)((size_t)row << 4)); for (c = 0; c < 8; c++) { val16 = nvm_word((row << 3) + c, partnum); if (c == 4) printf(" "); printf(" %02x %02x", - (unsigned int)(val16 & 0xff), - (unsigned int)(val16 >> 8)); + (uint)(val16 & 0xff), + (uint)(val16 >> 8)); } printf("\n"); } @@ -1320,26 +1370,9 @@ cmd_helper_cat(void) static void gbe_cat_buf(u8 *b) { - ssize_t rval; - - while (1) { - rval = rw_file_exact(STDOUT_FILENO, b, - GBE_PART_SIZE, 0, IO_WRITE); - - if (rval >= 0) { - /* - * A partial write is especially - * fatal, as it should already be - * prevented in rw_file_exact(). - */ - if ((size_t)rval != GBE_PART_SIZE) - err(EIO, "stdout: cat: Partial write"); - break; - } - - if (errno != EAGAIN) - err(errno, "stdout: cat"); - } + if (rw_file_exact(STDOUT_FILENO, b, + GBE_PART_SIZE, 0, IO_WRITE, LOOP_EAGAIN, LOOP_EINTR) < 0) + err(errno, "stdout: cat"); } static void @@ -1480,7 +1513,7 @@ check_nvm_bound(size_t c, size_t p) if (c >= NVM_WORDS) err(ECANCELED, "check_nvm_bound: out of bounds %lu", - (unsigned long)c); + (ulong)c); } static void @@ -1488,21 +1521,23 @@ check_bin(size_t a, const char *a_name) { if (a > 1) err(EINVAL, "%s must be 0 or 1, but is %lu", - a_name, (unsigned long)a); + a_name, (ulong)a); } static void rw_gbe_file_part(size_t p, int rw_type, const char *rw_type_str) { + ssize_t r; size_t gbe_rw_size = command[cmd_index].rw_size; u8 invert = command[cmd_index].invert; u8 *mem_offset; + off_t file_offset; if (rw_type < IO_PREAD || rw_type > IO_PWRITE) err(errno, "%s: %s: part %lu: invalid rw_type, %d", - fname, rw_type_str, (unsigned long)p, rw_type); + fname, rw_type_str, (ulong)p, rw_type); if (rw_type == IO_PWRITE) invert = 0; @@ -1512,12 +1547,116 @@ rw_gbe_file_part(size_t p, int rw_type, * E.g. read from p0 (file) to p1 (mem). */ mem_offset = gbe_mem_offset(p ^ invert, rw_type_str); + file_offset = (off_t)gbe_file_offset(p, rw_type_str); + + r = rw_gbe_file_exact(gbe_fd, mem_offset, + gbe_rw_size, file_offset, rw_type); - if (rw_file_exact(gbe_fd, mem_offset, - gbe_rw_size, gbe_file_offset(p, rw_type_str), - rw_type) == -1) + if (r == -1) err(errno, "%s: %s: part %lu", - fname, rw_type_str, (unsigned long)p); + fname, rw_type_str, (ulong)p); + + if ((size_t)r != gbe_rw_size) + err(EIO, "%s: partial %s: part %lu", + fname, rw_type_str, (ulong)p); +} + +static void +check_written_part(size_t p) +{ + ssize_t r; + size_t gbe_rw_size; + u8 *mem_offset; + off_t file_offset; + u8 *buf_restore; + + if (!part_modified[p]) + return; + + gbe_rw_size = command[cmd_index].rw_size; + + /* invert not needed for pwrite */ + mem_offset = gbe_mem_offset(p, "pwrite"); + file_offset = (off_t)gbe_file_offset(p, "pwrite"); + + memset(pad, 0xff, sizeof(pad)); + + r = rw_gbe_file_exact(gbe_fd, pad, + gbe_rw_size, file_offset, IO_PREAD); + + if (r == -1) + rw_check_err_read[p] = io_err_gbe = 1; + else if ((size_t)r != gbe_rw_size) + rw_check_partial_read[p] = io_err_gbe = 1; + else if (memcmp(mem_offset, pad, gbe_rw_size) != 0) + rw_check_bad_part[p] = io_err_gbe = 1; + + if (rw_check_err_read[p] || + rw_check_partial_read[p]) + return; + + /* + * We only load one part on-file, into memory but + * always at offset zero, for post-write checks. + * That's why we hardcode good_checksum(0). + */ + buf_restore = buf; + buf = pad; + post_rw_checksum[p] = good_checksum(0); + buf = buf_restore; +} + +static void +report_io_err_rw(void) +{ + size_t p; + + if (!io_err_gbe) + return; + + for (p = 0; p < 2; p++) { + if (!part_modified[p]) + continue; + + if (rw_check_err_read[p]) + fprintf(stderr, + "%s: pread: p%lu (post-verification)\n", + fname, (ulong)p); + if (rw_check_partial_read[p]) + fprintf(stderr, + "%s: partial pread: p%lu (post-verification)\n", + fname, (ulong)p); + if (rw_check_bad_part[p]) + fprintf(stderr, + "%s: pwrite: corrupt write on p%lu\n", + fname, (ulong)p); + + if (rw_check_err_read[p] || + rw_check_partial_read[p]) { + fprintf(stderr, + "%s: p%lu: skipped checksum verification " + "(because read failed)\n", + fname, (ulong)p); + + continue; + } + + fprintf(stderr, "%s: ", fname); + + if (post_rw_checksum[p]) + fprintf(stderr, "GOOD"); + else + fprintf(stderr, "BAD"); + + fprintf(stderr, " checksum in p%lu on-disk.\n", + (ulong)p); + + if (post_rw_checksum[p]) { + fprintf(stderr, + " This does NOT mean it's safe. it may be\n" + " salvageable if you use the cat feature.\n"); + } + } } /* @@ -1571,38 +1710,87 @@ gbe_x_offset(size_t p, const char *f_op, const char *d_type, return off; } +static ssize_t +rw_gbe_file_exact(int fd, u8 *mem, size_t nrw, + off_t off, int rw_type) +{ + if (mem == NULL) + goto err_rw_gbe_file_exact; + + if (mem != (void *)pad + && mem != (void *)rnum + && (mem < buf || mem >= (buf + GBE_FILE_SIZE))) + goto err_rw_gbe_file_exact; + + if (off < 0 || off >= gbe_file_size) + goto err_rw_gbe_file_exact; + + if (nrw > (size_t)(gbe_file_size - off)) + goto err_rw_gbe_file_exact; + + if (nrw > GBE_PART_SIZE) + goto err_rw_gbe_file_exact; + + return rw_file_exact(fd, mem, nrw, off, rw_type, + NO_LOOP_EAGAIN, LOOP_EINTR); + +err_rw_gbe_file_exact: + errno = EIO; + return -1; +} + /* - * 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. * - * The pread() and pwrite() functionality are - * provided by yet another portable function, - * prw() - see notes below. + * 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 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) + off_t off, int rw_type, int loop_eagain, + int loop_eintr) { ssize_t rv; size_t rc; - if (io_args(fd, mem, nrw, off, rw_type) == -1) { - errno = EIO; - return -1; - } - for (rc = 0, rv = 0; rc < nrw; ) { - if ((rv = rw_file_once(fd, mem, nrw, off, rw_type, rc)) <= 0) + if ((rv = rw_file_once(fd, mem, nrw, off, rw_type, rc, + loop_eagain, loop_eintr)) < 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; + } rc += (size_t)rv; } @@ -1611,31 +1799,52 @@ rw_file_exact(int fd, u8 *mem, size_t nrw, } /* - * May not return all requested bytes (len). - * Use rw_file_exact for guaranteed length. + * 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) + off_t off, int rw_type, size_t rc, + int loop_eagain, int loop_eintr) { ssize_t rv; size_t retries_on_zero = 0; + + /* + * 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. + */ size_t max_retries = 10; - if (io_args(fd, mem, nrw, off, rw_type) == -1) + if (mem == NULL) goto err_rw_file_once; read_again: - rv = do_rw(fd, mem + rc, nrw - rc, off + rc, rw_type); - - if (rv < 0 && errno == EINTR) - goto read_again; + rv = prw(fd, mem + rc, nrw - rc, off + rc, rw_type, + loop_eagain, loop_eintr); if (rv < 0) return -1; - if ((size_t)rv > SSIZE_MAX /* theoretical buggy libc */ - || (size_t)rv > (nrw - rc))/* don't overflow */ + if ((size_t)rv > (nrw - rc))/* don't overflow */ goto err_rw_file_once; if (rv != 0) @@ -1649,28 +1858,9 @@ err_rw_file_once: return -1; } -static ssize_t -do_rw(int fd, u8 *mem, - size_t nrw, off_t off, int rw_type) -{ - if (io_args(fd, mem, nrw, off, rw_type) == -1) - goto err_do_rw; - - if (rw_type == IO_READ) - return read(fd, mem, nrw); - - if (rw_type == IO_WRITE) - return write(fd, mem, nrw); - - if (rw_type == IO_PREAD || rw_type == IO_PWRITE) - return prw(fd, mem, nrw, off, rw_type); - -err_do_rw: - 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 @@ -1678,25 +1868,65 @@ err_do_rw: * * This limitation is acceptable, since nvmutil is * single-threaded. Portability is the main goal. + * If you need real pwrite/pread, just edit prw() + * + * 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. */ + static ssize_t prw(int fd, void *mem, size_t nrw, - off_t off, int rw_type) + off_t off, int rw_type, + int loop_eagain, int loop_eintr) { off_t off_orig; ssize_t r; int saved_errno; - int prw_type; int flags; + int positional_rw; - if (io_args(fd, mem, nrw, off, rw_type) == -1) + if (mem == NULL) goto err_prw; - prw_type = rw_type ^ IO_PREAD; - - if ((unsigned int)prw_type > IO_WRITE) + 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; + r = -1; + + if (rw_type >= IO_PREAD) + positional_rw = 1; /* pread/pwrite */ + else + positional_rw = 0; /* read/write */ + +try_rw_again: + + if (!positional_rw) { + if (rw_type == IO_WRITE) + r = write(fd, mem, nrw); + else if (rw_type == IO_READ) + r = read(fd, mem, nrw); + + 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); + } + flags = fcntl(fd, F_GETFL); if (flags == -1) return -1; @@ -1710,89 +1940,160 @@ prw(int fd, void *mem, size_t nrw, if (flags & O_APPEND) goto err_prw; - if ((off_orig = lseek_eintr(fd, (off_t)0, SEEK_CUR)) == (off_t)-1) - return -1; - - if (lseek_eintr(fd, off, SEEK_SET) == (off_t)-1) - return -1; + 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; do { - r = do_rw(fd, mem, nrw, off, prw_type); - } while (r < 0 && errno == EINTR); + if (rw_type == IO_PREAD) + r = read(fd, mem, nrw); + else if (rw_type == IO_PWRITE) + r = write(fd, mem, nrw); - saved_errno = errno; - if (lseek_eintr(fd, off_orig, SEEK_SET) == (off_t)-1) { - if (r < 0) - errno = saved_errno; + r = rw_over_nrw(r, nrw); + } while (r == -1 && + (errno == try_err(loop_eintr, EINTR) + || errno == try_err(loop_eagain, EAGAIN))); + saved_errno = errno; + if (lseek_loop(fd, off_orig, SEEK_SET, + loop_eagain, loop_eintr) == (off_t)-1) { + errno = saved_errno; return -1; } errno = saved_errno; - return r; + return rw_over_nrw(r, nrw); err_prw: errno = EIO; return -1; } +/* + * Check overflows caused by buggy libc. + * + * POSIX can say whatever it wants. + * specification != implementation + */ static int -io_args(int fd, void *mem, size_t nrw, - off_t off, int rw_type) +rw_over_nrw(ssize_t r, size_t nrw) { - if (mem == NULL) - goto err_io_args; - - if (mem != (void *)pad - && mem != (void *)rnum - && (mem < (void *)buf || mem >= (void *)(buf + GBE_FILE_SIZE))) - goto err_io_args; + if (r == -1) + return r; - if (off < 0 || off >= gbe_file_size) - goto err_io_args; - - if (nrw > (size_t)(gbe_file_size - off)) - goto err_io_args; + if ((size_t)r > SSIZE_MAX) { + /* + * Theoretical buggy libc + * check. Extremely academic. + * + * Specifications never + * allow this return value + * to exceed SSIZE_MAX, but + * spec != implementation + * + * Check this after using + * [p]read() or [p]write() + */ + goto err_rw_over_nrw; + } - if (nrw > GBE_PART_SIZE) - goto err_io_args; + /* + * Theoretical buggy libc: + * Should never return a number of + * bytes above the requested length. + */ + if ((size_t)r > nrw) + goto err_rw_over_nrw; - if (fd < 0 - || !nrw /* prevent zero read request */ - || nrw > (size_t)SSIZE_MAX /* prevent overflow */ - || (unsigned int)rw_type > IO_PWRITE) - goto err_io_args; + return r; - return 0; +err_rw_over_nrw: -err_io_args: errno = EIO; return -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_eintr(int fd, off_t off, int whence) +lseek_loop(int fd, off_t off, int whence, + int loop_eagain, int loop_eintr) { - off_t old; + off_t old = -1; do { old = lseek(fd, off, whence); - } while (old == (off_t)-1 && errno == EINTR); + } while (old == (off_t)-1 && ( + errno == try_err(loop_eintr, EINTR) || + errno == try_err(loop_eagain, EAGAIN))); return old; } +/* + * 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) +{ + if (loop_err) + return errval; + + /* errno is never negative, + so functions checking it + can use it accordingly */ + return -1; +} + +static int +close_files(void) +{ + int close_err_gbe = 0; + int close_err_rand = 0; + int saved_errno = errno; + + if (gbe_fd > -1) { + if (close(gbe_fd) == -1) + close_err_gbe = errno; + gbe_fd = -1; + } + + if (urandom_fd > -1) { + if (close(urandom_fd) == -1) + close_err_rand = errno; + urandom_fd = -1; + } + + if (saved_errno) + errno = saved_errno; + + if (close_err_gbe || close_err_rand) + return -1; + + return 0; +} + static void err(int nvm_errval, const char *msg, ...) { va_list args; - if (nvm_errval >= 0) { - close_files(); - errno = nvm_errval; - } if (errno <= 0) errno = ECANCELED; + if (!errno) + errno = nvm_errval; + + (void)close_files(); fprintf(stderr, "%s: ", getnvmprogname()); @@ -1806,22 +2107,6 @@ err(int nvm_errval, const char *msg, ...) exit(EXIT_FAILURE); } -static void -close_files(void) -{ - if (gbe_fd > -1) { - if (close(gbe_fd) == -1) - err(-1, "%s: close failed", fname); - gbe_fd = -1; - } - - if (urandom_fd > -1) { - if (close(urandom_fd) == -1) - err(-1, "%s: close failed", rname); - urandom_fd = -1; - } -} - static const char * getnvmprogname(void) { @@ -1839,7 +2124,7 @@ getnvmprogname(void) } static void -usage(u8 usage_exit) +usage(int usage_exit) { const char *util = getnvmprogname(); |
