/* * XXX replace all the checks on object validity with * calls to valid */ /*- * Copyright (c) 1997, 1998, 1999 * Nan Yang Computer Services Limited. All rights reserved. * * Parts copyright (c) 1997, 1998 Cybernet Corporation, NetMAX project. * * 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: vinumioctl.c,v 1.14 2000/10/27 03:07:53 grog Exp grog $ * $FreeBSD: src/sys/dev/vinum/vinumioctl.c,v 1.25.2.4 2002/02/03 00:44:19 grog Exp $ */ #include "vinumhdr.h" #include "request.h" #ifdef VINUMDEBUG #include #endif void attachobject(struct vinum_ioctl_msg *); void detachobject(struct vinum_ioctl_msg *); void renameobject(struct vinum_rename_msg *); void replaceobject(struct vinum_ioctl_msg *); void moveobject(struct vinum_ioctl_msg *); jmp_buf command_fail; /* return on a failed command */ /* ioctl routine */ int vinumioctl(struct dev_ioctl_args *ap) { cdev_t dev = ap->a_head.a_dev; u_long cmd = ap->a_cmd; caddr_t data = ap->a_data; int error; unsigned int index; /* for transferring config info */ unsigned int sdno; /* for transferring config info */ unsigned int objno; struct volume *vol; struct partinfo *dpart; int fe; /* free list element number */ struct _ioctl_reply *ioctl_reply; /* struct to return */ error = 0; /* First, decide what we're looking at */ switch (DEVTYPE(dev)) { case VINUM_SUPERDEV_TYPE: /* ordinary super device */ ioctl_reply = (struct _ioctl_reply *) data; /* save the address to reply to */ switch (cmd) { #ifdef VINUMDEBUG case VINUM_DEBUG: if (((struct debuginfo *) data)->changeit) /* change debug settings */ debug = (((struct debuginfo *) data)->param); else { if (debug & DEBUG_REMOTEGDB) boothowto |= RB_GDB; /* serial debug line */ else boothowto &= ~RB_GDB; /* local ddb */ Debugger("vinum debug"); } ioctl_reply = (struct _ioctl_reply *) data; /* reinstate the address to reply to */ ioctl_reply->error = 0; break; #endif case VINUM_CREATE: /* create a vinum object */ error = lock_config(); /* get the config for us alone */ if (error) /* can't do it, */ break; error = setjmp(command_fail); /* come back here on error */ if (error == 0) /* first time, */ ioctl_reply->error = parse_user_config((char *) data, /* update the config */ &keyword_set); else if (ioctl_reply->error == 0) { /* longjmp, but no error status */ error = 0; ioctl_reply->error = EINVAL; /* note that something's up */ ioctl_reply->msg[0] = '\0'; /* no message? */ } unlock_config(); break; case VINUM_GETCONFIG: /* get the configuration information */ bcopy(&vinum_conf, data, sizeof(vinum_conf)); break; /* start configuring the subsystem */ case VINUM_STARTCONFIG: error = start_config(*(int *) data); /* just lock it. Parameter is 'force' */ break; case VINUM_DRIVECONFIG: /* * Move the individual parts of the config to user space. * * Specify the index of the object in the first word of data, * and return the object there */ index = *(int *) data; if (index >= (unsigned)vinum_conf.drives_allocated) { error = ENXIO; } else { bcopy(&DRIVE[index], data, sizeof(struct drive)); } break; case VINUM_SDCONFIG: index = *(int *) data; if (index >= (unsigned) vinum_conf.subdisks_allocated) { error = ENXIO; } else { bcopy(&SD[index], data, sizeof(struct sd)); } break; case VINUM_PLEXCONFIG: index = *(int *) data; if (index >= (unsigned) vinum_conf.plexes_allocated) { error = ENXIO; } else { bcopy(&PLEX[index], data, sizeof(struct plex)); } break; case VINUM_VOLCONFIG: index = *(int *) data; if (index >= (unsigned) vinum_conf.volumes_allocated) { error = ENXIO; } else { bcopy(&VOL[index], data, sizeof(struct volume)); } break; case VINUM_PLEXSDCONFIG: index = ((int *)data)[0]; /* get the plex index */ sdno = ((int *)data)[1]; /* and the sd index */ if ((index >= (unsigned) vinum_conf.plexes_allocated) ||(sdno >= PLEX[index].subdisks)) { error = ENXIO; } else { bcopy(&SD[PLEX[index].sdnos[sdno]], data, sizeof(struct sd)); } break; case VINUM_SAVECONFIG: /* * We get called in two places: one from the * userland config routines, which call us * to complete the config and save it. This * call supplies the value 0 as a parameter. * * The other place is from the user "saveconfig" * routine, which can only work if we're *not* * configuring. In this case, supply parameter 1. */ if (VFLAGS & VF_CONFIGURING) { /* must be us, the others are asleep */ if (*(int *) data == 0) /* finish config */ finish_config(1); /* finish the configuration and update it */ else error = EBUSY; } if (error == 0) save_config(); /* save configuration to disk */ break; case VINUM_RELEASECONFIG: /* release the config */ if (VFLAGS & VF_CONFIGURING) { /* must be us, the others are asleep */ finish_config(0); /* finish the configuration, don't change it */ save_config(); /* save configuration to disk */ } else { error = EINVAL; /* release what config? */ } break; case VINUM_INIT: ioctl_reply = (struct _ioctl_reply *) data; /* reinstate the address to reply to */ ioctl_reply->error = 0; break; case VINUM_RESETCONFIG: if (vinum_inactive(0)) { /* if the volumes are not active */ /* * Note the open count. We may be called from v, so we'll be open. * Keep the count so we don't underflow */ free_vinum(1); /* clean up everything */ log(LOG_NOTICE, "vinum: CONFIGURATION OBLITERATED\n"); ioctl_reply = (struct _ioctl_reply *) data; /* reinstate the address to reply to */ ioctl_reply->error = 0; } else { error = EBUSY; } case VINUM_SETSTATE: setstate((struct vinum_ioctl_msg *) data); /* set an object state */ break; /* * Set state by force, without changing * anything else. */ case VINUM_SETSTATE_FORCE: setstate_by_force((struct vinum_ioctl_msg *) data); /* set an object state */ break; #ifdef VINUMDEBUG case VINUM_MEMINFO: vinum_meminfo(data); break; case VINUM_MALLOCINFO: error = vinum_mallocinfo(data); break; case VINUM_RQINFO: error = vinum_rqinfo(data); break; #endif case VINUM_REMOVE: remove((struct vinum_ioctl_msg *) data); /* remove an object */ break; case VINUM_GETFREELIST: /* get a drive free list element */ index = *(int *) data; /* get the drive index */ fe = ((int *) data)[1]; /* and the free list element */ if ((index >= (unsigned) vinum_conf.drives_allocated) /* plex doesn't exist */ ||(DRIVE[index].state == drive_unallocated)) { error = ENODEV; } else if (fe >= DRIVE[index].freelist_entries) { error = ENOENT; } else { bcopy(&DRIVE[index].freelist[fe], data, sizeof(struct drive_freelist)); } break; case VINUM_RESETSTATS: resetstats((struct vinum_ioctl_msg *) data); /* reset object stats */ break; /* attach an object to a superordinate object */ case VINUM_ATTACH: attachobject((struct vinum_ioctl_msg *) data); break; /* detach an object from a superordinate object */ case VINUM_DETACH: detachobject((struct vinum_ioctl_msg *) data); break; /* rename an object */ case VINUM_RENAME: renameobject((struct vinum_rename_msg *) data); break; /* replace an object */ case VINUM_REPLACE: replaceobject((struct vinum_ioctl_msg *) data); break; case VINUM_DAEMON: vinum_daemon(); /* perform the daemon */ break; case VINUM_FINDDAEMON: /* check for presence of daemon */ error = vinum_finddaemon(); break; case VINUM_SETDAEMON: /* set daemon flags */ error = vinum_setdaemonopts(*(int *) data); break; case VINUM_GETDAEMON: /* get daemon flags */ *(int *) data = daemon_options; break; case VINUM_PARITYOP: /* check/rebuild RAID-4/5 parity */ parityops((struct vinum_ioctl_msg *) data); break; /* move an object */ case VINUM_MOVE: moveobject((struct vinum_ioctl_msg *) data); break; case VINUM_LABEL: default: error = EINVAL; break; } break; case VINUM_DRIVE_TYPE: case VINUM_SD_TYPE: case VINUM_RAWSD_TYPE: case VINUM_RAWPLEX_TYPE: case VINUM_PLEX_TYPE: error = EINVAL; break; case VINUM_VOLUME_TYPE: objno = Volno(dev); if (objno >= (unsigned)vinum_conf.volumes_allocated) { error = ENXIO; break; } vol = &VOL[objno]; if (vol->state != volume_up) { error = EIO; break; } switch(cmd) { case DIOCGPART: dpart = (void *)data; bzero(dpart, sizeof(*dpart)); dpart->media_offset = 0; dpart->media_size = (u_int64_t)vol->size * DEV_BSIZE; dpart->media_blocks = vol->size; dpart->media_blksize = DEV_BSIZE; dpart->fstype = FS_BSDFFS; break; default: error = EINVAL; } break; default: error = EINVAL; break; } if (error) { log(LOG_WARNING, "vinumioctl: invalid ioctl from process %d (%s): %lx\n", curproc->p_pid, curproc->p_comm, cmd); } return error; } /* * The following four functions check the supplied * object index and return a pointer to the object * if it exists. Otherwise they longjmp out via * throw_rude_remark. */ struct drive * validdrive(int driveno, struct _ioctl_reply *reply) { if ((driveno < vinum_conf.drives_allocated) && (DRIVE[driveno].state > drive_referenced)) return &DRIVE[driveno]; strcpy(reply->msg, "No such drive"); reply->error = ENOENT; return NULL; } struct sd * validsd(int sdno, struct _ioctl_reply *reply) { if ((sdno < vinum_conf.subdisks_allocated) && (SD[sdno].state > sd_referenced)) return &SD[sdno]; strcpy(reply->msg, "No such subdisk"); reply->error = ENOENT; return NULL; } struct plex * validplex(int plexno, struct _ioctl_reply *reply) { if ((plexno < vinum_conf.plexes_allocated) && (PLEX[plexno].state > plex_referenced)) return &PLEX[plexno]; strcpy(reply->msg, "No such plex"); reply->error = ENOENT; return NULL; } struct volume * validvol(int volno, struct _ioctl_reply *reply) { if ((volno < vinum_conf.volumes_allocated) && (VOL[volno].state > volume_uninit)) return &VOL[volno]; strcpy(reply->msg, "No such volume"); reply->error = ENOENT; return NULL; } /* reset an object's stats */ void resetstats(struct vinum_ioctl_msg *msg) { struct _ioctl_reply *reply = (struct _ioctl_reply *) msg; switch (msg->type) { case drive_object: if (msg->index < vinum_conf.drives_allocated) { struct drive *drive = &DRIVE[msg->index]; if (drive->state > drive_referenced) { drive->reads = 0; /* number of reads on this drive */ drive->writes = 0; /* number of writes on this drive */ drive->bytes_read = 0; /* number of bytes read */ drive->bytes_written = 0; /* number of bytes written */ reply->error = 0; return; } reply->error = EINVAL; return; } case sd_object: if (msg->index < vinum_conf.subdisks_allocated) { struct sd *sd = &SD[msg->index]; if (sd->state > sd_referenced) { sd->reads = 0; /* number of reads on this subdisk */ sd->writes = 0; /* number of writes on this subdisk */ sd->bytes_read = 0; /* number of bytes read */ sd->bytes_written = 0; /* number of bytes written */ reply->error = 0; return; } reply->error = EINVAL; return; } break; case plex_object: if (msg->index < vinum_conf.plexes_allocated) { struct plex *plex = &PLEX[msg->index]; if (plex->state > plex_referenced) { plex->reads = 0; plex->writes = 0; /* number of writes on this plex */ plex->bytes_read = 0; /* number of bytes read */ plex->bytes_written = 0; /* number of bytes written */ plex->recovered_reads = 0; /* number of recovered read operations */ plex->degraded_writes = 0; /* number of degraded writes */ plex->parityless_writes = 0; /* number of parityless writes */ plex->multiblock = 0; /* requests that needed more than one block */ plex->multistripe = 0; /* requests that needed more than one stripe */ reply->error = 0; return; } reply->error = EINVAL; return; } break; case volume_object: if (msg->index < vinum_conf.volumes_allocated) { struct volume *vol = &VOL[msg->index]; if (vol->state > volume_uninit) { vol->bytes_read = 0; /* number of bytes read */ vol->bytes_written = 0; /* number of bytes written */ vol->reads = 0; /* number of reads on this volume */ vol->writes = 0; /* number of writes on this volume */ vol->recovered_reads = 0; /* reads recovered from another plex */ reply->error = 0; return; } reply->error = EINVAL; return; } case invalid_object: /* can't get this */ reply->error = EINVAL; return; } } /* attach an object to a superior object */ void attachobject(struct vinum_ioctl_msg *msg) { struct _ioctl_reply *reply = (struct _ioctl_reply *) msg; int sdno; struct sd *sd; struct plex *plex; struct volume *vol; switch (msg->type) { case drive_object: /* you can't attach a drive to anything */ case volume_object: /* nor a volume */ case invalid_object: /* "this can't happen" */ reply->error = EINVAL; reply->msg[0] = '\0'; /* vinum(8) doesn't do this */ return; case sd_object: sd = validsd(msg->index, reply); if (sd == NULL) /* not a valid subdisk */ return; plex = validplex(msg->otherobject, reply); if (plex) { /* * We should be more intelligent about this. * We should be able to reattach a dead * subdisk, but if we want to increase the total * number of subdisks, we have a lot of reshuffling * to do. XXX */ if ((plex->organization != plex_concat) /* can't attach to striped and RAID-4/5 */ &&(!msg->force)) { /* without using force */ reply->error = EINVAL; /* no message, the user should check */ strcpy(reply->msg, "Can't attach to this plex organization"); return; } if (sd->plexno >= 0) { /* already belong to a plex */ reply->error = EBUSY; /* no message, the user should check */ reply->msg[0] = '\0'; return; } sd->plexoffset = msg->offset; /* this is where we want it */ set_sd_state(sd->sdno, sd_stale, setstate_force); /* make sure it's stale */ give_sd_to_plex(plex->plexno, sd->sdno); /* and give it to the plex */ update_sd_config(sd->sdno, 0); save_config(); } if (sd->state == sd_reviving) reply->error = EAGAIN; /* need to revive it */ else reply->error = 0; break; case plex_object: plex = validplex(msg->index, reply); /* get plex */ if (plex == NULL) return; vol = validvol(msg->otherobject, reply); /* and volume information */ if (vol) { if ((vol->plexes == MAXPLEX) /* we have too many already */ ||(plex->volno >= 0)) { /* or the plex has an owner */ reply->error = EINVAL; /* no message, the user should check */ reply->msg[0] = '\0'; return; } for (sdno = 0; sdno < plex->subdisks; sdno++) { sd = &SD[plex->sdnos[sdno]]; if (sd->state > sd_down) /* real subdisk, vaguely accessible */ set_sd_state(plex->sdnos[sdno], sd_stale, setstate_force); /* make it stale */ } set_plex_state(plex->plexno, plex_up, setstate_none); /* update plex state */ give_plex_to_volume(msg->otherobject, msg->index); /* and give it to the volume */ update_plex_config(plex->plexno, 0); save_config(); } } } /* detach an object from a superior object */ void detachobject(struct vinum_ioctl_msg *msg) { struct _ioctl_reply *reply = (struct _ioctl_reply *) msg; struct sd *sd; struct plex *plex; struct volume *vol; int sdno; int plexno; switch (msg->type) { case drive_object: /* you can't detach a drive from anything */ case volume_object: /* nor a volume */ case invalid_object: /* "this can't happen" */ reply->error = EINVAL; reply->msg[0] = '\0'; /* vinum(8) doesn't do this */ return; case sd_object: sd = validsd(msg->index, reply); if (sd == NULL) return; if (sd->plexno < 0) { /* doesn't belong to a plex */ reply->error = ENOENT; strcpy(reply->msg, "Subdisk is not attached"); return; } else { /* valid plex number */ plex = &PLEX[sd->plexno]; if ((!msg->force) /* don't force things */ &&((plex->state == plex_up) /* and the plex is up */ ||((plex->state == plex_flaky) && sd->state == sd_up))) { /* or flaky with this sd up */ reply->error = EBUSY; /* we need this sd */ reply->msg[0] = '\0'; return; } sd->plexno = -1; /* anonymous sd */ if (plex->subdisks == 1) { /* this was the only subdisk */ Free(plex->sdnos); /* free the subdisk array */ plex->sdnos = NULL; /* and note the fact */ plex->subdisks_allocated = 0; /* no subdisk space */ } else { for (sdno = 0; sdno < plex->subdisks; sdno++) { if (plex->sdnos[sdno] == msg->index) /* found our subdisk */ break; } if (sdno < (plex->subdisks - 1)) /* not the last one, compact */ bcopy(&plex->sdnos[sdno + 1], &plex->sdnos[sdno], (plex->subdisks - 1 - sdno) * sizeof(int)); } plex->subdisks--; if (!bcmp(plex->name, sd->name, strlen(plex->name) + 1)) /* this subdisk is named after the plex */ { bcopy(sd->name, &sd->name[3], imin(strlen(sd->name) + 1, MAXSDNAME - 3)); bcopy("ex-", sd->name, 3); sd->name[MAXSDNAME - 1] = '\0'; } update_plex_config(plex->plexno, 0); if (isstriped(plex)) /* we've just mutilated our plex, */ set_plex_state(plex->plexno, plex_down, setstate_force | setstate_configuring); save_config(); reply->error = 0; } return; case plex_object: plex = validplex(msg->index, reply); /* get plex */ if (plex == NULL) return; if (plex->volno >= 0) { int volno = plex->volno; vol = &VOL[volno]; if ((!msg->force) /* don't force things */ &&((vol->state == volume_up) /* and the volume is up */ &&(vol->plexes == 1))) { /* and this is the last plex */ /* * XXX As elsewhere, check whether we will lose * mapping by removing this plex */ reply->error = EBUSY; /* we need this plex */ reply->msg[0] = '\0'; return; } plex->volno = -1; /* anonymous plex */ for (plexno = 0; plexno < vol->plexes; plexno++) { if (vol->plex[plexno] == msg->index) /* found our plex */ break; } if (plexno < (vol->plexes - 1)) /* not the last one, compact */ bcopy(&vol->plex[plexno + 1], &vol->plex[plexno], (vol->plexes - 1 - plexno) * sizeof(int)); vol->plexes--; vol->last_plex_read = 0; /* don't go beyond the end */ if (!bcmp(vol->name, plex->name, strlen(vol->name) + 1)) /* this plex is named after the volume */ { /* First, check if the subdisks are the same */ if (msg->recurse) { int sdno; for (sdno = 0; sdno < plex->subdisks; sdno++) { struct sd *sd = &SD[plex->sdnos[sdno]]; if (!bcmp(plex->name, sd->name, strlen(plex->name) + 1)) /* subdisk is named after the plex */ { bcopy(sd->name, &sd->name[3], imin(strlen(sd->name) + 1, MAXSDNAME - 3)); bcopy("ex-", sd->name, 3); sd->name[MAXSDNAME - 1] = '\0'; } } } bcopy(plex->name, &plex->name[3], imin(strlen(plex->name) + 1, MAXPLEXNAME - 3)); bcopy("ex-", plex->name, 3); plex->name[MAXPLEXNAME - 1] = '\0'; } update_volume_config(volno, 0); save_config(); reply->error = 0; } else { reply->error = ENOENT; strcpy(reply->msg, "Plex is not attached"); } } } void renameobject(struct vinum_rename_msg *msg) { struct _ioctl_reply *reply = (struct _ioctl_reply *) msg; struct drive *drive; struct sd *sd; struct plex *plex; struct volume *vol; switch (msg->type) { case drive_object: /* you can't attach a drive to anything */ if (find_drive(msg->newname, 0) >= 0) { /* we have that name already, */ reply->error = EEXIST; reply->msg[0] = '\0'; return; } drive = validdrive(msg->index, reply); if (drive) { bcopy(msg->newname, drive->label.name, MAXDRIVENAME); save_config(); reply->error = 0; } return; case sd_object: /* you can't attach a subdisk to anything */ if (find_subdisk(msg->newname, 0) >= 0) { /* we have that name already, */ reply->error = EEXIST; reply->msg[0] = '\0'; return; } sd = validsd(msg->index, reply); if (sd) { bcopy(msg->newname, sd->name, MAXSDNAME); update_sd_config(sd->sdno, 0); save_config(); reply->error = 0; } return; case plex_object: /* you can't attach a plex to anything */ if (find_plex(msg->newname, 0) >= 0) { /* we have that name already, */ reply->error = EEXIST; reply->msg[0] = '\0'; return; } plex = validplex(msg->index, reply); if (plex) { bcopy(msg->newname, plex->name, MAXPLEXNAME); update_plex_config(plex->plexno, 0); save_config(); reply->error = 0; } return; case volume_object: /* you can't attach a volume to anything */ if (find_volume(msg->newname, 0) >= 0) { /* we have that name already, */ reply->error = EEXIST; reply->msg[0] = '\0'; return; } vol = validvol(msg->index, reply); if (vol) { bcopy(msg->newname, vol->name, MAXVOLNAME); update_volume_config(msg->index, 0); save_config(); reply->error = 0; } return; case invalid_object: reply->error = EINVAL; reply->msg[0] = '\0'; } } /* * Replace one object with another. * Currently only for drives. * message->index is the drive number of the old drive * message->otherobject is the drive number of the new drive */ void replaceobject(struct vinum_ioctl_msg *msg) { struct _ioctl_reply *reply = (struct _ioctl_reply *) msg; reply->error = ENODEV; /* until I know how to do this */ strcpy(reply->msg, "replace not implemented yet"); /* save_config (); */ } void moveobject(struct vinum_ioctl_msg *msg) { struct _ioctl_reply *reply = (struct _ioctl_reply *) msg; struct drive *drive; struct sd *sd; /* Check that our objects are valid (i.e. they exist) */ drive = validdrive(msg->index, (struct _ioctl_reply *) msg); if (drive == NULL) return; sd = validsd(msg->otherobject, (struct _ioctl_reply *) msg); if (sd == NULL) return; if (sd->driveno == msg->index) /* sd already belongs to drive */ return; if (sd->state > sd_stale) set_sd_state(sd->sdno, sd_stale, setstate_force); /* make the subdisk stale */ else sd->state = sd_empty; if (sd->plexno >= 0) /* part of a plex, */ update_plex_state(sd->plexno); /* update its state */ /* Return the space on the old drive */ if ((sd->driveno >= 0) /* we have a drive, */ &&(sd->sectors > 0)) /* and some space on it */ return_drive_space(sd->driveno, /* return the space */ sd->driveoffset, sd->sectors); /* Reassign the old subdisk */ sd->driveno = msg->index; sd->driveoffset = -1; /* let the drive decide where to put us */ give_sd_to_drive(sd->sdno); reply->error = 0; }