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