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.2.2.1 2008/07/17 23:50:02 thomas 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 struct hammer_ioc_hist_entry ts1
,
61 struct hammer_ioc_hist_entry ts2
,
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
;
76 main(int ac
, char **av
)
78 const char *outFileName
= NULL
;
79 const char *outFilePostfix
= NULL
;
80 enum { CMD_DUMP
, CMD_ITERATEALL
} cmd
;
82 struct hammer_ioc_hist_entry ts1
;
83 struct hammer_ioc_hist_entry ts2
;
87 bzero(&ts1
, sizeof(ts1
));
88 bzero(&ts2
, sizeof(ts2
));
93 while ((c
= getopt(ac
, av
, "adDiuvo:t:")) != -1) {
96 if (type
!= TYPE_FILE
)
101 if (type
!= TYPE_FILE
)
106 if (type
!= TYPE_FILE
)
111 cmd
= CMD_ITERATEALL
;
114 outFilePostfix
= ".undo";
120 outFileName
= optarg
;
123 if (ts1
.tid
&& ts2
.tid
)
125 else if (ts1
.tid
== 0)
126 ts1
.tid
= parse_delta_time(optarg
);
128 ts2
.tid
= parse_delta_time(optarg
);
140 if (outFileName
&& outFilePostfix
) {
141 fprintf(stderr
, "The -o option may not be combined with -u\n");
153 * Validate the output template, if specified.
155 if (outFileName
&& mult
) {
156 const char *ptr
= outFileName
;
159 while ((ptr
= strchr(ptr
, '%')) != NULL
) {
162 fprintf(stderr
, "Malformed output "
168 } else if (ptr
[1] != '%') {
169 fprintf(stderr
, "Malformed output template\n");
180 dogenerate(*av
, outFileName
, outFilePostfix
,
181 mult
, -1, type
, ts1
, ts2
, 1);
184 doiterate(*av
, outFileName
, outFilePostfix
,
195 * Iterate through a file's history
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
;
211 tid_max
.tid
= HAMMER_MAX_TID
;
214 use_filename
= orig_filename
;
215 if ((fd
= open(orig_filename
, O_RDONLY
)) < 0) {
216 ts1
= find_recent(orig_filename
);
218 asprintf(&path
, "%s@@0x%016llx",
219 orig_filename
, ts1
.tid
);
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
);
229 for (i
= 0; i
< tid_num
; ++i
) {
230 if (i
&& tid_ary
[i
].tid
== tid_ary
[i
-1].tid
)
233 if (i
== tid_num
- 1) {
234 dogenerate(orig_filename
,
235 outFileName
, outFilePostfix
,
237 tid_ary
[i
], tid_max
, 0);
239 dogenerate(orig_filename
,
240 outFileName
, outFilePostfix
,
242 tid_ary
[i
], tid_ary
[i
+1], 0);
247 printf("%s: ITERATE ENTIRE HISTORY: %s\n",
248 orig_filename
, strerror(errno
));
255 * Generate output for a file as-of ts1 (ts1 may be 0!), if diffing then
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
,
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.
284 ts1
= find_recent(filename
);
285 asprintf(&ipath1
, "%s@@0x%016llx", filename
, ts1
.tid
);
286 if (lstat(ipath1
, &st
) < 0) {
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 "
294 idx
, filename
, ts1
.tid
);
298 "WARNING: %s was renamed at some point in the past,\n"
299 "attempting to continue with current version\n",
304 asprintf(&ipath2
, "%s", filename
);
306 asprintf(&ipath2
, "%s@@0x%015llx", filename
, ts2
.tid
);
308 if (lstat(ipath2
, &st
) < 0) {
311 fprintf(stderr
, "Cannot locate tgt/historical "
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
)
330 * Where do we stuff our output?
334 asprintf(&path
, outFileName
, elm
);
335 fp
= fopen(path
, "w");
342 fp
= fopen(outFileName
, "w");
348 } else if (outFilePostfix
) {
350 asprintf(&path
, "%s%s.%04d", filename
,
351 outFilePostfix
, idx
);
353 asprintf(&path
, "%s%s", filename
, outFilePostfix
);
355 fp
= fopen(path
, "w");
362 if (mult
&& type
== TYPE_FILE
) {
364 printf("\n>>> %s %04d 0x%016llx %s\n\n",
365 filename
, idx
, ts1
.tid
, timestamp(&ts1
));
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
));
379 if ((fi
= fopen(ipath1
, "r")) != NULL
) {
380 while ((n
= fread(buf
, 1, 8192, fi
)) > 0)
381 fwrite(buf
, 1, n
, fp
);
386 printf("diff -u %s %s (to %s)\n",
387 ipath1
, ipath2
, timestamp(&ts2
));
389 runcmd(fileno(fp
), "/usr/bin/diff", "diff", "-u", ipath1
, ipath2
, NULL
);
392 printf("diff -u %s %s\n", ipath2
, ipath1
);
394 runcmd(fileno(fp
), "/usr/bin/diff", "diff", "-u", ipath2
, ipath1
, NULL
);
397 if ((fi
= fopen(ipath1
, "r")) != NULL
) {
398 output_history(filename
, fileno(fi
), fp
, NULL
, NULL
);
411 * Try to find a recent version of the file.
413 * XXX if file cannot be found
416 struct hammer_ioc_hist_entry
417 find_recent(const char *filename
)
419 hammer_ioc_hist_entry_t tid_ary
= NULL
;
421 struct hammer_ioc_hist_entry hen
;
427 if ((fd
= open(filename
, O_RDONLY
)) >= 0) {
428 hen
= output_history(NULL
, fd
, NULL
, NULL
, NULL
);
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;
441 dirname
= strdup(".");
446 if ((fd
= open(dirname
, O_RDONLY
)) >= 0) {
447 output_history(NULL
, fd
, NULL
, &tid_ary
, &tid_num
);
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
);
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
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
)
479 if (tid1
->tid
> tid2
->tid
)
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
;
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
;
504 hist
.nxt_key
= HAMMER_MAX_KEY
;
509 if (ioctl(fd
, HAMMERIOC_GETHISTORY
, &hist
) < 0) {
511 printf("%s: %s\n", filename
, strerror(errno
));
515 printf("%s: objid=0x%016llx\n", filename
, hist
.obj_id
);
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
)
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) {
534 printf("%s: %s\n", filename
, strerror(errno
));
538 qsort(hist_ary
, tid_num
, sizeof(*hist_ary
), tid_cmp
);
541 for (i
= 0; fp
&& i
< tid_num
; ++i
) {
542 if (i
&& hist_ary
[i
].tid
== hist_ary
[i
-1].tid
)
544 t
= (time_t)hist_ary
[i
].time32
;
546 strftime(datestr
, sizeof(datestr
), "%d-%b-%Y %H:%M:%S", tp
);
547 printf("\t0x%016llx %s\n", hist_ary
[i
].tid
, datestr
);
550 hen
= hist_ary
[tid_num
-2];
553 *hist_aryp
= hist_ary
;
563 parse_delta_time(const char *timeStr
)
567 tid
= strtoull(timeStr
, NULL
, 0);
572 runcmd(int fd
, const char *cmd
, ...)
581 for (ac
= 0; va_arg(va
, void *) != NULL
; ++ac
)
585 av
= malloc((ac
+ 1) * sizeof(char *));
587 for (i
= 0; i
< ac
; ++i
)
588 av
[i
] = va_arg(va
, char *);
592 if ((pid
= fork()) < 0) {
595 } else if (pid
== 0) {
603 while (waitpid(pid
, NULL
, 0) != pid
)
610 * Convert tid to timestamp.
613 timestamp(hammer_ioc_hist_entry_t hen
)
615 static char timebuf
[64];
616 time_t t
= (time_t)hen
->time32
;
620 strftime(timebuf
, sizeof(timebuf
), "%d-%b-%Y %H:%M:%S", tp
);
627 fprintf(stderr
, "undo [-adDiuv] [-o outfile] "
628 "[-t transaction-id] [-t transaction-id] file...\n"
629 " -a Iterate all historical segments\n"
632 " -i Dump history transaction ids\n"
633 " -u Generate .undo files\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");