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