From 2e55bedc45e836899a18ea7f4a488f50597afad5 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Sat, 21 Sep 2024 19:31:16 +0200 Subject: misc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- Makefile | 4 +- README | 17 +++- TODO | 2 +- ask.c | 115 +++++++++++++++++++++ common.h | 30 +++++- deadshred.1 | 44 +++++++- deadshred.c | 315 ++++++++++++++++++++++++++++++++-------------------------- fmt.c | 16 +++ io.c | 127 ++++++++++++++++++++++++ map.c | 325 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sig.c | 3 + 11 files changed, 841 insertions(+), 157 deletions(-) create mode 100644 ask.c create mode 100644 map.c diff --git a/Makefile b/Makefile index 0e2dff4..07072f1 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,9 @@ OBJ =\ text.o\ avg.o\ rnd.o\ - sig.o + sig.o\ + ask.o\ + map.o HDR =\ common.h diff --git a/README b/README index 54ace7b..c3706b1 100644 --- a/README +++ b/README @@ -3,7 +3,7 @@ NAME SYNOPSIS deadshred [-b blocksize] [-o offset] [-l length | -e postend] - [-r] device [< random-source] + [-rY] device [map-file] [< random-source] DESCRIPTION The deadshred utility fills a file or block devices with @@ -36,13 +36,24 @@ OPTIONS Start writing from the end instead of from the beginning on the first pass over the device. + -Y + Do not ask for confirmation. + OPERANDS - The following operand is supported: + The following operands are supported: - file + device The file to override. Must be either a regular file or a block device. + map-file + If the file map-file exists and is non-empty, it + specifies what sections in the file to overwrite; + this file will be periodically, and upon exit, + updated to remove parts that has been successfully + overwritten. This file will be unlinked when it + becomes empty. + STDIN Unless the standard input is a terminal device, it shall be an unless source of either random data or a particular byte to diff --git a/TODO b/TODO index 6d59925..a852aa3 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,3 @@ -Add shred map operand +Test shred map support Test direction alternation Verify that aborting does not cause sections to be lost in the shred map diff --git a/ask.c b/ask.c new file mode 100644 index 0000000..bb8fa50 --- /dev/null +++ b/ask.c @@ -0,0 +1,115 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#if defined(__linux__) +# include +#endif + + +int +confirm(const char *device, off_t off, off_t len, off_t end) +{ + char buf[1024]; + int ttyfd = -1; + int need_close; + int ret = 1; + int custom_section = (off >= 0 || len >= 0 || end >= 0); + const char *type; + ssize_t i, r; + int confirm_state = 0; + struct stat st; + + if (isatty(STDIN_FILENO)) { + ttyfd = STDIN_FILENO; + need_close = 0; + } else { + ttyfd = open("/dev/tty", O_RDWR); + if (ttyfd < 0) + return 1; + need_close = 1; + } + + if (getpgrp() != tcgetpgrp(ttyfd)) + goto out; + + if (custom_section) { + if (off < 0) + off = 0; + if (end >= 0) + len = end - off; + else if (len >= 0) + end = off + len; + else if (off == 0) + custom_section = 0; + if (end >= 0 && end < off) + eprintf("the value of -e option is lower than value of -o option"); + } + + if (stat(device, &st)) + eprintf("stat %s:", device); + switch (st.st_mode & S_IFMT) { + case S_IFREG: + type = "file"; + break; + case S_IFBLK: + type = "device"; + break; + default: + eprintf("%s: not a regular file or block device", device); + } + + if (dprintf(ttyfd, "Are you sure you want to erase the contents of %s?\n", device) < 0) + goto printerror; + + if (S_ISREG(st.st_mode)) { + if (dprintf(ttyfd, "\nThe file is %s large.\n", exact_and_human_size(st.st_size, buf, 1)) < 0) + goto printerror; +#if defined(__linux__) + } else if (S_ISBLK(st.st_mode)) { + char *devname = get_device_name(major(st.st_rdev), minor(st.st_rdev)); + if (devname) { + /* TODO display device identity and size (look in /sys/class/block//) */ + } +#endif + } + + if (custom_section) { + if (dprintf(ttyfd, "\nYou have requested to only erase part of the %s:\n", type) < 0 || + (off >= 0 && dprintf(ttyfd, "\tFirst byte: %s\n", exact_and_human_size(off, buf, 0)) < 0) || + (len >= 0 && dprintf(ttyfd, "\tNumber of bytes: %s\n", exact_and_human_size(len, buf, 0)) < 0) || + (end >= 0 && dprintf(ttyfd, "\tEnd of overwrite: %s\n", exact_and_human_size(end, buf, 1)) < 0)) + goto printerror; + } + + if (dprintf(ttyfd, "\nAffirm by entering 'yes' in majuscule without quotes.\n") < 0) + goto printerror; + + ret = 0; + for (;;) { + r = read(ttyfd, buf, sizeof(buf)); + if (!r) { + goto out; + } else if (r < 0) { + if (errno == EINTR) + continue; + eprintf("read %s:", need_close ? "/dev/tty" : ""); + } + for (i = 0; i < r; i++) { + if (buf[i] != "YES\n"[confirm_state++]) + goto out; + if (buf[i] == '\n') { + ret = 1; + goto out; + } + } + } + +out: + if (need_close) + close(ttyfd); + if (dprintf(ttyfd, "%s\n", ret ? "" : "User did not affirm action. Aborting...") < 0) + goto printerror; + return ret; + +printerror: + eprintf("dprintf %s:", need_close ? "/dev/tty" : ""); +} diff --git a/common.h b/common.h index 241f280..ad4b8a8 100644 --- a/common.h +++ b/common.h @@ -40,17 +40,23 @@ struct status { int write_average_i; struct timespec write_average_begin_times[WRAVG_STEPS]; off_t write_average_amounts[WRAVG_STEPS]; + + size_t nspans; + size_t span_off; + size_t spans_size; + struct span *spans; }; #define STATUS_INIT {.pass_nr = 1, .last_success = {-1, 0}, .direction = FORWARDS} - - -/* deadshred.c */ -extern struct status status; -extern _Atomic volatile sig_atomic_t exiting; +#define STATUS_TRANSFER_SIZE (offsetof(struct status, nspans) + sizeof(size_t)) /* io.c */ +void writeall(int fd, const void *data, size_t n, const char *fname); +void filecpy(int fd, off_t src, off_t len, const char *fname); off_t filesize(int fd, const char *fname); +#if defined(__linux__) +char *get_device_name(unsigned int devmajor, unsigned int devminor); +#endif /* fmt.c */ @@ -62,6 +68,7 @@ __attribute__((__pure__)) off_t unhumansize(const char *s, char flag); const char *durationstr(const struct timespec *dur, char *buf, int second_decimals); const char *humanbytespersecond(double bytes_per_second, char *buf); +const char *exact_and_human_size(off_t bytes, char *buf, int with_unit); /* text.c */ @@ -87,6 +94,19 @@ void used_random(ssize_t amount); /* sig.c */ +extern _Atomic volatile sig_atomic_t exiting; void setup_sighandler(void); void block_sigs(void); void unblock_sigs(void); + + +/* ask.c */ +int confirm(const char *device, off_t off, off_t len, off_t end); + + +/* map.c */ +void add_span(struct status *status, off_t off, off_t amount, size_t blocksize, int try_join); +void dump_map(int fd, const struct status *status, const char *fname); +off_t measure_map_dump(const struct status *status); +off_t load_map(int fd, struct status *status, enum direction direction, const char *fname); +void update_map(int fd, const struct status *status, const char *fname); diff --git a/deadshred.1 b/deadshred.1 index e445c05..8d36103 100644 --- a/deadshred.1 +++ b/deadshred.1 @@ -13,8 +13,9 @@ deadshred \- override the contents of a device that may be broken | -e .IR postend ] -[-r] +[-rY] .I device +.RI [ map-file ] [< .IR random-source ] @@ -67,6 +68,9 @@ to overwrite. .B -r Start writing from the end instead of from the beginning on the first pass over the device. +.TP +.B -Y +Do not ask for confirmation. .PP The value of the .B -belo @@ -95,11 +99,21 @@ is recognised as a synonym, but the value is otherwise case sensitive). .SH OPERANDS -The following operand is supported: +The following operands are supported: .TP -.I file +.I device The file to override. Must be either a regular file or a block device. +.TP +.I map-file +If the file +.I map-file exists +and is non-empty, it specifies what sections in the +.I device +to overwrite; this file will be periodically, and +upon exit, updated to remove parts that has been +successfully overwritten. This file will be unlinked +when it becomes empty. .SH STDIN Unless the standard input is a terminal device, it shall be an @@ -108,10 +122,12 @@ fill the device with. .SH STDOUT If the process is terminated using either of the signals -SIGTERM or SIGINT, the process will write to standard output +SIGTERM or SIGINT, and a +.I map-file +was not specified, the process will write to standard output a map of sections that has not been overwritten yet. The output will be the concatenation of one string per section, -each on the format +followed by a , each on the format .PP .nf \fB\(dq%s%x-%x/%x\(dq,\fP <\fB\(dq0x\(dq\fP for the first section, \fB\(dq,\(dq\fP otherwise>\fB,\fP @@ -120,6 +136,24 @@ each on the format <\fIthe block size that should be used when trying to overwrite\fP> .fi +.SH INPUT FILES +If a +.I map-file +is specified, its content shall either be empty or +conform to the format specified for the standard +output. See the +.B STDOUT +section for more information. + +.SH OUTPUT FILES +If a +.I map-file +is specified, the data written to it will conform +to the format specified for the standard +output. See the +.B STDOUT +section for more information. + .SH NOTES While the .B deadshred diff --git a/deadshred.c b/deadshred.c index d7f2f5f..24d02fa 100644 --- a/deadshred.c +++ b/deadshred.c @@ -3,15 +3,17 @@ #include -USAGE("[-b blocksize] [-o offset] [-l length | -e postend] [-r] device [< random-source]"); +USAGE("[-b blocksize] [-o offset] [-l length | -e postend] [-rY] device [map-file] [< random-source]"); -struct status status = STATUS_INIT; -_Atomic volatile sig_atomic_t exiting = 0; +struct auxthread_input { + struct status initial_status; + int map_fd; + const char *map_fname; +}; + -static struct span *spans = NULL; -static size_t nspans = 0; -static size_t spans_size = 0; +static struct status status = STATUS_INIT; static off_t total_size = 0; static char total_size_1000[256]; @@ -24,38 +26,7 @@ static struct timespec start_time; static const struct timespec status_print_interval = {0, MILLISECONDS(500)}; static const struct timespec poll_timeout = {1, 0}; -static int status_print_pipe[2]; - - -static void -add_span(off_t off, off_t amount, size_t blocksize, int try_join) -{ - off_t end = off + amount; - - while ((off_t)(blocksize >> 1) >= amount) - blocksize >>= 1; - - if (try_join) { - if (off == spans[nspans - 1U].end || end == spans[nspans - 1U].start) { - spans[nspans - 1U].start = MIN(off, spans[nspans - 1U].start); - spans[nspans - 1U].end = MAX(end, spans[nspans - 1U].end); - spans[nspans - 1U].bad += amount; - spans[nspans - 1U].blocksize = MAX(blocksize, spans[nspans - 1U].blocksize); - return; - } - } - - if (nspans == spans_size) { - spans_size += 1024; - spans = ereallocarray(spans, spans_size, sizeof(*spans)); - } - - spans[nspans].start = off; - spans[nspans].end = end; - spans[nspans].bad = amount; - spans[nspans].blocksize = blocksize; - nspans++; -} +static int auxthread_pipe[2]; static void @@ -127,14 +98,27 @@ print_status(int done, struct status *s) } +static void +preprint(void) +{ + fprintf(stderr, "\n\n\n\n\n\n\033[K"); +} + + static void * -status_print_loop(void *initial_status) +auxthread_loop(void *input) { - struct pollfd pfd = {.fd = status_print_pipe[0], .events = POLLIN}; + struct pollfd pfd = {.fd = auxthread_pipe[0], .events = POLLIN}; ssize_t r; int terminate = 0; - struct status s = *(struct status *)initial_status; - size_t status_off; + int map_fd = ((struct auxthread_input *)input)->map_fd; + const char *map_fname = ((struct auxthread_input *)input)->map_fname; + struct status s = ((struct auxthread_input *)input)->initial_status; + size_t offset, spans_transfer_size; + + s.spans = NULL; + s.spans_size = 0; + s.span_off = 0; unblock_sigs(); @@ -149,22 +133,46 @@ status_print_loop(void *initial_status) case 0: break; default: - status_off = 0; + offset = 0; read_status_again: - r = read(status_print_pipe[0], &((char *)&s)[status_off], sizeof(s) - status_off); - if (r == (ssize_t)(sizeof(s) - status_off)) { - goto have_time; - } else if (!r) { + r = read(auxthread_pipe[0], &((char *)&s)[offset], STATUS_TRANSFER_SIZE - offset); + if (!r) { terminate = 1; break; } else if (r < 0) { if (errno == EINTR) goto read_status_again; eprintf("read :"); - } else { - status_off += (size_t)r; + } else if (r != (ssize_t)(STATUS_TRANSFER_SIZE - offset)) { + offset += (size_t)r; goto read_status_again; } + + if (!s.nspans) + goto have_time; + if (s.spans_size != s.nspans) { + s.spans_size = s.nspans; + s.spans = ereallocarray(s.spans, s.spans_size, sizeof(*s.spans)); + } + + offset = 0; + spans_transfer_size = s.nspans * sizeof(*s.spans); + read_spans_again: + r = read(auxthread_pipe[0], &((char *)s.spans)[offset], spans_transfer_size - offset); + if (!r) { + terminate = 1; + break; + } else if (r < 0) { + if (errno == EINTR) + goto read_spans_again; + eprintf("read :"); + } else if (r != (ssize_t)(spans_transfer_size - offset)) { + offset += (size_t)r; + goto read_spans_again; + } + + update_map(map_fd, &s, map_fname); + goto have_time; } if (clock_gettime(clck, &s.now)) eprintf("clock_gettime %s:", clkcstr); @@ -177,16 +185,16 @@ status_print_loop(void *initial_status) wravg_update_time(&s); } while (!terminate); + free(s.spans); return NULL; } static void -shredspan(int fd, struct span *span, const char *fname) +shredspan(int fd, struct span *span, const char *fname, int have_map) { off_t off, n; ssize_t r; - size_t status_off; struct timespec when = {0, 0}; int bad = span->bad > 0; int first_fail = 1; @@ -201,8 +209,8 @@ shredspan(int fd, struct span *span, const char *fname) span->start = off; else span->end = off; - close(status_print_pipe[1]); - status_print_pipe[1] = -1; + close(auxthread_pipe[1]); + auxthread_pipe[1] = -1; return; } @@ -213,22 +221,51 @@ shredspan(int fd, struct span *span, const char *fname) status.last_success = status.now; } if (libsimple_cmptimespec(&status.now, &when) >= 0) { + size_t saved_nspans = status.nspans; + size_t spans_transfer_size; + size_t spans_offset = 0; + size_t status_off = 0; libsimple_sumtimespec(&when, &status.now, &status_print_interval); - status_off = 0; + if (have_map && 0) /* TODO periodically 1 instead 0 */ + status.nspans -= status.span_off; + else + status.nspans = 0; + spans_transfer_size = status.nspans * sizeof(*status.spans); + spans_transfer_size += spans_offset = status.span_off * sizeof(*status.spans); write_status_again: - r = write(status_print_pipe[1], &((const char *)&status)[status_off], sizeof(status) - status_off); - if (r != (ssize_t)(sizeof(status) - status_off)) { + r = write(auxthread_pipe[1], &((const char *)&status)[status_off], STATUS_TRANSFER_SIZE - status_off); + if (r != (ssize_t)(STATUS_TRANSFER_SIZE - status_off)) { if (r >= 0) { status_off += (size_t)r; goto write_status_again; } else if (r == EINTR) { - if (exiting) + if (exiting) { + status.nspans = saved_nspans; goto userexit; + } goto write_status_again; } else { eprintf("write :"); } } + status.nspans = saved_nspans; + if (spans_offset < spans_transfer_size) { + write_spans_again: + r = write(auxthread_pipe[1], &((const char *)status.spans)[spans_offset], + spans_transfer_size - spans_offset); + if (r != (ssize_t)(spans_transfer_size - spans_offset)) { + if (r >= 0) { + spans_offset += (size_t)r; + goto write_spans_again; + } else if (r == EINTR) { + if (exiting) + goto userexit; + goto write_spans_again; + } else { + eprintf("write :"); + } + } + } } if (status.direction == FORWARDS) { n = MIN((off_t)span->blocksize, span->end - off); @@ -252,7 +289,7 @@ shredspan(int fd, struct span *span, const char *fname) } if (errno != EIO) eprintf("pwrite %s %zu %ji:", fname, (size_t)n, (intmax_t)off); - add_span(off, n, span->blocksize == 1U ? 1U : span->blocksize >> 1, !first_fail); + add_span(&status, off, n, span->blocksize == 1U ? 1U : span->blocksize >> 1, !first_fail); first_fail = 0; if (status.direction == FORWARDS) off += n; @@ -271,7 +308,7 @@ shredspan(int fd, struct span *span, const char *fname) off += (off_t)r; } else if ((off_t)r < n) { n -= (off_t)r; - add_span(off + (off_t)r, n, span->blocksize == 1U ? 1U : span->blocksize >> 1, !first_fail); + add_span(&status, off + (off_t)r, n, span->blocksize == 1U ? 1U : span->blocksize >> 1, !first_fail); first_fail = 0; if (status.direction == FORWARDS) off += n; @@ -300,43 +337,16 @@ shredspan(int fd, struct span *span, const char *fname) } -static void -dump_map(int fd, const char *fname) -{ - size_t i; - int r; - - if (!nspans) - return; - - for (i = 0; i < nspans; i++) { - r = dprintf(fd, "%s%jx-%jx/%zx", - i ? "," : "0x", - (uintmax_t)spans[i].start, - (uintmax_t)spans[i].end, - spans[i].blocksize); - if (r < 0) - goto fail; - } - r = dprintf(fd, "\n"); - if (r < 0) - goto fail; - - return; - -fail: - eprintf("dprintf %s:", fname); -} - - int main(int argc, char *argv[]) { off_t off = -1, len = -1, end = -1, blk = -1; size_t i, j; int fd, reverse_direction = 0; - pthread_t status_print_thread; - struct status initial_status; + int ask_for_confirmation = 1; + int map_fd = -1; + pthread_t auxthread; + struct auxthread_input auxthread_input; ARGBEGIN { case 'b': @@ -362,21 +372,18 @@ main(int argc, char *argv[]) case 'r': reverse_direction = 1; break; + case 'Y': + ask_for_confirmation = 0; + break; default: usage(); } ARGEND; - if (argc != 1) + if (argc < 1 || argc > 2) usage(); - /* TODO If in foreground, and /dev/tty can be opened, - * use /dev/tty — however if stdin is a tty, use - * stdin instead — to request configuration unless - * bypassed with new option (-Y). The prompt shall - * be identify the device and display it's size, - * also the section the wipe shall be displayed - * if -elo was used. The use shall be required - * to enter 'yes' in upper case. */ + if (ask_for_confirmation && !confirm(argv[0], off, len, end)) + return 1; fd = open(argv[0], O_WRONLY | O_DSYNC); if (fd < 0) @@ -384,52 +391,62 @@ main(int argc, char *argv[]) init_random(STDIN_FILENO, "", blk); - spans = emalloc(sizeof(*spans)); - spans[0].start = 0; - spans[0].end = filesize(fd, argv[0]); - spans[0].bad = 0; - spans[0].blocksize = max_blksize(); - nspans = 1U; - spans_size = 1U; + status.spans = emalloc(sizeof(*status.spans)); + status.spans[0].start = 0; + status.spans[0].end = filesize(fd, argv[0]); + status.spans[0].bad = 0; + status.spans[0].blocksize = max_blksize(); + status.nspans = 1U; + status.spans_size = 1U; if (off >= 0) { - if (off > spans[0].end) - eprintf("value of -o flag is beyond the end of the file"); - spans[0].start = off; + if (off > status.spans[0].end) + eprintf("value of -o option is beyond the end of the file"); + status.spans[0].start = off; } if (len >= 0) { - if (len > OFF_MAX - spans[0].start) - eprintf("the sum of the values of -o and -l flag is too large"); - end = spans[0].start + len; - if (end > spans[0].end) - eprintf("the sum of the values of -o and -l flag is beyond the end of the file"); - spans[0].end = end; + if (len > OFF_MAX - status.spans[0].start) + eprintf("the sum of the values of -o and -l option is too large"); + end = status.spans[0].start + len; + if (end > status.spans[0].end) + eprintf("the sum of the values of -o and -l option is beyond the end of the file"); + status.spans[0].end = end; } else if (end >= 0) { - if (end > spans[0].end) - eprintf("the value of -e flag is beyond the end of the file"); - spans[0].end = end; + if (end > status.spans[0].end) + eprintf("the value of -e option is beyond the end of the file"); + status.spans[0].end = end; } - total_size = spans[0].end - spans[0].start; + total_size = status.spans[0].end - status.spans[0].start; if (!total_size) { weprintf("attempting to shred 0 bytes"); close(fd); return 0; } - while ((off_t)spans[0].blocksize >> 1 > total_size) - spans[0].blocksize >>= 1; + while ((off_t)status.spans[0].blocksize >> 1 > total_size) + status.spans[0].blocksize >>= 1; humansize1000(total_size, total_size_1000); humansize1024(total_size, total_size_1024); - if (pipe(status_print_pipe)) + if (reverse_direction) + status.direction ^= 1; + + if (argv[1]) { + map_fd = open(argv[1], O_RDWR | O_CREAT, 0666); + if (map_fd < 0) + eprintf("open %s O_RDWR|O_CREAT 0666:", argv[1]); + if (lseek(map_fd, 0, SEEK_SET)) + eprintf("lseek %s 0 SEEK_SET:", argv[1]); + status.shredded = load_map(fd, &status, status.direction, argv[1]); + } + + if (pipe(auxthread_pipe)) eprintf("pipe:"); setup_sighandler(); block_sigs(); - if (reverse_direction) - status.direction ^= 1; if (clock_gettime(clck, &start_time)) { clck = CLOCK_MONOTONIC; clkcstr = "CLOCK_MONOTONIC"; @@ -439,33 +456,38 @@ main(int argc, char *argv[]) wravg_init(&start_time, &status); status.last_success = start_time; - initial_status = status; - errno = pthread_create(&status_print_thread, NULL, &status_print_loop, &initial_status); + auxthread_input.initial_status = status; + auxthread_input.map_fd = map_fd; + auxthread_input.map_fname = argv[1]; + errno = pthread_create(&auxthread, NULL, &auxthread_loop, &auxthread_input); if (errno) eprintf("pthread_create NULL:"); + libsimple_eprintf_preprint = &preprint; + for (;;) { - size_t old_nspans = nspans; - for (i = 0; i < old_nspans && !exiting; i++) - shredspan(fd, &spans[i], argv[0]); - if (exiting) { - memmove(&spans[0], &spans[i], (nspans -= i) * sizeof(*spans)); - break; + size_t old_nspans = status.nspans; + for (i = 0; i < old_nspans; i++) { + status.span_off = i; + shredspan(fd, &status.spans[i], argv[0], map_fd >= 0); + if (exiting) + goto out; } - for (i = 0, j = nspans, nspans -= old_nspans; i < nspans;) - spans[i++] = spans[--j]; - if (!nspans) + for (i = 0, j = status.nspans, status.nspans -= old_nspans; i < status.nspans;) + status.spans[i++] = status.spans[--j]; + if (!status.nspans) break; status.direction ^= 1; status.pass_nr += 1U; } +out: close(fd); - if (status_print_pipe[1] >= 0) - close(status_print_pipe[1]); + if (auxthread_pipe[1] >= 0) + close(auxthread_pipe[1]); destroy_random(); - errno = pthread_join(status_print_thread, NULL); + errno = pthread_join(auxthread, NULL); if (errno) weprintf("pthread_join:"); @@ -473,10 +495,19 @@ main(int argc, char *argv[]) eprintf("clock_gettime %s:", clkcstr); print_status(1, &status); - if (nspans) { - dump_map(STDOUT_FILENO, ""); - if (close(STDOUT_FILENO)) - eprintf("write "); + if (status.nspans > status.span_off) { + if (map_fd >= 0) { + update_map(map_fd, &status, argv[0]); + if (close(map_fd)) + eprintf("write %s:", argv[0]); + } else { + dump_map(STDOUT_FILENO, &status, ""); + if (close(STDOUT_FILENO)) + eprintf("write :"); + } + } else if (argv[1]) { + if (unlink(argv[1])) + weprintf("unlink %s:", argv[1]); } return 0; } diff --git a/fmt.c b/fmt.c index 4c931dd..7b73ed7 100644 --- a/fmt.c +++ b/fmt.c @@ -187,3 +187,19 @@ humanbytespersecond(double bytes_per_second, char *buf) return buf; } + + +const char * +exact_and_human_size(off_t bytes, char *buf, int with_unit) +{ + char *p = buf; + p += sprintf(p, "%ji%s", (intmax_t)bytes, !with_unit ? "" : bytes == 1 ? " byte" : " bytes"); + if (bytes >= 1024) { + p = stpcpy(p, " ("); + humansize1000(bytes, p); + p = stpcpy(strchr(p, '\0'), ", "); + humansize1024(bytes, p); + p = stpcpy(strchr(p, '\0'), ")"); + } + return buf; +} diff --git a/io.c b/io.c index 6ee9e29..98afe53 100644 --- a/io.c +++ b/io.c @@ -2,6 +2,61 @@ #include "common.h" +void +writeall(int fd, const void *data, size_t n, const char *fname) +{ + const char *text = data; + ssize_t r; + + while (n) { + r = write(fd, text, n); + if (r < 0) { + if (errno == EINTR) + continue; + eprintf("write %s:", fname); + } + text = &text[r]; + n -= (size_t)r; + } +} + + +void +filecpy(int fd, off_t src, off_t len, const char *fname) +{ + char buf[8UL << 10]; + ssize_t r; + size_t n; + size_t off; + + while (len) { + read_again: + r = pread(fd, buf, sizeof(buf), src); + if (r <= 0) { + if (!r) + eprintf("%s: unexpected end of file", fname); + if (errno == EINTR) + goto read_again; + eprintf("pread %s:", fname); + } + src += (off_t)r; + len -= (off_t)r; + n = (size_t)r; + + for (off = 0; off < n;) { + write_again: + r = write(fd, &buf[off], n - off); + if (r < 0) { + if (errno == EINTR) + goto write_again; + eprintf("write %s:", fname); + } + off += (size_t)r; + } + } +} + + off_t filesize(int fd, const char *fname) { @@ -23,3 +78,75 @@ filesize(int fd, const char *fname) return st.st_size; } + + +#if defined(__linux__) + +char * +get_device_name(unsigned int devmajor, unsigned int devminor) +{ + char dev[2 * 3 * sizeof(int) + 3]; + size_t devlen; + char buf[PATH_MAX]; + struct dirent *f; + DIR *dir; + int dfd, fd; + ssize_t r; + size_t off; + + devlen = (size_t)sprintf(dev, "%u:%u\n", devmajor, devminor); + + dir = opendir("/sys/class/block"); + if (!dir) { + weprintf("opendir /sys/class/block:"); + return NULL; + } + dfd = dirfd(dir); + if (dfd < 0) { + weprintf("dirfd /sys/class/block:"); + goto fail; + } + + while ((errno = 0, f = readdir(dir))) { + if (f->d_name[0] == '.') + continue; + if (strlen(f->d_name) > sizeof(buf) - sizeof("/dev")) + continue; + stpcpy(stpcpy(buf, f->d_name), "/dev"); + fd = openat(dfd, buf, O_RDONLY); + if (fd < 0) + continue; + + off = 0; + while (off < sizeof(buf)) { + r = read(fd, &buf[off], sizeof(buf) - off); + if (!r) + goto complete; + if (r < 0) { + if (errno == EINTR) + continue; + goto next; + } + off += (size_t)r; + } + goto next; + + complete: + if (off == devlen && !memcmp(dev, buf, devlen)) { + close(fd); + closedir(dir); + return strdup(f->d_name); + } + + next: + close(fd); + } + + if (errno) + weprintf("readdir /sys/class/block:"); +fail: + closedir(dir); + return NULL; +} + +#endif diff --git a/map.c b/map.c new file mode 100644 index 0000000..e72b02a --- /dev/null +++ b/map.c @@ -0,0 +1,325 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +void +add_span(struct status *status, off_t off, off_t amount, size_t blocksize, int try_join) +{ + off_t end = off + amount; + + while ((off_t)(blocksize >> 1) >= amount) + blocksize >>= 1; + + if (try_join) { + if (off == status->spans[status->nspans - 1U].end || end == status->spans[status->nspans - 1U].start) { + status->spans[status->nspans - 1U].start = MIN(off, status->spans[status->nspans - 1U].start); + status->spans[status->nspans - 1U].end = MAX(end, status->spans[status->nspans - 1U].end); + status->spans[status->nspans - 1U].bad += amount; + status->spans[status->nspans - 1U].blocksize = MAX(blocksize, status->spans[status->nspans - 1U].blocksize); + return; + } + } + + if (status->nspans == status->spans_size) { + status->spans_size += 1024; + status->spans = ereallocarray(status->spans, status->spans_size, sizeof(*status->spans)); + } + + status->spans[status->nspans].start = off; + status->spans[status->nspans].end = end; + status->spans[status->nspans].bad = amount; + status->spans[status->nspans].blocksize = blocksize; + status->nspans++; +} + + +void +dump_map(int fd, const struct status *status, const char *fname) +{ + size_t i; + int r; + + if (status->nspans == status->span_off) + return; + + for (i = status->span_off; i < status->nspans; i++) { + r = dprintf(fd, "%s%jx-%jx/%zx", + i ? "," : "0x", + (uintmax_t)status->spans[i].start, + (uintmax_t)status->spans[i].end, + status->spans[i].blocksize); + if (r < 0) + goto fail; + } + r = dprintf(fd, "\n"); + if (r < 0) + goto fail; + + return; + +fail: + eprintf("dprintf %s:", fname); +} + + +off_t +measure_map_dump(const struct status *status) +{ + size_t i; + int r; + off_t ret = 1; + + if (status->nspans == status->span_off) + return 0; + + for (i = status->span_off; i < status->nspans; i++) { + r = snprintf(NULL, 0, "%s%jx-%jx/%zx", + i ? "," : "0x", + (uintmax_t)status->spans[i].start, + (uintmax_t)status->spans[i].end, + status->spans[i].blocksize); + if (r < 0) + eprintf("snprintf:"); + ret += r; + } + + return ret; +} + + +static int +spancmp(const void *av, const void *bv) +{ + const struct span *a = av, *b = bv; + return a->start < b->start ? -1 : a->start > b->start; +} + + +off_t +load_map(int fd, struct status *status, enum direction direction, const char *fname) +{ + char buf[8UL << 10], *end; + size_t off = 0; + size_t p = 0, start = 0, i, j; + ssize_t r; + int empty = 1, state = 0, ended = 0; + uintmax_t v1 = 0, v2 = 0, v3 = 0, *vp; + struct span refspan = status->spans[0], t; + off_t preshredded, min_start; + + for (;;) { + r = read(fd, &buf[off], sizeof(buf) - off); + if (r <= 0) { + if (!r) + break; + if (errno == EINTR) + continue; + eprintf("read %s:", fname); + } + off += (size_t)r; + if (empty) { + if (off < 2) + continue; + status->nspans = 0; + empty = 0; + if (buf[0] != '0' || buf[1] != 'x') + goto invalid; + p = 2U; + } + if (ended) + goto invalid; + + while (p < off) { + switch (state) { + case 0: + errno = 0; + /* fall through */ + case 2: + case 4: + start = p; + if (!isxdigit(buf[p])) + goto invalid; + p++; + state += 1; + break; + case 1: + vp = &v1; + goto value; + case 3: + vp = &v2; + goto value; + case 5: + vp = &v3; + value: + if (isxdigit(buf[p])) { + p++; + break; + } + if (state == 1) { + if (buf[p] != '-') + goto invalid; + state += 1; + } else { + if (state == 3 && buf[p] == '/') { + state += 1; + } else if (buf[p] == ',') { + v3 = refspan.blocksize; + state += 3; + } else if (buf[p] == '\n') { + v3 = refspan.blocksize; + state += 3; + ended = 1; + } else { + goto invalid; + } + + } + buf[p++] = '\0'; + *vp = strtoumax(&buf[start], &end, 16); + if (errno || *end) + goto invalid; + if (state == 6) { + state = 0; + if (v1 >= v2 || v2 > (uintmax_t)OFF_MAX) + goto invalid; + if (v3 > v2 - v1) + v3 = v2 - v1; + if (v3 > (uintmax_t)SIZE_MAX) + v3 = ((uintmax_t)SIZE_MAX >> 1) + 1U; + if (v3 > refspan.blocksize) + v3 = refspan.blocksize; + + if ((off_t)v1 < refspan.start || (off_t)v2 > refspan.end) + eprintf("%s: contains out of bounds span", fname); + + if (status->nspans == status->spans_size) { + status->spans_size += 1024; + status->spans = ereallocarray(status->spans, status->spans_size, + sizeof(*status->spans)); + } + + status->spans[status->nspans].start = (off_t)v1; + status->spans[status->nspans].end = (off_t)v2; + status->spans[status->nspans].bad = 0; + status->spans[status->nspans].blocksize = (size_t)v3; + status->nspans++; + } + break; + } + } + + off = 0; + } + + if (!ended) + eprintf("%s: truncated file content", fname); + + if (empty) + return 0; + + qsort(status->spans, status->nspans, sizeof(*status->spans), &spancmp); + min_start = -1; + preshredded = refspan.end - refspan.start; + for (i = 0; i < status->nspans; i++) { + if (status->spans[i].start < min_start) + goto invalid; + if (status->spans[i].start == min_start && + status->spans[i].blocksize == status->spans[i - 1U].blocksize) { + status->spans[i - 1U].end = status->spans[i].end; + memmove(&status->spans[i], &status->spans[i + 1U], + (--status->nspans - i) * sizeof(*status->spans)); + i--; + } + min_start = status->spans[i].end; + preshredded -= status->spans[i].end - status->spans[i].start; + } + if (direction == BACKWARDS) { + for (i = 0, j = status->nspans; --j > i; i++) { + t = status->spans[i]; + status->spans[i] = status->spans[j]; + status->spans[j] = t; + } + } + + return preshredded; + +invalid: + eprintf("%s: invalid file content", fname); +} + + +void +update_map(int fd, const struct status *status, const char *fname) +{ +#define STR(S) (S), sizeof(S) - 1U + + static char dots[4096] = {0}; + + off_t old_len, new_len, pos; + ssize_t r; + + if (!dots[0]) + memset(dots, '.', sizeof(dots)); + + old_len = lseek(fd, 0, SEEK_CUR); + if (old_len < 0) + eprintf("lseek %s 0 SEEK_CUR:", fname); + + new_len = measure_map_dump(status); + + pos = old_len; + if (pos < new_len) { + while (pos < new_len - 1) { + r = write(fd, dots, (size_t)MIN((off_t)sizeof(dots), new_len - 1 - pos)); + if (r < 0) { + if (errno == EINTR) + continue; + eprintf("write %s:", fname); + } + pos += (off_t)r; + } + do { + r = write(fd, "\n", 1U); + } while (r < 0 && errno == EINTR); + if (r < 0) + eprintf("write %s:", fname); + } + + writeall(fd, STR(">> FIRST LINE IS CORRECT UNLESS THE AS ANOTHER NOTICE BELOW THIS ONE >>\n"), fname); + + filecpy(fd, 0, old_len, fname); + + writeall(fd, STR(">> LINE ABOVE IS THE CORRECT ONE >>\n"), fname); + + for (;;) { + if (!fdatasync(fd)) + break; + if (errno != EINTR) + eprintf("fdatasync %s:", fname); + } + + if (lseek(fd, 0, SEEK_SET)) + eprintf("lseek %s 0 SEEK_SET:", fname); + + dump_map(fd, status, fname); + + pos = lseek(fd, 0, SEEK_CUR); + if (pos < 0) + eprintf("lseek %s 0 SEEK_CUR:", fname); + + for (;;) { + if (!ftruncate(fd, pos)) + break; + if (errno != EINTR) + eprintf("ftruncate %s %ji:", fname, (intmax_t)pos); + } + + for (;;) { + if (!fsync(fd)) + break; + if (errno != EINTR) + eprintf("fsync %s:", fname); + } + +#undef STR +} diff --git a/sig.c b/sig.c index c7900fd..d77a46f 100644 --- a/sig.c +++ b/sig.c @@ -2,6 +2,9 @@ #include "common.h" +_Atomic volatile sig_atomic_t exiting = 0; + + static void sighandler(int signo) { -- cgit v1.2.3-70-g09d2