FreeBSD-5.3/sbin/vinum/v.c

Compare this file to the similar file:
Show the results in this format:

/* vinum.c: vinum interface program */
/*-
 * 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: v.c,v 1.38 2003/05/01 01:39:42 grog Exp $
 * $FreeBSD: src/sbin/vinum/v.c,v 1.40 2004/02/16 09:23:59 le Exp $
 */

#include "vext.h"

FILE *cf;						    /* config file handle */
FILE *History;						    /* history file */
char *historyfile;					    /* and its name */

char *dateformat;					    /* format in which to store date */

char buffer[BUFSIZE];					    /* buffer to read in to */

int line = 0;						    /* stdin line number for error messages */
int file_line = 0;					    /* and line in input file (yes, this is tacky) */
int inerror;						    /* set to 1 to exit after end of config file */

/* flags */

int debug = 0;						    /* debug flag, usage varies */
int force = 0;						    /* set to 1 to force some dangerous ops */
int interval = 0;					    /* interval in ms between init/revive */
int vflag = 0;						    /* set verbose operation or verify */
int Verbose = 0;					    /* set very verbose operation */
int recurse = 0;					    /* set recursion */
int sflag = 0;						    /* show statistics */
int SSize = 0;						    /* sector size for revive */
int dowait = 0;						    /* wait for completion */
char *objectname;					    /* name to be passed for -n flag */

/*
 * Structures to read kernel data into.  These are shortened versions
 * of the kernel data structures, without the bits and pieces we
 * shouldn't be using.
 */
struct __vinum_conf vinum_conf;				    /* configuration information */
struct _volume vol;
struct _plex plex;
struct _sd sd;
struct _drive drive;

jmp_buf command_fail;					    /* return on a failed command */
int superdev;						    /* vinum super device */
gid_t gid_operator;					    /* group operator for chown */
#define GROUP_OPERATOR "operator"
#define UID_ROOT 0					    /* no need to lookup... */

void start_daemon(void);

#define ofs(x) ((void *) (& ((struct confdata *) 0)->x))    /* offset of x in struct confdata */

char *token[MAXARGS];					    /* pointers to individual tokens */
int tokens;						    /* number of tokens */

int
main(int argc, char *argv[], char *envp[])
{
    struct stat histstat;
    struct group *g;

    if (modfind(VINUMMOD) < 0) {
	/* need to load the vinum module */
	if (kldload(VINUMMOD) < 0 || modfind(VINUMMOD) < 0) {
	    perror(VINUMMOD ": Kernel module not available");
	    return 1;
	}
    }
    dateformat = getenv("VINUM_DATEFORMAT");
    if (dateformat == NULL)
	dateformat = "%e %b %Y %H:%M:%S";
    historyfile = getenv("VINUM_HISTORY");
    if (historyfile == NULL)
	historyfile = DEFAULT_HISTORYFILE;
    if (stat(historyfile, &histstat) == 0) {		    /* history file exists */
	if ((histstat.st_mode & S_IFMT) != S_IFREG) {
	    fprintf(stderr,
		"Vinum history file %s must be a regular file\n",
		historyfile);
	    exit(1);
	}
    } else if ((errno != ENOENT)			    /* not "not there",  */
    &&(errno != EROFS)) {				    /* and not read-only file system */
	fprintf(stderr,
	    "Can't open %s: %s (%d)\n",
	    historyfile,
	    strerror(errno),
	    errno);
	exit(1);
    }
    History = fopen(historyfile, "a+");
    if (History != NULL) {
	timestamp();
	fprintf(History, "*** " VINUMMOD " started ***\n");
	fflush(History);				    /* before we start the daemon */
    }
    superdev = open(VINUM_SUPERDEV_NAME, O_RDWR);	    /* open vinum superdevice */
    if (superdev < 0) {					    /* no go */
	perror("Can't open " VINUM_SUPERDEV_NAME);
	return 1;
    }
    /*
     * Check that we match the kernel version.  There are a number of
     * possibilities here:
     *
     * 0: The versions are OK.
     * 1: The kernel module could be a pre-version 1 module, which
     *    doesn't include this check.  In that case, vinum_conf will be too
     *    short, and so we'll get an EINVAL back when trying to get it.  In
     *    this case we'll fake a 0 in the version.
     * 2: The module versions are different.  Print appropriate messages
     *    and die.
     */
    if (ioctl(superdev, VINUM_GETCONFIG, &vinum_conf) < 0) {
	if (errno == EINVAL)				    /* wrong length, */
	    vinum_conf.version = 0;			    /* must be the old version */
	else {
	    perror("Can't get vinum config");
	    return 1;
	}
    }
    if (vinum_conf.version != VINUMVERSION) {
	fprintf(stderr,
	    "Version mismatch.  The kernel module is version %d of Vinum,\n"
	    "but this program is designed for version %d\n",
	    vinum_conf.version,
	    VINUMVERSION);
	if (vinum_conf.version < VINUMVERSION)
	    fprintf(stderr, "Please upgrade your kernel module.\n");
	else
	    fprintf(stderr, "Please upgrade vinum(8).\n");
	return 1;
    }
    /* Check if the dæmon is running.  If not, start it in the
     * background */
    start_daemon();
    if ((g = getgrnam(GROUP_OPERATOR)) != NULL)
	gid_operator = g->gr_gid;
    endgrent();

    if (argc > 1) {					    /* we have a command on the line */
	if (setjmp(command_fail) != 0)			    /* long jumped out */
	    return -1;
	parseline(argc - 1, &argv[1]);			    /* do it */
    } else {
	/*
	 * Catch a possible race condition which could cause us to
	 * longjmp() into nowhere if we receive a SIGINT in the next few
	 * lines.
	 */
	if (setjmp(command_fail))			    /* come back here on catastrophic failure */
	    return 1;
	setsigs();					    /* set signal handler */
	for (;;) {					    /* ugh */
	    char *c;
	    int childstatus;				    /* from wait4 */

	    if (setjmp(command_fail) == 2)		    /* come back here on catastrophic failure */
		fprintf(stderr, "*** interrupted ***\n");   /* interrupted */

	    while (wait4(-1, &childstatus, WNOHANG, NULL) > 0);	/* wait for all dead children */
	    c = readline(VINUMMOD " -> ");		    /* get an input */
	    if (c == NULL) {				    /* EOF or error */
		if (ferror(stdin)) {
		    fprintf(stderr, "Can't read input: %s (%d)\n", strerror(errno), errno);
		    return 1;
		} else {				    /* EOF */
		    printf("\n");
		    return 0;
		}
	    } else if (*c) {				    /* got something there */
		add_history(c);				    /* save it in the history */
		strcpy(buffer, c);			    /* put it where we can munge it */
		free(c);
		line++;					    /* count the lines */
		tokens = tokenize(buffer, token, MAXARGS);
		/* got something potentially worth parsing */
		if (tokens)
		    parseline(tokens, token);		    /* and do what he says */
	    }
	    if (History)
		fflush(History);
	}
    }
    return 0;						    /* normal completion */
}

/* stop the hard way */
void
vinum_quit(int argc, char *argv[], char *argv0[])
{
    exit(0);
}

/* Set action on receiving a SIGINT */
void
setsigs()
{
    struct sigaction act;

    act.sa_handler = catchsig;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT, &act, NULL);
}

void
catchsig(int ignore)
{
    longjmp(command_fail, 2);
}

#define FUNKEY(x) { kw_##x, &vinum_##x }		    /* create pair "kw_foo", vinum_foo */
#define vinum_move vinum_mv				    /* synonym for 'mv' */

struct funkey {
    enum keyword kw;
    void (*fun) (int argc, char *argv[], char *arg0[]);
} funkeys[] = {

    FUNKEY(create),
	FUNKEY(read),
	FUNKEY(debug),
	FUNKEY(modify),
	FUNKEY(list),
	FUNKEY(ld),
	FUNKEY(ls),
	FUNKEY(lp),
	FUNKEY(lv),
	FUNKEY(info),
	FUNKEY(set),
	FUNKEY(init),
	FUNKEY(resetconfig),
	FUNKEY(rm),
	FUNKEY(mv),
	FUNKEY(move),
	FUNKEY(attach),
	FUNKEY(detach),
	FUNKEY(rename),
	FUNKEY(replace),
	FUNKEY(printconfig),
	FUNKEY(saveconfig),
	FUNKEY(start),
	FUNKEY(stop),
	FUNKEY(makedev),
	FUNKEY(help),
	FUNKEY(quit),
	FUNKEY(concat),
	FUNKEY(stripe),
	FUNKEY(raid4),
	FUNKEY(raid5),
	FUNKEY(mirror),
	FUNKEY(setdaemon),
	FUNKEY(readpol),
	FUNKEY(resetstats),
	FUNKEY(setstate),
	FUNKEY(checkparity),
	FUNKEY(rebuildparity),
	FUNKEY(dumpconfig)
};

/* Take args arguments at argv and attempt to perform the operation specified */
void
parseline(int args, char *argv[])
{
    int i;
    int j;
    enum keyword command;				    /* command to execute */

    if (History != NULL) {				    /* save the command to history file */
	timestamp();
	for (i = 0; i < args; i++)			    /* all args */
	    fprintf(History, "%s ", argv[i]);
	fputs("\n", History);
    }
    if ((args == 0)					    /* empty line */
    ||(*argv[0] == '#'))				    /* or a comment, */
	return;
    if (args == MAXARGS) {				    /* too many arguments, */
	fprintf(stderr, "Too many arguments to %s, this can't be right\n", argv[0]);
	return;
    }
    command = get_keyword(argv[0], &keyword_set);
    dowait = 0;						    /* initialize flags */
    force = 0;						    /* initialize flags */
    vflag = 0;						    /* initialize flags */
    Verbose = 0;					    /* initialize flags */
    recurse = 0;					    /* initialize flags */
    sflag = 0;						    /* initialize flags */
    objectname = NULL;					    /* no name yet */

    /*
     * first handle generic options
     * We don't use getopt(3) because
     * getopt doesn't allow merging flags
     * (for example, -fr).
     */
    for (i = 1; (i < args) && (argv[i][0] == '-'); i++) {   /* while we have flags */
	for (j = 1; j < strlen(argv[i]); j++)
	    switch (argv[i][j]) {
	    case 'd':					    /* -d: debug */
		debug = 1;
		break;

	    case 'f':					    /* -f: force */
		force = 1;
		break;

	    case 'i':					    /* interval */
		interval = 0;
		if (argv[i][j + 1] != '\0')		    /* operand follows, */
		    interval = atoi(&argv[i][j + 1]);	    /* use it */
		else if (args > (i + 1))		    /* another following, */
		    interval = atoi(argv[++i]);		    /* use it */
		if (interval == 0)			    /* nothing valid, */
		    fprintf(stderr, "-i: no interval specified\n");
		break;

	    case 'n':					    /* -n: get name */
		if (i == args - 1) {			    /* last arg */
		    fprintf(stderr, "-n requires a name parameter\n");
		    return;
		}
		objectname = argv[++i];			    /* pick it up */
		j = strlen(argv[i]);			    /* skip the next parm */
		break;

	    case 'r':					    /* -r: recurse */
		recurse = 1;
		break;

	    case 's':					    /* -s: show statistics */
		sflag = 1;
		break;

	    case 'S':
		SSize = 0;
		if (argv[i][j + 1] != '\0')		    /* operand follows, */
		    SSize = atoi(&argv[i][j + 1]);	    /* use it */
		else if (args > (i + 1))		    /* another following, */
		    SSize = atoi(argv[++i]);		    /* use it */
		if (SSize == 0)				    /* nothing valid, */
		    fprintf(stderr, "-S: no size specified\n");
		break;

	    case 'v':					    /* -v: verbose */
		vflag++;
		break;

	    case 'V':					    /* -V: Very verbose */
		vflag++;
		Verbose++;
		break;

	    case 'w':					    /* -w: wait for completion */
		dowait = 1;
		break;

	    default:
		fprintf(stderr, "Invalid flag: %s\n", argv[i]);
	    }
    }

    /* Pass what we have left to the command to handle it */
    for (j = 0; j < (sizeof(funkeys) / sizeof(struct funkey)); j++) {
	if (funkeys[j].kw == command) {			    /* found the command */
	    funkeys[j].fun(args - i, &argv[i], &argv[0]);
	    return;
	}
    }
    fprintf(stderr, "Unknown command: %s\n", argv[0]);
}

void
get_drive_info(struct _drive *drive, int index)
{
    *(int *) drive = index;				    /* put in drive to hand to driver */
    if (ioctl(superdev, VINUM_DRIVECONFIG, drive) < 0) {
	fprintf(stderr,
	    "Can't get config for drive %d: %s\n",
	    index,
	    strerror(errno));
	longjmp(command_fail, -1);
    }
}

void
get_sd_info(struct _sd *sd, int index)
{
    *(int *) sd = index;				    /* put in sd to hand to driver */
    if (ioctl(superdev, VINUM_SDCONFIG, sd) < 0) {
	fprintf(stderr,
	    "Can't get config for subdisk %d: %s\n",
	    index,
	    strerror(errno));
	longjmp(command_fail, -1);
    }
}

/* Get the contents of the sd entry for subdisk <sdno>
 * of the specified plex. */
void
get_plex_sd_info(struct _sd *sd, int plexno, int sdno)
{
    ((int *) sd)[0] = plexno;
    ((int *) sd)[1] = sdno;				    /* pass parameters */
    if (ioctl(superdev, VINUM_PLEXSDCONFIG, sd) < 0) {
	fprintf(stderr,
	    "Can't get config for subdisk %d (part of plex %d): %s\n",
	    sdno,
	    plexno,
	    strerror(errno));
	longjmp(command_fail, -1);
    }
}

void
get_plex_info(struct _plex *plex, int index)
{
    *(int *) plex = index;				    /* put in plex to hand to driver */
    if (ioctl(superdev, VINUM_PLEXCONFIG, plex) < 0) {
	fprintf(stderr,
	    "Can't get config for plex %d: %s\n",
	    index,
	    strerror(errno));
	longjmp(command_fail, -1);
    }
}

void
get_volume_info(struct _volume *volume, int index)
{
    *(int *) volume = index;				    /* put in volume to hand to driver */
    if (ioctl(superdev, VINUM_VOLCONFIG, volume) < 0) {
	fprintf(stderr,
	    "Can't get config for volume %d: %s\n",
	    index,
	    strerror(errno));
	longjmp(command_fail, -1);
    }
}

struct _drive *
find_drive_by_devname(char *name)
{
    int driveno;

    if (ioctl(superdev, VINUM_GETCONFIG, &vinum_conf) < 0) {
	perror("Can't get vinum config");
	return NULL;
    }
    for (driveno = 0; driveno < vinum_conf.drives_allocated; driveno++) {
	get_drive_info(&drive, driveno);
	if ((drive.state != drive_unallocated)		    /* real drive */
	&&(!strcmp(drive.devicename, name)))		    /* and the name's right, */
	    return &drive;				    /* found it */
    }
    return NULL;					    /* no drive of that name */
}

/* command line interface for the 'makedev' command */
void
vinum_makedev(int argc, char *argv[], char *arg0[])
{
    fprintf(stderr, "makedev is not needed for a DEVFS-based system\n");
}

/*
 * Find the object "name".  Return object type at type,
 * and the index as the return value.
 * If not found, return -1 and invalid_object.
 */
int
find_object(const char *name, enum objecttype *type)
{
    int object;

    if (ioctl(superdev, VINUM_GETCONFIG, &vinum_conf) < 0) {
	perror("Can't get vinum config");
	*type = invalid_object;
	return -1;
    }
    /* Search the drive table */
    for (object = 0; object < vinum_conf.drives_allocated; object++) {
	get_drive_info(&drive, object);
	if (strcmp(name, drive.label.name) == 0) {
	    *type = drive_object;
	    return object;
	}
    }

    /* Search the subdisk table */
    for (object = 0; object < vinum_conf.subdisks_allocated; object++) {
	get_sd_info(&sd, object);
	if (strcmp(name, sd.name) == 0) {
	    *type = sd_object;
	    return object;
	}
    }

    /* Search the plex table */
    for (object = 0; object < vinum_conf.plexes_allocated; object++) {
	get_plex_info(&plex, object);
	if (strcmp(name, plex.name) == 0) {
	    *type = plex_object;
	    return object;
	}
    }

    /* Search the volume table */
    for (object = 0; object < vinum_conf.volumes_allocated; object++) {
	get_volume_info(&vol, object);
	if (strcmp(name, vol.name) == 0) {
	    *type = volume_object;
	    return object;
	}
    }

    /* Didn't find the name: invalid */
    *type = invalid_object;
    return -1;
}

/* Continue reviving a subdisk in the background */
void
continue_revive(int sdno)
{
    struct _sd sd;
    pid_t pid;
    get_sd_info(&sd, sdno);

    if (dowait == 0)
	pid = fork();					    /* do this in the background */
    else
	pid = 0;
    if (pid == 0) {					    /* we're the child */
	struct _ioctl_reply reply;
	struct vinum_ioctl_msg *message = (struct vinum_ioctl_msg *) &reply;

	openlog(VINUMMOD, LOG_CONS | LOG_PERROR | LOG_PID, LOG_KERN);
	syslog(LOG_INFO | LOG_KERN, "reviving %s", sd.name);
	setproctitle("reviving %s", sd.name);

	for (reply.error = EAGAIN; reply.error == EAGAIN;) { /* revive the subdisk */
	    if (interval)
		usleep(interval * 1000);		    /* pause between each copy */
	    message->index = sdno;			    /* pass sd number */
	    message->type = sd_object;			    /* and type of object */
	    message->state = object_up;
	    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) {
	    syslog(LOG_ERR | LOG_KERN,
		"can't revive %s: %s",
		sd.name,
		reply.msg[0] ? reply.msg : strerror(reply.error));
	    if (dowait == 0)
		exit(1);
	} else {
	    get_sd_info(&sd, sdno);			    /* update the info */
	    syslog(LOG_INFO | LOG_KERN, "%s is %s", sd.name, sd_state(sd.state));
	    if (dowait == 0)
		exit(0);
	}
    } else if (pid < 0)					    /* couldn't fork? */
	fprintf(stderr, "Can't continue reviving %s: %s\n", sd.name, strerror(errno));
    else						    /* parent */
	printf("Reviving %s in the background\n", sd.name);
}

/*
 * Check if the daemon is running,
 * start it if it isn't.  The check itself
 * could take a while, so we do it as a separate
 * process, which will become the daemon if one isn't
 * running already
 */
void
start_daemon(void)
{
    int pid;
    int status;
    int error;

    pid = (int) fork();

    if (pid == 0) {					    /* We're the child, do the work */
	/*
	 * We have a problem when stopping the subsystem:
	 * The only way to know that we're idle is when
	 * all open superdevs close.  But we want the
	 * daemon to clean up for us, and since we can't
	 * count the opens, we need to have the main device
	 * closed when we stop.  We solve this conundrum
	 * by getting the daemon to open a separate device.
	 */
	close(superdev);				    /* this is the wrong device */
	superdev = open(VINUM_DAEMON_DEV_NAME, O_RDWR);	    /* open deamon superdevice */
	if (superdev < 0) {
	    perror("Can't open " VINUM_DAEMON_DEV_NAME);
	    exit(1);
	}
	error = daemon(0, 0);				    /* this will fork again, but who's counting? */
	if (error != 0) {
	    fprintf(stderr, "Can't start daemon: %s (%d)\n", strerror(errno), errno);
	    exit(1);
	}
	setproctitle(VINUMMOD " daemon");		    /* show what we're doing */
	status = ioctl(superdev, VINUM_FINDDAEMON, NULL);
	if (status != 0) {				    /* no daemon, */
	    ioctl(superdev, VINUM_DAEMON, &vflag);	    /* we should hang here */
	    syslog(LOG_ERR | LOG_KERN, "%s", strerror(errno));
	    exit(1);
	}
	exit(0);					    /* when told to die */
    } else if (pid < 0)					    /* couldn't fork */
	printf("Can't fork to check daemon\n");
}

void
timestamp()
{
    struct timeval now;
    struct tm *date;
    char datetext[MAXDATETEXT];
    time_t sec;

    if (History != NULL) {
	if (gettimeofday(&now, NULL) != 0) {
	    fprintf(stderr, "Can't get time: %s\n", strerror(errno));
	    return;
	}
	sec = now.tv_sec;
	date = localtime(&sec);
	strftime(datetext, MAXDATETEXT, dateformat, date),
	    fprintf(History,
	    "%s.%06ld ",
	    datetext,
	    now.tv_usec);
    }
}