recoverdisk.c revision 246329
1/*-
2 * ----------------------------------------------------------------------------
3 * "THE BEER-WARE LICENSE" (Revision 42):
4 * <phk@FreeBSD.ORG> wrote this file.  As long as you retain this notice you
5 * can do whatever you want with this stuff. If we meet some day, and you think
6 * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
7 * ----------------------------------------------------------------------------
8 *
9 * $FreeBSD: head/sbin/recoverdisk/recoverdisk.c 246329 2013-02-04 19:17:15Z delphij $
10 */
11#include <sys/param.h>
12#include <sys/queue.h>
13#include <sys/disk.h>
14#include <sys/stat.h>
15
16#include <err.h>
17#include <errno.h>
18#include <fcntl.h>
19#include <signal.h>
20#include <stdint.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24#include <time.h>
25#include <unistd.h>
26
27static volatile sig_atomic_t aborting = 0;
28static size_t bigsize = 1024 * 1024;
29static size_t medsize;
30static size_t minsize = 512;
31
32struct lump {
33	off_t			start;
34	off_t			len;
35	int			state;
36	TAILQ_ENTRY(lump)	list;
37};
38
39static TAILQ_HEAD(, lump) lumps = TAILQ_HEAD_INITIALIZER(lumps);
40
41static void
42new_lump(off_t start, off_t len, int state)
43{
44	struct lump *lp;
45
46	lp = malloc(sizeof *lp);
47	if (lp == NULL)
48		err(1, "Malloc failed");
49	lp->start = start;
50	lp->len = len;
51	lp->state = state;
52	TAILQ_INSERT_TAIL(&lumps, lp, list);
53}
54
55static struct lump *lp;
56static char *wworklist = NULL;
57static char *rworklist = NULL;
58
59
60#define PRINT_HEADER \
61	printf("%13s %7s %13s %5s %13s %13s %9s\n", \
62		"start", "size", "block-len", "state", "done", "remaining", "% done")
63
64#define PRINT_STATUS(start, i, len, state, d, t) \
65	printf("\r%13jd %7zu %13jd %5d %13jd %13jd %9.5f", \
66		(intmax_t)start, \
67		i,  \
68		(intmax_t)len, \
69		state, \
70		(intmax_t)d, \
71		(intmax_t)(t - d), \
72		100*(double)d/(double)t)
73
74/* Save the worklist if -w was given */
75static void
76save_worklist(void)
77{
78	FILE *file;
79	struct lump *llp;
80
81	if (wworklist != NULL) {
82		(void)fprintf(stderr, "\nSaving worklist ...");
83		fflush(stderr);
84
85		file = fopen(wworklist, "w");
86		if (file == NULL)
87			err(1, "Error opening file %s", wworklist);
88
89		TAILQ_FOREACH(llp, &lumps, list)
90			fprintf(file, "%jd %jd %d\n",
91			    (intmax_t)llp->start, (intmax_t)llp->len,
92			    llp->state);
93		fclose(file);
94		(void)fprintf(stderr, " done.\n");
95	}
96}
97
98/* Read the worklist if -r was given */
99static off_t
100read_worklist(off_t t)
101{
102	off_t s, l, d;
103	int state, lines;
104	FILE *file;
105
106	(void)fprintf(stderr, "Reading worklist ...");
107	fflush(stderr);
108	file = fopen(rworklist, "r");
109	if (file == NULL)
110		err(1, "Error opening file %s", rworklist);
111
112	lines = 0;
113	d = t;
114	for (;;) {
115		++lines;
116		if (3 != fscanf(file, "%jd %jd %d\n", &s, &l, &state)) {
117			if (!feof(file))
118				err(1, "Error parsing file %s at line %d",
119				    rworklist, lines);
120			else
121				break;
122		}
123		new_lump(s, l, state);
124		d -= l;
125	}
126	(void)fprintf(stderr, " done.\n");
127	/*
128	 * Return the number of bytes already read
129	 * (at least not in worklist).
130	 */
131	return (d);
132}
133
134static void
135usage(void)
136{
137	(void)fprintf(stderr, "usage: recoverdisk [-b bigsize] [-r readlist] "
138	    "[-s interval] [-w writelist] source [destination]\n");
139	exit(1);
140}
141
142static void
143sighandler(__unused int sig)
144{
145
146	aborting = 1;
147}
148
149int
150main(int argc, char * const argv[])
151{
152	int ch;
153	int fdr, fdw;
154	off_t t, d, start, len;
155	size_t i, j;
156	int error, state;
157	u_char *buf;
158	u_int sectorsize;
159	u_int stripesize;
160	time_t t1, t2;
161	struct stat sb;
162	u_int n, snapshot = 60;
163
164	while ((ch = getopt(argc, argv, "b:r:w:s:")) != -1) {
165		switch (ch) {
166		case 'b':
167			bigsize = strtoul(optarg, NULL, 0);
168			break;
169		case 'r':
170			rworklist = strdup(optarg);
171			if (rworklist == NULL)
172				err(1, "Cannot allocate enough memory");
173			break;
174		case 's':
175			snapshot = strtoul(optarg, NULL, 0);
176			break;
177		case 'w':
178			wworklist = strdup(optarg);
179			if (wworklist == NULL)
180				err(1, "Cannot allocate enough memory");
181			break;
182		default:
183			usage();
184			/* NOTREACHED */
185		}
186	}
187	argc -= optind;
188	argv += optind;
189
190	if (argc < 1 || argc > 2)
191		usage();
192
193	fdr = open(argv[0], O_RDONLY);
194	if (fdr < 0)
195		err(1, "Cannot open read descriptor %s", argv[0]);
196
197	error = fstat(fdr, &sb);
198	if (error < 0)
199		err(1, "fstat failed");
200	if (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode)) {
201		error = ioctl(fdr, DIOCGSECTORSIZE, &sectorsize);
202		if (error < 0)
203			err(1, "DIOCGSECTORSIZE failed");
204
205		error = ioctl(fdr, DIOCGSTRIPESIZE, &stripesize);
206		if (error == 0 && stripesize > sectorsize)
207			sectorsize = stripesize;
208
209		minsize = sectorsize;
210		bigsize = (bigsize / sectorsize) * sectorsize;
211
212		error = ioctl(fdr, DIOCGMEDIASIZE, &t);
213		if (error < 0)
214			err(1, "DIOCGMEDIASIZE failed");
215	} else {
216		t = sb.st_size;
217	}
218
219	if (bigsize < minsize)
220		bigsize = minsize;
221
222	for (ch = 0; (bigsize >> ch) > minsize; ch++)
223		continue;
224	medsize = bigsize >> (ch / 2);
225	medsize = (medsize / minsize) * minsize;
226
227	fprintf(stderr, "Bigsize = %zu, medsize = %zu, minsize = %zu\n",
228	    bigsize, medsize, minsize);
229
230	buf = malloc(bigsize);
231	if (buf == NULL)
232		err(1, "Cannot allocate %zu bytes buffer", bigsize);
233
234	if (argc > 1) {
235		fdw = open(argv[1], O_WRONLY | O_CREAT, DEFFILEMODE);
236		if (fdw < 0)
237			err(1, "Cannot open write descriptor %s", argv[1]);
238		if (ftruncate(fdw, t) < 0)
239			err(1, "Cannot truncate output %s to %jd bytes",
240			    argv[1], (intmax_t)t);
241	} else
242		fdw = -1;
243
244	if (rworklist != NULL) {
245		d = read_worklist(t);
246	} else {
247		new_lump(0, t, 0);
248		d = 0;
249	}
250	if (wworklist != NULL)
251		signal(SIGINT, sighandler);
252
253	t1 = 0;
254	start = len = i = state = 0;
255	PRINT_HEADER;
256	n = 0;
257	for (;;) {
258		lp = TAILQ_FIRST(&lumps);
259		if (lp == NULL)
260			break;
261		while (lp->len > 0 && !aborting) {
262			/* These are only copied for printing stats */
263			start = lp->start;
264			len = lp->len;
265			state = lp->state;
266
267			i = MIN(lp->len, (off_t)bigsize);
268			if (lp->state == 1)
269				i = MIN(lp->len, (off_t)medsize);
270			if (lp->state > 1)
271				i = MIN(lp->len, (off_t)minsize);
272			time(&t2);
273			if (t1 != t2 || lp->len < (off_t)bigsize) {
274				PRINT_STATUS(start, i, len, state, d, t);
275				t1 = t2;
276				if (++n == snapshot) {
277					save_worklist();
278					n = 0;
279				}
280			}
281			if (i == 0) {
282				errx(1, "BOGUS i %10jd", (intmax_t)i);
283			}
284			fflush(stdout);
285			j = pread(fdr, buf, i, lp->start);
286			if (j == i) {
287				d += i;
288				if (fdw >= 0)
289					j = pwrite(fdw, buf, i, lp->start);
290				else
291					j = i;
292				if (j != i)
293					printf("\nWrite error at %jd/%zu\n",
294					    lp->start, i);
295				lp->start += i;
296				lp->len -= i;
297				continue;
298			}
299			printf("\n%jd %zu failed (%s)\n",
300			    lp->start, i, strerror(errno));
301			if (errno == EINVAL) {
302				printf("read() size too big? Try with -b 131072");
303				aborting = 1;
304			}
305			if (errno == ENXIO)
306				aborting = 1;
307			new_lump(lp->start, i, lp->state + 1);
308			lp->start += i;
309			lp->len -= i;
310		}
311		if (aborting) {
312			save_worklist();
313			return (0);
314		}
315		TAILQ_REMOVE(&lumps, lp, list);
316		free(lp);
317	}
318	PRINT_STATUS(start, i, len, state, d, t);
319	save_worklist();
320	printf("\nCompleted\n");
321	return (0);
322}
323