]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/gvinum/gvinum.c
rtld-elf: link udivmoddi4 from compiler_rt
[FreeBSD/FreeBSD.git] / sbin / gvinum / gvinum.c
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  *  Copyright (c) 2004 Lukas Ertl
5  *  Copyright (c) 2005 Chris Jones
6  *  Copyright (c) 2007 Ulf Lilleengen
7  *  All rights reserved.
8  *
9  * Portions of this software were developed for the FreeBSD Project
10  * by Chris Jones thanks to the support of Google's Summer of Code
11  * program and mentoring by Lukas Ertl.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  * 1. Redistributions of source code must retain the above copyright
17  *    notice, this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above copyright
19  *    notice, this list of conditions and the following disclaimer in the
20  *    documentation and/or other materials provided with the distribution.
21  *
22  * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  *
34  * $FreeBSD$
35  */
36
37 #include <sys/param.h>
38 #include <sys/linker.h>
39 #include <sys/lock.h>
40 #include <sys/module.h>
41 #include <sys/mutex.h>
42 #include <sys/queue.h>
43 #include <sys/utsname.h>
44
45 #include <geom/vinum/geom_vinum_var.h>
46 #include <geom/vinum/geom_vinum_share.h>
47
48 #include <ctype.h>
49 #include <err.h>
50 #include <errno.h>
51 #include <libgeom.h>
52 #include <stdint.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <paths.h>
57 #include <readline/readline.h>
58 #include <readline/history.h>
59 #include <unistd.h>
60
61 #include "gvinum.h"
62
63 static void gvinum_attach(int, char * const *);
64 static void gvinum_concat(int, char * const *);
65 static void gvinum_create(int, char * const *);
66 static void gvinum_detach(int, char * const *);
67 static void gvinum_grow(int, char * const *);
68 static void gvinum_help(void);
69 static void gvinum_list(int, char * const *);
70 static void gvinum_move(int, char * const *);
71 static void gvinum_mirror(int, char * const *);
72 static void gvinum_parityop(int, char * const * , int);
73 static void gvinum_printconfig(int, char * const *);
74 static void gvinum_raid5(int, char * const *);
75 static void gvinum_rename(int, char * const *);
76 static void gvinum_resetconfig(int, char * const *);
77 static void gvinum_rm(int, char * const *);
78 static void gvinum_saveconfig(void);
79 static void gvinum_setstate(int, char * const *);
80 static void gvinum_start(int, char * const *);
81 static void gvinum_stop(int, char * const *);
82 static void gvinum_stripe(int, char * const *);
83 static void parseline(int, char * const *);
84 static void printconfig(FILE *, const char *);
85
86 static char *create_drive(const char *);
87 static void create_volume(int, char * const * , const char *);
88 static char *find_name(const char *, int, int);
89 static const char *find_pattern(char *, const char *);
90 static void copy_device(struct gv_drive *, const char *);
91 #define find_drive()                                                    \
92     find_name("gvinumdrive", GV_TYPE_DRIVE, GV_MAXDRIVENAME)
93
94 int
95 main(int argc, char **argv)
96 {
97         int tokens;
98         char buffer[BUFSIZ], *inputline, *token[GV_MAXARGS];
99
100         /* Load the module if necessary. */
101         if (modfind(GVINUMMOD) < 0) {
102                 if (kldload(GVINUMKLD) < 0 && modfind(GVINUMMOD) < 0)
103                         err(1, GVINUMKLD ": Kernel module not available");
104         }
105
106         /* Arguments given on the command line. */
107         if (argc > 1) {
108                 argc--;
109                 argv++;
110                 parseline(argc, argv);
111
112         /* Interactive mode. */
113         } else {
114                 for (;;) {
115                         inputline = readline("gvinum -> ");
116                         if (inputline == NULL) {
117                                 if (ferror(stdin)) {
118                                         err(1, "can't read input");
119                                 } else {
120                                         printf("\n");
121                                         exit(0);
122                                 }
123                         } else if (*inputline) {
124                                 add_history(inputline);
125                                 strcpy(buffer, inputline);
126                                 free(inputline);
127                                 tokens = gv_tokenize(buffer, token, GV_MAXARGS);
128                                 if (tokens)
129                                         parseline(tokens, token);
130                         }
131                 }
132         }
133         exit(0);
134 }
135
136 /* Attach a plex to a volume or a subdisk to a plex. */
137 static void
138 gvinum_attach(int argc, char * const *argv)
139 {
140         struct gctl_req *req;
141         const char *errstr;
142         int rename;
143         off_t offset;
144
145         rename = 0;
146         offset = -1;
147         if (argc < 3) {
148                 warnx("usage:\tattach <subdisk> <plex> [rename] "
149                     "[<plexoffset>]\n"
150                     "\tattach <plex> <volume> [rename]");
151                 return;
152         }
153         if (argc > 3) {
154                 if (!strcmp(argv[3], "rename")) {
155                         rename = 1;
156                         if (argc == 5)
157                                 offset = strtol(argv[4], NULL, 0);
158                 } else
159                         offset = strtol(argv[3], NULL, 0);
160         }
161         req = gctl_get_handle();
162         gctl_ro_param(req, "class", -1, "VINUM");
163         gctl_ro_param(req, "verb", -1, "attach");
164         gctl_ro_param(req, "child", -1, argv[1]);
165         gctl_ro_param(req, "parent", -1, argv[2]);
166         gctl_ro_param(req, "offset", sizeof(off_t), &offset);
167         gctl_ro_param(req, "rename", sizeof(int), &rename);
168         errstr = gctl_issue(req);
169         if (errstr != NULL)
170                 warnx("attach failed: %s", errstr);
171         gctl_free(req);
172 }
173
174 static void
175 gvinum_create(int argc, char * const *argv)
176 {
177         struct gctl_req *req;
178         struct gv_drive *d;
179         struct gv_plex *p;
180         struct gv_sd *s;
181         struct gv_volume *v;
182         FILE *tmp;
183         int drives, errors, fd, flags, i, line, plexes, plex_in_volume;
184         int sd_in_plex, status, subdisks, tokens, undeffd, volumes;
185         const char *errstr;
186         char buf[BUFSIZ], buf1[BUFSIZ], commandline[BUFSIZ], *sdname;
187         const char *ed;
188         char original[BUFSIZ], tmpfile[20], *token[GV_MAXARGS];
189         char plex[GV_MAXPLEXNAME], volume[GV_MAXVOLNAME];
190
191         tmp = NULL;
192         flags = 0;
193         for (i = 1; i < argc; i++) {
194                 /* Force flag used to ignore already created drives. */
195                 if (!strcmp(argv[i], "-f")) {
196                         flags |= GV_FLAG_F;
197                 /* Else it must be a file. */
198                 } else {
199                         if ((tmp = fopen(argv[i], "r")) == NULL) {
200                                 warn("can't open '%s' for reading", argv[i]);
201                                 return;
202                         }
203                 }       
204         }
205
206         /* We didn't get a file. */
207         if (tmp == NULL) {
208                 snprintf(tmpfile, sizeof(tmpfile), "/tmp/gvinum.XXXXXX");
209                 
210                 if ((fd = mkstemp(tmpfile)) == -1) {
211                         warn("temporary file not accessible");
212                         return;
213                 }
214                 if ((tmp = fdopen(fd, "w")) == NULL) {
215                         warn("can't open '%s' for writing", tmpfile);
216                         return;
217                 }
218                 printconfig(tmp, "# ");
219                 fclose(tmp);
220                 
221                 ed = getenv("EDITOR");
222                 if (ed == NULL)
223                         ed = _PATH_VI;
224                 
225                 snprintf(commandline, sizeof(commandline), "%s %s", ed,
226                     tmpfile);
227                 status = system(commandline);
228                 if (status != 0) {
229                         warn("couldn't exec %s; status: %d", ed, status);
230                         return;
231                 }
232                 
233                 if ((tmp = fopen(tmpfile, "r")) == NULL) {
234                         warn("can't open '%s' for reading", tmpfile);
235                         return;
236                 }
237         }
238
239         req = gctl_get_handle();
240         gctl_ro_param(req, "class", -1, "VINUM");
241         gctl_ro_param(req, "verb", -1, "create");
242         gctl_ro_param(req, "flags", sizeof(int), &flags);
243
244         drives = volumes = plexes = subdisks = 0;
245         plex_in_volume = sd_in_plex = undeffd = 0;
246         plex[0] = '\0';
247         errors = 0;
248         line = 1;
249         while ((fgets(buf, BUFSIZ, tmp)) != NULL) {
250
251                 /* Skip empty lines and comments. */
252                 if (*buf == '\0' || *buf == '#') {
253                         line++;
254                         continue;
255                 }
256
257                 /* Kill off the newline. */
258                 buf[strlen(buf) - 1] = '\0';
259
260                 /*
261                  * Copy the original input line in case we need it for error
262                  * output.
263                  */
264                 strlcpy(original, buf, sizeof(original));
265
266                 tokens = gv_tokenize(buf, token, GV_MAXARGS);
267                 if (tokens <= 0) {
268                         line++;
269                         continue;
270                 }
271
272                 /* Volume definition. */
273                 if (!strcmp(token[0], "volume")) {
274                         v = gv_new_volume(tokens, token);
275                         if (v == NULL) {
276                                 warnx("line %d: invalid volume definition",
277                                     line);
278                                 warnx("line %d: '%s'", line, original);
279                                 errors++;
280                                 line++;
281                                 continue;
282                         }
283
284                         /* Reset plex count for this volume. */
285                         plex_in_volume = 0;
286
287                         /*
288                          * Set default volume name for following plex
289                          * definitions.
290                          */
291                         strlcpy(volume, v->name, sizeof(volume));
292
293                         snprintf(buf1, sizeof(buf1), "volume%d", volumes);
294                         gctl_ro_param(req, buf1, sizeof(*v), v);
295                         volumes++;
296
297                 /* Plex definition. */
298                 } else if (!strcmp(token[0], "plex")) {
299                         p = gv_new_plex(tokens, token);
300                         if (p == NULL) {
301                                 warnx("line %d: invalid plex definition", line);
302                                 warnx("line %d: '%s'", line, original);
303                                 errors++;
304                                 line++;
305                                 continue;
306                         }
307
308                         /* Reset subdisk count for this plex. */
309                         sd_in_plex = 0;
310
311                         /* Default name. */
312                         if (strlen(p->name) == 0) {
313                                 snprintf(p->name, sizeof(p->name), "%s.p%d",
314                                     volume, plex_in_volume++);
315                         }
316
317                         /* Default volume. */
318                         if (strlen(p->volume) == 0) {
319                                 snprintf(p->volume, sizeof(p->volume), "%s",
320                                     volume);
321                         }
322
323                         /*
324                          * Set default plex name for following subdisk
325                          * definitions.
326                          */
327                         strlcpy(plex, p->name, sizeof(plex));
328
329                         snprintf(buf1, sizeof(buf1), "plex%d", plexes);
330                         gctl_ro_param(req, buf1, sizeof(*p), p);
331                         plexes++;
332
333                 /* Subdisk definition. */
334                 } else if (!strcmp(token[0], "sd")) {
335                         s = gv_new_sd(tokens, token);
336                         if (s == NULL) {
337                                 warnx("line %d: invalid subdisk "
338                                     "definition:", line);
339                                 warnx("line %d: '%s'", line, original);
340                                 errors++;
341                                 line++;
342                                 continue;
343                         }
344
345                         /* Default name. */
346                         if (strlen(s->name) == 0) {
347                                 if (strlen(plex) == 0) {
348                                         sdname = find_name("gvinumsubdisk.p",
349                                             GV_TYPE_SD, GV_MAXSDNAME);
350                                         snprintf(s->name, sizeof(s->name),
351                                             "%s.s%d", sdname, undeffd++);
352                                         free(sdname);
353                                 } else {
354                                         snprintf(s->name, sizeof(s->name),
355                                             "%s.s%d",plex, sd_in_plex++);
356                                 }
357                         }
358
359                         /* Default plex. */
360                         if (strlen(s->plex) == 0)
361                                 snprintf(s->plex, sizeof(s->plex), "%s", plex);
362
363                         snprintf(buf1, sizeof(buf1), "sd%d", subdisks);
364                         gctl_ro_param(req, buf1, sizeof(*s), s);
365                         subdisks++;
366
367                 /* Subdisk definition. */
368                 } else if (!strcmp(token[0], "drive")) {
369                         d = gv_new_drive(tokens, token);
370                         if (d == NULL) {
371                                 warnx("line %d: invalid drive definition:",
372                                     line);
373                                 warnx("line %d: '%s'", line, original);
374                                 errors++;
375                                 line++;
376                                 continue;
377                         }
378
379                         snprintf(buf1, sizeof(buf1), "drive%d", drives);
380                         gctl_ro_param(req, buf1, sizeof(*d), d);
381                         drives++;
382
383                 /* Everything else is bogus. */
384                 } else {
385                         warnx("line %d: invalid definition:", line);
386                         warnx("line %d: '%s'", line, original);
387                         errors++;
388                 }
389                 line++;
390         }
391
392         fclose(tmp);
393         unlink(tmpfile);
394
395         if (!errors && (volumes || plexes || subdisks || drives)) {
396                 gctl_ro_param(req, "volumes", sizeof(int), &volumes);
397                 gctl_ro_param(req, "plexes", sizeof(int), &plexes);
398                 gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
399                 gctl_ro_param(req, "drives", sizeof(int), &drives);
400                 errstr = gctl_issue(req);
401                 if (errstr != NULL)
402                         warnx("create failed: %s", errstr);
403         }
404         gctl_free(req);
405 }
406
407 /* Create a concatenated volume. */
408 static void
409 gvinum_concat(int argc, char * const *argv)
410 {
411
412         if (argc < 2) {
413                 warnx("usage:\tconcat [-fv] [-n name] drives\n");
414                 return;
415         }
416         create_volume(argc, argv, "concat");
417 }
418
419 /* Create a drive quick and dirty. */
420 static char *
421 create_drive(const char *device)
422 {
423         struct gv_drive *d;
424         struct gctl_req *req;
425         const char *errstr;
426         char *drivename, *dname;
427         int drives, i, flags, volumes, subdisks, plexes;
428         int found = 0;
429
430         flags = plexes = subdisks = volumes = 0;
431         drives = 1;
432         dname = NULL;
433
434         drivename = find_drive();
435         if (drivename == NULL)
436                 return (NULL);
437
438         req = gctl_get_handle();
439         gctl_ro_param(req, "class", -1, "VINUM");
440         gctl_ro_param(req, "verb", -1, "create");
441         d = gv_alloc_drive();
442         if (d == NULL)
443                 err(1, "unable to allocate for gv_drive object");
444
445         strlcpy(d->name, drivename, sizeof(d->name));
446         copy_device(d, device);
447         gctl_ro_param(req, "drive0", sizeof(*d), d);
448         gctl_ro_param(req, "flags", sizeof(int), &flags);
449         gctl_ro_param(req, "drives", sizeof(int), &drives);
450         gctl_ro_param(req, "volumes", sizeof(int), &volumes);
451         gctl_ro_param(req, "plexes", sizeof(int), &plexes);
452         gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
453         errstr = gctl_issue(req);
454         if (errstr != NULL) {
455                 warnx("error creating drive: %s", errstr);
456                 drivename = NULL;
457         } else {
458                 /* XXX: This is needed because we have to make sure the drives
459                  * are created before we return. */
460                 /* Loop until it's in the config. */
461                 for (i = 0; i < 100000; i++) {
462                         dname = find_name("gvinumdrive", GV_TYPE_DRIVE,
463                             GV_MAXDRIVENAME);
464                         /* If we got a different name, quit. */
465                         if (dname == NULL)
466                                 continue;
467                         if (strcmp(dname, drivename))
468                                 found = 1;
469                         free(dname);
470                         dname = NULL;
471                         if (found)
472                                 break;
473                         usleep(100000); /* Sleep for 0.1s */
474                 }
475                 if (found == 0) {
476                         warnx("error creating drive");
477                         drivename = NULL;
478                 }
479         }
480         gctl_free(req);
481         return (drivename);
482 }
483
484 /*
485  * General routine for creating a volume. Mainly for use by concat, mirror,
486  * raid5 and stripe commands.
487  */
488 static void
489 create_volume(int argc, char * const *argv, const char *verb)
490 {
491         struct gctl_req *req;
492         const char *errstr;
493         char buf[BUFSIZ], *drivename, *volname;
494         int drives, flags, i;
495         off_t stripesize;
496
497         flags = 0;
498         drives = 0;
499         volname = NULL;
500         stripesize = 262144;
501
502         /* XXX: Should we check for argument length? */
503
504         req = gctl_get_handle();
505         gctl_ro_param(req, "class", -1, "VINUM");
506
507         for (i = 1; i < argc; i++) {
508                 if (!strcmp(argv[i], "-f")) {
509                         flags |= GV_FLAG_F;
510                 } else if (!strcmp(argv[i], "-n")) {
511                         volname = argv[++i];
512                 } else if (!strcmp(argv[i], "-v")) {
513                         flags |= GV_FLAG_V;
514                 } else if (!strcmp(argv[i], "-s")) {
515                         flags |= GV_FLAG_S;
516                         if (!strcmp(verb, "raid5"))
517                                 stripesize = gv_sizespec(argv[++i]);
518                 } else {
519                         /* Assume it's a drive. */
520                         snprintf(buf, sizeof(buf), "drive%d", drives++);
521
522                         /* First we create the drive. */
523                         drivename = create_drive(argv[i]);
524                         if (drivename == NULL)
525                                 goto bad;
526                         /* Then we add it to the request. */
527                         gctl_ro_param(req, buf, -1, drivename);
528                 }
529         }
530
531         gctl_ro_param(req, "stripesize", sizeof(off_t), &stripesize);
532
533         /* Find a free volume name. */
534         if (volname == NULL)
535                 volname = find_name("gvinumvolume", GV_TYPE_VOL, GV_MAXVOLNAME);
536
537         /* Then we send a request to actually create the volumes. */
538         gctl_ro_param(req, "verb", -1, verb);
539         gctl_ro_param(req, "flags", sizeof(int), &flags);
540         gctl_ro_param(req, "drives", sizeof(int), &drives);
541         gctl_ro_param(req, "name", -1, volname);
542         errstr = gctl_issue(req);
543         if (errstr != NULL)
544                 warnx("creating %s volume failed: %s", verb, errstr);
545 bad:
546         gctl_free(req);
547 }
548
549 /* Parse a line of the config, return the word after <pattern>. */
550 static const char *
551 find_pattern(char *line, const char *pattern)
552 {
553         char *ptr;
554
555         ptr = strsep(&line, " ");
556         while (ptr != NULL) {
557                 if (!strcmp(ptr, pattern)) {
558                         /* Return the next. */
559                         ptr = strsep(&line, " ");
560                         return (ptr);
561                 }
562                 ptr = strsep(&line, " ");
563         }
564         return (NULL);
565 }
566
567 /* Find a free name for an object given a prefix. */
568 static char *
569 find_name(const char *prefix, int type, int namelen)
570 {
571         struct gctl_req *req;
572         char comment[1], buf[GV_CFG_LEN - 1], *sname, *ptr;
573         const char *errstr, *name;
574         int i, n, begin, len, conflict;
575         char line[1024];
576
577         comment[0] = '\0';
578
579         /* Find a name. Fetch out configuration first. */
580         req = gctl_get_handle();
581         gctl_ro_param(req, "class", -1, "VINUM");
582         gctl_ro_param(req, "verb", -1, "getconfig");
583         gctl_ro_param(req, "comment", -1, comment);
584         gctl_rw_param(req, "config", sizeof(buf), buf);
585         errstr = gctl_issue(req);
586         if (errstr != NULL) {
587                 warnx("can't get configuration: %s", errstr);
588                 return (NULL);
589         }
590         gctl_free(req);
591
592         begin = 0;
593         len = strlen(buf);
594         i = 0;
595         sname = malloc(namelen + 1);
596
597         /* XXX: Max object setting? */
598         for (n = 0; n < 10000; n++) {
599                 snprintf(sname, namelen, "%s%d", prefix, n);
600                 conflict = 0;
601                 begin = 0;
602                 /* Loop through the configuration line by line. */
603                 for (i = 0; i < len; i++) {
604                         if (buf[i] == '\n' || buf[i] == '\0') {
605                                 ptr = buf + begin;
606                                 strlcpy(line, ptr, (i - begin) + 1);
607                                 begin = i + 1;
608                                 switch (type) {
609                                 case GV_TYPE_DRIVE:
610                                         name = find_pattern(line, "drive");
611                                         break;
612                                 case GV_TYPE_VOL:
613                                         name = find_pattern(line, "volume");
614                                         break;
615                                 case GV_TYPE_PLEX:
616                                 case GV_TYPE_SD:
617                                         name = find_pattern(line, "name");
618                                         break;
619                                 default:
620                                         printf("Invalid type given\n");
621                                         continue;
622                                 }
623                                 if (name == NULL)
624                                         continue;
625                                 if (!strcmp(sname, name)) {
626                                         conflict = 1;
627                                         /* XXX: Could quit the loop earlier. */
628                                 }
629                         }
630                 }
631                 if (!conflict)
632                         return (sname);
633         }
634         free(sname);
635         return (NULL);
636 }
637
638 static void
639 copy_device(struct gv_drive *d, const char *device)
640 {
641
642         if (strncmp(device, "/dev/", 5) == 0)
643                 strlcpy(d->device, (device + 5), sizeof(d->device));
644         else
645                 strlcpy(d->device, device, sizeof(d->device));
646 }
647
648 /* Detach a plex or subdisk from its parent. */
649 static void
650 gvinum_detach(int argc, char * const *argv)
651 {
652         const char *errstr;
653         struct gctl_req *req;
654         int flags, i;
655
656         flags = 0;
657         optreset = 1;
658         optind = 1;
659         while ((i = getopt(argc, argv, "f")) != -1) {
660                 switch (i) {
661                 case 'f':
662                         flags |= GV_FLAG_F;
663                         break;
664                 default:
665                         warn("invalid flag: %c", i);
666                         return;
667                 }
668         }
669         argc -= optind;
670         argv += optind;
671         if (argc != 1) {
672                 warnx("usage: detach [-f] <subdisk> | <plex>");
673                 return;
674         }
675
676         req = gctl_get_handle();
677         gctl_ro_param(req, "class", -1, "VINUM");
678         gctl_ro_param(req, "verb", -1, "detach");
679         gctl_ro_param(req, "object", -1, argv[0]);
680         gctl_ro_param(req, "flags", sizeof(int), &flags);
681
682         errstr = gctl_issue(req);
683         if (errstr != NULL)
684                 warnx("detach failed: %s", errstr);
685         gctl_free(req);
686 }
687
688 static void
689 gvinum_help(void)
690 {
691
692         printf("COMMANDS\n"
693             "checkparity [-f] plex\n"
694             "        Check the parity blocks of a RAID-5 plex.\n"
695             "create [-f] description-file\n"
696             "        Create as per description-file or open editor.\n"
697             "attach plex volume [rename]\n"
698             "attach subdisk plex [offset] [rename]\n"
699             "        Attach a plex to a volume, or a subdisk to a plex\n"
700             "concat [-fv] [-n name] drives\n"
701             "        Create a concatenated volume from the specified drives.\n"
702             "detach [-f] [plex | subdisk]\n"
703             "        Detach a plex or a subdisk from the volume or plex to\n"
704             "        which it is attached.\n"
705             "grow plex drive\n"
706             "        Grow plex by creating a properly sized subdisk on drive\n"
707             "l | list [-r] [-v] [-V] [volume | plex | subdisk]\n"
708             "        List information about specified objects.\n"
709             "ld [-r] [-v] [-V] [volume]\n"
710             "        List information about drives.\n"
711             "ls [-r] [-v] [-V] [subdisk]\n"
712             "        List information about subdisks.\n"
713             "lp [-r] [-v] [-V] [plex]\n"
714             "        List information about plexes.\n"
715             "lv [-r] [-v] [-V] [volume]\n"
716             "        List information about volumes.\n"
717             "mirror [-fsv] [-n name] drives\n"
718             "        Create a mirrored volume from the specified drives.\n"
719             "move | mv -f drive object ...\n"
720             "        Move the object(s) to the specified drive.\n"
721             "quit    Exit the vinum program when running in interactive mode."
722             "  Nor-\n"
723             "        mally this would be done by entering the EOF character.\n"
724             "raid5 [-fv] [-s stripesize] [-n name] drives\n"
725             "        Create a RAID-5 volume from the specified drives.\n"
726             "rename [-r] [drive | subdisk | plex | volume] newname\n"
727             "        Change the name of the specified object.\n"
728             "rebuildparity plex [-f]\n"
729             "        Rebuild the parity blocks of a RAID-5 plex.\n"
730             "resetconfig [-f]\n"
731             "        Reset the complete gvinum configuration\n"
732             "rm [-r] [-f] volume | plex | subdisk | drive\n"
733             "        Remove an object.\n"
734             "saveconfig\n"
735             "        Save vinum configuration to disk after configuration"
736             " failures.\n"
737             "setstate [-f] state [volume | plex | subdisk | drive]\n"
738             "        Set state without influencing other objects, for"
739             " diagnostic pur-\n"
740             "        poses only.\n"
741             "start [-S size] volume | plex | subdisk\n"
742             "        Allow the system to access the objects.\n"
743             "stripe [-fv] [-n name] drives\n"
744             "        Create a striped volume from the specified drives.\n"
745         );
746 }
747
748 static void
749 gvinum_setstate(int argc, char * const *argv)
750 {
751         struct gctl_req *req;
752         int flags, i;
753         const char *errstr;
754
755         flags = 0;
756
757         optreset = 1;
758         optind = 1;
759
760         while ((i = getopt(argc, argv, "f")) != -1) {
761                 switch (i) {
762                 case 'f':
763                         flags |= GV_FLAG_F;
764                         break;
765                 case '?':
766                 default:
767                         warn("invalid flag: %c", i);
768                         return;
769                 }
770         }
771
772         argc -= optind;
773         argv += optind;
774
775         if (argc != 2) {
776                 warnx("usage: setstate [-f] <state> <obj>");
777                 return;
778         }
779
780         /*
781          * XXX: This hack is needed to avoid tripping over (now) invalid
782          * 'classic' vinum states and will go away later.
783          */
784         if (strcmp(argv[0], "up") && strcmp(argv[0], "down") &&
785             strcmp(argv[0], "stale")) {
786                 warnx("invalid state '%s'", argv[0]);
787                 return;
788         }
789
790         req = gctl_get_handle();
791         gctl_ro_param(req, "class", -1, "VINUM");
792         gctl_ro_param(req, "verb", -1, "setstate");
793         gctl_ro_param(req, "state", -1, argv[0]);
794         gctl_ro_param(req, "object", -1, argv[1]);
795         gctl_ro_param(req, "flags", sizeof(int), &flags);
796
797         errstr = gctl_issue(req);
798         if (errstr != NULL)
799                 warnx("%s", errstr);
800         gctl_free(req);
801 }
802
803 static void
804 gvinum_list(int argc, char * const *argv)
805 {
806         struct gctl_req *req;
807         int flags, i, j;
808         const char *errstr;
809         char buf[20], config[GV_CFG_LEN + 1];
810         const char *cmd;
811
812         flags = 0;
813         cmd = "list";
814
815         if (argc) {
816                 optreset = 1;
817                 optind = 1;
818                 cmd = argv[0];
819                 while ((j = getopt(argc, argv, "rsvV")) != -1) {
820                         switch (j) {
821                         case 'r':
822                                 flags |= GV_FLAG_R;
823                                 break;
824                         case 's':
825                                 flags |= GV_FLAG_S;
826                                 break;
827                         case 'v':
828                                 flags |= GV_FLAG_V;
829                                 break;
830                         case 'V':
831                                 flags |= GV_FLAG_V;
832                                 flags |= GV_FLAG_VV;
833                                 break;
834                         case '?':
835                         default:
836                                 return;
837                         }
838                 }
839                 argc -= optind;
840                 argv += optind;
841
842         }
843
844         req = gctl_get_handle();
845         gctl_ro_param(req, "class", -1, "VINUM");
846         gctl_ro_param(req, "verb", -1, "list");
847         gctl_ro_param(req, "cmd", -1, cmd);
848         gctl_ro_param(req, "argc", sizeof(int), &argc);
849         gctl_ro_param(req, "flags", sizeof(int), &flags);
850         gctl_rw_param(req, "config", sizeof(config), config);
851         if (argc) {
852                 for (i = 0; i < argc; i++) {
853                         snprintf(buf, sizeof(buf), "argv%d", i);
854                         gctl_ro_param(req, buf, -1, argv[i]);
855                 }
856         }
857         errstr = gctl_issue(req);
858         if (errstr != NULL) {
859                 warnx("can't get configuration: %s", errstr);
860                 gctl_free(req);
861                 return;
862         }
863
864         printf("%s", config);
865         gctl_free(req);
866 }
867
868 /* Create a mirrored volume. */
869 static void
870 gvinum_mirror(int argc, char * const *argv)
871 {
872
873         if (argc < 2) {
874                 warnx("usage\tmirror [-fsv] [-n name] drives\n");
875                 return;
876         }
877         create_volume(argc, argv, "mirror");
878 }
879
880 /* Note that move is currently of form '[-r] target object [...]' */
881 static void
882 gvinum_move(int argc, char * const *argv)
883 {
884         struct gctl_req *req;
885         const char *errstr;
886         char buf[20];
887         int flags, i, j;
888
889         flags = 0;
890         if (argc) {
891                 optreset = 1;
892                 optind = 1;
893                 while ((j = getopt(argc, argv, "f")) != -1) {
894                         switch (j) {
895                         case 'f':
896                                 flags |= GV_FLAG_F;
897                                 break;
898                         case '?':
899                         default:
900                                 return;
901                         }
902                 }
903                 argc -= optind;
904                 argv += optind;
905         }
906
907         switch (argc) {
908                 case 0:
909                         warnx("no destination or object(s) to move specified");
910                         return;
911                 case 1:
912                         warnx("no object(s) to move specified");
913                         return;
914                 default:
915                         break;
916         }
917
918         req = gctl_get_handle();
919         gctl_ro_param(req, "class", -1, "VINUM");
920         gctl_ro_param(req, "verb", -1, "move");
921         gctl_ro_param(req, "argc", sizeof(int), &argc);
922         gctl_ro_param(req, "flags", sizeof(int), &flags);
923         gctl_ro_param(req, "destination", -1, argv[0]);
924         for (i = 1; i < argc; i++) {
925                 snprintf(buf, sizeof(buf), "argv%d", i);
926                 gctl_ro_param(req, buf, -1, argv[i]);
927         }
928         errstr = gctl_issue(req);
929         if (errstr != NULL)
930                 warnx("can't move object(s):  %s", errstr);
931         gctl_free(req);
932 }
933
934 static void
935 gvinum_printconfig(int argc __unused, char * const *argv __unused)
936 {
937
938         printconfig(stdout, "");
939 }
940
941 static void
942 gvinum_parityop(int argc, char * const *argv, int rebuild)
943 {
944         struct gctl_req *req;
945         int flags, i;
946         const char *errstr;
947         const char *op;
948
949         if (rebuild) {
950                 op = "rebuildparity";
951         } else {
952                 op = "checkparity";
953         }
954
955         optreset = 1;
956         optind = 1;
957         flags = 0;
958         while ((i = getopt(argc, argv, "fv")) != -1) {
959                 switch (i) {
960                 case 'f':
961                         flags |= GV_FLAG_F;
962                         break;
963                 case 'v':
964                         flags |= GV_FLAG_V;
965                         break;
966                 default:
967                         warnx("invalid flag '%c'", i);
968                         return;
969                 }
970         }
971         argc -= optind;
972         argv += optind;
973
974         if (argc != 1) {
975                 warn("usage: %s [-f] [-v] <plex>", op);
976                 return;
977         }
978
979         req = gctl_get_handle();
980         gctl_ro_param(req, "class", -1, "VINUM");
981         gctl_ro_param(req, "verb", -1, op);
982         gctl_ro_param(req, "rebuild", sizeof(int), &rebuild);
983         gctl_ro_param(req, "flags", sizeof(int), &flags);
984         gctl_ro_param(req, "plex", -1, argv[0]);
985
986         errstr = gctl_issue(req);
987         if (errstr)
988                 warnx("%s\n", errstr);
989         gctl_free(req);
990 }
991
992 /* Create a RAID-5 volume. */
993 static void
994 gvinum_raid5(int argc, char * const *argv)
995 {
996
997         if (argc < 2) {
998                 warnx("usage:\traid5 [-fv] [-s stripesize] [-n name] drives\n");
999                 return;
1000         }
1001         create_volume(argc, argv, "raid5");
1002 }
1003
1004 static void
1005 gvinum_rename(int argc, char * const *argv)
1006 {
1007         struct gctl_req *req;
1008         const char *errstr;
1009         int flags, j;
1010
1011         flags = 0;
1012
1013         if (argc) {
1014                 optreset = 1;
1015                 optind = 1;
1016                 while ((j = getopt(argc, argv, "r")) != -1) {
1017                         switch (j) {
1018                         case 'r':
1019                                 flags |= GV_FLAG_R;
1020                                 break;
1021                         default:
1022                                 return;
1023                         }
1024                 }
1025                 argc -= optind;
1026                 argv += optind;
1027         }
1028
1029         switch (argc) {
1030                 case 0:
1031                         warnx("no object to rename specified");
1032                         return;
1033                 case 1:
1034                         warnx("no new name specified");
1035                         return;
1036                 case 2:
1037                         break;
1038                 default:
1039                         warnx("more than one new name specified");
1040                         return;
1041         }
1042
1043         req = gctl_get_handle();
1044         gctl_ro_param(req, "class", -1, "VINUM");
1045         gctl_ro_param(req, "verb", -1, "rename");
1046         gctl_ro_param(req, "flags", sizeof(int), &flags);
1047         gctl_ro_param(req, "object", -1, argv[0]);
1048         gctl_ro_param(req, "newname", -1, argv[1]);
1049         errstr = gctl_issue(req);
1050         if (errstr != NULL)
1051                 warnx("can't rename object:  %s", errstr);
1052         gctl_free(req);
1053 }
1054
1055 static void
1056 gvinum_rm(int argc, char * const *argv)
1057 {
1058         struct gctl_req *req;
1059         int flags, i, j;
1060         const char *errstr;
1061         char buf[20];
1062
1063         flags = 0;
1064         optreset = 1;
1065         optind = 1;
1066         while ((j = getopt(argc, argv, "rf")) != -1) {
1067                 switch (j) {
1068                 case 'f':
1069                         flags |= GV_FLAG_F;
1070                         break;
1071                 case 'r':
1072                         flags |= GV_FLAG_R;
1073                         break;
1074                 default:
1075                         return;
1076                 }
1077         }
1078         argc -= optind;
1079         argv += optind;
1080
1081         req = gctl_get_handle();
1082         gctl_ro_param(req, "class", -1, "VINUM");
1083         gctl_ro_param(req, "verb", -1, "remove");
1084         gctl_ro_param(req, "argc", sizeof(int), &argc);
1085         gctl_ro_param(req, "flags", sizeof(int), &flags);
1086         if (argc) {
1087                 for (i = 0; i < argc; i++) {
1088                         snprintf(buf, sizeof(buf), "argv%d", i);
1089                         gctl_ro_param(req, buf, -1, argv[i]);
1090                 }
1091         }
1092         errstr = gctl_issue(req);
1093         if (errstr != NULL) {
1094                 warnx("can't remove: %s", errstr);
1095                 gctl_free(req);
1096                 return;
1097         }
1098         gctl_free(req);
1099 }
1100
1101 static void
1102 gvinum_resetconfig(int argc, char * const *argv)
1103 {
1104         struct gctl_req *req;
1105         const char *errstr;
1106         char reply[32];
1107         int flags, i;
1108
1109         flags = 0;
1110         while ((i = getopt(argc, argv, "f")) != -1) {
1111                 switch (i) {
1112                 case 'f':
1113                         flags |= GV_FLAG_F;
1114                         break;
1115                 default:
1116                         warn("invalid flag: %c", i);
1117                         return;
1118                 }
1119         }
1120         if ((flags & GV_FLAG_F) == 0) {
1121                 if (!isatty(STDIN_FILENO)) {
1122                         warn("Please enter this command from a tty device\n");
1123                         return;
1124                 }
1125                 printf(" WARNING!  This command will completely wipe out"
1126                     " your gvinum configuration.\n"
1127                     " All data will be lost.  If you really want to do this,"
1128                     " enter the text\n\n"
1129                     " NO FUTURE\n"
1130                     " Enter text -> ");
1131                 fgets(reply, sizeof(reply), stdin);
1132                 if (strcmp(reply, "NO FUTURE\n")) {
1133                         printf("\n No change\n");
1134                         return;
1135                 }
1136         }
1137         req = gctl_get_handle();
1138         gctl_ro_param(req, "class", -1, "VINUM");
1139         gctl_ro_param(req, "verb", -1, "resetconfig");
1140         errstr = gctl_issue(req);
1141         if (errstr != NULL) {
1142                 warnx("can't reset config: %s", errstr);
1143                 gctl_free(req);
1144                 return;
1145         }
1146         gctl_free(req);
1147         printf("gvinum configuration obliterated\n");
1148 }
1149
1150 static void
1151 gvinum_saveconfig(void)
1152 {
1153         struct gctl_req *req;
1154         const char *errstr;
1155
1156         req = gctl_get_handle();
1157         gctl_ro_param(req, "class", -1, "VINUM");
1158         gctl_ro_param(req, "verb", -1, "saveconfig");
1159         errstr = gctl_issue(req);
1160         if (errstr != NULL)
1161                 warnx("can't save configuration: %s", errstr);
1162         gctl_free(req);
1163 }
1164
1165 static void
1166 gvinum_start(int argc, char * const *argv)
1167 {
1168         struct gctl_req *req;
1169         int i, initsize, j;
1170         const char *errstr;
1171         char buf[20];
1172
1173         /* 'start' with no arguments is a no-op. */
1174         if (argc == 1)
1175                 return;
1176
1177         initsize = 0;
1178
1179         optreset = 1;
1180         optind = 1;
1181         while ((j = getopt(argc, argv, "S")) != -1) {
1182                 switch (j) {
1183                 case 'S':
1184                         initsize = atoi(optarg);
1185                         break;
1186                 default:
1187                         return;
1188                 }
1189         }
1190         argc -= optind;
1191         argv += optind;
1192
1193         if (!initsize)
1194                 initsize = 512;
1195
1196         req = gctl_get_handle();
1197         gctl_ro_param(req, "class", -1, "VINUM");
1198         gctl_ro_param(req, "verb", -1, "start");
1199         gctl_ro_param(req, "argc", sizeof(int), &argc);
1200         gctl_ro_param(req, "initsize", sizeof(int), &initsize);
1201         if (argc) {
1202                 for (i = 0; i < argc; i++) {
1203                         snprintf(buf, sizeof(buf), "argv%d", i);
1204                         gctl_ro_param(req, buf, -1, argv[i]);
1205                 }
1206         }
1207         errstr = gctl_issue(req);
1208         if (errstr != NULL) {
1209                 warnx("can't start: %s", errstr);
1210                 gctl_free(req);
1211                 return;
1212         }
1213
1214         gctl_free(req);
1215 }
1216
1217 static void
1218 gvinum_stop(int argc __unused, char * const *argv __unused)
1219 {
1220         int err, fileid;
1221
1222         fileid = kldfind(GVINUMKLD);
1223         if (fileid == -1) {
1224                 if (modfind(GVINUMMOD) < 0)
1225                         warn("cannot find " GVINUMKLD);
1226                 return;
1227         }
1228
1229         /*
1230          * This little hack prevents that we end up in an infinite loop in
1231          * g_unload_class().  gv_unload() will return EAGAIN so that the GEOM
1232          * event thread will be free for the g_wither_geom() call from
1233          * gv_unload().  It's silly, but it works.
1234          */
1235         printf("unloading " GVINUMKLD " kernel module... ");
1236         fflush(stdout);
1237         if ((err = kldunload(fileid)) != 0 && (errno == EAGAIN)) {
1238                 sleep(1);
1239                 err = kldunload(fileid);
1240         }
1241         if (err != 0) {
1242                 printf(" failed!\n");
1243                 warn("cannot unload " GVINUMKLD);
1244                 return;
1245         }
1246
1247         printf("done\n");
1248         exit(0);
1249 }
1250
1251 /* Create a striped volume. */
1252 static void
1253 gvinum_stripe(int argc, char * const *argv)
1254 {
1255
1256         if (argc < 2) {
1257                 warnx("usage:\tstripe [-fv] [-n name] drives\n");
1258                 return;
1259         }
1260         create_volume(argc, argv, "stripe");
1261 }
1262
1263 /* Grow a subdisk by adding disk backed by provider. */
1264 static void
1265 gvinum_grow(int argc, char * const *argv)
1266 {
1267         struct gctl_req *req;
1268         char *drive, *sdname;
1269         char sdprefix[GV_MAXSDNAME];
1270         struct gv_drive *d;
1271         struct gv_sd *s;
1272         const char *errstr;
1273         int drives, volumes, plexes, subdisks, flags;
1274
1275         flags = 0;
1276         drives = volumes = plexes = subdisks = 0;
1277         if (argc < 3) {
1278                 warnx("usage:\tgrow plex drive\n");
1279                 return;
1280         }
1281
1282         s = gv_alloc_sd();
1283         if (s == NULL) {
1284                 warn("unable to create subdisk");
1285                 return;
1286         }
1287         d = gv_alloc_drive();
1288         if (d == NULL) {
1289                 warn("unable to create drive");
1290                 free(s);
1291                 return;
1292         }
1293         /* Lookup device and set an appropriate drive name. */
1294         drive = find_drive();
1295         if (drive == NULL) {
1296                 warn("unable to find an appropriate drive name");
1297                 free(s);
1298                 free(d);
1299                 return;
1300         }
1301         strlcpy(d->name, drive, sizeof(d->name));
1302         copy_device(d, argv[2]);
1303
1304         drives = 1;
1305
1306         /* We try to use the plex name as basis for the subdisk name. */
1307         snprintf(sdprefix, sizeof(sdprefix), "%s.s", argv[1]);
1308         sdname = find_name(sdprefix, GV_TYPE_SD, GV_MAXSDNAME);
1309         if (sdname == NULL) {
1310                 warn("unable to find an appropriate subdisk name");
1311                 free(s);
1312                 free(d);
1313                 free(drive);
1314                 return;
1315         }
1316         strlcpy(s->name, sdname, sizeof(s->name));
1317         free(sdname);
1318         strlcpy(s->plex, argv[1], sizeof(s->plex));
1319         strlcpy(s->drive, d->name, sizeof(s->drive));
1320         subdisks = 1;
1321
1322         req = gctl_get_handle();
1323         gctl_ro_param(req, "class", -1, "VINUM");
1324         gctl_ro_param(req, "verb", -1, "create");
1325         gctl_ro_param(req, "flags", sizeof(int), &flags);
1326         gctl_ro_param(req, "volumes", sizeof(int), &volumes);
1327         gctl_ro_param(req, "plexes", sizeof(int), &plexes);
1328         gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
1329         gctl_ro_param(req, "drives", sizeof(int), &drives);
1330         gctl_ro_param(req, "drive0", sizeof(*d), d);
1331         gctl_ro_param(req, "sd0", sizeof(*s), s);
1332         errstr = gctl_issue(req);
1333         free(drive);
1334         if (errstr != NULL) {
1335                 warnx("unable to grow plex: %s", errstr);
1336                 free(s);
1337                 free(d);
1338                 return;
1339         }
1340         gctl_free(req);
1341 }
1342
1343 static void
1344 parseline(int argc, char * const *argv)
1345 {
1346
1347         if (argc <= 0)
1348                 return;
1349
1350         if (!strcmp(argv[0], "create"))
1351                 gvinum_create(argc, argv);
1352         else if (!strcmp(argv[0], "exit") || !strcmp(argv[0], "quit"))
1353                 exit(0);
1354         else if (!strcmp(argv[0], "attach"))
1355                 gvinum_attach(argc, argv);
1356         else if (!strcmp(argv[0], "detach"))
1357                 gvinum_detach(argc, argv);
1358         else if (!strcmp(argv[0], "concat"))
1359                 gvinum_concat(argc, argv);
1360         else if (!strcmp(argv[0], "grow"))
1361                 gvinum_grow(argc, argv);
1362         else if (!strcmp(argv[0], "help"))
1363                 gvinum_help();
1364         else if (!strcmp(argv[0], "list") || !strcmp(argv[0], "l"))
1365                 gvinum_list(argc, argv);
1366         else if (!strcmp(argv[0], "ld"))
1367                 gvinum_list(argc, argv);
1368         else if (!strcmp(argv[0], "lp"))
1369                 gvinum_list(argc, argv);
1370         else if (!strcmp(argv[0], "ls"))
1371                 gvinum_list(argc, argv);
1372         else if (!strcmp(argv[0], "lv"))
1373                 gvinum_list(argc, argv);
1374         else if (!strcmp(argv[0], "mirror"))
1375                 gvinum_mirror(argc, argv);
1376         else if (!strcmp(argv[0], "move"))
1377                 gvinum_move(argc, argv);
1378         else if (!strcmp(argv[0], "mv"))
1379                 gvinum_move(argc, argv);
1380         else if (!strcmp(argv[0], "printconfig"))
1381                 gvinum_printconfig(argc, argv);
1382         else if (!strcmp(argv[0], "raid5"))
1383                 gvinum_raid5(argc, argv);
1384         else if (!strcmp(argv[0], "rename"))
1385                 gvinum_rename(argc, argv);
1386         else if (!strcmp(argv[0], "resetconfig"))
1387                 gvinum_resetconfig(argc, argv);
1388         else if (!strcmp(argv[0], "rm"))
1389                 gvinum_rm(argc, argv);
1390         else if (!strcmp(argv[0], "saveconfig"))
1391                 gvinum_saveconfig();
1392         else if (!strcmp(argv[0], "setstate"))
1393                 gvinum_setstate(argc, argv);
1394         else if (!strcmp(argv[0], "start"))
1395                 gvinum_start(argc, argv);
1396         else if (!strcmp(argv[0], "stop"))
1397                 gvinum_stop(argc, argv);
1398         else if (!strcmp(argv[0], "stripe"))
1399                 gvinum_stripe(argc, argv);
1400         else if (!strcmp(argv[0], "checkparity"))
1401                 gvinum_parityop(argc, argv, 0);
1402         else if (!strcmp(argv[0], "rebuildparity"))
1403                 gvinum_parityop(argc, argv, 1);
1404         else
1405                 printf("unknown command '%s'\n", argv[0]);
1406 }
1407
1408 /*
1409  * The guts of printconfig.  This is called from gvinum_printconfig and from
1410  * gvinum_create when called without an argument, in order to give the user
1411  * something to edit.
1412  */
1413 static void
1414 printconfig(FILE *of, const char *comment)
1415 {
1416         struct gctl_req *req;
1417         struct utsname uname_s;
1418         const char *errstr;
1419         time_t now;
1420         char buf[GV_CFG_LEN + 1];
1421         
1422         uname(&uname_s);
1423         time(&now);
1424
1425         req = gctl_get_handle();
1426         gctl_ro_param(req, "class", -1, "VINUM");
1427         gctl_ro_param(req, "verb", -1, "getconfig");
1428         gctl_ro_param(req, "comment", -1, comment);
1429         gctl_rw_param(req, "config", sizeof(buf), buf);
1430         errstr = gctl_issue(req);
1431         if (errstr != NULL) {
1432                 warnx("can't get configuration: %s", errstr);
1433                 return;
1434         }
1435         gctl_free(req);
1436
1437         fprintf(of, "# Vinum configuration of %s, saved at %s",
1438             uname_s.nodename,
1439             ctime(&now));
1440         
1441         if (*comment != '\0')
1442             fprintf(of, "# Current configuration:\n");
1443
1444         fprintf(of, "%s", buf);
1445 }