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