#define STATIC static
#include "vinumhdr.h"
#include <sys/sysmsg.h>
#include <sys/poll.h>
#include <sys/event.h>
#include <sys/udev.h>
#ifdef VINUMDEBUG
#include <sys/reboot.h>
int debug = 0;
extern int total_malloced;
extern int malloccount;
extern struct mc malloced[];
#endif
#include "request.h"
struct dev_ops vinum_ops =
{
{ "vinum", 0, D_DISK },
.d_open = vinumopen,
.d_close = vinumclose,
.d_read = physread,
.d_write = physwrite,
.d_ioctl = vinumioctl,
.d_kqfilter = vinumkqfilter,
.d_strategy = vinumstrategy,
.d_dump = vinumdump,
.d_psize = vinumsize,
};
STATIC void vinumattach(void *);
STATIC int vinum_modevent(module_t mod, modeventtype_t type, void *unused);
STATIC void vinum_initconf(void);
struct _vinum_conf vinum_conf;
cdev_t vinum_super_dev;
cdev_t vinum_wsuper_dev;
cdev_t vinum_daemon_dev;
static void
vinumattach(void *dummy)
{
char *cp, *cp1, *cp2, **drives;
int i, rv;
struct volume *vol;
if ((vinum_conf.flags & VF_LOADED) != 0)
panic("vinum: already loaded");
log(LOG_INFO, "vinum: loaded\n");
vinum_conf.flags |= VF_LOADED;
daemonq = NULL;
dqend = NULL;
#if 0
dev_ops_add(&vinum_ops, 0, 0);
#endif
vinum_initconf();
vinum_super_dev = make_dev(&vinum_ops, VINUM_SUPERDEV,
UID_ROOT, GID_WHEEL, 0600,
VINUM_SUPERDEV_BASE);
vinum_wsuper_dev = make_dev(&vinum_ops, VINUM_WRONGSUPERDEV,
UID_ROOT, GID_WHEEL, 0600,
VINUM_WRONGSUPERDEV_BASE);
vinum_daemon_dev = make_dev(&vinum_ops, VINUM_DAEMON_DEV,
UID_ROOT, GID_WHEEL, 0600,
VINUM_DAEMON_DEV_BASE);
if ((cp = kgetenv("vinum.drives")) != NULL) {
for (cp1 = cp, i = 0, drives = NULL; *cp1 != '\0'; i++) {
cp2 = cp1;
while (*cp1 != '\0' && *cp1 != ',' && *cp1 != ' ')
cp1++;
if (*cp1 != '\0')
*cp1++ = '\0';
drives = krealloc(drives, (unsigned long)((i + 1) * sizeof(char *)),
M_TEMP, M_WAITOK);
drives[i] = cp2;
}
if (i == 0)
goto bailout;
rv = vinum_scandisk(drives, i);
if (rv)
log(LOG_NOTICE, "vinum_scandisk() returned %d", rv);
bailout:
kfree(drives, M_TEMP);
}
if ((cp = kgetenv("vinum.root")) != NULL) {
for (i = 0; i < vinum_conf.volumes_used; i++) {
vol = &vinum_conf.volume[i];
if ((vol->state == volume_up)
&& (strcmp (vol->name, cp) == 0)
) {
rootdev = make_dev(&vinum_ops, i, UID_ROOT, GID_OPERATOR,
0640, VINUM_BASE "vinumroot");
udev_dict_set_cstr(rootdev, "subsystem", "raid");
udev_dict_set_cstr(rootdev, "disk-type", "raid");
log(LOG_INFO, "vinum: using volume %s for root device\n", cp);
break;
}
}
}
}
int
vinum_inactive(int confopen)
{
int i;
int can_do = 1;
if (confopen && (vinum_conf.flags & VF_OPEN))
return 0;
lock_config();
for (i = 0; i < vinum_conf.volumes_allocated; i++) {
if ((VOL[i].state > volume_down)
&& (VOL[i].flags & VF_OPEN)) {
can_do = 0;
break;
}
}
unlock_config();
return can_do;
}
void
free_vinum(int cleardrive)
{
union daemoninfo di = { .nothing = 0 };
int i;
int drives_allocated = vinum_conf.drives_allocated;
if (DRIVE != NULL) {
if (cleardrive) {
for (i = 0; i < drives_allocated; i++)
remove_drive(i);
} else {
for (i = 0; i < drives_allocated; i++)
free_drive(&DRIVE[i]);
}
Free(DRIVE);
}
while ((vinum_conf.flags & (VF_STOPPING | VF_DAEMONOPEN))
== (VF_STOPPING | VF_DAEMONOPEN)) {
queue_daemon_request(daemonrq_return, di);
tsleep(&vinumclose, 0, "vstop", 1);
}
if (SD != NULL) {
for (i = 0; i < vinum_conf.subdisks_allocated; i++) {
struct sd *sd = &vinum_conf.sd[i];
if (sd->sd_dev) {
destroy_dev(sd->sd_dev);
sd->sd_dev = NULL;
}
}
Free(SD);
}
if (PLEX != NULL) {
for (i = 0; i < vinum_conf.plexes_allocated; i++) {
struct plex *plex = &vinum_conf.plex[i];
if (plex->plex_dev) {
destroy_dev(plex->plex_dev);
plex->plex_dev = NULL;
}
if (plex->state != plex_unallocated) {
if (plex->sdnos)
Free(plex->sdnos);
}
}
Free(PLEX);
}
if (VOL != NULL) {
for (i = 0; i < vinum_conf.volumes_allocated; i++) {
struct volume *vol = &vinum_conf.volume[i];
if (vol->vol_dev) {
destroy_dev(vol->vol_dev);
vol->vol_dev = NULL;
}
}
Free(VOL);
}
bzero(&vinum_conf, sizeof(vinum_conf));
vinum_initconf();
}
STATIC void
vinum_initconf(void)
{
vinum_conf.physbufs = nswbuf_kva / 2 + 1;
DRIVE = (struct drive *) Malloc(sizeof(struct drive) * INITIAL_DRIVES);
CHECKALLOC(DRIVE, "vinum: no memory\n");
bzero(DRIVE, sizeof(struct drive) * INITIAL_DRIVES);
vinum_conf.drives_allocated = INITIAL_DRIVES;
vinum_conf.drives_used = 0;
VOL = (struct volume *) Malloc(sizeof(struct volume) * INITIAL_VOLUMES);
CHECKALLOC(VOL, "vinum: no memory\n");
bzero(VOL, sizeof(struct volume) * INITIAL_VOLUMES);
vinum_conf.volumes_allocated = INITIAL_VOLUMES;
vinum_conf.volumes_used = 0;
PLEX = (struct plex *) Malloc(sizeof(struct plex) * INITIAL_PLEXES);
CHECKALLOC(PLEX, "vinum: no memory\n");
bzero(PLEX, sizeof(struct plex) * INITIAL_PLEXES);
vinum_conf.plexes_allocated = INITIAL_PLEXES;
vinum_conf.plexes_used = 0;
SD = (struct sd *) Malloc(sizeof(struct sd) * INITIAL_SUBDISKS);
CHECKALLOC(SD, "vinum: no memory\n");
bzero(SD, sizeof(struct sd) * INITIAL_SUBDISKS);
vinum_conf.subdisks_allocated = INITIAL_SUBDISKS;
vinum_conf.subdisks_used = 0;
}
STATIC int
vinum_modevent(module_t mod, modeventtype_t type, void *unused)
{
switch (type) {
case MOD_LOAD:
vinumattach(NULL);
return 0;
case MOD_UNLOAD:
if (!vinum_inactive(1))
return EBUSY;
vinum_conf.flags |= VF_STOPPING;
sys_sync(NULL, NULL);
free_vinum(0);
if (vinum_super_dev) {
destroy_dev(vinum_super_dev);
vinum_super_dev = NULL;
}
if (vinum_wsuper_dev) {
destroy_dev(vinum_wsuper_dev);
vinum_wsuper_dev = NULL;
}
if (vinum_daemon_dev) {
destroy_dev(vinum_daemon_dev);
vinum_daemon_dev = NULL;
}
sync_devs();
#ifdef VINUMDEBUG
if (total_malloced) {
int i;
#ifdef INVARIANTS
int *poke;
#endif
for (i = 0; i < malloccount; i++) {
if (debug & DEBUG_WARNINGS)
log(LOG_WARNING,
"vinum: exiting with %d bytes malloced from %s:%d\n",
malloced[i].size,
malloced[i].file,
malloced[i].line);
#ifdef INVARIANTS
poke = &((int *) malloced[i].address)
[malloced[i].size / (2 * sizeof(int))];
if (*poke == 0xdeadc0de)
log(LOG_ERR,
"vinum: exiting with malloc table inconsistency at %p from %s:%d\n",
malloced[i].address,
malloced[i].file,
malloced[i].line);
#endif
Free(malloced[i].address);
}
}
#endif
dev_ops_remove_all(&vinum_ops);
log(LOG_INFO, "vinum: unloaded\n");
return 0;
default:
break;
}
return 0;
}
moduledata_t vinum_mod =
{
"vinum",
(modeventhand_t) vinum_modevent,
0
};
DECLARE_MODULE(vinum, vinum_mod, SI_SUB_RAID, SI_ORDER_MIDDLE);
MODULE_VERSION(vinum, 1);
int
vinumopen(struct dev_open_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
int error;
unsigned int index;
struct volume *vol;
struct plex *plex;
struct sd *sd;
int devminor;
devminor = minor(dev);
error = 0;
switch (DEVTYPE(dev)) {
case VINUM_VOLUME_TYPE:
index = Volno(dev);
if (index >= vinum_conf.volumes_allocated)
return ENXIO;
vol = &VOL[index];
switch (vol->state) {
case volume_unallocated:
case volume_uninit:
return ENXIO;
case volume_up:
vol->flags |= VF_OPEN;
return 0;
case volume_down:
return EIO;
default:
return EINVAL;
}
case VINUM_PLEX_TYPE:
if (Volno(dev) >= vinum_conf.volumes_allocated)
return ENXIO;
case VINUM_RAWPLEX_TYPE:
index = Plexno(dev);
if (index >= vinum_conf.plexes_allocated)
return ENXIO;
plex = &PLEX[index];
switch (plex->state) {
case plex_referenced:
case plex_unallocated:
return EINVAL;
default:
plex->flags |= VF_OPEN;
return 0;
}
case VINUM_SD_TYPE:
if ((Volno(dev) >= vinum_conf.volumes_allocated)
||(Plexno(dev) >= vinum_conf.plexes_allocated))
return ENXIO;
case VINUM_RAWSD_TYPE:
index = Sdno(dev);
if ((index >= vinum_conf.subdisks_allocated)
||(SD[index].state < sd_init))
return ENXIO;
sd = &SD[index];
switch (sd->state) {
case sd_unallocated:
case sd_uninit:
return EINVAL;
default:
sd->flags |= VF_OPEN;
return 0;
}
case VINUM_SUPERDEV_TYPE:
error = caps_priv_check(ap->a_cred, SYSCAP_RESTRICTEDROOT);
if (error == 0) {
if (devminor == VINUM_DAEMON_DEV)
vinum_conf.flags |= VF_DAEMONOPEN;
else if (devminor == VINUM_SUPERDEV)
vinum_conf.flags |= VF_OPEN;
else
error = ENODEV;
}
return error;
case VINUM_DRIVE_TYPE:
default:
return ENODEV;
}
}
int
vinumclose(struct dev_close_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
unsigned int index;
struct volume *vol;
int devminor;
devminor = minor(dev);
index = Volno(dev);
switch (DEVTYPE(dev)) {
case VINUM_VOLUME_TYPE:
if (index >= vinum_conf.volumes_allocated)
return ENXIO;
vol = &VOL[index];
switch (vol->state) {
case volume_unallocated:
case volume_uninit:
return ENXIO;
case volume_up:
vol->flags &= ~VF_OPEN;
return 0;
case volume_down:
return EIO;
default:
return EINVAL;
}
case VINUM_PLEX_TYPE:
if (Volno(dev) >= vinum_conf.volumes_allocated)
return ENXIO;
case VINUM_RAWPLEX_TYPE:
index = Plexno(dev);
if (index >= vinum_conf.plexes_allocated)
return ENXIO;
PLEX[index].flags &= ~VF_OPEN;
return 0;
case VINUM_SD_TYPE:
if ((Volno(dev) >= vinum_conf.volumes_allocated) ||
(Plexno(dev) >= vinum_conf.plexes_allocated))
return ENXIO;
case VINUM_RAWSD_TYPE:
index = Sdno(dev);
if (index >= vinum_conf.subdisks_allocated)
return ENXIO;
SD[index].flags &= ~VF_OPEN;
return 0;
case VINUM_SUPERDEV_TYPE:
if (devminor == VINUM_SUPERDEV)
vinum_conf.flags &= ~VF_OPEN;
else if (devminor == VINUM_DAEMON_DEV) {
vinum_conf.flags &= ~VF_DAEMONOPEN;
if (vinum_conf.flags & VF_STOPPING)
wakeup(&vinumclose);
}
return 0;
case VINUM_DRIVE_TYPE:
default:
return ENODEV;
}
}
int
vinumsize(struct dev_psize_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
struct volume *vol;
vol = &VOL[Volno(dev)];
if (vol->state == volume_up) {
ap->a_result = (int64_t)vol->size;
return(0);
} else {
return(ENXIO);
}
}
int
vinumdump(struct dev_dump_args *ap)
{
return ENXIO;
}
void
vinumfilt_detach(struct knote *kn) {}
int
vinumfilt_rd(struct knote *kn, long hint)
{
cdev_t dev = (cdev_t)kn->kn_hook;
if (seltrue(dev, POLLIN | POLLRDNORM))
return (1);
return (0);
}
int
vinumfilt_wr(struct knote *kn, long hint)
{
return (1);
}
struct filterops vinumfiltops_rd =
{ FILTEROP_ISFD, NULL, vinumfilt_detach, vinumfilt_rd };
struct filterops vinumfiltops_wr =
{ FILTEROP_ISFD, NULL, vinumfilt_detach, vinumfilt_wr };
int
vinumkqfilter(struct dev_kqfilter_args *ap)
{
if (ap->a_kn->kn_filter == EVFILT_READ) {
ap->a_kn->kn_fop = &vinumfiltops_rd;
ap->a_kn->kn_hook = (caddr_t)ap->a_head.a_dev;
ap->a_result = 0;
} else if (ap->a_kn->kn_filter == EVFILT_WRITE) {
ap->a_kn->kn_fop = &vinumfiltops_wr;
ap->a_result = 0;
} else {
ap->a_result = EOPNOTSUPP;
}
return (0);
}