mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-14 10:09:48 +00:00
b095b120af
vinum_init(): Change name of variable plexindex to objindex, which better describes its purpose. initsd(): Add a second parameter to determine whether it should wait for completion or not. This allows it to DTRT when called with the -w flag either directly or via initplex(). Add 'setstate' command (function vinum_setstate ()) to implement the VINUM_SETSTATE_FORCE ioctl for diddling individual object states. This is a repair tool which can also be used for panicing the system. Use with utmost care if at all. Add unimplemented commands 'checkparity' and 'rebuildparity'. Watch this space.
1864 lines
55 KiB
C
1864 lines
55 KiB
C
/* commands.c: vinum interface program, main commands */
|
|
/*-
|
|
* Copyright (c) 1997, 1998
|
|
* Nan Yang Computer Services Limited. All rights reserved.
|
|
*
|
|
* Written by Greg Lehey
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
/* $Id: commands.c,v 1.7 1999/07/03 05:52:32 grog Exp grog $ */
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <sys/mman.h>
|
|
#include <libutil.h>
|
|
#include <netdb.h>
|
|
#include <setjmp.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
#include <unistd.h>
|
|
#include <sys/ioctl.h>
|
|
#include <dev/vinum/vinumhdr.h>
|
|
#include "vext.h"
|
|
#include <sys/types.h>
|
|
#include <sys/linker.h>
|
|
#include <sys/module.h>
|
|
#include <sys/wait.h>
|
|
#include <readline/history.h>
|
|
#include <readline/readline.h>
|
|
#include <devstat.h>
|
|
|
|
static void dorename(struct vinum_rename_msg *msg, const char *oldname, const char *name, int maxlen);
|
|
|
|
void
|
|
vinum_create(int argc, char *argv[], char *arg0[])
|
|
{
|
|
int error;
|
|
FILE *dfd; /* file descriptor for the config file */
|
|
char buffer[BUFSIZE]; /* read config file in here */
|
|
char commandline[BUFSIZE]; /* issue command from here */
|
|
struct _ioctl_reply *reply;
|
|
int ioctltype; /* for ioctl call */
|
|
char tempfile[PATH_MAX]; /* name of temp file for direct editing */
|
|
char *file; /* file to read */
|
|
FILE *tf; /* temp file */
|
|
|
|
if (argc == 0) { /* no args, */
|
|
char *editor; /* editor to start */
|
|
int status;
|
|
|
|
editor = getenv("EDITOR");
|
|
if (editor == NULL)
|
|
editor = "/usr/bin/vi";
|
|
sprintf(tempfile, "/var/tmp/" VINUMMOD ".create.%d", getpid()); /* create a temp file */
|
|
tf = fopen(tempfile, "w"); /* open it */
|
|
if (tf == NULL) {
|
|
fprintf(stderr, "Can't open %s: %s\n", argv[0], strerror(errno));
|
|
return;
|
|
}
|
|
printconfig(tf, "# "); /* and put the current config it */
|
|
fclose(tf);
|
|
sprintf(commandline, "%s %s", editor, tempfile); /* create an edit command */
|
|
status = system(commandline); /* do it */
|
|
if (status != 0) {
|
|
fprintf(stderr, "Can't edit config: status %d\n", status);
|
|
return;
|
|
}
|
|
file = tempfile;
|
|
} else if (argc == 1)
|
|
file = argv[0];
|
|
else {
|
|
fprintf(stderr, "Expecting 1 parameter, not %d\n", argc);
|
|
return;
|
|
}
|
|
reply = (struct _ioctl_reply *) &buffer;
|
|
dfd = fopen(file, "r");
|
|
if (dfd == NULL) { /* no go */
|
|
fprintf(stderr, "Can't open %s: %s\n", file, strerror(errno));
|
|
return;
|
|
}
|
|
if (ioctl(superdev, VINUM_STARTCONFIG, &force)) { /* can't get config? */
|
|
printf("Can't configure: %s (%d)\n", strerror(errno), errno);
|
|
return;
|
|
}
|
|
file_line = 0; /* start with line 1 */
|
|
/* Parse the configuration, and add it to the global configuration */
|
|
for (;;) { /* love this style(9) */
|
|
char *configline;
|
|
|
|
configline = fgets(buffer, BUFSIZE, dfd);
|
|
if (history)
|
|
fprintf(history, "%s", buffer);
|
|
|
|
if (configline == NULL) {
|
|
if (ferror(dfd))
|
|
perror("Can't read config file");
|
|
break;
|
|
}
|
|
file_line++; /* count the lines */
|
|
if (verbose)
|
|
printf("%4d: %s", file_line, buffer);
|
|
strcpy(commandline, buffer); /* make a copy */
|
|
ioctl(superdev, VINUM_CREATE, buffer);
|
|
if (reply->error != 0) { /* error in config */
|
|
if (!verbose) /* print this line anyway */
|
|
printf("%4d: %s", file_line, commandline);
|
|
fprintf(stdout, "** %d %s: %s\n",
|
|
file_line,
|
|
reply->msg,
|
|
strerror(reply->error));
|
|
|
|
/*
|
|
* XXX at the moment, we reset the config
|
|
* lock on error, so try to get it again.
|
|
* If we fail, don't cry again.
|
|
*/
|
|
if (ioctl(superdev, VINUM_STARTCONFIG, &force)) /* can't get config? */
|
|
return;
|
|
}
|
|
}
|
|
fclose(dfd); /* done with the config file */
|
|
ioctltype = 0; /* saveconfig after update */
|
|
error = ioctl(superdev, VINUM_SAVECONFIG, &ioctltype); /* save the config to disk */
|
|
if (error != 0)
|
|
perror("Can't save Vinum config");
|
|
make_devices();
|
|
listconfig();
|
|
}
|
|
|
|
/* Read vinum config from a disk */
|
|
void
|
|
vinum_read(int argc, char *argv[], char *arg0[])
|
|
{
|
|
int error;
|
|
char buffer[BUFSIZE]; /* read config file in here */
|
|
struct _ioctl_reply *reply;
|
|
int i;
|
|
|
|
reply = (struct _ioctl_reply *) &buffer;
|
|
if (argc < 1) { /* wrong arg count */
|
|
fprintf(stderr, "Usage: read drive [drive ...]\n");
|
|
return;
|
|
}
|
|
strcpy(buffer, "read ");
|
|
for (i = 0; i < argc; i++) { /* each drive name */
|
|
strcat(buffer, argv[i]);
|
|
strcat(buffer, " ");
|
|
}
|
|
|
|
if (ioctl(superdev, VINUM_STARTCONFIG, &force)) { /* can't get config? */
|
|
fprintf(stderr, "Can't configure: %s (%d)\n", strerror(errno), errno);
|
|
return;
|
|
}
|
|
ioctl(superdev, VINUM_CREATE, &buffer);
|
|
if (reply->error != 0) { /* error in config */
|
|
fprintf(stdout, "** %s: %s\n", reply->msg, strerror(reply->error));
|
|
error = ioctl(superdev, VINUM_RELEASECONFIG, NULL); /* save the config to disk */
|
|
if (error != 0)
|
|
perror("Can't save Vinum config");
|
|
} else {
|
|
error = ioctl(superdev, VINUM_RELEASECONFIG, NULL); /* save the config to disk */
|
|
if (error != 0)
|
|
perror("Can't save Vinum config");
|
|
make_devices();
|
|
}
|
|
}
|
|
|
|
#ifdef VINUMDEBUG
|
|
void
|
|
vinum_debug(int argc, char *argv[], char *arg0[])
|
|
{
|
|
struct debuginfo info;
|
|
|
|
if (argc > 0) {
|
|
info.param = atoi(argv[0]);
|
|
info.changeit = 1;
|
|
} else {
|
|
info.changeit = 0;
|
|
sleep(2); /* give a chance to leave the window */
|
|
}
|
|
ioctl(superdev, VINUM_DEBUG, (caddr_t) & info);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
vinum_modify(int argc, char *argv[], char *arg0[])
|
|
{
|
|
fprintf(stderr, "Modify command is currently not implemented\n");
|
|
}
|
|
|
|
void
|
|
vinum_set(int argc, char *argv[], char *arg0[])
|
|
{
|
|
fprintf(stderr, "set is not implemented yet\n");
|
|
}
|
|
|
|
void
|
|
vinum_rm(int argc, char *argv[], char *arg0[])
|
|
{
|
|
int object;
|
|
struct _ioctl_reply reply;
|
|
struct vinum_ioctl_msg *message = (struct vinum_ioctl_msg *) &reply;
|
|
|
|
if (argc == 0) /* start everything */
|
|
fprintf(stderr, "Usage: rm object [object...]\n");
|
|
else { /* start specified objects */
|
|
int index;
|
|
enum objecttype type;
|
|
|
|
for (index = 0; index < argc; index++) {
|
|
object = find_object(argv[index], &type); /* look for it */
|
|
if (type == invalid_object)
|
|
fprintf(stderr, "Can't find object: %s\n", argv[index]);
|
|
else {
|
|
message->index = object; /* pass object number */
|
|
message->type = type; /* and type of object */
|
|
message->force = force; /* do we want to force the operation? */
|
|
message->recurse = recurse; /* do we want to remove subordinates? */
|
|
ioctl(superdev, VINUM_REMOVE, message);
|
|
if (reply.error != 0) {
|
|
fprintf(stderr,
|
|
"Can't remove %s: %s (%d)\n",
|
|
argv[index],
|
|
reply.msg[0] ? reply.msg : strerror(reply.error),
|
|
reply.error);
|
|
} else if (verbose)
|
|
fprintf(stderr, "%s removed\n", argv[index]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
vinum_resetconfig(int argc, char *argv[], char *arg0[])
|
|
{
|
|
char reply[32];
|
|
int error;
|
|
|
|
printf(" WARNING! This command will completely wipe out your vinum 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")) /* changed his mind */
|
|
printf("\n No change\n");
|
|
else {
|
|
error = ioctl(superdev, VINUM_RESETCONFIG, NULL); /* trash config on disk */
|
|
if (error) {
|
|
if (errno == EBUSY)
|
|
fprintf(stderr, "Can't reset configuration: objects are in use\n");
|
|
else
|
|
perror("Can't find vinum config");
|
|
} else {
|
|
make_devices(); /* recreate the /dev/vinum hierarchy */
|
|
printf("\b Vinum configuration obliterated\n");
|
|
start_daemon(); /* then restart the daemon */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Initialize a plex */
|
|
void
|
|
vinum_init(int argc, char *argv[], char *arg0[])
|
|
{
|
|
if (argc > 0) { /* initialize plexes */
|
|
int objindex;
|
|
int objno;
|
|
enum objecttype type; /* type returned */
|
|
|
|
if (history)
|
|
fflush(history); /* don't let all the kids do it. */
|
|
for (objindex = 0; objindex < argc; objindex++) {
|
|
objno = find_object(argv[objindex], &type); /* find the object */
|
|
if (objno < 0)
|
|
printf("Can't find %s\n", argv[objindex]);
|
|
else {
|
|
switch (type) {
|
|
case volume_object:
|
|
initvol(objno);
|
|
break;
|
|
|
|
case plex_object:
|
|
initplex(objno, argv[objindex]);
|
|
break;
|
|
|
|
case sd_object:
|
|
initsd(objno, dowait);
|
|
break;
|
|
|
|
default:
|
|
printf("Can't initalize %s: wrong object type\n", argv[objindex]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
initvol(int volno)
|
|
{
|
|
printf("Not implemented yet\n");
|
|
}
|
|
|
|
void
|
|
initplex(int plexno, char *name)
|
|
{
|
|
int sdno;
|
|
int plexfh = NULL; /* file handle for plex */
|
|
pid_t pid;
|
|
char filename[MAXPATHLEN]; /* create a file name here */
|
|
|
|
/* Variables for use by children */
|
|
int failed = 0; /* set if a child dies badly */
|
|
|
|
sprintf(filename, VINUM_DIR "/plex/%s", name);
|
|
if ((plexfh = open(filename, O_RDWR, S_IRWXU)) < 0) { /* got a plex, open it */
|
|
/*
|
|
* We don't actually write anything to the
|
|
* plex. We open it to ensure that nobody
|
|
* else tries to open it while we initialize
|
|
* its subdisks.
|
|
*/
|
|
fprintf(stderr, "can't open plex %s: %s\n", filename, strerror(errno));
|
|
return;
|
|
}
|
|
if (dowait == 0) {
|
|
pid = fork(); /* into the background with you */
|
|
if (pid != 0) { /* I'm the parent, or we failed */
|
|
if (pid < 0) /* failure */
|
|
printf("Couldn't fork: %s", strerror(errno));
|
|
close(plexfh); /* we don't need this any more */
|
|
return;
|
|
}
|
|
}
|
|
/*
|
|
* If we get here, we're either the first-level
|
|
* child (if we're not waiting) or we're going
|
|
* to wait.
|
|
*/
|
|
for (sdno = 0; sdno < plex.subdisks; sdno++) { /* initialize each subdisk */
|
|
get_plex_sd_info(&sd, plexno, sdno);
|
|
initsd(sd.sdno, 0);
|
|
}
|
|
/* Now wait for them to complete */
|
|
while (1) {
|
|
int status;
|
|
pid = wait(&status);
|
|
if (((int) pid == -1)
|
|
&& (errno == ECHILD)) /* all gone */
|
|
break;
|
|
if (WEXITSTATUS(status) != 0) { /* oh, oh */
|
|
printf("child %d exited with status 0x%x\n", pid, WEXITSTATUS(status));
|
|
failed++;
|
|
}
|
|
}
|
|
if (failed == 0) {
|
|
#if 0
|
|
message->index = plexno; /* pass object number */
|
|
message->type = plex_object; /* and type of object */
|
|
message->state = object_up;
|
|
message->force = 1; /* insist */
|
|
ioctl(superdev, VINUM_SETSTATE, message);
|
|
#endif
|
|
syslog(LOG_INFO | LOG_KERN, "plex %s initialized", plex.name);
|
|
} else
|
|
syslog(LOG_ERR | LOG_KERN, "couldn't initialize plex %s, %d processes died",
|
|
plex.name,
|
|
failed);
|
|
if (dowait == 0) /* we're the waiting child, */
|
|
exit(0); /* we've done our dash */
|
|
}
|
|
|
|
void
|
|
initsd(int sdno, int dowait)
|
|
{
|
|
pid_t pid;
|
|
struct _ioctl_reply reply;
|
|
struct vinum_ioctl_msg *message = (struct vinum_ioctl_msg *) &reply;
|
|
char filename[MAXPATHLEN]; /* create a file name here */
|
|
|
|
/* Variables for use by children */
|
|
int sdfh; /* and for subdisk */
|
|
char zeros[PLEXINITSIZE];
|
|
int count; /* write count */
|
|
long long offset; /* offset in subdisk */
|
|
long long sdsize; /* size of subdisk */
|
|
|
|
if (dowait == 0) {
|
|
pid = fork(); /* into the background with you */
|
|
if (pid > 0) /* I'm the parent */
|
|
return;
|
|
else if (pid < 0) { /* failure */
|
|
printf("couldn't fork for subdisk %d: %s", sdno, strerror(errno));
|
|
return;
|
|
}
|
|
}
|
|
openlog("vinum", LOG_CONS | LOG_PERROR | LOG_PID, LOG_KERN);
|
|
bzero(zeros, sizeof(zeros));
|
|
get_sd_info(&sd, sdno);
|
|
sdsize = sd.sectors * DEV_BSIZE; /* size of subdisk in bytes */
|
|
sprintf(filename, VINUM_DIR "/rsd/%s", sd.name);
|
|
setproctitle("initializing %s", filename); /* show what we're doing */
|
|
syslog(LOG_INFO | LOG_KERN, "initializing subdisk %s", filename);
|
|
if ((sdfh = open(filename, O_RDWR, S_IRWXU)) < 0) { /* no go */
|
|
syslog(LOG_ERR | LOG_KERN,
|
|
"can't open subdisk %s: %s",
|
|
filename,
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
/* Set the subdisk in initializing state */
|
|
message->index = sd.sdno; /* pass object number */
|
|
message->type = sd_object; /* and type of object */
|
|
message->state = object_initializing;
|
|
message->force = 1; /* insist */
|
|
ioctl(superdev, VINUM_SETSTATE, message);
|
|
for (offset = 0; offset < sdsize; offset += count) {
|
|
count = write(sdfh, zeros, PLEXINITSIZE); /* write a block */
|
|
if (count < 0) {
|
|
syslog(LOG_ERR | LOG_KERN,
|
|
"can't write subdisk %s: %s",
|
|
filename,
|
|
strerror(errno));
|
|
exit(1);
|
|
} else if (count == 0)
|
|
break;
|
|
}
|
|
syslog(LOG_INFO | LOG_KERN, "subdisk %s initialized", filename);
|
|
/* Bring the subdisk up */
|
|
message->index = sd.sdno; /* pass object number */
|
|
message->type = sd_object; /* and type of object */
|
|
message->state = object_initialized;
|
|
message->force = 0; /* don't insist */
|
|
ioctl(superdev, VINUM_SETSTATE, message);
|
|
exit(0);
|
|
}
|
|
|
|
void
|
|
vinum_start(int argc, char *argv[], char *arg0[])
|
|
{
|
|
int object;
|
|
struct _ioctl_reply reply;
|
|
struct vinum_ioctl_msg *message = (struct vinum_ioctl_msg *) &reply;
|
|
|
|
if (argc == 0) { /* start everything */
|
|
int devs = getnumdevs();
|
|
struct statinfo statinfo;
|
|
char *namelist;
|
|
char *enamelist; /* end of name list */
|
|
int i;
|
|
char **token; /* list of tokens */
|
|
int tokens; /* and their number */
|
|
|
|
statinfo.dinfo = malloc(devs * sizeof(struct statinfo));
|
|
namelist = malloc(devs * (DEVSTAT_NAME_LEN + 8));
|
|
token = malloc((devs + 1) * sizeof(char *));
|
|
if ((statinfo.dinfo == NULL) || (namelist == NULL) || (token == NULL)) {
|
|
fprintf(stderr, "Can't allocate memory for drive list\n");
|
|
return;
|
|
}
|
|
tokens = 0; /* no tokens yet */
|
|
if (getdevs(&statinfo) < 0) { /* find out what devices we have */
|
|
perror("Can't get device list");
|
|
return;
|
|
}
|
|
namelist[0] = '\0'; /* start with empty namelist */
|
|
enamelist = namelist; /* point to the end of the list */
|
|
|
|
for (i = 0; i < devs; i++) {
|
|
struct devstat *stat = &statinfo.dinfo->devices[i];
|
|
|
|
if (((stat->device_type & DEVSTAT_TYPE_MASK) == DEVSTAT_TYPE_DIRECT) /* disk device */
|
|
&&((stat->device_type & DEVSTAT_TYPE_PASS) == 0) /* and not passthrough */
|
|
&&((stat->device_name[0] != '\0'))) { /* and it has a name */
|
|
sprintf(enamelist, "/dev/%s%d", stat->device_name, stat->unit_number);
|
|
token[tokens] = enamelist; /* point to it */
|
|
tokens++; /* one more token */
|
|
enamelist = &enamelist[strlen(enamelist) + 1]; /* and start beyond the end */
|
|
}
|
|
}
|
|
free(statinfo.dinfo); /* don't need the list any more */
|
|
vinum_read(tokens, token, &token[0]); /* start the system */
|
|
free(namelist);
|
|
free(token);
|
|
list_defective_objects(); /* and list anything that's down */
|
|
} else { /* start specified objects */
|
|
int index;
|
|
enum objecttype type;
|
|
|
|
for (index = 0; index < argc; index++) {
|
|
object = find_object(argv[index], &type); /* look for it */
|
|
if (type == invalid_object)
|
|
fprintf(stderr, "Can't find object: %s\n", argv[index]);
|
|
else {
|
|
int doit = 0; /* set to 1 if we pass our tests */
|
|
switch (type) {
|
|
case drive_object:
|
|
if (drive.state == drive_up) /* already up */
|
|
fprintf(stderr, "%s is already up\n", drive.label.name);
|
|
else
|
|
doit = 1;
|
|
break;
|
|
|
|
case sd_object:
|
|
if (sd.state == sd_up) /* already up */
|
|
fprintf(stderr, "%s is already up\n", sd.name);
|
|
else
|
|
doit = 1;
|
|
break;
|
|
|
|
case plex_object:
|
|
if (plex.state == plex_up) /* already up */
|
|
fprintf(stderr, "%s is already up\n", plex.name);
|
|
else {
|
|
int sdno;
|
|
|
|
for (sdno = 0; sdno < plex.subdisks; sdno++) {
|
|
get_plex_sd_info(&sd, object, sdno);
|
|
if ((sd.state >= sd_empty)
|
|
&& (sd.state <= sd_reviving)) { /* candidate for init */
|
|
message->index = sd.sdno; /* pass object number */
|
|
message->type = sd_object; /* it's a subdisk */
|
|
message->state = object_up;
|
|
message->force = force; /* don't force it, use a larger hammer */
|
|
ioctl(superdev, VINUM_SETSTATE, message);
|
|
if (reply.error != 0) {
|
|
if (reply.error == EAGAIN) /* we're reviving */
|
|
continue_revive(sd.sdno);
|
|
else
|
|
fprintf(stderr,
|
|
"Can't start %s: %s (%d)\n",
|
|
sd.name,
|
|
reply.msg[0] ? reply.msg : strerror(reply.error),
|
|
reply.error);
|
|
}
|
|
if (Verbose)
|
|
vinum_lsi(sd.sdno, 0);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case volume_object:
|
|
if (vol.state == volume_up) /* already up */
|
|
fprintf(stderr, "%s is already up\n", vol.name);
|
|
else
|
|
doit = 1;
|
|
break;
|
|
|
|
default:
|
|
}
|
|
|
|
if (doit) {
|
|
message->index = object; /* pass object number */
|
|
message->type = type; /* and type of object */
|
|
message->state = object_up;
|
|
message->force = force; /* don't force it, use a larger hammer */
|
|
ioctl(superdev, VINUM_SETSTATE, message);
|
|
if (reply.error != 0) {
|
|
if ((reply.error == EAGAIN) /* we're reviving */
|
|
&&(type == sd_object))
|
|
continue_revive(object);
|
|
else
|
|
fprintf(stderr,
|
|
"Can't start %s: %s (%d)\n",
|
|
argv[index],
|
|
reply.msg[0] ? reply.msg : strerror(reply.error),
|
|
reply.error);
|
|
}
|
|
if (Verbose)
|
|
vinum_li(object, type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
vinum_stop(int argc, char *argv[], char *arg0[])
|
|
{
|
|
int object;
|
|
struct _ioctl_reply reply;
|
|
struct vinum_ioctl_msg *message = (struct vinum_ioctl_msg *) &reply;
|
|
|
|
message->force = force; /* should we force the transition? */
|
|
if (argc == 0) { /* stop vinum */
|
|
int fileid = 0; /* ID of Vinum kld */
|
|
|
|
close(superdev); /* we can't stop if we have vinum open */
|
|
sleep(1); /* wait for the daemon to let go */
|
|
fileid = kldfind(VINUMMOD);
|
|
if ((fileid < 0) /* no go */
|
|
||(kldunload(fileid) < 0))
|
|
perror("Can't unload " VINUMMOD);
|
|
else {
|
|
fprintf(stderr, VINUMMOD " unloaded\n");
|
|
exit(0);
|
|
}
|
|
|
|
/* If we got here, the stop failed. Reopen the superdevice. */
|
|
superdev = open(VINUM_SUPERDEV_NAME, O_RDWR); /* reopen vinum superdevice */
|
|
if (superdev < 0) {
|
|
perror("Can't reopen Vinum superdevice");
|
|
exit(1);
|
|
}
|
|
} else { /* stop specified objects */
|
|
int i;
|
|
enum objecttype type;
|
|
|
|
for (i = 0; i < argc; i++) {
|
|
object = find_object(argv[i], &type); /* look for it */
|
|
if (type == invalid_object)
|
|
fprintf(stderr, "Can't find object: %s\n", argv[i]);
|
|
else {
|
|
message->index = object; /* pass object number */
|
|
message->type = type; /* and type of object */
|
|
message->state = object_down;
|
|
ioctl(superdev, VINUM_SETSTATE, message);
|
|
if (reply.error != 0)
|
|
fprintf(stderr,
|
|
"Can't stop %s: %s (%d)\n",
|
|
argv[i],
|
|
reply.msg[0] ? reply.msg : strerror(reply.error),
|
|
reply.error);
|
|
if (Verbose)
|
|
vinum_li(object, type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
vinum_label(int argc, char *argv[], char *arg0[])
|
|
{
|
|
int object;
|
|
struct _ioctl_reply reply;
|
|
int *message = (int *) &reply;
|
|
|
|
if (argc == 0) /* start everything */
|
|
fprintf(stderr, "label: please specify one or more volume names\n");
|
|
else { /* start specified objects */
|
|
int i;
|
|
enum objecttype type;
|
|
|
|
for (i = 0; i < argc; i++) {
|
|
object = find_object(argv[i], &type); /* look for it */
|
|
if (type == invalid_object)
|
|
fprintf(stderr, "Can't find object: %s\n", argv[i]);
|
|
else if (type != volume_object) /* it exists, but it isn't a volume */
|
|
fprintf(stderr, "%s is not a volume\n", argv[i]);
|
|
else {
|
|
message[0] = object; /* pass object number */
|
|
ioctl(superdev, VINUM_LABEL, message);
|
|
if (reply.error != 0)
|
|
fprintf(stderr,
|
|
"Can't label %s: %s (%d)\n",
|
|
argv[i],
|
|
reply.msg[0] ? reply.msg : strerror(reply.error),
|
|
reply.error);
|
|
if (Verbose)
|
|
vinum_li(object, type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
reset_volume_stats(int volno, int recurse)
|
|
{
|
|
struct vinum_ioctl_msg msg;
|
|
struct _ioctl_reply *reply = (struct _ioctl_reply *) &msg;
|
|
|
|
msg.index = volno;
|
|
msg.type = volume_object;
|
|
/* XXX get these numbers right if we ever
|
|
* actually return errors */
|
|
if (ioctl(superdev, VINUM_RESETSTATS, &msg) < 0) {
|
|
fprintf(stderr, "Can't reset stats for volume %d: %s\n", volno, reply->msg);
|
|
longjmp(command_fail, -1);
|
|
} else if (recurse) {
|
|
struct volume vol;
|
|
int plexno;
|
|
|
|
get_volume_info(&vol, volno);
|
|
for (plexno = 0; plexno < vol.plexes; plexno++)
|
|
reset_plex_stats(vol.plex[plexno], recurse);
|
|
}
|
|
}
|
|
|
|
void
|
|
reset_plex_stats(int plexno, int recurse)
|
|
{
|
|
struct vinum_ioctl_msg msg;
|
|
struct _ioctl_reply *reply = (struct _ioctl_reply *) &msg;
|
|
|
|
msg.index = plexno;
|
|
msg.type = plex_object;
|
|
/* XXX get these numbers right if we ever
|
|
* actually return errors */
|
|
if (ioctl(superdev, VINUM_RESETSTATS, &msg) < 0) {
|
|
fprintf(stderr, "Can't reset stats for plex %d: %s\n", plexno, reply->msg);
|
|
longjmp(command_fail, -1);
|
|
} else if (recurse) {
|
|
struct plex plex;
|
|
struct sd sd;
|
|
int sdno;
|
|
|
|
get_plex_info(&plex, plexno);
|
|
for (sdno = 0; sdno < plex.subdisks; sdno++) {
|
|
get_plex_sd_info(&sd, plex.plexno, sdno);
|
|
reset_sd_stats(sd.sdno, recurse);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
reset_sd_stats(int sdno, int recurse)
|
|
{
|
|
struct vinum_ioctl_msg msg;
|
|
struct _ioctl_reply *reply = (struct _ioctl_reply *) &msg;
|
|
|
|
msg.index = sdno;
|
|
msg.type = sd_object;
|
|
/* XXX get these numbers right if we ever
|
|
* actually return errors */
|
|
if (ioctl(superdev, VINUM_RESETSTATS, &msg) < 0) {
|
|
fprintf(stderr, "Can't reset stats for subdisk %d: %s\n", sdno, reply->msg);
|
|
longjmp(command_fail, -1);
|
|
} else if (recurse) {
|
|
get_sd_info(&sd, sdno); /* get the info */
|
|
reset_drive_stats(sd.driveno); /* and clear the drive */
|
|
}
|
|
}
|
|
|
|
void
|
|
reset_drive_stats(int driveno)
|
|
{
|
|
struct vinum_ioctl_msg msg;
|
|
struct _ioctl_reply *reply = (struct _ioctl_reply *) &msg;
|
|
|
|
msg.index = driveno;
|
|
msg.type = drive_object;
|
|
/* XXX get these numbers right if we ever
|
|
* actually return errors */
|
|
if (ioctl(superdev, VINUM_RESETSTATS, &msg) < 0) {
|
|
fprintf(stderr, "Can't reset stats for drive %d: %s\n", driveno, reply->msg);
|
|
longjmp(command_fail, -1);
|
|
}
|
|
}
|
|
|
|
void
|
|
vinum_resetstats(int argc, char *argv[], char *argv0[])
|
|
{
|
|
int i;
|
|
int objno;
|
|
enum objecttype type;
|
|
|
|
if (ioctl(superdev, VINUM_GETCONFIG, &vinum_conf) < 0) {
|
|
perror("Can't get vinum config");
|
|
return;
|
|
}
|
|
if (argc == 0) {
|
|
for (objno = 0; objno < vinum_conf.volumes_allocated; objno++)
|
|
reset_volume_stats(objno, 1); /* clear everything recursively */
|
|
} else {
|
|
for (i = 0; i < argc; i++) {
|
|
objno = find_object(argv[i], &type);
|
|
if (objno >= 0) { /* not invalid */
|
|
switch (type) {
|
|
case drive_object:
|
|
reset_drive_stats(objno);
|
|
break;
|
|
|
|
case sd_object:
|
|
reset_sd_stats(objno, recurse);
|
|
break;
|
|
|
|
case plex_object:
|
|
reset_plex_stats(objno, recurse);
|
|
break;
|
|
|
|
case volume_object:
|
|
reset_volume_stats(objno, recurse);
|
|
break;
|
|
|
|
case invalid_object: /* can't get this */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Attach a subdisk to a plex, or a plex to a volume.
|
|
* attach subdisk plex [offset] [rename]
|
|
* attach plex volume [rename]
|
|
*/
|
|
void
|
|
vinum_attach(int argc, char *argv[], char *argv0[])
|
|
{
|
|
int i;
|
|
enum objecttype supertype;
|
|
struct vinum_ioctl_msg msg;
|
|
struct _ioctl_reply *reply = (struct _ioctl_reply *) &msg;
|
|
const char *objname = argv[0];
|
|
const char *supername = argv[1];
|
|
int sdno = -1;
|
|
int plexno = -1;
|
|
char newname[MAXNAME + 8];
|
|
int rename = 0; /* set if we want to rename the object */
|
|
|
|
if ((argc < 2)
|
|
|| (argc > 4)) {
|
|
fprintf(stderr,
|
|
"Usage: \tattach <subdisk> <plex> [rename] [<plexoffset>]\n"
|
|
"\tattach <plex> <volume> [rename]\n");
|
|
return;
|
|
}
|
|
if (ioctl(superdev, VINUM_GETCONFIG, &vinum_conf) < 0) {
|
|
perror("Can't get vinum config");
|
|
return;
|
|
}
|
|
msg.index = find_object(objname, &msg.type); /* find the object to attach */
|
|
msg.otherobject = find_object(supername, &supertype); /* and the object to attach to */
|
|
msg.force = force; /* did we specify the use of force? */
|
|
msg.recurse = recurse;
|
|
msg.offset = -1; /* and no offset */
|
|
|
|
for (i = 2; i < argc; i++) {
|
|
if (!strcmp(argv[i], "rename")) {
|
|
rename = 1;
|
|
msg.rename = 1; /* do renaming */
|
|
} else if (!isdigit(argv[i][0])) { /* not an offset */
|
|
fprintf(stderr, "Unknown attribute: %s\n", supername);
|
|
return;
|
|
} else
|
|
msg.offset = sizespec(argv[i]);
|
|
}
|
|
|
|
switch (msg.type) {
|
|
case sd_object:
|
|
find_object(argv[1], &supertype);
|
|
if (supertype != plex_object) { /* huh? */
|
|
fprintf(stderr, "%s can only be attached to a plex\n", objname);
|
|
return;
|
|
}
|
|
if (plex.organization != plex_concat) { /* not a cat plex, */
|
|
fprintf(stderr, "Can't attach subdisks to a %s plex\n", plex_org(plex.organization));
|
|
return;
|
|
}
|
|
sdno = msg.index; /* note the subdisk number for later */
|
|
break;
|
|
|
|
case plex_object:
|
|
find_object(argv[1], &supertype);
|
|
if (supertype != volume_object) { /* huh? */
|
|
fprintf(stderr, "%s can only be attached to a volume\n", objname);
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case volume_object:
|
|
case drive_object:
|
|
fprintf(stderr, "Can only attach subdisks and plexes\n");
|
|
return;
|
|
|
|
default:
|
|
fprintf(stderr, "%s is not a Vinum object\n", objname);
|
|
return;
|
|
}
|
|
|
|
ioctl(superdev, VINUM_ATTACH, &msg);
|
|
if (reply->error != 0) {
|
|
if (reply->error == EAGAIN) /* reviving */
|
|
continue_revive(sdno); /* continue the revive */
|
|
else
|
|
fprintf(stderr,
|
|
"Can't attach %s to %s: %s (%d)\n",
|
|
objname,
|
|
supername,
|
|
reply->msg[0] ? reply->msg : strerror(reply->error),
|
|
reply->error);
|
|
}
|
|
if (rename) {
|
|
struct sd;
|
|
struct plex;
|
|
struct volume;
|
|
|
|
/* we've overwritten msg with the
|
|
* ioctl reply, start again */
|
|
msg.index = find_object(objname, &msg.type); /* find the object to rename */
|
|
switch (msg.type) {
|
|
case sd_object:
|
|
get_sd_info(&sd, msg.index);
|
|
get_plex_info(&plex, sd.plexno);
|
|
for (sdno = 0; sdno < plex.subdisks; sdno++) {
|
|
if (plex.sdnos[sdno] == msg.index) /* found our subdisk */
|
|
break;
|
|
}
|
|
sprintf(newname, "%s.s%d", plex.name, sdno);
|
|
vinum_rename_2(sd.name, newname);
|
|
break;
|
|
|
|
case plex_object:
|
|
get_plex_info(&plex, msg.index);
|
|
get_volume_info(&vol, plex.volno);
|
|
for (plexno = 0; plexno < vol.plexes; plexno++) {
|
|
if (vol.plex[plexno] == msg.index) /* found our subdisk */
|
|
break;
|
|
}
|
|
sprintf(newname, "%s.p%d", vol.name, plexno);
|
|
vinum_rename_2(plex.name, newname); /* this may recurse */
|
|
break;
|
|
|
|
default: /* can't get here */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Detach a subdisk from a plex, or a plex from a volume.
|
|
* detach subdisk plex [rename]
|
|
* detach plex volume [rename]
|
|
*/
|
|
void
|
|
vinum_detach(int argc, char *argv[], char *argv0[])
|
|
{
|
|
struct vinum_ioctl_msg msg;
|
|
struct _ioctl_reply *reply = (struct _ioctl_reply *) &msg;
|
|
|
|
if ((argc < 1)
|
|
|| (argc > 2)) {
|
|
fprintf(stderr,
|
|
"Usage: \tdetach <subdisk> [rename]\n"
|
|
"\tdetach <plex> [rename]\n");
|
|
return;
|
|
}
|
|
if (ioctl(superdev, VINUM_GETCONFIG, &vinum_conf) < 0) {
|
|
perror("Can't get vinum config");
|
|
return;
|
|
}
|
|
msg.index = find_object(argv[0], &msg.type); /* find the object to detach */
|
|
msg.force = force; /* did we specify the use of force? */
|
|
msg.rename = 0; /* don't specify new name */
|
|
msg.recurse = recurse; /* but recurse if we have to */
|
|
|
|
/* XXX are we going to keep this?
|
|
* Don't document it yet, since the
|
|
* kernel side of things doesn't
|
|
* implement it */
|
|
if (argc == 2) {
|
|
if (!strcmp(argv[1], "rename"))
|
|
msg.rename = 1; /* do renaming */
|
|
else {
|
|
fprintf(stderr, "Unknown attribute: %s\n", argv[1]);
|
|
return;
|
|
}
|
|
}
|
|
if ((msg.type != sd_object)
|
|
&& (msg.type != plex_object)) {
|
|
fprintf(stderr, "Can only detach subdisks and plexes\n");
|
|
return;
|
|
}
|
|
ioctl(superdev, VINUM_DETACH, &msg);
|
|
if (reply->error != 0)
|
|
fprintf(stderr,
|
|
"Can't detach %s: %s (%d)\n",
|
|
argv[0],
|
|
reply->msg[0] ? reply->msg : strerror(reply->error),
|
|
reply->error);
|
|
}
|
|
|
|
static void
|
|
dorename(struct vinum_rename_msg *msg, const char *oldname, const char *name, int maxlen)
|
|
{
|
|
struct _ioctl_reply *reply = (struct _ioctl_reply *) msg;
|
|
|
|
if (strlen(name) > maxlen) {
|
|
fprintf(stderr, "%s is too long\n", name);
|
|
return;
|
|
}
|
|
strcpy(msg->newname, name);
|
|
ioctl(superdev, VINUM_RENAME, msg);
|
|
if (reply->error != 0)
|
|
fprintf(stderr,
|
|
"Can't rename %s to %s: %s (%d)\n",
|
|
oldname,
|
|
name,
|
|
reply->msg[0] ? reply->msg : strerror(reply->error),
|
|
reply->error);
|
|
}
|
|
|
|
/* Rename an object:
|
|
* rename <object> "newname"
|
|
*/
|
|
void
|
|
vinum_rename_2(char *oldname, char *newname)
|
|
{
|
|
struct vinum_rename_msg msg;
|
|
int volno;
|
|
int plexno;
|
|
|
|
msg.index = find_object(oldname, &msg.type); /* find the object to rename */
|
|
msg.recurse = recurse;
|
|
|
|
/* Ugh. Determine how long the name may be */
|
|
switch (msg.type) {
|
|
case drive_object:
|
|
dorename(&msg, oldname, newname, MAXDRIVENAME);
|
|
break;
|
|
|
|
case sd_object:
|
|
dorename(&msg, oldname, newname, MAXSDNAME);
|
|
break;
|
|
|
|
case plex_object:
|
|
plexno = msg.index;
|
|
dorename(&msg, oldname, newname, MAXPLEXNAME);
|
|
if (recurse) {
|
|
int sdno;
|
|
|
|
get_plex_info(&plex, plexno); /* find out who we are */
|
|
msg.type = sd_object;
|
|
for (sdno = 0; sdno < plex.subdisks; sdno++) {
|
|
char sdname[MAXPLEXNAME + 8];
|
|
|
|
get_plex_sd_info(&sd, plex.plexno, sdno); /* get info about the subdisk */
|
|
sprintf(sdname, "%s.s%d", newname, sdno);
|
|
msg.index = sd.sdno; /* number of the subdisk */
|
|
dorename(&msg, sd.name, sdname, MAXSDNAME);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case volume_object:
|
|
volno = msg.index;
|
|
dorename(&msg, oldname, newname, MAXVOLNAME);
|
|
if (recurse) {
|
|
int sdno;
|
|
int plexno;
|
|
|
|
get_volume_info(&vol, volno); /* find out who we are */
|
|
for (plexno = 0; plexno < vol.plexes; plexno++) {
|
|
char plexname[MAXVOLNAME + 8];
|
|
|
|
msg.type = plex_object;
|
|
sprintf(plexname, "%s.p%d", newname, plexno);
|
|
msg.index = vol.plex[plexno]; /* number of the plex */
|
|
dorename(&msg, plex.name, plexname, MAXPLEXNAME);
|
|
get_plex_info(&plex, vol.plex[plexno]); /* find out who we are */
|
|
msg.type = sd_object;
|
|
for (sdno = 0; sdno < plex.subdisks; sdno++) {
|
|
char sdname[MAXPLEXNAME + 8];
|
|
|
|
get_plex_sd_info(&sd, plex.plexno, sdno); /* get info about the subdisk */
|
|
sprintf(sdname, "%s.s%d", plexname, sdno);
|
|
msg.index = sd.sdno; /* number of the subdisk */
|
|
dorename(&msg, sd.name, sdname, MAXSDNAME);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr, "%s is not a Vinum object\n", oldname);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
vinum_rename(int argc, char *argv[], char *argv0[])
|
|
{
|
|
if (argc != 2) {
|
|
fprintf(stderr, "Usage: \trename <object> <new name>\n");
|
|
return;
|
|
}
|
|
if (ioctl(superdev, VINUM_GETCONFIG, &vinum_conf) < 0) {
|
|
perror("Can't get vinum config");
|
|
return;
|
|
}
|
|
vinum_rename_2(argv[0], argv[1]);
|
|
}
|
|
|
|
/*
|
|
* Replace an object. Currently only defined for a drive: move all
|
|
* the subdisks on a drive to a new drive.
|
|
*/
|
|
void
|
|
vinum_replace(int argc, char *argv[], char *argv0[])
|
|
{
|
|
fprintf(stderr, "replace not implemented yet\n");
|
|
}
|
|
|
|
/* Primitive help function */
|
|
void
|
|
vinum_help(int argc, char *argv[], char *argv0[])
|
|
{
|
|
char commands[] =
|
|
{
|
|
"COMMANDS\n"
|
|
"create [-f description-file]\n"
|
|
" Create a volume as described in description-file\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"
|
|
"debug\n"
|
|
" Cause the volume manager to enter the kernel debugger.\n"
|
|
"debug flags\n"
|
|
" Set debugging flags.\n"
|
|
"detach [plex | subdisk]\n"
|
|
" Detach a plex or subdisk from the volume or plex to which it is\n"
|
|
" attached.\n"
|
|
"info [-v]\n"
|
|
" List information about volume manager state.\n"
|
|
"init [-v] [-w] plex\n"
|
|
" Initialize a plex by writing zeroes to all its subdisks.\n"
|
|
"label volume\n"
|
|
" Create a volume label\n"
|
|
"list [-r] [-s] [-v] [-V] [volume | plex | subdisk]\n"
|
|
" List information about specified objects\n"
|
|
"l [-r] [-s] [-v] [-V] [volume | plex | subdisk]\n"
|
|
" List information about specified objects (alternative to\n"
|
|
" list command)\n"
|
|
"ld [-r] [-s] [-v] [-V] [volume]\n"
|
|
" List information about drives\n"
|
|
"ls [-r] [-s] [-v] [-V] [subdisk]\n"
|
|
" List information about subdisks\n"
|
|
"lp [-r] [-s] [-v] [-V] [plex]\n"
|
|
" List information about plexes\n"
|
|
"lv [-r] [-s] [-v] [-V] [volume]\n"
|
|
" List information about volumes\n"
|
|
"printconfig [file]\n"
|
|
" Write a copy of the current configuration to file.\n"
|
|
"makedev\n"
|
|
" Remake the device nodes in /dev/vinum.\n"
|
|
"quit\n"
|
|
" Exit the vinum program when running in interactive mode. Nor-\n"
|
|
" mally this would be done by entering the EOF character.\n"
|
|
"read disk [disk...]\n"
|
|
" Read the vinum configuration from the specified disks.\n"
|
|
"rename [-r] [drive | subdisk | plex | volume] newname\n"
|
|
" Change the name of the specified object.\n"
|
|
"resetconfig\n"
|
|
" Reset the complete vinum configuration.\n"
|
|
"resetstats [-r] [volume | plex | subdisk]\n"
|
|
" Reset statistisc counters for the specified objects, or for all\n"
|
|
" objects if none are specified.\n"
|
|
"rm [-f] [-r] volume | plex | subdisk\n"
|
|
" Remove an object\n"
|
|
"saveconfig\n"
|
|
" Save vinum configuration to disk.\n"
|
|
"setdaemon [value]\n"
|
|
" Set daemon configuration.\n"
|
|
"start\n"
|
|
" Read configuration from all vinum drives.\n"
|
|
"start [volume | plex | subdisk]\n"
|
|
" Allow the system to access the objects\n"
|
|
"stop [-f] [volume | plex | subdisk]\n"
|
|
" Terminate access to the objects, or stop vinum if no parameters\n"
|
|
" are specified.\n"
|
|
};
|
|
puts(commands);
|
|
}
|
|
|
|
/* Set daemon options.
|
|
* XXX quick and dirty: use a bitmap, which requires
|
|
* knowing which bit does what. FIXME */
|
|
void
|
|
vinum_setdaemon(int argc, char *argv[], char *argv0[])
|
|
{
|
|
int options;
|
|
|
|
switch (argc) {
|
|
case 0:
|
|
if (ioctl(superdev, VINUM_GETDAEMON, &options) < 0)
|
|
fprintf(stderr, "Can't get daemon options: %s (%d)\n", strerror(errno), errno);
|
|
else
|
|
printf("Options mask: %d\n", options);
|
|
break;
|
|
|
|
case 1:
|
|
options = atoi(argv[0]);
|
|
if (ioctl(superdev, VINUM_SETDAEMON, &options) < 0)
|
|
fprintf(stderr, "Can't set daemon options: %s (%d)\n", strerror(errno), errno);
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr, "Usage: \tsetdaemon [<bitmask>]\n");
|
|
}
|
|
}
|
|
|
|
/* Save config info */
|
|
void
|
|
vinum_saveconfig(int argc, char *argv[], char *argv0[])
|
|
{
|
|
int ioctltype;
|
|
|
|
if (argc != 0) {
|
|
printf("Usage: saveconfig\n");
|
|
return;
|
|
}
|
|
ioctltype = 1; /* user saveconfig */
|
|
if (ioctl(superdev, VINUM_SAVECONFIG, &ioctltype) < 0)
|
|
fprintf(stderr, "Can't save configuration: %s (%d)\n", strerror(errno), errno);
|
|
}
|
|
|
|
/*
|
|
* Create a volume name for the quick and dirty
|
|
* commands. It will be of the form "vinum#",
|
|
* where # is a small positive number.
|
|
*/
|
|
void
|
|
genvolname()
|
|
{
|
|
int v; /* volume number */
|
|
static char volumename[MAXVOLNAME]; /* name to create */
|
|
enum objecttype type;
|
|
|
|
objectname = volumename; /* point to it */
|
|
for (v = 0;; v++) {
|
|
sprintf(objectname, "vinum%d", v); /* create the name */
|
|
if (find_object(objectname, &type) == -1) /* does it exist? */
|
|
return; /* no, it's ours */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create a drive for the quick and dirty
|
|
* commands. The name will be of the form
|
|
* vinumdrive#, where # is a small positive
|
|
* number. Return the name of the drive.
|
|
*/
|
|
struct drive *
|
|
create_drive(char *devicename)
|
|
{
|
|
int d; /* volume number */
|
|
static char drivename[MAXDRIVENAME]; /* name to create */
|
|
enum objecttype type;
|
|
struct _ioctl_reply *reply;
|
|
|
|
/*
|
|
* We're never likely to get anything
|
|
* like 10000 drives. The only reason for
|
|
* this limit is to stop the thing
|
|
* looping if we have a bug somewhere.
|
|
*/
|
|
for (d = 0; d < 100000; d++) { /* look for a free drive number */
|
|
sprintf(drivename, "vinumdrive%d", d); /* create the name */
|
|
if (find_object(drivename, &type) == -1) { /* does it exist? */
|
|
char command[MAXDRIVENAME * 2];
|
|
|
|
sprintf(command, "drive %s device %s", drivename, devicename); /* create a create command */
|
|
if (verbose)
|
|
printf("drive %s device %s\n", drivename, devicename); /* create a create command */
|
|
ioctl(superdev, VINUM_CREATE, command);
|
|
reply = (struct _ioctl_reply *) &command;
|
|
if (reply->error != 0) { /* error in config */
|
|
if (reply->msg[0])
|
|
fprintf(stderr,
|
|
"Can't create drive %s, device %s: %s\n",
|
|
drivename,
|
|
devicename,
|
|
reply->msg);
|
|
else
|
|
fprintf(stderr,
|
|
"Can't create drive %s, device %s: %s (%d)\n",
|
|
drivename,
|
|
devicename,
|
|
strerror(reply->error),
|
|
reply->error);
|
|
longjmp(command_fail, -1); /* give up */
|
|
}
|
|
find_object(drivename, &type);
|
|
return &drive; /* return the name of the drive */
|
|
}
|
|
}
|
|
fprintf(stderr, "Can't generate a drive name\n");
|
|
/* NOTREACHED */
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Create a volume with a single concatenated plex from
|
|
* as much space as we can get on the specified drives.
|
|
* If the drives aren't Vinum drives, make them so.
|
|
*/
|
|
void
|
|
vinum_concat(int argc, char *argv[], char *argv0[])
|
|
{
|
|
int o; /* object number */
|
|
char buffer[BUFSIZE];
|
|
struct drive *drive; /* drive we're currently looking at */
|
|
struct _ioctl_reply *reply;
|
|
int ioctltype;
|
|
int error;
|
|
enum objecttype type;
|
|
|
|
reply = (struct _ioctl_reply *) &buffer;
|
|
if (ioctl(superdev, VINUM_STARTCONFIG, &force)) { /* can't get config? */
|
|
printf("Can't configure: %s (%d)\n", strerror(errno), errno);
|
|
return;
|
|
}
|
|
if (!objectname) /* we need a name for our object */
|
|
genvolname();
|
|
sprintf(buffer, "volume %s", objectname);
|
|
if (verbose)
|
|
printf("volume %s\n", objectname);
|
|
ioctl(superdev, VINUM_CREATE, buffer); /* create the volume */
|
|
if (reply->error != 0) { /* error in config */
|
|
if (reply->msg[0])
|
|
fprintf(stderr,
|
|
"Can't create volume %s: %s\n",
|
|
objectname,
|
|
reply->msg);
|
|
else
|
|
fprintf(stderr,
|
|
"Can't create volume %s: %s (%d)\n",
|
|
objectname,
|
|
strerror(reply->error),
|
|
reply->error);
|
|
longjmp(command_fail, -1); /* give up */
|
|
}
|
|
sprintf(buffer, "plex name %s.p0 org concat", objectname);
|
|
if (verbose)
|
|
printf(" plex name %s.p0 org concat\n", objectname);
|
|
ioctl(superdev, VINUM_CREATE, buffer);
|
|
if (reply->error != 0) { /* error in config */
|
|
if (reply->msg[0])
|
|
fprintf(stderr,
|
|
"Can't create plex %s.p0: %s\n",
|
|
objectname,
|
|
reply->msg);
|
|
else
|
|
fprintf(stderr,
|
|
"Can't create plex %s.p0: %s (%d)\n",
|
|
objectname,
|
|
strerror(reply->error),
|
|
reply->error);
|
|
longjmp(command_fail, -1); /* give up */
|
|
}
|
|
for (o = 0; o < argc; o++) {
|
|
if ((drive = find_drive_by_devname(argv[o])) == NULL) /* doesn't exist */
|
|
drive = create_drive(argv[o]); /* create it */
|
|
sprintf(buffer, "sd name %s.p0.s%d drive %s size 0", objectname, o, drive->label.name);
|
|
if (verbose)
|
|
printf(" sd name %s.p0.s%d drive %s size 0\n", objectname, o, drive->label.name);
|
|
ioctl(superdev, VINUM_CREATE, buffer);
|
|
if (reply->error != 0) { /* error in config */
|
|
if (reply->msg[0])
|
|
fprintf(stderr,
|
|
"Can't create subdisk %s.p0.s%d: %s\n",
|
|
objectname,
|
|
o,
|
|
reply->msg);
|
|
else
|
|
fprintf(stderr,
|
|
"Can't create subdisk %s.p0.s%d: %s (%d)\n",
|
|
objectname,
|
|
o,
|
|
strerror(reply->error),
|
|
reply->error);
|
|
longjmp(command_fail, -1); /* give up */
|
|
}
|
|
}
|
|
|
|
/* done, save the config */
|
|
ioctltype = 0; /* saveconfig after update */
|
|
error = ioctl(superdev, VINUM_SAVECONFIG, &ioctltype); /* save the config to disk */
|
|
if (error != 0)
|
|
perror("Can't save Vinum config");
|
|
find_object(objectname, &type); /* find the index of the volume */
|
|
make_vol_dev(vol.volno, 1); /* and create the devices */
|
|
if (verbose) {
|
|
verbose--; /* XXX don't give too much detail */
|
|
find_object(objectname, &type); /* point to the volume */
|
|
vinum_lvi(vol.volno, 1); /* and print info about it */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create a volume with a single striped plex from
|
|
* as much space as we can get on the specified drives.
|
|
* If the drives aren't Vinum drives, make them so.
|
|
*/
|
|
void
|
|
vinum_stripe(int argc, char *argv[], char *argv0[])
|
|
{
|
|
int o; /* object number */
|
|
char buffer[BUFSIZE];
|
|
struct drive *drive; /* drive we're currently looking at */
|
|
struct _ioctl_reply *reply;
|
|
int ioctltype;
|
|
int error;
|
|
enum objecttype type;
|
|
off_t maxsize;
|
|
int fe; /* freelist entry index */
|
|
struct drive_freelist freelist;
|
|
struct ferq { /* request to pass to ioctl */
|
|
int driveno;
|
|
int fe;
|
|
} *ferq = (struct ferq *) &freelist;
|
|
u_int64_t bigchunk; /* biggest chunk in freelist */
|
|
|
|
maxsize = QUAD_MAX;
|
|
reply = (struct _ioctl_reply *) &buffer;
|
|
|
|
/*
|
|
* First, check our drives.
|
|
*/
|
|
if (argc < 2) {
|
|
fprintf(stderr, "You need at least two drives to create a striped plex\n");
|
|
return;
|
|
}
|
|
if (ioctl(superdev, VINUM_STARTCONFIG, &force)) { /* can't get config? */
|
|
printf("Can't configure: %s (%d)\n", strerror(errno), errno);
|
|
return;
|
|
}
|
|
if (!objectname) /* we need a name for our object */
|
|
genvolname();
|
|
for (o = 0; o < argc; o++) {
|
|
if ((drive = find_drive_by_devname(argv[o])) == NULL) /* doesn't exist */
|
|
drive = create_drive(argv[o]); /* create it */
|
|
/* Now find the largest chunk available on the drive */
|
|
bigchunk = 0; /* ain't found nothin' yet */
|
|
for (fe = 0; fe < drive->freelist_entries; fe++) {
|
|
ferq->driveno = drive->driveno;
|
|
ferq->fe = fe;
|
|
if (ioctl(superdev, VINUM_GETFREELIST, &freelist) < 0) {
|
|
fprintf(stderr,
|
|
"Can't get free list element %d: %s\n",
|
|
fe,
|
|
strerror(errno));
|
|
longjmp(command_fail, -1);
|
|
}
|
|
bigchunk = bigchunk > freelist.sectors ? bigchunk : freelist.sectors; /* max it */
|
|
}
|
|
maxsize = min(maxsize, bigchunk); /* this is as much as we can do */
|
|
}
|
|
|
|
/* Now create the volume */
|
|
sprintf(buffer, "volume %s", objectname);
|
|
if (verbose)
|
|
printf("volume %s\n", objectname);
|
|
ioctl(superdev, VINUM_CREATE, buffer); /* create the volume */
|
|
if (reply->error != 0) { /* error in config */
|
|
if (reply->msg[0])
|
|
fprintf(stderr,
|
|
"Can't create volume %s: %s\n",
|
|
objectname,
|
|
reply->msg);
|
|
else
|
|
fprintf(stderr,
|
|
"Can't create volume %s: %s (%d)\n",
|
|
objectname,
|
|
strerror(reply->error),
|
|
reply->error);
|
|
longjmp(command_fail, -1); /* give up */
|
|
}
|
|
sprintf(buffer, "plex name %s.p0 org striped 256k", objectname);
|
|
if (verbose)
|
|
printf(" plex name %s.p0 org striped 256k\n", objectname);
|
|
ioctl(superdev, VINUM_CREATE, buffer);
|
|
if (reply->error != 0) { /* error in config */
|
|
if (reply->msg[0])
|
|
fprintf(stderr,
|
|
"Can't create plex %s.p0: %s\n",
|
|
objectname,
|
|
reply->msg);
|
|
else
|
|
fprintf(stderr,
|
|
"Can't create plex %s.p0: %s (%d)\n",
|
|
objectname,
|
|
strerror(reply->error),
|
|
reply->error);
|
|
longjmp(command_fail, -1); /* give up */
|
|
}
|
|
for (o = 0; o < argc; o++) {
|
|
drive = find_drive_by_devname(argv[o]); /* we know it exists... */
|
|
sprintf(buffer,
|
|
"sd name %s.p0.s%d drive %s size %lldb",
|
|
objectname,
|
|
o,
|
|
drive->label.name,
|
|
maxsize);
|
|
if (verbose)
|
|
printf(" sd name %s.p0.s%d drive %s size %lldb\n",
|
|
objectname,
|
|
o,
|
|
drive->label.name,
|
|
maxsize);
|
|
ioctl(superdev, VINUM_CREATE, buffer);
|
|
if (reply->error != 0) { /* error in config */
|
|
if (reply->msg[0])
|
|
fprintf(stderr,
|
|
"Can't create subdisk %s.p0.s%d: %s\n",
|
|
objectname,
|
|
o,
|
|
reply->msg);
|
|
else
|
|
fprintf(stderr,
|
|
"Can't create subdisk %s.p0.s%d: %s (%d)\n",
|
|
objectname,
|
|
o,
|
|
strerror(reply->error),
|
|
reply->error);
|
|
longjmp(command_fail, -1); /* give up */
|
|
}
|
|
}
|
|
|
|
/* done, save the config */
|
|
ioctltype = 0; /* saveconfig after update */
|
|
error = ioctl(superdev, VINUM_SAVECONFIG, &ioctltype); /* save the config to disk */
|
|
if (error != 0)
|
|
perror("Can't save Vinum config");
|
|
find_object(objectname, &type); /* find the index of the volume */
|
|
make_vol_dev(vol.volno, 1); /* and create the devices */
|
|
if (verbose) {
|
|
verbose--; /* XXX don't give too much detail */
|
|
find_object(objectname, &type); /* point to the volume */
|
|
vinum_lvi(vol.volno, 1); /* and print info about it */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create a volume with a two plexes from as much space
|
|
* as we can get on the specified drives. If the
|
|
* drives aren't Vinum drives, make them so.
|
|
*
|
|
* The number of drives must be even, and at least 4
|
|
* for a striped plex. Specify striped plexes with the
|
|
* -s flag; otherwise they will be concatenated. It's
|
|
* possible that the two plexes may differ in length.
|
|
*/
|
|
void
|
|
vinum_mirror(int argc, char *argv[], char *argv0[])
|
|
{
|
|
int o; /* object number */
|
|
int p; /* plex number */
|
|
char buffer[BUFSIZE];
|
|
struct drive *drive; /* drive we're currently looking at */
|
|
struct _ioctl_reply *reply;
|
|
int ioctltype;
|
|
int error;
|
|
enum objecttype type;
|
|
off_t maxsize[2]; /* maximum subdisk size for striped plexes */
|
|
int fe; /* freelist entry index */
|
|
struct drive_freelist freelist;
|
|
struct ferq { /* request to pass to ioctl */
|
|
int driveno;
|
|
int fe;
|
|
} *ferq = (struct ferq *) &freelist;
|
|
u_int64_t bigchunk; /* biggest chunk in freelist */
|
|
|
|
if (sflag) /* striped, */
|
|
maxsize[0] = maxsize[1] = QUAD_MAX; /* we need to calculate sd size */
|
|
else
|
|
maxsize[0] = maxsize[1] = 0; /* let the kernel routines do it */
|
|
|
|
reply = (struct _ioctl_reply *) &buffer;
|
|
|
|
/*
|
|
* First, check our drives.
|
|
*/
|
|
if (argc & 1) {
|
|
fprintf(stderr, "You need an even number of drives to create a mirrored volume\n");
|
|
return;
|
|
}
|
|
if (sflag && (argc < 4)) {
|
|
fprintf(stderr, "You need at least 4 drives to create a mirrored, striped volume\n");
|
|
return;
|
|
}
|
|
if (ioctl(superdev, VINUM_STARTCONFIG, &force)) { /* can't get config? */
|
|
printf("Can't configure: %s (%d)\n", strerror(errno), errno);
|
|
return;
|
|
}
|
|
if (!objectname) /* we need a name for our object */
|
|
genvolname();
|
|
for (o = 0; o < argc; o++) {
|
|
if ((drive = find_drive_by_devname(argv[o])) == NULL) /* doesn't exist */
|
|
drive = create_drive(argv[o]); /* create it */
|
|
if (sflag) { /* striping, */
|
|
/* Find the largest chunk available on the drive */
|
|
bigchunk = 0; /* ain't found nothin' yet */
|
|
for (fe = 0; fe < drive->freelist_entries; fe++) {
|
|
ferq->driveno = drive->driveno;
|
|
ferq->fe = fe;
|
|
if (ioctl(superdev, VINUM_GETFREELIST, &freelist) < 0) {
|
|
fprintf(stderr,
|
|
"Can't get free list element %d: %s\n",
|
|
fe,
|
|
strerror(errno));
|
|
longjmp(command_fail, -1);
|
|
}
|
|
bigchunk = bigchunk > freelist.sectors ? bigchunk : freelist.sectors; /* max it */
|
|
}
|
|
maxsize[o & 1] = min(maxsize[o & 1], bigchunk); /* get the maximum size of a subdisk */
|
|
}
|
|
}
|
|
|
|
/* Now create the volume */
|
|
sprintf(buffer, "volume %s setupstate", objectname);
|
|
if (verbose)
|
|
printf("volume %s setupstate\n", objectname);
|
|
ioctl(superdev, VINUM_CREATE, buffer); /* create the volume */
|
|
if (reply->error != 0) { /* error in config */
|
|
if (reply->msg[0])
|
|
fprintf(stderr,
|
|
"Can't create volume %s: %s\n",
|
|
objectname,
|
|
reply->msg);
|
|
else
|
|
fprintf(stderr,
|
|
"Can't create volume %s: %s (%d)\n",
|
|
objectname,
|
|
strerror(reply->error),
|
|
reply->error);
|
|
longjmp(command_fail, -1); /* give up */
|
|
}
|
|
for (p = 0; p < 2; p++) { /* create each plex */
|
|
if (sflag) {
|
|
sprintf(buffer, "plex name %s.p%d org striped 256k", objectname, p);
|
|
if (verbose)
|
|
printf(" plex name %s.p%d org striped 256k\n", objectname, p);
|
|
} else { /* concat */
|
|
sprintf(buffer, "plex name %s.p%d org concat", objectname, p);
|
|
if (verbose)
|
|
printf(" plex name %s.p%d org concat\n", objectname, p);
|
|
}
|
|
ioctl(superdev, VINUM_CREATE, buffer);
|
|
if (reply->error != 0) { /* error in config */
|
|
if (reply->msg[0])
|
|
fprintf(stderr,
|
|
"Can't create plex %s.p%d: %s\n",
|
|
objectname,
|
|
p,
|
|
reply->msg);
|
|
else
|
|
fprintf(stderr,
|
|
"Can't create plex %s.p%d: %s (%d)\n",
|
|
objectname,
|
|
p,
|
|
strerror(reply->error),
|
|
reply->error);
|
|
longjmp(command_fail, -1); /* give up */
|
|
}
|
|
/* Now look at the subdisks */
|
|
for (o = p; o < argc; o += 2) { /* every second one */
|
|
drive = find_drive_by_devname(argv[o]); /* we know it exists... */
|
|
sprintf(buffer,
|
|
"sd name %s.p%d.s%d drive %s size %lldb",
|
|
objectname,
|
|
p,
|
|
o >> 1,
|
|
drive->label.name,
|
|
maxsize[p]);
|
|
if (verbose)
|
|
printf(" sd name %s.p%d.s%d drive %s size %lldb\n",
|
|
objectname,
|
|
p,
|
|
o >> 1,
|
|
drive->label.name,
|
|
maxsize[p]);
|
|
ioctl(superdev, VINUM_CREATE, buffer);
|
|
if (reply->error != 0) { /* error in config */
|
|
if (reply->msg[0])
|
|
fprintf(stderr,
|
|
"Can't create subdisk %s.p%d.s%d: %s\n",
|
|
objectname,
|
|
p,
|
|
o >> 1,
|
|
reply->msg);
|
|
else
|
|
fprintf(stderr,
|
|
"Can't create subdisk %s.p%d.s%d: %s (%d)\n",
|
|
objectname,
|
|
p,
|
|
o >> 1,
|
|
strerror(reply->error),
|
|
reply->error);
|
|
longjmp(command_fail, -1); /* give up */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* done, save the config */
|
|
ioctltype = 0; /* saveconfig after update */
|
|
error = ioctl(superdev, VINUM_SAVECONFIG, &ioctltype); /* save the config to disk */
|
|
if (error != 0)
|
|
perror("Can't save Vinum config");
|
|
find_object(objectname, &type); /* find the index of the volume */
|
|
make_vol_dev(vol.volno, 1); /* and create the devices */
|
|
if (verbose) {
|
|
verbose--; /* XXX don't give too much detail */
|
|
sflag = 0; /* no stats, please */
|
|
find_object(objectname, &type); /* point to the volume */
|
|
vinum_lvi(vol.volno, 1); /* and print info about it */
|
|
}
|
|
}
|
|
|
|
void
|
|
vinum_readpol(int argc, char *argv[], char *argv0[])
|
|
{
|
|
int object;
|
|
struct _ioctl_reply reply;
|
|
struct vinum_ioctl_msg *message = (struct vinum_ioctl_msg *) &reply;
|
|
enum objecttype type;
|
|
struct plex plex;
|
|
struct volume vol;
|
|
int plexno;
|
|
|
|
if (argc == 0) { /* start everything */
|
|
fprintf(stderr, "Usage: readpol <volume> <plex>|round\n");
|
|
return;
|
|
}
|
|
object = find_object(argv[1], &type); /* look for it */
|
|
if (type != volume_object) {
|
|
fprintf(stderr, "%s is not a volume\n", argv[1]);
|
|
return;
|
|
}
|
|
get_volume_info(&vol, object);
|
|
if (strcmp(argv[2], "round")) { /* not 'round' */
|
|
object = find_object(argv[2], &type); /* look for it */
|
|
if (type != plex_object) {
|
|
fprintf(stderr, "%s is not a plex\n", argv[2]);
|
|
return;
|
|
}
|
|
get_plex_info(&plex, object);
|
|
plexno = plex.plexno;
|
|
} else /* round */
|
|
plexno = -1;
|
|
|
|
/* Set the value */
|
|
message->index = vol.volno;
|
|
message->otherobject = plexno;
|
|
if (ioctl(superdev, VINUM_READPOL, message) < 0)
|
|
fprintf(stderr, "Can't set read policy: %s (%d)\n", strerror(errno), errno);
|
|
if (verbose)
|
|
vinum_lpi(plexno, recurse);
|
|
}
|
|
|
|
/*
|
|
* Brute force set state function. Don't look at
|
|
* any dependencies, just do it.
|
|
*/
|
|
void
|
|
vinum_setstate(int argc, char *argv[], char *argv0[])
|
|
{
|
|
int object;
|
|
struct _ioctl_reply reply;
|
|
struct vinum_ioctl_msg *message = (struct vinum_ioctl_msg *) &reply;
|
|
int index;
|
|
enum objecttype type;
|
|
int state;
|
|
|
|
for (index = 1; index < argc; index++) {
|
|
object = find_object(argv[index], &type); /* look for it */
|
|
if (type == invalid_object)
|
|
fprintf(stderr, "Can't find object: %s\n", argv[index]);
|
|
else {
|
|
int doit = 0; /* set to 1 if we pass our tests */
|
|
switch (type) {
|
|
case drive_object:
|
|
state = DriveState(argv[0]); /* get the state */
|
|
if (drive.state == state) /* already in that state */
|
|
fprintf(stderr, "%s is already %s\n", drive.label.name, argv[0]);
|
|
else
|
|
doit = 1;
|
|
break;
|
|
|
|
case sd_object:
|
|
state = SdState(argv[0]); /* get the state */
|
|
if (sd.state == state) /* already in that state */
|
|
fprintf(stderr, "%s is already %s\n", sd.name, argv[0]);
|
|
else
|
|
doit = 1;
|
|
break;
|
|
|
|
case plex_object:
|
|
state = PlexState(argv[0]); /* get the state */
|
|
if (plex.state == state) /* already in that state */
|
|
fprintf(stderr, "%s is already %s\n", plex.name, argv[0]);
|
|
else
|
|
doit = 1;
|
|
break;
|
|
|
|
case volume_object:
|
|
state = VolState(argv[0]); /* get the state */
|
|
if (vol.state == state) /* already in that state */
|
|
fprintf(stderr, "%s is already %s\n", vol.name, argv[0]);
|
|
else
|
|
doit = 1;
|
|
break;
|
|
|
|
default:
|
|
}
|
|
|
|
if (state == -1)
|
|
fprintf(stderr, "Invalid state for object: %s\n", argv[0]);
|
|
else if (doit) {
|
|
message->index = object; /* pass object number */
|
|
message->type = type; /* and type of object */
|
|
message->state = state;
|
|
message->force = force; /* don't force it, use a larger hammer */
|
|
ioctl(superdev, VINUM_SETSTATE_FORCE, message);
|
|
if (reply.error != 0)
|
|
fprintf(stderr,
|
|
"Can't start %s: %s (%d)\n",
|
|
argv[index],
|
|
reply.msg[0] ? reply.msg : strerror(reply.error),
|
|
reply.error);
|
|
if (Verbose)
|
|
vinum_li(object, type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
vinum_checkparity(int argc, char *argv[], char *argv0[])
|
|
{
|
|
}
|
|
void
|
|
vinum_rebuildparity(int argc, char *argv[], char *argv0[])
|
|
{
|
|
}
|
|
|
|
/* Local Variables: */
|
|
/* fill-column: 50 */
|
|
/* End: */
|