1 /* 2 * Copyright (c) 2008 The DragonFly Project. All rights reserved. 3 * 4 * This code is derived from software contributed to The DragonFly Project 5 * by Matthew Dillon <dillon@backplane.com> 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the 16 * distribution. 17 * 3. Neither the name of The DragonFly Project nor the names of its 18 * contributors may be used to endorse or promote products derived 19 * from this software without specific, prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 31 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 * 34 * $DragonFly: src/usr.bin/undo/undo.c,v 1.1 2008/06/01 02:03:10 dillon Exp $ 35 */ 36 /* 37 * UNDO - retrieve an older version of a file. 38 */ 39 40 #include <sys/types.h> 41 #include <sys/stat.h> 42 #include <sys/wait.h> 43 #include <stdio.h> 44 #include <stdlib.h> 45 #include <stdarg.h> 46 #include <string.h> 47 #include <unistd.h> 48 #include <fcntl.h> 49 #include <errno.h> 50 #include <vfs/hammer/hammer_disk.h> 51 #include <vfs/hammer/hammer_ioctl.h> 52 53 enum undo_type { TYPE_FILE, TYPE_DIFF, TYPE_RDIFF, TYPE_HISTORY }; 54 55 static void doiterate(const char *orig_filename, const char *outFileName, 56 const char *outFilePostfix, int mult, enum undo_type type); 57 static void dogenerate(const char *filename, const char *outFileName, 58 const char *outFilePostfix, 59 int mult, int idx, enum undo_type type, 60 hammer_tid_t ts1, hammer_tid_t ts2); 61 static hammer_tid_t find_recent(const char *filename); 62 static hammer_tid_t output_history(const char *filename, int fd, FILE *fp, 63 hammer_tid_t **tid_ary, int *tid_num); 64 static hammer_tid_t parse_delta_time(const char *timeStr); 65 static void runcmd(int fd, const char *cmd, ...); 66 static char *timestamp(hammer_tid_t tid); 67 static void usage(void); 68 69 static int VerboseOpt; 70 71 int 72 main(int ac, char **av) 73 { 74 const char *outFileName = NULL; 75 const char *outFilePostfix = NULL; 76 enum { CMD_DUMP, CMD_ITERATEALL } cmd; 77 enum undo_type type; 78 hammer_tid_t ts1 = 0; 79 hammer_tid_t ts2 = 0; 80 int c; 81 int mult; 82 83 cmd = CMD_DUMP; 84 type = TYPE_FILE; 85 86 while ((c = getopt(ac, av, "dDhiuvo:t:")) != -1) { 87 switch(c) { 88 case 'd': 89 if (type != TYPE_FILE) 90 usage(); 91 type = TYPE_DIFF; 92 break; 93 case 'D': 94 if (type != TYPE_FILE) 95 usage(); 96 type = TYPE_RDIFF; 97 break; 98 case 'i': 99 if (type != TYPE_FILE) 100 usage(); 101 type = TYPE_HISTORY; 102 break; 103 case 'h': 104 cmd = CMD_ITERATEALL; 105 break; 106 case 'u': 107 outFilePostfix = ".undo"; 108 break; 109 case 'v': 110 ++VerboseOpt; 111 break; 112 case 'o': 113 outFileName = optarg; 114 break; 115 case 't': 116 if (ts1 && ts2) 117 usage(); 118 else if (ts1 == 0) 119 ts1 = parse_delta_time(optarg); 120 else 121 ts2 = parse_delta_time(optarg); 122 break; 123 default: 124 usage(); 125 /* NOT REACHED */ 126 break; 127 } 128 } 129 130 /* 131 * Option validation 132 */ 133 if (outFileName && outFilePostfix) { 134 fprintf(stderr, "The -o option may not be combined with -u\n"); 135 usage(); 136 } 137 138 ac -= optind; 139 av += optind; 140 mult = (ac > 1); 141 142 if (ac == 0) 143 usage(); 144 145 /* 146 * Validate the output template, if specified. 147 */ 148 if (outFileName && mult) { 149 const char *ptr = outFileName; 150 int didStr = 0; 151 152 while ((ptr = strchr(ptr, '%')) != NULL) { 153 if (ptr[1] == 's') { 154 if (didStr) { 155 fprintf(stderr, "Malformed output " 156 "template\n"); 157 usage(); 158 } 159 didStr = 1; 160 ++ptr; 161 } else if (ptr[1] != '%') { 162 fprintf(stderr, "Malformed output template\n"); 163 usage(); 164 } else { 165 ptr += 2; 166 } 167 } 168 } 169 170 while (ac) { 171 switch(cmd) { 172 case CMD_DUMP: 173 dogenerate(*av, outFileName, outFilePostfix, 174 mult, -1, type, 175 ts1, ts2); 176 break; 177 case CMD_ITERATEALL: 178 doiterate(*av, outFileName, outFilePostfix, 179 mult, type); 180 break; 181 } 182 ++av; 183 --ac; 184 } 185 return(0); 186 } 187 188 /* 189 * Iterate through a file's history 190 */ 191 static 192 void 193 doiterate(const char *orig_filename, const char *outFileName, 194 const char *outFilePostfix, int mult, enum undo_type type) 195 { 196 hammer_tid_t *tid_ary = NULL; 197 hammer_tid_t ts1; 198 const char *use_filename; 199 char *path = NULL; 200 int tid_num = 0; 201 int i; 202 int fd; 203 204 use_filename = orig_filename; 205 if ((fd = open(orig_filename, O_RDONLY)) < 0) { 206 ts1 = find_recent(orig_filename); 207 if (ts1) { 208 asprintf(&path, "%s@@0x%016llx", orig_filename, ts1); 209 use_filename = path; 210 } 211 } 212 213 if ((fd = open(use_filename, O_RDONLY)) >= 0) { 214 printf("%s: ITERATE ENTIRE HISTORY\n", orig_filename); 215 output_history(NULL, fd, NULL, &tid_ary, &tid_num); 216 close(fd); 217 218 for (i = 0; i < tid_num; ++i) { 219 if (i && tid_ary[i] == tid_ary[i-1]) 220 continue; 221 222 if (i == tid_num - 1) { 223 dogenerate(orig_filename, 224 outFileName, outFilePostfix, 225 mult, i, type, 226 tid_ary[i], HAMMER_MAX_TID); 227 } else { 228 dogenerate(orig_filename, 229 outFileName, outFilePostfix, 230 mult, i, type, 231 tid_ary[i], tid_ary[i+1]); 232 } 233 } 234 235 } else { 236 printf("%s: ITERATE ENTIRE HISTORY: %s\n", 237 orig_filename, strerror(errno)); 238 } 239 if (path) 240 free(path); 241 } 242 243 /* 244 * Generate output for a file as-of ts1 (ts1 may be 0!), if diffing then 245 * through ts2. 246 */ 247 static 248 void 249 dogenerate(const char *filename, const char *outFileName, 250 const char *outFilePostfix, 251 int mult, int idx, enum undo_type type, 252 hammer_tid_t ts1, hammer_tid_t ts2) 253 { 254 struct stat st; 255 const char *elm; 256 char *ipath1 = NULL; 257 char *ipath2 = NULL; 258 FILE *fi; 259 FILE *fp; 260 char *buf; 261 char *path; 262 int n; 263 264 buf = malloc(8192); 265 266 /* 267 * Open the input file. If ts1 is 0 try to locate the most recent 268 * version of the file prior to the current version. 269 */ 270 if (ts1 == 0) 271 ts1 = find_recent(filename); 272 asprintf(&ipath1, "%s@@0x%016llx", filename, ts1); 273 if (lstat(ipath1, &st) < 0) { 274 if (VerboseOpt) { 275 fprintf(stderr, "Cannot locate src/historical " 276 "idx=%d %s\n", 277 idx, ipath1); 278 } 279 goto done; 280 } 281 282 if (ts2 == 0) { 283 asprintf(&ipath2, "%s", filename); 284 } else { 285 asprintf(&ipath2, "%s@@0x%015llx", filename, ts2); 286 } 287 if (lstat(ipath2, &st) < 0) { 288 if (VerboseOpt) { 289 if (ts2) { 290 fprintf(stderr, "Cannot locate tgt/historical " 291 "idx=%d %s\n", 292 idx, ipath2); 293 } else if (VerboseOpt > 1) { 294 fprintf(stderr, "Cannot locate %s\n", filename); 295 } 296 } 297 ipath2 = strdup("/dev/null"); 298 } 299 300 /* 301 * elm is the last component of the input file name 302 */ 303 if ((elm = strrchr(filename, '/')) != NULL) 304 ++elm; 305 else 306 elm = filename; 307 308 /* 309 * Where do we stuff our output? 310 */ 311 if (outFileName) { 312 if (mult) { 313 asprintf(&path, outFileName, elm); 314 fp = fopen(path, "w"); 315 if (fp == NULL) { 316 perror(path); 317 exit(1); 318 } 319 free(path); 320 } else { 321 fp = fopen(outFileName, "w"); 322 if (fp == NULL) { 323 perror(outFileName); 324 exit(1); 325 } 326 } 327 } else if (outFilePostfix) { 328 if (idx >= 0) { 329 asprintf(&path, "%s%s.%04d", filename, 330 outFilePostfix, idx); 331 } else { 332 asprintf(&path, "%s%s", filename, outFilePostfix); 333 } 334 fp = fopen(path, "w"); 335 if (fp == NULL) { 336 perror(path); 337 exit(1); 338 } 339 free(path); 340 } else { 341 if (mult && type == TYPE_FILE) { 342 if (idx >= 0) { 343 printf("\n>>> %s %04d 0x%016llx %s\n\n", 344 filename, idx, ts1, timestamp(ts1)); 345 } else { 346 printf("\n>>> %s ---- 0x%016llx %s\n\n", 347 filename, ts1, timestamp(ts1)); 348 } 349 } else if (idx >= 0 && type == TYPE_FILE) { 350 printf("\n>>> %s %04d 0x%016llx %s\n\n", 351 filename, idx, ts1, timestamp(ts1)); 352 } 353 fp = stdout; 354 } 355 356 switch(type) { 357 case TYPE_FILE: 358 if ((fi = fopen(ipath1, "r")) != NULL) { 359 while ((n = fread(buf, 1, 8192, fi)) > 0) 360 fwrite(buf, 1, n, fp); 361 fclose(fi); 362 } 363 break; 364 case TYPE_DIFF: 365 printf("diff -u %s %s\n", ipath1, ipath2); 366 fflush(stdout); 367 runcmd(fileno(fp), "/usr/bin/diff", "diff", "-u", ipath1, ipath2, NULL); 368 break; 369 case TYPE_RDIFF: 370 printf("diff -u %s %s\n", ipath2, ipath1); 371 fflush(stdout); 372 runcmd(fileno(fp), "/usr/bin/diff", "diff", "-u", ipath2, ipath1, NULL); 373 break; 374 case TYPE_HISTORY: 375 if ((fi = fopen(ipath1, "r")) != NULL) { 376 output_history(filename, fileno(fi), fp, NULL, NULL); 377 fclose(fi); 378 } 379 break; 380 } 381 382 if (fp != stdout) 383 fclose(fp); 384 done: 385 free(buf); 386 } 387 388 /* 389 * Try to find a recent version of the file. 390 * 391 * XXX if file cannot be found 392 */ 393 static hammer_tid_t 394 find_recent(const char *filename) 395 { 396 hammer_tid_t *tid_ary = NULL; 397 int tid_num = 0; 398 hammer_tid_t tid; 399 char *dirname; 400 char *path; 401 int fd; 402 int i; 403 404 if ((fd = open(filename, O_RDONLY)) >= 0) { 405 tid = output_history(NULL, fd, NULL, NULL, NULL); 406 close(fd); 407 return(tid); 408 } 409 410 /* 411 * If the object does not exist acquire the history of its 412 * directory and then try accessing the object at each TID. 413 */ 414 if (strrchr(filename, '/')) { 415 dirname = strdup(filename); 416 *strrchr(dirname, '/') = 0; 417 } else { 418 dirname = strdup("."); 419 } 420 421 tid = 0; 422 if ((fd = open(dirname, O_RDONLY)) >= 0) { 423 output_history(NULL, fd, NULL, &tid_ary, &tid_num); 424 close(fd); 425 free(dirname); 426 427 for (i = tid_num - 1; i >= 0; --i) { 428 asprintf(&path, "%s@@0x%016llx", filename, tid_ary[i]); 429 if ((fd = open(path, O_RDONLY)) >= 0) { 430 tid = output_history(NULL, fd, NULL, NULL, NULL); 431 close(fd); 432 free(path); 433 break; 434 } 435 free(path); 436 } 437 } 438 return(tid); 439 } 440 441 /* 442 * Collect all the transaction ids representing changes made to the 443 * file, sort, and output (weeding out duplicates). If fp is NULL 444 * we do not output anything and simply return the most recent TID we 445 * can find. 446 */ 447 static int 448 tid_cmp(const void *arg1, const void *arg2) 449 { 450 const hammer_tid_t *tid1 = arg1; 451 const hammer_tid_t *tid2 = arg2; 452 453 if (*tid1 < *tid2) 454 return(-1); 455 if (*tid1 > *tid2) 456 return(1); 457 return(0); 458 } 459 460 static hammer_tid_t 461 output_history(const char *filename, int fd, FILE *fp, 462 hammer_tid_t **tid_aryp, int *tid_nump) 463 { 464 struct hammer_ioc_history hist; 465 char datestr[64]; 466 struct tm *tp; 467 time_t t; 468 int tid_max = 32; 469 int tid_num = 0; 470 int i; 471 hammer_tid_t *tid_ary = malloc(tid_max * sizeof(*tid_ary)); 472 hammer_tid_t tid; 473 474 bzero(&hist, sizeof(hist)); 475 hist.beg_tid = HAMMER_MIN_TID; 476 hist.end_tid = HAMMER_MAX_TID; 477 hist.head.flags |= HAMMER_IOC_HISTORY_ATKEY; 478 hist.key = 0; 479 hist.nxt_key = HAMMER_MAX_KEY; 480 481 tid = 0; 482 483 if (ioctl(fd, HAMMERIOC_GETHISTORY, &hist) < 0) { 484 if (filename) 485 printf("%s: %s\n", filename, strerror(errno)); 486 goto done; 487 } 488 if (filename) 489 printf("%s: objid=0x%016llx\n", filename, hist.obj_id); 490 for (;;) { 491 if (tid_num + hist.count >= tid_max) { 492 tid_max = (tid_max * 3 / 2) + hist.count; 493 tid_ary = realloc(tid_ary, tid_max * sizeof(*tid_ary)); 494 } 495 for (i = 0; i < hist.count; ++i) { 496 tid_ary[tid_num++] = hist.tid_ary[i]; 497 } 498 if (hist.head.flags & HAMMER_IOC_HISTORY_EOF) 499 break; 500 if (hist.head.flags & HAMMER_IOC_HISTORY_NEXT_KEY) { 501 hist.key = hist.nxt_key; 502 hist.nxt_key = HAMMER_MAX_KEY; 503 } 504 if (hist.head.flags & HAMMER_IOC_HISTORY_NEXT_TID) 505 hist.beg_tid = hist.nxt_tid; 506 if (ioctl(fd, HAMMERIOC_GETHISTORY, &hist) < 0) { 507 if (filename) 508 printf("%s: %s\n", filename, strerror(errno)); 509 break; 510 } 511 } 512 qsort(tid_ary, tid_num, sizeof(*tid_ary), tid_cmp); 513 if (tid_num == 0) 514 goto done; 515 for (i = 0; fp && i < tid_num; ++i) { 516 if (i && tid_ary[i] == tid_ary[i-1]) 517 continue; 518 t = (time_t)(tid_ary[i] / 1000000000); 519 tp = localtime(&t); 520 strftime(datestr, sizeof(datestr), "%d-%m-%Y %H:%M:%S", tp); 521 printf("\t0x%016llx %s\n", tid_ary[i], datestr); 522 } 523 if (tid_num > 1) 524 tid = tid_ary[tid_num-2]; 525 done: 526 if (tid_aryp) { 527 *tid_aryp = tid_ary; 528 *tid_nump = tid_num; 529 } else { 530 free(tid_ary); 531 } 532 return(tid); 533 } 534 535 static 536 hammer_tid_t 537 parse_delta_time(const char *timeStr) 538 { 539 hammer_tid_t tid; 540 char *ptr; 541 542 if (timeStr[0] == '0' && (timeStr[1] == 'x' || timeStr[1] == 'X')) { 543 tid = strtoull(timeStr, NULL, 0); 544 } else { 545 tid = strtol(timeStr, &ptr, 0); 546 547 switch(*ptr) { 548 case 'd': 549 tid *= 24; 550 /* fall through */ 551 case 'h': 552 tid *= 60; 553 /* fall through */ 554 case 'm': 555 tid *= 60; 556 /* fall through */ 557 case 's': 558 break; 559 default: 560 usage(); 561 } 562 tid = time(NULL) - tid; 563 tid *= 1000000000; 564 } 565 return(tid); 566 } 567 568 static void 569 runcmd(int fd, const char *cmd, ...) 570 { 571 va_list va; 572 pid_t pid; 573 char **av; 574 int ac; 575 int i; 576 577 va_start(va, cmd); 578 for (ac = 0; va_arg(va, void *) != NULL; ++ac) 579 ; 580 va_end(va); 581 582 av = malloc((ac + 1) * sizeof(char *)); 583 va_start(va, cmd); 584 for (i = 0; i < ac; ++i) 585 av[i] = va_arg(va, char *); 586 va_end(va); 587 av[i] = NULL; 588 589 if ((pid = fork()) < 0) { 590 perror("fork"); 591 exit(1); 592 } else if (pid == 0) { 593 if (fd != 1) { 594 dup2(fd, 1); 595 close(fd); 596 } 597 execv(cmd, av); 598 _exit(1); 599 } else { 600 while (waitpid(pid, NULL, 0) != pid) 601 ; 602 } 603 free(av); 604 } 605 606 /* 607 * Convert tid to timestamp. 608 */ 609 static char * 610 timestamp(hammer_tid_t tid) 611 { 612 static char timebuf[64]; 613 time_t t = (time_t)(tid / 1000000000); 614 struct tm *tp; 615 616 tp = localtime(&t); 617 strftime(timebuf, sizeof(timebuf), "%d-%m-%Y %H:%M:%S", tp); 618 return(timebuf); 619 } 620 621 static void 622 usage(void) 623 { 624 fprintf(stderr, "undo [-dDhiuv] [-t n{s,m,h,d}] [-t n...] " 625 "file1....fileN\n"); 626 fprintf(stderr, " -d Forward diff\n" 627 " -D Reverse diff\n" 628 " -i Dump history transaction ids\n" 629 " -h Iterate all historical segments\n" 630 " -u Generate .undo files\n" 631 " -v Verbose\n" 632 " -t spec Retrieve as of n secs,mins,etc ago\n" 633 " (a second one to diff two versions)\n" 634 " (a 0x TID may also be specified)\n"); 635 exit(1); 636 } 637 638