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