1 /* 2 * Copyright (c) 2008 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/hammer/cmd_mirror.c,v 1.16 2008/11/09 05:22:56 dillon Exp $ 35 */ 36 37 #include "hammer.h" 38 39 #define SERIALBUF_SIZE (512 * 1024) 40 41 static int read_mrecords(int fd, char *buf, u_int size, 42 hammer_ioc_mrecord_head_t pickup); 43 static int generate_histogram(int fd, const char *filesystem, 44 hammer_tid_t **histogram_ary, 45 struct hammer_ioc_mirror_rw *mirror_base); 46 static hammer_ioc_mrecord_any_t read_mrecord(int fdin, int *errorp, 47 hammer_ioc_mrecord_head_t pickup); 48 static void write_mrecord(int fdout, u_int32_t type, 49 hammer_ioc_mrecord_any_t mrec, int bytes); 50 static void generate_mrec_header(int fd, int pfs_id, 51 union hammer_ioc_mrecord_any *mrec_tmp); 52 static int validate_mrec_header(int fd, int fdin, int is_target, int pfs_id, 53 struct hammer_ioc_mrecord_head *pickup, 54 hammer_tid_t *tid_begp, hammer_tid_t *tid_endp); 55 static void update_pfs_snapshot(int fd, hammer_tid_t snapshot_tid, int pfs_id); 56 static ssize_t writebw(int fd, const void *buf, size_t nbytes, 57 u_int64_t *bwcount, struct timeval *tv1); 58 static int getyn(void); 59 static void mirror_usage(int code); 60 61 #define BULK_MINIMUM 20000 62 63 /* 64 * Generate a mirroring data stream from the specific source over the 65 * entire key range, but restricted to the specified transaction range. 66 * 67 * The HAMMER VFS does most of the work, we add a few new mrecord 68 * types to negotiate the TID ranges and verify that the entire 69 * stream made it to the destination. 70 */ 71 void 72 hammer_cmd_mirror_read(char **av, int ac, int streaming) 73 { 74 struct hammer_ioc_mirror_rw mirror; 75 struct hammer_ioc_pseudofs_rw pfs; 76 union hammer_ioc_mrecord_any mrec_tmp; 77 struct hammer_ioc_mrecord_head pickup; 78 hammer_ioc_mrecord_any_t mrec; 79 hammer_tid_t sync_tid; 80 hammer_tid_t *histogram_ary; 81 const char *filesystem; 82 char *buf = malloc(SERIALBUF_SIZE); 83 int interrupted = 0; 84 int error; 85 int fd; 86 int n; 87 int didwork; 88 int histogram; 89 int64_t total_bytes; 90 time_t base_t = time(NULL); 91 struct timeval bwtv; 92 u_int64_t bwcount; 93 94 if (ac == 0 || ac > 2) 95 mirror_usage(1); 96 filesystem = av[0]; 97 98 pickup.signature = 0; 99 pickup.type = 0; 100 histogram = -1; 101 histogram_ary = NULL; 102 103 again: 104 bzero(&mirror, sizeof(mirror)); 105 hammer_key_beg_init(&mirror.key_beg); 106 hammer_key_end_init(&mirror.key_end); 107 108 fd = getpfs(&pfs, filesystem); 109 110 if (streaming && VerboseOpt) { 111 fprintf(stderr, "\nRunning"); 112 fflush(stderr); 113 } 114 total_bytes = 0; 115 gettimeofday(&bwtv, NULL); 116 bwcount = 0; 117 118 /* 119 * Send initial header for the purpose of determining the 120 * shared-uuid. 121 */ 122 generate_mrec_header(fd, pfs.pfs_id, &mrec_tmp); 123 write_mrecord(1, HAMMER_MREC_TYPE_PFSD, 124 &mrec_tmp, sizeof(mrec_tmp.pfs)); 125 126 /* 127 * In 2-way mode the target will send us a PFS info packet 128 * first. Use the target's current snapshot TID as our default 129 * begin TID. 130 */ 131 mirror.tid_beg = 0; 132 if (TwoWayPipeOpt) { 133 n = validate_mrec_header(fd, 0, 0, pfs.pfs_id, &pickup, 134 NULL, &mirror.tid_beg); 135 if (n < 0) { /* got TERM record */ 136 relpfs(fd, &pfs); 137 return; 138 } 139 ++mirror.tid_beg; 140 } 141 142 /* 143 * Write out the PFS header, tid_beg will be updated if our PFS 144 * has a larger begin sync. tid_end is set to the latest source 145 * TID whos flush cycle has completed. 146 */ 147 generate_mrec_header(fd, pfs.pfs_id, &mrec_tmp); 148 if (mirror.tid_beg < mrec_tmp.pfs.pfsd.sync_beg_tid) 149 mirror.tid_beg = mrec_tmp.pfs.pfsd.sync_beg_tid; 150 mirror.tid_end = mrec_tmp.pfs.pfsd.sync_end_tid; 151 mirror.ubuf = buf; 152 mirror.size = SERIALBUF_SIZE; 153 mirror.pfs_id = pfs.pfs_id; 154 mirror.shared_uuid = pfs.ondisk->shared_uuid; 155 156 /* 157 * XXX If the histogram is exhausted and the TID delta is large 158 * the stream might have been offline for a while and is 159 * now picking it up again. Do another histogram. 160 */ 161 #if 0 162 if (TwoWayPipeOpt && streaming && histogram == 0) { 163 if (mirror.tid_end - mirror.tid_beg > BULK_MINIMUM) 164 histogram = -1; 165 } 166 #endif 167 168 /* 169 * Initial bulk startup control, try to do some incremental 170 * mirroring in order to allow the stream to be killed and 171 * restarted without having to start over. 172 */ 173 if (histogram < 0 && BulkOpt == 0) { 174 if (VerboseOpt) 175 fprintf(stderr, "\n"); 176 histogram = generate_histogram(fd, filesystem, 177 &histogram_ary, &mirror); 178 } 179 180 if (TwoWayPipeOpt && streaming && histogram > 0) { 181 mirror.tid_end = histogram_ary[--histogram]; 182 mrec_tmp.pfs.pfsd.sync_end_tid = mirror.tid_end; 183 } 184 185 write_mrecord(1, HAMMER_MREC_TYPE_PFSD, 186 &mrec_tmp, sizeof(mrec_tmp.pfs)); 187 188 /* 189 * A cycle file overrides the beginning TID only if we are 190 * not operating in two-way mode. 191 */ 192 if (TwoWayPipeOpt == 0) { 193 hammer_get_cycle(&mirror.key_beg, &mirror.tid_beg); 194 } 195 196 /* 197 * An additional argument overrides the beginning TID regardless 198 * of what mode we are in. This is not recommending if operating 199 * in two-way mode. 200 */ 201 if (ac == 2) 202 mirror.tid_beg = strtoull(av[1], NULL, 0); 203 204 if (streaming == 0 || VerboseOpt >= 2) { 205 fprintf(stderr, 206 "Mirror-read: Mirror from %016jx to %016jx\n", 207 (uintmax_t)mirror.tid_beg, (uintmax_t)mirror.tid_end); 208 } 209 if (mirror.key_beg.obj_id != (int64_t)HAMMER_MIN_OBJID) { 210 fprintf(stderr, "Mirror-read: Resuming at object %016jx\n", 211 (uintmax_t)mirror.key_beg.obj_id); 212 } 213 214 /* 215 * Nothing to do if begin equals end. 216 */ 217 if (mirror.tid_beg >= mirror.tid_end) { 218 if (streaming == 0 || VerboseOpt >= 2) 219 fprintf(stderr, "Mirror-read: No work to do\n"); 220 didwork = 0; 221 goto done; 222 } 223 didwork = 1; 224 225 /* 226 * Write out bulk records 227 */ 228 mirror.ubuf = buf; 229 mirror.size = SERIALBUF_SIZE; 230 231 do { 232 mirror.count = 0; 233 mirror.pfs_id = pfs.pfs_id; 234 mirror.shared_uuid = pfs.ondisk->shared_uuid; 235 if (ioctl(fd, HAMMERIOC_MIRROR_READ, &mirror) < 0) { 236 fprintf(stderr, "Mirror-read %s failed: %s\n", 237 filesystem, strerror(errno)); 238 exit(1); 239 } 240 if (mirror.head.flags & HAMMER_IOC_HEAD_ERROR) { 241 fprintf(stderr, 242 "Mirror-read %s fatal error %d\n", 243 filesystem, mirror.head.error); 244 exit(1); 245 } 246 if (mirror.count) { 247 if (BandwidthOpt) { 248 n = writebw(1, mirror.ubuf, mirror.count, 249 &bwcount, &bwtv); 250 } else { 251 n = write(1, mirror.ubuf, mirror.count); 252 } 253 if (n != mirror.count) { 254 fprintf(stderr, "Mirror-read %s failed: " 255 "short write\n", 256 filesystem); 257 exit(1); 258 } 259 } 260 total_bytes += mirror.count; 261 if (streaming && VerboseOpt) { 262 fprintf(stderr, 263 "\robj=%016jx tids=%016jx:%016jx %11jd", 264 (uintmax_t)mirror.key_cur.obj_id, 265 (uintmax_t)mirror.tid_beg, 266 (uintmax_t)mirror.tid_end, 267 (intmax_t)total_bytes); 268 fflush(stderr); 269 } 270 mirror.key_beg = mirror.key_cur; 271 272 /* 273 * Deal with time limit option 274 */ 275 if (TimeoutOpt && 276 (unsigned)(time(NULL) - base_t) > (unsigned)TimeoutOpt) { 277 fprintf(stderr, 278 "Mirror-read %s interrupted by timer at" 279 " %016jx\n", 280 filesystem, 281 (uintmax_t)mirror.key_cur.obj_id); 282 interrupted = 1; 283 break; 284 } 285 } while (mirror.count != 0); 286 287 done: 288 /* 289 * Write out the termination sync record - only if not interrupted 290 */ 291 if (interrupted == 0) { 292 if (didwork) { 293 write_mrecord(1, HAMMER_MREC_TYPE_SYNC, 294 &mrec_tmp, sizeof(mrec_tmp.sync)); 295 } else { 296 write_mrecord(1, HAMMER_MREC_TYPE_IDLE, 297 &mrec_tmp, sizeof(mrec_tmp.sync)); 298 } 299 } 300 301 /* 302 * If the -2 option was given (automatic when doing mirror-copy), 303 * a two-way pipe is assumed and we expect a response mrec from 304 * the target. 305 */ 306 if (TwoWayPipeOpt) { 307 mrec = read_mrecord(0, &error, &pickup); 308 if (mrec == NULL || 309 mrec->head.type != HAMMER_MREC_TYPE_UPDATE || 310 mrec->head.rec_size != sizeof(mrec->update)) { 311 fprintf(stderr, "mirror_read: Did not get final " 312 "acknowledgement packet from target\n"); 313 exit(1); 314 } 315 if (interrupted) { 316 if (CyclePath) { 317 hammer_set_cycle(&mirror.key_cur, mirror.tid_beg); 318 fprintf(stderr, "Cyclefile %s updated for " 319 "continuation\n", CyclePath); 320 } 321 } else { 322 sync_tid = mrec->update.tid; 323 if (CyclePath) { 324 hammer_key_beg_init(&mirror.key_beg); 325 hammer_set_cycle(&mirror.key_beg, sync_tid); 326 fprintf(stderr, 327 "Cyclefile %s updated to 0x%016jx\n", 328 CyclePath, (uintmax_t)sync_tid); 329 } 330 } 331 } else if (CyclePath) { 332 /* NOTE! mirror.tid_beg cannot be updated */ 333 fprintf(stderr, "Warning: cycle file (-c option) cannot be " 334 "fully updated unless you use mirror-copy\n"); 335 hammer_set_cycle(&mirror.key_beg, mirror.tid_beg); 336 } 337 if (streaming && interrupted == 0) { 338 time_t t1 = time(NULL); 339 time_t t2; 340 341 /* 342 * Two way streaming tries to break down large bulk 343 * transfers into smaller ones so it can sync the 344 * transaction id on the slave. This way if we get 345 * interrupted a restart doesn't have to start from 346 * scratch. 347 */ 348 if (TwoWayPipeOpt && streaming && histogram > 0) { 349 if (VerboseOpt) 350 fprintf(stderr, " (bulk incremental)"); 351 goto again; 352 } 353 354 if (VerboseOpt) { 355 fprintf(stderr, " W"); 356 fflush(stderr); 357 } 358 pfs.ondisk->sync_end_tid = mirror.tid_end; 359 if (ioctl(fd, HAMMERIOC_WAI_PSEUDOFS, &pfs) < 0) { 360 fprintf(stderr, "Mirror-read %s: cannot stream: %s\n", 361 filesystem, strerror(errno)); 362 } else { 363 t2 = time(NULL) - t1; 364 if (t2 >= 0 && t2 < DelayOpt) { 365 if (VerboseOpt) { 366 fprintf(stderr, "\bD"); 367 fflush(stderr); 368 } 369 sleep(DelayOpt - t2); 370 } 371 if (VerboseOpt) { 372 fprintf(stderr, "\b "); 373 fflush(stderr); 374 } 375 relpfs(fd, &pfs); 376 goto again; 377 } 378 } 379 write_mrecord(1, HAMMER_MREC_TYPE_TERM, 380 &mrec_tmp, sizeof(mrec_tmp.sync)); 381 relpfs(fd, &pfs); 382 fprintf(stderr, "Mirror-read %s succeeded\n", filesystem); 383 } 384 385 /* 386 * Ok, this isn't really a histogram. What we are trying to do 387 * here is find the first tid_end for the scan that returns 388 * at least some data. The frontend of the TID space will generally 389 * return nothing so we can't just divide out the full mirroring 390 * range. Once we find the point where a real data stream starts 391 * to get generated we can divide out the range from that point. 392 * 393 * When starting a new mirroring operation completely from scratch 394 * this code will take some time to run, but once some mirroring 395 * data is synchronized on the target you will be able to interrupt 396 * the stream and restart it and the later invocations of this 397 * code will be such that it should run much faster. 398 */ 399 static int 400 generate_histogram(int fd, const char *filesystem, 401 hammer_tid_t **histogram_ary, 402 struct hammer_ioc_mirror_rw *mirror_base) 403 { 404 struct hammer_ioc_mirror_rw mirror; 405 hammer_tid_t tid_beg; 406 hammer_tid_t tid_end; 407 hammer_tid_t tid_half; 408 int i; 409 410 mirror = *mirror_base; 411 tid_beg = mirror.tid_beg; 412 tid_end = mirror.tid_end; 413 414 if (*histogram_ary) 415 free(*histogram_ary); 416 if (tid_beg + BULK_MINIMUM >= tid_end) 417 return(0); 418 419 if (VerboseOpt) 420 fprintf(stderr, "Doing Range Test\n"); 421 while (tid_end - tid_beg > BULK_MINIMUM) { 422 tid_half = tid_beg + (tid_end - tid_beg) * 2 / 3; 423 mirror.count = 0; 424 mirror.tid_beg = tid_beg; 425 mirror.tid_end = tid_half; 426 427 if (VerboseOpt > 1) { 428 fprintf(stderr, "RangeTest %016jx/%016jx - %016jx (%jd) ", 429 (uintmax_t)tid_beg, 430 (uintmax_t)tid_end, 431 (uintmax_t)tid_half, 432 (intmax_t)(tid_half - tid_beg)); 433 } 434 fflush(stderr); 435 if (ioctl(fd, HAMMERIOC_MIRROR_READ, &mirror) < 0) { 436 fprintf(stderr, "Mirror-read %s failed: %s\n", 437 filesystem, strerror(errno)); 438 exit(1); 439 } 440 if (mirror.head.flags & HAMMER_IOC_HEAD_ERROR) { 441 fprintf(stderr, 442 "Mirror-read %s fatal error %d\n", 443 filesystem, mirror.head.error); 444 exit(1); 445 } 446 if (VerboseOpt > 1) 447 fprintf(stderr, "%d\n", mirror.count); 448 if (mirror.count > SERIALBUF_SIZE / 2) { 449 tid_end = tid_half; 450 } else { 451 tid_beg = tid_half; 452 } 453 } 454 455 tid_end = mirror_base->tid_end; 456 fprintf(stderr, "histogram range %016llx - %016llx\n", 457 (long long)tid_beg, (long long)tid_end); 458 459 /* 460 * The final array generates our incremental ending tids in 461 * reverse order. The caller also picks them off in reverse order. 462 */ 463 *histogram_ary = malloc(sizeof(hammer_tid_t) * 20); 464 for (i = 0; i < 20; ++i) { 465 (*histogram_ary)[i] = tid_end - (tid_end - tid_beg) / 20 * i; 466 } 467 return(20); 468 } 469 470 static void 471 create_pfs(const char *filesystem, uuid_t *s_uuid) 472 { 473 if (ForceYesOpt == 1) { 474 fprintf(stderr, "PFS slave %s does not exist. " 475 "Auto create new slave PFS!\n", filesystem); 476 477 } else { 478 fprintf(stderr, "PFS slave %s does not exist.\n" 479 "Do you want to create a new slave PFS? (yes|no) ", 480 filesystem); 481 fflush(stderr); 482 if (getyn() != 1) { 483 fprintf(stderr, "Aborting operation\n"); 484 exit(1); 485 } 486 } 487 488 u_int32_t status; 489 char *shared_uuid = NULL; 490 uuid_to_string(s_uuid, &shared_uuid, &status); 491 492 char *cmd = NULL; 493 asprintf(&cmd, "/sbin/hammer pfs-slave '%s' shared-uuid=%s 1>&2", 494 filesystem, shared_uuid); 495 free(shared_uuid); 496 497 if (cmd == NULL) { 498 fprintf(stderr, "Failed to alloc memory\n"); 499 exit(1); 500 } 501 if (system(cmd) != 0) { 502 fprintf(stderr, "Failed to create PFS\n"); 503 } 504 free(cmd); 505 } 506 507 /* 508 * Pipe the mirroring data stream on stdin to the HAMMER VFS, adding 509 * some additional packet types to negotiate TID ranges and to verify 510 * completion. The HAMMER VFS does most of the work. 511 * 512 * It is important to note that the mirror.key_{beg,end} range must 513 * match the ranged used by the original. For now both sides use 514 * range the entire key space. 515 * 516 * It is even more important that the records in the stream conform 517 * to the TID range also supplied in the stream. The HAMMER VFS will 518 * use the REC, PASS, and SKIP record types to track the portions of 519 * the B-Tree being scanned in order to be able to proactively delete 520 * records on the target within those active areas that are not mentioned 521 * by the source. 522 * 523 * The mirror.key_cur field is used by the VFS to do this tracking. It 524 * must be initialized to key_beg but then is persistently updated by 525 * the HAMMER VFS on each successive ioctl() call. If you blow up this 526 * field you will blow up the mirror target, possibly to the point of 527 * deleting everything. As a safety measure the HAMMER VFS simply marks 528 * the records that the source has destroyed as deleted on the target, 529 * and normal pruning operations will deal with their final disposition 530 * at some later time. 531 */ 532 void 533 hammer_cmd_mirror_write(char **av, int ac) 534 { 535 struct hammer_ioc_mirror_rw mirror; 536 const char *filesystem; 537 char *buf = malloc(SERIALBUF_SIZE); 538 struct hammer_ioc_pseudofs_rw pfs; 539 struct hammer_ioc_mrecord_head pickup; 540 struct hammer_ioc_synctid synctid; 541 union hammer_ioc_mrecord_any mrec_tmp; 542 hammer_ioc_mrecord_any_t mrec; 543 struct stat st; 544 int error; 545 int fd; 546 int n; 547 548 if (ac != 1) 549 mirror_usage(1); 550 filesystem = av[0]; 551 552 pickup.signature = 0; 553 pickup.type = 0; 554 555 again: 556 bzero(&mirror, sizeof(mirror)); 557 hammer_key_beg_init(&mirror.key_beg); 558 hammer_key_end_init(&mirror.key_end); 559 mirror.key_end = mirror.key_beg; 560 561 /* 562 * Read initial packet 563 */ 564 mrec = read_mrecord(0, &error, &pickup); 565 if (mrec == NULL) { 566 if (error == 0) 567 fprintf(stderr, "validate_mrec_header: short read\n"); 568 exit(1); 569 } 570 /* 571 * Validate packet 572 */ 573 if (mrec->head.type == HAMMER_MREC_TYPE_TERM) { 574 return; 575 } 576 if (mrec->head.type != HAMMER_MREC_TYPE_PFSD) { 577 fprintf(stderr, "validate_mrec_header: did not get expected " 578 "PFSD record type\n"); 579 exit(1); 580 } 581 if (mrec->head.rec_size != sizeof(mrec->pfs)) { 582 fprintf(stderr, "validate_mrec_header: unexpected payload " 583 "size\n"); 584 exit(1); 585 } 586 587 /* 588 * Create slave PFS if it doesn't yet exist 589 */ 590 if (lstat(filesystem, &st) != 0) { 591 create_pfs(filesystem, &mrec->pfs.pfsd.shared_uuid); 592 } 593 free(mrec); 594 mrec = NULL; 595 596 fd = getpfs(&pfs, filesystem); 597 598 /* 599 * In two-way mode the target writes out a PFS packet first. 600 * The source uses our tid_end as its tid_beg by default, 601 * picking up where it left off. 602 */ 603 mirror.tid_beg = 0; 604 if (TwoWayPipeOpt) { 605 generate_mrec_header(fd, pfs.pfs_id, &mrec_tmp); 606 if (mirror.tid_beg < mrec_tmp.pfs.pfsd.sync_beg_tid) 607 mirror.tid_beg = mrec_tmp.pfs.pfsd.sync_beg_tid; 608 mirror.tid_end = mrec_tmp.pfs.pfsd.sync_end_tid; 609 write_mrecord(1, HAMMER_MREC_TYPE_PFSD, 610 &mrec_tmp, sizeof(mrec_tmp.pfs)); 611 } 612 613 /* 614 * Read and process the PFS header. The source informs us of 615 * the TID range the stream represents. 616 */ 617 n = validate_mrec_header(fd, 0, 1, pfs.pfs_id, &pickup, 618 &mirror.tid_beg, &mirror.tid_end); 619 if (n < 0) { /* got TERM record */ 620 relpfs(fd, &pfs); 621 return; 622 } 623 624 mirror.ubuf = buf; 625 mirror.size = SERIALBUF_SIZE; 626 627 /* 628 * Read and process bulk records (REC, PASS, and SKIP types). 629 * 630 * On your life, do NOT mess with mirror.key_cur or your mirror 631 * target may become history. 632 */ 633 for (;;) { 634 mirror.count = 0; 635 mirror.pfs_id = pfs.pfs_id; 636 mirror.shared_uuid = pfs.ondisk->shared_uuid; 637 mirror.size = read_mrecords(0, buf, SERIALBUF_SIZE, &pickup); 638 if (mirror.size <= 0) 639 break; 640 if (ioctl(fd, HAMMERIOC_MIRROR_WRITE, &mirror) < 0) { 641 fprintf(stderr, "Mirror-write %s failed: %s\n", 642 filesystem, strerror(errno)); 643 exit(1); 644 } 645 if (mirror.head.flags & HAMMER_IOC_HEAD_ERROR) { 646 fprintf(stderr, 647 "Mirror-write %s fatal error %d\n", 648 filesystem, mirror.head.error); 649 exit(1); 650 } 651 #if 0 652 if (mirror.head.flags & HAMMER_IOC_HEAD_INTR) { 653 fprintf(stderr, 654 "Mirror-write %s interrupted by timer at" 655 " %016llx\n", 656 filesystem, 657 mirror.key_cur.obj_id); 658 exit(0); 659 } 660 #endif 661 } 662 663 /* 664 * Read and process the termination sync record. 665 */ 666 mrec = read_mrecord(0, &error, &pickup); 667 668 if (mrec && mrec->head.type == HAMMER_MREC_TYPE_TERM) { 669 fprintf(stderr, "Mirror-write: received termination request\n"); 670 free(mrec); 671 return; 672 } 673 674 if (mrec == NULL || 675 (mrec->head.type != HAMMER_MREC_TYPE_SYNC && 676 mrec->head.type != HAMMER_MREC_TYPE_IDLE) || 677 mrec->head.rec_size != sizeof(mrec->sync)) { 678 fprintf(stderr, "Mirror-write %s: Did not get termination " 679 "sync record, or rec_size is wrong rt=%d\n", 680 filesystem, mrec->head.type); 681 exit(1); 682 } 683 684 /* 685 * Update the PFS info on the target so the user has visibility 686 * into the new snapshot, and sync the target filesystem. 687 */ 688 if (mrec->head.type == HAMMER_MREC_TYPE_SYNC) { 689 update_pfs_snapshot(fd, mirror.tid_end, pfs.pfs_id); 690 691 bzero(&synctid, sizeof(synctid)); 692 synctid.op = HAMMER_SYNCTID_SYNC2; 693 ioctl(fd, HAMMERIOC_SYNCTID, &synctid); 694 695 if (VerboseOpt >= 2) { 696 fprintf(stderr, "Mirror-write %s: succeeded\n", 697 filesystem); 698 } 699 } 700 701 free(mrec); 702 mrec = NULL; 703 704 /* 705 * Report back to the originator. 706 */ 707 if (TwoWayPipeOpt) { 708 mrec_tmp.update.tid = mirror.tid_end; 709 write_mrecord(1, HAMMER_MREC_TYPE_UPDATE, 710 &mrec_tmp, sizeof(mrec_tmp.update)); 711 } else { 712 printf("Source can update synctid to 0x%016jx\n", 713 (uintmax_t)mirror.tid_end); 714 } 715 relpfs(fd, &pfs); 716 goto again; 717 } 718 719 void 720 hammer_cmd_mirror_dump(void) 721 { 722 char *buf = malloc(SERIALBUF_SIZE); 723 struct hammer_ioc_mrecord_head pickup; 724 hammer_ioc_mrecord_any_t mrec; 725 int error; 726 int size; 727 int offset; 728 int bytes; 729 730 /* 731 * Read and process the PFS header 732 */ 733 pickup.signature = 0; 734 pickup.type = 0; 735 736 mrec = read_mrecord(0, &error, &pickup); 737 738 /* 739 * Read and process bulk records 740 */ 741 for (;;) { 742 size = read_mrecords(0, buf, SERIALBUF_SIZE, &pickup); 743 if (size <= 0) 744 break; 745 offset = 0; 746 while (offset < size) { 747 mrec = (void *)((char *)buf + offset); 748 bytes = HAMMER_HEAD_DOALIGN(mrec->head.rec_size); 749 if (offset + bytes > size) { 750 fprintf(stderr, "Misaligned record\n"); 751 exit(1); 752 } 753 754 switch(mrec->head.type & HAMMER_MRECF_TYPE_MASK) { 755 case HAMMER_MREC_TYPE_REC_BADCRC: 756 case HAMMER_MREC_TYPE_REC: 757 printf("Record obj=%016jx key=%016jx " 758 "rt=%02x ot=%02x", 759 (uintmax_t)mrec->rec.leaf.base.obj_id, 760 (uintmax_t)mrec->rec.leaf.base.key, 761 mrec->rec.leaf.base.rec_type, 762 mrec->rec.leaf.base.obj_type); 763 if (mrec->head.type == 764 HAMMER_MREC_TYPE_REC_BADCRC) { 765 printf(" (BAD CRC)"); 766 } 767 printf("\n"); 768 printf(" tids %016jx:%016jx data=%d\n", 769 (uintmax_t)mrec->rec.leaf.base.create_tid, 770 (uintmax_t)mrec->rec.leaf.base.delete_tid, 771 mrec->rec.leaf.data_len); 772 break; 773 case HAMMER_MREC_TYPE_PASS: 774 printf("Pass obj=%016jx key=%016jx " 775 "rt=%02x ot=%02x\n", 776 (uintmax_t)mrec->rec.leaf.base.obj_id, 777 (uintmax_t)mrec->rec.leaf.base.key, 778 mrec->rec.leaf.base.rec_type, 779 mrec->rec.leaf.base.obj_type); 780 printf(" tids %016jx:%016jx data=%d\n", 781 (uintmax_t)mrec->rec.leaf.base.create_tid, 782 (uintmax_t)mrec->rec.leaf.base.delete_tid, 783 mrec->rec.leaf.data_len); 784 break; 785 case HAMMER_MREC_TYPE_SKIP: 786 printf("Skip obj=%016jx key=%016jx rt=%02x to\n" 787 " obj=%016jx key=%016jx rt=%02x\n", 788 (uintmax_t)mrec->skip.skip_beg.obj_id, 789 (uintmax_t)mrec->skip.skip_beg.key, 790 mrec->skip.skip_beg.rec_type, 791 (uintmax_t)mrec->skip.skip_end.obj_id, 792 (uintmax_t)mrec->skip.skip_end.key, 793 mrec->skip.skip_end.rec_type); 794 default: 795 break; 796 } 797 offset += bytes; 798 } 799 } 800 801 /* 802 * Read and process the termination sync record. 803 */ 804 mrec = read_mrecord(0, &error, &pickup); 805 if (mrec == NULL || 806 (mrec->head.type != HAMMER_MREC_TYPE_SYNC && 807 mrec->head.type != HAMMER_MREC_TYPE_IDLE) 808 ) { 809 fprintf(stderr, "Mirror-dump: Did not get termination " 810 "sync record\n"); 811 } 812 } 813 814 void 815 hammer_cmd_mirror_copy(char **av, int ac, int streaming) 816 { 817 pid_t pid1; 818 pid_t pid2; 819 int fds[2]; 820 const char *xav[16]; 821 char tbuf[16]; 822 char *ptr; 823 int xac; 824 825 if (ac != 2) 826 mirror_usage(1); 827 828 TwoWayPipeOpt = 1; 829 signal(SIGPIPE, SIG_IGN); 830 831 again: 832 if (pipe(fds) < 0) { 833 perror("pipe"); 834 exit(1); 835 } 836 837 /* 838 * Source 839 */ 840 if ((pid1 = fork()) == 0) { 841 signal(SIGPIPE, SIG_DFL); 842 dup2(fds[0], 0); 843 dup2(fds[0], 1); 844 close(fds[0]); 845 close(fds[1]); 846 if ((ptr = strchr(av[0], ':')) != NULL) { 847 *ptr++ = 0; 848 xac = 0; 849 xav[xac++] = "ssh"; 850 xav[xac++] = av[0]; 851 xav[xac++] = "hammer"; 852 853 switch(VerboseOpt) { 854 case 0: 855 break; 856 case 1: 857 xav[xac++] = "-v"; 858 break; 859 case 2: 860 xav[xac++] = "-vv"; 861 break; 862 default: 863 xav[xac++] = "-vvv"; 864 break; 865 } 866 if (ForceYesOpt) { 867 xav[xac++] = "-y"; 868 } 869 xav[xac++] = "-2"; 870 if (TimeoutOpt) { 871 snprintf(tbuf, sizeof(tbuf), "%d", TimeoutOpt); 872 xav[xac++] = "-t"; 873 xav[xac++] = tbuf; 874 } 875 if (streaming) 876 xav[xac++] = "mirror-read-stream"; 877 else 878 xav[xac++] = "mirror-read"; 879 xav[xac++] = ptr; 880 xav[xac++] = NULL; 881 execv("/usr/bin/ssh", (void *)xav); 882 } else { 883 hammer_cmd_mirror_read(av, 1, streaming); 884 fflush(stdout); 885 fflush(stderr); 886 } 887 _exit(1); 888 } 889 890 /* 891 * Target 892 */ 893 if ((pid2 = fork()) == 0) { 894 signal(SIGPIPE, SIG_DFL); 895 dup2(fds[1], 0); 896 dup2(fds[1], 1); 897 close(fds[0]); 898 close(fds[1]); 899 if ((ptr = strchr(av[1], ':')) != NULL) { 900 *ptr++ = 0; 901 xac = 0; 902 xav[xac++] = "ssh"; 903 xav[xac++] = av[1]; 904 xav[xac++] = "hammer"; 905 906 switch(VerboseOpt) { 907 case 0: 908 break; 909 case 1: 910 xav[xac++] = "-v"; 911 break; 912 case 2: 913 xav[xac++] = "-vv"; 914 break; 915 default: 916 xav[xac++] = "-vvv"; 917 break; 918 } 919 if (ForceYesOpt) { 920 xav[xac++] = "-y"; 921 } 922 xav[xac++] = "-2"; 923 xav[xac++] = "mirror-write"; 924 xav[xac++] = ptr; 925 xav[xac++] = NULL; 926 execv("/usr/bin/ssh", (void *)xav); 927 } else { 928 hammer_cmd_mirror_write(av + 1, 1); 929 fflush(stdout); 930 fflush(stderr); 931 } 932 _exit(1); 933 } 934 close(fds[0]); 935 close(fds[1]); 936 937 while (waitpid(pid1, NULL, 0) <= 0) 938 ; 939 while (waitpid(pid2, NULL, 0) <= 0) 940 ; 941 942 /* 943 * If the link is lost restart 944 */ 945 if (streaming) { 946 if (VerboseOpt) { 947 fprintf(stderr, "\nLost Link\n"); 948 fflush(stderr); 949 } 950 sleep(15 + DelayOpt); 951 goto again; 952 } 953 954 } 955 956 /* 957 * Read and return multiple mrecords 958 */ 959 static int 960 read_mrecords(int fd, char *buf, u_int size, hammer_ioc_mrecord_head_t pickup) 961 { 962 hammer_ioc_mrecord_any_t mrec; 963 u_int count; 964 size_t n; 965 size_t i; 966 size_t bytes; 967 int type; 968 969 count = 0; 970 while (size - count >= HAMMER_MREC_HEADSIZE) { 971 /* 972 * Cached the record header in case we run out of buffer 973 * space. 974 */ 975 fflush(stdout); 976 if (pickup->signature == 0) { 977 for (n = 0; n < HAMMER_MREC_HEADSIZE; n += i) { 978 i = read(fd, (char *)pickup + n, 979 HAMMER_MREC_HEADSIZE - n); 980 if (i <= 0) 981 break; 982 } 983 if (n == 0) 984 break; 985 if (n != HAMMER_MREC_HEADSIZE) { 986 fprintf(stderr, "read_mrecords: short read on pipe\n"); 987 exit(1); 988 } 989 if (pickup->signature != HAMMER_IOC_MIRROR_SIGNATURE) { 990 fprintf(stderr, "read_mrecords: malformed record on pipe, " 991 "bad signature\n"); 992 exit(1); 993 } 994 } 995 if (pickup->rec_size < HAMMER_MREC_HEADSIZE || 996 pickup->rec_size > sizeof(*mrec) + HAMMER_XBUFSIZE) { 997 fprintf(stderr, "read_mrecords: malformed record on pipe, " 998 "illegal rec_size\n"); 999 exit(1); 1000 } 1001 1002 /* 1003 * Stop if we have insufficient space for the record and data. 1004 */ 1005 bytes = HAMMER_HEAD_DOALIGN(pickup->rec_size); 1006 if (size - count < bytes) 1007 break; 1008 1009 /* 1010 * Stop if the record type is not a REC, SKIP, or PASS, 1011 * which are the only types the ioctl supports. Other types 1012 * are used only by the userland protocol. 1013 * 1014 * Ignore all flags. 1015 */ 1016 type = pickup->type & HAMMER_MRECF_TYPE_LOMASK; 1017 if (type != HAMMER_MREC_TYPE_PFSD && 1018 type != HAMMER_MREC_TYPE_REC && 1019 type != HAMMER_MREC_TYPE_SKIP && 1020 type != HAMMER_MREC_TYPE_PASS) { 1021 break; 1022 } 1023 1024 /* 1025 * Read the remainder and clear the pickup signature. 1026 */ 1027 for (n = HAMMER_MREC_HEADSIZE; n < bytes; n += i) { 1028 i = read(fd, buf + count + n, bytes - n); 1029 if (i <= 0) 1030 break; 1031 } 1032 if (n != bytes) { 1033 fprintf(stderr, "read_mrecords: short read on pipe\n"); 1034 exit(1); 1035 } 1036 1037 bcopy(pickup, buf + count, HAMMER_MREC_HEADSIZE); 1038 pickup->signature = 0; 1039 pickup->type = 0; 1040 mrec = (void *)(buf + count); 1041 1042 /* 1043 * Validate the completed record 1044 */ 1045 if (mrec->head.rec_crc != 1046 crc32((char *)mrec + HAMMER_MREC_CRCOFF, 1047 mrec->head.rec_size - HAMMER_MREC_CRCOFF)) { 1048 fprintf(stderr, "read_mrecords: malformed record " 1049 "on pipe, bad crc\n"); 1050 exit(1); 1051 } 1052 1053 /* 1054 * If its a B-Tree record validate the data crc. 1055 * 1056 * NOTE: If the VFS passes us an explicitly errorde mrec 1057 * we just pass it through. 1058 */ 1059 type = mrec->head.type & HAMMER_MRECF_TYPE_MASK; 1060 1061 if (type == HAMMER_MREC_TYPE_REC) { 1062 if (mrec->head.rec_size < 1063 sizeof(mrec->rec) + mrec->rec.leaf.data_len) { 1064 fprintf(stderr, 1065 "read_mrecords: malformed record on " 1066 "pipe, illegal element data_len\n"); 1067 exit(1); 1068 } 1069 if (mrec->rec.leaf.data_len && 1070 mrec->rec.leaf.data_offset && 1071 hammer_crc_test_leaf(&mrec->rec + 1, &mrec->rec.leaf) == 0) { 1072 fprintf(stderr, 1073 "read_mrecords: data_crc did not " 1074 "match data! obj=%016jx key=%016jx\n", 1075 (uintmax_t)mrec->rec.leaf.base.obj_id, 1076 (uintmax_t)mrec->rec.leaf.base.key); 1077 fprintf(stderr, 1078 "continuing, but there are problems\n"); 1079 } 1080 } 1081 count += bytes; 1082 } 1083 return(count); 1084 } 1085 1086 /* 1087 * Read and return a single mrecord. 1088 */ 1089 static 1090 hammer_ioc_mrecord_any_t 1091 read_mrecord(int fdin, int *errorp, hammer_ioc_mrecord_head_t pickup) 1092 { 1093 hammer_ioc_mrecord_any_t mrec; 1094 struct hammer_ioc_mrecord_head mrechd; 1095 size_t bytes; 1096 size_t n; 1097 size_t i; 1098 1099 if (pickup && pickup->type != 0) { 1100 mrechd = *pickup; 1101 pickup->signature = 0; 1102 pickup->type = 0; 1103 n = HAMMER_MREC_HEADSIZE; 1104 } else { 1105 /* 1106 * Read in the PFSD header from the sender. 1107 */ 1108 for (n = 0; n < HAMMER_MREC_HEADSIZE; n += i) { 1109 i = read(fdin, (char *)&mrechd + n, HAMMER_MREC_HEADSIZE - n); 1110 if (i <= 0) 1111 break; 1112 } 1113 if (n == 0) { 1114 *errorp = 0; /* EOF */ 1115 return(NULL); 1116 } 1117 if (n != HAMMER_MREC_HEADSIZE) { 1118 fprintf(stderr, "short read of mrecord header\n"); 1119 *errorp = EPIPE; 1120 return(NULL); 1121 } 1122 } 1123 if (mrechd.signature != HAMMER_IOC_MIRROR_SIGNATURE) { 1124 fprintf(stderr, "read_mrecord: bad signature\n"); 1125 *errorp = EINVAL; 1126 return(NULL); 1127 } 1128 bytes = HAMMER_HEAD_DOALIGN(mrechd.rec_size); 1129 assert(bytes >= sizeof(mrechd)); 1130 mrec = malloc(bytes); 1131 mrec->head = mrechd; 1132 1133 while (n < bytes) { 1134 i = read(fdin, (char *)mrec + n, bytes - n); 1135 if (i <= 0) 1136 break; 1137 n += i; 1138 } 1139 if (n != bytes) { 1140 fprintf(stderr, "read_mrecord: short read on payload\n"); 1141 *errorp = EPIPE; 1142 return(NULL); 1143 } 1144 if (mrec->head.rec_crc != 1145 crc32((char *)mrec + HAMMER_MREC_CRCOFF, 1146 mrec->head.rec_size - HAMMER_MREC_CRCOFF)) { 1147 fprintf(stderr, "read_mrecord: bad CRC\n"); 1148 *errorp = EINVAL; 1149 return(NULL); 1150 } 1151 *errorp = 0; 1152 return(mrec); 1153 } 1154 1155 static 1156 void 1157 write_mrecord(int fdout, u_int32_t type, hammer_ioc_mrecord_any_t mrec, 1158 int bytes) 1159 { 1160 char zbuf[HAMMER_HEAD_ALIGN]; 1161 int pad; 1162 1163 pad = HAMMER_HEAD_DOALIGN(bytes) - bytes; 1164 1165 assert(bytes >= (int)sizeof(mrec->head)); 1166 bzero(&mrec->head, sizeof(mrec->head)); 1167 mrec->head.signature = HAMMER_IOC_MIRROR_SIGNATURE; 1168 mrec->head.type = type; 1169 mrec->head.rec_size = bytes; 1170 mrec->head.rec_crc = crc32((char *)mrec + HAMMER_MREC_CRCOFF, 1171 bytes - HAMMER_MREC_CRCOFF); 1172 if (write(fdout, mrec, bytes) != bytes) { 1173 fprintf(stderr, "write_mrecord: error %d (%s)\n", 1174 errno, strerror(errno)); 1175 exit(1); 1176 } 1177 if (pad) { 1178 bzero(zbuf, pad); 1179 if (write(fdout, zbuf, pad) != pad) { 1180 fprintf(stderr, "write_mrecord: error %d (%s)\n", 1181 errno, strerror(errno)); 1182 exit(1); 1183 } 1184 } 1185 } 1186 1187 /* 1188 * Generate a mirroring header with the pfs information of the 1189 * originating filesytem. 1190 */ 1191 static void 1192 generate_mrec_header(int fd, int pfs_id, 1193 union hammer_ioc_mrecord_any *mrec_tmp) 1194 { 1195 struct hammer_ioc_pseudofs_rw pfs; 1196 1197 bzero(&pfs, sizeof(pfs)); 1198 bzero(mrec_tmp, sizeof(*mrec_tmp)); 1199 pfs.pfs_id = pfs_id; 1200 pfs.ondisk = &mrec_tmp->pfs.pfsd; 1201 pfs.bytes = sizeof(mrec_tmp->pfs.pfsd); 1202 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) { 1203 fprintf(stderr, "Mirror-read: not a HAMMER fs/pseudofs!\n"); 1204 exit(1); 1205 } 1206 if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) { 1207 fprintf(stderr, "Mirror-read: HAMMER pfs version mismatch!\n"); 1208 exit(1); 1209 } 1210 mrec_tmp->pfs.version = pfs.version; 1211 } 1212 1213 /* 1214 * Validate the pfs information from the originating filesystem 1215 * against the target filesystem. shared_uuid must match. 1216 * 1217 * return -1 if we got a TERM record 1218 */ 1219 static int 1220 validate_mrec_header(int fd, int fdin, int is_target, int pfs_id, 1221 struct hammer_ioc_mrecord_head *pickup, 1222 hammer_tid_t *tid_begp, hammer_tid_t *tid_endp) 1223 { 1224 struct hammer_ioc_pseudofs_rw pfs; 1225 struct hammer_pseudofs_data pfsd; 1226 hammer_ioc_mrecord_any_t mrec; 1227 int error; 1228 1229 /* 1230 * Get the PFSD info from the target filesystem. 1231 */ 1232 bzero(&pfs, sizeof(pfs)); 1233 bzero(&pfsd, sizeof(pfsd)); 1234 pfs.pfs_id = pfs_id; 1235 pfs.ondisk = &pfsd; 1236 pfs.bytes = sizeof(pfsd); 1237 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) { 1238 fprintf(stderr, "mirror-write: not a HAMMER fs/pseudofs!\n"); 1239 exit(1); 1240 } 1241 if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) { 1242 fprintf(stderr, "mirror-write: HAMMER pfs version mismatch!\n"); 1243 exit(1); 1244 } 1245 1246 mrec = read_mrecord(fdin, &error, pickup); 1247 if (mrec == NULL) { 1248 if (error == 0) 1249 fprintf(stderr, "validate_mrec_header: short read\n"); 1250 exit(1); 1251 } 1252 if (mrec->head.type == HAMMER_MREC_TYPE_TERM) { 1253 free(mrec); 1254 return(-1); 1255 } 1256 1257 if (mrec->head.type != HAMMER_MREC_TYPE_PFSD) { 1258 fprintf(stderr, "validate_mrec_header: did not get expected " 1259 "PFSD record type\n"); 1260 exit(1); 1261 } 1262 if (mrec->head.rec_size != sizeof(mrec->pfs)) { 1263 fprintf(stderr, "validate_mrec_header: unexpected payload " 1264 "size\n"); 1265 exit(1); 1266 } 1267 if (mrec->pfs.version != pfs.version) { 1268 fprintf(stderr, "validate_mrec_header: Version mismatch\n"); 1269 exit(1); 1270 } 1271 1272 /* 1273 * Whew. Ok, is the read PFS info compatible with the target? 1274 */ 1275 if (bcmp(&mrec->pfs.pfsd.shared_uuid, &pfsd.shared_uuid, 1276 sizeof(pfsd.shared_uuid)) != 0) { 1277 fprintf(stderr, 1278 "mirror-write: source and target have " 1279 "different shared-uuid's!\n"); 1280 exit(1); 1281 } 1282 if (is_target && 1283 (pfsd.mirror_flags & HAMMER_PFSD_SLAVE) == 0) { 1284 fprintf(stderr, "mirror-write: target must be in slave mode\n"); 1285 exit(1); 1286 } 1287 if (tid_begp) 1288 *tid_begp = mrec->pfs.pfsd.sync_beg_tid; 1289 if (tid_endp) 1290 *tid_endp = mrec->pfs.pfsd.sync_end_tid; 1291 free(mrec); 1292 return(0); 1293 } 1294 1295 static void 1296 update_pfs_snapshot(int fd, hammer_tid_t snapshot_tid, int pfs_id) 1297 { 1298 struct hammer_ioc_pseudofs_rw pfs; 1299 struct hammer_pseudofs_data pfsd; 1300 1301 bzero(&pfs, sizeof(pfs)); 1302 bzero(&pfsd, sizeof(pfsd)); 1303 pfs.pfs_id = pfs_id; 1304 pfs.ondisk = &pfsd; 1305 pfs.bytes = sizeof(pfsd); 1306 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) { 1307 perror("update_pfs_snapshot (read)"); 1308 exit(1); 1309 } 1310 if (pfsd.sync_end_tid != snapshot_tid) { 1311 pfsd.sync_end_tid = snapshot_tid; 1312 if (ioctl(fd, HAMMERIOC_SET_PSEUDOFS, &pfs) != 0) { 1313 perror("update_pfs_snapshot (rewrite)"); 1314 exit(1); 1315 } 1316 if (VerboseOpt >= 2) { 1317 fprintf(stderr, 1318 "Mirror-write: Completed, updated snapshot " 1319 "to %016jx\n", 1320 (uintmax_t)snapshot_tid); 1321 } 1322 } 1323 } 1324 1325 /* 1326 * Bandwidth-limited write in chunks 1327 */ 1328 static 1329 ssize_t 1330 writebw(int fd, const void *buf, size_t nbytes, 1331 u_int64_t *bwcount, struct timeval *tv1) 1332 { 1333 struct timeval tv2; 1334 size_t n; 1335 ssize_t r; 1336 ssize_t a; 1337 int usec; 1338 1339 a = 0; 1340 r = 0; 1341 while (nbytes) { 1342 if (*bwcount + nbytes > BandwidthOpt) 1343 n = BandwidthOpt - *bwcount; 1344 else 1345 n = nbytes; 1346 if (n) 1347 r = write(fd, buf, n); 1348 if (r >= 0) { 1349 a += r; 1350 nbytes -= r; 1351 buf = (const char *)buf + r; 1352 } 1353 if ((size_t)r != n) 1354 break; 1355 *bwcount += n; 1356 if (*bwcount >= BandwidthOpt) { 1357 gettimeofday(&tv2, NULL); 1358 usec = (int)(tv2.tv_sec - tv1->tv_sec) * 1000000 + 1359 (int)(tv2.tv_usec - tv1->tv_usec); 1360 if (usec >= 0 && usec < 1000000) 1361 usleep(1000000 - usec); 1362 gettimeofday(tv1, NULL); 1363 *bwcount -= BandwidthOpt; 1364 } 1365 } 1366 return(a ? a : r); 1367 } 1368 1369 /* 1370 * Get a yes or no answer from the terminal. The program may be run as 1371 * part of a two-way pipe so we cannot use stdin for this operation. 1372 */ 1373 static int 1374 getyn(void) 1375 { 1376 char buf[256]; 1377 FILE *fp; 1378 int result; 1379 1380 fp = fopen("/dev/tty", "r"); 1381 if (fp == NULL) { 1382 fprintf(stderr, "No terminal for response\n"); 1383 return(-1); 1384 } 1385 result = -1; 1386 while (fgets(buf, sizeof(buf), fp) != NULL) { 1387 if (buf[0] == 'y' || buf[0] == 'Y') { 1388 result = 1; 1389 break; 1390 } 1391 if (buf[0] == 'n' || buf[0] == 'N') { 1392 result = 0; 1393 break; 1394 } 1395 fprintf(stderr, "Response not understood\n"); 1396 break; 1397 } 1398 fclose(fp); 1399 return(result); 1400 } 1401 1402 static void 1403 mirror_usage(int code) 1404 { 1405 fprintf(stderr, 1406 "hammer mirror-read <filesystem> [begin-tid]\n" 1407 "hammer mirror-read-stream <filesystem> [begin-tid]\n" 1408 "hammer mirror-write <filesystem>\n" 1409 "hammer mirror-dump\n" 1410 "hammer mirror-copy [[user@]host:]<filesystem>" 1411 " [[user@]host:]<filesystem>\n" 1412 "hammer mirror-stream [[user@]host:]<filesystem>" 1413 " [[user@]host:]<filesystem>\n" 1414 ); 1415 exit(code); 1416 } 1417 1418