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