]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/recoverdisk/recoverdisk.c
THIS BRANCH IS OBSOLETE, PLEASE READ:
[FreeBSD/FreeBSD.git] / sbin / recoverdisk / recoverdisk.c
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 <assert.h>
19 #include <err.h>
20 #include <errno.h>
21 #include <math.h>
22 #include <fcntl.h>
23 #include <signal.h>
24 #include <stdint.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <termios.h>
29 #include <time.h>
30 #include <unistd.h>
31
32 /* Safe printf into a fixed-size buffer */
33 #define bprintf(buf, fmt, ...)                                          \
34         do {                                                            \
35                 int ibprintf;                                           \
36                 ibprintf = snprintf(buf, sizeof buf, fmt, __VA_ARGS__); \
37                 assert(ibprintf >= 0 && ibprintf < (int)sizeof buf);    \
38         } while (0)
39
40 struct lump {
41         off_t                   start;
42         off_t                   len;
43         int                     state;
44         TAILQ_ENTRY(lump)       list;
45 };
46
47 struct period {
48         time_t                  t0;
49         time_t                  t1;
50         char                    str[20];
51         off_t                   bytes_read;
52         TAILQ_ENTRY(period)     list;
53 };
54 TAILQ_HEAD(period_head, period);
55
56 static volatile sig_atomic_t aborting = 0;
57 static int verbose = 0;
58 static size_t bigsize = 1024 * 1024;
59 static size_t medsize;
60 static size_t minsize = 512;
61 static off_t tot_size;
62 static off_t done_size;
63 static char *input;
64 static char *wworklist = NULL;
65 static char *rworklist = NULL;
66 static const char *unreadable_pattern = "_UNREAD_";
67 static const int write_errors_are_fatal = 1;
68 static int fdr, fdw;
69
70 static TAILQ_HEAD(, lump) lumps = TAILQ_HEAD_INITIALIZER(lumps);
71 static struct period_head minute = TAILQ_HEAD_INITIALIZER(minute);
72 static struct period_head quarter = TAILQ_HEAD_INITIALIZER(quarter);
73 static struct period_head hour = TAILQ_HEAD_INITIALIZER(quarter);
74 static struct period_head day = TAILQ_HEAD_INITIALIZER(quarter);
75
76 /**********************************************************************/
77
78 static void
79 report_good_read2(time_t now, size_t bytes, struct period_head *ph, time_t dt)
80 {
81         struct period *pp;
82         const char *fmt;
83         struct tm tm1;
84
85         pp = TAILQ_FIRST(ph);
86         if (pp == NULL || pp->t1 < now) {
87                 pp = calloc(sizeof *pp, 1L);
88                 assert(pp != NULL);
89                 pp->t0 = (now / dt) * dt;
90                 pp->t1 = (now / dt + 1) * dt;
91                 assert(localtime_r(&pp->t0, &tm1) != NULL);
92                 if (dt < 86400)
93                         fmt = "%H:%M";
94                 else
95                         fmt = "%d%b";
96                 assert(strftime(pp->str, sizeof pp->str, fmt, &tm1) != 0);
97                 TAILQ_INSERT_HEAD(ph, pp, list);
98         }
99         pp->bytes_read += bytes;
100 }
101
102 static void
103 report_good_read(time_t now, size_t bytes)
104 {
105
106         report_good_read2(now, bytes, &minute, 60L);
107         report_good_read2(now, bytes, &quarter, 900L);
108         report_good_read2(now, bytes, &hour, 3600L);
109         report_good_read2(now, bytes, &day, 86400L);
110 }
111
112 static void
113 report_one_period(const char *period, struct period_head *ph)
114 {
115         struct period *pp;
116         int n;
117
118         n = 0;
119         printf("%s \xe2\x94\x82", period);
120         TAILQ_FOREACH(pp, ph, list) {
121                 if (n == 3) {
122                         TAILQ_REMOVE(ph, pp, list);
123                         free(pp);
124                         break;
125                 }
126                 if (n++)
127                         printf("  \xe2\x94\x82");
128                 printf("  %s %14jd", pp->str, pp->bytes_read);
129         }
130         for (; n < 3; n++) {
131                 printf("  \xe2\x94\x82");
132                 printf("  %5s %14s", "", "");
133         }
134         printf("\x1b[K\n");
135 }
136
137 static void
138 report_periods(void)
139 {
140         report_one_period("1m ", &minute);
141         report_one_period("15m", &quarter);
142         report_one_period("1h ", &hour);
143         report_one_period("1d ", &day);
144 }
145
146 /**********************************************************************/
147
148 static void
149 set_verbose(void)
150 {
151         struct winsize wsz;
152         time_t t0;
153
154         if (!isatty(STDIN_FILENO) || ioctl(STDIN_FILENO, TIOCGWINSZ, &wsz))
155                 return;
156         verbose = 1;
157         t0 = time(NULL);
158 }
159
160 static void
161 report_header(int eol)
162 {
163         printf("%13s %7s %13s %5s %13s %13s %9s",
164             "start",
165             "size",
166             "block-len",
167             "pass",
168             "done",
169             "remaining",
170             "% done");
171         if (eol)
172                 printf("\x1b[K");
173         putchar('\n');
174 }
175
176 #define REPORTWID 79
177
178 static void
179 report_hline(const char *how)
180 {
181         int j;
182
183         for (j = 0; j < REPORTWID; j++) {
184                 if (how && (j == 4 || j == 29 || j == 54)) {
185                         printf("%s", how);
186                 } else {
187                         printf("\xe2\x94\x80");
188                 }
189         }
190         printf("\x1b[K\n");
191 }
192
193 static off_t hist[REPORTWID];
194 static off_t last_done = -1;
195
196 static void
197 report_histogram(const struct lump *lp)
198 {
199         off_t j, bucket, fp, fe, k, now;
200         double a;
201         struct lump *lp2;
202
203         bucket = tot_size / REPORTWID;
204         if (tot_size > bucket * REPORTWID)
205                 bucket += 1;
206         if (done_size != last_done) {
207                 memset(hist, 0, sizeof hist);
208                 TAILQ_FOREACH(lp2, &lumps, list) {
209                         fp = lp2->start;
210                         fe = lp2->start + lp2->len;
211                         for (j = fp / bucket; fp < fe; j++) {
212                                 k = (j + 1) * bucket;
213                                 if (k > fe)
214                                         k = fe;
215                                 k -= fp;
216                                 hist[j] += k;
217                                 fp += k;
218                         }
219                 }
220                 last_done = done_size;
221         }
222         now = lp->start / bucket;
223         for (j = 0; j < REPORTWID; j++) {
224                 a = round(8 * (double)hist[j] / bucket);
225                 assert (a >= 0 && a < 9);
226                 if (a == 0 && hist[j])
227                         a = 1;
228                 if (j == now)
229                         printf("\x1b[31m");
230                 if (a == 0) {
231                         putchar(' ');
232                 } else {
233                         putchar(0xe2);
234                         putchar(0x96);
235                         putchar(0x80 + (int)a);
236                 }
237                 if (j == now)
238                         printf("\x1b[0m");
239         }
240         putchar('\n');
241 }
242
243 static void
244 report(const struct lump *lp, size_t sz)
245 {
246         struct winsize wsz;
247         int j;
248
249         assert(lp != NULL);
250
251         if (verbose) {
252                 printf("\x1b[H%s\x1b[K\n", input);
253                 report_header(1);
254         } else {
255                 putchar('\r');
256         }
257
258         printf("%13jd %7zu %13jd %5d %13jd %13jd %9.4f",
259             (intmax_t)lp->start,
260             sz,
261             (intmax_t)lp->len,
262             lp->state,
263             (intmax_t)done_size,
264             (intmax_t)(tot_size - done_size),
265             100*(double)done_size/(double)tot_size
266         );
267
268         if (verbose) {
269                 printf("\x1b[K\n");
270                 report_hline(NULL);
271                 report_histogram(lp);
272                 if (TAILQ_EMPTY(&minute)) {
273                         report_hline(NULL);
274                 } else {
275                         report_hline("\xe2\x94\xac");
276                         report_periods();
277                         report_hline("\xe2\x94\xb4");
278                 }
279                 j = ioctl(STDIN_FILENO, TIOCGWINSZ, &wsz);
280                 if (!j)
281                         printf("\x1b[%d;1H", wsz.ws_row);
282         }
283         fflush(stdout);
284 }
285
286 /**********************************************************************/
287
288 static void
289 new_lump(off_t start, off_t len, int state)
290 {
291         struct lump *lp;
292
293         lp = malloc(sizeof *lp);
294         if (lp == NULL)
295                 err(1, "Malloc failed");
296         lp->start = start;
297         lp->len = len;
298         lp->state = state;
299         TAILQ_INSERT_TAIL(&lumps, lp, list);
300 }
301
302 /**********************************************************************
303  * Save the worklist if -w was given
304  */
305
306 static void
307 save_worklist(void)
308 {
309         FILE *file;
310         struct lump *llp;
311         char buf[PATH_MAX];
312
313         if (fdw >= 0 && fdatasync(fdw))
314                 err(1, "Write error, probably disk full");
315
316         if (wworklist != NULL) {
317                 bprintf(buf, "%s.tmp", wworklist);
318                 (void)fprintf(stderr, "\nSaving worklist ...");
319                 (void)fflush(stderr);
320
321                 file = fopen(buf, "w");
322                 if (file == NULL)
323                         err(1, "Error opening file %s", buf);
324
325                 TAILQ_FOREACH(llp, &lumps, list)
326                         fprintf(file, "%jd %jd %d\n",
327                             (intmax_t)llp->start, (intmax_t)llp->len,
328                             llp->state);
329                 (void)fflush(file);
330                 if (ferror(file) || fdatasync(fileno(file)) || fclose(file))
331                         err(1, "Error writing file %s", buf);
332                 if (rename(buf, wworklist))
333                         err(1, "Error renaming %s to %s", buf, wworklist);
334                 (void)fprintf(stderr, " done.\n");
335         }
336 }
337
338 /* Read the worklist if -r was given */
339 static off_t
340 read_worklist(off_t t)
341 {
342         off_t s, l, d;
343         int state, lines;
344         FILE *file;
345
346         (void)fprintf(stderr, "Reading worklist ...");
347         (void)fflush(stderr);
348         file = fopen(rworklist, "r");
349         if (file == NULL)
350                 err(1, "Error opening file %s", rworklist);
351
352         lines = 0;
353         d = t;
354         for (;;) {
355                 ++lines;
356                 if (3 != fscanf(file, "%jd %jd %d\n", &s, &l, &state)) {
357                         if (!feof(file))
358                                 err(1, "Error parsing file %s at line %d",
359                                     rworklist, lines);
360                         else
361                                 break;
362                 }
363                 new_lump(s, l, state);
364                 d -= l;
365         }
366         if (fclose(file))
367                 err(1, "Error closing file %s", rworklist);
368         (void)fprintf(stderr, " done.\n");
369         /*
370          * Return the number of bytes already read
371          * (at least not in worklist).
372          */
373         return (d);
374 }
375
376 /**********************************************************************/
377
378 static void
379 write_buf(int fd, const void *buf, ssize_t len, off_t where)
380 {
381         ssize_t i;
382
383         i = pwrite(fd, buf, len, where);
384         if (i == len)
385                 return;
386
387         printf("\nWrite error at %jd/%zu\n\t%s\n",
388             where, i, strerror(errno));
389         save_worklist();
390         if (write_errors_are_fatal)
391                 exit(3);
392 }
393
394 static void
395 fill_buf(char *buf, ssize_t len, const char *pattern)
396 {
397         ssize_t sz = strlen(pattern);
398         ssize_t i, j;
399
400         for (i = 0; i < len; i += sz) {
401                 j = len - i;
402                 if (j > sz)
403                         j = sz;
404                 memcpy(buf + i, pattern, j);
405         }
406 }
407
408 /**********************************************************************/
409
410 static void
411 usage(void)
412 {
413         (void)fprintf(stderr, "usage: recoverdisk [-b bigsize] [-r readlist] "
414             "[-s interval] [-w writelist] source [destination]\n");
415         /* XXX update */
416         exit(1);
417 }
418
419 static void
420 sighandler(__unused int sig)
421 {
422
423         aborting = 1;
424 }
425
426 int
427 main(int argc, char * const argv[])
428 {
429         int ch;
430         size_t sz, j;
431         int error;
432         char *buf;
433         u_int sectorsize;
434         off_t stripesize;
435         time_t t1, t2;
436         struct stat sb;
437         u_int n, snapshot = 60;
438         static struct lump *lp;
439
440         while ((ch = getopt(argc, argv, "b:r:w:s:u:v")) != -1) {
441                 switch (ch) {
442                 case 'b':
443                         bigsize = strtoul(optarg, NULL, 0);
444                         break;
445                 case 'r':
446                         rworklist = strdup(optarg);
447                         if (rworklist == NULL)
448                                 err(1, "Cannot allocate enough memory");
449                         break;
450                 case 's':
451                         snapshot = strtoul(optarg, NULL, 0);
452                         break;
453                 case 'u':
454                         unreadable_pattern = optarg;
455                         break;
456                 case 'v':
457                         set_verbose();
458                         break;
459                 case 'w':
460                         wworklist = strdup(optarg);
461                         if (wworklist == NULL)
462                                 err(1, "Cannot allocate enough memory");
463                         break;
464                 default:
465                         usage();
466                         /* NOTREACHED */
467                 }
468         }
469         argc -= optind;
470         argv += optind;
471
472         if (argc < 1 || argc > 2)
473                 usage();
474
475         input = argv[0];
476         fdr = open(argv[0], O_RDONLY);
477         if (fdr < 0)
478                 err(1, "Cannot open read descriptor %s", argv[0]);
479
480         error = fstat(fdr, &sb);
481         if (error < 0)
482                 err(1, "fstat failed");
483         if (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode)) {
484                 error = ioctl(fdr, DIOCGSECTORSIZE, &sectorsize);
485                 if (error < 0)
486                         err(1, "DIOCGSECTORSIZE failed");
487
488                 error = ioctl(fdr, DIOCGSTRIPESIZE, &stripesize);
489                 if (error == 0 && stripesize > sectorsize)
490                         sectorsize = stripesize;
491
492                 minsize = sectorsize;
493                 bigsize = rounddown(bigsize, sectorsize);
494
495                 error = ioctl(fdr, DIOCGMEDIASIZE, &tot_size);
496                 if (error < 0)
497                         err(1, "DIOCGMEDIASIZE failed");
498         } else {
499                 tot_size = sb.st_size;
500         }
501
502         if (bigsize < minsize)
503                 bigsize = minsize;
504
505         for (ch = 0; (bigsize >> ch) > minsize; ch++)
506                 continue;
507         medsize = bigsize >> (ch / 2);
508         medsize = rounddown(medsize, minsize);
509
510         fprintf(stderr, "Bigsize = %zu, medsize = %zu, minsize = %zu\n",
511             bigsize, medsize, minsize);
512
513         buf = malloc(bigsize);
514         if (buf == NULL)
515                 err(1, "Cannot allocate %zu bytes buffer", bigsize);
516
517         if (argc > 1) {
518                 fdw = open(argv[1], O_WRONLY | O_CREAT, DEFFILEMODE);
519                 if (fdw < 0)
520                         err(1, "Cannot open write descriptor %s", argv[1]);
521                 if (ftruncate(fdw, tot_size) < 0)
522                         err(1, "Cannot truncate output %s to %jd bytes",
523                             argv[1], (intmax_t)tot_size);
524         } else
525                 fdw = -1;
526
527         if (rworklist != NULL) {
528                 done_size = read_worklist(tot_size);
529         } else {
530                 new_lump(0, tot_size, 0);
531                 done_size = 0;
532         }
533         if (wworklist != NULL)
534                 signal(SIGINT, sighandler);
535
536         t1 = time(NULL);
537         sz = 0;
538         if (!verbose)
539                 report_header(0);
540         else
541                 printf("\x1b[2J");
542         n = 0;
543         for (;;) {
544                 lp = TAILQ_FIRST(&lumps);
545                 if (lp == NULL)
546                         break;
547                 while (lp->len > 0) {
548
549                         if (lp->state == 0)
550                                 sz = MIN(lp->len, (off_t)bigsize);
551                         else if (lp->state == 1)
552                                 sz = MIN(lp->len, (off_t)medsize);
553                         else
554                                 sz = MIN(lp->len, (off_t)minsize);
555                         assert(sz != 0);
556
557                         t2 = time(NULL);
558                         if (t1 != t2 || lp->len < (off_t)bigsize) {
559                                 t1 = t2;
560                                 if (++n == snapshot) {
561                                         save_worklist();
562                                         n = 0;
563                                 }
564                                 report(lp, sz);
565                         }
566
567                         j = pread(fdr, buf, sz, lp->start);
568 #if 0
569 if (!(random() & 0xf)) {
570         j = -1;
571         errno = EIO;
572 }
573 #endif
574                         if (j == sz) {
575                                 done_size += sz;
576                                 if (fdw >= 0)
577                                         write_buf(fdw, buf, sz, lp->start);
578                                 lp->start += sz;
579                                 lp->len -= sz;
580                                 if (verbose && lp->state > 2)
581                                         report_good_read(t2, sz);
582                                 continue;
583                         }
584                         error = errno;
585
586                         printf("%jd %zu %d read error (%s)\n",
587                             lp->start, sz, lp->state, strerror(error));
588                         if (verbose)
589                                 report(lp, sz);
590                         if (error == EINVAL) {
591                                 printf("Try with -b 131072 or lower ?\n");
592                                 aborting = 1;
593                                 break;
594                         }
595                         if (error == ENXIO) {
596                                 printf("Input device probably detached...\n");
597                                 aborting = 1;
598                                 break;
599                         }
600                         if (fdw >= 0 && strlen(unreadable_pattern)) {
601                                 fill_buf(buf, sz, unreadable_pattern);
602                                 write_buf(fdw, buf, sz, lp->start);
603                         }
604                         new_lump(lp->start, sz, lp->state + 1);
605                         lp->start += sz;
606                         lp->len -= sz;
607                 }
608                 if (aborting)
609                         save_worklist();
610                 if (aborting || !TAILQ_NEXT(lp, list))
611                         report(lp, sz);
612                 if (aborting)
613                         break;
614                 assert(lp->len == 0);
615                 TAILQ_REMOVE(&lumps, lp, list);
616                 free(lp);
617         }
618         printf("%s", aborting ? "Aborted\n" : "Completed\n");
619         free(buf);
620         return (0);
621 }