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