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