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 <err.h> 19 #include <errno.h> 20 #include <fcntl.h> 21 #include <signal.h> 22 #include <stdint.h> 23 #include <stdio.h> 24 #include <stdlib.h> 25 #include <string.h> 26 #include <time.h> 27 #include <unistd.h> 28 29 static volatile sig_atomic_t aborting = 0; 30 static size_t bigsize = 1024 * 1024; 31 static size_t medsize; 32 static size_t minsize = 512; 33 34 struct lump { 35 off_t start; 36 off_t len; 37 int state; 38 TAILQ_ENTRY(lump) list; 39 }; 40 41 static TAILQ_HEAD(, lump) lumps = TAILQ_HEAD_INITIALIZER(lumps); 42 43 static void 44 new_lump(off_t start, off_t len, int state) 45 { 46 struct lump *lp; 47 48 lp = malloc(sizeof *lp); 49 if (lp == NULL) 50 err(1, "Malloc failed"); 51 lp->start = start; 52 lp->len = len; 53 lp->state = state; 54 TAILQ_INSERT_TAIL(&lumps, lp, list); 55 } 56 57 static struct lump *lp; 58 static char *wworklist = NULL; 59 static char *rworklist = NULL; 60 61 62 #define PRINT_HEADER \ 63 printf("%13s %7s %13s %5s %13s %13s %9s\n", \ 64 "start", "size", "block-len", "state", "done", "remaining", "% done") 65 66 #define PRINT_STATUS(start, i, len, state, d, t) \ 67 printf("\r%13jd %7zu %13jd %5d %13jd %13jd %9.5f", \ 68 (intmax_t)start, \ 69 i, \ 70 (intmax_t)len, \ 71 state, \ 72 (intmax_t)d, \ 73 (intmax_t)(t - d), \ 74 100*(double)d/(double)t) 75 76 /* Save the worklist if -w was given */ 77 static void 78 save_worklist(void) 79 { 80 FILE *file; 81 struct lump *llp; 82 83 if (wworklist != NULL) { 84 (void)fprintf(stderr, "\nSaving worklist ..."); 85 fflush(stderr); 86 87 file = fopen(wworklist, "w"); 88 if (file == NULL) 89 err(1, "Error opening file %s", wworklist); 90 91 TAILQ_FOREACH(llp, &lumps, list) 92 fprintf(file, "%jd %jd %d\n", 93 (intmax_t)llp->start, (intmax_t)llp->len, 94 llp->state); 95 fclose(file); 96 (void)fprintf(stderr, " done.\n"); 97 } 98 } 99 100 /* Read the worklist if -r was given */ 101 static off_t 102 read_worklist(off_t t) 103 { 104 off_t s, l, d; 105 int state, lines; 106 FILE *file; 107 108 (void)fprintf(stderr, "Reading worklist ..."); 109 fflush(stderr); 110 file = fopen(rworklist, "r"); 111 if (file == NULL) 112 err(1, "Error opening file %s", rworklist); 113 114 lines = 0; 115 d = t; 116 for (;;) { 117 ++lines; 118 if (3 != fscanf(file, "%jd %jd %d\n", &s, &l, &state)) { 119 if (!feof(file)) 120 err(1, "Error parsing file %s at line %d", 121 rworklist, lines); 122 else 123 break; 124 } 125 new_lump(s, l, state); 126 d -= l; 127 } 128 (void)fprintf(stderr, " done.\n"); 129 /* 130 * Return the number of bytes already read 131 * (at least not in worklist). 132 */ 133 return (d); 134 } 135 136 static void 137 usage(void) 138 { 139 (void)fprintf(stderr, "usage: recoverdisk [-b bigsize] [-r readlist] " 140 "[-s interval] [-w writelist] source [destination]\n"); 141 exit(1); 142 } 143 144 static void 145 sighandler(__unused int sig) 146 { 147 148 aborting = 1; 149 } 150 151 int 152 main(int argc, char * const argv[]) 153 { 154 int ch; 155 int fdr, fdw; 156 off_t t, d, start, len; 157 size_t i, j; 158 int error, state; 159 u_char *buf; 160 u_int sectorsize; 161 off_t stripesize; 162 time_t t1, t2; 163 struct stat sb; 164 u_int n, snapshot = 60; 165 166 while ((ch = getopt(argc, argv, "b:r:w:s:")) != -1) { 167 switch (ch) { 168 case 'b': 169 bigsize = strtoul(optarg, NULL, 0); 170 break; 171 case 'r': 172 rworklist = strdup(optarg); 173 if (rworklist == NULL) 174 err(1, "Cannot allocate enough memory"); 175 break; 176 case 's': 177 snapshot = strtoul(optarg, NULL, 0); 178 break; 179 case 'w': 180 wworklist = strdup(optarg); 181 if (wworklist == NULL) 182 err(1, "Cannot allocate enough memory"); 183 break; 184 default: 185 usage(); 186 /* NOTREACHED */ 187 } 188 } 189 argc -= optind; 190 argv += optind; 191 192 if (argc < 1 || argc > 2) 193 usage(); 194 195 fdr = open(argv[0], O_RDONLY); 196 if (fdr < 0) 197 err(1, "Cannot open read descriptor %s", argv[0]); 198 199 error = fstat(fdr, &sb); 200 if (error < 0) 201 err(1, "fstat failed"); 202 if (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode)) { 203 error = ioctl(fdr, DIOCGSECTORSIZE, §orsize); 204 if (error < 0) 205 err(1, "DIOCGSECTORSIZE failed"); 206 207 error = ioctl(fdr, DIOCGSTRIPESIZE, &stripesize); 208 if (error == 0 && stripesize > sectorsize) 209 sectorsize = stripesize; 210 211 minsize = sectorsize; 212 bigsize = rounddown(bigsize, sectorsize); 213 214 error = ioctl(fdr, DIOCGMEDIASIZE, &t); 215 if (error < 0) 216 err(1, "DIOCGMEDIASIZE failed"); 217 } else { 218 t = sb.st_size; 219 } 220 221 if (bigsize < minsize) 222 bigsize = minsize; 223 224 for (ch = 0; (bigsize >> ch) > minsize; ch++) 225 continue; 226 medsize = bigsize >> (ch / 2); 227 medsize = rounddown(medsize, minsize); 228 229 fprintf(stderr, "Bigsize = %zu, medsize = %zu, minsize = %zu\n", 230 bigsize, medsize, minsize); 231 232 buf = malloc(bigsize); 233 if (buf == NULL) 234 err(1, "Cannot allocate %zu bytes buffer", bigsize); 235 236 if (argc > 1) { 237 fdw = open(argv[1], O_WRONLY | O_CREAT, DEFFILEMODE); 238 if (fdw < 0) 239 err(1, "Cannot open write descriptor %s", argv[1]); 240 if (ftruncate(fdw, t) < 0) 241 err(1, "Cannot truncate output %s to %jd bytes", 242 argv[1], (intmax_t)t); 243 } else 244 fdw = -1; 245 246 if (rworklist != NULL) { 247 d = read_worklist(t); 248 } else { 249 new_lump(0, t, 0); 250 d = 0; 251 } 252 if (wworklist != NULL) 253 signal(SIGINT, sighandler); 254 255 t1 = 0; 256 start = len = i = state = 0; 257 PRINT_HEADER; 258 n = 0; 259 for (;;) { 260 lp = TAILQ_FIRST(&lumps); 261 if (lp == NULL) 262 break; 263 while (lp->len > 0 && !aborting) { 264 /* These are only copied for printing stats */ 265 start = lp->start; 266 len = lp->len; 267 state = lp->state; 268 269 i = MIN(lp->len, (off_t)bigsize); 270 if (lp->state == 1) 271 i = MIN(lp->len, (off_t)medsize); 272 if (lp->state > 1) 273 i = MIN(lp->len, (off_t)minsize); 274 time(&t2); 275 if (t1 != t2 || lp->len < (off_t)bigsize) { 276 PRINT_STATUS(start, i, len, state, d, t); 277 t1 = t2; 278 if (++n == snapshot) { 279 save_worklist(); 280 n = 0; 281 } 282 } 283 if (i == 0) { 284 errx(1, "BOGUS i %10jd", (intmax_t)i); 285 } 286 fflush(stdout); 287 j = pread(fdr, buf, i, lp->start); 288 if (j == i) { 289 d += i; 290 if (fdw >= 0) 291 j = pwrite(fdw, buf, i, lp->start); 292 else 293 j = i; 294 if (j != i) 295 printf("\nWrite error at %jd/%zu\n", 296 lp->start, i); 297 lp->start += i; 298 lp->len -= i; 299 continue; 300 } 301 printf("\n%jd %zu failed (%s)\n", 302 lp->start, i, strerror(errno)); 303 if (errno == EINVAL) { 304 printf("read() size too big? Try with -b 131072"); 305 aborting = 1; 306 } 307 if (errno == ENXIO) 308 aborting = 1; 309 new_lump(lp->start, i, lp->state + 1); 310 lp->start += i; 311 lp->len -= i; 312 } 313 if (aborting) { 314 save_worklist(); 315 return (0); 316 } 317 TAILQ_REMOVE(&lumps, lp, list); 318 free(lp); 319 } 320 PRINT_STATUS(start, i, len, state, d, t); 321 save_worklist(); 322 printf("\nCompleted\n"); 323 return (0); 324 } 325