2 * Copyright (c) 2008 The DragonFly Project. All rights reserved.
4 * This code is derived from software contributed to The DragonFly Project
5 * by Matthew Dillon <dillon@backplane.com>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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
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
34 * $DragonFly: src/usr.bin/undo/undo.c,v 1.1 2008/06/01 02:03:10 dillon Exp $
37 * UNDO - retrieve an older version of a file.
40 #include <sys/types.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 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);
69 static int VerboseOpt;
72 main(int ac, char **av)
74 const char *outFileName = NULL;
75 const char *outFilePostfix = NULL;
76 enum { CMD_DUMP, CMD_ITERATEALL } cmd;
86 while ((c = getopt(ac, av, "dDhiuvo:t:")) != -1) {
89 if (type != TYPE_FILE)
94 if (type != TYPE_FILE)
99 if (type != TYPE_FILE)
104 cmd = CMD_ITERATEALL;
107 outFilePostfix = ".undo";
113 outFileName = optarg;
119 ts1 = parse_delta_time(optarg);
121 ts2 = parse_delta_time(optarg);
133 if (outFileName && outFilePostfix) {
134 fprintf(stderr, "The -o option may not be combined with -u\n");
146 * Validate the output template, if specified.
148 if (outFileName && mult) {
149 const char *ptr = outFileName;
152 while ((ptr = strchr(ptr, '%')) != NULL) {
155 fprintf(stderr, "Malformed output "
161 } else if (ptr[1] != '%') {
162 fprintf(stderr, "Malformed output template\n");
173 dogenerate(*av, outFileName, outFilePostfix,
178 doiterate(*av, outFileName, outFilePostfix,
189 * Iterate through a file's history
193 doiterate(const char *orig_filename, const char *outFileName,
194 const char *outFilePostfix, int mult, enum undo_type type)
196 hammer_tid_t *tid_ary = NULL;
198 const char *use_filename;
204 use_filename = orig_filename;
205 if ((fd = open(orig_filename, O_RDONLY)) < 0) {
206 ts1 = find_recent(orig_filename);
208 asprintf(&path, "%s@@0x%016llx", orig_filename, ts1);
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);
218 for (i = 0; i < tid_num; ++i) {
219 if (i && tid_ary[i] == tid_ary[i-1])
222 if (i == tid_num - 1) {
223 dogenerate(orig_filename,
224 outFileName, outFilePostfix,
226 tid_ary[i], HAMMER_MAX_TID);
228 dogenerate(orig_filename,
229 outFileName, outFilePostfix,
231 tid_ary[i], tid_ary[i+1]);
236 printf("%s: ITERATE ENTIRE HISTORY: %s\n",
237 orig_filename, strerror(errno));
244 * Generate output for a file as-of ts1 (ts1 may be 0!), if diffing then
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)
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.
271 ts1 = find_recent(filename);
272 asprintf(&ipath1, "%s@@0x%016llx", filename, ts1);
273 if (lstat(ipath1, &st) < 0) {
275 fprintf(stderr, "Cannot locate src/historical "
283 asprintf(&ipath2, "%s", filename);
285 asprintf(&ipath2, "%s@@0x%015llx", filename, ts2);
287 if (lstat(ipath2, &st) < 0) {
290 fprintf(stderr, "Cannot locate tgt/historical "
293 } else if (VerboseOpt > 1) {
294 fprintf(stderr, "Cannot locate %s\n", filename);
297 ipath2 = strdup("/dev/null");
301 * elm is the last component of the input file name
303 if ((elm = strrchr(filename, '/')) != NULL)
309 * Where do we stuff our output?
313 asprintf(&path, outFileName, elm);
314 fp = fopen(path, "w");
321 fp = fopen(outFileName, "w");
327 } else if (outFilePostfix) {
329 asprintf(&path, "%s%s.%04d", filename,
330 outFilePostfix, idx);
332 asprintf(&path, "%s%s", filename, outFilePostfix);
334 fp = fopen(path, "w");
341 if (mult && type == TYPE_FILE) {
343 printf("\n>>> %s %04d 0x%016llx %s\n\n",
344 filename, idx, ts1, timestamp(ts1));
346 printf("\n>>> %s ---- 0x%016llx %s\n\n",
347 filename, ts1, timestamp(ts1));
349 } else if (idx >= 0 && type == TYPE_FILE) {
350 printf("\n>>> %s %04d 0x%016llx %s\n\n",
351 filename, idx, ts1, timestamp(ts1));
358 if ((fi = fopen(ipath1, "r")) != NULL) {
359 while ((n = fread(buf, 1, 8192, fi)) > 0)
360 fwrite(buf, 1, n, fp);
365 printf("diff -u %s %s\n", ipath1, ipath2);
367 runcmd(fileno(fp), "/usr/bin/diff", "diff", "-u", ipath1, ipath2, NULL);
370 printf("diff -u %s %s\n", ipath2, ipath1);
372 runcmd(fileno(fp), "/usr/bin/diff", "diff", "-u", ipath2, ipath1, NULL);
375 if ((fi = fopen(ipath1, "r")) != NULL) {
376 output_history(filename, fileno(fi), fp, NULL, NULL);
389 * Try to find a recent version of the file.
391 * XXX if file cannot be found
394 find_recent(const char *filename)
396 hammer_tid_t *tid_ary = NULL;
404 if ((fd = open(filename, O_RDONLY)) >= 0) {
405 tid = output_history(NULL, fd, NULL, NULL, NULL);
411 * If the object does not exist acquire the history of its
412 * directory and then try accessing the object at each TID.
414 if (strrchr(filename, '/')) {
415 dirname = strdup(filename);
416 *strrchr(dirname, '/') = 0;
418 dirname = strdup(".");
422 if ((fd = open(dirname, O_RDONLY)) >= 0) {
423 output_history(NULL, fd, NULL, &tid_ary, &tid_num);
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);
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
448 tid_cmp(const void *arg1, const void *arg2)
450 const hammer_tid_t *tid1 = arg1;
451 const hammer_tid_t *tid2 = arg2;
461 output_history(const char *filename, int fd, FILE *fp,
462 hammer_tid_t **tid_aryp, int *tid_nump)
464 struct hammer_ioc_history hist;
471 hammer_tid_t *tid_ary = malloc(tid_max * sizeof(*tid_ary));
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;
479 hist.nxt_key = HAMMER_MAX_KEY;
483 if (ioctl(fd, HAMMERIOC_GETHISTORY, &hist) < 0) {
485 printf("%s: %s\n", filename, strerror(errno));
489 printf("%s: objid=0x%016llx\n", filename, hist.obj_id);
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));
495 for (i = 0; i < hist.count; ++i) {
496 tid_ary[tid_num++] = hist.tid_ary[i];
498 if (hist.head.flags & HAMMER_IOC_HISTORY_EOF)
500 if (hist.head.flags & HAMMER_IOC_HISTORY_NEXT_KEY) {
501 hist.key = hist.nxt_key;
502 hist.nxt_key = HAMMER_MAX_KEY;
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) {
508 printf("%s: %s\n", filename, strerror(errno));
512 qsort(tid_ary, tid_num, sizeof(*tid_ary), tid_cmp);
515 for (i = 0; fp && i < tid_num; ++i) {
516 if (i && tid_ary[i] == tid_ary[i-1])
518 t = (time_t)(tid_ary[i] / 1000000000);
520 strftime(datestr, sizeof(datestr), "%d-%m-%Y %H:%M:%S", tp);
521 printf("\t0x%016llx %s\n", tid_ary[i], datestr);
524 tid = tid_ary[tid_num-2];
537 parse_delta_time(const char *timeStr)
542 if (timeStr[0] == '0' && (timeStr[1] == 'x' || timeStr[1] == 'X')) {
543 tid = strtoull(timeStr, NULL, 0);
545 tid = strtol(timeStr, &ptr, 0);
562 tid = time(NULL) - tid;
569 runcmd(int fd, const char *cmd, ...)
578 for (ac = 0; va_arg(va, void *) != NULL; ++ac)
582 av = malloc((ac + 1) * sizeof(char *));
584 for (i = 0; i < ac; ++i)
585 av[i] = va_arg(va, char *);
589 if ((pid = fork()) < 0) {
592 } else if (pid == 0) {
600 while (waitpid(pid, NULL, 0) != pid)
607 * Convert tid to timestamp.
610 timestamp(hammer_tid_t tid)
612 static char timebuf[64];
613 time_t t = (time_t)(tid / 1000000000);
617 strftime(timebuf, sizeof(timebuf), "%d-%m-%Y %H:%M:%S", tp);
624 fprintf(stderr, "undo [-dDhiuv] [-t n{s,m,h,d}] [-t n...] "
626 fprintf(stderr, " -d Forward diff\n"
628 " -i Dump history transaction ids\n"
629 " -h Iterate all historical segments\n"
630 " -u Generate .undo files\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");