]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/gvinum/gvinum.c
Upgrade to OpenSSH 7.7p1.
[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 line, 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                                 line++;             /* count the lines */
128                                 tokens = gv_tokenize(buffer, token, GV_MAXARGS);
129                                 if (tokens)
130                                         parseline(tokens, token);
131                         }
132                 }
133         }
134         exit(0);
135 }
136
137 /* Attach a plex to a volume or a subdisk to a plex. */
138 static void
139 gvinum_attach(int argc, char * const *argv)
140 {
141         struct gctl_req *req;
142         const char *errstr;
143         int rename;
144         off_t offset;
145
146         rename = 0;
147         offset = -1;
148         if (argc < 3) {
149                 warnx("usage:\tattach <subdisk> <plex> [rename] "
150                     "[<plexoffset>]\n"
151                     "\tattach <plex> <volume> [rename]");
152                 return;
153         }
154         if (argc > 3) {
155                 if (!strcmp(argv[3], "rename")) {
156                         rename = 1;
157                         if (argc == 5)
158                                 offset = strtol(argv[4], NULL, 0);
159                 } else
160                         offset = strtol(argv[3], NULL, 0);
161         }
162         req = gctl_get_handle();
163         gctl_ro_param(req, "class", -1, "VINUM");
164         gctl_ro_param(req, "verb", -1, "attach");
165         gctl_ro_param(req, "child", -1, argv[1]);
166         gctl_ro_param(req, "parent", -1, argv[2]);
167         gctl_ro_param(req, "offset", sizeof(off_t), &offset);
168         gctl_ro_param(req, "rename", sizeof(int), &rename);
169         errstr = gctl_issue(req);
170         if (errstr != NULL)
171                 warnx("attach failed: %s", errstr);
172         gctl_free(req);
173 }
174
175 static void
176 gvinum_create(int argc, char * const *argv)
177 {
178         struct gctl_req *req;
179         struct gv_drive *d;
180         struct gv_plex *p;
181         struct gv_sd *s;
182         struct gv_volume *v;
183         FILE *tmp;
184         int drives, errors, fd, flags, i, line, plexes, plex_in_volume;
185         int sd_in_plex, status, subdisks, tokens, undeffd, volumes;
186         const char *errstr;
187         char buf[BUFSIZ], buf1[BUFSIZ], commandline[BUFSIZ], *ed, *sdname;
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], *cmd, config[GV_CFG_LEN + 1];
810
811         flags = 0;
812         cmd = "list";
813
814         if (argc) {
815                 optreset = 1;
816                 optind = 1;
817                 cmd = argv[0];
818                 while ((j = getopt(argc, argv, "rsvV")) != -1) {
819                         switch (j) {
820                         case 'r':
821                                 flags |= GV_FLAG_R;
822                                 break;
823                         case 's':
824                                 flags |= GV_FLAG_S;
825                                 break;
826                         case 'v':
827                                 flags |= GV_FLAG_V;
828                                 break;
829                         case 'V':
830                                 flags |= GV_FLAG_V;
831                                 flags |= GV_FLAG_VV;
832                                 break;
833                         case '?':
834                         default:
835                                 return;
836                         }
837                 }
838                 argc -= optind;
839                 argv += optind;
840
841         }
842
843         req = gctl_get_handle();
844         gctl_ro_param(req, "class", -1, "VINUM");
845         gctl_ro_param(req, "verb", -1, "list");
846         gctl_ro_param(req, "cmd", -1, cmd);
847         gctl_ro_param(req, "argc", sizeof(int), &argc);
848         gctl_ro_param(req, "flags", sizeof(int), &flags);
849         gctl_rw_param(req, "config", sizeof(config), config);
850         if (argc) {
851                 for (i = 0; i < argc; i++) {
852                         snprintf(buf, sizeof(buf), "argv%d", i);
853                         gctl_ro_param(req, buf, -1, argv[i]);
854                 }
855         }
856         errstr = gctl_issue(req);
857         if (errstr != NULL) {
858                 warnx("can't get configuration: %s", errstr);
859                 gctl_free(req);
860                 return;
861         }
862
863         printf("%s", config);
864         gctl_free(req);
865 }
866
867 /* Create a mirrored volume. */
868 static void
869 gvinum_mirror(int argc, char * const *argv)
870 {
871
872         if (argc < 2) {
873                 warnx("usage\tmirror [-fsv] [-n name] drives\n");
874                 return;
875         }
876         create_volume(argc, argv, "mirror");
877 }
878
879 /* Note that move is currently of form '[-r] target object [...]' */
880 static void
881 gvinum_move(int argc, char * const *argv)
882 {
883         struct gctl_req *req;
884         const char *errstr;
885         char buf[20];
886         int flags, i, j;
887
888         flags = 0;
889         if (argc) {
890                 optreset = 1;
891                 optind = 1;
892                 while ((j = getopt(argc, argv, "f")) != -1) {
893                         switch (j) {
894                         case 'f':
895                                 flags |= GV_FLAG_F;
896                                 break;
897                         case '?':
898                         default:
899                                 return;
900                         }
901                 }
902                 argc -= optind;
903                 argv += optind;
904         }
905
906         switch (argc) {
907                 case 0:
908                         warnx("no destination or object(s) to move specified");
909                         return;
910                 case 1:
911                         warnx("no object(s) to move specified");
912                         return;
913                 default:
914                         break;
915         }
916
917         req = gctl_get_handle();
918         gctl_ro_param(req, "class", -1, "VINUM");
919         gctl_ro_param(req, "verb", -1, "move");
920         gctl_ro_param(req, "argc", sizeof(int), &argc);
921         gctl_ro_param(req, "flags", sizeof(int), &flags);
922         gctl_ro_param(req, "destination", -1, argv[0]);
923         for (i = 1; i < argc; i++) {
924                 snprintf(buf, sizeof(buf), "argv%d", i);
925                 gctl_ro_param(req, buf, -1, argv[i]);
926         }
927         errstr = gctl_issue(req);
928         if (errstr != NULL)
929                 warnx("can't move object(s):  %s", errstr);
930         gctl_free(req);
931 }
932
933 static void
934 gvinum_printconfig(int argc, char * const *argv)
935 {
936
937         printconfig(stdout, "");
938 }
939
940 static void
941 gvinum_parityop(int argc, char * const *argv, int rebuild)
942 {
943         struct gctl_req *req;
944         int flags, i;
945         const char *errstr;
946         char *op;
947
948         if (rebuild) {
949                 op = "rebuildparity";
950         } else {
951                 op = "checkparity";
952         }
953
954         optreset = 1;
955         optind = 1;
956         flags = 0;
957         while ((i = getopt(argc, argv, "fv")) != -1) {
958                 switch (i) {
959                 case 'f':
960                         flags |= GV_FLAG_F;
961                         break;
962                 case 'v':
963                         flags |= GV_FLAG_V;
964                         break;
965                 default:
966                         warnx("invalid flag '%c'", i);
967                         return;
968                 }
969         }
970         argc -= optind;
971         argv += optind;
972
973         if (argc != 1) {
974                 warn("usage: %s [-f] [-v] <plex>", op);
975                 return;
976         }
977
978         req = gctl_get_handle();
979         gctl_ro_param(req, "class", -1, "VINUM");
980         gctl_ro_param(req, "verb", -1, op);
981         gctl_ro_param(req, "rebuild", sizeof(int), &rebuild);
982         gctl_ro_param(req, "flags", sizeof(int), &flags);
983         gctl_ro_param(req, "plex", -1, argv[0]);
984
985         errstr = gctl_issue(req);
986         if (errstr)
987                 warnx("%s\n", errstr);
988         gctl_free(req);
989 }
990
991 /* Create a RAID-5 volume. */
992 static void
993 gvinum_raid5(int argc, char * const *argv)
994 {
995
996         if (argc < 2) {
997                 warnx("usage:\traid5 [-fv] [-s stripesize] [-n name] drives\n");
998                 return;
999         }
1000         create_volume(argc, argv, "raid5");
1001 }
1002
1003 static void
1004 gvinum_rename(int argc, char * const *argv)
1005 {
1006         struct gctl_req *req;
1007         const char *errstr;
1008         int flags, j;
1009
1010         flags = 0;
1011
1012         if (argc) {
1013                 optreset = 1;
1014                 optind = 1;
1015                 while ((j = getopt(argc, argv, "r")) != -1) {
1016                         switch (j) {
1017                         case 'r':
1018                                 flags |= GV_FLAG_R;
1019                                 break;
1020                         default:
1021                                 return;
1022                         }
1023                 }
1024                 argc -= optind;
1025                 argv += optind;
1026         }
1027
1028         switch (argc) {
1029                 case 0:
1030                         warnx("no object to rename specified");
1031                         return;
1032                 case 1:
1033                         warnx("no new name specified");
1034                         return;
1035                 case 2:
1036                         break;
1037                 default:
1038                         warnx("more than one new name specified");
1039                         return;
1040         }
1041
1042         req = gctl_get_handle();
1043         gctl_ro_param(req, "class", -1, "VINUM");
1044         gctl_ro_param(req, "verb", -1, "rename");
1045         gctl_ro_param(req, "flags", sizeof(int), &flags);
1046         gctl_ro_param(req, "object", -1, argv[0]);
1047         gctl_ro_param(req, "newname", -1, argv[1]);
1048         errstr = gctl_issue(req);
1049         if (errstr != NULL)
1050                 warnx("can't rename object:  %s", errstr);
1051         gctl_free(req);
1052 }
1053
1054 static void
1055 gvinum_rm(int argc, char * const *argv)
1056 {
1057         struct gctl_req *req;
1058         int flags, i, j;
1059         const char *errstr;
1060         char buf[20];
1061
1062         flags = 0;
1063         optreset = 1;
1064         optind = 1;
1065         while ((j = getopt(argc, argv, "rf")) != -1) {
1066                 switch (j) {
1067                 case 'f':
1068                         flags |= GV_FLAG_F;
1069                         break;
1070                 case 'r':
1071                         flags |= GV_FLAG_R;
1072                         break;
1073                 default:
1074                         return;
1075                 }
1076         }
1077         argc -= optind;
1078         argv += optind;
1079
1080         req = gctl_get_handle();
1081         gctl_ro_param(req, "class", -1, "VINUM");
1082         gctl_ro_param(req, "verb", -1, "remove");
1083         gctl_ro_param(req, "argc", sizeof(int), &argc);
1084         gctl_ro_param(req, "flags", sizeof(int), &flags);
1085         if (argc) {
1086                 for (i = 0; i < argc; i++) {
1087                         snprintf(buf, sizeof(buf), "argv%d", i);
1088                         gctl_ro_param(req, buf, -1, argv[i]);
1089                 }
1090         }
1091         errstr = gctl_issue(req);
1092         if (errstr != NULL) {
1093                 warnx("can't remove: %s", errstr);
1094                 gctl_free(req);
1095                 return;
1096         }
1097         gctl_free(req);
1098 }
1099
1100 static void
1101 gvinum_resetconfig(int argc, char * const *argv)
1102 {
1103         struct gctl_req *req;
1104         const char *errstr;
1105         char reply[32];
1106         int flags, i;
1107
1108         flags = 0;
1109         while ((i = getopt(argc, argv, "f")) != -1) {
1110                 switch (i) {
1111                 case 'f':
1112                         flags |= GV_FLAG_F;
1113                         break;
1114                 default:
1115                         warn("invalid flag: %c", i);
1116                         return;
1117                 }
1118         }
1119         if ((flags & GV_FLAG_F) == 0) {
1120                 if (!isatty(STDIN_FILENO)) {
1121                         warn("Please enter this command from a tty device\n");
1122                         return;
1123                 }
1124                 printf(" WARNING!  This command will completely wipe out"
1125                     " your gvinum configuration.\n"
1126                     " All data will be lost.  If you really want to do this,"
1127                     " enter the text\n\n"
1128                     " NO FUTURE\n"
1129                     " Enter text -> ");
1130                 fgets(reply, sizeof(reply), stdin);
1131                 if (strcmp(reply, "NO FUTURE\n")) {
1132                         printf("\n No change\n");
1133                         return;
1134                 }
1135         }
1136         req = gctl_get_handle();
1137         gctl_ro_param(req, "class", -1, "VINUM");
1138         gctl_ro_param(req, "verb", -1, "resetconfig");
1139         errstr = gctl_issue(req);
1140         if (errstr != NULL) {
1141                 warnx("can't reset config: %s", errstr);
1142                 gctl_free(req);
1143                 return;
1144         }
1145         gctl_free(req);
1146         printf("gvinum configuration obliterated\n");
1147 }
1148
1149 static void
1150 gvinum_saveconfig(void)
1151 {
1152         struct gctl_req *req;
1153         const char *errstr;
1154
1155         req = gctl_get_handle();
1156         gctl_ro_param(req, "class", -1, "VINUM");
1157         gctl_ro_param(req, "verb", -1, "saveconfig");
1158         errstr = gctl_issue(req);
1159         if (errstr != NULL)
1160                 warnx("can't save configuration: %s", errstr);
1161         gctl_free(req);
1162 }
1163
1164 static void
1165 gvinum_start(int argc, char * const *argv)
1166 {
1167         struct gctl_req *req;
1168         int i, initsize, j;
1169         const char *errstr;
1170         char buf[20];
1171
1172         /* 'start' with no arguments is a no-op. */
1173         if (argc == 1)
1174                 return;
1175
1176         initsize = 0;
1177
1178         optreset = 1;
1179         optind = 1;
1180         while ((j = getopt(argc, argv, "S")) != -1) {
1181                 switch (j) {
1182                 case 'S':
1183                         initsize = atoi(optarg);
1184                         break;
1185                 default:
1186                         return;
1187                 }
1188         }
1189         argc -= optind;
1190         argv += optind;
1191
1192         if (!initsize)
1193                 initsize = 512;
1194
1195         req = gctl_get_handle();
1196         gctl_ro_param(req, "class", -1, "VINUM");
1197         gctl_ro_param(req, "verb", -1, "start");
1198         gctl_ro_param(req, "argc", sizeof(int), &argc);
1199         gctl_ro_param(req, "initsize", sizeof(int), &initsize);
1200         if (argc) {
1201                 for (i = 0; i < argc; i++) {
1202                         snprintf(buf, sizeof(buf), "argv%d", i);
1203                         gctl_ro_param(req, buf, -1, argv[i]);
1204                 }
1205         }
1206         errstr = gctl_issue(req);
1207         if (errstr != NULL) {
1208                 warnx("can't start: %s", errstr);
1209                 gctl_free(req);
1210                 return;
1211         }
1212
1213         gctl_free(req);
1214 }
1215
1216 static void
1217 gvinum_stop(int argc, char * const *argv)
1218 {
1219         int err, fileid;
1220
1221         fileid = kldfind(GVINUMKLD);
1222         if (fileid == -1) {
1223                 if (modfind(GVINUMMOD) < 0)
1224                         warn("cannot find " GVINUMKLD);
1225                 return;
1226         }
1227
1228         /*
1229          * This little hack prevents that we end up in an infinite loop in
1230          * g_unload_class().  gv_unload() will return EAGAIN so that the GEOM
1231          * event thread will be free for the g_wither_geom() call from
1232          * gv_unload().  It's silly, but it works.
1233          */
1234         printf("unloading " GVINUMKLD " kernel module... ");
1235         fflush(stdout);
1236         if ((err = kldunload(fileid)) != 0 && (errno == EAGAIN)) {
1237                 sleep(1);
1238                 err = kldunload(fileid);
1239         }
1240         if (err != 0) {
1241                 printf(" failed!\n");
1242                 warn("cannot unload " GVINUMKLD);
1243                 return;
1244         }
1245
1246         printf("done\n");
1247         exit(0);
1248 }
1249
1250 /* Create a striped volume. */
1251 static void
1252 gvinum_stripe(int argc, char * const *argv)
1253 {
1254
1255         if (argc < 2) {
1256                 warnx("usage:\tstripe [-fv] [-n name] drives\n");
1257                 return;
1258         }
1259         create_volume(argc, argv, "stripe");
1260 }
1261
1262 /* Grow a subdisk by adding disk backed by provider. */
1263 static void
1264 gvinum_grow(int argc, char * const *argv)
1265 {
1266         struct gctl_req *req;
1267         char *drive, *sdname;
1268         char sdprefix[GV_MAXSDNAME];
1269         struct gv_drive *d;
1270         struct gv_sd *s;
1271         const char *errstr;
1272         int drives, volumes, plexes, subdisks, flags;
1273
1274         flags = 0;
1275         drives = volumes = plexes = subdisks = 0;
1276         if (argc < 3) {
1277                 warnx("usage:\tgrow plex drive\n");
1278                 return;
1279         }
1280
1281         s = gv_alloc_sd();
1282         if (s == NULL) {
1283                 warn("unable to create subdisk");
1284                 return;
1285         }
1286         d = gv_alloc_drive();
1287         if (d == NULL) {
1288                 warn("unable to create drive");
1289                 free(s);
1290                 return;
1291         }
1292         /* Lookup device and set an appropriate drive name. */
1293         drive = find_drive();
1294         if (drive == NULL) {
1295                 warn("unable to find an appropriate drive name");
1296                 free(s);
1297                 free(d);
1298                 return;
1299         }
1300         strlcpy(d->name, drive, sizeof(d->name));
1301         copy_device(d, argv[2]);
1302
1303         drives = 1;
1304
1305         /* We try to use the plex name as basis for the subdisk name. */
1306         snprintf(sdprefix, sizeof(sdprefix), "%s.s", argv[1]);
1307         sdname = find_name(sdprefix, GV_TYPE_SD, GV_MAXSDNAME);
1308         if (sdname == NULL) {
1309                 warn("unable to find an appropriate subdisk name");
1310                 free(s);
1311                 free(d);
1312                 free(drive);
1313                 return;
1314         }
1315         strlcpy(s->name, sdname, sizeof(s->name));
1316         free(sdname);
1317         strlcpy(s->plex, argv[1], sizeof(s->plex));
1318         strlcpy(s->drive, d->name, sizeof(s->drive));
1319         subdisks = 1;
1320
1321         req = gctl_get_handle();
1322         gctl_ro_param(req, "class", -1, "VINUM");
1323         gctl_ro_param(req, "verb", -1, "create");
1324         gctl_ro_param(req, "flags", sizeof(int), &flags);
1325         gctl_ro_param(req, "volumes", sizeof(int), &volumes);
1326         gctl_ro_param(req, "plexes", sizeof(int), &plexes);
1327         gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
1328         gctl_ro_param(req, "drives", sizeof(int), &drives);
1329         gctl_ro_param(req, "drive0", sizeof(*d), d);
1330         gctl_ro_param(req, "sd0", sizeof(*s), s);
1331         errstr = gctl_issue(req);
1332         free(drive);
1333         if (errstr != NULL) {
1334                 warnx("unable to grow plex: %s", errstr);
1335                 free(s);
1336                 free(d);
1337                 return;
1338         }
1339         gctl_free(req);
1340 }
1341
1342 static void
1343 parseline(int argc, char * const *argv)
1344 {
1345
1346         if (argc <= 0)
1347                 return;
1348
1349         if (!strcmp(argv[0], "create"))
1350                 gvinum_create(argc, argv);
1351         else if (!strcmp(argv[0], "exit") || !strcmp(argv[0], "quit"))
1352                 exit(0);
1353         else if (!strcmp(argv[0], "attach"))
1354                 gvinum_attach(argc, argv);
1355         else if (!strcmp(argv[0], "detach"))
1356                 gvinum_detach(argc, argv);
1357         else if (!strcmp(argv[0], "concat"))
1358                 gvinum_concat(argc, argv);
1359         else if (!strcmp(argv[0], "grow"))
1360                 gvinum_grow(argc, argv);
1361         else if (!strcmp(argv[0], "help"))
1362                 gvinum_help();
1363         else if (!strcmp(argv[0], "list") || !strcmp(argv[0], "l"))
1364                 gvinum_list(argc, argv);
1365         else if (!strcmp(argv[0], "ld"))
1366                 gvinum_list(argc, argv);
1367         else if (!strcmp(argv[0], "lp"))
1368                 gvinum_list(argc, argv);
1369         else if (!strcmp(argv[0], "ls"))
1370                 gvinum_list(argc, argv);
1371         else if (!strcmp(argv[0], "lv"))
1372                 gvinum_list(argc, argv);
1373         else if (!strcmp(argv[0], "mirror"))
1374                 gvinum_mirror(argc, argv);
1375         else if (!strcmp(argv[0], "move"))
1376                 gvinum_move(argc, argv);
1377         else if (!strcmp(argv[0], "mv"))
1378                 gvinum_move(argc, argv);
1379         else if (!strcmp(argv[0], "printconfig"))
1380                 gvinum_printconfig(argc, argv);
1381         else if (!strcmp(argv[0], "raid5"))
1382                 gvinum_raid5(argc, argv);
1383         else if (!strcmp(argv[0], "rename"))
1384                 gvinum_rename(argc, argv);
1385         else if (!strcmp(argv[0], "resetconfig"))
1386                 gvinum_resetconfig(argc, argv);
1387         else if (!strcmp(argv[0], "rm"))
1388                 gvinum_rm(argc, argv);
1389         else if (!strcmp(argv[0], "saveconfig"))
1390                 gvinum_saveconfig();
1391         else if (!strcmp(argv[0], "setstate"))
1392                 gvinum_setstate(argc, argv);
1393         else if (!strcmp(argv[0], "start"))
1394                 gvinum_start(argc, argv);
1395         else if (!strcmp(argv[0], "stop"))
1396                 gvinum_stop(argc, argv);
1397         else if (!strcmp(argv[0], "stripe"))
1398                 gvinum_stripe(argc, argv);
1399         else if (!strcmp(argv[0], "checkparity"))
1400                 gvinum_parityop(argc, argv, 0);
1401         else if (!strcmp(argv[0], "rebuildparity"))
1402                 gvinum_parityop(argc, argv, 1);
1403         else
1404                 printf("unknown command '%s'\n", argv[0]);
1405 }
1406
1407 /*
1408  * The guts of printconfig.  This is called from gvinum_printconfig and from
1409  * gvinum_create when called without an argument, in order to give the user
1410  * something to edit.
1411  */
1412 static void
1413 printconfig(FILE *of, const char *comment)
1414 {
1415         struct gctl_req *req;
1416         struct utsname uname_s;
1417         const char *errstr;
1418         time_t now;
1419         char buf[GV_CFG_LEN + 1];
1420         
1421         uname(&uname_s);
1422         time(&now);
1423
1424         req = gctl_get_handle();
1425         gctl_ro_param(req, "class", -1, "VINUM");
1426         gctl_ro_param(req, "verb", -1, "getconfig");
1427         gctl_ro_param(req, "comment", -1, comment);
1428         gctl_rw_param(req, "config", sizeof(buf), buf);
1429         errstr = gctl_issue(req);
1430         if (errstr != NULL) {
1431                 warnx("can't get configuration: %s", errstr);
1432                 return;
1433         }
1434         gctl_free(req);
1435
1436         fprintf(of, "# Vinum configuration of %s, saved at %s",
1437             uname_s.nodename,
1438             ctime(&now));
1439         
1440         if (*comment != '\0')
1441             fprintf(of, "# Current configuration:\n");
1442
1443         fprintf(of, "%s", buf);
1444 }