1
0
mirror of https://git.FreeBSD.org/src.git synced 2024-12-23 11:18:54 +00:00
freebsd/sbin/vinum/commands.c
David E. O'Brien 1d9153d7ad Use _PATH_DEV.
Reviewed by:	grog
2001-02-28 17:50:29 +00:00

2457 lines
73 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.36 2000/12/20 05:05:39 grog Exp $
* $FreeBSD$
*/
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <netdb.h>
#include <paths.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 <dev/vinum/request.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))
vinum_perror("Can't read config file");
break;
}
file_line++; /* count the lines */
if (vflag)
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 (!vflag) /* 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)
vinum_perror("Can't save Vinum config");
make_devices();
listconfig();
checkupdates(); /* make sure we're updating */
}
/* 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)
vinum_perror("Can't save Vinum config");
} else {
error = ioctl(superdev, VINUM_RELEASECONFIG, NULL); /* save the config to disk */
if (error != 0)
vinum_perror("Can't save Vinum config");
make_devices();
}
checkupdates(); /* make sure we're updating */
}
#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");
checkupdates(); /* make sure we're updating */
}
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 (vflag)
fprintf(stderr, "%s removed\n", argv[index]);
}
}
checkupdates(); /* make sure we're updating */
}
}
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
vinum_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 */
}
}
checkupdates(); /* make sure we're updating */
}
/* Initialize subdisks */
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 initialize %s: wrong object type\n", argv[objindex]);
break;
}
}
}
}
checkupdates(); /* make sure we're updating */
}
void
initvol(int volno)
{
printf("Initializing volumes is 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 */
}
/* Initialize a subdisk. */
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 */
int initsize; /* actual size to write */
int64_t 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;
}
}
if (SSize != 0) { /* specified a size for init */
if (SSize < 512)
SSize <<= DEV_BSHIFT;
initsize = min(SSize, MAXPLEXINITSIZE);
} else
initsize = PLEXINITSIZE;
openlog("vinum", LOG_CONS | LOG_PERROR | LOG_PID, LOG_KERN);
get_sd_info(&sd, sdno);
sdsize = sd.sectors * DEV_BSIZE; /* size of subdisk in bytes */
sprintf(filename, VINUM_DIR "/sd/%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->verify = vflag; /* verify what we write? */
message->force = 1; /* insist */
ioctl(superdev, VINUM_SETSTATE, message);
if ((SSize > 0) /* specified a size for init */
&&(SSize < 512))
SSize <<= DEV_BSHIFT;
if (reply.error) {
fprintf(stderr,
"Can't initialize %s: %s (%d)\n",
filename,
strerror(reply.error),
reply.error);
exit(1);
} else {
do {
if (interval) /* pause between copies */
usleep(interval * 1000);
message->index = sd.sdno; /* pass object number */
message->type = sd_object; /* and type of object */
message->state = object_up;
message->verify = vflag; /* verify what we write? */
message->blocksize = SSize;
ioctl(superdev, VINUM_SETSTATE, message);
}
while (reply.error == EAGAIN); /* until we're done */
if (reply.error) {
fprintf(stderr,
"Can't initialize %s: %s (%d)\n",
filename,
strerror(reply.error),
reply.error);
get_sd_info(&sd, sdno);
if (sd.state != sd_up)
/* Set the subdisk down */
message->index = sd.sdno; /* pass object number */
message->type = sd_object; /* and type of object */
message->state = object_down;
message->verify = vflag; /* verify what we write? */
message->force = 1; /* insist */
ioctl(superdev, VINUM_SETSTATE, message);
}
}
printf("subdisk %s initialized\n", filename);
if (!dowait)
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 */
bzero(&statinfo, sizeof(struct statinfo));
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;
}
bzero(statinfo.dinfo, sizeof(struct devinfo));
tokens = 0; /* no tokens yet */
if (getdevs(&statinfo) < 0) { /* find out what devices we have */
vinum_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, "%s%s%d", _PATH_DEV, 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;
/*
* First, see if we can bring it up
* just by asking. This might happen
* if somebody has used setupstate on
* the subdisks. If we don't do this,
* we'll return success, but the plex
* won't have changed state. Note
* that we don't check for errors
* here.
*/
message->index = plex.plexno; /* pass object number */
message->type = plex_object; /* it's a plex */
message->state = object_up;
message->force = 0; /* don't force it */
ioctl(superdev, VINUM_SETSTATE, message);
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 start */
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 */
/*
* We don't do any checking here.
* The kernel module has a better
* understanding of these things,
* let it do it.
*/
if (SSize != 0) { /* specified a size for init */
if (SSize < 512)
SSize <<= DEV_BSHIFT;
message->blocksize = SSize;
} else
message->blocksize = DEFAULT_REVIVE_BLOCKSIZE;
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 */
/*
* We don't do any checking here.
* The kernel module has a better
* understanding of these things,
* let it do it.
*/
if (SSize != 0) { /* specified a size for init or revive */
if (SSize < 512)
SSize <<= DEV_BSHIFT;
message->blocksize = SSize;
} else
message->blocksize = 0;
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);
}
}
}
}
checkupdates(); /* make sure we're updating */
}
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;
if (checkupdates() && (!force)) /* not updating? */
return;
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))
vinum_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) {
vinum_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);
}
}
}
checkupdates(); /* not updating? */
}
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) {
vinum_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 oldname[MAXNAME + 8];
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) {
vinum_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, */
&&(!force)) {
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);
sprintf(oldname, "%s", sd.name);
vinum_rename_2(oldname, 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);
sprintf(oldname, "%s", plex.name);
vinum_rename_2(oldname, newname); /* this may recurse */
break;
default: /* can't get here */
}
}
checkupdates(); /* make sure we're updating */
}
/* 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) {
vinum_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);
checkupdates(); /* make sure we're updating */
}
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) {
vinum_perror("Can't get vinum config");
return;
}
vinum_rename_2(argv[0], argv[1]);
checkupdates(); /* make sure we're updating */
}
/*
* Move objects:
*
* mv <dest> <src> ...
*/
void
vinum_mv(int argc, char *argv[], char *argv0[])
{
int i; /* loop index */
int srcobj;
int destobj;
enum objecttype srct;
enum objecttype destt;
int sdno;
struct _ioctl_reply reply;
struct vinum_ioctl_msg *msg = (struct vinum_ioctl_msg *) &reply;
if (argc < 2) {
fprintf(stderr, "Usage: \tmove <dest> <src> ...\n");
return;
}
/* Get current config */
if (ioctl(superdev, VINUM_GETCONFIG, &vinum_conf) < 0) {
vinum_perror("Cannot get vinum config\n");
return;
}
/* Get our destination */
destobj = find_object(argv[0], &destt);
if (destobj == -1) {
fprintf(stderr, "Can't find %s\n", argv[0]);
return;
}
/* Verify that the target is a drive */
if (destt != drive_object) {
fprintf(stderr, "%s is not a drive\n", argv[0]);
return;
}
for (i = 1; i < argc; i++) { /* for all the sources */
srcobj = find_object(argv[i], &srct);
if (srcobj == -1) {
fprintf(stderr, "Can't find %s\n", argv[i]);
continue;
}
msg->index = destobj;
switch (srct) { /* Handle the source object */
case drive_object: /* Move all subdisks on the drive to dst. */
get_drive_info(&drive, srcobj); /* get info on drive */
for (sdno = 0; sdno < vinum_conf.subdisks_allocated; ++sdno) {
get_sd_info(&sd, sdno);
if (sd.driveno == srcobj) {
msg->index = destobj;
msg->otherobject = sd.sdno;
if (ioctl(superdev, VINUM_MOVE, msg) < 0)
fprintf(stderr,
"Can't move %s (part of %s) to %s: %s (%d)\n",
sd.name,
drive.label.name,
argv[0],
strerror(reply.error),
reply.error);
}
}
break;
case sd_object:
msg->otherobject = srcobj;
if (ioctl(superdev, VINUM_MOVE, msg) < 0)
fprintf(stderr,
"Can't move %s to %s: %s (%d)\n",
sd.name,
argv[0],
strerror(reply.error),
reply.error);
break;
case plex_object:
get_plex_info(&plex, srcobj);
for (sdno = 0; sdno < plex.subdisks; ++sdno) {
get_plex_sd_info(&sd, plex.plexno, sdno);
msg->index = destobj;
msg->otherobject = sd.sdno;
if (ioctl(superdev, VINUM_MOVE, msg) < 0)
fprintf(stderr,
"Can't move %s (part of %s) to %s: %s (%d)\n",
sd.name,
plex.name,
argv[0],
strerror(reply.error),
reply.error);
}
break;
case volume_object:
case invalid_object:
default:
fprintf(stderr, "Can't move %s (inappropriate object).\n", argv[i]);
break;
}
if (reply.error)
fprintf(stderr,
"Can't move %s to %s: %s (%d)\n",
argv[i],
argv[0],
strerror(reply.error),
reply.error);
}
checkupdates(); /* make sure we're updating */
}
/*
* Replace objects. Not implemented, may never be.
*/
void
vinum_replace(int argc, char *argv[], char *argv0[])
{
fprintf(stderr, "'replace' not implemented yet. Use 'move' instead\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 " _PATH_DEV "vinum.\n"
"move drive [subdisk | plex | drive]\n"
" Move the subdisks of the specified object(s) to drive.\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");
}
checkupdates(); /* make sure we're updating */
}
int
checkupdates()
{
int options;
if (ioctl(superdev, VINUM_GETDAEMON, &options) < 0)
fprintf(stderr, "Can't get daemon options: %s (%d)\n", strerror(errno), errno);
if (options & daemon_noupdate) {
fprintf(stderr, "*** Warning: configuration updates are disabled. ***\n");
return 1;
} else
return 0;
}
/* 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);
checkupdates(); /* make sure we're updating */
}
/*
* 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 (vflag)
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 (vflag)
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 (vflag)
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 (vflag)
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)
vinum_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 (vflag) {
vflag--; /* 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 (vflag)
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 (vflag)
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,
(long long) maxsize);
if (vflag)
printf(" sd name %s.p0.s%d drive %s size %lldb\n",
objectname,
o,
drive->label.name,
(long long) 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)
vinum_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 (vflag) {
vflag--; /* 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 RAID-4 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_raid4(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 < 3) {
fprintf(stderr, "You need at least three drives to create a RAID-4 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 (vflag)
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 raid4 256k", objectname);
if (vflag)
printf(" plex name %s.p0 org raid4 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,
(long long) maxsize);
if (vflag)
printf(" sd name %s.p0.s%d drive %s size %lldb\n",
objectname,
o,
drive->label.name,
(long long) 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)
vinum_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 (vflag) {
vflag--; /* 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 RAID-4 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_raid5(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 < 3) {
fprintf(stderr, "You need at least three drives to create a RAID-5 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 (vflag)
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 raid5 256k", objectname);
if (vflag)
printf(" plex name %s.p0 org raid5 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,
(long long) maxsize);
if (vflag)
printf(" sd name %s.p0.s%d drive %s size %lldb\n",
objectname,
o,
drive->label.name,
(long long) 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)
vinum_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 (vflag) {
vflag--; /* 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 (vflag)
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 (vflag)
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 (vflag)
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,
(long long) maxsize[p]);
if (vflag)
printf(" sd name %s.p%d.s%d drive %s size %lldb\n",
objectname,
p,
o >> 1,
drive->label.name,
(long long) 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)
vinum_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 (vflag) {
vflag--; /* 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 (vflag)
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:
state = 0; /* to keep the compiler happy */
}
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[])
{
Verbose = vflag; /* accept -v for verbose */
if (argc == 0) /* no parameters? */
fprintf(stderr, "Usage: checkparity object [object...]\n");
else
parityops(argc, argv, checkparity);
}
void
vinum_rebuildparity(int argc, char *argv[], char *argv0[])
{
if (argc == 0) /* no parameters? */
fprintf(stderr, "Usage: rebuildparity object [object...]\n");
else
parityops(argc, argv, vflag ? rebuildandcheckparity : rebuildparity);
}
/*
* Common code for rebuildparity and checkparity.
* We bend the meanings of some flags here:
*
* -v: Report incorrect parity on rebuild.
* -V: Show running count of position being checked.
* -f: Start from beginning of the plex.
*/
void
parityops(int argc, char *argv[], enum parityop op)
{
int object;
struct plex plex;
struct _ioctl_reply reply;
struct vinum_ioctl_msg *message = (struct vinum_ioctl_msg *) &reply;
int index;
enum objecttype type;
char *msg;
off_t block;
if (op == checkparity)
msg = "Checking";
else
msg = "Rebuilding";
for (index = 0; index < argc; index++) {
object = find_object(argv[index], &type); /* look for it */
if (type != plex_object)
fprintf(stderr, "%s is not a plex\n", argv[index]);
else {
get_plex_info(&plex, object);
if (!isparity((&plex)))
fprintf(stderr, "%s is not a RAID-4 or RAID-5 plex\n", argv[index]);
else {
do {
message->index = object; /* pass object number */
message->type = type; /* and type of object */
message->op = op; /* what to do */
if (force)
message->offset = 0; /* start at the beginning */
else
message->offset = plex.checkblock; /* continue where we left off */
force = 0; /* don't reset after the first time */
ioctl(superdev, VINUM_PARITYOP, message);
get_plex_info(&plex, object);
if (Verbose) {
block = (plex.checkblock << DEV_BSHIFT) * (plex.subdisks - 1);
if (block != 0)
printf("\r%s at %s (%d%%) ",
msg,
roughlength(block, 1),
((int) (block * 100 / plex.length) >> DEV_BSHIFT));
if ((reply.error == EAGAIN)
&& (reply.msg[0])) /* got a comment back */
fputs(reply.msg, stderr); /* show it */
fflush(stdout);
}
}
while (reply.error == EAGAIN);
if (reply.error != 0) {
if (reply.msg[0])
fputs(reply.msg, stderr);
else
fprintf(stderr,
"%s failed: %s\n",
msg,
strerror(reply.error));
} else if (Verbose) {
if (op == checkparity)
fprintf(stderr, "%s has correct parity\n", argv[index]);
else
fprintf(stderr, "Rebuilt parity on %s\n", argv[index]);
}
}
}
}
}
/* Local Variables: */
/* fill-column: 50 */
/* End: */