/* See LICENSE file for copyright and license details. */ #include "common.h" #include USAGE("[-b blocksize] [-o offset] [-l length | -e postend] [-rY] device [map-file] [< random-source]"); struct auxthread_input { struct status initial_status; int map_fd; const char *map_fname; }; static struct status status = STATUS_INIT; static off_t total_size = 0; static char total_size_1000[256]; static char total_size_1024[256]; static clockid_t clck = CLOCK_MONOTONIC_COARSE; static const char *clkcstr = "CLOCK_MONOTONIC_COARSE"; static struct timespec max_success = {-1, 0}; static struct timespec start_time; #define MAP_SAVE_INTERVAL 3600 static const struct timespec status_print_interval = {0, MILLISECONDS(500)}; static const struct timespec poll_timeout = {1, 0}; static int auxthread_pipe[2]; static void print_status(int done, struct status *s) { static char buf1[2048] = {0}; static char buf2[2048] = {0}; static int bufi = 0; char subbuf1[256]; char subbuf2[256]; char subbuf3[256]; char subbuf4[256]; char subbuf5[256]; char subbuf6[256]; char subbuf7[256]; struct timespec since_success = {-1, 0}; struct timespec time_spent; if (s->last_success.tv_sec >= 0) { libsimple_difftimespec(&since_success, &s->now, &s->last_success); if (libsimple_cmptimespec(&since_success, &max_success) > 0) max_success = since_success; } /* TODO deal with machine and process suspension */ libsimple_difftimespec(&time_spent, &s->now, &start_time); sprintf(bufi == 0 ? buf1 : buf2, "%ji bytes (%s, %s, %.2lf %%) of %s (%s) shredded\033[K\n" "failed writes: %ju; bad sections: %ju (%ji bytes, %s, %s)\033[K\n" "time spent shredding: %s; performance: %s\033[K\n" "time since last successful write: %s\033[K\n" "maximum time until a successful write: %s\033[K\n" "pass: %ju; pass direction: %s\033[K\n" "%s", /* line 1 { */ (intmax_t)s->shredded, humansize1000(s->shredded, subbuf1), humansize1024(s->shredded, subbuf2), 100 * (double)s->shredded / (double)total_size, total_size_1000, total_size_1024, /* } line 2 { */ s->bad_writes, s->bad_sections, (intmax_t)s->bad_bytes, humansize1000(s->bad_bytes, subbuf3), humansize1024(s->bad_bytes, subbuf4), /* } line 3 { */ durationstr(&time_spent, subbuf5, 1), wravg_get(s), /* } line 4 { */ durationstr(&since_success, subbuf6, 2), /* } line 5 { */ durationstr(&max_success, subbuf7, 2), /* } line 6 { */ s->pass_nr, s->direction == FORWARDS ? "forwards" : "backwards", /* } */ done ? "" : "\033[6A"); if (strcmp(buf1, buf2)) { fprintf(stderr, "%s", bufi == 0 ? buf1 : buf2); fflush(stderr); } bufi ^= 1; } static void preprint(void) { fprintf(stderr, "\n\n\n\n\n\n\033[K"); } static void * auxthread_loop(void *input) { struct pollfd pfd = {.fd = auxthread_pipe[0], .events = POLLIN}; ssize_t r; int terminate = 0, save_map = 0; 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(); do { switch (ppoll(&pfd, 1U, &poll_timeout, NULL)) { case -1: if (errno != EINTR) eprintf("ppoll:"); if (exiting) break; continue; case 0: break; default: offset = 0; read_status_again: 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 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; } save_map = 1; goto have_time; } if (clock_gettime(clck, &s.now)) eprintf("clock_gettime %s:", clkcstr); have_time: if (exiting) { fprintf(stderr, "\033[K\nTermination initiated by user...\033[K\n\033[K\n"); terminate = 1; } print_status(0, &s); wravg_update_time(&s); if (save_map) { save_map = 0; update_map(map_fd, &s, map_fname); } } while (!terminate); free(s.spans); return NULL; } static void shredspan(int fd, struct span *span, const char *fname, int have_map) { off_t off, n; ssize_t r; struct timespec when = {0, 0}; int bad = span->bad > 0; int first_fail = 1; int was_successful = 0; int need_save = 0; int need_save_in = -1; const char *random_data; off = (status.direction == FORWARDS ? span->start : span->end); while (status.direction == FORWARDS ? off < span->end : off > span->start) { if (exiting) { userexit: if (status.direction == FORWARDS) span->start = off; else span->end = off; close(auxthread_pipe[1]); auxthread_pipe[1] = -1; return; } if (clock_gettime(clck, &status.now)) eprintf("clock_gettime %s:", clkcstr); if (was_successful) { need_save |= !need_save_in; if (need_save_in < 0) need_save_in = MAP_SAVE_INTERVAL; was_successful = 0; 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; if (need_save_in > 0) need_save_in--; libsimple_sumtimespec(&when, &status.now, &status_print_interval); if (have_map && need_save) { need_save = 0; need_save_in = -1; 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(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) { 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); } else { n = off--; off -= off % (off_t)span->blocksize; if (off < span->start) off = span->start; n -= off; if (!n) break; } random_data = get_random(span->blocksize); pwrite_again: r = pwrite(fd, random_data, (size_t)n, off); if (r < 0) { if (errno == EINTR) { if (exiting) goto userexit; goto pwrite_again; } if (errno != EIO) eprintf("pwrite %s %zu %ji:", fname, (size_t)n, (intmax_t)off); add_span(&status, off, n, span->blocksize == 1U ? 1U : span->blocksize >> 1, !first_fail); first_fail = 0; if (status.direction == FORWARDS) off += n; if (!span->bad) status.bad_bytes += n; if (bad) bad = 0; else status.bad_sections += 1U; when.tv_sec = 0; when.tv_nsec = 0; status.bad_writes += 1U; continue; } if (status.direction == FORWARDS) { off += (off_t)r; } else if ((off_t)r < n) { n -= (off_t)r; 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; if (!span->bad) status.bad_bytes += n; if (bad) bad = 0; else status.bad_sections += 1U; when.tv_sec = 0; when.tv_nsec = 0; status.bad_writes += 1U; } status.shredded += (off_t)r; used_random(r); wravg_update_write(r, &status); was_successful = 1; if (span->bad) { status.bad_bytes -= (off_t)r; span->bad -= (off_t)r; } } if (bad && !span->bad) status.bad_sections -= 1U; } 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; int ask_for_confirmation = 1; int map_fd = -1; pthread_t auxthread; struct auxthread_input auxthread_input; ARGBEGIN { case 'b': if (blk >= 0) usage(); blk = unhumansize(ARG(), FLAG()); break; case 'e': if (len >= 0 || end >= 0) usage(); end = unhumansize(ARG(), FLAG()); break; case 'l': if (len >= 0 || end >= 0) usage(); len = unhumansize(ARG(), FLAG()); break; case 'o': if (off >= 0) usage(); off = unhumansize(ARG(), FLAG()); break; case 'r': reverse_direction = 1; break; case 'Y': ask_for_confirmation = 0; break; default: usage(); } ARGEND; if (argc < 1 || argc > 2) usage(); if (ask_for_confirmation && !confirm(argv[0], off, len, end)) return 1; fd = open(argv[0], O_WRONLY | O_DSYNC); if (fd < 0) eprintf("open %s O_WRONLY|O_DSYNC:", argv[0]); init_random(STDIN_FILENO, "", blk); 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 > 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 - 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 > status.spans[0].end) eprintf("the value of -e option is beyond the end of the file"); status.spans[0].end = end; } 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)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 (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 (clock_gettime(clck, &start_time)) { clck = CLOCK_MONOTONIC; clkcstr = "CLOCK_MONOTONIC"; if (clock_gettime(clck, &start_time)) eprintf("clock_gettime %s:", clkcstr); } wravg_init(&start_time, &status); status.last_success = start_time; 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 = 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 = 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 (auxthread_pipe[1] >= 0) close(auxthread_pipe[1]); destroy_random(); errno = pthread_join(auxthread, NULL); if (errno) weprintf("pthread_join:"); if (clock_gettime(clck, &status.now)) eprintf("clock_gettime %s:", clkcstr); print_status(1, &status); 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; }