Silence -Wold-style-definition.
[dragonfly.git] / usr.bin / undo / undo.c
blobe297c1d8c03f44c944253e98823e2ed60c34e88a
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:
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.
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.
34 * $DragonFly: src/usr.bin/undo/undo.c,v 1.6 2008/07/17 21:34:47 thomas Exp $
37 * UNDO - retrieve an older version of a file.
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>
53 enum undo_type { TYPE_FILE, TYPE_DIFF, TYPE_RDIFF, TYPE_HISTORY };
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);
73 static int VerboseOpt;
75 int
76 main(int ac, char **av)
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;
87 bzero(&ts1, sizeof(ts1));
88 bzero(&ts2, sizeof(ts2));
90 cmd = CMD_DUMP;
91 type = TYPE_FILE;
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;
138 * Option validation
140 if (outFileName && outFilePostfix) {
141 fprintf(stderr, "The -o option may not be combined with -u\n");
142 usage();
145 ac -= optind;
146 av += optind;
147 mult = (ac > 1);
149 if (ac == 0)
150 usage();
153 * Validate the output template, if specified.
155 if (outFileName && mult) {
156 const char *ptr = outFileName;
157 int didStr = 0;
159 while ((ptr = strchr(ptr, '%')) != NULL) {
160 if (ptr[1] == 's') {
161 if (didStr) {
162 fprintf(stderr, "Malformed output "
163 "template\n");
164 usage();
166 didStr = 1;
167 ++ptr;
168 } else if (ptr[1] != '%') {
169 fprintf(stderr, "Malformed output template\n");
170 usage();
171 } else {
172 ptr += 2;
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;
188 ++av;
189 --ac;
191 return(0);
195 * Iterate through a file's history
197 static
198 void
199 doiterate(const char *orig_filename, const char *outFileName,
200 const char *outFilePostfix, int mult, enum undo_type type)
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;
211 tid_max.tid = HAMMER_MAX_TID;
212 tid_max.time32 = 0;
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;
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);
229 for (i = 0; i < tid_num; ++i) {
230 if (i && tid_ary[i].tid == tid_ary[i-1].tid)
231 continue;
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);
246 } else {
247 printf("%s: ITERATE ENTIRE HISTORY: %s\n",
248 orig_filename, strerror(errno));
250 if (path)
251 free(path);
255 * Generate output for a file as-of ts1 (ts1 may be 0!), if diffing then
256 * through ts2.
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)
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;
277 buf = malloc(8192);
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.
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;
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);
303 if (ts2.tid == 0) {
304 asprintf(&ipath2, "%s", filename);
305 } else {
306 asprintf(&ipath2, "%s@@0x%015llx", filename, ts2.tid);
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);
318 ipath2 = strdup("/dev/null");
322 * elm is the last component of the input file name
324 if ((elm = strrchr(filename, '/')) != NULL)
325 ++elm;
326 else
327 elm = filename;
330 * Where do we stuff our output?
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);
340 free(path);
341 } else {
342 fp = fopen(outFileName, "w");
343 if (fp == NULL) {
344 perror(outFileName);
345 exit(1);
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);
355 fp = fopen(path, "w");
356 if (fp == NULL) {
357 perror(path);
358 exit(1);
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));
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));
374 fp = stdout;
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);
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);
401 break;
404 if (fp != stdout)
405 fclose(fp);
406 done:
407 free(buf);
411 * Try to find a recent version of the file.
413 * XXX if file cannot be found
415 static
416 struct hammer_ioc_hist_entry
417 find_recent(const char *filename)
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;
427 if ((fd = open(filename, O_RDONLY)) >= 0) {
428 hen = output_history(NULL, fd, NULL, NULL, NULL);
429 close(fd);
430 return(hen);
434 * If the object does not exist acquire the history of its
435 * directory and then try accessing the object at each TID.
437 if (strrchr(filename, '/')) {
438 dirname = strdup(filename);
439 *strrchr(dirname, '/') = 0;
440 } else {
441 dirname = strdup(".");
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);
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;
459 free(path);
462 return(hen);
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.
471 static int
472 tid_cmp(const void *arg1, const void *arg2)
474 const struct hammer_ioc_hist_entry *tid1 = arg1;
475 const struct hammer_ioc_hist_entry *tid2 = arg2;
477 if (tid1->tid < tid2->tid)
478 return(-1);
479 if (tid1->tid > tid2->tid)
480 return(1);
481 return(0);
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)
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));
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;
506 hen.tid = 0;
507 hen.time32 = 0;
509 if (ioctl(fd, HAMMERIOC_GETHISTORY, &hist) < 0) {
510 if (filename)
511 printf("%s: %s\n", filename, strerror(errno));
512 goto done;
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));
521 for (i = 0; i < hist.count; ++i) {
522 hist_ary[tid_num++] = hist.hist_ary[i];
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;
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;
538 qsort(hist_ary, tid_num, sizeof(*hist_ary), tid_cmp);
539 if (tid_num == 0)
540 goto done;
541 for (i = 0; fp && i < tid_num; ++i) {
542 if (i && hist_ary[i].tid == hist_ary[i-1].tid)
543 continue;
544 t = (time_t)hist_ary[i].time32;
545 tp = localtime(&t);
546 strftime(datestr, sizeof(datestr), "%d-%b-%Y %H:%M:%S", tp);
547 printf("\t0x%016llx %s\n", hist_ary[i].tid, datestr);
549 if (tid_num > 1)
550 hen = hist_ary[tid_num-2];
551 done:
552 if (hist_aryp) {
553 *hist_aryp = hist_ary;
554 *tid_nump = tid_num;
555 } else {
556 free(hist_ary);
558 return(hen);
561 static
562 hammer_tid_t
563 parse_delta_time(const char *timeStr)
565 hammer_tid_t tid;
567 tid = strtoull(timeStr, NULL, 0);
568 return(tid);
571 static void
572 runcmd(int fd, const char *cmd, ...)
574 va_list va;
575 pid_t pid;
576 char **av;
577 int ac;
578 int i;
580 va_start(va, cmd);
581 for (ac = 0; va_arg(va, void *) != NULL; ++ac)
583 va_end(va);
585 av = malloc((ac + 1) * sizeof(char *));
586 va_start(va, cmd);
587 for (i = 0; i < ac; ++i)
588 av[i] = va_arg(va, char *);
589 va_end(va);
590 av[i] = NULL;
592 if ((pid = fork()) < 0) {
593 perror("fork");
594 exit(1);
595 } else if (pid == 0) {
596 if (fd != 1) {
597 dup2(fd, 1);
598 close(fd);
600 execv(cmd, av);
601 _exit(1);
602 } else {
603 while (waitpid(pid, NULL, 0) != pid)
606 free(av);
610 * Convert tid to timestamp.
612 static char *
613 timestamp(hammer_ioc_hist_entry_t hen)
615 static char timebuf[64];
616 time_t t = (time_t)hen->time32;
617 struct tm *tp;
619 tp = localtime(&t);
620 strftime(timebuf, sizeof(timebuf), "%d-%b-%Y %H:%M:%S", tp);
621 return(timebuf);
624 static void
625 usage(void)
627 fprintf(stderr, "undo [-adDiuv] [-o outfile] "
628 "[-t transaction-id] [-t transaction-id] file...\n"
629 " -a Iterate all historical segments\n"
630 " -d Forward diff\n"
631 " -D Reverse diff\n"
632 " -i Dump history transaction ids\n"
633 " -u Generate .undo files\n"
634 " -v Verbose\n"
635 " -o file Output to the specified file\n"
636 " -t TID Retrieve as of transaction-id, TID\n"
637 " (a second `-t TID' to diff two versions)\n");
638 exit(1);