HAMMER Utilities: Sync with 60F
[dragonfly.git] / sbin / hammer / cmd_mirror.c
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.5 2008/07/07 03:51:28 dillon Exp $
35  */
36
37 #include "hammer.h"
38
39 #define SERIALBUF_SIZE  (512 * 1024)
40
41 struct hammer_pfs_head {
42         u_int32_t version;
43         struct hammer_pseudofs_data pfsd;
44 };
45
46 static int read_mrecords(int fd, char *buf, u_int size,
47                          hammer_ioc_mrecord_t pickup);
48 static struct hammer_ioc_mrecord *read_mrecord(int fdin, int *errorp,
49                          hammer_ioc_mrecord_t pickup);
50 static void write_mrecord(int fdout, u_int32_t type, void *payload, int bytes);
51 static void generate_mrec_header(int fd, int fdout,
52                          hammer_tid_t *tid_begp, hammer_tid_t *tid_endp);
53 static void validate_mrec_header(int fd, int fdin,
54                          hammer_tid_t *tid_begp, hammer_tid_t *tid_endp);
55 static void update_pfs_snapshot(int fd, hammer_tid_t snapshot_tid);
56 static void mirror_usage(int code);
57
58 void
59 hammer_cmd_mirror_read(char **av, int ac)
60 {
61         struct hammer_ioc_mirror_rw mirror;
62         hammer_ioc_mrecord_t mrec;
63         hammer_tid_t sync_tid;
64         const char *filesystem;
65         char *buf = malloc(SERIALBUF_SIZE);
66         int interrupted = 0;
67         int error;
68         int fd;
69         int n;
70         time_t base_t = time(NULL);
71
72         if (ac > 2)
73                 mirror_usage(1);
74         filesystem = av[0];
75
76         bzero(&mirror, sizeof(mirror));
77         hammer_key_beg_init(&mirror.key_beg);
78         hammer_key_end_init(&mirror.key_end);
79
80         fd = open(filesystem, O_RDONLY);
81         if (fd < 0)
82                 err(1, "Unable to open %s", filesystem);
83
84         /*
85          * Write out the PFS header
86          */
87         generate_mrec_header(fd, 1, &mirror.tid_beg, &mirror.tid_end);
88         hammer_get_cycle(&mirror.key_beg, &mirror.tid_beg);
89
90         fprintf(stderr, "mirror-read: Mirror from %016llx to %016llx\n",
91                 mirror.tid_beg, mirror.tid_end);
92         if (mirror.key_beg.obj_id != (int64_t)HAMMER_MIN_OBJID) {
93                 fprintf(stderr, "mirror-read: Resuming at object %016llx\n",
94                         mirror.key_beg.obj_id);
95         }
96
97         /*
98          * Write out bulk records
99          */
100         mirror.ubuf = buf;
101         mirror.size = SERIALBUF_SIZE;
102         if (ac == 2)
103                 mirror.tid_beg = strtoull(av[1], NULL, 0);
104
105         do {
106                 mirror.count = 0;
107                 if (ioctl(fd, HAMMERIOC_MIRROR_READ, &mirror) < 0) {
108                         fprintf(stderr, "Mirror-read %s failed: %s\n",
109                                 filesystem, strerror(errno));
110                         exit(1);
111                 }
112                 if (mirror.count) {
113                         n = write(1, mirror.ubuf, mirror.count);
114                         if (n != mirror.count) {
115                                 fprintf(stderr, "Mirror-read %s failed: "
116                                                 "short write\n",
117                                 filesystem);
118                                 exit(1);
119                         }
120                 }
121                 mirror.key_beg = mirror.key_cur;
122                 if (TimeoutOpt &&
123                     (unsigned)(time(NULL) - base_t) > (unsigned)TimeoutOpt) {
124                         fprintf(stderr,
125                                 "Mirror-read %s interrupted by timer at"
126                                 " %016llx\n",
127                                 filesystem,
128                                 mirror.key_cur.obj_id);
129                         interrupted = 1;
130                         break;
131                 }
132         } while (mirror.count != 0);
133
134         /*
135          * Write out the termination sync record
136          */
137         write_mrecord(1, HAMMER_MREC_TYPE_SYNC, NULL, 0);
138
139         /*
140          * If the -2 option was given (automatic when doing mirror-copy),
141          * a two-way pipe is assumed and we expect a response mrec from
142          * the target.
143          */
144         if (TwoWayPipeOpt) {
145                 mrec = read_mrecord(0, &error, NULL);
146                 if (mrec == NULL || mrec->type != HAMMER_MREC_TYPE_UPDATE) {
147                         fprintf(stderr, "mirror_read: Did not get final "
148                                         "acknowledgement packet from target\n");
149                         exit(1);
150                 }
151                 if (interrupted) {
152                         if (CyclePath) {
153                                 hammer_set_cycle(&mirror.key_cur, mirror.tid_beg);
154                                 fprintf(stderr, "Cyclefile %s updated for continuation\n", CyclePath);
155                         }
156                 } else {
157                         sync_tid = *(hammer_tid_t *)(mrec + 1);
158                         if (CyclePath) {
159                                 hammer_key_beg_init(&mirror.key_beg);
160                                 hammer_set_cycle(&mirror.key_beg, sync_tid);
161                                 fprintf(stderr, "Cyclefile %s updated to 0x%016llx\n",
162                                         CyclePath, sync_tid);
163                         } else {
164                                 fprintf(stderr, "Source can update synctid "
165                                                 "to 0x%016llx\n",
166                                         sync_tid);
167                         }
168                 }
169         } else if (CyclePath) {
170                 /* NOTE! mirror.tid_beg cannot be updated */
171                 fprintf(stderr, "Warning: cycle file (-c option) cannot be "
172                                 "fully updated unless you use mirror-copy\n");
173                 hammer_set_cycle(&mirror.key_beg, mirror.tid_beg);
174         }
175         fprintf(stderr, "Mirror-read %s succeeded\n", filesystem);
176 }
177
178 void
179 hammer_cmd_mirror_write(char **av, int ac)
180 {
181         struct hammer_ioc_mirror_rw mirror;
182         const char *filesystem;
183         char *buf = malloc(SERIALBUF_SIZE);
184         struct hammer_ioc_mrecord pickup;
185         struct hammer_ioc_synctid synctid;
186         hammer_ioc_mrecord_t mrec;
187         int error;
188         int fd;
189
190         if (ac > 2)
191                 mirror_usage(1);
192         filesystem = av[0];
193
194         bzero(&mirror, sizeof(mirror));
195         hammer_key_beg_init(&mirror.key_beg);
196         hammer_key_end_init(&mirror.key_end);
197
198         fd = open(filesystem, O_RDONLY);
199         if (fd < 0)
200                 err(1, "Unable to open %s", filesystem);
201
202         /*
203          * Read and process the PFS header 
204          */
205         validate_mrec_header(fd, 0, &mirror.tid_beg, &mirror.tid_end);
206
207         mirror.ubuf = buf;
208         mirror.size = SERIALBUF_SIZE;
209
210         pickup.signature = 0;
211         pickup.type = 0;
212
213         /*
214          * Read and process bulk records
215          */
216         for (;;) {
217                 mirror.count = 0;
218                 mirror.size = read_mrecords(0, buf, SERIALBUF_SIZE, &pickup);
219                 if (mirror.size <= 0)
220                         break;
221                 if (ioctl(fd, HAMMERIOC_MIRROR_WRITE, &mirror) < 0) {
222                         fprintf(stderr, "Mirror-write %s failed: %s\n",
223                                 filesystem, strerror(errno));
224                         exit(1);
225                 }
226 #if 0
227                 if (mirror.head.flags & HAMMER_IOC_HEAD_INTR) {
228                         fprintf(stderr,
229                                 "Mirror-write %s interrupted by timer at"
230                                 " %016llx\n",
231                                 filesystem,
232                                 mirror.key_cur.obj_id);
233                         exit(0);
234                 }
235 #endif
236                 mirror.key_beg = mirror.key_cur;
237         }
238
239         /*
240          * Read and process the termination sync record.
241          */
242         mrec = read_mrecord(0, &error, &pickup);
243         if (mrec == NULL || mrec->type != HAMMER_MREC_TYPE_SYNC) {
244                 fprintf(stderr, "Mirror-write %s: Did not get termination "
245                                 "sync record\n",
246                                 filesystem);
247         }
248
249         /*
250          * Update the PFS info on the target so the user has visibility
251          * into the new snapshot.
252          */
253         update_pfs_snapshot(fd, mirror.tid_end);
254
255         /*
256          * Sync the target filesystem
257          */
258         bzero(&synctid, sizeof(synctid));
259         synctid.op = HAMMER_SYNCTID_SYNC2;
260         ioctl(fd, HAMMERIOC_SYNCTID, &synctid);
261
262         fprintf(stderr, "Mirror-write %s: succeeded\n", filesystem);
263
264         /*
265          * Report back to the originator.
266          */
267         if (TwoWayPipeOpt) {
268                 write_mrecord(1, HAMMER_MREC_TYPE_UPDATE,
269                               &mirror.tid_end, sizeof(mirror.tid_end));
270         } else {
271                 printf("Source can update synctid to 0x%016llx\n",
272                        mirror.tid_end);
273         }
274 }
275
276 void
277 hammer_cmd_mirror_dump(void)
278 {
279         char *buf = malloc(SERIALBUF_SIZE);
280         struct hammer_ioc_mrecord pickup;
281         hammer_ioc_mrecord_t mrec;
282         int error;
283         int size;
284
285         /*
286          * Read and process the PFS header 
287          */
288         pickup.signature = 0;
289         pickup.type = 0;
290
291         mrec = read_mrecord(0, &error, &pickup);
292
293         /*
294          * Read and process bulk records
295          */
296         for (;;) {
297                 size = read_mrecords(0, buf, SERIALBUF_SIZE, &pickup);
298                 if (size <= 0)
299                         break;
300                 mrec = (void *)buf;
301                 while (mrec < (hammer_ioc_mrecord_t)((char *)buf + size)) {
302                         printf("Record obj=%016llx key=%016llx "
303                                "rt=%02x ot=%02x\n",
304                                 mrec->leaf.base.obj_id,
305                                 mrec->leaf.base.key,
306                                 mrec->leaf.base.rec_type,
307                                 mrec->leaf.base.obj_type);
308                         printf("       tids %016llx:%016llx data=%d\n",
309                                 mrec->leaf.base.create_tid,
310                                 mrec->leaf.base.delete_tid,
311                                 mrec->leaf.data_len);
312                         mrec = (void *)((char *)mrec + mrec->rec_size);
313                 }
314         }
315
316         /*
317          * Read and process the termination sync record.
318          */
319         mrec = read_mrecord(0, &error, &pickup);
320         if (mrec == NULL || mrec->type != HAMMER_MREC_TYPE_SYNC) {
321                 fprintf(stderr, "Mirror-dump: Did not get termination "
322                                 "sync record\n");
323         }
324 }
325
326 void
327 hammer_cmd_mirror_copy(char **av, int ac)
328 {
329         pid_t pid1;
330         pid_t pid2;
331         int fds[2];
332         const char *xav[16];
333         char tbuf[16];
334         char *ptr;
335         int xac;
336
337         if (ac != 2)
338                 mirror_usage(1);
339
340         if (pipe(fds) < 0) {
341                 perror("pipe");
342                 exit(1);
343         }
344
345         TwoWayPipeOpt = 1;
346
347         /*
348          * Source
349          */
350         if ((pid1 = fork()) == 0) {
351                 dup2(fds[0], 0);
352                 dup2(fds[0], 1);
353                 close(fds[0]);
354                 close(fds[1]);
355                 if ((ptr = strchr(av[0], ':')) != NULL) {
356                         *ptr++ = 0;
357                         xac = 0;
358                         xav[xac++] = "ssh";
359                         xav[xac++] = av[0];
360                         xav[xac++] = "hammer";
361                         if (VerboseOpt)
362                                 xav[xac++] = "-v";
363                         xav[xac++] = "-2";
364                         if (TimeoutOpt) {
365                                 snprintf(tbuf, sizeof(tbuf), "%d", TimeoutOpt);
366                                 xav[xac++] = "-t";
367                                 xav[xac++] = tbuf;
368                         }
369                         xav[xac++] = "mirror-read";
370                         xav[xac++] = ptr;
371                         xav[xac++] = NULL;
372                         execv("/usr/bin/ssh", (void *)xav);
373                 } else {
374                         hammer_cmd_mirror_read(av, 1);
375                         fflush(stdout);
376                         fflush(stderr);
377                 }
378                 _exit(1);
379         }
380
381         /*
382          * Target
383          */
384         if ((pid2 = fork()) == 0) {
385                 dup2(fds[1], 0);
386                 dup2(fds[1], 1);
387                 close(fds[0]);
388                 close(fds[1]);
389                 if ((ptr = strchr(av[1], ':')) != NULL) {
390                         *ptr++ = 0;
391                         xac = 0;
392                         xav[xac++] = "ssh";
393                         xav[xac++] = av[1];
394                         xav[xac++] = "hammer";
395                         if (VerboseOpt)
396                                 xav[xac++] = "-v";
397                         xav[xac++] = "-2";
398                         xav[xac++] = "mirror-write";
399                         xav[xac++] = ptr;
400                         xav[xac++] = NULL;
401                         execv("/usr/bin/ssh", (void *)xav);
402                 } else {
403                         hammer_cmd_mirror_write(av + 1, 1);
404                         fflush(stdout);
405                         fflush(stderr);
406                 }
407                 _exit(1);
408         }
409         close(fds[0]);
410         close(fds[1]);
411
412         while (waitpid(pid1, NULL, 0) <= 0)
413                 ;
414         while (waitpid(pid2, NULL, 0) <= 0)
415                 ;
416 }
417
418 /*
419  * Read and return multiple mrecords
420  */
421 static int
422 read_mrecords(int fd, char *buf, u_int size, hammer_ioc_mrecord_t pickup)
423 {
424         u_int count;
425         size_t n;
426         size_t i;
427
428         count = 0;
429         while (size - count >= HAMMER_MREC_HEADSIZE) {
430                 /*
431                  * Cached the record header in case we run out of buffer
432                  * space.
433                  */
434                 if (pickup->signature == 0) {
435                         for (n = 0; n < HAMMER_MREC_HEADSIZE; n += i) {
436                                 i = read(fd, (char *)pickup + n,
437                                          HAMMER_MREC_HEADSIZE - n);
438                                 if (i <= 0)
439                                         break;
440                         }
441                         if (n == 0)
442                                 break;
443                         if (n != HAMMER_MREC_HEADSIZE) {
444                                 fprintf(stderr, "read_mrecords: short read on pipe\n");
445                                 exit(1);
446                         }
447
448                         if (pickup->signature != HAMMER_IOC_MIRROR_SIGNATURE) {
449                                 fprintf(stderr, "read_mrecords: malformed record on pipe, bad signature\n");
450                                 exit(1);
451                         }
452                         if (pickup->rec_crc != crc32((char *)pickup + HAMMER_MREC_CRCOFF, HAMMER_MREC_HEADSIZE - HAMMER_MREC_CRCOFF)) {
453                                 fprintf(stderr, "read_mrecords: malformed record on pipe, bad crc\n");
454                                 exit(1);
455                         }
456                 }
457                 if (pickup->rec_size < HAMMER_MREC_HEADSIZE ||
458                     pickup->rec_size > HAMMER_MREC_HEADSIZE + HAMMER_XBUFSIZE) {
459                         fprintf(stderr, "read_mrecords: malformed record on pipe, illegal rec_size\n");
460                         exit(1);
461                 }
462                 if (HAMMER_MREC_HEADSIZE + pickup->leaf.data_len > pickup->rec_size) {
463                         fprintf(stderr, "read_mrecords: malformed record on pipe, illegal element data_len\n");
464                         exit(1);
465                 }
466
467                 /*
468                  * Stop if we have insufficient space for the record and data.
469                  */
470                 if (size - count < pickup->rec_size)
471                         break;
472
473                 /*
474                  * Stop if the record type is not HAMMER_MREC_TYPE_REC
475                  */
476                 if (pickup->type != HAMMER_MREC_TYPE_REC)
477                         break;
478
479                 /*
480                  * Read the remainder and clear the pickup signature.
481                  */
482                 bcopy(pickup, buf + count, HAMMER_MREC_HEADSIZE);
483                 pickup->signature = 0;
484                 pickup->type = 0;
485                 for (n = HAMMER_MREC_HEADSIZE; n < pickup->rec_size; n += i) {
486                         i = read(fd, buf + count + n, pickup->rec_size - n);
487                         if (i <= 0)
488                                 break;
489                 }
490                 if (n != pickup->rec_size) {
491                         fprintf(stderr, "read_mrecords: short read on pipe\n");
492                         exit(1);
493                 }
494                 if (pickup->leaf.data_len && pickup->leaf.data_offset) {
495                         if (hammer_crc_test_leaf(buf + count + HAMMER_MREC_HEADSIZE, &pickup->leaf) == 0) {
496                                 fprintf(stderr, "read_mrecords: data_crc did not match data! obj=%016llx key=%016llx\n", pickup->leaf.base.obj_id, pickup->leaf.base.key);
497                                 fprintf(stderr, "continuing, but there are problems\n");
498                         }
499                 }
500
501                 count += pickup->rec_size;
502         }
503         return(count);
504 }
505
506 /*
507  * Read and return a single mrecord.  The returned mrec->rec_size will be
508  * adjusted to be the size of the payload.
509  */
510 static
511 struct hammer_ioc_mrecord *
512 read_mrecord(int fdin, int *errorp, hammer_ioc_mrecord_t pickup)
513 {
514         hammer_ioc_mrecord_t mrec;
515         struct hammer_ioc_mrecord mrechd;
516         size_t bytes;
517         size_t n;
518         size_t i;
519
520         if (pickup && pickup->type != 0) {
521                 mrechd = *pickup;
522                 pickup->signature = 0;
523                 pickup->type = 0;
524                 n = HAMMER_MREC_HEADSIZE;
525         } else {
526                 /*
527                  * Read in the PFSD header from the sender.
528                  */
529                 for (n = 0; n < HAMMER_MREC_HEADSIZE; n += i) {
530                         i = read(fdin, (char *)&mrechd + n, HAMMER_MREC_HEADSIZE - n);
531                         if (i <= 0)
532                                 break;
533                 }
534                 if (n == 0) {
535                         *errorp = 0;    /* EOF */
536                         return(NULL);
537                 }
538                 if (n != HAMMER_MREC_HEADSIZE) {
539                         fprintf(stderr, "short read of mrecord header\n");
540                         *errorp = EPIPE;
541                         return(NULL);
542                 }
543         }
544         if (mrechd.signature != HAMMER_IOC_MIRROR_SIGNATURE) {
545                 fprintf(stderr, "read_mrecord: bad signature\n");
546                 *errorp = EINVAL;
547                 return(NULL);
548         }
549         bytes = mrechd.rec_size;
550         if (bytes < HAMMER_MREC_HEADSIZE)
551                 bytes = (int)HAMMER_MREC_HEADSIZE;
552         mrec = malloc(bytes);
553         *mrec = mrechd;
554         while (n < bytes) {
555                 i = read(fdin, (char *)mrec + n, bytes - n);
556                 if (i <= 0)
557                         break;
558                 n += i;
559         }
560         if (n != bytes) {
561                 fprintf(stderr, "read_mrecord: short read on payload\n");
562                 *errorp = EPIPE;
563                 return(NULL);
564         }
565         if (mrec->rec_crc != crc32((char *)mrec + HAMMER_MREC_CRCOFF,
566                                    bytes - HAMMER_MREC_CRCOFF)) {
567                 fprintf(stderr, "read_mrecord: bad CRC\n");
568                 *errorp = EINVAL;
569                 return(NULL);
570         }
571         mrec->rec_size -= HAMMER_MREC_HEADSIZE;
572         *errorp = 0;
573         return(mrec);
574 }
575
576 static
577 void
578 write_mrecord(int fdout, u_int32_t type, void *payload, int bytes)
579 {
580         hammer_ioc_mrecord_t mrec;
581
582         mrec = malloc(HAMMER_MREC_HEADSIZE + bytes);
583         bzero(mrec, sizeof(*mrec));
584         mrec->signature = HAMMER_IOC_MIRROR_SIGNATURE;
585         mrec->type = type;
586         mrec->rec_size = HAMMER_MREC_HEADSIZE + bytes;
587         bcopy(payload, mrec + 1, bytes);
588         mrec->rec_crc = crc32((char *)mrec + HAMMER_MREC_CRCOFF,
589                                    mrec->rec_size - HAMMER_MREC_CRCOFF);
590         if (write(fdout, mrec, mrec->rec_size) != (int)mrec->rec_size) {
591                 fprintf(stderr, "write_mrecord: error %d (%s)\n",
592                         errno, strerror(errno));
593                 exit(1);
594         }
595         free(mrec);
596 }
597
598 /*
599  * Generate a mirroring header with the pfs information of the
600  * originating filesytem.
601  */
602 static void
603 generate_mrec_header(int fd, int fdout,
604                      hammer_tid_t *tid_begp, hammer_tid_t *tid_endp)
605 {
606         struct hammer_ioc_pseudofs_rw pfs;
607         struct hammer_pfs_head pfs_head;
608
609         bzero(&pfs, sizeof(pfs));
610         bzero(&pfs_head, sizeof(pfs_head));
611         pfs.ondisk = &pfs_head.pfsd;
612         pfs.bytes = sizeof(pfs_head.pfsd);
613         if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) {
614                 fprintf(stderr, "mirror-read: not a HAMMER fs/pseudofs!\n");
615                 exit(1);
616         }
617         if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) {
618                 fprintf(stderr, "mirror-read: HAMMER pfs version mismatch!\n");
619                 exit(1);
620         }
621
622         /*
623          * sync_beg_tid - lowest TID on source after which a full history
624          *                is available.
625          *
626          * sync_end_tid - highest fully synchronized TID from source.
627          */
628         *tid_begp = pfs_head.pfsd.sync_beg_tid;
629         *tid_endp = pfs_head.pfsd.sync_end_tid;
630
631         pfs_head.version = pfs.version;
632         write_mrecord(fdout, HAMMER_MREC_TYPE_PFSD,
633                       &pfs_head, sizeof(pfs_head));
634 }
635
636 /*
637  * Validate the pfs information from the originating filesystem
638  * against the target filesystem.  shared_uuid must match.
639  */
640 static void
641 validate_mrec_header(int fd, int fdin,
642                      hammer_tid_t *tid_begp, hammer_tid_t *tid_endp)
643 {
644         struct hammer_ioc_pseudofs_rw pfs;
645         struct hammer_pfs_head *pfs_head;
646         struct hammer_pseudofs_data pfsd;
647         hammer_ioc_mrecord_t mrec;
648         size_t bytes;
649         int error;
650
651         /*
652          * Get the PFSD info from the target filesystem.
653          */
654         bzero(&pfs, sizeof(pfs));
655         bzero(&pfsd, sizeof(pfsd));
656         pfs.ondisk = &pfsd;
657         pfs.bytes = sizeof(pfsd);
658         if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) {
659                 fprintf(stderr, "mirror-write: not a HAMMER fs/pseudofs!\n");
660                 exit(1);
661         }
662         if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) {
663                 fprintf(stderr, "mirror-write: HAMMER pfs version mismatch!\n");
664                 exit(1);
665         }
666
667         mrec = read_mrecord(fdin, &error, NULL);
668         if (mrec == NULL) {
669                 if (error == 0)
670                         fprintf(stderr, "validate_mrec_header: short read\n");
671                 exit(1);
672         }
673         if (mrec->type != HAMMER_MREC_TYPE_PFSD) {
674                 fprintf(stderr, "validate_mrec_header: did not get expected "
675                                 "PFSD record type\n");
676                 exit(1);
677         }
678         pfs_head = (void *)(mrec + 1);
679         bytes = mrec->rec_size; /* post-adjusted for payload */
680         if (bytes != sizeof(*pfs_head)) {
681                 fprintf(stderr, "validate_mrec_header: unexpected payload "
682                                 "size\n");
683                 exit(1);
684         }
685         if (pfs_head->version != pfs.version) {
686                 fprintf(stderr, "validate_mrec_header: Version mismatch\n");
687                 exit(1);
688         }
689
690         /*
691          * Whew.  Ok, is the read PFS info compatible with the target?
692          */
693         if (bcmp(&pfs_head->pfsd.shared_uuid, &pfsd.shared_uuid, sizeof(pfsd.shared_uuid)) != 0) {
694                 fprintf(stderr, "mirror-write: source and target have different shared_uuid's!\n");
695                 exit(1);
696         }
697         if ((pfsd.mirror_flags & HAMMER_PFSD_SLAVE) == 0) {
698                 fprintf(stderr, "mirror-write: target must be in slave mode\n");
699                 exit(1);
700         }
701         *tid_begp = pfs_head->pfsd.sync_beg_tid;
702         *tid_endp = pfs_head->pfsd.sync_end_tid;
703         free(mrec);
704 }
705
706 static void
707 update_pfs_snapshot(int fd, hammer_tid_t snapshot_tid)
708 {
709         struct hammer_ioc_pseudofs_rw pfs;
710         struct hammer_pseudofs_data pfsd;
711
712         bzero(&pfs, sizeof(pfs));
713         bzero(&pfsd, sizeof(pfsd));
714         pfs.ondisk = &pfsd;
715         pfs.bytes = sizeof(pfsd);
716         if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) {
717                 perror("update_pfs_snapshot (read)");
718                 exit(1);
719         }
720         pfsd.sync_end_tid = snapshot_tid;
721         if (ioctl(fd, HAMMERIOC_SET_PSEUDOFS, &pfs) != 0) {
722                 perror("update_pfs_snapshot (rewrite)");
723                 exit(1);
724         }
725 }
726
727
728 static void
729 mirror_usage(int code)
730 {
731         fprintf(stderr, 
732                 "hammer mirror-read <filesystem>\n"
733                 "hammer mirror-write <filesystem>\n"
734                 "hammer mirror-dump\n"
735                 "hammer mirror-copy [[user@]host:]fs [[user@]host:]fs\n"
736         );
737         exit(code);
738 }