gvinum.c revision 130391
1/*
2 *  Copyright (c) 2004 Lukas Ertl
3 *  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $FreeBSD: head/sbin/gvinum/gvinum.c 130391 2004-06-12 21:22:47Z le $
27 */
28
29#include <sys/param.h>
30#include <sys/linker.h>
31#include <sys/lock.h>
32#include <sys/module.h>
33#include <sys/mutex.h>
34#include <sys/queue.h>
35#include <sys/utsname.h>
36
37#include <geom/vinum/geom_vinum_var.h>
38#include <geom/vinum/geom_vinum_share.h>
39
40#include <ctype.h>
41#include <err.h>
42#include <libgeom.h>
43#include <stdint.h>
44#include <stdio.h>
45#include <stdlib.h>
46#include <paths.h>
47#include <readline/readline.h>
48#include <readline/history.h>
49#include <unistd.h>
50
51#include "gvinum.h"
52
53void	gvinum_cancelinit(int, char **);
54void	gvinum_create(int, char **);
55void	gvinum_help(void);
56void	gvinum_init(int, char **);
57void	gvinum_list(int, char **);
58void	gvinum_printconfig(int, char **);
59void	gvinum_rm(int, char **);
60void	gvinum_saveconfig(void);
61void	gvinum_start(int, char **);
62void	gvinum_stop(int, char **);
63void	parseline(int, char **);
64void	printconfig(FILE *, char *);
65
66int
67main(int argc, char **argv)
68{
69	int line, tokens;
70	char buffer[BUFSIZ], *inputline, *token[GV_MAXARGS];
71
72	/* Load the module if necessary. */
73	if (kldfind(GVINUMMOD) < 0 && kldload(GVINUMMOD) < 0)
74		err(1, GVINUMMOD ": Kernel module not available");
75
76	/* Arguments given on the command line. */
77	if (argc > 1) {
78		argc--;
79		argv++;
80		parseline(argc, argv);
81
82	/* Interactive mode. */
83	} else {
84		for (;;) {
85			inputline = readline("gvinum -> ");
86			if (inputline == NULL) {
87				if (ferror(stdin)) {
88					err(1, "can't read input");
89				} else {
90					printf("\n");
91					exit(0);
92				}
93			} else if (*inputline) {
94				add_history(inputline);
95				strcpy(buffer, inputline);
96				free(inputline);
97				line++;		    /* count the lines */
98				tokens = gv_tokenize(buffer, token, GV_MAXARGS);
99				if (tokens)
100					parseline(tokens, token);
101			}
102		}
103	}
104	exit(0);
105}
106
107void
108gvinum_cancelinit(int argc, char **argv)
109{
110	struct gctl_req *req;
111	int i;
112	const char *errstr;
113	char buf[20];
114
115	if (argc == 1)
116		return;
117
118	argc--;
119	argv++;
120
121	req = gctl_get_handle();
122	gctl_ro_param(req, "class", -1, "VINUM");
123	gctl_ro_param(req, "verb", -1, "cancelinit");
124	gctl_ro_param(req, "argc", sizeof(int), &argc);
125	if (argc) {
126		for (i = 0; i < argc; i++) {
127			snprintf(buf, sizeof(buf), "argv%d", i);
128			gctl_ro_param(req, buf, -1, argv[i]);
129		}
130	}
131	errstr = gctl_issue(req);
132	if (errstr != NULL) {
133		warnx("can't init: %s", errstr);
134		gctl_free(req);
135		return;
136	}
137
138	gctl_free(req);
139	gvinum_list(0, NULL);
140}
141
142void
143gvinum_create(int argc, char **argv)
144{
145	struct gctl_req *req;
146	struct gv_drive *d;
147	struct gv_plex *p;
148	struct gv_sd *s;
149	struct gv_volume *v;
150	FILE *tmp;
151	int drives, errors, fd, line, plexes, plex_in_volume;
152	int sd_in_plex, status, subdisks, tokens, volumes;
153	const char *errstr;
154	char buf[BUFSIZ], buf1[BUFSIZ], commandline[BUFSIZ], *ed;
155	char original[BUFSIZ], tmpfile[20], *token[GV_MAXARGS];
156	char plex[GV_MAXPLEXNAME], volume[GV_MAXVOLNAME];
157
158	snprintf(tmpfile, sizeof(tmpfile), "/tmp/gvinum.XXXXXX");
159
160	if ((fd = mkstemp(tmpfile)) == -1) {
161		warn("temporary file not accessible");
162		return;
163	}
164	if ((tmp = fdopen(fd, "w")) == NULL) {
165		warn("can't open '%s' for writing", tmpfile);
166		return;
167	}
168	printconfig(tmp, "# ");
169	fclose(tmp);
170
171	ed = getenv("EDITOR");
172	if (ed == NULL)
173		ed = _PATH_VI;
174
175	snprintf(commandline, sizeof(commandline), "%s %s", ed, tmpfile);
176	status = system(commandline);
177	if (status != 0) {
178		warn("couldn't exec %s; status: %d", ed, status);
179		return;
180	}
181
182	if ((tmp = fopen(tmpfile, "r")) == NULL) {
183		warn("can't open '%s' for reading", tmpfile);
184	}
185
186	req = gctl_get_handle();
187	gctl_ro_param(req, "class", -1, "VINUM");
188	gctl_ro_param(req, "verb", -1, "create");
189
190	drives = volumes = plexes = subdisks = 0;
191	plex_in_volume = sd_in_plex = 0;
192	errors = 0;
193	line = 1;
194	while ((fgets(buf, BUFSIZ, tmp)) != NULL) {
195
196		/* Skip empty lines and comments. */
197		if (*buf == '\0' || *buf == '#') {
198			line++;
199			continue;
200		}
201
202		/* Kill off the newline. */
203		buf[strlen(buf) - 1] = '\0';
204
205		/*
206		 * Copy the original input line in case we need it for error
207		 * output.
208		 */
209		strncpy(original, buf, sizeof(buf));
210
211		tokens = gv_tokenize(buf, token, GV_MAXARGS);
212
213		if (tokens > 0) {
214			/* Volume definition. */
215			if (!strcmp(token[0], "volume")) {
216				v = gv_new_volume(tokens, token);
217				if (v == NULL) {
218					warnx("line %d: invalid volume "
219					    "definition", line);
220					warnx("line %d: '%s'", line, original);
221					errors++;
222				} else {
223					/* Reset plex count for this volume. */
224					plex_in_volume = 0;
225
226					/*
227					 * Set default volume name for
228					 * following plex definitions.
229					 */
230					strncpy(volume, v->name,
231					    sizeof(volume));
232
233					snprintf(buf1, sizeof(buf1), "volume%d",
234					    volumes);
235					gctl_ro_param(req, buf1, sizeof(*v), v);
236					volumes++;
237				}
238
239			/* Plex definition. */
240			} else if (!strcmp(token[0], "plex")) {
241				p = gv_new_plex(tokens, token);
242				if (p == NULL) {
243					warnx("line %d: invalid plex "
244					    "definition", line);
245					warnx("line %d: '%s'", line, original);
246					errors++;
247				} else {
248					/* Reset subdisk count for this plex. */
249					sd_in_plex = 0;
250
251					/* Default name. */
252					if (strlen(p->name) == 0) {
253						snprintf(p->name,
254						    GV_MAXPLEXNAME,
255						    "%s.p%d", volume,
256						    plex_in_volume++);
257					}
258
259					/* Default volume. */
260					if (strlen(p->volume) == 0) {
261						snprintf(p->volume,
262						    GV_MAXVOLNAME, "%s",
263						    volume);
264					}
265
266					/*
267					 * Set default plex name for following
268					 * subdisk definitions.
269					 */
270					strncpy(plex, p->name, GV_MAXPLEXNAME);
271
272					snprintf(buf1, sizeof(buf1), "plex%d",
273					    plexes);
274					gctl_ro_param(req, buf1, sizeof(*p), p);
275					plexes++;
276				}
277
278			/* Subdisk definition. */
279			} else if (!strcmp(token[0], "sd")) {
280				s = gv_new_sd(tokens, token);
281				if (s == NULL) {
282					warnx("line %d: invalid subdisk "
283					    "definition:", line);
284					warnx("line %d: '%s'", line, original);
285					errors++;
286				} else {
287					/* Default name. */
288					if (strlen(s->name) == 0) {
289						snprintf(s->name, GV_MAXSDNAME,
290						    "%s.s%d", plex,
291						    sd_in_plex++);
292					}
293
294					/* Default plex. */
295					if (strlen(s->plex) == 0) {
296						snprintf(s->plex,
297						    GV_MAXPLEXNAME, "%s", plex);
298					}
299
300					snprintf(buf1, sizeof(buf1), "sd%d",
301					    subdisks);
302					gctl_ro_param(req, buf1, sizeof(*s), s);
303					subdisks++;
304				}
305
306			/* Subdisk definition. */
307			} else if (!strcmp(token[0], "drive")) {
308				d = gv_new_drive(tokens, token);
309				if (d == NULL) {
310					warnx("line %d: invalid drive "
311					    "definition:", line);
312					warnx("line %d: '%s'", line, original);
313					errors++;
314				} else {
315					snprintf(buf1, sizeof(buf1), "drive%d",
316					    drives);
317					gctl_ro_param(req, buf1, sizeof(*d), d);
318					drives++;
319				}
320
321			/* Everything else is bogus. */
322			} else {
323				warnx("line %d: invalid definition:", line);
324				warnx("line %d: '%s'", line, original);
325				errors++;
326			}
327		}
328		line++;
329	}
330
331	fclose(tmp);
332	unlink(tmpfile);
333
334	if (!errors && (volumes || plexes || subdisks || drives)) {
335		gctl_ro_param(req, "volumes", sizeof(int), &volumes);
336		gctl_ro_param(req, "plexes", sizeof(int), &plexes);
337		gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
338		gctl_ro_param(req, "drives", sizeof(int), &drives);
339		errstr = gctl_issue(req);
340		if (errstr != NULL)
341			warnx("create failed: %s", errstr);
342	}
343	gctl_free(req);
344	gvinum_list(0, NULL);
345}
346
347void
348gvinum_help(void)
349{
350	printf("COMMANDS\n"
351	    "attach plex volume [rename]\n"
352	    "attach subdisk plex [offset] [rename]\n"
353	    "        Attach a plex to a volume, or a subdisk to a plex.\n"
354	    "checkparity plex [-f] [-v]\n"
355	    "        Check the parity blocks of a RAID-4 or RAID-5 plex.\n"
356	    "concat [-f] [-n name] [-v] drives\n"
357	    "        Create a concatenated volume from the specified drives.\n"
358	    "create [-f] description-file\n"
359	    "        Create a volume as described in description-file.\n"
360	    "detach [-f] [plex | subdisk]\n"
361	    "        Detach a plex or subdisk from the volume or plex to"
362	    "which it is\n"
363	    "        attached.\n"
364	    "dumpconfig [drive ...]\n"
365	    "        List the configuration information stored on the"
366	    " specified\n"
367	    "        drives, or all drives in the system if no drive names"
368	    " are speci-\n"
369	    "        fied.\n"
370	    "info [-v] [-V]\n"
371	    "        List information about volume manager state.\n"
372	    "init [-S size] [-w] plex | subdisk\n"
373	    "        Initialize the contents of a subdisk or all the subdisks"
374	    " of a\n"
375	    "        plex to all zeros.\n"
376	    "label volume\n"
377	    "        Create a volume label.\n"
378	    "l | list [-r] [-s] [-v] [-V] [volume | plex | subdisk]\n"
379	    "        List information about specified objects.\n"
380	    "ld [-r] [-s] [-v] [-V] [volume]\n"
381	    "        List information about drives.\n"
382	    "ls [-r] [-s] [-v] [-V] [subdisk]\n"
383	    "        List information about subdisks.\n"
384	    "lp [-r] [-s] [-v] [-V] [plex]\n"
385	    "        List information about plexes.\n"
386	    "lv [-r] [-s] [-v] [-V] [volume]\n"
387	    "        List information about volumes.\n"
388	    "mirror [-f] [-n name] [-s] [-v] drives\n"
389	    "        Create a mirrored volume from the specified drives.\n"
390	    "move | mv -f drive object ...\n"
391	    "        Move the object(s) to the specified drive.\n"
392	    "printconfig [file]\n"
393	    "        Write a copy of the current configuration to file.\n"
394	    "quit    Exit the vinum program when running in interactive mode."
395	    "  Nor-\n"
396	    "        mally this would be done by entering the EOF character.\n"
397	    "rename [-r] [drive | subdisk | plex | volume] newname\n"
398	    "        Change the name of the specified object.\n"
399	    "rebuildparity plex [-f] [-v] [-V]\n"
400	    "        Rebuild the parity blocks of a RAID-4 or RAID-5 plex.\n"
401	    "resetconfig\n"
402	    "        Reset the complete vinum configuration.\n"
403	    "rm [-f] [-r] volume | plex | subdisk\n"
404	    "        Remove an object.\n"
405	    "saveconfig\n"
406	    "        Save vinum configuration to disk after configuration"
407	    " failures.\n"
408	    "setstate state [volume | plex | subdisk | drive]\n"
409	    "        Set state without influencing other objects, for"
410	    " diagnostic pur-\n"
411	    "        poses only.\n"
412	    "start [-i interval] [-S size] [-w] volume | plex | subdisk\n"
413	    "        Allow the system to access the objects.\n"
414	    "stop [-f] [volume | plex | subdisk]\n"
415	    "        Terminate access to the objects, or stop vinum if no"
416	    " parameters\n"
417	    "        are specified.\n"
418	    "stripe [-f] [-n name] [-v] drives\n"
419	    "        Create a striped volume from the specified drives.\n"
420	);
421
422	return;
423}
424
425void
426gvinum_init(int argc, char **argv)
427{
428	struct gctl_req *req;
429	int i, initsize, j;
430	const char *errstr;
431	char buf[20];
432
433	initsize = 0;
434	optreset = 1;
435	optind = 1;
436	while ((j = getopt(argc, argv, "S")) != -1) {
437		switch (j) {
438		case 'S':
439			initsize = atoi(optarg);
440			break;
441		case '?':
442		default:
443			return;
444		}
445	}
446	argc -= optind;
447	argv += optind;
448
449	if (!initsize)
450		initsize = 512;
451
452	req = gctl_get_handle();
453	gctl_ro_param(req, "class", -1, "VINUM");
454	gctl_ro_param(req, "verb", -1, "init");
455	gctl_ro_param(req, "argc", sizeof(int), &argc);
456	gctl_ro_param(req, "initsize", sizeof(int), &initsize);
457	if (argc) {
458		for (i = 0; i < argc; i++) {
459			snprintf(buf, sizeof(buf), "argv%d", i);
460			gctl_ro_param(req, buf, -1, argv[i]);
461		}
462	}
463	errstr = gctl_issue(req);
464	if (errstr != NULL) {
465		warnx("can't init: %s", errstr);
466		gctl_free(req);
467		return;
468	}
469
470	gctl_free(req);
471	gvinum_list(0, NULL);
472}
473
474void
475gvinum_list(int argc, char **argv)
476{
477	struct gctl_req *req;
478	int flags, i, j;
479	const char *errstr;
480	char buf[20], *cmd, config[GV_CFG_LEN + 1];
481
482	flags = 0;
483	cmd = "list";
484
485	if (argc) {
486		optreset = 1;
487		optind = 1;
488		cmd = argv[0];
489		while ((j = getopt(argc, argv, "rsvV")) != -1) {
490			switch (j) {
491			case 'r':
492				flags |= GV_FLAG_R;
493				break;
494			case 's':
495				flags |= GV_FLAG_S;
496				break;
497			case 'v':
498				flags |= GV_FLAG_V;
499				break;
500			case 'V':
501				flags |= GV_FLAG_V;
502				flags |= GV_FLAG_VV;
503				break;
504			case '?':
505			default:
506				return;
507			}
508		}
509		argc -= optind;
510		argv += optind;
511
512	}
513
514	req = gctl_get_handle();
515	gctl_ro_param(req, "class", -1, "VINUM");
516	gctl_ro_param(req, "verb", -1, "list");
517	gctl_ro_param(req, "cmd", -1, cmd);
518	gctl_ro_param(req, "argc", sizeof(int), &argc);
519	gctl_ro_param(req, "flags", sizeof(int), &flags);
520	gctl_rw_param(req, "config", sizeof(config), config);
521	if (argc) {
522		for (i = 0; i < argc; i++) {
523			snprintf(buf, sizeof(buf), "argv%d", i);
524			gctl_ro_param(req, buf, -1, argv[i]);
525		}
526	}
527	errstr = gctl_issue(req);
528	if (errstr != NULL) {
529		warnx("can't get configuration: %s", errstr);
530		gctl_free(req);
531		return;
532	}
533
534	printf("%s", config);
535	gctl_free(req);
536	return;
537}
538
539void
540gvinum_printconfig(int argc, char **argv)
541{
542	printconfig(stdout, "");
543}
544
545void
546gvinum_rm(int argc, char **argv)
547{
548	struct gctl_req *req;
549	int flags, i, j;
550	const char *errstr;
551	char buf[20], *cmd;
552
553	cmd = argv[0];
554	flags = 0;
555	optreset = 1;
556	optind = 1;
557	while ((j = getopt(argc, argv, "r")) != -1) {
558		switch (j) {
559		case 'r':
560			flags |= GV_FLAG_R;
561			break;
562		case '?':
563		default:
564			return;
565		}
566	}
567	argc -= optind;
568	argv += optind;
569
570	req = gctl_get_handle();
571	gctl_ro_param(req, "class", -1, "VINUM");
572	gctl_ro_param(req, "verb", -1, "remove");
573	gctl_ro_param(req, "argc", sizeof(int), &argc);
574	gctl_ro_param(req, "flags", sizeof(int), &flags);
575	if (argc) {
576		for (i = 0; i < argc; i++) {
577			snprintf(buf, sizeof(buf), "argv%d", i);
578			gctl_ro_param(req, buf, -1, argv[i]);
579		}
580	}
581	errstr = gctl_issue(req);
582	if (errstr != NULL) {
583		warnx("can't remove: %s", errstr);
584		gctl_free(req);
585		return;
586	}
587	gctl_free(req);
588	gvinum_list(0, NULL);
589}
590
591void
592gvinum_saveconfig(void)
593{
594	struct gctl_req *req;
595	const char *errstr;
596
597	req = gctl_get_handle();
598	gctl_ro_param(req, "class", -1, "VINUM");
599	gctl_ro_param(req, "verb", -1, "saveconfig");
600	errstr = gctl_issue(req);
601	if (errstr != NULL)
602		warnx("can't save configuration: %s", errstr);
603	gctl_free(req);
604}
605
606void
607gvinum_start(int argc, char **argv)
608{
609	struct gctl_req *req;
610	int i, initsize, j;
611	const char *errstr;
612	char buf[20];
613
614	/* 'start' with no arguments is a no-op. */
615	if (argc == 1)
616		return;
617
618	initsize = 0;
619
620	optreset = 1;
621	optind = 1;
622	while ((j = getopt(argc, argv, "S")) != -1) {
623		switch (j) {
624		case 'S':
625			initsize = atoi(optarg);
626			break;
627		case '?':
628		default:
629			return;
630		}
631	}
632	argc -= optind;
633	argv += optind;
634
635	if (!initsize)
636		initsize = 512;
637
638	req = gctl_get_handle();
639	gctl_ro_param(req, "class", -1, "VINUM");
640	gctl_ro_param(req, "verb", -1, "start");
641	gctl_ro_param(req, "argc", sizeof(int), &argc);
642	gctl_ro_param(req, "initsize", sizeof(int), &initsize);
643	if (argc) {
644		for (i = 0; i < argc; i++) {
645			snprintf(buf, sizeof(buf), "argv%d", i);
646			gctl_ro_param(req, buf, -1, argv[i]);
647		}
648	}
649	errstr = gctl_issue(req);
650	if (errstr != NULL) {
651		warnx("can't start: %s", errstr);
652		gctl_free(req);
653		return;
654	}
655
656	gctl_free(req);
657	gvinum_list(0, NULL);
658}
659
660void
661gvinum_stop(int argc, char **argv)
662{
663	int fileid;
664
665	fileid = kldfind(GVINUMMOD);
666	if (fileid == -1) {
667		warn("cannot find " GVINUMMOD);
668		return;
669	}
670	if (kldunload(fileid) != 0) {
671		warn("cannot unload " GVINUMMOD);
672		return;
673	}
674
675	warnx(GVINUMMOD " unloaded");
676	exit(0);
677}
678
679void
680parseline(int argc, char **argv)
681{
682	if (argc <= 0)
683		return;
684
685	if (!strcmp(argv[0], "cancelinit"))
686		gvinum_cancelinit(argc, argv);
687	else if (!strcmp(argv[0], "create"))
688		gvinum_create(argc, argv);
689	else if (!strcmp(argv[0], "exit") || !strcmp(argv[0], "quit"))
690		exit(0);
691	else if (!strcmp(argv[0], "help"))
692		gvinum_help();
693	else if (!strcmp(argv[0], "init"))
694		gvinum_init(argc, argv);
695	else if (!strcmp(argv[0], "list") || !strcmp(argv[0], "l"))
696		gvinum_list(argc, argv);
697	else if (!strcmp(argv[0], "ld"))
698		gvinum_list(argc, argv);
699	else if (!strcmp(argv[0], "lp"))
700		gvinum_list(argc, argv);
701	else if (!strcmp(argv[0], "ls"))
702		gvinum_list(argc, argv);
703	else if (!strcmp(argv[0], "lv"))
704		gvinum_list(argc, argv);
705	else if (!strcmp(argv[0], "printconfig"))
706		gvinum_printconfig(argc, argv);
707	else if (!strcmp(argv[0], "rm"))
708		gvinum_rm(argc, argv);
709	else if (!strcmp(argv[0], "saveconfig"))
710		gvinum_saveconfig();
711	else if (!strcmp(argv[0], "start"))
712		gvinum_start(argc, argv);
713	else if (!strcmp(argv[0], "stop"))
714		gvinum_stop(argc, argv);
715	else
716		printf("unknown command '%s'\n", argv[0]);
717
718	return;
719}
720
721/*
722 * The guts of printconfig.  This is called from gvinum_printconfig and from
723 * gvinum_create when called without an argument, in order to give the user
724 * something to edit.
725 */
726void
727printconfig(FILE *of, char *comment)
728{
729	struct gctl_req *req;
730	struct utsname uname_s;
731	const char *errstr;
732	time_t now;
733	char buf[GV_CFG_LEN + 1];
734
735	uname(&uname_s);
736	time(&now);
737
738	req = gctl_get_handle();
739	gctl_ro_param(req, "class", -1, "VINUM");
740	gctl_ro_param(req, "verb", -1, "getconfig");
741	gctl_ro_param(req, "comment", -1, comment);
742	gctl_rw_param(req, "config", sizeof(buf), buf);
743	errstr = gctl_issue(req);
744	if (errstr != NULL) {
745		warnx("can't get configuration: %s", errstr);
746		return;
747	}
748	gctl_free(req);
749
750	fprintf(of, "# Vinum configuration of %s, saved at %s",
751	    uname_s.nodename,
752	    ctime(&now));
753
754	if (*comment != '\0')
755	    fprintf(of, "# Current configuration:\n");
756
757	fprintf(of, buf);
758}
759