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.6 2008/07/17 21:34:47 thomas 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 <sys/tree.h> 44 #include <sys/ioctl.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <stdarg.h> 48 #include <string.h> 49 #include <unistd.h> 50 #include <fcntl.h> 51 #include <errno.h> 52 #include <err.h> 53 #include <vfs/hammer/hammer_disk.h> 54 #include <vfs/hammer/hammer_ioctl.h> 55 56 /* 57 * Sorted list of transaction ids 58 */ 59 struct undo_hist_entry; 60 RB_HEAD(undo_hist_entry_rb_tree, undo_hist_entry); 61 RB_PROTOTYPE2(undo_hist_entry_rb_tree, undo_hist_entry, rbnode, 62 undo_hist_entry_compare, hammer_tid_t); 63 64 struct undo_hist_entry { 65 RB_ENTRY(undo_hist_entry) rbnode; 66 struct hammer_ioc_hist_entry tse; 67 ino_t inum; 68 }; 69 70 enum undo_type { TYPE_FILE, TYPE_DIFF, TYPE_RDIFF, TYPE_HISTORY }; 71 enum undo_cmd { CMD_DUMP, CMD_ITERATEALL }; 72 73 #define UNDO_FLAG_MULT 0x0001 74 #define UNDO_FLAG_INOCHG 0x0002 75 #define UNDO_FLAG_TID_INDEX1 0x0004 76 #define UNDO_FLAG_TID_INDEX2 0x0008 77 78 static int undo_hist_entry_compare(struct undo_hist_entry *he1, 79 struct undo_hist_entry *he2); 80 static void doiterate(const char *filename, int flags, 81 struct hammer_ioc_hist_entry ts1, 82 struct hammer_ioc_hist_entry ts2, 83 enum undo_cmd cmd, enum undo_type type); 84 static int doiterate_dump(const char *filename, int flags, 85 struct undo_hist_entry_rb_tree *ptse_tree, 86 struct hammer_ioc_hist_entry ts1, 87 struct hammer_ioc_hist_entry ts2, 88 enum undo_type type); 89 static int doiterate_iterall(const char *filename, int flags, 90 struct undo_hist_entry_rb_tree *ptse_tree, 91 enum undo_type type); 92 static void dogenerate(const char *filename, int flags, 93 struct hammer_ioc_hist_entry ts1, 94 struct hammer_ioc_hist_entry ts2, 95 int idx, enum undo_type type); 96 static void __collect_history(int fd, int *error, 97 struct undo_hist_entry_rb_tree *tse_tree); 98 static void collect_history(const char *filename, int *errorp, 99 struct undo_hist_entry_rb_tree *dir_tree); 100 static void collect_dir_history(const char *filename, int *error, 101 struct undo_hist_entry_rb_tree *dir_tree); 102 static void clean_tree(struct undo_hist_entry_rb_tree *tree); 103 static hammer_tid_t parse_delta_time(const char *timeStr, int *flags, 104 int ind_flag); 105 static FILE *_fopen(const char *filename, const char *mode); 106 static void runcmd(int fd, const char *cmd, ...); 107 static char *timestamp(struct hammer_ioc_hist_entry *hen); 108 static void usage(void); 109 110 static int VerboseOpt; 111 static const char *OutFileName = NULL; 112 static const char *OutFilePostfix = NULL; 113 114 RB_GENERATE2(undo_hist_entry_rb_tree, undo_hist_entry, rbnode, 115 undo_hist_entry_compare, hammer_tid_t, tse.tid); 116 117 118 int 119 main(int ac, char **av) 120 { 121 enum undo_cmd cmd; 122 enum undo_type type; 123 struct hammer_ioc_hist_entry ts1; 124 struct hammer_ioc_hist_entry ts2; 125 int c; 126 int count_t; 127 int flags; 128 129 bzero(&ts1, sizeof(ts1)); 130 bzero(&ts2, sizeof(ts2)); 131 132 cmd = CMD_DUMP; 133 type = TYPE_FILE; 134 count_t = 0; 135 flags = 0; 136 137 while ((c = getopt(ac, av, "adDiuvo:t:")) != -1) { 138 switch(c) { 139 case 'd': 140 type = TYPE_DIFF; 141 break; 142 case 'D': 143 type = TYPE_RDIFF; 144 break; 145 case 'i': 146 if (type != TYPE_FILE) 147 usage(); 148 type = TYPE_HISTORY; 149 cmd = CMD_ITERATEALL; 150 break; 151 case 'a': 152 cmd = CMD_ITERATEALL; 153 break; 154 case 'u': 155 OutFilePostfix = ".undo"; 156 break; 157 case 'v': 158 ++VerboseOpt; 159 break; 160 case 'o': 161 OutFileName = optarg; 162 break; 163 case 't': 164 /* 165 * Parse one or two -t options. If two are specified 166 * -d is implied (but may be overridden) 167 */ 168 ++count_t; 169 if (count_t == 1) { 170 ts1.tid = parse_delta_time(optarg, &flags, 171 UNDO_FLAG_TID_INDEX1); 172 } else if (count_t == 2) { 173 ts2.tid = parse_delta_time(optarg, &flags, 174 UNDO_FLAG_TID_INDEX2); 175 if (type == TYPE_FILE) 176 type = TYPE_DIFF; 177 } else { 178 usage(); 179 } 180 break; 181 default: 182 usage(); 183 /* NOT REACHED */ 184 break; 185 } 186 } 187 188 /* 189 * Option validation 190 */ 191 if (OutFileName && OutFilePostfix) { 192 fprintf(stderr, "The -o option may not be combined with -u\n"); 193 usage(); 194 } 195 196 ac -= optind; 197 av += optind; 198 if (ac > 1) 199 flags |= UNDO_FLAG_MULT; 200 201 if (ac == 0) 202 usage(); 203 204 /* 205 * Validate the output template, if specified. 206 */ 207 if (OutFileName && (flags & UNDO_FLAG_MULT)) { 208 const char *ptr = OutFileName; 209 int didStr = 0; 210 211 while ((ptr = strchr(ptr, '%')) != NULL) { 212 if (ptr[1] == 's') { 213 if (didStr) { 214 fprintf(stderr, "Malformed output " 215 "template\n"); 216 usage(); 217 } 218 didStr = 1; 219 ++ptr; 220 } else if (ptr[1] != '%') { 221 fprintf(stderr, "Malformed output template\n"); 222 usage(); 223 } else { 224 ptr += 2; 225 } 226 } 227 } 228 229 while (ac) { 230 doiterate(*av, flags, ts1, ts2, cmd, type); 231 ++av; 232 --ac; 233 } 234 return(0); 235 } 236 237 /* 238 * Iterate through a file's history. If cmd == CMD_DUMP we take the 239 * next-to-last transaction id, unless another given. Otherwise if 240 * cmd == CMD_ITERATEALL we scan all transaction ids. 241 * 242 * Also iterate through the directory's history to locate other inodes that 243 * used the particular file name. 244 */ 245 static 246 void 247 doiterate(const char *filename, int flags, 248 struct hammer_ioc_hist_entry ts1, 249 struct hammer_ioc_hist_entry ts2, 250 enum undo_cmd cmd, enum undo_type type) 251 { 252 struct undo_hist_entry_rb_tree dir_tree; 253 struct undo_hist_entry_rb_tree tse_tree; 254 struct undo_hist_entry *tse; 255 struct stat sb; 256 char *path = NULL; 257 int error; 258 259 RB_INIT(&dir_tree); 260 RB_INIT(&tse_tree); 261 262 /* 263 * Use the directory history to locate all possible versions of 264 * the file. 265 */ 266 collect_dir_history(filename, &error, &dir_tree); 267 RB_FOREACH(tse, undo_hist_entry_rb_tree, &dir_tree) { 268 asprintf(&path, "%s@@0x%016jx", filename, (uintmax_t)tse->tse.tid); 269 if (stat(path, &sb) == 0 && (sb.st_mode & S_IFIFO)) { 270 fprintf(stderr, "Warning: fake transaction id %s@@0x%016jx\n", 271 filename, 272 (uintmax_t)tse->tse.tid); 273 free(path); 274 continue; 275 } 276 collect_history(path, &error, &tse_tree); 277 free(path); 278 } 279 collect_history(filename, &error, &tse_tree); 280 281 switch (cmd) { 282 case CMD_DUMP: 283 if (doiterate_dump(filename, flags, &tse_tree, ts1, ts2, type) == -1) 284 printf("%s: No UNDO history found\n", filename); 285 break; 286 case CMD_ITERATEALL: 287 if (doiterate_iterall(filename, flags, &tse_tree, type) == -1) 288 printf("%s: No UNDO history found\n", filename); 289 break; 290 default: 291 fprintf(stderr, "Invalid command %d\n", cmd); 292 break; 293 } 294 295 clean_tree(&dir_tree); 296 clean_tree(&tse_tree); 297 } 298 299 static 300 int 301 doiterate_dump(const char *filename, int flags, 302 struct undo_hist_entry_rb_tree *ptse_tree, 303 struct hammer_ioc_hist_entry ts1, 304 struct hammer_ioc_hist_entry ts2, 305 enum undo_type type) 306 { 307 struct undo_hist_entry *tse1; 308 struct undo_hist_entry *tse2; 309 310 /* 311 * Find entry if tid set to placeholder index 312 */ 313 if (flags & UNDO_FLAG_TID_INDEX1) { 314 tse1 = RB_MAX(undo_hist_entry_rb_tree, ptse_tree); 315 while (tse1 && ts1.tid--) { 316 tse1 = RB_PREV(undo_hist_entry_rb_tree, 317 ptse_tree, tse1); 318 } 319 if (tse1) 320 ts1 = tse1->tse; 321 else 322 ts1.tid = 0; 323 } 324 if (flags & UNDO_FLAG_TID_INDEX2) { 325 tse2 = RB_MAX(undo_hist_entry_rb_tree, ptse_tree); 326 while (tse2 && ts2.tid--) { 327 tse2 = RB_PREV(undo_hist_entry_rb_tree, 328 ptse_tree, tse2); 329 } 330 if (tse2) 331 ts2 = tse2->tse; 332 else 333 ts2.tid = 0; 334 } 335 336 /* 337 * Single entry, most recent prior to current 338 */ 339 if (ts1.tid == 0) { 340 tse2 = RB_MAX(undo_hist_entry_rb_tree, ptse_tree); 341 if (tse2) { 342 ts2 = tse2->tse; 343 tse1 = RB_PREV(undo_hist_entry_rb_tree, 344 ptse_tree, tse2); 345 if (tse1) 346 ts1 = tse1->tse; 347 } 348 } 349 350 if (ts1.tid) { 351 dogenerate(filename, 0, ts1, ts2, 0, type); 352 return(0); 353 } 354 return(-1); 355 } 356 357 static 358 int 359 doiterate_iterall(const char *filename, int flags, 360 struct undo_hist_entry_rb_tree *ptse_tree, 361 enum undo_type type) 362 { 363 struct undo_hist_entry *tse1; 364 struct undo_hist_entry *tse2; 365 struct hammer_ioc_hist_entry tid_max; 366 int i; 367 368 if (RB_ROOT(ptse_tree) == NULL) 369 return(-1); 370 371 /* 372 * Iterate entire history 373 */ 374 printf("%s: ITERATE ENTIRE HISTORY\n", filename); 375 376 tse1 = NULL; 377 i = 0; 378 RB_FOREACH(tse2, undo_hist_entry_rb_tree, ptse_tree) { 379 if (tse1) { 380 dogenerate(filename, flags, tse1->tse, tse2->tse, i, type); 381 } 382 if (tse1 && tse2->inum != tse1->inum) 383 flags |= UNDO_FLAG_INOCHG; 384 else 385 flags &= ~UNDO_FLAG_INOCHG; 386 tse1 = tse2; 387 ++i; 388 } 389 390 /* 391 * There is no delta to print for the last pair, 392 * because they are identical. 393 */ 394 if (type != TYPE_DIFF && type != TYPE_RDIFF) { 395 tid_max.tid = HAMMER_MAX_TID; 396 tid_max.time32 = 0; 397 dogenerate(filename, flags, tse1->tse, tid_max, i, type); 398 } 399 return(0); 400 } 401 402 /* 403 * Generate output for a file as-of ts1 (ts1 may be 0!), if diffing then 404 * through ts2. 405 */ 406 static 407 void 408 dogenerate(const char *filename, int flags, 409 struct hammer_ioc_hist_entry ts1, 410 struct hammer_ioc_hist_entry ts2, 411 int idx, enum undo_type type) 412 { 413 struct stat st; 414 const char *elm; 415 char *ipath1 = NULL; 416 char *ipath2 = NULL; 417 FILE *fi; 418 FILE *fp; 419 char *buf; 420 char *path; 421 time_t t; 422 struct tm *tp; 423 char datestr[64]; 424 int n; 425 426 /* 427 * Open the input file. If ts1 is 0 try to locate the most recent 428 * version of the file prior to the current version. 429 */ 430 if (ts1.tid == 0) 431 asprintf(&ipath1, "%s", filename); 432 else 433 asprintf(&ipath1, "%s@@0x%016jx", filename, (uintmax_t)ts1.tid); 434 435 if (ts2.tid == 0) 436 asprintf(&ipath2, "%s", filename); 437 else 438 asprintf(&ipath2, "%s@@0x%016jx", filename, (uintmax_t)ts2.tid); 439 440 if (lstat(ipath1, &st) < 0 && lstat(ipath2, &st) < 0) { 441 if (idx == 0 || VerboseOpt) { 442 fprintf(stderr, "Unable to access either %s or %s\n", 443 ipath1, ipath2); 444 } 445 free(ipath1); 446 free(ipath2); 447 return; 448 } 449 450 /* 451 * elm is the last component of the input file name 452 */ 453 if ((elm = strrchr(filename, '/')) != NULL) 454 ++elm; 455 else 456 elm = filename; 457 458 /* 459 * Where do we stuff our output? 460 */ 461 if (OutFileName) { 462 if (flags & UNDO_FLAG_MULT) { 463 asprintf(&path, OutFileName, elm); 464 fp = _fopen(path, "w"); 465 free(path); 466 } else { 467 fp = _fopen(OutFileName, "w"); 468 } 469 } else if (OutFilePostfix) { 470 if (idx >= 0) { 471 asprintf(&path, "%s%s.%04d", filename, 472 OutFilePostfix, idx); 473 } else { 474 asprintf(&path, "%s%s", filename, OutFilePostfix); 475 } 476 fp = _fopen(path, "w"); 477 free(path); 478 } else { 479 if ((flags & UNDO_FLAG_MULT) && type == TYPE_FILE) { 480 if (idx >= 0) { 481 printf("\n>>> %s %04d 0x%016jx %s\n\n", 482 filename, idx, (uintmax_t)ts1.tid, 483 timestamp(&ts1)); 484 } else { 485 printf("\n>>> %s ---- 0x%016jx %s\n\n", 486 filename, (uintmax_t)ts1.tid, 487 timestamp(&ts1)); 488 } 489 } else if (idx >= 0 && type == TYPE_FILE) { 490 printf("\n>>> %s %04d 0x%016jx %s\n\n", 491 filename, idx, (uintmax_t)ts1.tid, 492 timestamp(&ts1)); 493 } 494 fp = stdout; 495 } 496 497 switch(type) { 498 case TYPE_FILE: 499 buf = malloc(8192); 500 if (buf == NULL) 501 err(1, "malloc"); 502 if ((fi = fopen(ipath1, "r")) != NULL) { 503 while ((n = fread(buf, 1, 8192, fi)) > 0) 504 fwrite(buf, 1, n, fp); 505 fclose(fi); 506 } 507 free(buf); 508 break; 509 case TYPE_DIFF: 510 printf("diff -N -r -u %s %s (to %s)\n", 511 ipath1, ipath2, timestamp(&ts2)); 512 fflush(stdout); 513 runcmd(fileno(fp), "/usr/bin/diff", "diff", "-N", "-r", "-u", 514 ipath1, ipath2, NULL); 515 break; 516 case TYPE_RDIFF: 517 printf("diff -N -r -u %s %s\n", ipath2, ipath1); 518 fflush(stdout); 519 runcmd(fileno(fp), "/usr/bin/diff", "diff", "-N", "-r", "-u", 520 ipath2, ipath1, NULL); 521 break; 522 case TYPE_HISTORY: 523 t = (time_t)ts1.time32; 524 tp = localtime(&t); 525 strftime(datestr, sizeof(datestr), "%d-%b-%Y %H:%M:%S", tp); 526 printf("\t0x%016jx %s", (uintmax_t)ts1.tid, datestr); 527 if (flags & UNDO_FLAG_INOCHG) 528 printf(" inode-change"); 529 if (lstat(ipath1, &st) < 0) 530 printf(" file-deleted"); 531 printf("\n"); 532 break; 533 } 534 535 if (fp != stdout) 536 fclose(fp); 537 } 538 539 static 540 void 541 clean_tree(struct undo_hist_entry_rb_tree *tree) 542 { 543 struct undo_hist_entry *tse; 544 545 while ((tse = RB_ROOT(tree)) != NULL) { 546 RB_REMOVE(undo_hist_entry_rb_tree, tree, tse); 547 free(tse); 548 } 549 } 550 551 static 552 void 553 __collect_history(int fd, int *errorp, struct undo_hist_entry_rb_tree *tse_tree) 554 { 555 struct hammer_ioc_history hist; 556 struct undo_hist_entry *tse; 557 struct stat st; 558 int i; 559 560 /* 561 * Setup 562 */ 563 bzero(&hist, sizeof(hist)); 564 hist.beg_tid = HAMMER_MIN_TID; 565 hist.end_tid = HAMMER_MAX_TID; 566 hist.head.flags |= HAMMER_IOC_HISTORY_ATKEY; 567 hist.key = 0; 568 hist.nxt_key = HAMMER_MAX_KEY; 569 570 *errorp = 0; 571 572 /* 573 * Save the inode so inode changes can be reported. 574 */ 575 st.st_ino = 0; 576 fstat(fd, &st); 577 578 /* 579 * Collect a unique set of transaction ids 580 */ 581 if (ioctl(fd, HAMMERIOC_GETHISTORY, &hist) < 0) { 582 *errorp = errno; 583 return; 584 } 585 for (;;) { 586 for (i = 0; i < hist.count; ++i) { 587 tse = malloc(sizeof(*tse)); 588 tse->tse = hist.hist_ary[i]; 589 tse->inum = st.st_ino; 590 if (RB_INSERT(undo_hist_entry_rb_tree, tse_tree, tse)) { 591 free(tse); 592 } 593 } 594 if (hist.head.flags & HAMMER_IOC_HISTORY_EOF) 595 break; 596 if (hist.head.flags & HAMMER_IOC_HISTORY_NEXT_KEY) { 597 hist.key = hist.nxt_key; 598 hist.nxt_key = HAMMER_MAX_KEY; 599 } 600 if (hist.head.flags & HAMMER_IOC_HISTORY_NEXT_TID) 601 hist.beg_tid = hist.nxt_tid; 602 if (ioctl(fd, HAMMERIOC_GETHISTORY, &hist) < 0) { 603 *errorp = errno; 604 break; 605 } 606 } 607 } 608 609 static 610 void 611 collect_history(const char *filename, int *errorp, 612 struct undo_hist_entry_rb_tree *dir_tree) 613 { 614 int fd; 615 616 fd = open(filename, O_RDONLY); 617 if (fd == -1) { 618 *errorp = errno; 619 return; 620 } 621 __collect_history(fd, errorp, dir_tree); 622 close(fd); 623 } 624 625 static 626 void 627 collect_dir_history(const char *filename, int *errorp, 628 struct undo_hist_entry_rb_tree *dir_tree) 629 { 630 char *dirname; 631 632 if (strrchr(filename, '/')) { 633 dirname = strdup(filename); 634 *strrchr(dirname, '/') = 0; 635 } else { 636 dirname = strdup("."); 637 } 638 639 collect_history(dirname, errorp, dir_tree); 640 free(dirname); 641 } 642 643 static 644 hammer_tid_t 645 parse_delta_time(const char *timeStr, int *flags, int ind_flag) 646 { 647 hammer_tid_t tid; 648 649 tid = strtoull(timeStr, NULL, 0); 650 if (timeStr[0] == '+') 651 ++timeStr; 652 if (timeStr[0] >= '0' && timeStr[0] <= '9' && timeStr[1] != 'x') 653 *flags |= ind_flag; 654 return(tid); 655 } 656 657 static 658 FILE* 659 _fopen(const char *filename, const char *mode) 660 { 661 FILE *fp; 662 663 fp = fopen(filename, mode); 664 if (fp == NULL) 665 err(1, "%s", filename); 666 return(fp); 667 } 668 669 static void 670 runcmd(int fd, const char *cmd, ...) 671 { 672 va_list va; 673 pid_t pid; 674 char **av; 675 int ac; 676 int i; 677 678 va_start(va, cmd); 679 for (ac = 0; va_arg(va, void *) != NULL; ++ac) 680 ; 681 va_end(va); 682 683 av = malloc((ac + 1) * sizeof(char *)); 684 va_start(va, cmd); 685 for (i = 0; i < ac; ++i) 686 av[i] = va_arg(va, char *); 687 va_end(va); 688 av[i] = NULL; 689 690 if ((pid = fork()) < 0) { 691 err(1, "fork"); 692 } else if (pid == 0) { 693 if (fd != 1) { 694 dup2(fd, 1); 695 close(fd); 696 } 697 execv(cmd, av); 698 _exit(1); 699 } else { 700 while (waitpid(pid, NULL, 0) != pid) 701 ; 702 } 703 free(av); 704 } 705 706 /* 707 * Convert tid to timestamp. 708 */ 709 static char * 710 timestamp(struct hammer_ioc_hist_entry *hen) 711 { 712 static char timebuf[64]; 713 time_t t = (time_t)hen->time32; 714 struct tm *tp; 715 716 tp = localtime(&t); 717 strftime(timebuf, sizeof(timebuf), "%d-%b-%Y %H:%M:%S", tp); 718 return(timebuf); 719 } 720 721 static 722 int 723 undo_hist_entry_compare(struct undo_hist_entry *he1, 724 struct undo_hist_entry *he2) 725 { 726 if (he1->tse.tid < he2->tse.tid) 727 return(-1); 728 if (he1->tse.tid > he2->tse.tid) 729 return(1); 730 return(0); 731 } 732 733 static void 734 usage(void) 735 { 736 fprintf(stderr, "undo [-adDiuv] [-o outfile] " 737 "[-t transaction-id] [-t transaction-id] path...\n" 738 " -a Iterate all historical segments\n" 739 " -d Forward diff\n" 740 " -D Reverse diff\n" 741 " -i Dump history transaction ids\n" 742 " -u Generate .undo files\n" 743 " -v Verbose\n" 744 " -o file Output to the specified file\n" 745 " -t TID Retrieve as of transaction-id, TID\n" 746 " (a second `-t TID' to diff two)\n" 747 " transaction ids must be prefixed with 0x, and\n" 748 " otherwise may specify an index starting at 0\n" 749 " and iterating backwards through the history.\n" 750 ); 751 exit(1); 752 } 753 754