Win32: Fix warnings
[qemu] / qemu-img.c
1 /*
2  * QEMU disk image utility
3  *
4  * Copyright (c) 2003-2008 Fabrice Bellard
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 #include "qemu-common.h"
25 #include "block_int.h"
26 #include <assert.h>
27
28 #ifdef _WIN32
29 #define WIN32_LEAN_AND_MEAN
30 #include <windows.h>
31 #endif
32
33 static void __attribute__((noreturn)) error(const char *fmt, ...)
34 {
35     va_list ap;
36     va_start(ap, fmt);
37     fprintf(stderr, "qemu-img: ");
38     vfprintf(stderr, fmt, ap);
39     fprintf(stderr, "\n");
40     exit(1);
41     va_end(ap);
42 }
43
44 static void format_print(void *opaque, const char *name)
45 {
46     printf(" %s", name);
47 }
48
49 static void help(void)
50 {
51     printf("qemu-img version " QEMU_VERSION ", Copyright (c) 2004-2008 Fabrice Bellard\n"
52            "usage: qemu-img command [command options]\n"
53            "QEMU disk image utility\n"
54            "\n"
55            "Command syntax:\n"
56            "  create [-e] [-6] [-b base_image] [-f fmt] filename [size]\n"
57            "  commit [-f fmt] filename\n"
58            "  convert [-c] [-e] [-6] [-f fmt] [-O output_fmt] [-B output_base_image] filename [filename2 [...]] output_filename\n"
59            "  info [-f fmt] filename\n"
60            "\n"
61            "Command parameters:\n"
62            "  'filename' is a disk image filename\n"
63            "  'base_image' is the read-only disk image which is used as base for a copy on\n"
64            "    write image; the copy on write image only stores the modified data\n"
65            "  'output_base_image' forces the output image to be created as a copy on write\n"
66            "    image of the specified base image; 'output_base_image' should have the same\n"
67            "    content as the input's base image, however the path, image format, etc may\n"
68            "    differ\n"
69            "  'fmt' is the disk image format. It is guessed automatically in most cases\n"
70            "  'size' is the disk image size in kilobytes. Optional suffixes 'M' (megabyte)\n"
71            "    and 'G' (gigabyte) are supported\n"
72            "  'output_filename' is the destination disk image filename\n"
73            "  'output_fmt' is the destination format\n"
74            "  '-c' indicates that target image must be compressed (qcow format only)\n"
75            "  '-e' indicates that the target image must be encrypted (qcow format only)\n"
76            "  '-6' indicates that the target image must use compatibility level 6 (vmdk format only)\n"
77            );
78     printf("\nSupported format:");
79     bdrv_iterate_format(format_print, NULL);
80     printf("\n");
81     exit(1);
82 }
83
84 #if defined(WIN32)
85 /* XXX: put correct support for win32 */
86 static int read_password(char *buf, int buf_size)
87 {
88     int c, i;
89     printf("Password: ");
90     fflush(stdout);
91     i = 0;
92     for(;;) {
93         c = getchar();
94         if (c == '\n')
95             break;
96         if (i < (buf_size - 1))
97             buf[i++] = c;
98     }
99     buf[i] = '\0';
100     return 0;
101 }
102
103 #else
104
105 #include <termios.h>
106
107 static struct termios oldtty;
108
109 static void term_exit(void)
110 {
111     tcsetattr (0, TCSANOW, &oldtty);
112 }
113
114 static void term_init(void)
115 {
116     struct termios tty;
117
118     tcgetattr (0, &tty);
119     oldtty = tty;
120
121     tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP
122                           |INLCR|IGNCR|ICRNL|IXON);
123     tty.c_oflag |= OPOST;
124     tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN);
125     tty.c_cflag &= ~(CSIZE|PARENB);
126     tty.c_cflag |= CS8;
127     tty.c_cc[VMIN] = 1;
128     tty.c_cc[VTIME] = 0;
129
130     tcsetattr (0, TCSANOW, &tty);
131
132     atexit(term_exit);
133 }
134
135 static int read_password(char *buf, int buf_size)
136 {
137     uint8_t ch;
138     int i, ret;
139
140     printf("password: ");
141     fflush(stdout);
142     term_init();
143     i = 0;
144     for(;;) {
145         ret = read(0, &ch, 1);
146         if (ret == -1) {
147             if (errno == EAGAIN || errno == EINTR) {
148                 continue;
149             } else {
150                 ret = -1;
151                 break;
152             }
153         } else if (ret == 0) {
154             ret = -1;
155             break;
156         } else {
157             if (ch == '\r') {
158                 ret = 0;
159                 break;
160             }
161             if (i < (buf_size - 1))
162                 buf[i++] = ch;
163         }
164     }
165     term_exit();
166     buf[i] = '\0';
167     printf("\n");
168     return ret;
169 }
170 #endif
171
172 static BlockDriverState *bdrv_new_open(const char *filename,
173                                        const char *fmt)
174 {
175     BlockDriverState *bs;
176     BlockDriver *drv;
177     char password[256];
178
179     bs = bdrv_new("");
180     if (!bs)
181         error("Not enough memory");
182     if (fmt) {
183         drv = bdrv_find_format(fmt);
184         if (!drv)
185             error("Unknown file format '%s'", fmt);
186     } else {
187         drv = NULL;
188     }
189     if (bdrv_open2(bs, filename, 0, drv) < 0) {
190         error("Could not open '%s'", filename);
191     }
192     if (bdrv_is_encrypted(bs)) {
193         printf("Disk image '%s' is encrypted.\n", filename);
194         if (read_password(password, sizeof(password)) < 0)
195             error("No password given");
196         if (bdrv_set_key(bs, password) < 0)
197             error("invalid password");
198     }
199     return bs;
200 }
201
202 static int img_create(int argc, char **argv)
203 {
204     int c, ret, flags;
205     const char *fmt = "raw";
206     const char *filename;
207     const char *base_filename = NULL;
208     uint64_t size;
209     const char *p;
210     BlockDriver *drv;
211
212     flags = 0;
213     for(;;) {
214         c = getopt(argc, argv, "b:f:he6");
215         if (c == -1)
216             break;
217         switch(c) {
218         case 'h':
219             help();
220             break;
221         case 'b':
222             base_filename = optarg;
223             break;
224         case 'f':
225             fmt = optarg;
226             break;
227         case 'e':
228             flags |= BLOCK_FLAG_ENCRYPT;
229             break;
230         case '6':
231             flags |= BLOCK_FLAG_COMPAT6;
232             break;
233         }
234     }
235     if (optind >= argc)
236         help();
237     filename = argv[optind++];
238     size = 0;
239     if (base_filename) {
240         BlockDriverState *bs;
241         bs = bdrv_new_open(base_filename, NULL);
242         bdrv_get_geometry(bs, &size);
243         size *= 512;
244         bdrv_delete(bs);
245     } else {
246         if (optind >= argc)
247             help();
248         p = argv[optind];
249         size = strtoul(p, (char **)&p, 0);
250         if (*p == 'M') {
251             size *= 1024 * 1024;
252         } else if (*p == 'G') {
253             size *= 1024 * 1024 * 1024;
254         } else if (*p == 'k' || *p == 'K' || *p == '\0') {
255             size *= 1024;
256         } else {
257             help();
258         }
259     }
260     drv = bdrv_find_format(fmt);
261     if (!drv)
262         error("Unknown file format '%s'", fmt);
263     printf("Formatting '%s', fmt=%s",
264            filename, fmt);
265     if (flags & BLOCK_FLAG_ENCRYPT)
266         printf(", encrypted");
267     if (flags & BLOCK_FLAG_COMPAT6)
268         printf(", compatibility level=6");
269     if (base_filename) {
270         printf(", backing_file=%s",
271                base_filename);
272     }
273     printf(", size=%" PRIu64 " kB\n", size / 1024);
274     ret = bdrv_create(drv, filename, size / 512, base_filename, flags);
275     if (ret < 0) {
276         if (ret == -ENOTSUP) {
277             error("Formatting or formatting option not supported for file format '%s'", fmt);
278         } else {
279             error("Error while formatting");
280         }
281     }
282     return 0;
283 }
284
285 static int img_commit(int argc, char **argv)
286 {
287     int c, ret;
288     const char *filename, *fmt;
289     BlockDriver *drv;
290     BlockDriverState *bs;
291
292     fmt = NULL;
293     for(;;) {
294         c = getopt(argc, argv, "f:h");
295         if (c == -1)
296             break;
297         switch(c) {
298         case 'h':
299             help();
300             break;
301         case 'f':
302             fmt = optarg;
303             break;
304         }
305     }
306     if (optind >= argc)
307         help();
308     filename = argv[optind++];
309
310     bs = bdrv_new("");
311     if (!bs)
312         error("Not enough memory");
313     if (fmt) {
314         drv = bdrv_find_format(fmt);
315         if (!drv)
316             error("Unknown file format '%s'", fmt);
317     } else {
318         drv = NULL;
319     }
320     if (bdrv_open2(bs, filename, 0, drv) < 0) {
321         error("Could not open '%s'", filename);
322     }
323     ret = bdrv_commit(bs);
324     switch(ret) {
325     case 0:
326         printf("Image committed.\n");
327         break;
328     case -ENOENT:
329         error("No disk inserted");
330         break;
331     case -EACCES:
332         error("Image is read-only");
333         break;
334     case -ENOTSUP:
335         error("Image is already committed");
336         break;
337     default:
338         error("Error while committing image");
339         break;
340     }
341
342     bdrv_delete(bs);
343     return 0;
344 }
345
346 static int is_not_zero(const uint8_t *sector, int len)
347 {
348     int i;
349     len >>= 2;
350     for(i = 0;i < len; i++) {
351         if (((uint32_t *)sector)[i] != 0)
352             return 1;
353     }
354     return 0;
355 }
356
357 /*
358  * Returns true iff the first sector pointed to by 'buf' contains at least
359  * a non-NUL byte.
360  *
361  * 'pnum' is set to the number of sectors (including and immediately following
362  * the first one) that are known to be in the same allocated/unallocated state.
363  */
364 static int is_allocated_sectors(const uint8_t *buf, int n, int *pnum)
365 {
366     int v, i;
367
368     if (n <= 0) {
369         *pnum = 0;
370         return 0;
371     }
372     v = is_not_zero(buf, 512);
373     for(i = 1; i < n; i++) {
374         buf += 512;
375         if (v != is_not_zero(buf, 512))
376             break;
377     }
378     *pnum = i;
379     return v;
380 }
381
382 #define IO_BUF_SIZE 65536
383
384 static int img_convert(int argc, char **argv)
385 {
386     int c, ret, n, n1, bs_n, bs_i, flags, cluster_size, cluster_sectors;
387     const char *fmt, *out_fmt, *out_baseimg, *out_filename;
388     BlockDriver *drv;
389     BlockDriverState **bs, *out_bs;
390     int64_t total_sectors, nb_sectors, sector_num, bs_offset;
391     uint64_t bs_sectors;
392     uint8_t buf[IO_BUF_SIZE];
393     const uint8_t *buf1;
394     BlockDriverInfo bdi;
395
396     fmt = NULL;
397     out_fmt = "raw";
398     out_baseimg = NULL;
399     flags = 0;
400     for(;;) {
401         c = getopt(argc, argv, "f:O:B:hce6");
402         if (c == -1)
403             break;
404         switch(c) {
405         case 'h':
406             help();
407             break;
408         case 'f':
409             fmt = optarg;
410             break;
411         case 'O':
412             out_fmt = optarg;
413             break;
414         case 'B':
415             out_baseimg = optarg;
416             break;
417         case 'c':
418             flags |= BLOCK_FLAG_COMPRESS;
419             break;
420         case 'e':
421             flags |= BLOCK_FLAG_ENCRYPT;
422             break;
423         case '6':
424             flags |= BLOCK_FLAG_COMPAT6;
425             break;
426         }
427     }
428
429     bs_n = argc - optind - 1;
430     if (bs_n < 1) help();
431
432     out_filename = argv[argc - 1];
433
434     if (bs_n > 1 && out_baseimg)
435         error("-B makes no sense when concatenating multiple input images");
436         
437     bs = calloc(bs_n, sizeof(BlockDriverState *));
438     if (!bs)
439         error("Out of memory");
440
441     total_sectors = 0;
442     for (bs_i = 0; bs_i < bs_n; bs_i++) {
443         bs[bs_i] = bdrv_new_open(argv[optind + bs_i], fmt);
444         if (!bs[bs_i])
445             error("Could not open '%s'", argv[optind + bs_i]);
446         bdrv_get_geometry(bs[bs_i], &bs_sectors);
447         total_sectors += bs_sectors;
448     }
449
450     drv = bdrv_find_format(out_fmt);
451     if (!drv)
452         error("Unknown file format '%s'", out_fmt);
453     if (flags & BLOCK_FLAG_COMPRESS && drv != &bdrv_qcow && drv != &bdrv_qcow2)
454         error("Compression not supported for this file format");
455     if (flags & BLOCK_FLAG_ENCRYPT && drv != &bdrv_qcow && drv != &bdrv_qcow2)
456         error("Encryption not supported for this file format");
457     if (flags & BLOCK_FLAG_COMPAT6 && drv != &bdrv_vmdk)
458         error("Alternative compatibility level not supported for this file format");
459     if (flags & BLOCK_FLAG_ENCRYPT && flags & BLOCK_FLAG_COMPRESS)
460         error("Compression and encryption not supported at the same time");
461
462     ret = bdrv_create(drv, out_filename, total_sectors, out_baseimg, flags);
463     if (ret < 0) {
464         if (ret == -ENOTSUP) {
465             error("Formatting not supported for file format '%s'", fmt);
466         } else {
467             error("Error while formatting '%s'", out_filename);
468         }
469     }
470
471     out_bs = bdrv_new_open(out_filename, out_fmt);
472
473     bs_i = 0;
474     bs_offset = 0;
475     bdrv_get_geometry(bs[0], &bs_sectors);
476
477     if (flags & BLOCK_FLAG_COMPRESS) {
478         if (bdrv_get_info(out_bs, &bdi) < 0)
479             error("could not get block driver info");
480         cluster_size = bdi.cluster_size;
481         if (cluster_size <= 0 || cluster_size > IO_BUF_SIZE)
482             error("invalid cluster size");
483         cluster_sectors = cluster_size >> 9;
484         sector_num = 0;
485         for(;;) {
486             int64_t bs_num;
487             int remainder;
488             uint8_t *buf2;
489
490             nb_sectors = total_sectors - sector_num;
491             if (nb_sectors <= 0)
492                 break;
493             if (nb_sectors >= cluster_sectors)
494                 n = cluster_sectors;
495             else
496                 n = nb_sectors;
497
498             bs_num = sector_num - bs_offset;
499             assert (bs_num >= 0);
500             remainder = n;
501             buf2 = buf;
502             while (remainder > 0) {
503                 int nlow;
504                 while (bs_num == bs_sectors) {
505                     bs_i++;
506                     assert (bs_i < bs_n);
507                     bs_offset += bs_sectors;
508                     bdrv_get_geometry(bs[bs_i], &bs_sectors);
509                     bs_num = 0;
510                     /* printf("changing part: sector_num=%lld, "
511                        "bs_i=%d, bs_offset=%lld, bs_sectors=%lld\n",
512                        sector_num, bs_i, bs_offset, bs_sectors); */
513                 }
514                 assert (bs_num < bs_sectors);
515
516                 nlow = (remainder > bs_sectors - bs_num) ? bs_sectors - bs_num : remainder;
517
518                 if (bdrv_read(bs[bs_i], bs_num, buf2, nlow) < 0) 
519                     error("error while reading");
520
521                 buf2 += nlow * 512;
522                 bs_num += nlow;
523
524                 remainder -= nlow;
525             }
526             assert (remainder == 0);
527
528             if (n < cluster_sectors)
529                 memset(buf + n * 512, 0, cluster_size - n * 512);
530             if (is_not_zero(buf, cluster_size)) {
531                 if (bdrv_write_compressed(out_bs, sector_num, buf,
532                                           cluster_sectors) != 0)
533                     error("error while compressing sector %" PRId64,
534                           sector_num);
535             }
536             sector_num += n;
537         }
538         /* signal EOF to align */
539         bdrv_write_compressed(out_bs, 0, NULL, 0);
540     } else {
541         sector_num = 0; // total number of sectors converted so far
542         for(;;) {
543             nb_sectors = total_sectors - sector_num;
544             if (nb_sectors <= 0)
545                 break;
546             if (nb_sectors >= (IO_BUF_SIZE / 512))
547                 n = (IO_BUF_SIZE / 512);
548             else
549                 n = nb_sectors;
550
551             while (sector_num - bs_offset >= bs_sectors) {
552                 bs_i ++;
553                 assert (bs_i < bs_n);
554                 bs_offset += bs_sectors;
555                 bdrv_get_geometry(bs[bs_i], &bs_sectors);
556                 /* printf("changing part: sector_num=%lld, bs_i=%d, "
557                   "bs_offset=%lld, bs_sectors=%lld\n",
558                    sector_num, bs_i, bs_offset, bs_sectors); */
559             }
560
561             if (n > bs_offset + bs_sectors - sector_num)
562                 n = bs_offset + bs_sectors - sector_num;
563
564             /* If the output image is being created as a copy on write image,
565                assume that sectors which are unallocated in the input image
566                are present in both the output's and input's base images (no
567                need to copy them). */
568             if (out_baseimg) {
569                if (!bdrv_is_allocated(bs[bs_i], sector_num - bs_offset, n, &n1)) {
570                   sector_num += n1;
571                   continue;
572                }
573                /* The next 'n1' sectors are allocated in the input image. Copy
574                   only those as they may be followed by unallocated sectors. */
575                n = n1;
576             }
577
578             if (bdrv_read(bs[bs_i], sector_num - bs_offset, buf, n) < 0) 
579                 error("error while reading");
580             /* NOTE: at the same time we convert, we do not write zero
581                sectors to have a chance to compress the image. Ideally, we
582                should add a specific call to have the info to go faster */
583             buf1 = buf;
584             while (n > 0) {
585                 /* If the output image is being created as a copy on write image,
586                    copy all sectors even the ones containing only NUL bytes,
587                    because they may differ from the sectors in the base image. */
588                 if (out_baseimg || is_allocated_sectors(buf1, n, &n1)) {
589                     if (bdrv_write(out_bs, sector_num, buf1, n1) < 0)
590                         error("error while writing");
591                 }
592                 sector_num += n1;
593                 n -= n1;
594                 buf1 += n1 * 512;
595             }
596         }
597     }
598     bdrv_delete(out_bs);
599     for (bs_i = 0; bs_i < bs_n; bs_i++)
600         bdrv_delete(bs[bs_i]);
601     free(bs);
602     return 0;
603 }
604
605 #ifdef _WIN32
606 static int64_t get_allocated_file_size(const char *filename)
607 {
608     typedef DWORD (WINAPI * get_compressed_t)(const char *filename, DWORD *high);
609     get_compressed_t get_compressed;
610     struct _stati64 st;
611
612     /* WinNT support GetCompressedFileSize to determine allocate size */
613     get_compressed = (get_compressed_t) GetProcAddress(GetModuleHandle("kernel32"), "GetCompressedFileSizeA");
614     if (get_compressed) {
615         DWORD high, low;
616         low = get_compressed(filename, &high);
617         if (low != 0xFFFFFFFFlu || GetLastError() == NO_ERROR)
618             return (((int64_t) high) << 32) + low;
619     }
620
621     if (_stati64(filename, &st) < 0)
622         return -1;
623     return st.st_size;
624 }
625 #else
626 static int64_t get_allocated_file_size(const char *filename)
627 {
628     struct stat st;
629     if (stat(filename, &st) < 0)
630         return -1;
631     return (int64_t)st.st_blocks * 512;
632 }
633 #endif
634
635 static void dump_snapshots(BlockDriverState *bs)
636 {
637     QEMUSnapshotInfo *sn_tab, *sn;
638     int nb_sns, i;
639     char buf[256];
640
641     nb_sns = bdrv_snapshot_list(bs, &sn_tab);
642     if (nb_sns <= 0)
643         return;
644     printf("Snapshot list:\n");
645     printf("%s\n", bdrv_snapshot_dump(buf, sizeof(buf), NULL));
646     for(i = 0; i < nb_sns; i++) {
647         sn = &sn_tab[i];
648         printf("%s\n", bdrv_snapshot_dump(buf, sizeof(buf), sn));
649     }
650     qemu_free(sn_tab);
651 }
652
653 static int img_info(int argc, char **argv)
654 {
655     int c;
656     const char *filename, *fmt;
657     BlockDriver *drv;
658     BlockDriverState *bs;
659     char fmt_name[128], size_buf[128], dsize_buf[128];
660     uint64_t total_sectors;
661     int64_t allocated_size;
662     char backing_filename[1024];
663     char backing_filename2[1024];
664     BlockDriverInfo bdi;
665
666     fmt = NULL;
667     for(;;) {
668         c = getopt(argc, argv, "f:h");
669         if (c == -1)
670             break;
671         switch(c) {
672         case 'h':
673             help();
674             break;
675         case 'f':
676             fmt = optarg;
677             break;
678         }
679     }
680     if (optind >= argc)
681         help();
682     filename = argv[optind++];
683
684     bs = bdrv_new("");
685     if (!bs)
686         error("Not enough memory");
687     if (fmt) {
688         drv = bdrv_find_format(fmt);
689         if (!drv)
690             error("Unknown file format '%s'", fmt);
691     } else {
692         drv = NULL;
693     }
694     if (bdrv_open2(bs, filename, 0, drv) < 0) {
695         error("Could not open '%s'", filename);
696     }
697     bdrv_get_format(bs, fmt_name, sizeof(fmt_name));
698     bdrv_get_geometry(bs, &total_sectors);
699     get_human_readable_size(size_buf, sizeof(size_buf), total_sectors * 512);
700     allocated_size = get_allocated_file_size(filename);
701     if (allocated_size < 0)
702         snprintf(dsize_buf, sizeof(dsize_buf), "unavailable");
703     else
704         get_human_readable_size(dsize_buf, sizeof(dsize_buf),
705                                 allocated_size);
706     printf("image: %s\n"
707            "file format: %s\n"
708            "virtual size: %s (%" PRId64 " bytes)\n"
709            "disk size: %s\n",
710            filename, fmt_name, size_buf,
711            (total_sectors * 512),
712            dsize_buf);
713     if (bdrv_is_encrypted(bs))
714         printf("encrypted: yes\n");
715     if (bdrv_get_info(bs, &bdi) >= 0) {
716         if (bdi.cluster_size != 0)
717             printf("cluster_size: %d\n", bdi.cluster_size);
718     }
719     bdrv_get_backing_filename(bs, backing_filename, sizeof(backing_filename));
720     if (backing_filename[0] != '\0') {
721         path_combine(backing_filename2, sizeof(backing_filename2),
722                      filename, backing_filename);
723         printf("backing file: %s (actual path: %s)\n",
724                backing_filename,
725                backing_filename2);
726     }
727     dump_snapshots(bs);
728     bdrv_delete(bs);
729     return 0;
730 }
731
732 int main(int argc, char **argv)
733 {
734     const char *cmd;
735
736     bdrv_init();
737     if (argc < 2)
738         help();
739     cmd = argv[1];
740     optind++;
741     if (!strcmp(cmd, "create")) {
742         img_create(argc, argv);
743     } else if (!strcmp(cmd, "commit")) {
744         img_commit(argc, argv);
745     } else if (!strcmp(cmd, "convert")) {
746         img_convert(argc, argv);
747     } else if (!strcmp(cmd, "info")) {
748         img_info(argc, argv);
749     } else {
750         help();
751     }
752     return 0;
753 }