ia64/xen-unstable

view tools/blktap2/drivers/tapdisk-diff.c @ 19817:b7f73a7f3078

blktap2: portability fixes for NetBSD

- Use standard off_t and lseek() instead of non-portable off64_t and
lseek64()
- Use uuid API as documented in DCE 1.1 RPC specification
- Add NetBSD implementation for blk_getimagesize() and
blk_getsectorsize()
- Use blk_getimagesize() and blk_getsectorsize()
- Fix uuid header check

Signed-off-by: Christoph Egger <Christoph.Egger@amd.com>
Signed-off-by: Keir Fraser <keir.fraser@citrix.com>
author Keir Fraser <keir.fraser@citrix.com>
date Tue Jun 23 17:24:14 2009 +0100 (2009-06-23)
parents 1c627434605e
children
line source
1 /*
2 * Copyright (c) 2008, XenSource Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of XenSource Inc. nor the names of its contributors
13 * may be used to endorse or promote products derived from this software
14 * without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
20 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28 #include <stdio.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <assert.h>
34 #include <libgen.h> /* for basename(3) */
35 #include <unistd.h>
37 #include "list.h"
38 #include "scheduler.h"
39 #include "tapdisk-vbd.h"
40 #include "tapdisk-server.h"
41 #include "libvhd.h"
43 #define POLL_READ 0
44 #define POLL_WRITE 1
46 #define SPB_SHIFT (VHD_BLOCK_SHIFT - SECTOR_SHIFT)
48 struct tapdisk_stream_poll {
49 int pipe[2];
50 int set;
51 };
53 struct tapdisk_stream_request {
54 uint64_t sec;
55 uint32_t secs;
56 uint64_t seqno;
57 blkif_request_t blkif_req;
58 struct list_head next;
59 };
61 struct tapdisk_stream {
62 td_vbd_t *vbd;
64 unsigned int id;
66 int err;
68 uint64_t cur;
69 uint64_t start;
70 uint64_t end;
72 uint64_t started;
73 uint64_t completed;
75 struct tapdisk_stream_poll poll;
76 event_id_t enqueue_event_id;
78 struct list_head free_list;
79 struct list_head pending_list;
80 struct list_head completed_list;
82 struct tapdisk_stream_request requests[MAX_REQUESTS];
83 };
85 static unsigned int tapdisk_stream_count;
87 static void tapdisk_stream_close_image(struct tapdisk_stream *);
89 static char *program;
90 static struct tapdisk_stream stream1, stream2;
91 static vhd_context_t vhd1;
93 static void
94 usage(FILE *stream)
95 {
96 printf("usage: %s <-n type:/path/to/image> <-m type:/path/to/image>\n",
97 program);
98 }
100 static int
101 open_vhd(const char *path, vhd_context_t *vhd)
102 {
103 int err;
105 err = vhd_open(vhd, path, VHD_OPEN_RDONLY);
106 if (err) {
107 printf("error opening %s: %d\n", path, err);
108 return err;
109 }
111 err = vhd_get_bat(vhd);
112 if (err)
113 {
114 printf("error reading BAT for %s: %d\n", path, err);
115 vhd_close(vhd);
116 return err;
117 }
119 return 0;
120 }
122 static inline void
123 tapdisk_stream_poll_initialize(struct tapdisk_stream_poll *p)
124 {
125 p->set = 0;
126 p->pipe[POLL_READ] = p->pipe[POLL_WRITE] = -1;
127 }
129 static int
130 tapdisk_stream_poll_open(struct tapdisk_stream_poll *p)
131 {
132 int err;
134 tapdisk_stream_poll_initialize(p);
136 err = pipe(p->pipe);
137 if (err)
138 return -errno;
140 err = fcntl(p->pipe[POLL_READ], F_SETFL, O_NONBLOCK);
141 if (err)
142 goto out;
144 err = fcntl(p->pipe[POLL_WRITE], F_SETFL, O_NONBLOCK);
145 if (err)
146 goto out;
148 return 0;
150 out:
151 close(p->pipe[POLL_READ]);
152 close(p->pipe[POLL_WRITE]);
153 tapdisk_stream_poll_initialize(p);
154 return -errno;
155 }
157 static void
158 tapdisk_stream_poll_close(struct tapdisk_stream_poll *p)
159 {
160 if (p->pipe[POLL_READ] != -1)
161 close(p->pipe[POLL_READ]);
162 if (p->pipe[POLL_WRITE] != -1)
163 close(p->pipe[POLL_WRITE]);
164 tapdisk_stream_poll_initialize(p);
165 }
167 static inline void
168 tapdisk_stream_poll_clear(struct tapdisk_stream_poll *p)
169 {
170 int dummy;
172 read(p->pipe[POLL_READ], &dummy, sizeof(dummy));
173 p->set = 0;
174 }
176 static inline void
177 tapdisk_stream_poll_set(struct tapdisk_stream_poll *p)
178 {
179 int dummy = 0;
181 if (!p->set) {
182 write(p->pipe[POLL_WRITE], &dummy, sizeof(dummy));
183 p->set = 1;
184 }
185 }
187 static inline int
188 tapdisk_stream_stop(struct tapdisk_stream *s)
189 {
190 return ((s->cur == s->end || s->err) &&
191 list_empty(&s->pending_list) &&
192 list_empty(&s->completed_list));
193 }
195 static inline void
196 tapdisk_stream_initialize_request(struct tapdisk_stream_request *req)
197 {
198 memset(req, 0, sizeof(*req));
199 INIT_LIST_HEAD(&req->next);
200 }
202 static inline int
203 tapdisk_stream_request_idx(struct tapdisk_stream *s,
204 struct tapdisk_stream_request *req)
205 {
206 return (req - s->requests);
207 }
209 static inline struct tapdisk_stream_request *
210 tapdisk_stream_get_request(struct tapdisk_stream *s)
211 {
212 struct tapdisk_stream_request *req;
214 if (list_empty(&s->free_list))
215 return NULL;
217 req = list_entry(s->free_list.next,
218 struct tapdisk_stream_request, next);
220 list_del_init(&req->next);
221 tapdisk_stream_initialize_request(req);
223 return req;
224 }
226 static inline void
227 tapdisk_stream_queue_completed(struct tapdisk_stream *s,
228 struct tapdisk_stream_request *sreq)
229 {
230 struct tapdisk_stream_request *itr;
232 list_for_each_entry(itr, &s->completed_list, next)
233 if (sreq->seqno < itr->seqno) {
234 list_add_tail(&sreq->next, &itr->next);
235 return;
236 }
238 list_add_tail(&sreq->next, &s->completed_list);
239 }
241 static int
242 tapdisk_result_compare(struct tapdisk_stream_request *sreq1,
243 struct tapdisk_stream_request *sreq2)
244 {
245 unsigned long idx1, idx2;
246 char *buf1, *buf2;
247 int result;
249 assert(sreq1->seqno == sreq2->seqno);
250 assert(sreq1->secs == sreq2->secs);
251 idx1 = (unsigned long)tapdisk_stream_request_idx(&stream1,
252 sreq1);
253 idx2 = (unsigned long)tapdisk_stream_request_idx(&stream2,
254 sreq2);
255 buf1 = (char *)MMAP_VADDR(stream1.vbd->ring.vstart, idx1, 0);
256 buf2 = (char *)MMAP_VADDR(stream2.vbd->ring.vstart, idx2, 0);
258 result = memcmp(buf1, buf2, sreq1->secs << SECTOR_SHIFT);
259 return result;
260 }
262 static int
263 tapdisk_stream_process_data(void)
264 {
265 struct tapdisk_stream_request *sreq1, *sreq2, *tmp1, *tmp2;
266 int advance_both;
267 int result = 0;
269 sreq1 = list_entry(stream1.completed_list.next,
270 struct tapdisk_stream_request, next);
271 sreq2 = list_entry(stream2.completed_list.next,
272 struct tapdisk_stream_request, next);
273 tmp1 = list_entry(sreq1->next.next,
274 struct tapdisk_stream_request, next);
275 tmp2 = list_entry(sreq2->next.next,
276 struct tapdisk_stream_request, next);
277 while (result == 0 &&
278 &sreq1->next != &stream1.completed_list &&
279 &sreq2->next != &stream2.completed_list) {
280 //printf("checking: %llu|%llu\n", sreq1->seqno, sreq2->seqno);
281 advance_both = 1;
282 if (sreq1->seqno < sreq2->seqno) {
283 advance_both = 0;
284 goto advance1;
285 }
286 if (sreq1->seqno > sreq2->seqno)
287 goto advance2;
289 result = tapdisk_result_compare(sreq1, sreq2);
291 stream1.completed++;
292 stream2.completed++;
294 list_del_init(&sreq1->next);
295 list_add_tail(&sreq1->next, &stream1.free_list);
296 list_del_init(&sreq2->next);
297 list_add_tail(&sreq2->next, &stream2.free_list);
299 advance1:
300 sreq1 = tmp1;
301 tmp1 = list_entry(tmp1->next.next,
302 struct tapdisk_stream_request, next);
303 if (!advance_both)
304 continue;
305 advance2:
306 sreq2 = tmp2;
307 tmp2 = list_entry(tmp2->next.next,
308 struct tapdisk_stream_request, next);
309 }
311 return result;
312 }
314 static void
315 tapdisk_stream_dequeue(void *arg, blkif_response_t *rsp)
316 {
317 struct tapdisk_stream *s = (struct tapdisk_stream *)arg;
318 struct tapdisk_stream_request *sreq = s->requests + rsp->id;
320 list_del_init(&sreq->next);
322 if (rsp->status == BLKIF_RSP_OKAY)
323 tapdisk_stream_queue_completed(s, sreq);
324 else {
325 s->err = EIO;
326 list_add_tail(&sreq->next, &s->free_list);
327 fprintf(stderr, "error reading sector 0x%"PRIx64"\n", sreq->sec);
328 }
330 if (tapdisk_stream_process_data()) {
331 fprintf(stderr, "mismatch at sector 0x%"PRIx64"\n",
332 sreq->sec);
333 stream1.err = EINVAL;
334 stream2.err = EINVAL;
335 }
337 tapdisk_stream_poll_set(&stream1.poll);
338 tapdisk_stream_poll_set(&stream2.poll);
339 }
341 static inline int
342 tapdisk_stream_enqueue_copy(struct tapdisk_stream *s,
343 struct tapdisk_stream_request *r)
344 {
345 td_vbd_t *vbd;
346 blkif_request_t *breq;
347 td_vbd_request_t *vreq;
348 struct tapdisk_stream_request *sreq;
349 int idx;
351 vbd = stream2.vbd;
352 sreq = tapdisk_stream_get_request(s);
353 if (!sreq)
354 return 1;
356 idx = tapdisk_stream_request_idx(s, sreq);
358 sreq->sec = r->sec;
359 sreq->secs = r->secs;
360 sreq->seqno = r->seqno;
362 breq = &sreq->blkif_req;
363 breq->id = idx;
364 breq->nr_segments = r->blkif_req.nr_segments;
365 breq->sector_number = r->blkif_req.sector_number;
366 breq->operation = BLKIF_OP_READ;
368 for (int i = 0; i < r->blkif_req.nr_segments; i++) {
369 struct blkif_request_segment *seg = breq->seg + i;
370 seg->first_sect = r->blkif_req.seg[i].first_sect;
371 seg->last_sect = r->blkif_req.seg[i].last_sect;
372 }
373 s->cur += sreq->secs;
375 vreq = vbd->request_list + idx;
376 assert(list_empty(&vreq->next));
377 assert(vreq->secs_pending == 0);
379 memcpy(&vreq->req, breq, sizeof(*breq));
380 vbd->received++;
381 vreq->vbd = vbd;
383 tapdisk_vbd_move_request(vreq, &vbd->new_requests);
384 list_add_tail(&sreq->next, &s->pending_list);
386 return 0;
387 }
389 static void
390 tapdisk_stream_enqueue1(void)
391 {
392 td_vbd_t *vbd;
393 int i, idx, psize, blk;
394 struct tapdisk_stream *s = &stream1;
396 vbd = s->vbd;
397 psize = getpagesize();
399 while (s->cur < s->end && !s->err) {
400 blkif_request_t *breq;
401 td_vbd_request_t *vreq;
402 struct tapdisk_stream_request *sreq;
404 /* skip any blocks that are not present in this image */
405 blk = s->cur >> SPB_SHIFT;
406 while (s->cur < s->end && vhd1.bat.bat[blk] == DD_BLK_UNUSED) {
407 //printf("skipping block %d\n", blk);
408 blk++;
409 s->cur = blk << SPB_SHIFT;
410 }
412 if (s->cur >= s->end)
413 break;
415 sreq = tapdisk_stream_get_request(s);
416 if (!sreq)
417 break;
419 idx = tapdisk_stream_request_idx(s, sreq);
421 sreq->sec = s->cur;
422 sreq->secs = 0;
423 sreq->seqno = s->started++;
425 breq = &sreq->blkif_req;
426 breq->id = idx;
427 breq->nr_segments = 0;
428 breq->sector_number = sreq->sec;
429 breq->operation = BLKIF_OP_READ;
431 for (i = 0; i < BLKIF_MAX_SEGMENTS_PER_REQUEST; i++) {
432 uint32_t secs;
433 struct blkif_request_segment *seg = breq->seg + i;
435 secs = MIN(s->end - s->cur, psize >> SECTOR_SHIFT);
436 secs = MIN(((blk + 1) << SPB_SHIFT) - s->cur, secs);
437 if (!secs)
438 break;
440 sreq->secs += secs;
441 s->cur += secs;
443 seg->first_sect = 0;
444 seg->last_sect = secs - 1;
445 breq->nr_segments++;
446 }
448 vreq = vbd->request_list + idx;
450 assert(list_empty(&vreq->next));
451 assert(vreq->secs_pending == 0);
453 memcpy(&vreq->req, breq, sizeof(*breq));
454 vbd->received++;
455 vreq->vbd = vbd;
457 tapdisk_vbd_move_request(vreq, &vbd->new_requests);
458 list_add_tail(&sreq->next, &s->pending_list);
459 }
461 tapdisk_vbd_issue_requests(vbd);
462 }
464 static void
465 tapdisk_stream_enqueue2(void)
466 {
467 td_vbd_t *vbd;
468 int i, blk;
469 struct tapdisk_stream_request *itr;
470 struct tapdisk_stream *s = &stream2;
472 vbd = s->vbd;
474 /* issue the same requests that we issued on stream1 */
475 list_for_each_entry(itr, &stream1.completed_list, next) {
476 if (itr->sec < s->cur)
477 continue;
478 if (tapdisk_stream_enqueue_copy(s, itr))
479 goto done;
480 }
482 list_for_each_entry(itr, &stream1.pending_list, next) {
483 if (itr->sec < s->cur)
484 continue;
485 if (tapdisk_stream_enqueue_copy(s, itr))
486 goto done;
487 }
489 stream2.cur = stream1.cur;
491 done:
492 tapdisk_vbd_issue_requests(vbd);
493 }
495 static inline int
496 tapdisk_diff_done(void)
497 {
498 return (tapdisk_stream_stop(&stream1) && tapdisk_stream_stop(&stream2));
499 }
501 static void
502 tapdisk_diff_stop(void)
503 {
504 tapdisk_stream_close_image(&stream1);
505 tapdisk_stream_close_image(&stream2);
506 }
508 static void
509 tapdisk_stream_enqueue(event_id_t id, char mode, void *arg)
510 {
511 struct tapdisk_stream *s = (struct tapdisk_stream *)arg;
513 tapdisk_stream_poll_clear(&s->poll);
515 if (tapdisk_diff_done()) {
516 tapdisk_diff_stop();
517 return;
518 }
520 if (s == &stream1)
521 tapdisk_stream_enqueue1();
522 else if (s == &stream2)
523 tapdisk_stream_enqueue2();
524 else
525 assert(0);
527 if (tapdisk_diff_done()) {
528 // we have to check again for the case when stream1 had no
529 // blocks at all
530 tapdisk_diff_stop();
531 return;
532 }
533 }
535 static int
536 tapdisk_stream_open_image(struct tapdisk_stream *s, const char *path, int type)
537 {
538 int err;
539 image_t image;
541 s->id = tapdisk_stream_count++;
543 err = tapdisk_vbd_initialize(-1, -1, s->id);
544 if (err)
545 goto out;
547 s->vbd = tapdisk_server_get_vbd(s->id);
548 if (!s->vbd) {
549 err = ENODEV;
550 goto out;
551 }
553 tapdisk_vbd_set_callback(s->vbd, tapdisk_stream_dequeue, s);
555 err = tapdisk_vbd_open_vdi(s->vbd, path, type,
556 TAPDISK_STORAGE_TYPE_DEFAULT,
557 TD_OPEN_RDONLY);
558 if (err)
559 goto out;
561 s->vbd->reopened = 1;
563 err = tapdisk_vbd_get_image_info(s->vbd, &image);
564 if (err) {
565 fprintf(stderr, "failed getting image size: %d\n", err);
566 return err;
567 }
569 s->start = 0;
570 s->cur = s->start;
571 s->end = image.size;
573 err = 0;
575 out:
576 if (err)
577 fprintf(stderr, "failed to open image %s: %d\n", path, err);
578 return err;
579 }
581 static void
582 tapdisk_stream_close_image(struct tapdisk_stream *s)
583 {
584 td_vbd_t *vbd;
586 vbd = tapdisk_server_get_vbd(s->id);
587 if (vbd) {
588 tapdisk_vbd_close_vdi(vbd);
589 tapdisk_server_remove_vbd(vbd);
590 free((void *)vbd->ring.vstart);
591 free(vbd->name);
592 free(vbd);
593 s->vbd = NULL;
594 }
595 }
597 static int
598 tapdisk_stream_initialize_requests(struct tapdisk_stream *s)
599 {
600 size_t size;
601 td_ring_t *ring;
602 int err, i, psize;
604 ring = &s->vbd->ring;
605 psize = getpagesize();
606 size = psize * BLKTAP_MMAP_REGION_SIZE;
608 /* sneaky -- set up ring->vstart so tapdisk_vbd will use our buffers */
609 err = posix_memalign((void **)&ring->vstart, psize, size);
610 if (err) {
611 fprintf(stderr, "failed to allocate buffers: %d\n", err);
612 ring->vstart = 0;
613 return err;
614 }
616 for (i = 0; i < MAX_REQUESTS; i++) {
617 struct tapdisk_stream_request *req = s->requests + i;
618 tapdisk_stream_initialize_request(req);
619 list_add_tail(&req->next, &s->free_list);
620 }
622 return 0;
623 }
625 static int
626 tapdisk_stream_register_enqueue_event(struct tapdisk_stream *s)
627 {
628 int err;
629 struct tapdisk_stream_poll *p = &s->poll;
631 err = tapdisk_stream_poll_open(p);
632 if (err)
633 goto out;
635 err = tapdisk_server_register_event(SCHEDULER_POLL_READ_FD,
636 p->pipe[POLL_READ], 0,
637 tapdisk_stream_enqueue, s);
638 if (err < 0)
639 goto out;
641 s->enqueue_event_id = err;
642 err = 0;
644 out:
645 if (err)
646 fprintf(stderr, "failed to register event: %d\n", err);
647 return err;
648 }
650 static void
651 tapdisk_stream_unregister_enqueue_event(struct tapdisk_stream *s)
652 {
653 if (s->enqueue_event_id) {
654 tapdisk_server_unregister_event(s->enqueue_event_id);
655 s->enqueue_event_id = 0;
656 }
657 tapdisk_stream_poll_close(&s->poll);
658 }
660 static inline void
661 tapdisk_stream_initialize(struct tapdisk_stream *s)
662 {
663 memset(s, 0, sizeof(*s));
664 INIT_LIST_HEAD(&s->free_list);
665 INIT_LIST_HEAD(&s->pending_list);
666 INIT_LIST_HEAD(&s->completed_list);
667 }
669 static int
670 tapdisk_stream_open(struct tapdisk_stream *s, const char *arg)
671 {
672 int err, type;
673 char *path;
675 err = tapdisk_parse_disk_type(arg, &path, &type);
676 if (err)
677 return err;
679 tapdisk_stream_initialize(s);
681 err = tapdisk_stream_open_image(s, path, type);
682 if (err)
683 return err;
685 err = tapdisk_stream_initialize_requests(s);
686 if (err)
687 return err;
689 err = tapdisk_stream_register_enqueue_event(s);
690 if (err)
691 return err;
693 tapdisk_stream_enqueue(s->enqueue_event_id,
694 SCHEDULER_POLL_READ_FD, s);
696 return 0;
697 }
699 static void
700 tapdisk_stream_release(struct tapdisk_stream *s)
701 {
702 tapdisk_stream_close_image(s);
703 tapdisk_stream_unregister_enqueue_event(s);
704 }
706 static int
707 tapdisk_stream_run(struct tapdisk_stream *s)
708 {
709 tapdisk_stream_enqueue(s->enqueue_event_id, SCHEDULER_POLL_READ_FD, s);
710 tapdisk_server_run();
711 return s->err;
712 }
714 int
715 main(int argc, char *argv[])
716 {
717 int c, err, type1;
718 const char *arg1 = NULL, *arg2 = NULL;
719 char *path1;
721 err = 0;
723 program = basename(argv[0]);
725 while ((c = getopt(argc, argv, "n:m:h")) != -1) {
726 switch (c) {
727 case 'n':
728 arg1 = optarg;
729 break;
730 case 'm':
731 arg2 = optarg;
732 break;
733 case 'h':
734 usage(stdout);
735 return 0;
736 default:
737 goto fail_usage;
738 }
739 }
741 if (!arg1 || !arg2)
742 goto fail_usage;
744 err = tapdisk_parse_disk_type(arg1, &path1, &type1);
745 if (err)
746 return err;
747 if (type1 != DISK_TYPE_VHD) {
748 printf("error: first VDI is not VHD\n");
749 return EINVAL;
750 }
752 err = open_vhd(path1, &vhd1);
753 if (err)
754 return err;
756 tapdisk_start_logging("tapdisk-diff");
758 err = tapdisk_server_initialize(NULL, NULL);
759 if (err)
760 goto out;
762 err = tapdisk_stream_open(&stream1, arg1);
763 if (err) {
764 fprintf(stderr, "Failed to open %s: %s\n",
765 arg1, strerror(-err));
766 goto out;
767 }
769 err = tapdisk_stream_open(&stream2, arg2);
770 if (err) {
771 fprintf(stderr, "Failed to open %s: %s\n",
772 arg2, strerror(-err));
773 goto out1;
774 }
776 if (stream1.end != stream2.end) {
777 fprintf(stderr, "Image sizes differ: %"PRIu64" != %"PRIu64"\n",
778 stream1.end, stream2.end);
779 err = EINVAL;
780 goto out2;
781 }
783 tapdisk_server_run();
785 out2:
786 tapdisk_stream_release(&stream2);
787 out1:
788 tapdisk_stream_release(&stream1);
789 out:
790 vhd_close(&vhd1);
791 tapdisk_stop_logging();
793 return err ? : stream1.err;
795 fail_usage:
796 usage(stderr);
797 return 1;
798 }