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