2 * SPDX-License-Identifier: Beerware
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 * ----------------------------------------------------------------------------
13 #include <sys/param.h>
14 #include <sys/queue.h>
32 /* Safe printf into a fixed-size buffer */
33 #define bprintf(buf, fmt, ...) \
36 ibprintf = snprintf(buf, sizeof buf, fmt, __VA_ARGS__); \
37 assert(ibprintf >= 0 && ibprintf < (int)sizeof buf); \
44 TAILQ_ENTRY(lump) list;
52 TAILQ_ENTRY(period) list;
54 TAILQ_HEAD(period_head, period);
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;
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;
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);
76 /**********************************************************************/
79 report_good_read2(time_t now, size_t bytes, struct period_head *ph, time_t dt)
86 if (pp == NULL || pp->t1 < now) {
87 pp = calloc(sizeof *pp, 1L);
89 pp->t0 = (now / dt) * dt;
90 pp->t1 = (now / dt + 1) * dt;
91 assert(localtime_r(&pp->t0, &tm1) != NULL);
96 assert(strftime(pp->str, sizeof pp->str, fmt, &tm1) != 0);
97 TAILQ_INSERT_HEAD(ph, pp, list);
99 pp->bytes_read += bytes;
103 report_good_read(time_t now, size_t bytes)
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);
113 report_one_period(const char *period, struct period_head *ph)
119 printf("%s \xe2\x94\x82", period);
120 TAILQ_FOREACH(pp, ph, list) {
122 TAILQ_REMOVE(ph, pp, list);
127 printf(" \xe2\x94\x82");
128 printf(" %s %14jd", pp->str, pp->bytes_read);
131 printf(" \xe2\x94\x82");
132 printf(" %5s %14s", "", "");
140 report_one_period("1m ", &minute);
141 report_one_period("15m", &quarter);
142 report_one_period("1h ", &hour);
143 report_one_period("1d ", &day);
146 /**********************************************************************/
154 if (!isatty(STDIN_FILENO) || ioctl(STDIN_FILENO, TIOCGWINSZ, &wsz))
162 report_header(int eol)
164 printf("%13s %7s %13s %5s %13s %13s %9s",
180 report_hline(const char *how)
184 for (j = 0; j < REPORTWID; j++) {
185 if (how && (j == 4 || j == 29 || j == 54)) {
188 printf("\xe2\x94\x80");
194 static off_t hist[REPORTWID];
195 static off_t last_done = -1;
198 report_histogram(const struct lump *lp)
200 off_t j, bucket, fp, fe, k, now;
204 bucket = tot_size / REPORTWID;
205 if (tot_size > bucket * REPORTWID)
207 if (done_size != last_done) {
208 memset(hist, 0, sizeof hist);
209 TAILQ_FOREACH(lp2, &lumps, list) {
211 fe = lp2->start + lp2->len;
212 for (j = fp / bucket; fp < fe; j++) {
213 k = (j + 1) * bucket;
221 last_done = done_size;
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])
236 putchar(0x80 + (int)a);
245 report(const struct lump *lp, size_t sz)
253 printf("\x1b[H%s\x1b[K\n", input);
259 printf("%13jd %7zu %13jd %5d %13jd %13jd %9.4f",
265 (intmax_t)(tot_size - done_size),
266 100*(double)done_size/(double)tot_size
272 report_histogram(lp);
273 if (TAILQ_EMPTY(&minute)) {
276 report_hline("\xe2\x94\xac");
278 report_hline("\xe2\x94\xb4");
280 j = ioctl(STDIN_FILENO, TIOCGWINSZ, &wsz);
282 printf("\x1b[%d;1H", wsz.ws_row);
287 /**********************************************************************/
290 new_lump(off_t start, off_t len, int state)
294 lp = malloc(sizeof *lp);
296 err(1, "Malloc failed");
300 TAILQ_INSERT_TAIL(&lumps, lp, list);
303 /**********************************************************************
304 * Save the worklist if -w was given
314 if (fdw >= 0 && fdatasync(fdw))
315 err(1, "Write error, probably disk full");
317 if (wworklist != NULL) {
318 bprintf(buf, "%s.tmp", wworklist);
319 (void)fprintf(stderr, "\nSaving worklist ...");
320 (void)fflush(stderr);
322 file = fopen(buf, "w");
324 err(1, "Error opening file %s", buf);
326 TAILQ_FOREACH(llp, &lumps, list)
327 fprintf(file, "%jd %jd %d\n",
328 (intmax_t)llp->start, (intmax_t)llp->len,
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");
339 /* Read the worklist if -r was given */
341 read_worklist(off_t t)
347 (void)fprintf(stderr, "Reading worklist ...");
348 (void)fflush(stderr);
349 file = fopen(rworklist, "r");
351 err(1, "Error opening file %s", rworklist);
357 if (3 != fscanf(file, "%jd %jd %d\n", &s, &l, &state)) {
359 err(1, "Error parsing file %s at line %d",
364 new_lump(s, l, state);
368 err(1, "Error closing file %s", rworklist);
369 (void)fprintf(stderr, " done.\n");
371 * Return the number of bytes already read
372 * (at least not in worklist).
377 /**********************************************************************/
380 write_buf(int fd, const void *buf, ssize_t len, off_t where)
384 i = pwrite(fd, buf, len, where);
388 printf("\nWrite error at %jd/%zu\n\t%s\n",
389 where, i, strerror(errno));
391 if (write_errors_are_fatal)
396 fill_buf(char *buf, ssize_t len, const char *pattern)
398 ssize_t sz = strlen(pattern);
401 for (i = 0; i < len; i += sz) {
405 memcpy(buf + i, pattern, j);
409 /**********************************************************************/
414 (void)fprintf(stderr, "usage: recoverdisk [-b bigsize] [-r readlist] "
415 "[-s interval] [-w writelist] source [destination]\n");
421 sighandler(__unused int sig)
428 main(int argc, char * const argv[])
438 u_int n, snapshot = 60;
439 static struct lump *lp;
441 while ((ch = getopt(argc, argv, "b:r:w:s:u:v")) != -1) {
444 bigsize = strtoul(optarg, NULL, 0);
447 rworklist = strdup(optarg);
448 if (rworklist == NULL)
449 err(1, "Cannot allocate enough memory");
452 snapshot = strtoul(optarg, NULL, 0);
455 unreadable_pattern = optarg;
461 wworklist = strdup(optarg);
462 if (wworklist == NULL)
463 err(1, "Cannot allocate enough memory");
473 if (argc < 1 || argc > 2)
477 fdr = open(argv[0], O_RDONLY);
479 err(1, "Cannot open read descriptor %s", argv[0]);
481 error = fstat(fdr, &sb);
483 err(1, "fstat failed");
484 if (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode)) {
485 error = ioctl(fdr, DIOCGSECTORSIZE, §orsize);
487 err(1, "DIOCGSECTORSIZE failed");
489 error = ioctl(fdr, DIOCGSTRIPESIZE, &stripesize);
490 if (error == 0 && stripesize > sectorsize)
491 sectorsize = stripesize;
493 minsize = sectorsize;
494 bigsize = rounddown(bigsize, sectorsize);
496 error = ioctl(fdr, DIOCGMEDIASIZE, &tot_size);
498 err(1, "DIOCGMEDIASIZE failed");
500 tot_size = sb.st_size;
503 if (bigsize < minsize)
506 for (ch = 0; (bigsize >> ch) > minsize; ch++)
508 medsize = bigsize >> (ch / 2);
509 medsize = rounddown(medsize, minsize);
511 fprintf(stderr, "Bigsize = %zu, medsize = %zu, minsize = %zu\n",
512 bigsize, medsize, minsize);
514 buf = malloc(bigsize);
516 err(1, "Cannot allocate %zu bytes buffer", bigsize);
519 fdw = open(argv[1], O_WRONLY | O_CREAT, DEFFILEMODE);
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);
528 if (rworklist != NULL) {
529 done_size = read_worklist(tot_size);
531 new_lump(0, tot_size, 0);
534 if (wworklist != NULL)
535 signal(SIGINT, sighandler);
543 lp = TAILQ_FIRST(&lumps);
546 while (lp->len > 0) {
549 sz = MIN(lp->len, (off_t)bigsize);
550 else if (lp->state == 1)
551 sz = MIN(lp->len, (off_t)medsize);
553 sz = MIN(lp->len, (off_t)minsize);
557 if (t1 != t2 || lp->len < (off_t)bigsize) {
559 if (++n == snapshot) {
566 j = pread(fdr, buf, sz, lp->start);
568 if (!(random() & 0xf)) {
576 write_buf(fdw, buf, sz, lp->start);
579 if (verbose && lp->state > 2)
580 report_good_read(t2, sz);
585 printf("%jd %zu %d read error (%s)\n",
586 lp->start, sz, lp->state, strerror(error));
589 if (error == EINVAL) {
590 printf("Try with -b 131072 or lower ?\n");
594 if (error == ENXIO) {
595 printf("Input device probably detached...\n");
599 if (fdw >= 0 && strlen(unreadable_pattern)) {
600 fill_buf(buf, sz, unreadable_pattern);
601 write_buf(fdw, buf, sz, lp->start);
603 new_lump(lp->start, sz, lp->state + 1);
609 if (aborting || !TAILQ_NEXT(lp, list))
613 assert(lp->len == 0);
614 TAILQ_REMOVE(&lumps, lp, list);
617 printf("%s", aborting ? "Aborted\n" : "Completed\n");