xref: /freebsd/sbin/recoverdisk/recoverdisk.c (revision 2b743a9e9ddc6736208dc8ca1ce06ce64ad20a19)
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 volatile sig_atomic_t aborting = 0;
28 static size_t bigsize = 1024 * 1024;
29 static size_t medsize = 64 * 1024;
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 /* Save the worklist if -w was given */
60 static void
61 save_worklist(void)
62 {
63 	FILE *file;
64 
65 	if (wworklist != NULL) {
66 		(void)fprintf(stderr, "\nSaving worklist ...");
67 		fflush(stderr);
68 
69 		file = fopen(wworklist, "w");
70 		if (file == NULL)
71 			err(1, "Error opening file %s", wworklist);
72 
73 		for (;;) {
74 			lp = TAILQ_FIRST(&lumps);
75 			if (lp == NULL)
76 				break;
77 			fprintf(file, "%jd %jd %d\n",
78 			    (intmax_t)lp->start, (intmax_t)lp->len, lp->state);
79 			TAILQ_REMOVE(&lumps, lp, list);
80 		}
81 		(void)fprintf(stderr, " done.\n");
82 	}
83 }
84 
85 /* Read the worklist if -r was given */
86 static off_t
87 read_worklist(off_t t)
88 {
89 	off_t s, l, d;
90 	int state, lines;
91 	FILE *file;
92 
93 	(void)fprintf(stderr, "Reading worklist ...");
94 	fflush(stderr);
95 	file = fopen(rworklist, "r");
96 	if (file == NULL)
97 		err(1, "Error opening file %s", rworklist);
98 
99 	lines = 0;
100 	d = t;
101 	for (;;) {
102 		++lines;
103 		if (3 != fscanf(file, "%jd %jd %d\n", &s, &l, &state)) {
104 			if (!feof(file))
105 				err(1, "Error parsing file %s at line %d",
106 				    rworklist, lines);
107 			else
108 				break;
109 		}
110 		new_lump(s, l, state);
111 		d -= l;
112 	}
113 	(void)fprintf(stderr, " done.\n");
114 	/*
115 	 * Return the number of bytes already read
116 	 * (at least not in worklist).
117 	 */
118 	return (d);
119 }
120 
121 static void
122 usage(void)
123 {
124 	(void)fprintf(stderr,
125     "usage: recoverdisk [-r worklist] [-w worklist] source-drive [destination]\n");
126 	exit(1);
127 }
128 
129 static void
130 sighandler(__unused int sig)
131 {
132 
133 	aborting = 1;
134 }
135 
136 int
137 main(int argc, char * const argv[])
138 {
139 	int ch;
140 	int fdr, fdw;
141 	off_t t, d;
142 	size_t i, j;
143 	int error, flags;
144 	u_char *buf;
145 	u_int sectorsize;
146 	time_t t1, t2;
147 	struct stat sb;
148 
149 	while ((ch = getopt(argc, argv, "r:w:")) != -1) {
150 		switch (ch) {
151 		case 'r':
152 			rworklist = strdup(optarg);
153 			if (rworklist == NULL)
154 				err(1, "Cannot allocate enough memory");
155 			break;
156 		case 'w':
157 			wworklist = strdup(optarg);
158 			if (wworklist == NULL)
159 				err(1, "Cannot allocate enough memory");
160 			break;
161 		default:
162 			usage();
163 			/* NOTREACHED */
164 		}
165 	}
166 	argc -= optind;
167 	argv += optind;
168 
169 	if (argc < 1 || argc > 2)
170 		usage();
171 
172 	fdr = open(argv[0], O_RDONLY);
173 	if (fdr < 0)
174 		err(1, "Cannot open read descriptor %s", argv[0]);
175 
176 	error = fstat(fdr, &sb);
177 	if (error < 0)
178 		err(1, "fstat failed");
179 	flags = O_WRONLY;
180 	if (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode)) {
181 		error = ioctl(fdr, DIOCGSECTORSIZE, &sectorsize);
182 		if (error < 0)
183 			err(1, "DIOCGSECTORSIZE failed");
184 
185 		/*
186 		 * Make medsize roughly 64kB, depending on native sector
187 		 * size. bigsize has to be a multiple of medsize.
188 		 * For media with 2352 sectors, this will
189 		 * result in 2352, 63504, and 1016064 bytes.
190 		 */
191 		minsize = sectorsize;
192 		medsize = (medsize / sectorsize) * sectorsize;
193 		bigsize = medsize * 16;
194 
195 		error = ioctl(fdr, DIOCGMEDIASIZE, &t);
196 		if (error < 0)
197 			err(1, "DIOCGMEDIASIZE failed");
198 	} else {
199 		t = sb.st_size;
200 		flags |= O_CREAT | O_TRUNC;
201 	}
202 
203 	buf = malloc(bigsize);
204 	if (buf == NULL)
205 		err(1, "Cannot allocate %jd bytes buffer", (intmax_t)bigsize);
206 
207 	if (argc > 1) {
208 		fdw = open(argv[1], flags, DEFFILEMODE);
209 		if (fdw < 0)
210 			err(1, "Cannot open write descriptor %s", argv[1]);
211 	} else
212 		fdw = -1;
213 
214 	if (rworklist != NULL) {
215 		d = read_worklist(t);
216 	} else {
217 		new_lump(0, t, 0);
218 		d = 0;
219 	}
220 	if (wworklist != NULL)
221 		signal(SIGINT, sighandler);
222 
223 	t1 = 0;
224 	printf("%13s %7s %13s %5s %13s %13s %9s\n",
225 	    "start", "size", "len", "state", "done", "remaining", "% done");
226 	for (;;) {
227 		lp = TAILQ_FIRST(&lumps);
228 		if (lp == NULL)
229 			break;
230 		while (lp->len > 0 && !aborting) {
231 			i = MIN(lp->len, (off_t)bigsize);
232 			if (lp->state == 1)
233 				i = MIN(lp->len, (off_t)medsize);
234 			if (lp->state > 1)
235 				i = MIN(lp->len, (off_t)minsize);
236 			time(&t2);
237 			if (t1 != t2 || lp->len < (off_t)bigsize) {
238 				printf("\r%13jd %7zu %13jd %5d %13jd %13jd %.7f",
239 				    (intmax_t)lp->start,
240 				    i,
241 				    (intmax_t)lp->len,
242 				    lp->state,
243 				    (intmax_t)d,
244 				    (intmax_t)(t - d),
245 				    (double)d/(double)t);
246 				t1 = t2;
247 			}
248 			if (i == 0) {
249 				errx(1, "BOGUS i %10jd", (intmax_t)i);
250 			}
251 			fflush(stdout);
252 			j = pread(fdr, buf, i, lp->start);
253 			if (j == i) {
254 				d += i;
255 				if (fdw >= 0)
256 					j = pwrite(fdw, buf, i, lp->start);
257 				else
258 					j = i;
259 				if (j != i)
260 					printf("\nWrite error at %jd/%zu\n",
261 					    lp->start, i);
262 				lp->start += i;
263 				lp->len -= i;
264 				continue;
265 			}
266 			printf("\n%jd %zu failed %d\n", lp->start, i, errno);
267 			new_lump(lp->start, i, lp->state + 1);
268 			lp->start += i;
269 			lp->len -= i;
270 		}
271 		if (aborting) {
272 			save_worklist();
273 			return (0);
274 		}
275 		TAILQ_REMOVE(&lumps, lp, list);
276 		free(lp);
277 	}
278 	printf("\nCompleted\n");
279 	return (0);
280 }
281