Merge from vendor branch LIBARCHIVE:
[dragonfly.git] / sbin / jscan / jscan.c
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.12 2007/08/09 21:53:02 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)) != NULL) {
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: %016llx\n",
273                     output_transid);
274         }
275         if (jmodes & JMODEF_MIRROR) {
276             fprintf(stderr, "Starting transid for MIRROR: %016llx\n",
277                     mirror_transid);
278         }
279         if (jmodes & JMODEF_RECORD) {
280             fprintf(stderr, "Starting transid for RECORD: %016llx\n",
281                     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 [-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);
570 }
571