b0aace7c56345fa4f59fde0538cac696092388d6
[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.2 2008/07/02 22:05:59 dillon Exp $
35  */
36
37 #include "hammer.h"
38
39 #define SERIALBUF_SIZE  (512 * 1024)
40
41 struct hammer_pfs_head {
42         struct hammer_ioc_mrecord mrec;
43         u_int32_t version;
44         struct hammer_pseudofs_data pfsd;
45 };
46
47 static int read_mrecords(int fd, char *buf, u_int size,
48                          hammer_ioc_mrecord_t pickup);
49 static void generate_mrec_header(int fd, int fdout,
50                          hammer_tid_t *tid_begp, hammer_tid_t *tid_endp);
51 static void validate_mrec_header(int fd, int fdin,
52                          hammer_tid_t *tid_begp, hammer_tid_t *tid_endp);
53 static void run_cmd(const char *path, ...);
54 static void mirror_usage(int code);
55
56 void
57 hammer_cmd_mirror_read(char **av, int ac)
58 {
59         struct hammer_ioc_mirror_rw mirror;
60         const char *filesystem;
61         char *buf = malloc(SERIALBUF_SIZE);
62         int fd;
63
64         if (ac > 2)
65                 mirror_usage(1);
66         filesystem = av[0];
67
68         bzero(&mirror, sizeof(mirror));
69         hammer_key_beg_init(&mirror.key_beg);
70         hammer_key_end_init(&mirror.key_end);
71
72         fd = open(filesystem, O_RDONLY);
73         if (fd < 0)
74                 err(1, "Unable to open %s", filesystem);
75
76         hammer_get_cycle(&mirror.key_beg);
77
78         generate_mrec_header(fd, 1, &mirror.tid_beg, &mirror.tid_end);
79
80         mirror.ubuf = buf;
81         mirror.size = SERIALBUF_SIZE;
82         if (ac == 2)
83                 mirror.tid_beg = strtoull(av[1], NULL, 0);
84
85         do {
86                 mirror.count = 0;
87                 if (ioctl(fd, HAMMERIOC_MIRROR_READ, &mirror) < 0) {
88                         fprintf(stderr, "Mirror-read %s failed: %s\n",
89                                 filesystem, strerror(errno));
90                         exit(1);
91                 }
92                 if (mirror.head.flags & HAMMER_IOC_HEAD_INTR) {
93                         fprintf(stderr,
94                                 "Mirror-read %s interrupted by timer at"
95                                 " %016llx %08x\n",
96                                 filesystem,
97                                 mirror.key_cur.obj_id,
98                                 mirror.key_cur.localization);
99                         if (CyclePath)
100                                 hammer_set_cycle(&mirror.key_cur);
101                         exit(0);
102                 }
103                 mirror.key_beg = mirror.key_cur;
104                 if (mirror.count)
105                         write(1, mirror.ubuf, mirror.count);
106         } while (mirror.count != 0);
107
108         /* generate_mrec_update(fd, 1); */
109
110         if (CyclePath)
111                 hammer_reset_cycle();
112         fprintf(stderr, "Mirror-read %s succeeded\n", filesystem);
113 }
114
115 void
116 hammer_cmd_mirror_write(char **av, int ac)
117 {
118         struct hammer_ioc_mirror_rw mirror;
119         const char *filesystem;
120         char *buf = malloc(SERIALBUF_SIZE);
121         int fd;
122         struct hammer_ioc_mrecord pickup;
123
124         if (ac > 2)
125                 mirror_usage(1);
126         filesystem = av[0];
127
128         bzero(&mirror, sizeof(mirror));
129         hammer_key_beg_init(&mirror.key_beg);
130         hammer_key_end_init(&mirror.key_end);
131
132         fd = open(filesystem, O_RDONLY);
133         if (fd < 0)
134                 err(1, "Unable to open %s", filesystem);
135
136         validate_mrec_header(fd, 0, &mirror.tid_beg, &mirror.tid_end);
137
138         mirror.ubuf = buf;
139         mirror.size = SERIALBUF_SIZE;
140
141         pickup.signature = 0;
142
143         for (;;) {
144                 mirror.count = 0;
145                 mirror.size = read_mrecords(0, buf, SERIALBUF_SIZE, &pickup);
146                 if (mirror.size <= 0)
147                         break;
148                 if (ioctl(fd, HAMMERIOC_MIRROR_WRITE, &mirror) < 0) {
149                         fprintf(stderr, "Mirror-write %s failed: %s\n",
150                                 filesystem, strerror(errno));
151                         exit(1);
152                 }
153                 if (mirror.head.flags & HAMMER_IOC_HEAD_INTR) {
154                         fprintf(stderr,
155                                 "Mirror-write %s interrupted by timer at"
156                                 " %016llx %08x\n",
157                                 filesystem,
158                                 mirror.key_cur.obj_id,
159                                 mirror.key_cur.localization);
160                         exit(0);
161                 }
162                 mirror.key_beg = mirror.key_cur;
163         }
164         fprintf(stderr, "Mirror-write %s succeeded\n", filesystem);
165 }
166
167 void
168 hammer_cmd_mirror_copy(char **av, int ac)
169 {
170         pid_t pid1;
171         pid_t pid2;
172         int fds[2];
173         char *ptr;
174
175         if (ac != 2)
176                 mirror_usage(1);
177
178         if (pipe(fds) < 0) {
179                 perror("pipe");
180                 exit(1);
181         }
182
183         /*
184          * Source
185          */
186         if ((pid1 = fork()) == 0) {
187                 dup2(fds[0], 0);
188                 dup2(fds[0], 1);
189                 close(fds[0]);
190                 close(fds[1]);
191                 if ((ptr = strchr(av[0], ':')) != NULL) {
192                         *ptr++ = 0;
193                         run_cmd("/usr/bin/ssh", "ssh",
194                                 av[0], "hammer mirror-read", ptr, NULL);
195                         _exit(1);
196                 } else {
197                         hammer_cmd_mirror_read(av, 1);
198                 }
199         }
200
201         /*
202          * Target
203          */
204         if ((pid2 = fork()) == 0) {
205                 dup2(fds[1], 0);
206                 dup2(fds[1], 1);
207                 close(fds[0]);
208                 close(fds[1]);
209                 if ((ptr = strchr(av[1], ':')) != NULL) {
210                         *ptr++ = 0;
211                         run_cmd("/usr/bin/ssh", "ssh",
212                                 av[1], "hammer mirror-write", ptr, NULL);
213                         _exit(1);
214                 } else {
215                         hammer_cmd_mirror_write(av + 1, 1);
216                 }
217         }
218         close(fds[0]);
219         close(fds[1]);
220
221         while (waitpid(pid1, NULL, 0) <= 0)
222                 ;
223         while (waitpid(pid2, NULL, 0) <= 0)
224                 ;
225 }
226
227 static int
228 read_mrecords(int fd, char *buf, u_int size, hammer_ioc_mrecord_t pickup)
229 {
230         u_int count;
231         size_t n;
232         size_t i;
233
234         count = 0;
235         while (size - count >= HAMMER_MREC_HEADSIZE) {
236                 /*
237                  * Cached the record header in case we run out of buffer
238                  * space.
239                  */
240                 if (pickup->signature == 0) {
241                         for (n = 0; n < HAMMER_MREC_HEADSIZE; n += i) {
242                                 i = read(fd, (char *)pickup + n,
243                                          HAMMER_MREC_HEADSIZE - n);
244                                 if (i <= 0)
245                                         break;
246                         }
247                         if (n == 0)
248                                 break;
249                         if (n != HAMMER_MREC_HEADSIZE) {
250                                 fprintf(stderr, "read_mrecords: short read on pipe\n");
251                                 exit(1);
252                         }
253
254                         if (pickup->signature != HAMMER_IOC_MIRROR_SIGNATURE) {
255                                 fprintf(stderr, "read_mrecords: malformed record on pipe, bad signature\n");
256                                 exit(1);
257                         }
258                         if (pickup->rec_crc != crc32((char *)pickup + HAMMER_MREC_CRCOFF, HAMMER_MREC_HEADSIZE - HAMMER_MREC_CRCOFF)) {
259                                 fprintf(stderr, "read_mrecords: malformed record on pipe, bad crc\n");
260                                 exit(1);
261                         }
262                 }
263                 if (pickup->rec_size < HAMMER_MREC_HEADSIZE ||
264                     pickup->rec_size > HAMMER_MREC_HEADSIZE + HAMMER_XBUFSIZE) {
265                         fprintf(stderr, "read_mrecords: malformed record on pipe, illegal rec_size\n");
266                         exit(1);
267                 }
268                 if (HAMMER_MREC_HEADSIZE + pickup->leaf.data_len > pickup->rec_size) {
269                         fprintf(stderr, "read_mrecords: malformed record on pipe, illegal element data_len\n");
270                         exit(1);
271                 }
272
273                 /*
274                  * Stop if we have insufficient space for the record and data.
275                  */
276                 if (size - count < pickup->rec_size)
277                         break;
278
279                 /*
280                  * Read the remainder and clear the pickup signature.
281                  */
282                 bcopy(pickup, buf + count, HAMMER_MREC_HEADSIZE);
283                 pickup->signature = 0;
284                 for (n = HAMMER_MREC_HEADSIZE; n < pickup->rec_size; n += i) {
285                         i = read(fd, buf + count + n, pickup->rec_size - n);
286                         if (i <= 0)
287                                 break;
288                 }
289                 if (n != pickup->rec_size) {
290                         fprintf(stderr, "read_mrecords: short read on pipe\n");
291                         exit(1);
292                 }
293                 if (pickup->leaf.data_len && pickup->leaf.data_offset) {
294                         if (hammer_crc_test_leaf(buf + count + HAMMER_MREC_HEADSIZE, &pickup->leaf) == 0) {
295                                 fprintf(stderr, "read_mrecords: data_crc did not match data! obj=%016llx key=%016llx\n", pickup->leaf.base.obj_id, pickup->leaf.base.key);
296                                 fprintf(stderr, "continuing, but there are problems\n");
297                         }
298                 }
299
300                 count += pickup->rec_size;
301         }
302         return(count);
303 }
304
305 /*
306  * Generate a mirroring header with the pfs information of the
307  * originating filesytem.
308  */
309 static void
310 generate_mrec_header(int fd, int fdout,
311                      hammer_tid_t *tid_begp, hammer_tid_t *tid_endp)
312 {
313         struct hammer_ioc_pseudofs_rw pfs;
314         struct hammer_pfs_head pfs_head;
315
316         bzero(&pfs, sizeof(pfs));
317         bzero(&pfs_head, sizeof(pfs_head));
318         pfs.ondisk = &pfs_head.pfsd;
319         pfs.bytes = sizeof(pfs_head.pfsd);
320         if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) {
321                 fprintf(stderr, "mirror-read: not a HAMMER fs/pseudofs!\n");
322                 exit(1);
323         }
324         if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) {
325                 fprintf(stderr, "mirror-read: HAMMER pfs version mismatch!\n");
326                 exit(1);
327         }
328
329         /*
330          * sync_beg_tid - lowest TID on source after which a full history
331          *                is available.
332          *
333          * sync_end_tid - highest fully synchronized TID from source.
334          */
335         *tid_begp = pfs_head.pfsd.sync_beg_tid;
336         *tid_endp = pfs_head.pfsd.sync_end_tid;
337
338         pfs_head.version = pfs.version;
339         pfs_head.mrec.signature = HAMMER_IOC_MIRROR_SIGNATURE;
340         pfs_head.mrec.rec_size = sizeof(pfs_head);
341         pfs_head.mrec.type = HAMMER_MREC_TYPE_PFSD;
342         pfs_head.mrec.rec_crc = crc32((char *)&pfs_head + HAMMER_MREC_CRCOFF,
343                                       sizeof(pfs_head) - HAMMER_MREC_CRCOFF);
344         write(fdout, &pfs_head, sizeof(pfs_head));
345 }
346
347 /*
348  * Validate the pfs information from the originating filesystem
349  * against the target filesystem.  shared_uuid must match.
350  */
351 static void
352 validate_mrec_header(int fd, int fdin,
353                      hammer_tid_t *tid_begp, hammer_tid_t *tid_endp)
354 {
355         struct hammer_ioc_pseudofs_rw pfs;
356         struct hammer_pfs_head pfs_head;
357         struct hammer_pseudofs_data pfsd;
358         size_t bytes;
359         size_t n;
360         size_t i;
361
362         /*
363          * Get the PFSD info from the target filesystem.
364          */
365         bzero(&pfs, sizeof(pfs));
366         bzero(&pfsd, sizeof(pfsd));
367         pfs.ondisk = &pfsd;
368         pfs.bytes = sizeof(pfsd);
369         if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) {
370                 fprintf(stderr, "mirror-write: not a HAMMER fs/pseudofs!\n");
371                 exit(1);
372         }
373         if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) {
374                 fprintf(stderr, "mirror-write: HAMMER pfs version mismatch!\n");
375                 exit(1);
376         }
377
378         /*
379          * Read in the PFSD header from the sender.
380          */
381         for (n = 0; n < HAMMER_MREC_HEADSIZE; n += i) {
382                 i = read(fdin, (char *)&pfs_head + n, HAMMER_MREC_HEADSIZE - n);
383                 if (i <= 0)
384                         break;
385         }
386         if (n != HAMMER_MREC_HEADSIZE) {
387                 fprintf(stderr, "mirror-write: short read of PFS header\n");
388                 exit(1);
389         }
390         if (pfs_head.mrec.signature != HAMMER_IOC_MIRROR_SIGNATURE) {
391                 fprintf(stderr, "mirror-write: PFS header has bad signature\n");
392                 exit(1);
393         }
394         if (pfs_head.mrec.type != HAMMER_MREC_TYPE_PFSD) {
395                 fprintf(stderr, "mirror-write: Expected PFS header, got mirroring record header instead!\n");
396                 exit(1);
397         }
398         bytes = pfs_head.mrec.rec_size;
399         if (bytes < HAMMER_MREC_HEADSIZE)
400                 bytes = (int)HAMMER_MREC_HEADSIZE;
401         if (bytes > sizeof(pfs_head))
402                 bytes = sizeof(pfs_head);
403         while (n < bytes) {
404                 i = read(fdin, (char *)&pfs_head + n, bytes - n);
405                 if (i <= 0)
406                         break;
407                 n += i;
408         }
409         if (n != bytes) {
410                 fprintf(stderr, "mirror-write: short read of PFS payload\n");
411                 exit(1);
412         }
413         if (pfs_head.version != pfs.version) {
414                 fprintf(stderr, "mirror-write: Version mismatch in PFS header\n");
415                 exit(1);
416         }
417         if (pfs_head.mrec.rec_size != sizeof(pfs_head)) {
418                 fprintf(stderr, "mirror-write: The PFS header has the wrong size!\n");
419                 exit(1);
420         }
421
422         /*
423          * Whew.  Ok, is the read PFS info compatible with the target?
424          */
425         if (bcmp(&pfs_head.pfsd.shared_uuid, &pfsd.shared_uuid, sizeof(pfsd.shared_uuid)) != 0) {
426                 fprintf(stderr, "mirror-write: source and target have different shared_uuid's!\n");
427                 exit(1);
428         }
429         if ((pfsd.mirror_flags & HAMMER_PFSD_SLAVE) == 0) {
430                 fprintf(stderr, "mirror-write: target must be in slave mode\n");
431                 exit(1);
432         }
433         *tid_begp = pfs_head.pfsd.sync_beg_tid;
434         *tid_endp = pfs_head.pfsd.sync_end_tid;
435 }
436
437 static void
438 run_cmd(const char *path, ...)
439 {
440         va_list va;
441         char *av[16];
442         int n;
443
444         va_start(va, path);
445         for (n = 0; n < 16; ++n) {
446                 av[n] = va_arg(va, char *);
447                 if (av[n] == NULL)
448                         break;
449         }
450         va_end(va);
451         assert(n != 16);
452         execv(path, av);
453 }
454
455 static void
456 mirror_usage(int code)
457 {
458         fprintf(stderr, 
459                 "hammer mirror-read <filesystem>\n"
460                 "hammer mirror-write <filesystem>\n"
461                 "hammer mirror-copy [[user@]host:]fs [[user@]host:]fs\n"
462         );
463         exit(code);
464 }