aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <maandree@kth.se>2024-09-21 19:31:16 +0200
committerMattias Andrée <maandree@kth.se>2024-09-21 19:31:16 +0200
commit2e55bedc45e836899a18ea7f4a488f50597afad5 (patch)
tree5a9e3cc1465cfc711fb1f74580cb17f20a46212e
parentUpdate documentation and adjust blocksize if larger than the device size (diff)
downloaddeadshred-2e55bedc45e836899a18ea7f4a488f50597afad5.tar.gz
deadshred-2e55bedc45e836899a18ea7f4a488f50597afad5.tar.bz2
deadshred-2e55bedc45e836899a18ea7f4a488f50597afad5.tar.xz
misc
Signed-off-by: Mattias Andrée <maandree@kth.se>
-rw-r--r--Makefile4
-rw-r--r--README17
-rw-r--r--TODO2
-rw-r--r--ask.c115
-rw-r--r--common.h30
-rw-r--r--deadshred.144
-rw-r--r--deadshred.c315
-rw-r--r--fmt.c16
-rw-r--r--io.c127
-rw-r--r--map.c325
-rw-r--r--sig.c3
11 files changed, 841 insertions, 157 deletions
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 <sys/sysmacros.h>
+#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/<devname>/) */
+ }
+#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" : "<stdin>");
+ }
+ 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" : "<stdin>");
+}
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 <newline>, 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 <libsimple-arg.h>
-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 <internal pipe>:");
- } 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 <internal pipe>:");
+ } 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 <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);
@@ -252,7 +289,7 @@ shredspan(int fd, struct span *span, const char *fname)
}
if (errno != EIO)
eprintf("pwrite %s <buffer> %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, "<stdin>", 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, "<stdout>");
- if (close(STDOUT_FILENO))
- eprintf("write <stdout>");
+ 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;
}
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)
{