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