2 * Copyright (c) 2003,2004 The DragonFly Project. All rights reserved.
4 * This code is derived from software contributed to The DragonFly Project
5 * by Matthew Dillon <dillon@backplane.com>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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
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
34 * $DragonFly: src/sbin/jscan/jscan.c,v 1.12 2007/08/09 21:53:02 swildner Exp $
39 static int donecheck(enum jdirection direction
, struct jdata
*jd
,
41 static void usage(const char *av0
);
46 off_t prefix_file_size
= 100 * 1024 * 1024;
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),
62 const char *, const char *, int64_t);
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
;
77 int64_t mirror_transid
;
78 int64_t output_transid
;
79 int64_t record_transid
;
87 while ((ch
= getopt(ac
, av
, "2c:dfm:o:s:uvw:D:O:W:F")) != -1) {
90 jmodes
|= JMODEF_INPUT_FULL
;
93 trans_count
= strtoll(optarg
, &ptr
, 0);
110 fprintf(stderr
, "Bad suffix for value specified with -c, use 'k', 'm', 'g', 't', or nothing\n");
115 jmodes
|= JMODEF_DEBUG
;
118 jmodes
|= JMODEF_LOOP_FOREVER
;
124 jmodes
|= JMODEF_MIRROR
;
125 if (strcmp(optarg
, "none") != 0)
126 mirror_transid_file
= optarg
;
129 jmodes
|= JMODEF_OUTPUT_FULL
;
132 jmodes
|= JMODEF_OUTPUT
;
133 if (strcmp(optarg
, "none") != 0)
134 output_transid_file
= optarg
;
137 prefix_file_size
= strtoll(optarg
, &ptr
, 0);
140 prefix_file_size
*= 1024;
143 prefix_file_size
*= 1024;
146 prefix_file_size
*= 1024;
149 prefix_file_size
*= 1024;
154 fprintf(stderr
, "Bad suffix for value specified with -s, use 'k', 'm', 'g', 't', or nothing\n");
159 jdirection
= JD_BACKWARDS
;
162 jmodes
|= JMODEF_RECORD_TMP
;
165 jmodes
|= JMODEF_RECORD
;
166 record_prefix
= optarg
;
167 asprintf(&record_transid_file
, "%s.transid", record_prefix
);
170 mirror_directory
= optarg
;
176 fprintf(stderr
, "unknown option: -%c\n", optopt
);
184 if ((jmodes
& JMODEF_COMMAND_MASK
) == 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");
191 if (strcmp(mirror_directory
, ".") != 0) {
193 if (stat(mirror_directory
, &sb
) != 0) {
194 perror ("Could not stat mirror directory");
197 if (!S_ISDIR(sb
.st_mode
))
199 fprintf (stderr
, "Mirror directory '%s' is not a directory\n", mirror_directory
);
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");
212 * The input will either be a pipe, a regular file, or a journaling
217 input_prefix
= "<stdin>";
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");
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
);
235 input_prefix
= av
[optind
];
236 jf
= jopen_prefix(input_prefix
, 0);
237 jmodes
|= JMODEF_INPUT_PREFIX
;
240 fprintf(stderr
, "Unable to open input %s: %s\n",
241 input_prefix
, strerror(errno
));
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
);
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)
271 if (jmodes
& JMODEF_OUTPUT
) {
272 fprintf(stderr
, "Starting transid for OUTPUT: %016llx\n",
275 if (jmodes
& JMODEF_MIRROR
) {
276 fprintf(stderr
, "Starting transid for MIRROR: %016llx\n",
279 if (jmodes
& JMODEF_RECORD
) {
280 fprintf(stderr
, "Starting transid for RECORD: %016llx\n",
285 if (strcmp(mirror_directory
, ".") != 0) {
286 if (chdir (mirror_directory
) != 0) {
287 perror ("Could not enter mirror directory");
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
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",
308 if (jmodes
& JMODEF_MIRROR
) {
309 fork_subprocess(jf
, jscan_do_mirror
, record_prefix
,
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
,
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
);
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
,
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
,
341 } else if (jmodes
& JMODEF_DEBUG
) {
342 jscan_do_debug(jf
, NULL
, NULL
, 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
359 if ((jmodes
& JMODEF_TRANSID_GOOD_MASK
) && !(jmodes
& JMODEF_INPUT_PIPE
))
360 jd
= jseek(jf
, transid
, jdirection
);
362 jd
= jread(jf
, NULL
, jdirection
);
363 jmodes
|= JMODEF_MEMORY_TRACKING
;
365 jsession_init(&jsdebug
, jf
, jdirection
,
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
;
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
)) {
384 jd
= jread(jf
, jd
, jdirection
);
387 jsession_term(&jsdebug
);
388 jsession_term(&jsoutput
);
389 jsession_term(&jsmirror
);
394 * Returns one if we need to break out of our scanning loop, zero otherwise.
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)
403 if (jd
->jd_transid
<= transid
&& trans_count
&& --trans_count
== 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.
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
,
423 if ((pid
= fork()) == 0) {
424 jmodes
&= ~(JMODEF_DEBUG
| JMODEF_INPUT_PIPE
);
425 jmodes
|= JMODEF_LOOP_FOREVER
; /* keep checking for new input */
427 jf
= jopen_prefix(input_prefix
, 0);
428 jmodes
|= JMODEF_INPUT_PREFIX
;
429 func(jf
, transid_file
, info
, transid
);
432 } else if (pid
< 0) {
433 fprintf(stderr
, "fork(): %s\n", strerror(errno
));
439 jscan_do_output(struct jfile
*jf
, const char *output_transid_file
, const char *dummy __unused
, int64_t transid
)
442 struct jsession jsdebug
;
443 struct jsession jsoutput
;
445 jsession_init(&jsdebug
, jf
, jdirection
,
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
);
453 jd
= jread(jf
, NULL
, jdirection
);
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
)) {
463 jd
= jread(jf
, jd
, jdirection
);
465 jsession_term(&jsdebug
);
466 jsession_term(&jsoutput
);
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
;
476 jsession_init(&jsdebug
, jf
, jdirection
,
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
);
485 jd
= jread(jf
, NULL
, jdirection
);
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
)) {
495 jd
= jread(jf
, jd
, jdirection
);
497 jsession_term(&jsdebug
);
498 jsession_term(&jsmirror
);
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
;
508 jsession_init(&jsdebug
, jfin
, jdirection
,
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
);
519 if ((jmodes
& JMODEF_RECORD_TRANSID_GOOD
) && !(jmodes
& JMODEF_INPUT_PIPE
))
520 jd
= jseek(jfin
, transid
, jdirection
);
522 jd
= jread(jfin
, NULL
, jdirection
);
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
)) {
532 jd
= jread(jfin
, jd
, jdirection
);
534 jclose(jsrecord
.ss_jfout
);
535 jsrecord
.ss_jfout
= NULL
;
536 jsession_term(&jsdebug
);
537 jsession_term(&jsrecord
);
541 jscan_do_debug(struct jfile
*jf
, const char *dummy1 __unused
,
542 const char *dummy __unused
, int64_t transid __unused
)
544 struct jsession jsdebug
;
547 jsession_init(&jsdebug
, jf
, jdirection
,
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
)) {
558 jsession_term(&jsdebug
);
562 usage(const char *av0
)
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",