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