/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "mhd_local.h" #include #include #include #include #include #include /* * manipulate drives */ /* * null list constant */ const mhd_drive_list_t mhd_null_list = MHD_NULL_LIST; /* * add drive to list */ void mhd_add_drive( mhd_drive_list_t *dlp, mhd_drive_t *dp ) { /* add drive to list */ if (dlp->dl_ndrive >= dlp->dl_alloc) { dlp->dl_alloc += 10; dlp->dl_drives = Realloc(dlp->dl_drives, (dlp->dl_alloc * sizeof (*dlp->dl_drives))); } dlp->dl_drives[dlp->dl_ndrive++] = dp; } /* * delete drive from list */ void mhd_del_drive( mhd_drive_list_t *dlp, mhd_drive_t *dp ) { uint_t i; /* delete drive from list */ for (i = 0; (i < dlp->dl_ndrive); ++i) { if (dlp->dl_drives[i] == dp) break; } assert(dlp->dl_drives[i] == dp); for (/* void */; (i < dlp->dl_ndrive); ++i) dlp->dl_drives[i] = dlp->dl_drives[i + 1]; dlp->dl_ndrive--; } /* * free drive list */ void mhd_free_list( mhd_drive_list_t *dlp ) { if (dlp->dl_drives != NULL) Free(dlp->dl_drives); (void) memset(dlp, 0, sizeof (*dlp)); } /* * manipulate drive state */ int mhd_state( mhd_drive_t *dp, mhd_state_t new_state, mhd_error_t *mhep ) { mhd_drive_set_t *sp = dp->dr_sp; mhd_state_t old_state = dp->dr_state; /* check lock */ assert(MUTEX_HELD(&sp->sr_mx)); /* set state and kick thread */ MHDPRINTF2(("%s: state 0x%x now 0x%x\n", dp->dr_rname, dp->dr_state, new_state)); dp->dr_state = new_state; mhd_cv_broadcast(&dp->dr_cv); /* if this is the last PROBING drive, disable any failfast */ if ((old_state & DRIVE_PROBING) && (! (new_state & DRIVE_PROBING))) { mhd_drive_list_t *dlp = &sp->sr_drives; uint_t cnt, i; for (cnt = 0, i = 0; (i < dlp->dl_ndrive); ++i) { if (dlp->dl_drives[i]->dr_state & DRIVE_PROBING) ++cnt; } if (cnt == 0) { mhd_error_t status = mhd_null_error; if (mhep == NULL) mhep = &status; if (mhd_ff_disarm(sp, mhep) != 0) { if (mhep == &status) { mhde_perror(mhep, dp->dr_rname); mhd_clrerror(mhep); } return (-1); } } } /* return success */ return (0); } int mhd_state_set( mhd_drive_t *dp, mhd_state_t new_state, mhd_error_t *mhep ) { return (mhd_state(dp, (dp->dr_state | new_state), mhep)); } static int mhd_state_clr( mhd_drive_t *dp, mhd_state_t new_state, mhd_error_t *mhep ) { return (mhd_state(dp, (dp->dr_state & ~new_state), mhep)); } /* * idle a drive */ int mhd_idle( mhd_drive_t *dp, mhd_error_t *mhep ) { mhd_drive_set_t *sp = dp->dr_sp; /* check lock */ assert(MUTEX_HELD(&sp->sr_mx)); /* wait for thread to idle */ for (;;) { if (DRIVE_IS_IDLE(dp)) return (0); if (mhd_state(dp, DRIVE_IDLING, mhep) != 0) return (-1); (void) mhd_cv_wait(&sp->sr_cv, &sp->sr_mx); } } /* * reserve the drive */ static int mhd_reserve( mhd_drive_t *dp ) { mhd_drive_set_t *sp = dp->dr_sp; int serial = (sp->sr_options & MHD_SERIAL); mhd_mhioctkown_t *tkp = &sp->sr_timeouts.mh_tk; struct mhioctkown tkown; int err; /* check locks */ assert(MUTEX_HELD(&sp->sr_mx)); assert(dp->dr_fd >= 0); assert(dp->dr_state == DRIVE_RESERVING); /* setup timeouts */ (void) memset(&tkown, 0, sizeof (tkown)); tkown.reinstate_resv_delay = tkp->reinstate_resv_delay; tkown.min_ownership_delay = tkp->min_ownership_delay; tkown.max_ownership_delay = tkp->max_ownership_delay; /* reserve drive */ if (! serial) mhd_mx_unlock(&sp->sr_mx); err = ioctl(dp->dr_fd, MHIOCTKOWN, &tkown); if (! serial) mhd_mx_lock(&sp->sr_mx); if (err != 0) { mhd_perror("%s: MHIOCTKOWN", dp->dr_rname); (void) mhd_state(dp, DRIVE_ERRORED, NULL); dp->dr_errnum = errno; return (-1); } /* return success */ MHDPRINTF(("%s: MHIOCTKOWN: succeeded\n", dp->dr_rname)); (void) mhd_state(dp, DRIVE_IDLE, NULL); return (0); } /* * failfast the drive */ static int mhd_failfast( mhd_drive_t *dp ) { mhd_drive_set_t *sp = dp->dr_sp; int serial = (sp->sr_options & MHD_SERIAL); int ff = sp->sr_timeouts.mh_ff; char *release = ((ff == 0) ? " (release)" : ""); int err; /* check locks */ assert(MUTEX_HELD(&sp->sr_mx)); assert(dp->dr_fd >= 0); assert(dp->dr_state == DRIVE_FAILFASTING); /* failfast drive */ if (! serial) mhd_mx_unlock(&sp->sr_mx); err = ioctl(dp->dr_fd, MHIOCENFAILFAST, &ff); if (! serial) mhd_mx_lock(&sp->sr_mx); if (err != 0) { mhd_perror("%s: MHIOCENFAILFAST%s", dp->dr_rname, release); (void) mhd_state(dp, DRIVE_ERRORED, NULL); dp->dr_errnum = errno; return (-1); } /* return success */ MHDPRINTF(("%s: MHIOCENFAILFAST%s: succeeded\n", dp->dr_rname, release)); (void) mhd_state(dp, DRIVE_IDLE, NULL); return (0); } /* * release the drive */ static int mhd_release( mhd_drive_t *dp ) { mhd_drive_set_t *sp = dp->dr_sp; int serial = (sp->sr_options & MHD_SERIAL); int ff = 0; /* disable failfast */ int err; /* check locks */ assert(MUTEX_HELD(&sp->sr_mx)); assert(dp->dr_fd >= 0); assert(dp->dr_state == DRIVE_RELEASING); /* disable failfast */ if (! serial) mhd_mx_unlock(&sp->sr_mx); err = ioctl(dp->dr_fd, MHIOCENFAILFAST, &ff); if (! serial) mhd_mx_lock(&sp->sr_mx); if (err != 0) { mhd_perror("%s: MHIOCENFAILFAST (release)", dp->dr_rname); (void) mhd_state(dp, DRIVE_ERRORED, NULL); dp->dr_errnum = errno; return (-1); } MHDPRINTF(("%s: MHIOCENFAILFAST (release): succeeded\n", dp->dr_rname)); /* release drive */ if (! serial) mhd_mx_unlock(&sp->sr_mx); err = ioctl(dp->dr_fd, MHIOCRELEASE, NULL); if (! serial) mhd_mx_lock(&sp->sr_mx); if (err != 0) { mhd_perror("%s: MHIOCRELEASE", dp->dr_rname); (void) mhd_state(dp, DRIVE_ERRORED, NULL); dp->dr_errnum = errno; return (-1); } /* return success */ MHDPRINTF(("%s: MHIOCRELEASE: succeeded\n", dp->dr_rname)); (void) mhd_state(dp, DRIVE_IDLE, NULL); return (0); } /* * probe the drive */ static int mhd_probe( mhd_drive_t *dp ) { mhd_drive_set_t *sp = dp->dr_sp; int serial = (sp->sr_options & MHD_SERIAL); int err; mhd_msec_t now; /* check locks */ assert(MUTEX_HELD(&sp->sr_mx)); assert(dp->dr_fd >= 0); assert(dp->dr_state & (DRIVE_PROBING | DRIVE_STATUSING)); /* get status (we may get dumped from PROBING here) */ if (! serial) mhd_mx_unlock(&sp->sr_mx); err = ioctl(dp->dr_fd, MHIOCSTATUS, NULL); now = mhd_time(); if (! serial) mhd_mx_lock(&sp->sr_mx); if (! (dp->dr_state & (DRIVE_PROBING | DRIVE_STATUSING))) return (0); /* update status */ if (dp->dr_state & DRIVE_STATUSING) { if (err == 1) { MHDPRINTF(("%s: MHIOCSTATUS: reserved\n", dp->dr_rname)); dp->dr_errnum = MHD_E_RESERVED; } else if (err != 0) { mhd_perror("%s: MHIOCSTATUS", dp->dr_rname); dp->dr_errnum = errno; } else { MHDPRINTF(("%s: MHIOCSTATUS: available\n", dp->dr_rname)); dp->dr_errnum = 0; } (void) mhd_state_clr(dp, DRIVE_STATUSING, NULL); } /* update time or die */ if (dp->dr_state & DRIVE_PROBING) { /* check our drive */ if (err == 0) { dp->dr_time = now; } else if (err == 1) { mhd_eprintf("%s: %s: reservation conflict\n", sp->sr_name, dp->dr_rname); mhd_ff_die(sp); } /* check other drives */ mhd_ff_check(sp); } /* return success */ return (0); } /* * cached controller map */ typedef struct { char *regexpr1; uint_t tray; uint_t bus; char *regexpr2; char *scan; } mhd_ctlrmap_t; static rwlock_t ctlr_rw = DEFAULTRWLOCK; static time_t ctlr_mtime = 0; static size_t ctlr_num = 0; static mhd_ctlrmap_t *ctlr_map = NULL; /* * free up controller map */ static void free_map() { size_t i; assert(RW_WRITE_HELD(&ctlr_rw)); for (i = 0; (i < ctlr_num); ++i) { mhd_ctlrmap_t *cmp = &ctlr_map[i]; if (cmp->regexpr1 != NULL) Free(cmp->regexpr1); if (cmp->regexpr2 != NULL) Free(cmp->regexpr2); if (cmp->scan != NULL) Free(cmp->scan); } if (ctlr_map != NULL) Free(ctlr_map); ctlr_num = 0; ctlr_map = NULL; } /* * unlock controller map */ static void unlock_map() { assert(RW_WRITE_HELD(&ctlr_rw) | RW_READ_HELD(&ctlr_rw)); mhd_rw_unlock(&ctlr_rw); } /* * update controller map and lock it */ static int update_map() { struct stat statbuf; FILE *fp; char line[256], expr1[256], expr2[256], scan[256]; unsigned tray, bus; int rval = -1; /* see if map file has changed */ mhd_rw_rdlock(&ctlr_rw); if (stat(METACTLRMAP, &statbuf) != 0) { mhd_perror(METACTLRMAP); goto out; } if (statbuf.st_mtime == ctlr_mtime) { rval = 0; goto out; } /* trade up to writer lock, check again */ mhd_rw_unlock(&ctlr_rw); mhd_rw_wrlock(&ctlr_rw); if (statbuf.st_mtime == ctlr_mtime) { rval = 0; goto out; } if (ctlr_mtime != 0) mhd_eprintf("updating controller map\n"); ctlr_mtime = statbuf.st_mtime; /* toss existing cache */ free_map(); /* parse md.ctlrmap */ if ((fp = fopen(METACTLRMAP, "r")) == NULL) { mhd_perror(METACTLRMAP); goto out; } clearerr(fp); while (fgets(line, sizeof (line), fp) != NULL) { char *regexpr1 = NULL; char *regexpr2 = NULL; mhd_ctlrmap_t *cmp; /* skip blank lines and comments */ if ((line[0] == '\0') || (line[0] == '\n') || (line[0] == '#')) continue; /* parse line */ if (((sscanf(line, "\"%[^\"]\" %u %u \"%[^\"]\" \"%[^\"]\"", expr1, &tray, &bus, expr2, scan)) != 5) || ((regexpr1 = regcmp(expr1, 0)) == NULL) || ((regexpr2 = regcmp(expr2, 0)) == NULL)) { mhd_eprintf("%s: bad regex(es) '%s'\n", METACTLRMAP, line); if (regexpr1 != NULL) Free(regexpr1); if (regexpr2 != NULL) Free(regexpr2); continue; } /* add to cache */ ctlr_map = Realloc(ctlr_map, ((ctlr_num + 1) * sizeof (*ctlr_map))); cmp = &ctlr_map[ctlr_num++]; cmp->regexpr1 = regexpr1; cmp->tray = tray; cmp->bus = bus; cmp->regexpr2 = regexpr2; cmp->scan = Strdup(scan); } if (ferror(fp)) { mhd_perror(METACTLRMAP); (void) fclose(fp); goto out; } if (fclose(fp) != 0) { mhd_perror(METACTLRMAP); goto out; } /* success */ rval = 0; /* return success */ out: if (rval != 0) { mhd_rw_unlock(&ctlr_rw); return (-1); } return (0); } static char * get_pln_ctlr_name( char *path ) { char *devicesname, *p; char retval[MAXPATHLEN]; devicesname = Strdup(path); if ((p = strrchr(devicesname, '/')) == NULL) { Free(devicesname); return (NULL); } /* strip off the "ssd@..." portion of the devices name */ *p = '\0'; /* strip off the "../../" in front of "devices" */ if ((p = strstr(devicesname, "/devices/")) == NULL) { Free(devicesname); return (NULL); } (void) snprintf(retval, sizeof (retval), "%s:ctlr", p); Free(devicesname); return (Strdup(retval)); } struct pln_cache { char *pln_name; enum mhd_ctlrtype_t ctype; struct pln_cache *next; }; static struct pln_cache *pln_cache_anchor = NULL; static mutex_t mhd_pln_mx = DEFAULTMUTEX; /* singled threaded by caller */ static void add_pln_cache( char *pln_name, enum mhd_ctlrtype_t ctype ) { struct pln_cache *p; p = Malloc(sizeof (*p)); p->pln_name = pln_name; p->ctype = ctype; p->next = pln_cache_anchor; pln_cache_anchor = p; } /* singled threaded by caller */ static int find_pln_cache( char *pln_name, enum mhd_ctlrtype_t *ctype_ret ) { struct pln_cache *p; for (p = pln_cache_anchor; p != NULL; p = p->next) { if (strcmp(pln_name, p->pln_name) == 0) { *ctype_ret = p->ctype; return (1); } } return (0); } static void free_pln_cache(void) { struct pln_cache *p, *n = NULL; (void) mutex_lock(&mhd_pln_mx); for (p = pln_cache_anchor; p != NULL; p = n) { n = p->next; Free(p->pln_name); Free(p); } pln_cache_anchor = NULL; (void) mutex_unlock(&mhd_pln_mx); } /* * match on SSA Model 200. */ static void match_SSA200( mhd_drive_t *dp, char *path ) { mhd_cinfo_t *cinfop = &dp->dr_drive_id.did_cinfo; struct uscsi_cmd ucmd; union scsi_cdb cdb; struct scsi_inquiry inq; int fd; char *pln_ctlr_name; enum mhd_ctlrtype_t ctype; char *p; if ((pln_ctlr_name = get_pln_ctlr_name(path)) == NULL) return; (void) mutex_lock(&mhd_pln_mx); if (find_pln_cache(pln_ctlr_name, &ctype) == 1) { (void) mutex_unlock(&mhd_pln_mx); if (ctype != MHD_CTLR_SSA200) return; /* over-ride for SSA200 */ cinfop->mhc_ctype = ctype; cinfop->mhc_tray = cinfop->mhc_bus; return; } if ((fd = open(pln_ctlr_name, (O_RDONLY|O_NDELAY), 0)) < 0) { (void) mutex_unlock(&mhd_pln_mx); Free(pln_ctlr_name); return; } (void) memset(&ucmd, 0, sizeof (ucmd)); (void) memset(&cdb, 0, sizeof (cdb)); (void) memset(&inq, 0, sizeof (inq)); cdb.scc_cmd = SCMD_INQUIRY; cdb.g0_count0 = sizeof (inq); ucmd.uscsi_cdb = (caddr_t)&cdb; ucmd.uscsi_cdblen = CDB_GROUP0; ucmd.uscsi_bufaddr = (caddr_t)&inq; ucmd.uscsi_buflen = sizeof (inq); ucmd.uscsi_flags = USCSI_READ | USCSI_ISOLATE | USCSI_DIAGNOSE; ucmd.uscsi_timeout = 30; if (ioctl(fd, USCSICMD, &ucmd)) { (void) mutex_unlock(&mhd_pln_mx); (void) close(fd); MHDPRINTF(("%s: USCSICMD(SCMD_INQUIRY): failed errno %d\n", pln_ctlr_name, errno)); Free(pln_ctlr_name); return; } (void) close(fd); MHDPRINTF(("%s: USCSICMD(SCMD_INQUIRY): success\n", pln_ctlr_name)); /* Make all trailing spaces be null char */ for (p = inq.inq_pid + sizeof (inq.inq_pid) - 1; p != inq.inq_pid; p--) { if (*p == '\0') continue; if (!isspace(*p)) break; *p = '\0'; } if (strncmp(inq.inq_pid, META_SSA200_PID, sizeof (inq.inq_pid)) != 0) goto out; /* over-ride the ctype, and tray */ cinfop->mhc_ctype = MHD_CTLR_SSA200; cinfop->mhc_tray = cinfop->mhc_bus; out: add_pln_cache(pln_ctlr_name, cinfop->mhc_ctype); (void) mutex_unlock(&mhd_pln_mx); } /* * get controller info */ static void match_SSA100( mhd_drive_t *dp, char *path ) { mhd_cinfo_t *cinfop = &dp->dr_drive_id.did_cinfo; uint_t i; char *p; lloff_t wwn; const char *fmt; /* update and lock controller map */ if (update_map() != 0) return; /* give up */ assert(RW_WRITE_HELD(&ctlr_rw) || RW_READ_HELD(&ctlr_rw)); /* look for match in cache */ for (i = 0; (i < ctlr_num); ++i) { mhd_ctlrmap_t *cmp = &ctlr_map[i]; fmt = cmp->scan; if ((regex(cmp->regexpr1, path) != NULL) && ((p = regex(cmp->regexpr2, path)) != NULL) && (sscanf(p, fmt, (ulong_t *)&wwn._p._u, (ulong_t *)&wwn._p._l) == 2)) { cinfop->mhc_ctype = MHD_CTLR_SSA100; cinfop->mhc_tray = cmp->tray; cinfop->mhc_bus = cmp->bus; cinfop->mhc_wwn = wwn._f; match_SSA200(dp, path); break; } } /* unlock controller map */ unlock_map(); } /* * get unique drive ID */ static int mhd_ident( mhd_drive_t *dp ) { mhd_drive_set_t *sp = dp->dr_sp; int serial = (sp->sr_options & MHD_SERIAL); struct uscsi_cmd ucmd; union scsi_cdb cdb; struct scsi_inquiry inq; struct extvtoc vtoc_buf; char path[MAXPATHLEN + 1]; int len; int err; /* check locks */ assert(MUTEX_HELD(&sp->sr_mx)); assert(dp->dr_fd >= 0); assert(dp->dr_state & DRIVE_IDENTING); /* reset ID */ (void) memset(&dp->dr_drive_id, 0, sizeof (dp->dr_drive_id)); /* get serial number */ if (dp->dr_state & DRIVE_SERIALING) { if (! serial) mhd_mx_unlock(&sp->sr_mx); (void) memset(&ucmd, 0, sizeof (ucmd)); (void) memset(&cdb, 0, sizeof (cdb)); (void) memset(&inq, 0, sizeof (inq)); cdb.scc_cmd = SCMD_INQUIRY; cdb.g0_count0 = sizeof (inq); ucmd.uscsi_cdb = (caddr_t)&cdb; ucmd.uscsi_cdblen = CDB_GROUP0; ucmd.uscsi_bufaddr = (caddr_t)&inq; ucmd.uscsi_buflen = sizeof (inq); ucmd.uscsi_flags = USCSI_READ | USCSI_ISOLATE | USCSI_DIAGNOSE; ucmd.uscsi_timeout = 30; err = ioctl(dp->dr_fd, USCSICMD, &ucmd); if (! serial) mhd_mx_lock(&sp->sr_mx); if (err != 0) { MHDPRINTF(( "%s: USCSICMD(SCMD_INQUIRY): failed errno %d\n", dp->dr_rname, errno)); dp->dr_drive_id.did_flags &= ~MHD_DID_SERIAL; } else { char *p, *e; uint_t i; MHDPRINTF(("%s: USCSICMD(SCMD_INQUIRY): success\n", dp->dr_rname)); dp->dr_drive_id.did_flags |= MHD_DID_SERIAL; p = dp->dr_drive_id.did_serial; e = p + sizeof (dp->dr_drive_id.did_serial); for (i = 0; ((i < sizeof (inq.inq_vid)) && (p < e)); ++i) *p++ = inq.inq_vid[i]; for (i = 0; ((i < sizeof (inq.inq_pid)) && (p < e)); ++i) *p++ = inq.inq_pid[i]; for (i = 0; ((i < sizeof (inq.inq_revision)) && (p < e)); ++i) *p++ = inq.inq_revision[i]; for (i = 0; ((i < sizeof (inq.inq_serial)) && (p < e)); ++i) *p++ = inq.inq_serial[i]; assert(p == e); for (p = dp->dr_drive_id.did_serial; (p < e); ++p) { if (*p == '\0') *p = ' '; } } } else { dp->dr_drive_id.did_flags &= ~MHD_DID_SERIAL; } /* get VTOC */ if (dp->dr_state & DRIVE_VTOCING) { if (! serial) mhd_mx_unlock(&sp->sr_mx); (void) memset(&vtoc_buf, 0, sizeof (vtoc_buf)); err = read_extvtoc(dp->dr_fd, &vtoc_buf); if (! serial) mhd_mx_lock(&sp->sr_mx); if (err < 0) { MHDPRINTF(("%s: read_extvtoc: failed errno %d\n", dp->dr_rname, errno)); dp->dr_drive_id.did_flags &= ~MHD_DID_TIME; } else { MHDPRINTF(("%s: read_extvtoc: success\n", dp->dr_rname)); dp->dr_drive_id.did_flags |= MHD_DID_TIME; dp->dr_drive_id.did_time = vtoc_buf.timestamp[0]; } } else { dp->dr_drive_id.did_flags &= ~MHD_DID_TIME; } /* get controller info */ if (dp->dr_state & DRIVE_CINFOING) { if (! serial) mhd_mx_unlock(&sp->sr_mx); len = readlink(dp->dr_rname0, path, (sizeof (path) - 1)); if (! serial) mhd_mx_lock(&sp->sr_mx); if (len >= sizeof (path)) { len = -1; errno = ENAMETOOLONG; } if (len < 0) { MHDPRINTF(("%s: readlink: failed errno %d\n", dp->dr_rname0, errno)); dp->dr_drive_id.did_flags &= ~MHD_DID_CINFO; } else { MHDPRINTF(("%s: readlink: success\n", dp->dr_rname0)); dp->dr_drive_id.did_flags |= MHD_DID_CINFO; (void) memset(&dp->dr_drive_id.did_cinfo, 0, sizeof (dp->dr_drive_id.did_cinfo)); match_SSA100(dp, path); } } else { dp->dr_drive_id.did_flags &= ~MHD_DID_CINFO; } /* return success */ (void) mhd_state_clr(dp, DRIVE_IDENTING, NULL); return (0); } /* * disk thread */ static void mhd_drive_thread( mhd_drive_t *dp ) { mhd_drive_set_t *sp = dp->dr_sp; /* wait for dp->dr_thread to be filled in */ assert(sp != NULL); mhd_mx_lock(&sp->sr_mx); /* forever */ for (;;) { /* check locks */ assert(MUTEX_HELD(&sp->sr_mx)); assert(dp->dr_thread == thr_self()); /* check for changed set */ if (sp != dp->dr_sp) { MHDPRINTF2(("%s: changed from set '%s' to '%s'\n", dp->dr_rname, sp->sr_name, dp->dr_sp->sr_name)); mhd_mx_unlock(&sp->sr_mx); sp = dp->dr_sp; mhd_mx_lock(&sp->sr_mx); } /* open drive, if necessary */ if ((dp->dr_fd < 0) && (! (DRIVE_IS_IDLE(dp) || (dp->dr_state == DRIVE_IDLING)))) { int serial = (sp->sr_options & MHD_SERIAL); if (! serial) mhd_mx_unlock(&sp->sr_mx); dp->dr_fd = open(dp->dr_rname0, (O_RDWR|O_NDELAY), 0); if (! serial) mhd_mx_lock(&sp->sr_mx); if (dp->dr_fd < 0) { mhd_perror("%s: open", dp->dr_rname); (void) mhd_state(dp, DRIVE_ERRORED, NULL); dp->dr_errnum = errno; } continue; } /* dispatch */ switch (dp->dr_state) { case DRIVE_IDLE: MHDPRINTF1(("%s: IDLE\n", dp->dr_rname)); break; case DRIVE_ERRORED: MHDPRINTF1(("%s: ERRORED %d\n", dp->dr_rname, dp->dr_errnum)); break; case DRIVE_IDLING: (void) mhd_state(dp, DRIVE_IDLE, NULL); continue; case DRIVE_RESERVING: MHDPRINTF1(("%s: RESERVING\n", dp->dr_rname)); (void) mhd_reserve(dp); assert(DRIVE_IS_IDLE(dp)); continue; case DRIVE_FAILFASTING: MHDPRINTF1(("%s: FAILFASTING\n", dp->dr_rname)); (void) mhd_failfast(dp); assert(DRIVE_IS_IDLE(dp)); continue; case DRIVE_RELEASING: MHDPRINTF1(("%s: RELEASING\n", dp->dr_rname)); (void) mhd_release(dp); assert(DRIVE_IS_IDLE(dp)); continue; /* non-exclusive states */ default: assert(! (dp->dr_state & (DRIVE_EXCLUSIVE_STATES & ~DRIVE_ERRORED))); if (dp->dr_state & (DRIVE_PROBING | DRIVE_STATUSING)) { MHDPRINTF1(("%s: PROBING\n", dp->dr_rname)); (void) mhd_probe(dp); assert(! (dp->dr_state & DRIVE_STATUSING)); } if (dp->dr_state & DRIVE_IDENTING) { MHDPRINTF1(("%s: IDENTING\n", dp->dr_rname)); (void) mhd_ident(dp); assert(! (dp->dr_state & DRIVE_IDENTING)); continue; /* in case we're probing */ } break; } /* close drive, if possible */ if ((dp->dr_fd >= 0) && (DRIVE_IS_IDLE(dp))) { int serial = (sp->sr_options & MHD_SERIAL); if (! serial) mhd_mx_unlock(&sp->sr_mx); (void) close(dp->dr_fd); /* sd/ssd bug */ if (! serial) mhd_mx_lock(&sp->sr_mx); dp->dr_fd = -1; } /* wake up anybody waiting */ mhd_cv_broadcast(&sp->sr_cv); /* see if anything happened */ if (! DRIVE_IS_IDLE(dp)) continue; /* wait for something to happen */ if (! (dp->dr_state & DRIVE_PROBING)) { mhd_cv_wait(&dp->dr_cv, &sp->sr_mx); } else { mhd_cv_timedwait(&dp->dr_cv, &sp->sr_mx, (sp->sr_timeouts.mh_ff / 2)); } } } /* * kick off drive thread */ static int mhd_thread_create( mhd_drive_t *dp, mhd_error_t *mhep ) { mhd_drive_set_t *sp = dp->dr_sp; thread_t thread = NULL; int rval = 0; /* check lock and thread */ assert(MUTEX_HELD(&sp->sr_mx)); assert(dp->dr_thread == NULL); /* create thread */ if (thr_create(NULL, 0, (void *(*)(void *))mhd_drive_thread, (void *)dp, (THR_DETACHED | THR_BOUND), &thread) != 0) { rval = mhd_error(mhep, errno, "thr_create"); } else { assert(thread != NULL); dp->dr_thread = thread; } /* return success */ return (rval); } /* * peel off s%u from name */ static char * diskname( const char *sname ) { char *dname; char *p, *e; /* duplicate name */ if ((dname = Strdup(sname)) == NULL) return (NULL); /* gobble number and 's' */ p = e = dname + strlen(dname) - 1; for (; (p > dname); --p) { if (!isdigit(*p)) break; } if ((p == e) || (p <= dname)) { Free(dname); return (NULL); } if (*p-- != 's') { Free(dname); return (NULL); } if ((p <= dname) || (!isdigit(*p))) { Free(dname); return (NULL); } *(++p) = '\0'; return (dname); } /* * create new drive */ mhd_drive_t * mhd_create_drive( mhd_drive_set_t *sp, /* new set */ char *rname, /* raw drive name */ int *fdp, /* open device or -1 */ mhd_error_t *mhep /* returned error */ ) { mhd_drive_t *dp = NULL; char *rname0 = NULL; /* check locks */ assert(MUTEX_HELD(&sp->sr_mx)); /* if drive already exists */ if ((dp = mhd_find_drive(rname)) != NULL) { mhd_drive_set_t *oldsp = dp->dr_sp; /* if set has changed, move drive */ if (oldsp != sp) { mhd_mx_unlock(&sp->sr_mx); mhd_mx_lock(&oldsp->sr_mx); if (mhd_idle(dp, mhep) != 0) { mhd_mx_unlock(&oldsp->sr_mx); mhd_mx_lock(&sp->sr_mx); return (NULL); } mhd_del_drive_from_set(dp); mhd_mx_unlock(&oldsp->sr_mx); mhd_mx_lock(&sp->sr_mx); mhd_add_drive_to_set(sp, dp); } /* return drive */ return (dp); } /* build slice0 */ rname0 = Malloc(strlen(rname) + strlen("s0") + 1); (void) strcpy(rname0, rname); (void) strcat(rname0, "s0"); /* allocate and initialize drive */ dp = Zalloc(sizeof (*dp)); dp->dr_sp = sp; dp->dr_rname = Strdup(rname); dp->dr_rname0 = rname0; mhd_cv_init(&dp->dr_cv); dp->dr_thread = NULL; dp->dr_fd = -1; dp->dr_state = DRIVE_IDLE; /* steal open drive */ if ((fdp != NULL) && (*fdp >= 0)) { dp->dr_fd = *fdp; *fdp = -1; } /* add to set */ mhd_add_drive_to_set(sp, dp); /* kick off drive thread */ if (mhd_thread_create(dp, mhep) != 0) { Free(dp->dr_rname0); Free(dp->dr_rname); Free(dp); return (NULL); } /* return drive */ return (dp); } /* * find or create drive in any set */ static mhd_drive_t * mhd_create_drive_anyset( char *rname, int *fdp, mhd_error_t *mhep ) { mhd_drive_set_t *null_sp = mhd_create_set(NULL, 0, NULL, NULL); mhd_drive_t *dp; /* check locks */ assert(null_sp != NULL); /* drive already exists */ if ((dp = mhd_find_drive(rname)) != NULL) return (dp); /* add to null set */ mhd_mx_lock(&null_sp->sr_mx); dp = mhd_create_drive(null_sp, rname, fdp, mhep); mhd_mx_unlock(&null_sp->sr_mx); /* return drive */ return (dp); } /* * process a file in the tree walk */ static int do_disk( const char *path, const struct stat *statp, int type ) { char *dname = NULL; int fd = -1; struct dk_cinfo cinfo; mhd_error_t status = mhd_null_error; /* skip all but character devices */ if ((type != FTW_F) || (! S_ISCHR(statp->st_mode)) || ((dname = diskname(path)) == NULL)) { return (0); } /* see if drive already exists */ if (mhd_find_drive(dname) != NULL) return (0); /* see if device is a disk */ if ((fd = open(path, (O_RDONLY|O_NDELAY), 0)) < 0) goto out; if (ioctl(fd, DKIOCINFO, &cinfo) != 0) { switch (errno) { case EINVAL: case ENOTTY: break; default: mhd_perror("DKIOCINFO: %s", path); break; } goto out; } /* skip CDROMs */ if (cinfo.dki_ctype == DKC_CDROM) { (void) close(fd); Free(dname); return (0); } /* put disk on list */ if (mhd_create_drive_anyset(dname, &fd, &status) == NULL) { mhde_perror(&status, ""); goto out; } /* cleanup, return success (no matter what) */ out: if (dname != NULL) Free(dname); if (fd >= 0) (void) close(fd); mhd_clrerror(&status); return (0); } /* * find or create all the drives under a given directory */ int mhd_create_drives( char *path, mhd_error_t *mhep ) { /* default */ if ((path == NULL) || (*path == '\0')) path = "/dev/rdsk"; free_pln_cache(); /* walk the directory, adding disks */ if (ftw(path, do_disk, 5) != 0) return (mhd_error(mhep, errno, path)); /* return success */ return (0); }