/* See LICENSE file for copyright and license details. */ #include "common.h" #include /* TODO why has the program started to freeze (since multithreading?)*/ USAGE("[-o offset] [-l length | -e postend] device [< random-source]"); static _Atomic volatile sig_atomic_t exiting = 0; static struct span *spans = NULL; static size_t nspans = 0; static size_t spans_size = 0; static off_t shredded = 0; static off_t total_size = 0; static char total_size_1000[256]; static char total_size_1024[256]; static uintmax_t bad_writes = 0; static uintmax_t bad_sections = 0; static off_t bad_bytes = 0; static clockid_t clck = CLOCK_MONOTONIC_COARSE; static const char *clkcstr = "CLOCK_MONOTONIC_COARSE"; static struct timespec last_success = {-1, 0}; static struct timespec max_success = {-1, 0}; static struct timespec start_time; static const struct timespec progress_print_interval = {0, MILLISECONDS(500)}; static const struct timespec poll_timeout = {0, MILLISECONDS(500)}; static int progress_print_sig_pipe[2]; static pthread_mutex_t progress_mutex; static enum direction direction = FORWARDS; /* TODO add option (-b) to switch start direction */ static uintmax_t pass_nr = 1; static void signal_handler(int signo) { (void) signo; exiting = 1; } 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 void print_progress(int done, const struct timespec *now) { 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 (last_success.tv_sec >= 0) { libsimple_difftimespec(&since_success, now, &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, 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)shredded, humansize1000(shredded, subbuf1), humansize1024(shredded, subbuf2), 100 * (double)shredded / (double)total_size, total_size_1000, total_size_1024, /* } line 2 { */ bad_writes, bad_sections, (intmax_t)bad_bytes, humansize1000(bad_bytes, subbuf3), humansize1024(bad_bytes, subbuf4), /* } line 3 { */ durationstr(&time_spent, subbuf5, 1), wravg_get(now), /* } line 4 { */ durationstr(&since_success, subbuf6, 2), /* } line 5 { */ durationstr(&max_success, subbuf7, 2), /* } line 6 { */ pass_nr, direction == FORWARDS ? "forwards" : "backwards", /* } */ done ? "" : "\033[6A"); if (strcmp(buf1, buf2)) { fprintf(stderr, "%s", bufi == 0 ? buf1 : buf2); fflush(stderr); } bufi ^= 1; } static void * progress_print_loop(void *user) { struct pollfd pfd = {.fd = progress_print_sig_pipe[0], .events = POLLOUT}; struct timespec now; ssize_t r; int terminate = 0; sigset_t sigset; (void) user; sigemptyset(&sigset); sigaddset(&sigset, SIGTERM); sigaddset(&sigset, SIGINT); errno = pthread_sigmask(SIG_UNBLOCK, &sigset, NULL); if (errno) eprintf("pthread_sigmask SIG_UNBLOCK {SIGTERM, SIGINT} NULL:"); do { switch (ppoll(&pfd, 1U, &poll_timeout, NULL)) { case -1: if (errno != EINTR) eprintf("ppoll:"); if (exiting) break; continue; case 0: break; default: r = read(progress_print_sig_pipe[0], &now, sizeof(now)); if (r == (ssize_t)sizeof(now)) { goto have_time; } else if (!r) { terminate = 1; } else if (r < 0) { if (errno == EINTR) continue; eprintf("read :"); } break; } if (clock_gettime(clck, &now)) eprintf("clock_gettime %s:", clkcstr); have_time: pthread_mutex_lock(&progress_mutex); if (exiting) { fprintf(stderr, "\033[K\nTermination initialised by user...\033[K\n\033[K\n"); terminate = 1; } print_progress(0, &now); wravg_update_time(&now); pthread_mutex_unlock(&progress_mutex); } while (!terminate); return NULL; } static void shredspan(int fd, struct span *span, const char *fname) { off_t off, n; ssize_t r; struct timespec now, when = {0, 0}; int bad = span->bad > 0; int first_fail = 1; const char *random_data; pthread_mutex_lock(&progress_mutex); off = (direction == FORWARDS ? span->start : span->end); while (direction == FORWARDS ? off < span->end : off > span->start) { if (exiting) { userexit: if (direction == FORWARDS) span->start = off; else span->end = off; close(progress_print_sig_pipe[1]); progress_print_sig_pipe[1] = -1; goto out; } if (clock_gettime(clck, &now)) eprintf("clock_gettime %s:", clkcstr); if (libsimple_cmptimespec(&now, &when) >= 0) { libsimple_sumtimespec(&when, &now, &progress_print_interval); write(progress_print_sig_pipe[1], &now, sizeof(now)); } if (direction == FORWARDS) { n = MIN((off_t)span->blocksize, span->end - off); } else { n = off--; off &= ~(off_t)(span->blocksize - 1U); if (off < span->start) off = span->start; n -= off; if (!n) break; } random_data = get_random(span->blocksize); pthread_mutex_unlock(&progress_mutex); pwrite_again: r = pwrite(fd, random_data, (size_t)n, off); if (r < 0) { if (errno == EINTR) { if (exiting) { pthread_mutex_lock(&progress_mutex); goto userexit; } goto pwrite_again; } pthread_mutex_lock(&progress_mutex); if (errno != EIO) weprintf("pwrite %s %zu %ji:", fname, (size_t)n, (intmax_t)off); add_span(off, n, span->blocksize == 1U ? 1U : span->blocksize >> 1, !first_fail); first_fail = 0; if (direction == FORWARDS) off += n; if (!span->bad) bad_bytes += n; if (bad) bad = 0; else bad_sections += 1U; when.tv_sec = 0; when.tv_nsec = 0; bad_writes += 1U; continue; } pthread_mutex_lock(&progress_mutex); if (direction == FORWARDS) { 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); first_fail = 0; if (direction == FORWARDS) off += n; if (!span->bad) bad_bytes += n; if (bad) bad = 0; else bad_sections += 1U; when.tv_sec = 0; when.tv_nsec = 0; bad_writes += 1U; } shredded += (off_t)r; used_random(r); wravg_update_write(r); last_success = now; if (span->bad) { bad_bytes -= (off_t)r; span->bad -= (off_t)r; } } if (bad && !span->bad) bad_sections -= 1U; out: pthread_mutex_unlock(&progress_mutex); } 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; size_t i, j; int fd; struct timespec now; struct sigaction sa; pthread_t progress_print_thread; sigset_t sigset; ARGBEGIN { case 'o': if (off >= 0) usage(); off = unhumansize(ARG(), FLAG()); break; case 'l': if (len >= 0 || end >= 0) usage(); len = unhumansize(ARG(), FLAG()); break; case 'e': if (len >= 0 || end >= 0) usage(); end = unhumansize(ARG(), FLAG()); break; default: usage(); } ARGEND; if (argc != 1) usage(); memset(&sa, 0, sizeof(sa)); sa.sa_handler = &signal_handler; sigemptyset(&sa.sa_mask); if (sigaction(SIGTERM, &sa, NULL)) eprintf("sigaction SIGTERM {.sa_handler=, .sa_mask={}, .sa_flags=0} NULL:"); if (sigaction(SIGINT, &sa, NULL)) eprintf("sigaction SIGINT {.sa_handler=, .sa_mask={}, .sa_flags=0} NULL:"); sigemptyset(&sigset); sigaddset(&sigset, SIGTERM); sigaddset(&sigset, SIGINT); errno = pthread_sigmask(SIG_BLOCK, &sigset, NULL); if (errno) eprintf("pthread_sigmask SIG_BLOCK {SIGTERM, SIGINT} NULL:"); fd = open(argv[0], O_WRONLY | O_DSYNC); if (fd < 0) eprintf("open %s O_WRONLY|O_DSYNC:", argv[0]); init_random(STDIN_FILENO, ""); 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; 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 (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; } 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; } total_size = spans[0].end - spans[0].start; humansize1000(total_size, total_size_1000); humansize1024(total_size, total_size_1024); if (pipe(progress_print_sig_pipe)) eprintf("pipe:"); errno = pthread_mutex_init(&progress_mutex, NULL); if (errno) eprintf("pthread_mutex_init NULL:"); errno = pthread_create(&progress_print_thread, NULL, &progress_print_loop, NULL); if (errno) eprintf("pthread_create NULL:"); 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); last_success = start_time; while (nspans) { 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; } /* TODO bug: this is sometimes reached immediately on write failure */ for (i = 0, j = nspans, nspans -= old_nspans; i < nspans;) spans[i++] = spans[--j]; direction ^= 1; pass_nr++; } close(fd); if (progress_print_sig_pipe[1] >= 0) close(progress_print_sig_pipe[1]); errno = pthread_join(progress_print_thread, NULL); if (errno) weprintf("pthread_join:"); pthread_mutex_destroy(&progress_mutex); if (clock_gettime(clck, &now)) eprintf("clock_gettime %s:", clkcstr); print_progress(1, &now); if (nspans) { /* TODO document in man page */ dump_map(STDOUT_FILENO, ""); if (close(STDOUT_FILENO)) eprintf("write "); } return 0; }