diff options
| author | Mattias Andrée <maandree@kth.se> | 2024-09-21 19:31:16 +0200 | 
|---|---|---|
| committer | Mattias Andrée <maandree@kth.se> | 2024-09-21 19:31:16 +0200 | 
| commit | 2e55bedc45e836899a18ea7f4a488f50597afad5 (patch) | |
| tree | 5a9e3cc1465cfc711fb1f74580cb17f20a46212e | |
| parent | Update documentation and adjust blocksize if larger than the device size (diff) | |
| download | deadshred-2e55bedc45e836899a18ea7f4a488f50597afad5.tar.gz deadshred-2e55bedc45e836899a18ea7f4a488f50597afad5.tar.bz2 deadshred-2e55bedc45e836899a18ea7f4a488f50597afad5.tar.xz | |
misc
Signed-off-by: Mattias Andrée <maandree@kth.se>
Diffstat (limited to '')
| -rw-r--r-- | Makefile | 4 | ||||
| -rw-r--r-- | README | 17 | ||||
| -rw-r--r-- | TODO | 2 | ||||
| -rw-r--r-- | ask.c | 115 | ||||
| -rw-r--r-- | common.h | 30 | ||||
| -rw-r--r-- | deadshred.1 | 44 | ||||
| -rw-r--r-- | deadshred.c | 315 | ||||
| -rw-r--r-- | fmt.c | 16 | ||||
| -rw-r--r-- | io.c | 127 | ||||
| -rw-r--r-- | map.c | 325 | ||||
| -rw-r--r-- | sig.c | 3 | 
11 files changed, 841 insertions, 157 deletions
| @@ -10,7 +10,9 @@ OBJ =\  	text.o\  	avg.o\  	rnd.o\ -	sig.o +	sig.o\ +	ask.o\ +	map.o  HDR =\  	common.h @@ -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 @@ -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 @@ -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>"); +} @@ -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;  } @@ -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; +} @@ -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 @@ -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 +} @@ -2,6 +2,9 @@  #include "common.h" +_Atomic volatile sig_atomic_t exiting = 0; + +  static void  sighandler(int signo)  { | 
