aboutsummaryrefslogtreecommitdiffstats
path: root/sbin/gvinum
diff options
context:
space:
mode:
Diffstat (limited to 'sbin/gvinum')
-rw-r--r--sbin/gvinum/Makefile15
-rw-r--r--sbin/gvinum/gvinum.8441
-rw-r--r--sbin/gvinum/gvinum.c1432
-rw-r--r--sbin/gvinum/gvinum.h39
4 files changed, 1927 insertions, 0 deletions
diff --git a/sbin/gvinum/Makefile b/sbin/gvinum/Makefile
new file mode 100644
index 000000000000..8cccf56ecdbb
--- /dev/null
+++ b/sbin/gvinum/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+PROG= gvinum
+SRCS= gvinum.c gvinum.h geom_vinum_share.c
+MAN= gvinum.8
+
+WARNS?= 2
+CFLAGS= -I${.CURDIR}/../../sys -I${DESTDIR}/${INCLUDEDIR}/edit
+
+DPADD= ${LIBEDIT} ${LIBTERMCAP} ${LIBDEVSTAT} ${LIBKVM} ${LIBGEOM}
+LDADD= -ledit -ltermcap -ldevstat -lkvm -lgeom
+
+.PATH: ${.CURDIR}/../../sys/geom/vinum
+
+.include <bsd.prog.mk>
diff --git a/sbin/gvinum/gvinum.8 b/sbin/gvinum/gvinum.8
new file mode 100644
index 000000000000..c8e770926949
--- /dev/null
+++ b/sbin/gvinum/gvinum.8
@@ -0,0 +1,441 @@
+.\" Copyright (c) 2005 Chris Jones
+.\" All rights reserved.
+.\"
+.\" This software was developed for the FreeBSD Project by Chris Jones
+.\" thanks to the support of Google's Summer of Code program and
+.\" mentoring by Lukas Ertl.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd April 10, 2009
+.Dt GVINUM 8
+.Os
+.Sh NAME
+.Nm gvinum
+.Nd Logical Volume Manager control program
+.Sh SYNOPSIS
+.Nm
+.Op Ar command
+.Op Fl options
+.Sh COMMANDS
+.Bl -tag -width indent
+.It Ic attach Ar plex volume Op Cm rename
+.It Ic attach Ar subdisk plex Oo Ar offset Oc Op Cm rename
+Attach a plex to a volume, or a subdisk to a plex.
+If offset is specified, the subdisk will be attached to the given offset within
+the plex.
+If rename is specified, the subdisk or plex will change name according to the
+object it attaches to.
+.It Ic checkparity Oo Fl f Oc Ar plex
+Check the parity blocks of a RAID-5 plex.
+The parity check will start at the
+beginning of the plex if the
+.Fl f
+flag is specified, or otherwise at the location of the parity check pointer,
+the first location at which plex's parity is incorrect.
+All subdisks in the
+plex must be up for a parity check.
+.It Ic concat Oo Fl fv Oc Oo Fl n Ar name Oc Ar drives
+Create a concatenated volume from the specified drives.
+If no name is specified, a unique name will be set by gvinum.
+.It Ic create Oo Fl f Oc Op Ar description-file
+Create a volume as described in
+.Ar description-file .
+If no
+.Ar description-file
+provided, opens an editor and provides the current
+.Nm
+configuration for editing.
+The
+.Fl f
+flag will make gvinum ignore any errors regarding creating objects that already
+exists.
+However, in contrast to vinum, objects that are not properly named in the
+.Ar description-file
+will not be created when the
+.Fl f
+flag is given.
+.It Ic detach Oo Fl f Oc Op Ar plex | subdisk
+Detach a plex or subdisk from the volume or plex to which it is
+attached.
+.It Ic grow Ar plex device
+Grow a plex by creating a gvinum drive and subdisk on device and attach it to
+the plex.
+If required by the plex organization, it will be put into the growable state.
+.It Ic help
+Provides a synopsis of
+.Nm
+commands and arguments.
+.It Ic l | list Oo Fl rvV Oc Op Ar volume | plex | subdisk
+.It Ic ld Oo Fl rvV Oc Op Ar drive ...
+.It Ic ls Oo Fl rvV Oc Op Ar subdisk ...
+.It Ic lp Oo Fl rvV Oc Op Ar plex ...
+.It Ic lv Oo Fl rvV Oc Op Ar volume ...
+List information about the relevant object(s).
+The
+.Fl r
+flag provides recursive display, showing each object's subordinate objects in
+proper relation.
+The
+.Fl v
+and
+.Fl V
+flags provide progressively more detailed output.
+.It Ic mirror Oo Fl fsv Oc Oo Fl n Ar name Oc Ar drives
+Create a mirrored volume from the specified drives.
+It requires at least a multiple of 2 drives.
+If no name is specified, a unique name will be set by gvinum.
+If the
+.Fl s
+flag is specified, a striped mirror will be created, and thus requires a
+multiple of 4 drives.
+.It Ic move | mv Fl f Ar drive subdisk Op Ar ...
+Move the subdisk(s) to the specified drive.
+The
+.Fl f
+flag is required, as all data on the indicated subdisk(s) will be destroyed as
+part of the move.
+This can currently only be done when the subdisk is
+not being accessed.
+.Pp
+If a single subdisk is moved, and it forms a part of a RAID-5 plex, the moved
+subdisks will need to be set to the
+.Dq stale
+state, and the plex will require a
+.Ic start
+command.
+If multiple subdisk(s) is moved, and form part of a RAID-5 plex, the
+moved disk(s) will need to be set to the
+.Dq up
+state and the plex will require a
+.Ic rebuildparity
+command.
+If the subdisk(s) form part of a plex that is mirrored with other
+plexes, the plex will require restarting and will sync once restarted.
+Moving
+more than one subdisk in a RAID-5 plex or subdisks from both sides of a
+mirrored plex volume will destroy data.
+Note that parity rebuilds and syncing
+must be started manually after a move.
+.It Ic printconfig
+Write a copy of the current configuration to standard output.
+.It Ic quit
+Exit
+.Nm
+when running in interactive mode.
+Normally this would be done by entering the
+EOF character.
+.It Ic raid5 Oo Fl fv Oc Oo Fl s Ar stripesize Oc Oo Fl n Ar name Oc Ar drives
+Create a RAID-5 volume from the specified drives.
+If no name is specified,a unique name will be set by
+.Ic gvinum.
+This organization requires at least three drives.
+.It Ic rename Oo Fl r Oc Ar drive | subdisk | plex | volume newname
+Change the name of the specified object.
+The
+.Fl r
+flag will recursively rename subordinate objects.
+.Pp
+Note that device nodes will not be renamed until
+.Nm
+is restarted.
+.It Ic rebuildparity Oo Fl f Oc Ar plex
+Rebuild the parity blocks of a RAID-5 plex.
+The parity rebuild will start at
+the beginning of the plex if the
+.Fl f
+flag is specified, or otherwise at the location of the parity check pointer.
+All subdisks in the plex must be up for a parity check.
+.It Ic resetconfig
+Reset the complete
+.Nm
+configuration.
+.It Ic rm Oo Fl r Oc Ar volume | plex | subdisk
+Remove an object and, if
+.Fl r
+is specified, its subordinate objects.
+.It Ic saveconfig
+Save
+.Nm
+configuration to disk after configuration failures.
+.It Ic setstate Oo Fl f Oc Ar state volume | plex | subdisk | drive
+Set state without influencing other objects, for diagnostic purposes
+only.
+The
+.Fl f
+flag forces state changes regardless of whether they are legal.
+.It Ic start
+Read configuration from all vinum drives.
+.It Ic start Oo Fl S Ar size Oc Ar volume | plex | subdisk
+Allow the system to access the objects.
+If necessary, plexes will be synced and rebuilt.
+If a subdisk was added to a running RAID-5 or striped plex, gvinum will
+expand into this subdisk and grow the whole RAID-5 array.
+This can be done without unmounting your filesystem.
+The
+.Fl S
+flag is currently ignored.
+.It Ic stop Oo Fl f Oc Op Ar volume | plex | subdisk
+Terminate access to the objects, or stop
+.Nm
+if no parameters are specified.
+.It Ic stripe Oo Fl fv Oc Oo Fl n Ar name Oc Ar drives
+Create a striped volume from the specified drives. If no name is specified,
+a unique name will be set by Ic gvinum. This organization requires at least two
+drives.
+.El
+.Sh DESCRIPTION
+The
+.Nm
+utility communicates with the kernel component of the GVinum logical volume
+manager.
+It is designed either for interactive use, when started without
+command line arguments, or to execute a single command if the command is
+supplied on the command line.
+In interactive mode,
+.Nm
+maintains a command line history.
+.Sh OPTIONS
+The
+.Nm
+commands may be followed by an option.
+.Bl -tag -width indent
+.It Fl f
+The
+.Fl f
+.Pq Dq force
+option overrides safety checks.
+It should be used with extreme caution.
+This
+option is required in order to use the
+.Ic move
+command.
+.It Fl r
+The
+.Fl r
+.Pq Dq recursive
+option applies the command recursively to subordinate objects.
+For example, in
+conjunction with the
+.Ic lv
+command, the
+.Fl r
+option will also show information about the plexes and subdisks belonging to
+the volume.
+It is also used by the
+.Ic rename
+command to indicate that subordinate objects such as subdisks should be renamed
+to match the object(s) specified and by the
+.Ic rm
+command to delete plexes belonging to a volume and so on.
+.It Fl v
+The
+.Fl v
+.Pq Dq verbose
+option provides more detailed output.
+.It Fl V
+The
+.Fl V
+.Pq Dq "very verbose"
+option provides even more detailed output than
+.Fl v .
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width ".Ev EDITOR"
+.It Ev EDITOR
+The name of the editor to use for editing configuration files, by
+default
+.Xr vi 1
+is invoked.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /dev/gvinum/plex"
+.It Pa /dev/gvinum
+directory with device nodes for
+.Nm
+objects
+.El
+.Sh EXAMPLES
+To create a mirror on disks /dev/ad1 and /dev/ad2, create a filesystem, mount,
+unmount and then stop Ic gvinum:
+.Pp
+.Dl "gvinum mirror /dev/ad1 /dev/ad2"
+.Dl "newfs /dev/gvinum/gvinumvolume0"
+.Dl "mount /dev/gvinum/gvinumvolume0 /mnt"
+.Dl "..."
+.Dl "unmount /mnt"
+.Dl "gvinum stop"
+.Pp
+To create a striped mirror on disks /dev/ad1 /dev/ad2 /dev/ad3 and /dev/ad4
+named "data" and create a filesystem:
+.Pp
+.Dl "gvinum mirror -s -n data /dev/ad1 /dev/ad2 /dev/ad3 /dev/ad4"
+.Dl "newfs /dev/gvinum/data"
+.Pp
+To create a raid5 array on disks /dev/ad1 /dev/ad2 and /dev/ad3, with stripesize
+493k you can use the raid5 command:
+.Pp
+.Dl "gvinum raid5 -s 493k /dev/ad1 /dev/ad2 /dev/ad3"
+.Pp
+Then the volume will be created automatically.
+Afterwards, you have to initialize the volume:
+.Pp
+.Dl "gvinum start myraid5vol"
+.Pp
+The initialization will start, and the states will be updated when it's
+finished.
+The list command will give you information about its progress.
+.Pp
+Imagine that one of the drives fails, and the output of 'printconfig' looks
+something like this:
+.Pp
+.Dl "drive gvinumdrive1 device /dev/ad2"
+.Dl "drive gvinumdrive2 device /dev/???"
+.Dl "drive gvinumdrive0 device /dev/ad1"
+.Dl "volume myraid5vol"
+.Dl "plex name myraid5vol.p0 org raid5 986s vol myraid5vol"
+.Dl "sd name myraid5vol.p0.s2 drive gvinumdrive2 len 32538s driveoffset 265s"
+.Dl "plex myraid5vol.p0 plexoffset 1972s"
+.Dl "sd name myraid5vol.p0.s1 drive gvinumdrive1 len 32538s driveoffset 265s"
+.Dl "plex myraid5vol.p0 plexoffset 986s"
+.Dl "sd name myraid5vol.p0.s0 drive gvinumdrive0 len 32538s driveoffset 265s"
+.Dl "plex myraid5vol.p0 plexoffset 0s"
+.Pp
+Create a new drive with this configuration:
+.Pp
+.Dl "drive gdrive4 device /dev/ad4"
+.Pp
+Then move the stale subdisk to the new drive:
+.Pp
+.Dl "gvinum move gdrive4 myraid5vol.p0.s2"
+.Pp
+Then, initiate the rebuild:
+.Pp
+.Dl "gvinum start myraid5vol.p0"
+.Pp
+The plex will go up form degraded mode after the rebuild is finished.
+The plex can still be used while the rebuild is in progress, although requests
+might be delayed.
+.Pp
+Given the configuration as in the previous example, growing a RAID-5 or STRIPED
+array is accomplished by using the grow command:
+.Pp
+.Dl "gvinum grow myraid5vol.p0 /dev/ad4"
+.Pp
+If everything went ok, the plex state should now be set to growable.
+You can then start the growing with the
+.Ic start
+command:
+.Pp
+.Dl "gvinum start myraid5vol.p0"
+.Pp
+As with rebuilding, you can watch the progress using the
+.Ic list
+command.
+.Pp
+For a more advanced usage and detailed explanation of gvinum, the
+handbook is recommended.
+.Pp
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr geom 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 5.3 .
+The
+.Nm vinum
+utility, on which
+.Nm
+is based, was written by
+.An "Greg Lehey" .
+.Pp
+The
+.Nm
+utility
+was written by
+.An "Lukas Ertl" .
+The
+.Ic move
+and
+.Ic rename
+commands and
+documentation were added by
+.An "Chris Jones"
+through the 2005 Google Summer
+of Code program.
+.Ic a partial rewrite of gvinum was done by "Lukas Ertl" and "Ulf Lilleengen"
+through the 2007 Google Summer of Code program.
+The documentation have been updated to reflect the new functionality.
+.Sh AUTHORS
+.An Lukas Ertl Aq le@FreeBSD.org
+.An Chris Jones Aq soc-cjones@FreeBSD.org
+.An Ulf Lilleengen Aq lulf@FreeBSD.org
+.Sh BUGS
+Currently,
+.Nm
+does not rename devices in
+.Pa /dev/gvinum
+until reloaded.
+.Pp
+The
+.Fl S
+initsize flag to
+.Ic start
+is ignored.
+.Pp
+Moving subdisks that are not part of a mirrored or RAID-5 volume will
+destroy data.
+It is perhaps a bug to permit this.
+.Pp
+Plexes in which subdisks have been moved do not automatically sync or
+rebuild parity.
+This may leave data unprotected and is perhaps unwise.
+.Pp
+Currently,
+.Nm
+does not yet fully implement all of the functions found in
+.Xr vinum 4 .
+Specifically, the following commands from
+.Xr vinum 4
+are not supported:
+.Bl -tag -width indent
+.It Ic debug
+Cause the volume manager to enter the kernel debugger.
+.It Ic debug Ar flags
+Set debugging flags.
+.It Ic dumpconfig Op Ar drive ...
+List the configuration information stored on the specified drives, or all
+drives in the system if no drive names are specified.
+.It Ic info Op Fl vV
+List information about volume manager state.
+.It Ic label Ar volume
+Create a volume label.
+.It Ic resetstats Oo Fl r Oc Op Ar volume | plex | subdisk
+Reset statistics counters for the specified objects, or for all objects if none
+are specified.
+.It Ic setdaemon Op Ar value
+Set daemon configuration.
+.El
diff --git a/sbin/gvinum/gvinum.c b/sbin/gvinum/gvinum.c
new file mode 100644
index 000000000000..203976100c0e
--- /dev/null
+++ b/sbin/gvinum/gvinum.c
@@ -0,0 +1,1432 @@
+/*
+ * Copyright (c) 2004 Lukas Ertl
+ * Copyright (c) 2005 Chris Jones
+ * Copyright (c) 2007 Ulf Lilleengen
+ * All rights reserved.
+ *
+ * Portions of this software were developed for the FreeBSD Project
+ * by Chris Jones thanks to the support of Google's Summer of Code
+ * program and mentoring by Lukas Ertl.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/queue.h>
+#include <sys/utsname.h>
+
+#include <geom/vinum/geom_vinum_var.h>
+#include <geom/vinum/geom_vinum_share.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <libgeom.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <paths.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+#include <unistd.h>
+
+#include "gvinum.h"
+
+void gvinum_attach(int, char **);
+void gvinum_concat(int, char **);
+void gvinum_create(int, char **);
+void gvinum_detach(int, char **);
+void gvinum_grow(int, char **);
+void gvinum_help(void);
+void gvinum_list(int, char **);
+void gvinum_move(int, char **);
+void gvinum_mirror(int, char **);
+void gvinum_parityop(int, char **, int);
+void gvinum_printconfig(int, char **);
+void gvinum_raid5(int, char **);
+void gvinum_rename(int, char **);
+void gvinum_resetconfig(void);
+void gvinum_rm(int, char **);
+void gvinum_saveconfig(void);
+void gvinum_setstate(int, char **);
+void gvinum_start(int, char **);
+void gvinum_stop(int, char **);
+void gvinum_stripe(int, char **);
+void parseline(int, char **);
+void printconfig(FILE *, char *);
+
+char *create_drive(char *);
+void create_volume(int, char **, char *);
+char *find_name(const char *, int, int);
+char *find_pattern(char *, char *);
+void copy_device(struct gv_drive *, const char *);
+#define find_drive() find_name("gvinumdrive", GV_TYPE_DRIVE, GV_MAXDRIVENAME)
+
+int
+main(int argc, char **argv)
+{
+ int line, tokens;
+ char buffer[BUFSIZ], *inputline, *token[GV_MAXARGS];
+
+ /* Load the module if necessary. */
+ if (kldfind(GVINUMMOD) < 0 && kldload(GVINUMMOD) < 0)
+ err(1, GVINUMMOD ": Kernel module not available");
+
+ /* Arguments given on the command line. */
+ if (argc > 1) {
+ argc--;
+ argv++;
+ parseline(argc, argv);
+
+ /* Interactive mode. */
+ } else {
+ for (;;) {
+ inputline = readline("gvinum -> ");
+ if (inputline == NULL) {
+ if (ferror(stdin)) {
+ err(1, "can't read input");
+ } else {
+ printf("\n");
+ exit(0);
+ }
+ } else if (*inputline) {
+ add_history(inputline);
+ strcpy(buffer, inputline);
+ free(inputline);
+ line++; /* count the lines */
+ tokens = gv_tokenize(buffer, token, GV_MAXARGS);
+ if (tokens)
+ parseline(tokens, token);
+ }
+ }
+ }
+ exit(0);
+}
+
+/* Attach a plex to a volume or a subdisk to a plex. */
+void
+gvinum_attach(int argc, char **argv)
+{
+ struct gctl_req *req;
+ const char *errstr;
+ int rename;
+ off_t offset;
+
+ rename = 0;
+ offset = -1;
+ if (argc < 3) {
+ warnx("usage:\tattach <subdisk> <plex> [rename] "
+ "[<plexoffset>]\n"
+ "\tattach <plex> <volume> [rename]");
+ return;
+ }
+ if (argc > 3) {
+ if (!strcmp(argv[3], "rename")) {
+ rename = 1;
+ if (argc == 5)
+ offset = strtol(argv[4], NULL, 0);
+ } else
+ offset = strtol(argv[3], NULL, 0);
+ }
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "attach");
+ gctl_ro_param(req, "child", -1, argv[1]);
+ gctl_ro_param(req, "parent", -1, argv[2]);
+ gctl_ro_param(req, "offset", sizeof(off_t), &offset);
+ gctl_ro_param(req, "rename", sizeof(int), &rename);
+ errstr = gctl_issue(req);
+ if (errstr != NULL)
+ warnx("attach failed: %s", errstr);
+ gctl_free(req);
+}
+
+void
+gvinum_create(int argc, char **argv)
+{
+ struct gctl_req *req;
+ struct gv_drive *d;
+ struct gv_plex *p;
+ struct gv_sd *s;
+ struct gv_volume *v;
+ FILE *tmp;
+ int drives, errors, fd, flags, i, line, plexes, plex_in_volume;
+ int sd_in_plex, status, subdisks, tokens, undeffd, volumes;
+ const char *errstr;
+ char buf[BUFSIZ], buf1[BUFSIZ], commandline[BUFSIZ], *ed, *sdname;
+ char original[BUFSIZ], tmpfile[20], *token[GV_MAXARGS];
+ char plex[GV_MAXPLEXNAME], volume[GV_MAXVOLNAME];
+
+ tmp = NULL;
+ flags = 0;
+ for (i = 1; i < argc; i++) {
+ /* Force flag used to ignore already created drives. */
+ if (!strcmp(argv[i], "-f")) {
+ flags |= GV_FLAG_F;
+ /* Else it must be a file. */
+ } else {
+ if ((tmp = fopen(argv[1], "r")) == NULL) {
+ warn("can't open '%s' for reading", argv[1]);
+ return;
+ }
+ }
+ }
+
+ /* We didn't get a file. */
+ if (tmp == NULL) {
+ snprintf(tmpfile, sizeof(tmpfile), "/tmp/gvinum.XXXXXX");
+
+ if ((fd = mkstemp(tmpfile)) == -1) {
+ warn("temporary file not accessible");
+ return;
+ }
+ if ((tmp = fdopen(fd, "w")) == NULL) {
+ warn("can't open '%s' for writing", tmpfile);
+ return;
+ }
+ printconfig(tmp, "# ");
+ fclose(tmp);
+
+ ed = getenv("EDITOR");
+ if (ed == NULL)
+ ed = _PATH_VI;
+
+ snprintf(commandline, sizeof(commandline), "%s %s", ed,
+ tmpfile);
+ status = system(commandline);
+ if (status != 0) {
+ warn("couldn't exec %s; status: %d", ed, status);
+ return;
+ }
+
+ if ((tmp = fopen(tmpfile, "r")) == NULL) {
+ warn("can't open '%s' for reading", tmpfile);
+ return;
+ }
+ }
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "create");
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+
+ drives = volumes = plexes = subdisks = 0;
+ plex_in_volume = sd_in_plex = undeffd = 0;
+ plex[0] = '\0';
+ errors = 0;
+ line = 1;
+ while ((fgets(buf, BUFSIZ, tmp)) != NULL) {
+
+ /* Skip empty lines and comments. */
+ if (*buf == '\0' || *buf == '#') {
+ line++;
+ continue;
+ }
+
+ /* Kill off the newline. */
+ buf[strlen(buf) - 1] = '\0';
+
+ /*
+ * Copy the original input line in case we need it for error
+ * output.
+ */
+ strlcpy(original, buf, sizeof(original));
+
+ tokens = gv_tokenize(buf, token, GV_MAXARGS);
+ if (tokens <= 0) {
+ line++;
+ continue;
+ }
+
+ /* Volume definition. */
+ if (!strcmp(token[0], "volume")) {
+ v = gv_new_volume(tokens, token);
+ if (v == NULL) {
+ warnx("line %d: invalid volume definition",
+ line);
+ warnx("line %d: '%s'", line, original);
+ errors++;
+ line++;
+ continue;
+ }
+
+ /* Reset plex count for this volume. */
+ plex_in_volume = 0;
+
+ /*
+ * Set default volume name for following plex
+ * definitions.
+ */
+ strlcpy(volume, v->name, sizeof(volume));
+
+ snprintf(buf1, sizeof(buf1), "volume%d", volumes);
+ gctl_ro_param(req, buf1, sizeof(*v), v);
+ volumes++;
+
+ /* Plex definition. */
+ } else if (!strcmp(token[0], "plex")) {
+ p = gv_new_plex(tokens, token);
+ if (p == NULL) {
+ warnx("line %d: invalid plex definition", line);
+ warnx("line %d: '%s'", line, original);
+ errors++;
+ line++;
+ continue;
+ }
+
+ /* Reset subdisk count for this plex. */
+ sd_in_plex = 0;
+
+ /* Default name. */
+ if (strlen(p->name) == 0) {
+ snprintf(p->name, sizeof(p->name), "%s.p%d",
+ volume, plex_in_volume++);
+ }
+
+ /* Default volume. */
+ if (strlen(p->volume) == 0) {
+ snprintf(p->volume, sizeof(p->volume), "%s",
+ volume);
+ }
+
+ /*
+ * Set default plex name for following subdisk
+ * definitions.
+ */
+ strlcpy(plex, p->name, sizeof(plex));
+
+ snprintf(buf1, sizeof(buf1), "plex%d", plexes);
+ gctl_ro_param(req, buf1, sizeof(*p), p);
+ plexes++;
+
+ /* Subdisk definition. */
+ } else if (!strcmp(token[0], "sd")) {
+ s = gv_new_sd(tokens, token);
+ if (s == NULL) {
+ warnx("line %d: invalid subdisk "
+ "definition:", line);
+ warnx("line %d: '%s'", line, original);
+ errors++;
+ line++;
+ continue;
+ }
+
+ /* Default name. */
+ if (strlen(s->name) == 0) {
+ if (strlen(plex) == 0) {
+ sdname = find_name("gvinumsubdisk.p",
+ GV_TYPE_SD, GV_MAXSDNAME);
+ snprintf(s->name, sizeof(s->name),
+ "%s.s%d", sdname, undeffd++);
+ free(sdname);
+ } else {
+ snprintf(s->name, sizeof(s->name),
+ "%s.s%d",plex, sd_in_plex++);
+ }
+ }
+
+ /* Default plex. */
+ if (strlen(s->plex) == 0)
+ snprintf(s->plex, sizeof(s->plex), "%s", plex);
+
+ snprintf(buf1, sizeof(buf1), "sd%d", subdisks);
+ gctl_ro_param(req, buf1, sizeof(*s), s);
+ subdisks++;
+
+ /* Subdisk definition. */
+ } else if (!strcmp(token[0], "drive")) {
+ d = gv_new_drive(tokens, token);
+ if (d == NULL) {
+ warnx("line %d: invalid drive definition:",
+ line);
+ warnx("line %d: '%s'", line, original);
+ errors++;
+ line++;
+ continue;
+ }
+
+ snprintf(buf1, sizeof(buf1), "drive%d", drives);
+ gctl_ro_param(req, buf1, sizeof(*d), d);
+ drives++;
+
+ /* Everything else is bogus. */
+ } else {
+ warnx("line %d: invalid definition:", line);
+ warnx("line %d: '%s'", line, original);
+ errors++;
+ }
+ line++;
+ }
+
+ fclose(tmp);
+ unlink(tmpfile);
+
+ if (!errors && (volumes || plexes || subdisks || drives)) {
+ gctl_ro_param(req, "volumes", sizeof(int), &volumes);
+ gctl_ro_param(req, "plexes", sizeof(int), &plexes);
+ gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
+ gctl_ro_param(req, "drives", sizeof(int), &drives);
+ errstr = gctl_issue(req);
+ if (errstr != NULL)
+ warnx("create failed: %s", errstr);
+ }
+ gctl_free(req);
+}
+
+/* Create a concatenated volume. */
+void
+gvinum_concat(int argc, char **argv)
+{
+
+ if (argc < 2) {
+ warnx("usage:\tconcat [-fv] [-n name] drives\n");
+ return;
+ }
+ create_volume(argc, argv, "concat");
+}
+
+
+/* Create a drive quick and dirty. */
+char *
+create_drive(char *device)
+{
+ struct gv_drive *d;
+ struct gctl_req *req;
+ const char *errstr;
+ char *drivename, *dname;
+ int drives, i, flags, volumes, subdisks, plexes;
+
+ flags = plexes = subdisks = volumes = 0;
+ drives = 1;
+ dname = NULL;
+
+ drivename = find_drive();
+ if (drivename == NULL)
+ return (NULL);
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "create");
+ d = gv_alloc_drive();
+ if (d == NULL)
+ err(1, "unable to allocate for gv_drive object");
+
+ strlcpy(d->name, drivename, sizeof(d->name));
+ copy_device(d, device);
+ gctl_ro_param(req, "drive0", sizeof(*d), d);
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+ gctl_ro_param(req, "drives", sizeof(int), &drives);
+ gctl_ro_param(req, "volumes", sizeof(int), &volumes);
+ gctl_ro_param(req, "plexes", sizeof(int), &plexes);
+ gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
+ errstr = gctl_issue(req);
+ if (errstr != NULL) {
+ warnx("error creating drive: %s", errstr);
+ gctl_free(req);
+ return (NULL);
+ } else {
+ gctl_free(req);
+ /* XXX: This is needed because we have to make sure the drives
+ * are created before we return. */
+ /* Loop until it's in the config. */
+ for (i = 0; i < 100000; i++) {
+ dname = find_name("gvinumdrive", GV_TYPE_DRIVE,
+ GV_MAXDRIVENAME);
+ /* If we got a different name, quit. */
+ if (dname == NULL)
+ continue;
+ if (strcmp(dname, drivename)) {
+ free(dname);
+ return (drivename);
+ }
+ free(dname);
+ dname = NULL;
+ usleep(100000); /* Sleep for 0.1s */
+ }
+ }
+ gctl_free(req);
+ return (drivename);
+}
+
+/*
+ * General routine for creating a volume. Mainly for use by concat, mirror,
+ * raid5 and stripe commands.
+ */
+void
+create_volume(int argc, char **argv, char *verb)
+{
+ struct gctl_req *req;
+ const char *errstr;
+ char buf[BUFSIZ], *drivename, *volname;
+ int drives, flags, i;
+ off_t stripesize;
+
+ flags = 0;
+ drives = 0;
+ volname = NULL;
+ stripesize = 262144;
+
+ /* XXX: Should we check for argument length? */
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "-f")) {
+ flags |= GV_FLAG_F;
+ } else if (!strcmp(argv[i], "-n")) {
+ volname = argv[++i];
+ } else if (!strcmp(argv[i], "-v")) {
+ flags |= GV_FLAG_V;
+ } else if (!strcmp(argv[i], "-s")) {
+ flags |= GV_FLAG_S;
+ if (!strcmp(verb, "raid5"))
+ stripesize = gv_sizespec(argv[++i]);
+ } else {
+ /* Assume it's a drive. */
+ snprintf(buf, sizeof(buf), "drive%d", drives++);
+
+ /* First we create the drive. */
+ drivename = create_drive(argv[i]);
+ if (drivename == NULL)
+ goto bad;
+ /* Then we add it to the request. */
+ gctl_ro_param(req, buf, -1, drivename);
+ }
+ }
+
+ gctl_ro_param(req, "stripesize", sizeof(off_t), &stripesize);
+
+ /* Find a free volume name. */
+ if (volname == NULL)
+ volname = find_name("gvinumvolume", GV_TYPE_VOL, GV_MAXVOLNAME);
+
+ /* Then we send a request to actually create the volumes. */
+ gctl_ro_param(req, "verb", -1, verb);
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+ gctl_ro_param(req, "drives", sizeof(int), &drives);
+ gctl_ro_param(req, "name", -1, volname);
+ errstr = gctl_issue(req);
+ if (errstr != NULL)
+ warnx("creating %s volume failed: %s", verb, errstr);
+bad:
+ gctl_free(req);
+}
+
+/* Parse a line of the config, return the word after <pattern>. */
+char *
+find_pattern(char *line, char *pattern)
+{
+ char *ptr;
+
+ ptr = strsep(&line, " ");
+ while (ptr != NULL) {
+ if (!strcmp(ptr, pattern)) {
+ /* Return the next. */
+ ptr = strsep(&line, " ");
+ return (ptr);
+ }
+ ptr = strsep(&line, " ");
+ }
+ return (NULL);
+}
+
+/* Find a free name for an object given a a prefix. */
+char *
+find_name(const char *prefix, int type, int namelen)
+{
+ struct gctl_req *req;
+ char comment[1], buf[GV_CFG_LEN - 1], *name, *sname, *ptr;
+ const char *errstr;
+ int i, n, begin, len, conflict;
+ char line[1024];
+
+ comment[0] = '\0';
+
+ /* Find a name. Fetch out configuration first. */
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "getconfig");
+ gctl_ro_param(req, "comment", -1, comment);
+ gctl_rw_param(req, "config", sizeof(buf), buf);
+ errstr = gctl_issue(req);
+ if (errstr != NULL) {
+ warnx("can't get configuration: %s", errstr);
+ return (NULL);
+ }
+ gctl_free(req);
+
+ begin = 0;
+ len = strlen(buf);
+ i = 0;
+ sname = malloc(namelen + 1);
+
+ /* XXX: Max object setting? */
+ for (n = 0; n < 10000; n++) {
+ snprintf(sname, namelen, "%s%d", prefix, n);
+ conflict = 0;
+ begin = 0;
+ /* Loop through the configuration line by line. */
+ for (i = 0; i < len; i++) {
+ if (buf[i] == '\n' || buf[i] == '\0') {
+ ptr = buf + begin;
+ strlcpy(line, ptr, (i - begin) + 1);
+ begin = i + 1;
+ switch (type) {
+ case GV_TYPE_DRIVE:
+ name = find_pattern(line, "drive");
+ break;
+ case GV_TYPE_VOL:
+ name = find_pattern(line, "volume");
+ break;
+ case GV_TYPE_PLEX:
+ case GV_TYPE_SD:
+ name = find_pattern(line, "name");
+ break;
+ default:
+ printf("Invalid type given\n");
+ continue;
+ }
+ if (name == NULL)
+ continue;
+ if (!strcmp(sname, name)) {
+ conflict = 1;
+ /* XXX: Could quit the loop earlier. */
+ }
+ }
+ }
+ if (!conflict)
+ return (sname);
+ }
+ free(sname);
+ return (NULL);
+}
+
+void
+copy_device(struct gv_drive *d, const char *device)
+{
+ if (strncmp(device, "/dev/", 5) == 0)
+ strlcpy(d->device, (device + 5), sizeof(d->device));
+ else
+ strlcpy(d->device, device, sizeof(d->device));
+}
+
+/* Detach a plex or subdisk from its parent. */
+void
+gvinum_detach(int argc, char **argv)
+{
+ const char *errstr;
+ struct gctl_req *req;
+ int flags, i;
+
+ flags = 0;
+ optreset = 1;
+ optind = 1;
+ while ((i = getopt(argc, argv, "f")) != -1) {
+ switch(i) {
+ case 'f':
+ flags |= GV_FLAG_F;
+ break;
+ default:
+ warn("invalid flag: %c", i);
+ return;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 1) {
+ warnx("usage: detach [-f] <subdisk> | <plex>");
+ return;
+ }
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "detach");
+ gctl_ro_param(req, "object", -1, argv[0]);
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+
+ errstr = gctl_issue(req);
+ if (errstr != NULL)
+ warnx("detach failed: %s", errstr);
+ gctl_free(req);
+}
+
+void
+gvinum_help(void)
+{
+ printf("COMMANDS\n"
+ "checkparity [-f] plex\n"
+ " Check the parity blocks of a RAID-5 plex.\n"
+ "create [-f] description-file\n"
+ " Create as per description-file or open editor.\n"
+ "attach plex volume [rename]\n"
+ "attach subdisk plex [offset] [rename]\n"
+ " Attach a plex to a volume, or a subdisk to a plex\n"
+ "concat [-fv] [-n name] drives\n"
+ " Create a concatenated volume from the specified drives.\n"
+ "detach [-f] [plex | subdisk]\n"
+ " Detach a plex or a subdisk from the volume or plex to\n"
+ " which it is attached.\n"
+ "grow plex drive\n"
+ " Grow plex by creating a properly sized subdisk on drive\n"
+ "l | list [-r] [-v] [-V] [volume | plex | subdisk]\n"
+ " List information about specified objects.\n"
+ "ld [-r] [-v] [-V] [volume]\n"
+ " List information about drives.\n"
+ "ls [-r] [-v] [-V] [subdisk]\n"
+ " List information about subdisks.\n"
+ "lp [-r] [-v] [-V] [plex]\n"
+ " List information about plexes.\n"
+ "lv [-r] [-v] [-V] [volume]\n"
+ " List information about volumes.\n"
+ "mirror [-fsv] [-n name] drives\n"
+ " Create a mirrored volume from the specified drives.\n"
+ "move | mv -f drive object ...\n"
+ " Move the object(s) to the specified drive.\n"
+ "quit Exit the vinum program when running in interactive mode."
+ " Nor-\n"
+ " mally this would be done by entering the EOF character.\n"
+ "raid5 [-fv] [-s stripesize] [-n name] drives\n"
+ " Create a RAID-5 volume from the specified drives.\n"
+ "rename [-r] [drive | subdisk | plex | volume] newname\n"
+ " Change the name of the specified object.\n"
+ "rebuildparity plex [-f]\n"
+ " Rebuild the parity blocks of a RAID-5 plex.\n"
+ "resetconfig\n"
+ " Reset the complete gvinum configuration\n"
+ "rm [-r] [-f] volume | plex | subdisk | drive\n"
+ " Remove an object.\n"
+ "saveconfig\n"
+ " Save vinum configuration to disk after configuration"
+ " failures.\n"
+ "setstate [-f] state [volume | plex | subdisk | drive]\n"
+ " Set state without influencing other objects, for"
+ " diagnostic pur-\n"
+ " poses only.\n"
+ "start [-S size] volume | plex | subdisk\n"
+ " Allow the system to access the objects.\n"
+ "stripe [-fv] [-n name] drives\n"
+ " Create a striped volume from the specified drives.\n"
+ );
+
+ return;
+}
+
+void
+gvinum_setstate(int argc, char **argv)
+{
+ struct gctl_req *req;
+ int flags, i;
+ const char *errstr;
+
+ flags = 0;
+
+ optreset = 1;
+ optind = 1;
+
+ while ((i = getopt(argc, argv, "f")) != -1) {
+ switch (i) {
+ case 'f':
+ flags |= GV_FLAG_F;
+ break;
+ case '?':
+ default:
+ warn("invalid flag: %c", i);
+ return;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 2) {
+ warnx("usage: setstate [-f] <state> <obj>");
+ return;
+ }
+
+ /*
+ * XXX: This hack is needed to avoid tripping over (now) invalid
+ * 'classic' vinum states and will go away later.
+ */
+ if (strcmp(argv[0], "up") && strcmp(argv[0], "down") &&
+ strcmp(argv[0], "stale")) {
+ warnx("invalid state '%s'", argv[0]);
+ return;
+ }
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "setstate");
+ gctl_ro_param(req, "state", -1, argv[0]);
+ gctl_ro_param(req, "object", -1, argv[1]);
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+
+ errstr = gctl_issue(req);
+ if (errstr != NULL)
+ warnx("%s", errstr);
+ gctl_free(req);
+}
+
+void
+gvinum_list(int argc, char **argv)
+{
+ struct gctl_req *req;
+ int flags, i, j;
+ const char *errstr;
+ char buf[20], *cmd, config[GV_CFG_LEN + 1];
+
+ flags = 0;
+ cmd = "list";
+
+ if (argc) {
+ optreset = 1;
+ optind = 1;
+ cmd = argv[0];
+ while ((j = getopt(argc, argv, "rsvV")) != -1) {
+ switch (j) {
+ case 'r':
+ flags |= GV_FLAG_R;
+ break;
+ case 's':
+ flags |= GV_FLAG_S;
+ break;
+ case 'v':
+ flags |= GV_FLAG_V;
+ break;
+ case 'V':
+ flags |= GV_FLAG_V;
+ flags |= GV_FLAG_VV;
+ break;
+ case '?':
+ default:
+ return;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ }
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "list");
+ gctl_ro_param(req, "cmd", -1, cmd);
+ gctl_ro_param(req, "argc", sizeof(int), &argc);
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+ gctl_rw_param(req, "config", sizeof(config), config);
+ if (argc) {
+ for (i = 0; i < argc; i++) {
+ snprintf(buf, sizeof(buf), "argv%d", i);
+ gctl_ro_param(req, buf, -1, argv[i]);
+ }
+ }
+ errstr = gctl_issue(req);
+ if (errstr != NULL) {
+ warnx("can't get configuration: %s", errstr);
+ gctl_free(req);
+ return;
+ }
+
+ printf("%s", config);
+ gctl_free(req);
+ return;
+}
+
+/* Create a mirrored volume. */
+void
+gvinum_mirror(int argc, char **argv)
+{
+
+ if (argc < 2) {
+ warnx("usage\tmirror [-fsv] [-n name] drives\n");
+ return;
+ }
+ create_volume(argc, argv, "mirror");
+}
+
+/* Note that move is currently of form '[-r] target object [...]' */
+void
+gvinum_move(int argc, char **argv)
+{
+ struct gctl_req *req;
+ const char *errstr;
+ char buf[20];
+ int flags, i, j;
+
+ flags = 0;
+ if (argc) {
+ optreset = 1;
+ optind = 1;
+ while ((j = getopt(argc, argv, "f")) != -1) {
+ switch (j) {
+ case 'f':
+ flags |= GV_FLAG_F;
+ break;
+ case '?':
+ default:
+ return;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ }
+
+ switch (argc) {
+ case 0:
+ warnx("no destination or object(s) to move specified");
+ return;
+ case 1:
+ warnx("no object(s) to move specified");
+ return;
+ default:
+ break;
+ }
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "move");
+ gctl_ro_param(req, "argc", sizeof(int), &argc);
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+ gctl_ro_param(req, "destination", -1, argv[0]);
+ for (i = 1; i < argc; i++) {
+ snprintf(buf, sizeof(buf), "argv%d", i);
+ gctl_ro_param(req, buf, -1, argv[i]);
+ }
+ errstr = gctl_issue(req);
+ if (errstr != NULL)
+ warnx("can't move object(s): %s", errstr);
+ gctl_free(req);
+ return;
+}
+
+void
+gvinum_printconfig(int argc, char **argv)
+{
+ printconfig(stdout, "");
+}
+
+void
+gvinum_parityop(int argc, char **argv, int rebuild)
+{
+ struct gctl_req *req;
+ int flags, i;
+ const char *errstr;
+ char *op, *msg;
+
+ if (rebuild) {
+ op = "rebuildparity";
+ msg = "Rebuilding";
+ } else {
+ op = "checkparity";
+ msg = "Checking";
+ }
+
+ optreset = 1;
+ optind = 1;
+ flags = 0;
+ while ((i = getopt(argc, argv, "fv")) != -1) {
+ switch (i) {
+ case 'f':
+ flags |= GV_FLAG_F;
+ break;
+ case 'v':
+ flags |= GV_FLAG_V;
+ break;
+ case '?':
+ default:
+ warnx("invalid flag '%c'", i);
+ return;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1) {
+ warn("usage: %s [-f] [-v] <plex>", op);
+ return;
+ }
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, op);
+ gctl_ro_param(req, "rebuild", sizeof(int), &rebuild);
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+ gctl_ro_param(req, "plex", -1, argv[0]);
+
+ errstr = gctl_issue(req);
+ if (errstr)
+ warnx("%s\n", errstr);
+ gctl_free(req);
+}
+
+/* Create a RAID-5 volume. */
+void
+gvinum_raid5(int argc, char **argv)
+{
+
+ if (argc < 2) {
+ warnx("usage:\traid5 [-fv] [-s stripesize] [-n name] drives\n");
+ return;
+ }
+ create_volume(argc, argv, "raid5");
+}
+
+
+void
+gvinum_rename(int argc, char **argv)
+{
+ struct gctl_req *req;
+ const char *errstr;
+ int flags, j;
+
+ flags = 0;
+
+ if (argc) {
+ optreset = 1;
+ optind = 1;
+ while ((j = getopt(argc, argv, "r")) != -1) {
+ switch (j) {
+ case 'r':
+ flags |= GV_FLAG_R;
+ break;
+ case '?':
+ default:
+ return;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ }
+
+ switch (argc) {
+ case 0:
+ warnx("no object to rename specified");
+ return;
+ case 1:
+ warnx("no new name specified");
+ return;
+ case 2:
+ break;
+ default:
+ warnx("more than one new name specified");
+ return;
+ }
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "rename");
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+ gctl_ro_param(req, "object", -1, argv[0]);
+ gctl_ro_param(req, "newname", -1, argv[1]);
+ errstr = gctl_issue(req);
+ if (errstr != NULL)
+ warnx("can't rename object: %s", errstr);
+ gctl_free(req);
+ return;
+}
+
+void
+gvinum_rm(int argc, char **argv)
+{
+ struct gctl_req *req;
+ int flags, i, j;
+ const char *errstr;
+ char buf[20], *cmd;
+
+ cmd = argv[0];
+ flags = 0;
+ optreset = 1;
+ optind = 1;
+ while ((j = getopt(argc, argv, "rf")) != -1) {
+ switch (j) {
+ case 'f':
+ flags |= GV_FLAG_F;
+ break;
+ case 'r':
+ flags |= GV_FLAG_R;
+ break;
+ case '?':
+ default:
+ return;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "remove");
+ gctl_ro_param(req, "argc", sizeof(int), &argc);
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+ if (argc) {
+ for (i = 0; i < argc; i++) {
+ snprintf(buf, sizeof(buf), "argv%d", i);
+ gctl_ro_param(req, buf, -1, argv[i]);
+ }
+ }
+ errstr = gctl_issue(req);
+ if (errstr != NULL) {
+ warnx("can't remove: %s", errstr);
+ gctl_free(req);
+ return;
+ }
+ gctl_free(req);
+}
+
+void
+gvinum_resetconfig(void)
+{
+ struct gctl_req *req;
+ const char *errstr;
+ char reply[32];
+
+ if (!isatty(STDIN_FILENO)) {
+ warn("Please enter this command from a tty device\n");
+ return;
+ }
+ printf(" WARNING! This command will completely wipe out your gvinum"
+ "configuration.\n"
+ " All data will be lost. If you really want to do this,"
+ " enter the text\n\n"
+ " NO FUTURE\n"
+ " Enter text -> ");
+ fgets(reply, sizeof(reply), stdin);
+ if (strcmp(reply, "NO FUTURE\n")) {
+ printf("\n No change\n");
+ return;
+ }
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "resetconfig");
+ errstr = gctl_issue(req);
+ if (errstr != NULL) {
+ warnx("can't reset config: %s", errstr);
+ gctl_free(req);
+ return;
+ }
+ gctl_free(req);
+ printf("gvinum configuration obliterated\n");
+}
+
+void
+gvinum_saveconfig(void)
+{
+ struct gctl_req *req;
+ const char *errstr;
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "saveconfig");
+ errstr = gctl_issue(req);
+ if (errstr != NULL)
+ warnx("can't save configuration: %s", errstr);
+ gctl_free(req);
+}
+
+void
+gvinum_start(int argc, char **argv)
+{
+ struct gctl_req *req;
+ int i, initsize, j;
+ const char *errstr;
+ char buf[20];
+
+ /* 'start' with no arguments is a no-op. */
+ if (argc == 1)
+ return;
+
+ initsize = 0;
+
+ optreset = 1;
+ optind = 1;
+ while ((j = getopt(argc, argv, "S")) != -1) {
+ switch (j) {
+ case 'S':
+ initsize = atoi(optarg);
+ break;
+ case '?':
+ default:
+ return;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!initsize)
+ initsize = 512;
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "start");
+ gctl_ro_param(req, "argc", sizeof(int), &argc);
+ gctl_ro_param(req, "initsize", sizeof(int), &initsize);
+ if (argc) {
+ for (i = 0; i < argc; i++) {
+ snprintf(buf, sizeof(buf), "argv%d", i);
+ gctl_ro_param(req, buf, -1, argv[i]);
+ }
+ }
+ errstr = gctl_issue(req);
+ if (errstr != NULL) {
+ warnx("can't start: %s", errstr);
+ gctl_free(req);
+ return;
+ }
+
+ gctl_free(req);
+}
+
+void
+gvinum_stop(int argc, char **argv)
+{
+ int err, fileid;
+
+ fileid = kldfind(GVINUMMOD);
+ if (fileid == -1) {
+ warn("cannot find " GVINUMMOD);
+ return;
+ }
+
+ /*
+ * This little hack prevents that we end up in an infinite loop in
+ * g_unload_class(). gv_unload() will return EAGAIN so that the GEOM
+ * event thread will be free for the g_wither_geom() call from
+ * gv_unload(). It's silly, but it works.
+ */
+ printf("unloading " GVINUMMOD " kernel module... ");
+ fflush(stdout);
+ if ((err = kldunload(fileid)) != 0 && (errno == EAGAIN)) {
+ sleep(1);
+ err = kldunload(fileid);
+ }
+ if (err != 0) {
+ printf(" failed!\n");
+ warn("cannot unload " GVINUMMOD);
+ return;
+ }
+
+ printf("done\n");
+ exit(0);
+}
+
+/* Create a striped volume. */
+void
+gvinum_stripe(int argc, char **argv)
+{
+
+ if (argc < 2) {
+ warnx("usage:\tstripe [-fv] [-n name] drives\n");
+ return;
+ }
+ create_volume(argc, argv, "stripe");
+}
+
+/* Grow a subdisk by adding disk backed by provider. */
+void
+gvinum_grow(int argc, char **argv)
+{
+ struct gctl_req *req;
+ char *drive, *sdname;
+ char sdprefix[GV_MAXSDNAME];
+ struct gv_drive *d;
+ struct gv_sd *s;
+ const char *errstr;
+ int drives, volumes, plexes, subdisks, flags;
+
+ drives = volumes = plexes = subdisks = 0;
+ if (argc < 3) {
+ warnx("usage:\tgrow plex drive\n");
+ return;
+ }
+
+ s = gv_alloc_sd();
+ if (s == NULL) {
+ warn("unable to create subdisk");
+ return;
+ }
+ d = gv_alloc_drive();
+ if (d == NULL) {
+ warn("unable to create drive");
+ free(s);
+ return;
+ }
+ /* Lookup device and set an appropriate drive name. */
+ drive = find_drive();
+ if (drive == NULL) {
+ warn("unable to find an appropriate drive name");
+ free(s);
+ free(d);
+ return;
+ }
+ strlcpy(d->name, drive, sizeof(d->name));
+ copy_device(d, argv[2]);
+
+ drives = 1;
+
+ /* We try to use the plex name as basis for the subdisk name. */
+ snprintf(sdprefix, sizeof(sdprefix), "%s.s", argv[1]);
+ sdname = find_name(sdprefix, GV_TYPE_SD, GV_MAXSDNAME);
+ if (sdname == NULL) {
+ warn("unable to find an appropriate subdisk name");
+ free(s);
+ free(d);
+ free(drive);
+ return;
+ }
+ strlcpy(s->name, sdname, sizeof(s->name));
+ free(sdname);
+ strlcpy(s->plex, argv[1], sizeof(s->plex));
+ strlcpy(s->drive, d->name, sizeof(s->drive));
+ subdisks = 1;
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "create");
+ gctl_ro_param(req, "flags", sizeof(int), &flags);
+ gctl_ro_param(req, "volumes", sizeof(int), &volumes);
+ gctl_ro_param(req, "plexes", sizeof(int), &plexes);
+ gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
+ gctl_ro_param(req, "drives", sizeof(int), &drives);
+ gctl_ro_param(req, "drive0", sizeof(*d), d);
+ gctl_ro_param(req, "sd0", sizeof(*s), s);
+ errstr = gctl_issue(req);
+ free(drive);
+ if (errstr != NULL) {
+ warnx("unable to grow plex: %s", errstr);
+ free(s);
+ free(d);
+ return;
+ }
+ gctl_free(req);
+}
+
+void
+parseline(int argc, char **argv)
+{
+ if (argc <= 0)
+ return;
+
+ if (!strcmp(argv[0], "create"))
+ gvinum_create(argc, argv);
+ else if (!strcmp(argv[0], "exit") || !strcmp(argv[0], "quit"))
+ exit(0);
+ else if (!strcmp(argv[0], "attach"))
+ gvinum_attach(argc, argv);
+ else if (!strcmp(argv[0], "detach"))
+ gvinum_detach(argc, argv);
+ else if (!strcmp(argv[0], "concat"))
+ gvinum_concat(argc, argv);
+ else if (!strcmp(argv[0], "grow"))
+ gvinum_grow(argc, argv);
+ else if (!strcmp(argv[0], "help"))
+ gvinum_help();
+ else if (!strcmp(argv[0], "list") || !strcmp(argv[0], "l"))
+ gvinum_list(argc, argv);
+ else if (!strcmp(argv[0], "ld"))
+ gvinum_list(argc, argv);
+ else if (!strcmp(argv[0], "lp"))
+ gvinum_list(argc, argv);
+ else if (!strcmp(argv[0], "ls"))
+ gvinum_list(argc, argv);
+ else if (!strcmp(argv[0], "lv"))
+ gvinum_list(argc, argv);
+ else if (!strcmp(argv[0], "mirror"))
+ gvinum_mirror(argc, argv);
+ else if (!strcmp(argv[0], "move"))
+ gvinum_move(argc, argv);
+ else if (!strcmp(argv[0], "mv"))
+ gvinum_move(argc, argv);
+ else if (!strcmp(argv[0], "printconfig"))
+ gvinum_printconfig(argc, argv);
+ else if (!strcmp(argv[0], "raid5"))
+ gvinum_raid5(argc, argv);
+ else if (!strcmp(argv[0], "rename"))
+ gvinum_rename(argc, argv);
+ else if (!strcmp(argv[0], "resetconfig"))
+ gvinum_resetconfig();
+ else if (!strcmp(argv[0], "rm"))
+ gvinum_rm(argc, argv);
+ else if (!strcmp(argv[0], "saveconfig"))
+ gvinum_saveconfig();
+ else if (!strcmp(argv[0], "setstate"))
+ gvinum_setstate(argc, argv);
+ else if (!strcmp(argv[0], "start"))
+ gvinum_start(argc, argv);
+ else if (!strcmp(argv[0], "stop"))
+ gvinum_stop(argc, argv);
+ else if (!strcmp(argv[0], "stripe"))
+ gvinum_stripe(argc, argv);
+ else if (!strcmp(argv[0], "checkparity"))
+ gvinum_parityop(argc, argv, 0);
+ else if (!strcmp(argv[0], "rebuildparity"))
+ gvinum_parityop(argc, argv, 1);
+ else
+ printf("unknown command '%s'\n", argv[0]);
+
+ return;
+}
+
+/*
+ * The guts of printconfig. This is called from gvinum_printconfig and from
+ * gvinum_create when called without an argument, in order to give the user
+ * something to edit.
+ */
+void
+printconfig(FILE *of, char *comment)
+{
+ struct gctl_req *req;
+ struct utsname uname_s;
+ const char *errstr;
+ time_t now;
+ char buf[GV_CFG_LEN + 1];
+
+ uname(&uname_s);
+ time(&now);
+
+ req = gctl_get_handle();
+ gctl_ro_param(req, "class", -1, "VINUM");
+ gctl_ro_param(req, "verb", -1, "getconfig");
+ gctl_ro_param(req, "comment", -1, comment);
+ gctl_rw_param(req, "config", sizeof(buf), buf);
+ errstr = gctl_issue(req);
+ if (errstr != NULL) {
+ warnx("can't get configuration: %s", errstr);
+ return;
+ }
+ gctl_free(req);
+
+ fprintf(of, "# Vinum configuration of %s, saved at %s",
+ uname_s.nodename,
+ ctime(&now));
+
+ if (*comment != '\0')
+ fprintf(of, "# Current configuration:\n");
+
+ fprintf(of, "%s", buf);
+}
diff --git a/sbin/gvinum/gvinum.h b/sbin/gvinum/gvinum.h
new file mode 100644
index 000000000000..d1b45a07749b
--- /dev/null
+++ b/sbin/gvinum/gvinum.h
@@ -0,0 +1,39 @@
+/*-
+ * Copyright (c) 1997, 1998
+ * Nan Yang Computer Services Limited. All rights reserved.
+ *
+ * This software is distributed under the so-called ``Berkeley
+ * License'':
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Nan Yang Computer
+ * Services Limited.
+ * 4. Neither the name of the Company nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * This software is provided ``as is'', and any express or implied
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose are disclaimed.
+ * In no event shall the company or contributors be liable for any
+ * direct, indirect, incidental, special, exemplary, or consequential
+ * damages (including, but not limited to, procurement of substitute
+ * goods or services; loss of use, data, or profits; or business
+ * interruption) however caused and on any theory of liability, whether
+ * in contract, strict liability, or tort (including negligence or
+ * otherwise) arising in any way out of the use of this software, even if
+ * advised of the possibility of such damage.
+ */
+
+/* $FreeBSD$ */
+
+#define GVINUMMOD "geom_vinum"