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