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 fclose(file); 129 (void)fprintf(stderr, " done.\n"); 130 /* 131 * Return the number of bytes already read 132 * (at least not in worklist). 133 */ 134 return (d); 135 } 136 137 static void 138 usage(void) 139 { 140 (void)fprintf(stderr, "usage: recoverdisk [-b bigsize] [-r readlist] " 141 "[-s interval] [-w writelist] source [destination]\n"); 142 exit(1); 143 } 144 145 static void 146 sighandler(__unused int sig) 147 { 148 149 aborting = 1; 150 } 151 152 int 153 main(int argc, char * const argv[]) 154 { 155 int ch; 156 int fdr, fdw; 157 off_t t, d, start, len; 158 size_t i, j; 159 int error, state; 160 u_char *buf; 161 u_int sectorsize; 162 off_t stripesize; 163 time_t t1, t2; 164 struct stat sb; 165 u_int n, snapshot = 60; 166 167 while ((ch = getopt(argc, argv, "b:r:w:s:")) != -1) { 168 switch (ch) { 169 case 'b': 170 bigsize = strtoul(optarg, NULL, 0); 171 break; 172 case 'r': 173 rworklist = strdup(optarg); 174 if (rworklist == NULL) 175 err(1, "Cannot allocate enough memory"); 176 break; 177 case 's': 178 snapshot = strtoul(optarg, NULL, 0); 179 break; 180 case 'w': 181 wworklist = strdup(optarg); 182 if (wworklist == NULL) 183 err(1, "Cannot allocate enough memory"); 184 break; 185 default: 186 usage(); 187 /* NOTREACHED */ 188 } 189 } 190 argc -= optind; 191 argv += optind; 192 193 if (argc < 1 || argc > 2) 194 usage(); 195 196 fdr = open(argv[0], O_RDONLY); 197 if (fdr < 0) 198 err(1, "Cannot open read descriptor %s", argv[0]); 199 200 error = fstat(fdr, &sb); 201 if (error < 0) 202 err(1, "fstat failed"); 203 if (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode)) { 204 error = ioctl(fdr, DIOCGSECTORSIZE, §orsize); 205 if (error < 0) 206 err(1, "DIOCGSECTORSIZE failed"); 207 208 error = ioctl(fdr, DIOCGSTRIPESIZE, &stripesize); 209 if (error == 0 && stripesize > sectorsize) 210 sectorsize = stripesize; 211 212 minsize = sectorsize; 213 bigsize = rounddown(bigsize, sectorsize); 214 215 error = ioctl(fdr, DIOCGMEDIASIZE, &t); 216 if (error < 0) 217 err(1, "DIOCGMEDIASIZE failed"); 218 } else { 219 t = sb.st_size; 220 } 221 222 if (bigsize < minsize) 223 bigsize = minsize; 224 225 for (ch = 0; (bigsize >> ch) > minsize; ch++) 226 continue; 227 medsize = bigsize >> (ch / 2); 228 medsize = rounddown(medsize, minsize); 229 230 fprintf(stderr, "Bigsize = %zu, medsize = %zu, minsize = %zu\n", 231 bigsize, medsize, minsize); 232 233 buf = malloc(bigsize); 234 if (buf == NULL) 235 err(1, "Cannot allocate %zu bytes buffer", bigsize); 236 237 if (argc > 1) { 238 fdw = open(argv[1], O_WRONLY | O_CREAT, DEFFILEMODE); 239 if (fdw < 0) 240 err(1, "Cannot open write descriptor %s", argv[1]); 241 if (ftruncate(fdw, t) < 0) 242 err(1, "Cannot truncate output %s to %jd bytes", 243 argv[1], (intmax_t)t); 244 } else 245 fdw = -1; 246 247 if (rworklist != NULL) { 248 d = read_worklist(t); 249 } else { 250 new_lump(0, t, 0); 251 d = 0; 252 } 253 if (wworklist != NULL) 254 signal(SIGINT, sighandler); 255 256 t1 = 0; 257 start = len = i = state = 0; 258 PRINT_HEADER; 259 n = 0; 260 for (;;) { 261 lp = TAILQ_FIRST(&lumps); 262 if (lp == NULL) 263 break; 264 while (lp->len > 0 && !aborting) { 265 /* These are only copied for printing stats */ 266 start = lp->start; 267 len = lp->len; 268 state = lp->state; 269 270 i = MIN(lp->len, (off_t)bigsize); 271 if (lp->state == 1) 272 i = MIN(lp->len, (off_t)medsize); 273 if (lp->state > 1) 274 i = MIN(lp->len, (off_t)minsize); 275 time(&t2); 276 if (t1 != t2 || lp->len < (off_t)bigsize) { 277 PRINT_STATUS(start, i, len, state, d, t); 278 t1 = t2; 279 if (++n == snapshot) { 280 save_worklist(); 281 n = 0; 282 } 283 } 284 if (i == 0) { 285 errx(1, "BOGUS i %10jd", (intmax_t)i); 286 } 287 fflush(stdout); 288 j = pread(fdr, buf, i, lp->start); 289 if (j == i) { 290 d += i; 291 if (fdw >= 0) 292 j = pwrite(fdw, buf, i, lp->start); 293 else 294 j = i; 295 if (j != i) 296 printf("\nWrite error at %jd/%zu\n", 297 lp->start, i); 298 lp->start += i; 299 lp->len -= i; 300 continue; 301 } 302 printf("\n%jd %zu failed (%s)\n", 303 lp->start, i, strerror(errno)); 304 if (errno == EINVAL) { 305 printf("read() size too big? Try with -b 131072"); 306 aborting = 1; 307 } 308 if (errno == ENXIO) 309 aborting = 1; 310 new_lump(lp->start, i, lp->state + 1); 311 lp->start += i; 312 lp->len -= i; 313 } 314 if (aborting) { 315 save_worklist(); 316 return (0); 317 } 318 TAILQ_REMOVE(&lumps, lp, list); 319 free(lp); 320 } 321 PRINT_STATUS(start, i, len, state, d, t); 322 save_worklist(); 323 printf("\nCompleted\n"); 324 return (0); 325 } 326