xref: /dragonfly/usr.bin/undo/undo.c (revision 3641b7ca)
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 
638