1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24#include "qemu-common.h"
25#include "osdep.h"
26#include "block_int.h"
27#include <assert.h>
28
29#ifdef _WIN32
30#define WIN32_LEAN_AND_MEAN
31#include <windows.h>
32#endif
33
34
35#define BRDV_O_FLAGS BDRV_O_CACHE_WB
36
37static void QEMU_NORETURN error(const char *fmt, ...)
38{
39 va_list ap;
40 va_start(ap, fmt);
41 fprintf(stderr, "qemu-img: ");
42 vfprintf(stderr, fmt, ap);
43 fprintf(stderr, "\n");
44 exit(1);
45 va_end(ap);
46}
47
48static void format_print(void *opaque, const char *name)
49{
50 printf(" %s", name);
51}
52
53
54static void help(void)
55{
56 printf("qemu-img version " QEMU_VERSION ", Copyright (c) 2004-2008 Fabrice Bellard\n"
57 "usage: qemu-img command [command options]\n"
58 "QEMU disk image utility\n"
59 "\n"
60 "Command syntax:\n"
61 " create [-e] [-6] [-b base_image] [-f fmt] filename [size]\n"
62 " commit [-f fmt] filename\n"
63 " convert [-c] [-e] [-6] [-f fmt] [-O output_fmt] [-B output_base_image] filename [filename2 [...]] output_filename\n"
64 " info [-f fmt] filename\n"
65 " snapshot [-l | -a snapshot | -c snapshot | -d snapshot] filename\n"
66 "\n"
67 "Command parameters:\n"
68 " 'filename' is a disk image filename\n"
69 " 'base_image' is the read-only disk image which is used as base for a copy on\n"
70 " write image; the copy on write image only stores the modified data\n"
71 " 'output_base_image' forces the output image to be created as a copy on write\n"
72 " image of the specified base image; 'output_base_image' should have the same\n"
73 " content as the input's base image, however the path, image format, etc may\n"
74 " differ\n"
75 " 'fmt' is the disk image format. It is guessed automatically in most cases\n"
76 " 'size' is the disk image size in kilobytes. Optional suffixes\n"
77 " 'M' (megabyte, 1024 * 1024) and 'G' (gigabyte, 1024 * 1024 * 1024) are"
78 " supported any @code{k} or @code{K} is ignored\n"
79 " 'output_filename' is the destination disk image filename\n"
80 " 'output_fmt' is the destination format\n"
81 " '-c' indicates that target image must be compressed (qcow format only)\n"
82 " '-e' indicates that the target image must be encrypted (qcow format only)\n"
83 " '-6' indicates that the target image must use compatibility level 6 (vmdk format only)\n"
84 " '-h' with or without a command shows this help and lists the supported formats\n"
85 "\n"
86 "Parameters to snapshot subcommand:\n"
87 " 'snapshot' is the name of the snapshot to create, apply or delete\n"
88 " '-a' applies a snapshot (revert disk to saved state)\n"
89 " '-c' creates a snapshot\n"
90 " '-d' deletes a snapshot\n"
91 " '-l' lists all snapshots in the given image\n"
92 );
93 printf("\nSupported formats:");
94 bdrv_iterate_format(format_print, NULL);
95 printf("\n");
96 exit(1);
97}
98
99#if defined(WIN32)
100
101static int read_password(char *buf, int buf_size)
102{
103 int c, i;
104 printf("Password: ");
105 fflush(stdout);
106 i = 0;
107 for(;;) {
108 c = getchar();
109 if (c == '\n')
110 break;
111 if (i < (buf_size - 1))
112 buf[i++] = c;
113 }
114 buf[i] = '\0';
115 return 0;
116}
117
118#else
119
120#include <termios.h>
121
122static struct termios oldtty;
123
124static void term_exit(void)
125{
126 tcsetattr (0, TCSANOW, &oldtty);
127}
128
129static void term_init(void)
130{
131 struct termios tty;
132
133 tcgetattr (0, &tty);
134 oldtty = tty;
135
136 tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP
137 |INLCR|IGNCR|ICRNL|IXON);
138 tty.c_oflag |= OPOST;
139 tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN);
140 tty.c_cflag &= ~(CSIZE|PARENB);
141 tty.c_cflag |= CS8;
142 tty.c_cc[VMIN] = 1;
143 tty.c_cc[VTIME] = 0;
144
145 tcsetattr (0, TCSANOW, &tty);
146
147 atexit(term_exit);
148}
149
150static int read_password(char *buf, int buf_size)
151{
152 uint8_t ch;
153 int i, ret;
154
155 printf("password: ");
156 fflush(stdout);
157 term_init();
158 i = 0;
159 for(;;) {
160 ret = read(0, &ch, 1);
161 if (ret == -1) {
162 if (errno == EAGAIN || errno == EINTR) {
163 continue;
164 } else {
165 ret = -1;
166 break;
167 }
168 } else if (ret == 0) {
169 ret = -1;
170 break;
171 } else {
172 if (ch == '\r') {
173 ret = 0;
174 break;
175 }
176 if (i < (buf_size - 1))
177 buf[i++] = ch;
178 }
179 }
180 term_exit();
181 buf[i] = '\0';
182 printf("\n");
183 return ret;
184}
185#endif
186
187static BlockDriverState *bdrv_new_open(const char *filename,
188 const char *fmt)
189{
190 BlockDriverState *bs;
191 BlockDriver *drv;
192 char password[256];
193
194 bs = bdrv_new("");
195 if (!bs)
196 error("Not enough memory");
197 if (fmt) {
198 drv = bdrv_find_format(fmt);
199 if (!drv)
200 error("Unknown file format '%s'", fmt);
201 } else {
202 drv = NULL;
203 }
204 if (bdrv_open2(bs, filename, BRDV_O_FLAGS, drv) < 0) {
205 error("Could not open '%s'", filename);
206 }
207 if (bdrv_is_encrypted(bs)) {
208 printf("Disk image '%s' is encrypted.\n", filename);
209 if (read_password(password, sizeof(password)) < 0)
210 error("No password given");
211 if (bdrv_set_key(bs, password) < 0)
212 error("invalid password");
213 }
214 return bs;
215}
216
217static int img_create(int argc, char **argv)
218{
219 int c, ret, flags;
220 const char *fmt = "raw";
221 const char *filename;
222 const char *base_filename = NULL;
223 uint64_t size;
224 const char *p;
225 BlockDriver *drv;
226
227 flags = 0;
228 for(;;) {
229 c = getopt(argc, argv, "b:f:he6");
230 if (c == -1)
231 break;
232 switch(c) {
233 case 'h':
234 help();
235 break;
236 case 'b':
237 base_filename = optarg;
238 break;
239 case 'f':
240 fmt = optarg;
241 break;
242 case 'e':
243 flags |= BLOCK_FLAG_ENCRYPT;
244 break;
245 case '6':
246 flags |= BLOCK_FLAG_COMPAT6;
247 break;
248 }
249 }
250 if (optind >= argc)
251 help();
252 filename = argv[optind++];
253 size = 0;
254 if (base_filename) {
255 BlockDriverState *bs;
256 bs = bdrv_new_open(base_filename, NULL);
257 bdrv_get_geometry(bs, &size);
258 size *= 512;
259 bdrv_delete(bs);
260 } else {
261 if (optind >= argc)
262 help();
263 p = argv[optind];
264 size = strtoul(p, (char **)&p, 0);
265 if (*p == 'M') {
266 size *= 1024 * 1024;
267 } else if (*p == 'G') {
268 size *= 1024 * 1024 * 1024;
269 } else if (*p == 'k' || *p == 'K' || *p == '\0') {
270 size *= 1024;
271 } else {
272 help();
273 }
274 }
275 drv = bdrv_find_format(fmt);
276 if (!drv)
277 error("Unknown file format '%s'", fmt);
278 printf("Formatting '%s', fmt=%s",
279 filename, fmt);
280 if (flags & BLOCK_FLAG_ENCRYPT)
281 printf(", encrypted");
282 if (flags & BLOCK_FLAG_COMPAT6)
283 printf(", compatibility level=6");
284 if (base_filename) {
285 printf(", backing_file=%s",
286 base_filename);
287 }
288 printf(", size=%" PRIu64 " kB\n", size / 1024);
289 ret = bdrv_create(drv, filename, size / 512, base_filename, flags);
290 if (ret < 0) {
291 if (ret == -ENOTSUP) {
292 error("Formatting or formatting option not supported for file format '%s'", fmt);
293 } else {
294 error("Error while formatting");
295 }
296 }
297 return 0;
298}
299
300static int img_commit(int argc, char **argv)
301{
302 int c, ret;
303 const char *filename, *fmt;
304 BlockDriver *drv;
305 BlockDriverState *bs;
306
307 fmt = NULL;
308 for(;;) {
309 c = getopt(argc, argv, "f:h");
310 if (c == -1)
311 break;
312 switch(c) {
313 case 'h':
314 help();
315 break;
316 case 'f':
317 fmt = optarg;
318 break;
319 }
320 }
321 if (optind >= argc)
322 help();
323 filename = argv[optind++];
324
325 bs = bdrv_new("");
326 if (!bs)
327 error("Not enough memory");
328 if (fmt) {
329 drv = bdrv_find_format(fmt);
330 if (!drv)
331 error("Unknown file format '%s'", fmt);
332 } else {
333 drv = NULL;
334 }
335 if (bdrv_open2(bs, filename, BRDV_O_FLAGS, drv) < 0) {
336 error("Could not open '%s'", filename);
337 }
338 ret = bdrv_commit(bs);
339 switch(ret) {
340 case 0:
341 printf("Image committed.\n");
342 break;
343 case -ENOENT:
344 error("No disk inserted");
345 break;
346 case -EACCES:
347 error("Image is read-only");
348 break;
349 case -ENOTSUP:
350 error("Image is already committed");
351 break;
352 default:
353 error("Error while committing image");
354 break;
355 }
356
357 bdrv_delete(bs);
358 return 0;
359}
360
361static int is_not_zero(const uint8_t *sector, int len)
362{
363 int i;
364 len >>= 2;
365 for(i = 0;i < len; i++) {
366 if (((uint32_t *)sector)[i] != 0)
367 return 1;
368 }
369 return 0;
370}
371
372
373
374
375
376
377
378
379static int is_allocated_sectors(const uint8_t *buf, int n, int *pnum)
380{
381 int v, i;
382
383 if (n <= 0) {
384 *pnum = 0;
385 return 0;
386 }
387 v = is_not_zero(buf, 512);
388 for(i = 1; i < n; i++) {
389 buf += 512;
390 if (v != is_not_zero(buf, 512))
391 break;
392 }
393 *pnum = i;
394 return v;
395}
396
397#define IO_BUF_SIZE 65536
398
399static int img_convert(int argc, char **argv)
400{
401 int c, ret, n, n1, bs_n, bs_i, flags, cluster_size, cluster_sectors;
402 const char *fmt, *out_fmt, *out_baseimg, *out_filename;
403 BlockDriver *drv;
404 BlockDriverState **bs, *out_bs;
405 int64_t total_sectors, nb_sectors, sector_num, bs_offset;
406 uint64_t bs_sectors;
407 uint8_t buf[IO_BUF_SIZE];
408 const uint8_t *buf1;
409 BlockDriverInfo bdi;
410
411 fmt = NULL;
412 out_fmt = "raw";
413 out_baseimg = NULL;
414 flags = 0;
415 for(;;) {
416 c = getopt(argc, argv, "f:O:B:hce6");
417 if (c == -1)
418 break;
419 switch(c) {
420 case 'h':
421 help();
422 break;
423 case 'f':
424 fmt = optarg;
425 break;
426 case 'O':
427 out_fmt = optarg;
428 break;
429 case 'B':
430 out_baseimg = optarg;
431 break;
432 case 'c':
433 flags |= BLOCK_FLAG_COMPRESS;
434 break;
435 case 'e':
436 flags |= BLOCK_FLAG_ENCRYPT;
437 break;
438 case '6':
439 flags |= BLOCK_FLAG_COMPAT6;
440 break;
441 }
442 }
443
444 bs_n = argc - optind - 1;
445 if (bs_n < 1) help();
446
447 out_filename = argv[argc - 1];
448
449 if (bs_n > 1 && out_baseimg)
450 error("-B makes no sense when concatenating multiple input images");
451
452 bs = calloc(bs_n, sizeof(BlockDriverState *));
453 if (!bs)
454 error("Out of memory");
455
456 total_sectors = 0;
457 for (bs_i = 0; bs_i < bs_n; bs_i++) {
458 bs[bs_i] = bdrv_new_open(argv[optind + bs_i], fmt);
459 if (!bs[bs_i])
460 error("Could not open '%s'", argv[optind + bs_i]);
461 bdrv_get_geometry(bs[bs_i], &bs_sectors);
462 total_sectors += bs_sectors;
463 }
464
465 drv = bdrv_find_format(out_fmt);
466 if (!drv)
467 error("Unknown file format '%s'", out_fmt);
468 if (flags & BLOCK_FLAG_COMPRESS && drv != &bdrv_qcow && drv != &bdrv_qcow2)
469 error("Compression not supported for this file format");
470 if (flags & BLOCK_FLAG_ENCRYPT && drv != &bdrv_qcow && drv != &bdrv_qcow2)
471 error("Encryption not supported for this file format");
472 if (flags & BLOCK_FLAG_COMPAT6 && drv != &bdrv_vmdk)
473 error("Alternative compatibility level not supported for this file format");
474 if (flags & BLOCK_FLAG_ENCRYPT && flags & BLOCK_FLAG_COMPRESS)
475 error("Compression and encryption not supported at the same time");
476
477 ret = bdrv_create(drv, out_filename, total_sectors, out_baseimg, flags);
478 if (ret < 0) {
479 if (ret == -ENOTSUP) {
480 error("Formatting not supported for file format '%s'", fmt);
481 } else {
482 error("Error while formatting '%s'", out_filename);
483 }
484 }
485
486 out_bs = bdrv_new_open(out_filename, out_fmt);
487
488 bs_i = 0;
489 bs_offset = 0;
490 bdrv_get_geometry(bs[0], &bs_sectors);
491
492 if (flags & BLOCK_FLAG_COMPRESS) {
493 if (bdrv_get_info(out_bs, &bdi) < 0)
494 error("could not get block driver info");
495 cluster_size = bdi.cluster_size;
496 if (cluster_size <= 0 || cluster_size > IO_BUF_SIZE)
497 error("invalid cluster size");
498 cluster_sectors = cluster_size >> 9;
499 sector_num = 0;
500 for(;;) {
501 int64_t bs_num;
502 int remainder;
503 uint8_t *buf2;
504
505 nb_sectors = total_sectors - sector_num;
506 if (nb_sectors <= 0)
507 break;
508 if (nb_sectors >= cluster_sectors)
509 n = cluster_sectors;
510 else
511 n = nb_sectors;
512
513 bs_num = sector_num - bs_offset;
514 assert (bs_num >= 0);
515 remainder = n;
516 buf2 = buf;
517 while (remainder > 0) {
518 int nlow;
519 while (bs_num == bs_sectors) {
520 bs_i++;
521 assert (bs_i < bs_n);
522 bs_offset += bs_sectors;
523 bdrv_get_geometry(bs[bs_i], &bs_sectors);
524 bs_num = 0;
525
526
527
528 }
529 assert (bs_num < bs_sectors);
530
531 nlow = (remainder > bs_sectors - bs_num) ? bs_sectors - bs_num : remainder;
532
533 if (bdrv_read(bs[bs_i], bs_num, buf2, nlow) < 0)
534 error("error while reading");
535
536 buf2 += nlow * 512;
537 bs_num += nlow;
538
539 remainder -= nlow;
540 }
541 assert (remainder == 0);
542
543 if (n < cluster_sectors)
544 memset(buf + n * 512, 0, cluster_size - n * 512);
545 if (is_not_zero(buf, cluster_size)) {
546 if (bdrv_write_compressed(out_bs, sector_num, buf,
547 cluster_sectors) != 0)
548 error("error while compressing sector %" PRId64,
549 sector_num);
550 }
551 sector_num += n;
552 }
553
554 bdrv_write_compressed(out_bs, 0, NULL, 0);
555 } else {
556 sector_num = 0;
557 for(;;) {
558 nb_sectors = total_sectors - sector_num;
559 if (nb_sectors <= 0)
560 break;
561 if (nb_sectors >= (IO_BUF_SIZE / 512))
562 n = (IO_BUF_SIZE / 512);
563 else
564 n = nb_sectors;
565
566 while (sector_num - bs_offset >= bs_sectors) {
567 bs_i ++;
568 assert (bs_i < bs_n);
569 bs_offset += bs_sectors;
570 bdrv_get_geometry(bs[bs_i], &bs_sectors);
571
572
573
574 }
575
576 if (n > bs_offset + bs_sectors - sector_num)
577 n = bs_offset + bs_sectors - sector_num;
578
579
580
581
582
583 if (out_baseimg) {
584 if (!bdrv_is_allocated(bs[bs_i], sector_num - bs_offset, n, &n1)) {
585 sector_num += n1;
586 continue;
587 }
588
589
590 n = n1;
591 }
592
593 if (bdrv_read(bs[bs_i], sector_num - bs_offset, buf, n) < 0)
594 error("error while reading");
595
596
597
598 buf1 = buf;
599 while (n > 0) {
600
601
602
603 if (out_baseimg || is_allocated_sectors(buf1, n, &n1)) {
604 if (bdrv_write(out_bs, sector_num, buf1, n1) < 0)
605 error("error while writing");
606 }
607 sector_num += n1;
608 n -= n1;
609 buf1 += n1 * 512;
610 }
611 }
612 }
613 bdrv_delete(out_bs);
614 for (bs_i = 0; bs_i < bs_n; bs_i++)
615 bdrv_delete(bs[bs_i]);
616 free(bs);
617 return 0;
618}
619
620#ifdef _WIN32
621static int64_t get_allocated_file_size(const char *filename)
622{
623 typedef DWORD (WINAPI * get_compressed_t)(const char *filename, DWORD *high);
624 get_compressed_t get_compressed;
625 struct _stati64 st;
626
627
628 get_compressed = (get_compressed_t) GetProcAddress(GetModuleHandle("kernel32"), "GetCompressedFileSizeA");
629 if (get_compressed) {
630 DWORD high, low;
631 low = get_compressed(filename, &high);
632 if (low != 0xFFFFFFFFlu || GetLastError() == NO_ERROR)
633 return (((int64_t) high) << 32) + low;
634 }
635
636 if (_stati64(filename, &st) < 0)
637 return -1;
638 return st.st_size;
639}
640#else
641static int64_t get_allocated_file_size(const char *filename)
642{
643 struct stat st;
644 if (stat(filename, &st) < 0)
645 return -1;
646 return (int64_t)st.st_blocks * 512;
647}
648#endif
649
650static void dump_snapshots(BlockDriverState *bs)
651{
652 QEMUSnapshotInfo *sn_tab, *sn;
653 int nb_sns, i;
654 char buf[256];
655
656 nb_sns = bdrv_snapshot_list(bs, &sn_tab);
657 if (nb_sns <= 0)
658 return;
659 printf("Snapshot list:\n");
660 printf("%s\n", bdrv_snapshot_dump(buf, sizeof(buf), NULL));
661 for(i = 0; i < nb_sns; i++) {
662 sn = &sn_tab[i];
663 printf("%s\n", bdrv_snapshot_dump(buf, sizeof(buf), sn));
664 }
665 qemu_free(sn_tab);
666}
667
668static int img_info(int argc, char **argv)
669{
670 int c;
671 const char *filename, *fmt;
672 BlockDriver *drv;
673 BlockDriverState *bs;
674 char fmt_name[128], size_buf[128], dsize_buf[128];
675 uint64_t total_sectors;
676 int64_t allocated_size;
677 char backing_filename[1024];
678 char backing_filename2[1024];
679 BlockDriverInfo bdi;
680
681 fmt = NULL;
682 for(;;) {
683 c = getopt(argc, argv, "f:h");
684 if (c == -1)
685 break;
686 switch(c) {
687 case 'h':
688 help();
689 break;
690 case 'f':
691 fmt = optarg;
692 break;
693 }
694 }
695 if (optind >= argc)
696 help();
697 filename = argv[optind++];
698
699 bs = bdrv_new("");
700 if (!bs)
701 error("Not enough memory");
702 if (fmt) {
703 drv = bdrv_find_format(fmt);
704 if (!drv)
705 error("Unknown file format '%s'", fmt);
706 } else {
707 drv = NULL;
708 }
709 if (bdrv_open2(bs, filename, BRDV_O_FLAGS, drv) < 0) {
710 error("Could not open '%s'", filename);
711 }
712 bdrv_get_format(bs, fmt_name, sizeof(fmt_name));
713 bdrv_get_geometry(bs, &total_sectors);
714 get_human_readable_size(size_buf, sizeof(size_buf), total_sectors * 512);
715 allocated_size = get_allocated_file_size(filename);
716 if (allocated_size < 0)
717 snprintf(dsize_buf, sizeof(dsize_buf), "unavailable");
718 else
719 get_human_readable_size(dsize_buf, sizeof(dsize_buf),
720 allocated_size);
721 printf("image: %s\n"
722 "file format: %s\n"
723 "virtual size: %s (%" PRId64 " bytes)\n"
724 "disk size: %s\n",
725 filename, fmt_name, size_buf,
726 (total_sectors * 512),
727 dsize_buf);
728 if (bdrv_is_encrypted(bs))
729 printf("encrypted: yes\n");
730 if (bdrv_get_info(bs, &bdi) >= 0) {
731 if (bdi.cluster_size != 0)
732 printf("cluster_size: %d\n", bdi.cluster_size);
733 if (bdi.highest_alloc)
734 printf("highest_alloc: %" PRId64 "\n", bdi.highest_alloc);
735 if (bdi.num_free_bytes)
736 printf("num_free_bytes: %" PRId64 "\n", bdi.num_free_bytes);
737 }
738 bdrv_get_backing_filename(bs, backing_filename, sizeof(backing_filename));
739 if (backing_filename[0] != '\0') {
740 path_combine(backing_filename2, sizeof(backing_filename2),
741 filename, backing_filename);
742 printf("backing file: %s (actual path: %s)\n",
743 backing_filename,
744 backing_filename2);
745 }
746 dump_snapshots(bs);
747 bdrv_delete(bs);
748 return 0;
749}
750
751#define SNAPSHOT_LIST 1
752#define SNAPSHOT_CREATE 2
753#define SNAPSHOT_APPLY 3
754#define SNAPSHOT_DELETE 4
755
756static void img_snapshot(int argc, char **argv)
757{
758 BlockDriverState *bs;
759 QEMUSnapshotInfo sn;
760 char *filename, *snapshot_name = NULL;
761 int c, ret;
762 int action = 0;
763 qemu_timeval tv;
764
765
766 for(;;) {
767 c = getopt(argc, argv, "la:c:d:h");
768 if (c == -1)
769 break;
770 switch(c) {
771 case 'h':
772 help();
773 return;
774 case 'l':
775 if (action) {
776 help();
777 return;
778 }
779 action = SNAPSHOT_LIST;
780 break;
781 case 'a':
782 if (action) {
783 help();
784 return;
785 }
786 action = SNAPSHOT_APPLY;
787 snapshot_name = optarg;
788 break;
789 case 'c':
790 if (action) {
791 help();
792 return;
793 }
794 action = SNAPSHOT_CREATE;
795 snapshot_name = optarg;
796 break;
797 case 'd':
798 if (action) {
799 help();
800 return;
801 }
802 action = SNAPSHOT_DELETE;
803 snapshot_name = optarg;
804 break;
805 }
806 }
807
808 if (optind >= argc)
809 help();
810 filename = argv[optind++];
811
812
813 bs = bdrv_new("");
814 if (!bs)
815 error("Not enough memory");
816
817 if (bdrv_open2(bs, filename, 0, NULL) < 0) {
818 error("Could not open '%s'", filename);
819 }
820
821
822 switch(action) {
823 case SNAPSHOT_LIST:
824 dump_snapshots(bs);
825 break;
826
827 case SNAPSHOT_CREATE:
828 memset(&sn, 0, sizeof(sn));
829 pstrcpy(sn.name, sizeof(sn.name), snapshot_name);
830
831 qemu_gettimeofday(&tv);
832 sn.date_sec = tv.tv_sec;
833 sn.date_nsec = tv.tv_usec * 1000;
834
835 ret = bdrv_snapshot_create(bs, &sn);
836 if (ret)
837 error("Could not create snapshot '%s': %d (%s)",
838 snapshot_name, ret, strerror(-ret));
839 break;
840
841 case SNAPSHOT_APPLY:
842 ret = bdrv_snapshot_goto(bs, snapshot_name);
843 if (ret)
844 error("Could not apply snapshot '%s': %d (%s)",
845 snapshot_name, ret, strerror(-ret));
846 break;
847
848 case SNAPSHOT_DELETE:
849 ret = bdrv_snapshot_delete(bs, snapshot_name);
850 if (ret)
851 error("Could not delete snapshot '%s': %d (%s)",
852 snapshot_name, ret, strerror(-ret));
853 break;
854 }
855
856
857 bdrv_delete(bs);
858}
859
860int main(int argc, char **argv)
861{
862 const char *cmd;
863
864 bdrv_init();
865 if (argc < 2)
866 help();
867 cmd = argv[1];
868 argc--; argv++;
869 if (!strcmp(cmd, "create")) {
870 img_create(argc, argv);
871 } else if (!strcmp(cmd, "commit")) {
872 img_commit(argc, argv);
873 } else if (!strcmp(cmd, "convert")) {
874 img_convert(argc, argv);
875 } else if (!strcmp(cmd, "info")) {
876 img_info(argc, argv);
877 } else if (!strcmp(cmd, "snapshot")) {
878 img_snapshot(argc, argv);
879 } else {
880 help();
881 }
882 return 0;
883}
884