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, §orsize); 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 } 620