xref: /freebsd/sbin/recoverdisk/recoverdisk.c (revision 076ad2f8)
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$
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 
27 static volatile sig_atomic_t aborting = 0;
28 static size_t bigsize = 1024 * 1024;
29 static size_t medsize;
30 static size_t minsize = 512;
31 
32 struct lump {
33 	off_t			start;
34 	off_t			len;
35 	int			state;
36 	TAILQ_ENTRY(lump)	list;
37 };
38 
39 static TAILQ_HEAD(, lump) lumps = TAILQ_HEAD_INITIALIZER(lumps);
40 
41 static void
42 new_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 
55 static struct lump *lp;
56 static char *wworklist = NULL;
57 static 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 */
75 static void
76 save_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 */
99 static off_t
100 read_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 
134 static void
135 usage(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 
142 static void
143 sighandler(__unused int sig)
144 {
145 
146 	aborting = 1;
147 }
148 
149 int
150 main(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 	off_t 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 = rounddown(bigsize, 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 = rounddown(medsize, 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