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 <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 struct hammer_ioc_hist_entry ts1, 61 struct hammer_ioc_hist_entry ts2, 62 int force); 63 static struct hammer_ioc_hist_entry 64 find_recent(const char *filename); 65 static struct hammer_ioc_hist_entry 66 output_history(const char *filename, int fd, FILE *fp, 67 struct hammer_ioc_hist_entry **hist_ary, int *tid_num); 68 static hammer_tid_t parse_delta_time(const char *timeStr); 69 static void runcmd(int fd, const char *cmd, ...); 70 static char *timestamp(hammer_ioc_hist_entry_t hen); 71 static void usage(void); 72 73 static int VerboseOpt; 74 75 int 76 main(int ac, char **av) 77 { 78 const char *outFileName = NULL; 79 const char *outFilePostfix = NULL; 80 enum { CMD_DUMP, CMD_ITERATEALL } cmd; 81 enum undo_type type; 82 struct hammer_ioc_hist_entry ts1; 83 struct hammer_ioc_hist_entry ts2; 84 int c; 85 int mult; 86 87 bzero(&ts1, sizeof(ts1)); 88 bzero(&ts2, sizeof(ts2)); 89 90 cmd = CMD_DUMP; 91 type = TYPE_FILE; 92 93 while ((c = getopt(ac, av, "adDiuvo:t:")) != -1) { 94 switch(c) { 95 case 'd': 96 if (type != TYPE_FILE) 97 usage(); 98 type = TYPE_DIFF; 99 break; 100 case 'D': 101 if (type != TYPE_FILE) 102 usage(); 103 type = TYPE_RDIFF; 104 break; 105 case 'i': 106 if (type != TYPE_FILE) 107 usage(); 108 type = TYPE_HISTORY; 109 break; 110 case 'a': 111 cmd = CMD_ITERATEALL; 112 break; 113 case 'u': 114 outFilePostfix = ".undo"; 115 break; 116 case 'v': 117 ++VerboseOpt; 118 break; 119 case 'o': 120 outFileName = optarg; 121 break; 122 case 't': 123 if (ts1.tid && ts2.tid) 124 usage(); 125 else if (ts1.tid == 0) 126 ts1.tid = parse_delta_time(optarg); 127 else 128 ts2.tid = parse_delta_time(optarg); 129 break; 130 default: 131 usage(); 132 /* NOT REACHED */ 133 break; 134 } 135 } 136 137 /* 138 * Option validation 139 */ 140 if (outFileName && outFilePostfix) { 141 fprintf(stderr, "The -o option may not be combined with -u\n"); 142 usage(); 143 } 144 145 ac -= optind; 146 av += optind; 147 mult = (ac > 1); 148 149 if (ac == 0) 150 usage(); 151 152 /* 153 * Validate the output template, if specified. 154 */ 155 if (outFileName && mult) { 156 const char *ptr = outFileName; 157 int didStr = 0; 158 159 while ((ptr = strchr(ptr, '%')) != NULL) { 160 if (ptr[1] == 's') { 161 if (didStr) { 162 fprintf(stderr, "Malformed output " 163 "template\n"); 164 usage(); 165 } 166 didStr = 1; 167 ++ptr; 168 } else if (ptr[1] != '%') { 169 fprintf(stderr, "Malformed output template\n"); 170 usage(); 171 } else { 172 ptr += 2; 173 } 174 } 175 } 176 177 while (ac) { 178 switch(cmd) { 179 case CMD_DUMP: 180 dogenerate(*av, outFileName, outFilePostfix, 181 mult, -1, type, ts1, ts2, 1); 182 break; 183 case CMD_ITERATEALL: 184 doiterate(*av, outFileName, outFilePostfix, 185 mult, type); 186 break; 187 } 188 ++av; 189 --ac; 190 } 191 return(0); 192 } 193 194 /* 195 * Iterate through a file's history 196 */ 197 static 198 void 199 doiterate(const char *orig_filename, const char *outFileName, 200 const char *outFilePostfix, int mult, enum undo_type type) 201 { 202 hammer_ioc_hist_entry_t tid_ary = NULL; 203 struct hammer_ioc_hist_entry tid_max; 204 struct hammer_ioc_hist_entry ts1; 205 const char *use_filename; 206 char *path = NULL; 207 int tid_num = 0; 208 int i; 209 int fd; 210 211 tid_max.tid = HAMMER_MAX_TID; 212 tid_max.time32 = 0; 213 214 use_filename = orig_filename; 215 if ((fd = open(orig_filename, O_RDONLY)) < 0) { 216 ts1 = find_recent(orig_filename); 217 if (ts1.tid) { 218 asprintf(&path, "%s@@0x%016llx", 219 orig_filename, ts1.tid); 220 use_filename = path; 221 } 222 } 223 224 if ((fd = open(use_filename, O_RDONLY)) >= 0) { 225 printf("%s: ITERATE ENTIRE HISTORY\n", orig_filename); 226 output_history(NULL, fd, NULL, &tid_ary, &tid_num); 227 close(fd); 228 229 for (i = 0; i < tid_num; ++i) { 230 if (i && tid_ary[i].tid == tid_ary[i-1].tid) 231 continue; 232 233 if (i == tid_num - 1) { 234 dogenerate(orig_filename, 235 outFileName, outFilePostfix, 236 mult, i, type, 237 tid_ary[i], tid_max, 0); 238 } else { 239 dogenerate(orig_filename, 240 outFileName, outFilePostfix, 241 mult, i, type, 242 tid_ary[i], tid_ary[i+1], 0); 243 } 244 } 245 246 } else { 247 printf("%s: ITERATE ENTIRE HISTORY: %s\n", 248 orig_filename, strerror(errno)); 249 } 250 if (path) 251 free(path); 252 } 253 254 /* 255 * Generate output for a file as-of ts1 (ts1 may be 0!), if diffing then 256 * through ts2. 257 */ 258 static 259 void 260 dogenerate(const char *filename, const char *outFileName, 261 const char *outFilePostfix, 262 int mult, int idx, enum undo_type type, 263 struct hammer_ioc_hist_entry ts1, 264 struct hammer_ioc_hist_entry ts2, 265 int force) 266 { 267 struct stat st; 268 const char *elm; 269 char *ipath1 = NULL; 270 char *ipath2 = NULL; 271 FILE *fi; 272 FILE *fp; 273 char *buf; 274 char *path; 275 int n; 276 277 buf = malloc(8192); 278 279 /* 280 * Open the input file. If ts1 is 0 try to locate the most recent 281 * version of the file prior to the current version. 282 */ 283 if (ts1.tid == 0) 284 ts1 = find_recent(filename); 285 asprintf(&ipath1, "%s@@0x%016llx", filename, ts1.tid); 286 if (lstat(ipath1, &st) < 0) { 287 free(ipath1); 288 asprintf(&ipath1, "%s", filename); 289 if (force == 0 || lstat(ipath1, &st) < 0) { 290 fprintf(stderr, "Cannot locate src/historical " 291 "idx=%d %s@@0x%016llx,\n" 292 "the file may have been renamed " 293 "in the past.\n", 294 idx, filename, ts1.tid); 295 goto done; 296 } 297 fprintf(stderr, 298 "WARNING: %s was renamed at some point in the past,\n" 299 "attempting to continue with current version\n", 300 filename); 301 } 302 303 if (ts2.tid == 0) { 304 asprintf(&ipath2, "%s", filename); 305 } else { 306 asprintf(&ipath2, "%s@@0x%015llx", filename, ts2.tid); 307 } 308 if (lstat(ipath2, &st) < 0) { 309 if (VerboseOpt) { 310 if (ts2.tid) { 311 fprintf(stderr, "Cannot locate tgt/historical " 312 "idx=%d %s\n", 313 idx, ipath2); 314 } else if (VerboseOpt > 1) { 315 fprintf(stderr, "Cannot locate %s\n", filename); 316 } 317 } 318 ipath2 = strdup("/dev/null"); 319 } 320 321 /* 322 * elm is the last component of the input file name 323 */ 324 if ((elm = strrchr(filename, '/')) != NULL) 325 ++elm; 326 else 327 elm = filename; 328 329 /* 330 * Where do we stuff our output? 331 */ 332 if (outFileName) { 333 if (mult) { 334 asprintf(&path, outFileName, elm); 335 fp = fopen(path, "w"); 336 if (fp == NULL) { 337 perror(path); 338 exit(1); 339 } 340 free(path); 341 } else { 342 fp = fopen(outFileName, "w"); 343 if (fp == NULL) { 344 perror(outFileName); 345 exit(1); 346 } 347 } 348 } else if (outFilePostfix) { 349 if (idx >= 0) { 350 asprintf(&path, "%s%s.%04d", filename, 351 outFilePostfix, idx); 352 } else { 353 asprintf(&path, "%s%s", filename, outFilePostfix); 354 } 355 fp = fopen(path, "w"); 356 if (fp == NULL) { 357 perror(path); 358 exit(1); 359 } 360 free(path); 361 } else { 362 if (mult && type == TYPE_FILE) { 363 if (idx >= 0) { 364 printf("\n>>> %s %04d 0x%016llx %s\n\n", 365 filename, idx, ts1.tid, timestamp(&ts1)); 366 } else { 367 printf("\n>>> %s ---- 0x%016llx %s\n\n", 368 filename, ts1.tid, timestamp(&ts1)); 369 } 370 } else if (idx >= 0 && type == TYPE_FILE) { 371 printf("\n>>> %s %04d 0x%016llx %s\n\n", 372 filename, idx, ts1.tid, timestamp(&ts1)); 373 } 374 fp = stdout; 375 } 376 377 switch(type) { 378 case TYPE_FILE: 379 if ((fi = fopen(ipath1, "r")) != NULL) { 380 while ((n = fread(buf, 1, 8192, fi)) > 0) 381 fwrite(buf, 1, n, fp); 382 fclose(fi); 383 } 384 break; 385 case TYPE_DIFF: 386 printf("diff -u %s %s (to %s)\n", 387 ipath1, ipath2, timestamp(&ts2)); 388 fflush(stdout); 389 runcmd(fileno(fp), "/usr/bin/diff", "diff", "-u", ipath1, ipath2, NULL); 390 break; 391 case TYPE_RDIFF: 392 printf("diff -u %s %s\n", ipath2, ipath1); 393 fflush(stdout); 394 runcmd(fileno(fp), "/usr/bin/diff", "diff", "-u", ipath2, ipath1, NULL); 395 break; 396 case TYPE_HISTORY: 397 if ((fi = fopen(ipath1, "r")) != NULL) { 398 output_history(filename, fileno(fi), fp, NULL, NULL); 399 fclose(fi); 400 } 401 break; 402 } 403 404 if (fp != stdout) 405 fclose(fp); 406 done: 407 free(buf); 408 } 409 410 /* 411 * Try to find a recent version of the file. 412 * 413 * XXX if file cannot be found 414 */ 415 static 416 struct hammer_ioc_hist_entry 417 find_recent(const char *filename) 418 { 419 hammer_ioc_hist_entry_t tid_ary = NULL; 420 int tid_num = 0; 421 struct hammer_ioc_hist_entry hen; 422 char *dirname; 423 char *path; 424 int fd; 425 int i; 426 427 if ((fd = open(filename, O_RDONLY)) >= 0) { 428 hen = output_history(NULL, fd, NULL, NULL, NULL); 429 close(fd); 430 return(hen); 431 } 432 433 /* 434 * If the object does not exist acquire the history of its 435 * directory and then try accessing the object at each TID. 436 */ 437 if (strrchr(filename, '/')) { 438 dirname = strdup(filename); 439 *strrchr(dirname, '/') = 0; 440 } else { 441 dirname = strdup("."); 442 } 443 444 hen.tid = 0; 445 hen.time32 = 0; 446 if ((fd = open(dirname, O_RDONLY)) >= 0) { 447 output_history(NULL, fd, NULL, &tid_ary, &tid_num); 448 close(fd); 449 free(dirname); 450 451 for (i = tid_num - 1; i >= 0; --i) { 452 asprintf(&path, "%s@@0x%016llx", filename, tid_ary[i].tid); 453 if ((fd = open(path, O_RDONLY)) >= 0) { 454 hen = output_history(NULL, fd, NULL, NULL, NULL); 455 close(fd); 456 free(path); 457 break; 458 } 459 free(path); 460 } 461 } 462 return(hen); 463 } 464 465 /* 466 * Collect all the transaction ids representing changes made to the 467 * file, sort, and output (weeding out duplicates). If fp is NULL 468 * we do not output anything and simply return the most recent TID we 469 * can find. 470 */ 471 static int 472 tid_cmp(const void *arg1, const void *arg2) 473 { 474 const struct hammer_ioc_hist_entry *tid1 = arg1; 475 const struct hammer_ioc_hist_entry *tid2 = arg2; 476 477 if (tid1->tid < tid2->tid) 478 return(-1); 479 if (tid1->tid > tid2->tid) 480 return(1); 481 return(0); 482 } 483 484 static 485 struct hammer_ioc_hist_entry 486 output_history(const char *filename, int fd, FILE *fp, 487 struct hammer_ioc_hist_entry **hist_aryp, int *tid_nump) 488 { 489 struct hammer_ioc_hist_entry hen; 490 struct hammer_ioc_history hist; 491 char datestr[64]; 492 struct tm *tp; 493 time_t t; 494 int tid_max = 32; 495 int tid_num = 0; 496 int i; 497 hammer_ioc_hist_entry_t hist_ary = malloc(tid_max * sizeof(*hist_ary)); 498 499 bzero(&hist, sizeof(hist)); 500 hist.beg_tid = HAMMER_MIN_TID; 501 hist.end_tid = HAMMER_MAX_TID; 502 hist.head.flags |= HAMMER_IOC_HISTORY_ATKEY; 503 hist.key = 0; 504 hist.nxt_key = HAMMER_MAX_KEY; 505 506 hen.tid = 0; 507 hen.time32 = 0; 508 509 if (ioctl(fd, HAMMERIOC_GETHISTORY, &hist) < 0) { 510 if (filename) 511 printf("%s: %s\n", filename, strerror(errno)); 512 goto done; 513 } 514 if (filename) 515 printf("%s: objid=0x%016llx\n", filename, hist.obj_id); 516 for (;;) { 517 if (tid_num + hist.count >= tid_max) { 518 tid_max = (tid_max * 3 / 2) + hist.count; 519 hist_ary = realloc(hist_ary, tid_max * sizeof(*hist_ary)); 520 } 521 for (i = 0; i < hist.count; ++i) { 522 hist_ary[tid_num++] = hist.hist_ary[i]; 523 } 524 if (hist.head.flags & HAMMER_IOC_HISTORY_EOF) 525 break; 526 if (hist.head.flags & HAMMER_IOC_HISTORY_NEXT_KEY) { 527 hist.key = hist.nxt_key; 528 hist.nxt_key = HAMMER_MAX_KEY; 529 } 530 if (hist.head.flags & HAMMER_IOC_HISTORY_NEXT_TID) 531 hist.beg_tid = hist.nxt_tid; 532 if (ioctl(fd, HAMMERIOC_GETHISTORY, &hist) < 0) { 533 if (filename) 534 printf("%s: %s\n", filename, strerror(errno)); 535 break; 536 } 537 } 538 qsort(hist_ary, tid_num, sizeof(*hist_ary), tid_cmp); 539 if (tid_num == 0) 540 goto done; 541 542 /* 543 * If fp != NULL dump the history to stdout. 544 */ 545 for (i = 0; fp && i < tid_num; ++i) { 546 if (i && hist_ary[i].tid == hist_ary[i-1].tid) 547 continue; 548 t = (time_t)hist_ary[i].time32; 549 tp = localtime(&t); 550 strftime(datestr, sizeof(datestr), "%d-%b-%Y %H:%M:%S", tp); 551 printf("\t0x%016llx %s\n", hist_ary[i].tid, datestr); 552 } 553 554 /* 555 * Locate the next-to-last entry, ignoring any duplicates. 556 */ 557 if (tid_num > 1) { 558 for (i = tid_num - 2; i > 0; --i) { 559 if (hist_ary[i].tid != hist_ary[tid_num-1].tid) 560 break; 561 } 562 hen = hist_ary[i]; 563 } 564 done: 565 if (hist_aryp) { 566 *hist_aryp = hist_ary; 567 *tid_nump = tid_num; 568 } else { 569 free(hist_ary); 570 } 571 return(hen); 572 } 573 574 static 575 hammer_tid_t 576 parse_delta_time(const char *timeStr) 577 { 578 hammer_tid_t tid; 579 580 tid = strtoull(timeStr, NULL, 0); 581 return(tid); 582 } 583 584 static void 585 runcmd(int fd, const char *cmd, ...) 586 { 587 va_list va; 588 pid_t pid; 589 char **av; 590 int ac; 591 int i; 592 593 va_start(va, cmd); 594 for (ac = 0; va_arg(va, void *) != NULL; ++ac) 595 ; 596 va_end(va); 597 598 av = malloc((ac + 1) * sizeof(char *)); 599 va_start(va, cmd); 600 for (i = 0; i < ac; ++i) 601 av[i] = va_arg(va, char *); 602 va_end(va); 603 av[i] = NULL; 604 605 if ((pid = fork()) < 0) { 606 perror("fork"); 607 exit(1); 608 } else if (pid == 0) { 609 if (fd != 1) { 610 dup2(fd, 1); 611 close(fd); 612 } 613 execv(cmd, av); 614 _exit(1); 615 } else { 616 while (waitpid(pid, NULL, 0) != pid) 617 ; 618 } 619 free(av); 620 } 621 622 /* 623 * Convert tid to timestamp. 624 */ 625 static char * 626 timestamp(hammer_ioc_hist_entry_t hen) 627 { 628 static char timebuf[64]; 629 time_t t = (time_t)hen->time32; 630 struct tm *tp; 631 632 tp = localtime(&t); 633 strftime(timebuf, sizeof(timebuf), "%d-%b-%Y %H:%M:%S", tp); 634 return(timebuf); 635 } 636 637 static void 638 usage(void) 639 { 640 fprintf(stderr, "undo [-adDiuv] [-o outfile] " 641 "[-t transaction-id] [-t transaction-id] file...\n" 642 " -a Iterate all historical segments\n" 643 " -d Forward diff\n" 644 " -D Reverse diff\n" 645 " -i Dump history transaction ids\n" 646 " -u Generate .undo files\n" 647 " -v Verbose\n" 648 " -o file Output to the specified file\n" 649 " -t TID Retrieve as of transaction-id, TID\n" 650 " (a second `-t TID' to diff two versions)\n"); 651 exit(1); 652 } 653 654