]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/recoverdisk/recoverdisk.c
Add two options to recoverdisk(1) inspired by a recent
[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         printf("\x1b[2J");
157         verbose = 1;
158         t0 = time(NULL);
159 }
160
161 static void
162 report_header(int eol)
163 {
164         printf("%13s %7s %13s %5s %13s %13s %9s",
165             "start",
166             "size",
167             "block-len",
168             "pass",
169             "done",
170             "remaining",
171             "% done");
172         if (eol)
173                 printf("\x1b[K");
174         putchar('\n');
175 }
176
177 #define REPORTWID 79
178
179 static void
180 report_hline(const char *how)
181 {
182         int j;
183
184         for (j = 0; j < REPORTWID; j++) {
185                 if (how && (j == 4 || j == 29 || j == 54)) {
186                         printf("%s", how);
187                 } else {
188                         printf("\xe2\x94\x80");
189                 }
190         }
191         printf("\x1b[K\n");
192 }
193
194 static off_t hist[REPORTWID];
195 static off_t last_done = -1;
196
197 static void
198 report_histogram(const struct lump *lp)
199 {
200         off_t j, bucket, fp, fe, k, now;
201         double a;
202         struct lump *lp2;
203
204         bucket = tot_size / REPORTWID;
205         if (tot_size > bucket * REPORTWID)
206                 bucket += 1;
207         if (done_size != last_done) {
208                 memset(hist, 0, sizeof hist);
209                 TAILQ_FOREACH(lp2, &lumps, list) {
210                         fp = lp2->start;
211                         fe = lp2->start + lp2->len;
212                         for (j = fp / bucket; fp < fe; j++) {
213                                 k = (j + 1) * bucket;
214                                 if (k > fe)
215                                         k = fe;
216                                 k -= fp;
217                                 hist[j] += k;
218                                 fp += k;
219                         }
220                 }
221                 last_done = done_size;
222         }
223         now = lp->start / bucket;
224         for (j = 0; j < REPORTWID; j++) {
225                 a = round(8 * (double)hist[j] / bucket);
226                 assert (a >= 0 && a < 9);
227                 if (a == 0 && hist[j])
228                         a = 1;
229                 if (j == now)
230                         printf("\x1b[31m");
231                 if (a == 0) {
232                         putchar(' ');
233                 } else {
234                         putchar(0xe2);
235                         putchar(0x96);
236                         putchar(0x80 + (int)a);
237                 }
238                 if (j == now)
239                         printf("\x1b[0m");
240         }
241         putchar('\n');
242 }
243
244 static void
245 report(const struct lump *lp, size_t sz)
246 {
247         struct winsize wsz;
248         int j;
249
250         assert(lp != NULL);
251
252         if (verbose) {
253                 printf("\x1b[H%s\x1b[K\n", input);
254                 report_header(1);
255         } else {
256                 putchar('\r');
257         }
258
259         printf("%13jd %7zu %13jd %5d %13jd %13jd %9.4f",
260             (intmax_t)lp->start,
261             sz,
262             (intmax_t)lp->len,
263             lp->state,
264             (intmax_t)done_size,
265             (intmax_t)(tot_size - done_size),
266             100*(double)done_size/(double)tot_size
267         );
268
269         if (verbose) {
270                 printf("\x1b[K\n");
271                 report_hline(NULL);
272                 report_histogram(lp);
273                 if (TAILQ_EMPTY(&minute)) {
274                         report_hline(NULL);
275                 } else {
276                         report_hline("\xe2\x94\xac");
277                         report_periods();
278                         report_hline("\xe2\x94\xb4");
279                 }
280                 j = ioctl(STDIN_FILENO, TIOCGWINSZ, &wsz);
281                 if (!j)
282                         printf("\x1b[%d;1H", wsz.ws_row);
283         }
284         fflush(stdout);
285 }
286
287 /**********************************************************************/
288
289 static void
290 new_lump(off_t start, off_t len, int state)
291 {
292         struct lump *lp;
293
294         lp = malloc(sizeof *lp);
295         if (lp == NULL)
296                 err(1, "Malloc failed");
297         lp->start = start;
298         lp->len = len;
299         lp->state = state;
300         TAILQ_INSERT_TAIL(&lumps, lp, list);
301 }
302
303 /**********************************************************************
304  * Save the worklist if -w was given
305  */
306
307 static void
308 save_worklist(void)
309 {
310         FILE *file;
311         struct lump *llp;
312         char buf[PATH_MAX];
313
314         if (fdw >= 0 && fdatasync(fdw))
315                 err(1, "Write error, probably disk full");
316
317         if (wworklist != NULL) {
318                 bprintf(buf, "%s.tmp", wworklist);
319                 (void)fprintf(stderr, "\nSaving worklist ...");
320                 (void)fflush(stderr);
321
322                 file = fopen(buf, "w");
323                 if (file == NULL)
324                         err(1, "Error opening file %s", buf);
325
326                 TAILQ_FOREACH(llp, &lumps, list)
327                         fprintf(file, "%jd %jd %d\n",
328                             (intmax_t)llp->start, (intmax_t)llp->len,
329                             llp->state);
330                 (void)fflush(file);
331                 if (ferror(file) || fdatasync(fileno(file)) || fclose(file))
332                         err(1, "Error writing file %s", buf);
333                 if (rename(buf, wworklist))
334                         err(1, "Error renaming %s to %s", buf, wworklist);
335                 (void)fprintf(stderr, " done.\n");
336         }
337 }
338
339 /* Read the worklist if -r was given */
340 static off_t
341 read_worklist(off_t t)
342 {
343         off_t s, l, d;
344         int state, lines;
345         FILE *file;
346
347         (void)fprintf(stderr, "Reading worklist ...");
348         (void)fflush(stderr);
349         file = fopen(rworklist, "r");
350         if (file == NULL)
351                 err(1, "Error opening file %s", rworklist);
352
353         lines = 0;
354         d = t;
355         for (;;) {
356                 ++lines;
357                 if (3 != fscanf(file, "%jd %jd %d\n", &s, &l, &state)) {
358                         if (!feof(file))
359                                 err(1, "Error parsing file %s at line %d",
360                                     rworklist, lines);
361                         else
362                                 break;
363                 }
364                 new_lump(s, l, state);
365                 d -= l;
366         }
367         if (fclose(file))
368                 err(1, "Error closing file %s", rworklist);
369         (void)fprintf(stderr, " done.\n");
370         /*
371          * Return the number of bytes already read
372          * (at least not in worklist).
373          */
374         return (d);
375 }
376
377 /**********************************************************************/
378
379 static void
380 write_buf(int fd, const void *buf, ssize_t len, off_t where)
381 {
382         ssize_t i;
383
384         i = pwrite(fd, buf, len, where);
385         if (i == len)
386                 return;
387
388         printf("\nWrite error at %jd/%zu\n\t%s\n",
389             where, i, strerror(errno));
390         save_worklist();
391         if (write_errors_are_fatal)
392                 exit(3);
393 }
394
395 static void
396 fill_buf(char *buf, ssize_t len, const char *pattern)
397 {
398         ssize_t sz = strlen(pattern);
399         ssize_t i, j;
400
401         for (i = 0; i < len; i += sz) {
402                 j = len - i;
403                 if (j > sz)
404                         j = sz;
405                 memcpy(buf + i, pattern, j);
406         }
407 }
408
409 /**********************************************************************/
410
411 static void
412 usage(void)
413 {
414         (void)fprintf(stderr, "usage: recoverdisk [-b bigsize] [-r readlist] "
415             "[-s interval] [-w writelist] source [destination]\n");
416         /* XXX update */
417         exit(1);
418 }
419
420 static void
421 sighandler(__unused int sig)
422 {
423
424         aborting = 1;
425 }
426
427 int
428 main(int argc, char * const argv[])
429 {
430         int ch;
431         size_t sz, j;
432         int error;
433         char *buf;
434         u_int sectorsize;
435         off_t stripesize;
436         time_t t1, t2;
437         struct stat sb;
438         u_int n, snapshot = 60;
439         static struct lump *lp;
440
441         while ((ch = getopt(argc, argv, "b:r:w:s:u:v")) != -1) {
442                 switch (ch) {
443                 case 'b':
444                         bigsize = strtoul(optarg, NULL, 0);
445                         break;
446                 case 'r':
447                         rworklist = strdup(optarg);
448                         if (rworklist == NULL)
449                                 err(1, "Cannot allocate enough memory");
450                         break;
451                 case 's':
452                         snapshot = strtoul(optarg, NULL, 0);
453                         break;
454                 case 'u':
455                         unreadable_pattern = optarg;
456                         break;
457                 case 'v':
458                         set_verbose();
459                         break;
460                 case 'w':
461                         wworklist = strdup(optarg);
462                         if (wworklist == NULL)
463                                 err(1, "Cannot allocate enough memory");
464                         break;
465                 default:
466                         usage();
467                         /* NOTREACHED */
468                 }
469         }
470         argc -= optind;
471         argv += optind;
472
473         if (argc < 1 || argc > 2)
474                 usage();
475
476         input = argv[0];
477         fdr = open(argv[0], O_RDONLY);
478         if (fdr < 0)
479                 err(1, "Cannot open read descriptor %s", argv[0]);
480
481         error = fstat(fdr, &sb);
482         if (error < 0)
483                 err(1, "fstat failed");
484         if (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode)) {
485                 error = ioctl(fdr, DIOCGSECTORSIZE, &sectorsize);
486                 if (error < 0)
487                         err(1, "DIOCGSECTORSIZE failed");
488
489                 error = ioctl(fdr, DIOCGSTRIPESIZE, &stripesize);
490                 if (error == 0 && stripesize > sectorsize)
491                         sectorsize = stripesize;
492
493                 minsize = sectorsize;
494                 bigsize = rounddown(bigsize, sectorsize);
495
496                 error = ioctl(fdr, DIOCGMEDIASIZE, &tot_size);
497                 if (error < 0)
498                         err(1, "DIOCGMEDIASIZE failed");
499         } else {
500                 tot_size = sb.st_size;
501         }
502
503         if (bigsize < minsize)
504                 bigsize = minsize;
505
506         for (ch = 0; (bigsize >> ch) > minsize; ch++)
507                 continue;
508         medsize = bigsize >> (ch / 2);
509         medsize = rounddown(medsize, minsize);
510
511         fprintf(stderr, "Bigsize = %zu, medsize = %zu, minsize = %zu\n",
512             bigsize, medsize, minsize);
513
514         buf = malloc(bigsize);
515         if (buf == NULL)
516                 err(1, "Cannot allocate %zu bytes buffer", bigsize);
517
518         if (argc > 1) {
519                 fdw = open(argv[1], O_WRONLY | O_CREAT, DEFFILEMODE);
520                 if (fdw < 0)
521                         err(1, "Cannot open write descriptor %s", argv[1]);
522                 if (ftruncate(fdw, tot_size) < 0)
523                         err(1, "Cannot truncate output %s to %jd bytes",
524                             argv[1], (intmax_t)tot_size);
525         } else
526                 fdw = -1;
527
528         if (rworklist != NULL) {
529                 done_size = read_worklist(tot_size);
530         } else {
531                 new_lump(0, tot_size, 0);
532                 done_size = 0;
533         }
534         if (wworklist != NULL)
535                 signal(SIGINT, sighandler);
536
537         t1 = time(NULL);
538         sz = 0;
539         if (!verbose)
540                 report_header(0);
541         n = 0;
542         for (;;) {
543                 lp = TAILQ_FIRST(&lumps);
544                 if (lp == NULL)
545                         break;
546                 while (lp->len > 0) {
547
548                         if (lp->state == 0)
549                                 sz = MIN(lp->len, (off_t)bigsize);
550                         else if (lp->state == 1)
551                                 sz = MIN(lp->len, (off_t)medsize);
552                         else
553                                 sz = MIN(lp->len, (off_t)minsize);
554                         assert(sz != 0);
555
556                         t2 = time(NULL);
557                         if (t1 != t2 || lp->len < (off_t)bigsize) {
558                                 t1 = t2;
559                                 if (++n == snapshot) {
560                                         save_worklist();
561                                         n = 0;
562                                 }
563                                 report(lp, sz);
564                         }
565
566                         j = pread(fdr, buf, sz, lp->start);
567 #if 0
568 if (!(random() & 0xf)) {
569         j = -1;
570         errno = EIO;
571 }
572 #endif
573                         if (j == sz) {
574                                 done_size += sz;
575                                 if (fdw >= 0)
576                                         write_buf(fdw, buf, sz, lp->start);
577                                 lp->start += sz;
578                                 lp->len -= sz;
579                                 if (verbose && lp->state > 2)
580                                         report_good_read(t2, sz);
581                                 continue;
582                         }
583                         error = errno;
584
585                         printf("%jd %zu %d read error (%s)\n",
586                             lp->start, sz, lp->state, strerror(error));
587                         if (verbose)
588                                 report(lp, sz);
589                         if (error == EINVAL) {
590                                 printf("Try with -b 131072 or lower ?\n");
591                                 aborting = 1;
592                                 break;
593                         }
594                         if (error == ENXIO) {
595                                 printf("Input device probably detached...\n");
596                                 aborting = 1;
597                                 break;
598                         }
599                         if (fdw >= 0 && strlen(unreadable_pattern)) {
600                                 fill_buf(buf, sz, unreadable_pattern);
601                                 write_buf(fdw, buf, sz, lp->start);
602                         }
603                         new_lump(lp->start, sz, lp->state + 1);
604                         lp->start += sz;
605                         lp->len -= sz;
606                 }
607                 if (aborting)
608                         save_worklist();
609                 if (aborting || !TAILQ_NEXT(lp, list))
610                         report(lp, sz);
611                 if (aborting)
612                         break;
613                 assert(lp->len == 0);
614                 TAILQ_REMOVE(&lumps, lp, list);
615                 free(lp);
616         }
617         printf("%s", aborting ? "Aborted\n" : "Completed\n");
618         free(buf);
619         return (0);
620 }