From ad6c9cbcd8548425e9db258e51906b569eb46c7f Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Tue, 17 Sep 2024 18:25:35 +0200 Subject: First commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- .gitignore | 15 ++++ LICENSE | 15 ++++ Makefile | 37 ++++++++++ TODO | 9 +++ config.mk | 8 +++ deadshred.c | 231 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 315 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 TODO create mode 100644 config.mk create mode 100644 deadshred.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a0cae35 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*\#* +*~ +*.o +*.a +*.lo +*.su +*.so +*.so.* +*.dll +*.dylib +*.gch +*.gcov +*.gcno +*.gcda +/deadshred diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fccd785 --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +ISC License + +© 2024 Mattias Andrée + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1e91bbd --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +.POSIX: + +CONFIGFILE = config.mk +include $(CONFIGFILE) + +OBJ =\ + deadshred.o + +HDR = + +all: deadshred +$(OBJ): $(HDR) + +.c.o: + $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) + +deadshred: $(OBJ) + $(CC) -o $@ $(OBJ) $(LDFLAGS) + +install: deadshred + mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" + mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1/" + cp -- deadshred "$(DESTDIR)$(PREFIX)/bin/" + cp -- deadshred.1 "$(DESTDIR)$(MANPREFIX)/man1/" + +uninstall: + -rm -f -- "$(DESTDIR)$(PREFIX)/bin/deadshred" + -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/deadshred.1" + +clean: + -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.gch *.gcov *.gcno *.gcda + -rm -f -- deadshred + +.SUFFIXES: +.SUFFIXES: .o .c + +.PHONY: all install uninstall clean diff --git a/TODO b/TODO new file mode 100644 index 0000000..1aede57 --- /dev/null +++ b/TODO @@ -0,0 +1,9 @@ +Add README +Add man page +Add `-n iterations` +Add shred map for continuing later (print to stdout on SIGTERM) + +Enhance progress printout with: + exact bytes count + bad sections (count, bytes) + shredded bytes per seconds diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..aeb8cec --- /dev/null +++ b/config.mk @@ -0,0 +1,8 @@ +PREFIX = /usr +MANPREFIX = $(PREFIX)/share/man + +CC = c99 + +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE +CFLAGS = +LDFLAGS = -lsimple diff --git a/deadshred.c b/deadshred.c new file mode 100644 index 0000000..5323d15 --- /dev/null +++ b/deadshred.c @@ -0,0 +1,231 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + + +USAGE("file"); + + +struct span { + off_t start; + off_t end; + size_t blocksize; +}; + + +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 reservoir[128U << 10]; +static size_t reservoir_off = sizeof(reservoir); + +static char total_size_1000[256]; +static char total_size_1024[256]; + + +static off_t +filesize(int fd, const char *fname) +{ + struct stat st; + + if (fstat(fd, &st)) + eprintf("fstat %s:", fname); + + switch (st.st_mode & S_IFMT) { + case S_IFREG: + break; + case S_IFBLK: + if (ioctl(fd, BLKGETSIZE64, &st.st_size) < 0) + eprintf("ioctl %s BLKGETSIZE64:", fname); + break; + default: + eprintf("%s: not a regular file or block device", fname); + } + + return st.st_size; +} + + +static void +ensure_random(size_t needed) +{ + size_t off; + ssize_t r; + + if (sizeof(reservoir) - reservoir_off >= needed) + return; + + for (off = 0; off < reservoir_off;) { + r = read(STDIN_FILENO, &reservoir[off], reservoir_off - off); + if (r <= 0) { + if (!r) + eprintf("random source depleted"); + if (errno == EINTR) + continue; + eprintf("read :"); + } + off += (size_t)r; + } + + reservoir_off = 0; +} + + +static off_t +add_span(off_t off, off_t max_end, size_t blocksize) +{ + off_t end; + + end = MIN((off | ((off_t)blocksize - 1)) + 1, max_end); + while ((off_t)(blocksize >> 1) >= end - off) + blocksize >>= 1; + + if (nspans == spans_size) { + spans_size += 1024; + spans = ereallocarray(spans, spans_size, sizeof(*spans)); + } + + spans[nspans].start = off; + spans[nspans].end = end; + spans[nspans].blocksize = (size_t)blocksize; + nspans++; +} + + +static char * +humansize1000(off_t s, char *buf) +{ + const char *units = "kMGTPEZYRQ"; + size_t unit = 0; + if (s < 1000) { + sprintf(buf, "%u B", (unsigned)s); + return buf; + } + s /= 100; + while (units[unit + 1U] && s >= 10000) { + s /= 1000; + unit++; + } + sprintf(buf, "%u.%u %cB", (unsigned)s / 10U, (unsigned)s % 10U, units[unit]); + return buf; +} + + +static char * +humansize1024(off_t s, char *buf) +{ + const char *units = "KMGTPEZYRQ"; + size_t unit = 0; + if (s < 1024) { + sprintf(buf, "%u B", (unsigned)s); + return buf; + } + while (units[unit + 1U] && s >= 1024 * 1024) { + s /= 1024; + unit++; + } + sprintf(buf, "%lu.%u %ciB", (unsigned long int)s / 1024U, (unsigned long int)((s * 10 % 10240) / 1024U), units[unit]); + return buf; +} + + +static int +print_progress(int done) +{ + static char buf1[1024] = {0}; + static char buf2[1024] = {0}; + static int i = 0; + + char subbuf1[256]; + char subbuf2[256]; + + sprintf(i == 0 ? buf1 : buf2, + "%.2lf %% (%s, %s) of %s (%s) shredded\033[K\n%s", + 100. * (double)shredded / (double)total_size, + humansize1000(shredded, subbuf1), + humansize1024(shredded, subbuf2), + total_size_1000, + total_size_1024, + done ? "" : "\033[A"); + + if (strcmp(buf1, buf2)) { + fprintf(stderr, "%s", i == 0 ? buf1 : buf2); + fflush(stderr); + } + + i ^= 1; +} + + +static void +shredspan(int fd, const struct span *span, const char *fname) +{ + off_t off; + ssize_t r; + + for (off = span->start; off < span->end;) { + print_progress(0); + if (lseek(fd, off, SEEK_SET) < 0) + eprintf("lseek %s %ji SEEK_SET\n", fname, (intmax_t)off); + ensure_random(span->blocksize); + r = write(fd, &reservoir[reservoir_off], (size_t)MIN((off_t)span->blocksize, span->end - off)); + if (r < 0) { + if (errno != EINTR) + off = add_span(off, span->end, span->blocksize == 1U ? 1U : span->blocksize); + continue; + } + off += (off_t)r; + shredded += (off_t)r; + reservoir_off += (size_t)r; + } +} + + +int +main(int argc, char *argv[]) +{ + char buf1[256]; + char buf2[256]; + size_t i; + int fd; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc != 1) + usage(); + + fd = open(argv[0], O_WRONLY); + if (fd < 0) + eprintf("open %s O_WRONLY:", argv[0]); + + spans = emalloc(sizeof(*spans)); + spans[0].start = 0; + spans[0].end = total_size = filesize(fd, argv[0]); + spans[0].blocksize = 8192U; + nspans = 1U; + spans_size = 1U; + + humansize1000(total_size, total_size_1000); + humansize1024(total_size, total_size_1024); + + while (nspans) { + size_t old_nspans = nspans; + for (i = 0; i < old_nspans; i++) + shredspan(fd, &spans[i], argv[0]); + nspans -= old_nspans; + memmove(&spans[0], &spans[old_nspans], nspans * sizeof(*spans)); + } + + close(fd); + + print_progress(1); + return 0; +} -- cgit v1.2.3-70-g09d2