btrfs-progs: subvol_uuid_search: return error encoded pointer
[platform/upstream/btrfs-progs.git] / cmds-send.c
1 /*
2  * Copyright (C) 2012 Alexander Block.  All rights reserved.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public
6  * License v2 as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public
14  * License along with this program; if not, write to the
15  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
16  * Boston, MA 021110-1307, USA.
17  */
18
19
20 #include "kerncompat.h"
21
22 #include <unistd.h>
23 #include <stdint.h>
24 #include <dirent.h>
25 #include <fcntl.h>
26 #include <pthread.h>
27 #include <math.h>
28 #include <sys/stat.h>
29 #include <sys/types.h>
30 #include <sys/ioctl.h>
31 #include <libgen.h>
32 #include <mntent.h>
33 #include <assert.h>
34 #include <getopt.h>
35 #include <uuid/uuid.h>
36 #include <limits.h>
37
38 #include "ctree.h"
39 #include "ioctl.h"
40 #include "commands.h"
41 #include "list.h"
42 #include "utils.h"
43
44 #include "send.h"
45 #include "send-utils.h"
46
47 #define SEND_BUFFER_SIZE        (64 * 1024)
48
49 /*
50  * Default is 1 for historical reasons, changing may break scripts that expect
51  * the 'At subvol' message.
52  */
53 static int g_verbose = 1;
54
55 struct btrfs_send {
56         int send_fd;
57         int dump_fd;
58         int mnt_fd;
59
60         u64 *clone_sources;
61         u64 clone_sources_count;
62
63         char *root_path;
64         struct subvol_uuid_search sus;
65 };
66
67 static int get_root_id(struct btrfs_send *sctx, const char *path, u64 *root_id)
68 {
69         struct subvol_info *si;
70
71         si = subvol_uuid_search(&sctx->sus, 0, NULL, 0, path,
72                         subvol_search_by_path);
73         if (IS_ERR(si))
74                 return PTR_ERR(si);
75         *root_id = si->root_id;
76         free(si->path);
77         free(si);
78         return 0;
79 }
80
81 static struct subvol_info *get_parent(struct btrfs_send *sctx, u64 root_id)
82 {
83         struct subvol_info *si_tmp;
84         struct subvol_info *si;
85
86         si_tmp = subvol_uuid_search(&sctx->sus, root_id, NULL, 0, NULL,
87                         subvol_search_by_root_id);
88         if (IS_ERR(si_tmp))
89                 return si_tmp;
90
91         si = subvol_uuid_search(&sctx->sus, 0, si_tmp->parent_uuid, 0, NULL,
92                         subvol_search_by_uuid);
93         free(si_tmp->path);
94         free(si_tmp);
95         return si;
96 }
97
98 static int find_good_parent(struct btrfs_send *sctx, u64 root_id, u64 *found)
99 {
100         int ret;
101         struct subvol_info *parent = NULL;
102         struct subvol_info *parent2 = NULL;
103         struct subvol_info *best_parent = NULL;
104         u64 best_diff = (u64)-1;
105         int i;
106
107         parent = get_parent(sctx, root_id);
108         if (IS_ERR(parent)) {
109                 ret = PTR_ERR(parent);
110                 goto out;
111         }
112
113         for (i = 0; i < sctx->clone_sources_count; i++) {
114                 if (sctx->clone_sources[i] == parent->root_id) {
115                         best_parent = parent;
116                         parent = NULL;
117                         goto out_found;
118                 }
119         }
120
121         for (i = 0; i < sctx->clone_sources_count; i++) {
122                 s64 tmp;
123
124                 parent2 = get_parent(sctx, sctx->clone_sources[i]);
125                 if (IS_ERR(parent2))
126                         continue;
127                 if (parent2->root_id != parent->root_id) {
128                         free(parent2->path);
129                         free(parent2);
130                         parent2 = NULL;
131                         continue;
132                 }
133
134                 free(parent2->path);
135                 free(parent2);
136                 parent2 = subvol_uuid_search(&sctx->sus,
137                                 sctx->clone_sources[i], NULL, 0, NULL,
138                                 subvol_search_by_root_id);
139                 if (IS_ERR(parent2)) {
140                         ret = PTR_ERR(parent2);
141                         goto out;
142                 }
143                 tmp = parent2->ctransid - parent->ctransid;
144                 if (tmp < 0)
145                         tmp = -tmp;
146                 if (tmp < best_diff) {
147                         if (best_parent) {
148                                 free(best_parent->path);
149                                 free(best_parent);
150                         }
151                         best_parent = parent2;
152                         parent2 = NULL;
153                         best_diff = tmp;
154                 } else {
155                         free(parent2->path);
156                         free(parent2);
157                         parent2 = NULL;
158                 }
159         }
160
161         if (!best_parent) {
162                 ret = -ENOENT;
163                 goto out;
164         }
165
166 out_found:
167         *found = best_parent->root_id;
168         ret = 0;
169
170 out:
171         if (parent) {
172                 free(parent->path);
173                 free(parent);
174         }
175         if (best_parent) {
176                 free(best_parent->path);
177                 free(best_parent);
178         }
179         return ret;
180 }
181
182 static int add_clone_source(struct btrfs_send *sctx, u64 root_id)
183 {
184         void *tmp;
185
186         tmp = sctx->clone_sources;
187         sctx->clone_sources = realloc(sctx->clone_sources,
188                 sizeof(*sctx->clone_sources) * (sctx->clone_sources_count + 1));
189
190         if (!sctx->clone_sources) {
191                 free(tmp);
192                 return -ENOMEM;
193         }
194         sctx->clone_sources[sctx->clone_sources_count++] = root_id;
195
196         return 0;
197 }
198
199 #if 0
200 static int write_buf(int fd, const char *buf, size_t size)
201 {
202         int ret;
203         size_t pos = 0;
204
205         while (pos < size) {
206                 ssize_t wbytes;
207
208                 wbytes = write(fd, buf + pos, size - pos);
209                 if (wbytes < 0) {
210                         ret = -errno;
211                         error("failed to dump stream: %s", strerror(-ret));
212                         goto out;
213                 }
214                 if (!wbytes) {
215                         ret = -EIO;
216                         error("failed to dump stream: %s", strerror(-ret));
217                         goto out;
218                 }
219                 pos += wbytes;
220         }
221         ret = 0;
222
223 out:
224         return ret;
225 }
226
227 static void* read_sent_data_copy(void *arg)
228 {
229         int ret;
230         struct btrfs_send *sctx = (struct btrfs_send*)arg;
231         char buf[SEND_BUFFER_SIZE];
232
233         while (1) {
234                 ssize_t rbytes;
235
236                 rbytes = read(sctx->send_fd, buf, sizeof(buf));
237                 if (rbytes < 0) {
238                         ret = -errno;
239                         error("failed to read stream from kernel: %s",
240                                 strerror(-ret));
241                         goto out;
242                 }
243                 if (!rbytes) {
244                         ret = 0;
245                         goto out;
246                 }
247                 ret = write_buf(sctx->dump_fd, buf, rbytes);
248                 if (ret < 0)
249                         goto out;
250         }
251
252 out:
253         if (ret < 0)
254                 exit(-ret);
255
256         return ERR_PTR(ret);
257 }
258 #endif
259
260 static void *read_sent_data(void *arg)
261 {
262         int ret;
263         struct btrfs_send *sctx = (struct btrfs_send*)arg;
264
265         while (1) {
266                 ssize_t sbytes;
267
268                 /* Source is a pipe, output is either file or stdout */
269                 sbytes = splice(sctx->send_fd, NULL, sctx->dump_fd,
270                                 NULL, SEND_BUFFER_SIZE, SPLICE_F_MORE);
271                 if (sbytes < 0) {
272                         ret = -errno;
273                         error("failed to read stream from kernel: %s",
274                                 strerror(-ret));
275                         goto out;
276                 }
277                 if (!sbytes) {
278                         ret = 0;
279                         goto out;
280                 }
281         }
282
283 out:
284         if (ret < 0)
285                 exit(-ret);
286
287         return ERR_PTR(ret);
288 }
289
290 static int do_send(struct btrfs_send *send, u64 parent_root_id,
291                    int is_first_subvol, int is_last_subvol, const char *subvol,
292                    u64 flags)
293 {
294         int ret;
295         pthread_t t_read;
296         struct btrfs_ioctl_send_args io_send;
297         void *t_err = NULL;
298         int subvol_fd = -1;
299         int pipefd[2] = {-1, -1};
300
301         subvol_fd = openat(send->mnt_fd, subvol, O_RDONLY | O_NOATIME);
302         if (subvol_fd < 0) {
303                 ret = -errno;
304                 error("cannot open %s: %s", subvol, strerror(-ret));
305                 goto out;
306         }
307
308         ret = pipe(pipefd);
309         if (ret < 0) {
310                 ret = -errno;
311                 error("pipe failed: %s", strerror(-ret));
312                 goto out;
313         }
314
315         memset(&io_send, 0, sizeof(io_send));
316         io_send.send_fd = pipefd[1];
317         send->send_fd = pipefd[0];
318
319         if (!ret)
320                 ret = pthread_create(&t_read, NULL, read_sent_data, send);
321         if (ret) {
322                 ret = -ret;
323                 error("thread setup failed: %s", strerror(-ret));
324                 goto out;
325         }
326
327         io_send.flags = flags;
328         io_send.clone_sources = (__u64*)send->clone_sources;
329         io_send.clone_sources_count = send->clone_sources_count;
330         io_send.parent_root = parent_root_id;
331         if (!is_first_subvol)
332                 io_send.flags |= BTRFS_SEND_FLAG_OMIT_STREAM_HEADER;
333         if (!is_last_subvol)
334                 io_send.flags |= BTRFS_SEND_FLAG_OMIT_END_CMD;
335         ret = ioctl(subvol_fd, BTRFS_IOC_SEND, &io_send);
336         if (ret < 0) {
337                 ret = -errno;
338                 error("send ioctl failed with %d: %s", ret, strerror(-ret));
339                 if (ret == -EINVAL && (!is_first_subvol || !is_last_subvol))
340                         fprintf(stderr,
341                                 "Try upgrading your kernel or don't use -e.\n");
342                 goto out;
343         }
344         if (g_verbose > 1)
345                 fprintf(stderr, "BTRFS_IOC_SEND returned %d\n", ret);
346
347         if (g_verbose > 1)
348                 fprintf(stderr, "joining genl thread\n");
349
350         close(pipefd[1]);
351         pipefd[1] = -1;
352
353         ret = pthread_join(t_read, &t_err);
354         if (ret) {
355                 ret = -ret;
356                 error("pthread_join failed: %s", strerror(-ret));
357                 goto out;
358         }
359         if (t_err) {
360                 ret = (long int)t_err;
361                 error("failed to process send stream, ret=%ld (%s)",
362                                 (long int)t_err, strerror(-ret));
363                 goto out;
364         }
365
366         ret = 0;
367
368 out:
369         if (subvol_fd != -1)
370                 close(subvol_fd);
371         if (pipefd[0] != -1)
372                 close(pipefd[0]);
373         if (pipefd[1] != -1)
374                 close(pipefd[1]);
375         return ret;
376 }
377
378 static int init_root_path(struct btrfs_send *sctx, const char *subvol)
379 {
380         int ret = 0;
381
382         if (sctx->root_path)
383                 goto out;
384
385         ret = find_mount_root(subvol, &sctx->root_path);
386         if (ret < 0) {
387                 error("failed to determine mount point for %s: %s",
388                         subvol, strerror(-ret));
389                 ret = -EINVAL;
390                 goto out;
391         }
392         if (ret > 0) {
393                 error("%s doesn't belong to btrfs mount point", subvol);
394                 ret = -EINVAL;
395                 goto out;
396         }
397
398         sctx->mnt_fd = open(sctx->root_path, O_RDONLY | O_NOATIME);
399         if (sctx->mnt_fd < 0) {
400                 ret = -errno;
401                 error("cannot open '%s': %s", sctx->root_path, strerror(-ret));
402                 goto out;
403         }
404
405         ret = subvol_uuid_search_init(sctx->mnt_fd, &sctx->sus);
406         if (ret < 0) {
407                 error("failed to initialize subvol search: %s",
408                         strerror(-ret));
409                 goto out;
410         }
411
412 out:
413         return ret;
414
415 }
416
417 static int is_subvol_ro(struct btrfs_send *sctx, const char *subvol)
418 {
419         int ret;
420         u64 flags;
421         int fd = -1;
422
423         fd = openat(sctx->mnt_fd, subvol, O_RDONLY | O_NOATIME);
424         if (fd < 0) {
425                 ret = -errno;
426                 error("cannot open %s: %s", subvol, strerror(-ret));
427                 goto out;
428         }
429
430         ret = ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags);
431         if (ret < 0) {
432                 ret = -errno;
433                 error("failed to get flags for subvolume %s: %s",
434                         subvol, strerror(-ret));
435                 goto out;
436         }
437
438         if (flags & BTRFS_SUBVOL_RDONLY)
439                 ret = 1;
440         else
441                 ret = 0;
442
443 out:
444         if (fd != -1)
445                 close(fd);
446
447         return ret;
448 }
449
450 static int set_root_info(struct btrfs_send *sctx, const char *subvol,
451                 u64 *root_id)
452 {
453         int ret;
454
455         ret = init_root_path(sctx, subvol);
456         if (ret < 0)
457                 goto out;
458
459         ret = get_root_id(sctx, subvol_strip_mountpoint(sctx->root_path, subvol),
460                 root_id);
461         if (ret < 0) {
462                 error("cannot resolve rootid for %s", subvol);
463                 goto out;
464         }
465
466 out:
467         return ret;
468 }
469
470 static void free_send_info(struct btrfs_send *sctx)
471 {
472         if (sctx->mnt_fd >= 0) {
473                 close(sctx->mnt_fd);
474                 sctx->mnt_fd = -1;
475         }
476         free(sctx->root_path);
477         sctx->root_path = NULL;
478         subvol_uuid_search_finit(&sctx->sus);
479 }
480
481 int cmd_send(int argc, char **argv)
482 {
483         char *subvol = NULL;
484         int ret;
485         char outname[PATH_MAX];
486         struct btrfs_send send;
487         u32 i;
488         char *mount_root = NULL;
489         char *snapshot_parent = NULL;
490         u64 root_id = 0;
491         u64 parent_root_id = 0;
492         int full_send = 1;
493         int new_end_cmd_semantic = 0;
494         u64 send_flags = 0;
495
496         memset(&send, 0, sizeof(send));
497         send.dump_fd = fileno(stdout);
498         outname[0] = 0;
499
500         while (1) {
501                 enum { GETOPT_VAL_SEND_NO_DATA = 256 };
502                 static const struct option long_options[] = {
503                         { "verbose", no_argument, NULL, 'v' },
504                         { "quiet", no_argument, NULL, 'q' },
505                         { "no-data", no_argument, NULL, GETOPT_VAL_SEND_NO_DATA }
506                 };
507                 int c = getopt_long(argc, argv, "vqec:f:i:p:", long_options, NULL);
508
509                 if (c < 0)
510                         break;
511
512                 switch (c) {
513                 case 'v':
514                         g_verbose++;
515                         break;
516                 case 'q':
517                         g_verbose = 0;
518                         break;
519                 case 'e':
520                         new_end_cmd_semantic = 1;
521                         break;
522                 case 'c':
523                         subvol = realpath(optarg, NULL);
524                         if (!subvol) {
525                                 ret = -errno;
526                                 error("realpath %s failed: %s\n", optarg, strerror(-ret));
527                                 goto out;
528                         }
529
530                         ret = set_root_info(&send, subvol, &root_id);
531                         if (ret < 0)
532                                 goto out;
533
534                         ret = is_subvol_ro(&send, subvol);
535                         if (ret < 0)
536                                 goto out;
537                         if (!ret) {
538                                 ret = -EINVAL;
539                                 error("cloned subvolume %s is not read-only", subvol);
540                                 goto out;
541                         }
542
543                         ret = add_clone_source(&send, root_id);
544                         if (ret < 0) {
545                                 error("cannot add clone source: %s", strerror(-ret));
546                                 goto out;
547                         }
548                         free(subvol);
549                         subvol = NULL;
550                         free_send_info(&send);
551                         full_send = 0;
552                         break;
553                 case 'f':
554                         if (arg_copy_path(outname, optarg, sizeof(outname))) {
555                                 error("output file path too long (%zu)", strlen(optarg));
556                                 ret = 1;
557                                 goto out;
558                         }
559                         break;
560                 case 'p':
561                         if (snapshot_parent) {
562                                 error("you cannot have more than one parent (-p)");
563                                 ret = 1;
564                                 goto out;
565                         }
566                         snapshot_parent = realpath(optarg, NULL);
567                         if (!snapshot_parent) {
568                                 ret = -errno;
569                                 error("realpath %s failed: %s", optarg, strerror(-ret));
570                                 goto out;
571                         }
572
573                         ret = is_subvol_ro(&send, snapshot_parent);
574                         if (ret < 0)
575                                 goto out;
576                         if (!ret) {
577                                 ret = -EINVAL;
578                                 error("parent subvolume %s is not read-only",
579                                         snapshot_parent);
580                                 goto out;
581                         }
582
583                         full_send = 0;
584                         break;
585                 case 'i':
586                         error("option -i was removed, use -c instead");
587                         ret = 1;
588                         goto out;
589                 case GETOPT_VAL_SEND_NO_DATA:
590                         send_flags |= BTRFS_SEND_FLAG_NO_FILE_DATA;
591                         break;
592                 case '?':
593                 default:
594                         error("send arguments invalid");
595                         ret = 1;
596                         goto out;
597                 }
598         }
599
600         if (check_argc_min(argc - optind, 1))
601                 usage(cmd_send_usage);
602
603         if (outname[0]) {
604                 int tmpfd;
605
606                 /*
607                  * Try to use an existing file first. Even if send runs as
608                  * root, it might not have permissions to create file (eg. on a
609                  * NFS) but it should still be able to use a pre-created file.
610                  */
611                 tmpfd = open(outname, O_WRONLY | O_TRUNC);
612                 if (tmpfd < 0) {
613                         if (errno == ENOENT)
614                                 tmpfd = open(outname,
615                                         O_CREAT | O_WRONLY | O_TRUNC, 0600);
616                 }
617                 send.dump_fd = tmpfd;
618                 if (send.dump_fd == -1) {
619                         ret = -errno;
620                         error("cannot create '%s': %s", outname, strerror(-ret));
621                         goto out;
622                 }
623         }
624
625         if (isatty(send.dump_fd)) {
626                 error(
627             "not dumping send stream into a terminal, redirect it into a file");
628                 ret = 1;
629                 goto out;
630         }
631
632         /* use first send subvol to determine mount_root */
633         subvol = realpath(argv[optind], NULL);
634         if (!subvol) {
635                 ret = -errno;
636                 error("unable to resolve %s", argv[optind]);
637                 goto out;
638         }
639
640         ret = init_root_path(&send, subvol);
641         if (ret < 0)
642                 goto out;
643
644         if (snapshot_parent != NULL) {
645                 ret = get_root_id(&send,
646                         subvol_strip_mountpoint(send.root_path, snapshot_parent),
647                         &parent_root_id);
648                 if (ret < 0) {
649                         error("could not resolve rootid for %s", snapshot_parent);
650                         goto out;
651                 }
652
653                 ret = add_clone_source(&send, parent_root_id);
654                 if (ret < 0) {
655                         error("cannot add clone source: %s", strerror(-ret));
656                         goto out;
657                 }
658         }
659
660         for (i = optind; i < argc; i++) {
661                 free(subvol);
662                 subvol = realpath(argv[i], NULL);
663                 if (!subvol) {
664                         ret = -errno;
665                         error("unable to resolve %s", argv[i]);
666                         goto out;
667                 }
668
669                 ret = find_mount_root(subvol, &mount_root);
670                 if (ret < 0) {
671                         error("find_mount_root failed on %s: %s", subvol,
672                                 strerror(-ret));
673                         goto out;
674                 }
675                 if (ret > 0) {
676                         error("%s does not belong to btrfs mount point",
677                                 subvol);
678                         ret = -EINVAL;
679                         goto out;
680                 }
681                 if (strcmp(send.root_path, mount_root) != 0) {
682                         ret = -EINVAL;
683                         error("all subvolumes must be from the same filesystem");
684                         goto out;
685                 }
686                 free(mount_root);
687
688                 ret = is_subvol_ro(&send, subvol);
689                 if (ret < 0)
690                         goto out;
691                 if (!ret) {
692                         ret = -EINVAL;
693                         error("subvolume %s is not read-only", subvol);
694                         goto out;
695                 }
696         }
697
698         if ((send_flags & BTRFS_SEND_FLAG_NO_FILE_DATA) && g_verbose > 1)
699                 if (g_verbose > 1)
700                         fprintf(stderr, "Mode NO_FILE_DATA enabled\n");
701
702         for (i = optind; i < argc; i++) {
703                 int is_first_subvol;
704                 int is_last_subvol;
705
706                 free(subvol);
707                 subvol = argv[i];
708
709                 if (g_verbose > 0)
710                         fprintf(stderr, "At subvol %s\n", subvol);
711
712                 subvol = realpath(subvol, NULL);
713                 if (!subvol) {
714                         ret = -errno;
715                         error("realpath %s failed: %s", argv[i], strerror(-ret));
716                         goto out;
717                 }
718
719                 if (!full_send && !snapshot_parent) {
720                         ret = set_root_info(&send, subvol, &root_id);
721                         if (ret < 0)
722                                 goto out;
723
724                         ret = find_good_parent(&send, root_id, &parent_root_id);
725                         if (ret < 0) {
726                                 error("parent determination failed for %lld",
727                                         root_id);
728                                 goto out;
729                         }
730                 }
731
732                 if (new_end_cmd_semantic) {
733                         /* require new kernel */
734                         is_first_subvol = (i == optind);
735                         is_last_subvol = (i == argc - 1);
736                 } else {
737                         /* be compatible to old and new kernel */
738                         is_first_subvol = 1;
739                         is_last_subvol = 1;
740                 }
741                 ret = do_send(&send, parent_root_id, is_first_subvol,
742                               is_last_subvol, subvol, send_flags);
743                 if (ret < 0)
744                         goto out;
745
746                 if (!full_send && !snapshot_parent) {
747                         /* done with this subvol, so add it to the clone sources */
748                         ret = add_clone_source(&send, root_id);
749                         if (ret < 0) {
750                                 error("cannot add clone source: %s", strerror(-ret));
751                                 goto out;
752                         }
753                         free_send_info(&send);
754                 }
755         }
756
757         ret = 0;
758
759 out:
760         free(subvol);
761         free(snapshot_parent);
762         free(send.clone_sources);
763         free_send_info(&send);
764         return !!ret;
765 }
766
767 const char * const cmd_send_usage[] = {
768         "btrfs send [-ve] [-p <parent>] [-c <clone-src>] [-f <outfile>] <subvol> [<subvol>...]",
769         "Send the subvolume(s) to stdout.",
770         "Sends the subvolume(s) specified by <subvol> to stdout.",
771         "<subvol> should be read-only here.",
772         "By default, this will send the whole subvolume. To do an incremental",
773         "send, use '-p <parent>'. If you want to allow btrfs to clone from",
774         "any additional local snapshots, use '-c <clone-src>' (multiple times",
775         "where applicable). You must not specify clone sources unless you",
776         "guarantee that these snapshots are exactly in the same state on both",
777         "sides, the sender and the receiver. It is allowed to omit the",
778         "'-p <parent>' option when '-c <clone-src>' options are given, in",
779         "which case 'btrfs send' will determine a suitable parent among the",
780         "clone sources itself.",
781         "\n",
782         "-e               If sending multiple subvols at once, use the new",
783         "                 format and omit the end-cmd between the subvols.",
784         "-p <parent>      Send an incremental stream from <parent> to",
785         "                 <subvol>.",
786         "-c <clone-src>   Use this snapshot as a clone source for an ",
787         "                 incremental send (multiple allowed)",
788         "-f <outfile>     Output is normally written to stdout. To write to",
789         "                 a file, use this option. An alternative would be to",
790         "                 use pipes.",
791         "--no-data        send in NO_FILE_DATA mode, Note: the output stream",
792         "                 does not contain any file data and thus cannot be used",
793         "                 to transfer changes. This mode is faster and useful to",
794         "                 show the differences in metadata.",
795         "-v|--verbose     enable verbose output to stderr, each occurrence of",
796         "                 this option increases verbosity",
797         "-q|--quiet       suppress all messages, except errors",
798         NULL
799 };