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