HAMMER Utilities: Sync to 58B
[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.2 2008/06/24 17:40:24 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                    struct hammer_ioc_hist_entry ts1,
61                    struct hammer_ioc_hist_entry ts2);
62 static struct hammer_ioc_hist_entry
63             find_recent(const char *filename);
64 static 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);
67 static hammer_tid_t parse_delta_time(const char *timeStr);
68 static void runcmd(int fd, const char *cmd, ...);
69 static char *timestamp(hammer_ioc_hist_entry_t hen);
70 static void usage(void);
71
72 static int VerboseOpt;
73
74 int
75 main(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;
81         struct hammer_ioc_hist_entry ts1;
82         struct hammer_ioc_hist_entry ts2;
83         int c;
84         int mult;
85
86         bzero(&ts1, sizeof(ts1));
87         bzero(&ts2, sizeof(ts2));
88
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':
122                         if (ts1.tid && ts2.tid)
123                                 usage();
124                         else if (ts1.tid == 0)
125                                 ts1.tid = parse_delta_time(optarg);
126                         else
127                                 ts2.tid = parse_delta_time(optarg);
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,
180                                    mult, -1, type, ts1, ts2);
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  */
196 static
197 void
198 doiterate(const char *orig_filename, const char *outFileName,
199            const char *outFilePostfix, int mult, enum undo_type type)
200 {
201         hammer_ioc_hist_entry_t tid_ary = NULL;
202         struct hammer_ioc_hist_entry tid_max;
203         struct hammer_ioc_hist_entry ts1;
204         const char *use_filename;
205         char *path = NULL;
206         int tid_num = 0;
207         int i;
208         int fd;
209
210         tid_max.tid = HAMMER_MAX_TID;
211         tid_max.time32 = 0;
212
213         use_filename = orig_filename;
214         if ((fd = open(orig_filename, O_RDONLY)) < 0) {
215                 ts1 = find_recent(orig_filename);
216                 if (ts1.tid) {
217                         asprintf(&path, "%s@@0x%016llx",
218                                  orig_filename, ts1.tid);
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) {
229                         if (i && tid_ary[i].tid == tid_ary[i-1].tid)
230                                 continue;
231
232                         if (i == tid_num - 1) {
233                                 dogenerate(orig_filename,
234                                            outFileName, outFilePostfix,
235                                            mult, i, type,
236                                            tid_ary[i], tid_max);
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  */
257 static
258 void
259 dogenerate(const char *filename, const char *outFileName,
260            const char *outFilePostfix,
261            int mult, int idx, enum undo_type type,
262            struct hammer_ioc_hist_entry ts1,
263            struct hammer_ioc_hist_entry ts2)
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          */
281         if (ts1.tid == 0)
282                 ts1 = find_recent(filename);
283         asprintf(&ipath1, "%s@@0x%016llx", filename, ts1.tid);
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
293         if (ts2.tid == 0) {
294                 asprintf(&ipath2, "%s", filename);
295         } else {
296                 asprintf(&ipath2, "%s@@0x%015llx", filename, ts2.tid);
297         }
298         if (lstat(ipath2, &st) < 0) {
299                 if (VerboseOpt) {
300                         if (ts2.tid) {
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",
355                                        filename, idx, ts1.tid, timestamp(&ts1));
356                         } else {
357                                 printf("\n>>> %s ---- 0x%016llx %s\n\n",
358                                        filename, ts1.tid, timestamp(&ts1));
359                         }
360                 } else if (idx >= 0 && type == TYPE_FILE) {
361                         printf("\n>>> %s %04d 0x%016llx %s\n\n", 
362                                filename, idx, ts1.tid, timestamp(&ts1));
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:
376                 printf("diff -u %s %s (to %s)\n",
377                        ipath1, ipath2, timestamp(&ts2));
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);
396 done:
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  */
405 static
406 struct hammer_ioc_hist_entry
407 find_recent(const char *filename)
408 {
409         hammer_ioc_hist_entry_t tid_ary = NULL;
410         int tid_num = 0;
411         struct hammer_ioc_hist_entry hen;
412         char *dirname;
413         char *path;
414         int fd;
415         int i;
416
417         if ((fd = open(filename, O_RDONLY)) >= 0) {
418                 hen = output_history(NULL, fd, NULL, NULL, NULL);
419                 close(fd);
420                 return(hen);
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
434         hen.tid = 0;
435         hen.time32 = 0;
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) {
442                         asprintf(&path, "%s@@0x%016llx", filename, tid_ary[i].tid);
443                         if ((fd = open(path, O_RDONLY)) >= 0) {
444                                 hen = output_history(NULL, fd, NULL, NULL, NULL);
445                                 close(fd);
446                                 free(path);
447                                 break;
448                         }
449                         free(path);
450                 }
451         }
452         return(hen);
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  */
461 static int
462 tid_cmp(const void *arg1, const void *arg2)
463 {
464         const struct hammer_ioc_hist_entry *tid1 = arg1;
465         const struct hammer_ioc_hist_entry *tid2 = arg2;
466
467         if (tid1->tid < tid2->tid)
468                 return(-1);
469         if (tid1->tid > tid2->tid)
470                 return(1);
471         return(0);
472 }
473
474 static
475 struct hammer_ioc_hist_entry
476 output_history(const char *filename, int fd, FILE *fp,
477                struct hammer_ioc_hist_entry **hist_aryp, int *tid_nump)
478 {
479         struct hammer_ioc_hist_entry hen;
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;
487         hammer_ioc_hist_entry_t hist_ary = malloc(tid_max * sizeof(*hist_ary));
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
496         hen.tid = 0;
497         hen.time32 = 0;
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;
509                         hist_ary = realloc(hist_ary, tid_max * sizeof(*hist_ary));
510                 }
511                 for (i = 0; i < hist.count; ++i) {
512                         hist_ary[tid_num++] = hist.hist_ary[i];
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         }
528         qsort(hist_ary, tid_num, sizeof(*hist_ary), tid_cmp);
529         if (tid_num == 0)
530                 goto done;
531         for (i = 0; fp && i < tid_num; ++i) {
532                 if (i && hist_ary[i].tid == hist_ary[i-1].tid)
533                         continue;
534                 t = (time_t)hist_ary[i].time32;
535                 tp = localtime(&t);
536                 strftime(datestr, sizeof(datestr), "%d-%b-%Y %H:%M:%S", tp);
537                 printf("\t0x%016llx %s\n", hist_ary[i].tid, datestr);
538         }
539         if (tid_num > 1)
540                 hen = hist_ary[tid_num-2];
541 done:
542         if (hist_aryp) {
543                 *hist_aryp = hist_ary;
544                 *tid_nump = tid_num;
545         } else {
546                 free(hist_ary);
547         }
548         return(hen);
549 }
550
551 static
552 hammer_tid_t
553 parse_delta_time(const char *timeStr)
554 {
555         hammer_tid_t tid;
556
557         tid = strtoull(timeStr, NULL, 0);
558         return(tid);
559 }
560
561 static void
562 runcmd(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  */
602 static char *
603 timestamp(hammer_ioc_hist_entry_t hen)
604 {
605         static char timebuf[64];
606         time_t t = (time_t)hen->time32;
607         struct tm *tp;
608
609         tp = localtime(&t);
610         strftime(timebuf, sizeof(timebuf), "%d-%b-%Y %H:%M:%S", tp);
611         return(timebuf);
612 }
613
614 static void
615 usage(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