xref: /dragonfly/sbin/jscan/jscan.c (revision 1b722dce)
1 /*
2  * Copyright (c) 2003,2004 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/sbin/jscan/jscan.c,v 1.13 2008/06/05 18:06:30 swildner Exp $
35  */
36 
37 #include "jscan.h"
38 
39 static int donecheck(enum jdirection direction, struct jdata *jd,
40 		     int64_t transid);
41 static void usage(const char *av0);
42 
43 int jmodes;
44 int fsync_opt;
45 int verbose_opt;
46 off_t prefix_file_size = 100 * 1024 * 1024;
47 off_t trans_count;
48 static enum jdirection jdirection = JD_FORWARDS;
49 
50 static void jscan_do_output(struct jfile *, const char *,
51 			    const char *, int64_t);
52 static void jscan_do_mirror(struct jfile *, const char *,
53 			    const char *, int64_t);
54 static void jscan_do_record(struct jfile *, const char *,
55 			    const char *, int64_t);
56 static void jscan_do_debug(struct jfile *, const char *,
57 			    const char *, int64_t);
58 static void fork_subprocess(struct jfile *,
59 			    void (*)(struct jfile *, const char *,
60 				     const char *, int64_t),
61 			    const char *,
62 			    const char *, const char *, int64_t);
63 
64 int
65 main(int ac, char **av)
66 {
67     const char *input_prefix = NULL;
68     char *output_transid_file = NULL;
69     char *mirror_transid_file = NULL;
70     const char *mirror_directory = ".";
71     char *record_prefix = NULL;
72     char *record_transid_file = NULL;
73     struct jsession jsdebug;
74     struct jsession jsoutput;
75     struct jsession jsmirror;
76     char *ptr;
77     int64_t mirror_transid;
78     int64_t output_transid;
79     int64_t record_transid;
80     int64_t transid;
81     int input_fd;
82     struct stat st;
83     struct jfile *jf;
84     struct jdata *jd;
85     int ch;
86 
87     while ((ch = getopt(ac, av, "2c:dfm:o:s:uvw:D:O:W:F")) != -1) {
88 	switch(ch) {
89 	case '2':
90 	    jmodes |= JMODEF_INPUT_FULL;
91 	    break;
92 	case 'c':
93 	    trans_count = strtoll(optarg, &ptr, 0);
94 	    switch(*ptr) {
95 	    case 't':
96 		trans_count *= 1024;
97 		/* fall through */
98 	    case 'g':
99 		trans_count *= 1024;
100 		/* fall through */
101 	    case 'm':
102 		trans_count *= 1024;
103 		/* fall through */
104 	    case 'k':
105 		trans_count *= 1024;
106 		break;
107 	    case 0:
108 		break;
109 	    default:
110 		fprintf(stderr, "Bad suffix for value specified with -c, use 'k', 'm', 'g', 't', or nothing\n");
111 		usage(av[0]);
112 	    }
113 	    break;
114 	case 'd':
115 	    jmodes |= JMODEF_DEBUG;
116 	    break;
117 	case 'f':
118 	    jmodes |= JMODEF_LOOP_FOREVER;
119 	    break;
120 	case 'v':
121 	    ++verbose_opt;
122 	    break;
123 	case 'm':
124 	    jmodes |= JMODEF_MIRROR;
125 	    if (strcmp(optarg, "none") != 0)
126 		mirror_transid_file = optarg;
127 	    break;
128 	case 'O':
129 	    jmodes |= JMODEF_OUTPUT_FULL;
130 	    /* fall through */
131 	case 'o':
132 	    jmodes |= JMODEF_OUTPUT;
133 	    if (strcmp(optarg, "none") != 0)
134 		output_transid_file = optarg;
135 	    break;
136 	case 's':
137 	    prefix_file_size = strtoll(optarg, &ptr, 0);
138 	    switch(*ptr) {
139 	    case 't':
140 		prefix_file_size *= 1024;
141 		/* fall through */
142 	    case 'g':
143 		prefix_file_size *= 1024;
144 		/* fall through */
145 	    case 'm':
146 		prefix_file_size *= 1024;
147 		/* fall through */
148 	    case 'k':
149 		prefix_file_size *= 1024;
150 		break;
151 	    case 0:
152 		break;
153 	    default:
154 		fprintf(stderr, "Bad suffix for value specified with -s, use 'k', 'm', 'g', 't', or nothing\n");
155 		usage(av[0]);
156 	    }
157 	    break;
158 	case 'u':
159 	    jdirection = JD_BACKWARDS;
160 	    break;
161 	case 'W':
162 	    jmodes |= JMODEF_RECORD_TMP;
163 	    /* fall through */
164 	case 'w':
165 	    jmodes |= JMODEF_RECORD;
166 	    record_prefix = optarg;
167 	    asprintf(&record_transid_file, "%s.transid", record_prefix);
168 	    break;
169 	case 'D':
170 	    mirror_directory = optarg;
171 	    break;
172 	case 'F':
173 	    ++fsync_opt;
174 	    break;
175 	default:
176 	    fprintf(stderr, "unknown option: -%c\n", optopt);
177 	    usage(av[0]);
178 	}
179     }
180 
181     /*
182      * Sanity checks
183      */
184     if ((jmodes & JMODEF_COMMAND_MASK) == 0)
185 	usage(av[0]);
186     if (optind > ac + 1)  {
187 	fprintf(stderr, "Only one input file or prefix may be specified,\n"
188 			"or zero if stdin is to be the input.\n");
189 	usage(av[0]);
190     }
191     if (strcmp(mirror_directory, ".") != 0) {
192 	struct stat sb;
193 	if (stat(mirror_directory, &sb) != 0) {
194 	    perror ("Could not stat mirror directory");
195 	    usage(av[0]);
196 	}
197 	if (!S_ISDIR(sb.st_mode))
198 	{
199 	    fprintf (stderr, "Mirror directory '%s' is not a directory\n", mirror_directory);
200 	    usage(av[0]);
201 	}
202     }
203     if (jdirection == JD_BACKWARDS && (jmodes & (JMODEF_RECORD|JMODEF_OUTPUT))) {
204 	fprintf(stderr, "Undo mode is only good in mirroring mode and "
205 			"cannot be mixed with other modes.\n");
206 	exit(1);
207     }
208 
209     /*
210      * STEP1 - OPEN INPUT
211      *
212      * The input will either be a pipe, a regular file, or a journaling
213      * file prefix.
214      */
215     jf = NULL;
216     if (optind == ac) {
217 	input_prefix = "<stdin>";
218 	input_fd = 0;
219 	if (fstat(0, &st) < 0 || !S_ISREG(st.st_mode)) {
220 	    jmodes |= JMODEF_INPUT_PIPE;
221 	    if (jdirection == JD_BACKWARDS) {
222 		fprintf(stderr, "Cannot scan journals on pipes backwards\n");
223 		usage(av[0]);
224 	    }
225 	}
226 	jf = jopen_fd(input_fd);
227     } else if (stat(av[optind], &st) == 0 && S_ISREG(st.st_mode)) {
228 	input_prefix = av[optind];
229 	if ((input_fd = open(av[optind], O_RDONLY)) != 0) {
230 	    jf = jopen_fd(input_fd);
231 	} else {
232 	    jf = NULL;
233 	}
234     } else {
235 	input_prefix = av[optind];
236 	jf = jopen_prefix(input_prefix, 0);
237 	jmodes |= JMODEF_INPUT_PREFIX;
238     }
239     if (jf == NULL) {
240 	fprintf(stderr, "Unable to open input %s: %s\n",
241 		input_prefix, strerror(errno));
242 	exit(1);
243     }
244 
245     /*
246      * STEP 1 - SYNCHRONIZING THE INPUT STREAM
247      *
248      * Figure out the starting point for our various output modes.  Figure
249      * out the earliest transaction id and try to seek to that point,
250      * otherwise we might have to scan through terrabytes of data.
251      *
252      * Invalid transid's will be set to 0, but it should also be noted
253      * that 0 is also a valid transid.
254      */
255     get_transid_from_file(output_transid_file, &output_transid,
256 			  JMODEF_OUTPUT_TRANSID_GOOD);
257     get_transid_from_file(mirror_transid_file, &mirror_transid,
258 			  JMODEF_MIRROR_TRANSID_GOOD);
259     get_transid_from_file(record_transid_file, &record_transid,
260 			  JMODEF_RECORD_TRANSID_GOOD);
261     transid = LLONG_MAX;
262     if ((jmodes & JMODEF_OUTPUT_TRANSID_GOOD) && output_transid < transid)
263 	transid = output_transid;
264     if ((jmodes & JMODEF_MIRROR_TRANSID_GOOD) && mirror_transid < transid)
265 	transid = mirror_transid;
266     if ((jmodes & JMODEF_RECORD_TRANSID_GOOD) && record_transid < transid)
267 	transid = record_transid;
268     if ((jmodes & JMODEF_TRANSID_GOOD_MASK) == 0)
269 	transid = 0;
270     if (verbose_opt) {
271 	if (jmodes & JMODEF_OUTPUT) {
272 	    fprintf(stderr, "Starting transid for OUTPUT: %016jx\n",
273 		    (uintmax_t)output_transid);
274 	}
275 	if (jmodes & JMODEF_MIRROR) {
276 	    fprintf(stderr, "Starting transid for MIRROR: %016jx\n",
277 		    (uintmax_t)mirror_transid);
278 	}
279 	if (jmodes & JMODEF_RECORD) {
280 	    fprintf(stderr, "Starting transid for RECORD: %016jx\n",
281 		    (uintmax_t)record_transid);
282 	}
283     }
284 
285     if (strcmp(mirror_directory, ".") != 0) {
286 	if (chdir (mirror_directory) != 0) {
287 	    perror ("Could not enter mirror directory");
288 	    exit (1);
289 	}
290     }
291 
292     /*
293      * Now it gets more difficult.  If we are recording then the input
294      * could be representative of continuing data and not have any
295      * prior, older data that the output or mirror modes might need.  Those
296      * modes must work off the recording data even as we write to it.
297      * In that case we fork and have the sub-processes work off the
298      * record output.
299      *
300      * Then we take the input and start recording.
301      */
302     if (jmodes & JMODEF_RECORD) {
303 	if (jrecord_init(record_prefix) < 0) {
304 	    fprintf(stderr, "Unable to initialize file set for: %s\n",
305 		    record_prefix);
306 	    exit(1);
307 	}
308 	if (jmodes & JMODEF_MIRROR) {
309 	    fork_subprocess(jf, jscan_do_mirror, record_prefix,
310 			    mirror_transid_file,
311 			    mirror_directory, mirror_transid);
312 	    /* XXX ack stream for temporary record file removal */
313 	}
314 	if (jmodes & JMODEF_OUTPUT) {
315 	    fork_subprocess(jf, jscan_do_output, record_prefix,
316 			    record_transid_file,
317 			    NULL, output_transid);
318 	    /* XXX ack stream for temporary record file removal */
319 	}
320 	jscan_do_record(jf, record_transid_file, record_prefix, record_transid);
321 	exit(0);
322     }
323 
324     /*
325      * If the input is a prefix set we can just pass it to the appropriate
326      * jscan_do_*() function.  If we are doing both output and mirroring
327      * we fork the mirror and do the output in the foreground since that
328      * is going to stdout.
329      */
330     if (jmodes & JMODEF_INPUT_PREFIX) {
331 	if ((jmodes & JMODEF_OUTPUT) && (jmodes & JMODEF_MIRROR)) {
332 	    fork_subprocess(jf, jscan_do_mirror, input_prefix,
333 			    mirror_transid_file,
334 			    mirror_directory, mirror_transid);
335 	    jscan_do_output(jf, output_transid_file, NULL, output_transid);
336 	} else if (jmodes & JMODEF_OUTPUT) {
337 	    jscan_do_output(jf, output_transid_file, NULL, output_transid);
338 	} else if (jmodes & JMODEF_MIRROR) {
339 	    jscan_do_mirror(jf, mirror_transid_file, mirror_directory,
340 			    mirror_transid);
341 	} else if (jmodes & JMODEF_DEBUG) {
342 	    jscan_do_debug(jf, NULL, NULL, 0);
343 	}
344 	exit(0);
345     }
346 
347     /*
348      * The input is not a prefix set and we are not recording, which means
349      * we have to transfer the data on the input pipe to the output and
350      * mirroring code on the fly.  This also means that we must keep track
351      * of meta-data records in-memory.  However, if the input is a regular
352      * file we *CAN* try to optimize where we start reading.
353      *
354      * NOTE: If the mirroring code encounters a transaction record that is
355      * not marked begin, and it does not have the begin record, it will
356      * attempt to locate the begin record if the input is not a pipe, then
357      * seek back.
358      */
359     if ((jmodes & JMODEF_TRANSID_GOOD_MASK) && !(jmodes & JMODEF_INPUT_PIPE))
360 	jd = jseek(jf, transid, jdirection);
361     else
362 	jd = jread(jf, NULL, jdirection);
363     jmodes |= JMODEF_MEMORY_TRACKING;
364 
365     jsession_init(&jsdebug, jf, jdirection,
366 		  NULL, 0);
367     jsession_init(&jsoutput, jf, jdirection,
368 		  output_transid_file, output_transid);
369     jsession_init(&jsmirror, jf, jdirection,
370 		  mirror_transid_file, mirror_transid);
371     jsmirror.ss_mirror_directory = mirror_directory;
372 
373     while (jd != NULL) {
374 	if ((jmodes & JMODEF_DEBUG) && jsession_check(&jsdebug, jd))
375 	    dump_debug(&jsdebug, jd);
376 	if ((jmodes & JMODEF_OUTPUT) && jsession_check(&jsoutput, jd))
377 	    dump_output(&jsoutput, jd);
378 	if ((jmodes & JMODEF_MIRROR) && jsession_check(&jsmirror, jd))
379 	    dump_mirror(&jsmirror, jd);
380 	if (donecheck(jdirection, jd, transid)) {
381 	    jfree(jf, jd);
382 	    break;
383 	}
384 	jd = jread(jf, jd, jdirection);
385     }
386     jclose(jf);
387     jsession_term(&jsdebug);
388     jsession_term(&jsoutput);
389     jsession_term(&jsmirror);
390     return(0);
391 }
392 
393 /*
394  * Returns one if we need to break out of our scanning loop, zero otherwise.
395  */
396 static int
397 donecheck(enum jdirection direction, struct jdata *jd, int64_t transid)
398 {
399     if (direction == JD_FORWARDS) {
400 	if (jd->jd_transid > transid && trans_count && --trans_count == 0)
401 	    return(1);
402     } else {
403 	if (jd->jd_transid <= transid && trans_count && --trans_count == 0)
404 	    return(1);
405     }
406     return(0);
407 }
408 
409 /*
410  * When we have multiple commands and are writing to a prefix set, we can
411  * 'background' the output and/or mirroring command and have the background
412  * processes feed off the prefix set the foreground process is writing to.
413  */
414 static void
415 fork_subprocess(struct jfile *jftoclose,
416 	void (*func)(struct jfile *, const char *, const char *, int64_t),
417 	const char *input_prefix, const char *transid_file, const char *info,
418 	int64_t transid)
419 {
420     pid_t pid;
421     struct jfile *jf;
422 
423     if ((pid = fork()) == 0) {
424 	jmodes &= ~(JMODEF_DEBUG | JMODEF_INPUT_PIPE);
425 	jmodes |= JMODEF_LOOP_FOREVER;	/* keep checking for new input */
426 	jclose(jftoclose);
427 	jf = jopen_prefix(input_prefix, 0);
428 	jmodes |= JMODEF_INPUT_PREFIX;
429 	func(jf, transid_file, info, transid);
430 	jclose(jf);
431 	exit(0);
432     } else if (pid < 0) {
433 	fprintf(stderr, "fork(): %s\n", strerror(errno));
434 	exit(1);
435     }
436 }
437 
438 static void
439 jscan_do_output(struct jfile *jf, const char *output_transid_file, const char *dummy __unused, int64_t transid)
440 {
441     struct jdata *jd;
442     struct jsession jsdebug;
443     struct jsession jsoutput;
444 
445     jsession_init(&jsdebug, jf, jdirection,
446 		  NULL, 0);
447     jsession_init(&jsoutput, jf, jdirection,
448 		  output_transid_file, transid);
449 
450     if ((jmodes & JMODEF_OUTPUT_TRANSID_GOOD) && !(jmodes & JMODEF_INPUT_PIPE))
451 	jd = jseek(jf, transid, jdirection);
452     else
453 	jd = jread(jf, NULL, jdirection);
454     while (jd != NULL) {
455 	if ((jmodes & JMODEF_DEBUG) && jsession_check(&jsdebug, jd))
456 	    dump_debug(&jsdebug, jd);
457 	if (jsession_check(&jsoutput, jd))
458 	    dump_output(&jsoutput, jd);
459 	if (donecheck(jdirection, jd, transid)) {
460 	    jfree(jf, jd);
461 	    break;
462 	}
463 	jd = jread(jf, jd, jdirection);
464     }
465     jsession_term(&jsdebug);
466     jsession_term(&jsoutput);
467 }
468 
469 static void
470 jscan_do_mirror(struct jfile *jf, const char *mirror_transid_file, const char *mirror_directory, int64_t transid)
471 {
472     struct jsession jsdebug;
473     struct jsession jsmirror;
474     struct jdata *jd;
475 
476     jsession_init(&jsdebug, jf, jdirection,
477 		  NULL, 0);
478     jsession_init(&jsmirror, jf, jdirection,
479 		  mirror_transid_file, transid);
480     jsmirror.ss_mirror_directory = mirror_directory;
481 
482     if ((jmodes & JMODEF_MIRROR_TRANSID_GOOD) && !(jmodes & JMODEF_INPUT_PIPE))
483 	jd = jseek(jf, transid, jdirection);
484     else
485 	jd = jread(jf, NULL, jdirection);
486     while (jd != NULL) {
487 	if ((jmodes & JMODEF_DEBUG) && jsession_check(&jsdebug, jd))
488 	    dump_debug(&jsdebug, jd);
489 	if (jsession_check(&jsmirror, jd))
490 	    dump_mirror(&jsmirror, jd);
491 	if (donecheck(jdirection, jd, transid)) {
492 	    jfree(jf, jd);
493 	    break;
494 	}
495 	jd = jread(jf, jd, jdirection);
496     }
497     jsession_term(&jsdebug);
498     jsession_term(&jsmirror);
499 }
500 
501 static void
502 jscan_do_record(struct jfile *jfin, const char *record_transid_file, const char *prefix, int64_t transid)
503 {
504     struct jsession jsdebug;
505     struct jsession jsrecord;
506     struct jdata *jd;
507 
508     jsession_init(&jsdebug, jfin, jdirection,
509 		  NULL, 0);
510     jsession_init(&jsrecord, jfin, jdirection,
511 		  record_transid_file, transid);
512 
513     assert(jdirection == JD_FORWARDS);
514     jsrecord.ss_jfout = jopen_prefix(prefix, 1);
515     if (jsrecord.ss_jfout == NULL) {
516 	fprintf(stderr, "Unable to open prefix set for writing: %s\n", prefix);
517 	exit(1);
518     }
519     if ((jmodes & JMODEF_RECORD_TRANSID_GOOD) && !(jmodes & JMODEF_INPUT_PIPE))
520 	jd = jseek(jfin, transid, jdirection);
521     else
522 	jd = jread(jfin, NULL, jdirection);
523     while (jd != NULL) {
524 	if ((jmodes & JMODEF_DEBUG) && jsession_check(&jsdebug, jd))
525 	    dump_debug(&jsdebug, jd);
526 	if (jsession_check(&jsrecord, jd))
527 	    dump_record(&jsrecord, jd);
528 	if (donecheck(jdirection, jd, transid)) {
529 	    jfree(jfin, jd);
530 	    break;
531 	}
532 	jd = jread(jfin, jd, jdirection);
533     }
534     jclose(jsrecord.ss_jfout);
535     jsrecord.ss_jfout = NULL;
536     jsession_term(&jsdebug);
537     jsession_term(&jsrecord);
538 }
539 
540 static void
541 jscan_do_debug(struct jfile *jf, const char *dummy1 __unused,
542 	       const char *dummy __unused, int64_t transid __unused)
543 {
544     struct jsession jsdebug;
545     struct jdata *jd;
546 
547     jsession_init(&jsdebug, jf, jdirection,
548 		  NULL, 0);
549     jd = NULL;
550     while ((jd = jread(jf, jd, jdirection)) != NULL) {
551 	if (jsession_check(&jsdebug, jd))
552 	    dump_debug(&jsdebug, jd);
553 	if (donecheck(jdirection, jd, transid)) {
554 	    jfree(jf, jd);
555 	    break;
556 	}
557     }
558     jsession_term(&jsdebug);
559 }
560 
561 static void
562 usage(const char *av0)
563 {
564     fprintf(stderr,
565 	"%s [-2dfuvF] [-D dir] [-m mirror_transid_file/none]\n"
566 	"\t[-o/O output_transid_file/none]\n"
567 	"\t[-s size[kmgt]] -w/W record_prefix] [input_file/input_prefix]\n",
568 	av0);
569     exit(1);
570 }
571 
572