HAMMER 25/many: Pruning code
[dragonfly.git] / sbin / jscan / jscan.c
blob53925e11d863e8572ff3e7fb9c45c1d03c145818
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:
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.
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.
34 * $DragonFly: src/sbin/jscan/jscan.c,v 1.12 2007/08/09 21:53:02 swildner Exp $
37 #include "jscan.h"
39 static int donecheck(enum jdirection direction, struct jdata *jd,
40 int64_t transid);
41 static void usage(const char *av0);
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;
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);
64 int
65 main(int ac, char **av)
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;
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]);
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]);
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]);
182 * Sanity checks
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]);
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]);
197 if (!S_ISDIR(sb.st_mode))
199 fprintf (stderr, "Mirror directory '%s' is not a directory\n", mirror_directory);
200 usage(av[0]);
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);
210 * STEP1 - OPEN INPUT
212 * The input will either be a pipe, a regular file, or a journaling
213 * file prefix.
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]);
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)) != NULL) {
230 jf = jopen_fd(input_fd);
231 } else {
232 jf = NULL;
234 } else {
235 input_prefix = av[optind];
236 jf = jopen_prefix(input_prefix, 0);
237 jmodes |= JMODEF_INPUT_PREFIX;
239 if (jf == NULL) {
240 fprintf(stderr, "Unable to open input %s: %s\n",
241 input_prefix, strerror(errno));
242 exit(1);
246 * STEP 1 - SYNCHRONIZING THE INPUT STREAM
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.
252 * Invalid transid's will be set to 0, but it should also be noted
253 * that 0 is also a valid transid.
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: %016llx\n",
273 output_transid);
275 if (jmodes & JMODEF_MIRROR) {
276 fprintf(stderr, "Starting transid for MIRROR: %016llx\n",
277 mirror_transid);
279 if (jmodes & JMODEF_RECORD) {
280 fprintf(stderr, "Starting transid for RECORD: %016llx\n",
281 record_transid);
285 if (strcmp(mirror_directory, ".") != 0) {
286 if (chdir (mirror_directory) != 0) {
287 perror ("Could not enter mirror directory");
288 exit (1);
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.
300 * Then we take the input and start recording.
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);
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 */
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 */
320 jscan_do_record(jf, record_transid_file, record_prefix, record_transid);
321 exit(0);
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.
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);
344 exit(0);
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.
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.
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;
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;
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;
384 jd = jread(jf, jd, jdirection);
386 jclose(jf);
387 jsession_term(&jsdebug);
388 jsession_term(&jsoutput);
389 jsession_term(&jsmirror);
390 return(0);
394 * Returns one if we need to break out of our scanning loop, zero otherwise.
396 static int
397 donecheck(enum jdirection direction, struct jdata *jd, int64_t transid)
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);
406 return(0);
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.
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)
420 pid_t pid;
421 struct jfile *jf;
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);
438 static void
439 jscan_do_output(struct jfile *jf, const char *output_transid_file, const char *dummy __unused, int64_t transid)
441 struct jdata *jd;
442 struct jsession jsdebug;
443 struct jsession jsoutput;
445 jsession_init(&jsdebug, jf, jdirection,
446 NULL, 0);
447 jsession_init(&jsoutput, jf, jdirection,
448 output_transid_file, transid);
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;
463 jd = jread(jf, jd, jdirection);
465 jsession_term(&jsdebug);
466 jsession_term(&jsoutput);
469 static void
470 jscan_do_mirror(struct jfile *jf, const char *mirror_transid_file, const char *mirror_directory, int64_t transid)
472 struct jsession jsdebug;
473 struct jsession jsmirror;
474 struct jdata *jd;
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;
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;
495 jd = jread(jf, jd, jdirection);
497 jsession_term(&jsdebug);
498 jsession_term(&jsmirror);
501 static void
502 jscan_do_record(struct jfile *jfin, const char *record_transid_file, const char *prefix, int64_t transid)
504 struct jsession jsdebug;
505 struct jsession jsrecord;
506 struct jdata *jd;
508 jsession_init(&jsdebug, jfin, jdirection,
509 NULL, 0);
510 jsession_init(&jsrecord, jfin, jdirection,
511 record_transid_file, transid);
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);
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;
532 jd = jread(jfin, jd, jdirection);
534 jclose(jsrecord.ss_jfout);
535 jsrecord.ss_jfout = NULL;
536 jsession_term(&jsdebug);
537 jsession_term(&jsrecord);
540 static void
541 jscan_do_debug(struct jfile *jf, const char *dummy1 __unused,
542 const char *dummy __unused, int64_t transid __unused)
544 struct jsession jsdebug;
545 struct jdata *jd;
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;
558 jsession_term(&jsdebug);
561 static void
562 usage(const char *av0)
564 fprintf(stderr,
565 "%s [-2duF] [-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);