/* See LICENSE file for copyright and license details. */
#include "common.h"
#include <libsimple-arg.h>
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 byte%s (%s, %s, %.2lf %%) of %s (%s) shredded\033[K\n"
"failed writes: %ju; bad sections: %ju (%ji byte%s, %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,
s->shredded == 1 ? "" : "s",
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,
s->bad_bytes == 1 ? "" : "s",
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 <internal pipe>:");
} 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 <internal pipe>:");
} 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 <internal pipe>:");
}
}
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 <internal pipe>:");
}
}
}
}
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 <buffer> %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, "<stdin>", 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, "<stdout>");
if (close(STDOUT_FILENO))
eprintf("write <stdout>:");
}
} else if (argv[1]) {
if (unlink(argv[1]))
weprintf("unlink %s:", argv[1]);
}
return 0;
}