/* * Search the devid cache, returning dev_t list for all * device paths mapping to the device identified by the * given devid. * * Primary interface used by ddi_lyr_devid_to_devlist() */ int e_devid_cache_to_devt_list(ddi_devid_t devid, char *minor_name, int *retndevts, dev_t **retdevts) { char *path, **paths; int i, j, n; dev_t *devts, *udevts; dev_t tdevt; int ndevts, undevts, ndevts_alloced; dev_info_t *devi, **devis; int ndevis, npaths, nalloced; ddi_devid_t match_devid; DEVID_LOG_FIND(("find", devid, NULL)); ASSERT(ddi_devid_valid(devid) == DDI_SUCCESS); if (ddi_devid_valid(devid) != DDI_SUCCESS) { DEVID_LOG_ERR(("invalid devid", devid, NULL)); return (DDI_FAILURE); } nalloced = 128; for (;;) { paths = kmem_zalloc(nalloced * sizeof (char *), KM_SLEEP); devis = kmem_zalloc(nalloced * sizeof (dev_info_t *), KM_SLEEP); rw_enter(&dcfd->nvf_lock, RW_READER); n = e_devid_cache_devi_path_lists(devid, nalloced, &ndevis, devis, &npaths, paths); if (n <= nalloced) break; rw_exit(&dcfd->nvf_lock); for (i = 0; i < ndevis; i++) ndi_rele_devi(devis[i]); kmem_free(paths, nalloced * sizeof (char *)); kmem_free(devis, nalloced * sizeof (dev_info_t *)); nalloced = n + 128; } for (i = 0; i < npaths; i++) { path = i_ddi_strdup(paths[i], KM_SLEEP); paths[i] = path; } rw_exit(&dcfd->nvf_lock); if (ndevis == 0 && npaths == 0) { DEVID_LOG_ERR(("no devid found", devid, NULL)); kmem_free(paths, nalloced * sizeof (char *)); kmem_free(devis, nalloced * sizeof (dev_info_t *)); return (DDI_FAILURE); } ndevts_alloced = 128; restart: ndevts = 0; devts = kmem_alloc(ndevts_alloced * sizeof (dev_t), KM_SLEEP); for (i = 0; i < ndevis; i++) { ASSERT(!DEVI_IS_ATTACHING(devis[i])); ASSERT(!DEVI_IS_DETACHING(devis[i])); e_devid_minor_to_devlist(devis[i], minor_name, ndevts_alloced, &ndevts, devts); if (ndevts > ndevts_alloced) { kmem_free(devts, ndevts_alloced * sizeof (dev_t)); ndevts_alloced += 128; goto restart; } } for (i = 0; i < npaths; i++) { DEVID_LOG_LOOKUP((CE_CONT, "lookup %s\n", paths[i])); devi = e_ddi_hold_devi_by_path(paths[i], 0); if (devi == NULL) { DEVID_LOG_STALE(("stale device reference", devid, paths[i])); continue; } /* * Verify the newly attached device registered a matching devid */ if (i_ddi_devi_get_devid(DDI_DEV_T_ANY, devi, &match_devid) != DDI_SUCCESS) { DEVIDERR((CE_CONT, "%s: no devid registered on attach\n", paths[i])); ddi_release_devi(devi); continue; } if (ddi_devid_compare(devid, match_devid) != 0) { DEVID_LOG_STALE(("new devid registered", devid, paths[i])); ddi_release_devi(devi); ddi_devid_free(match_devid); continue; } ddi_devid_free(match_devid); e_devid_minor_to_devlist(devi, minor_name, ndevts_alloced, &ndevts, devts); ddi_release_devi(devi); if (ndevts > ndevts_alloced) { kmem_free(devts, ndevts_alloced * sizeof (dev_t)); ndevts_alloced += 128; goto restart; } } /* drop hold from e_devid_cache_devi_path_lists */ for (i = 0; i < ndevis; i++) { ndi_rele_devi(devis[i]); } for (i = 0; i < npaths; i++) { kmem_free(paths[i], strlen(paths[i]) + 1); } kmem_free(paths, nalloced * sizeof (char *)); kmem_free(devis, nalloced * sizeof (dev_info_t *)); if (ndevts == 0) { DEVID_LOG_ERR(("no devid found", devid, NULL)); kmem_free(devts, ndevts_alloced * sizeof (dev_t)); return (DDI_FAILURE); } /* * Build the final list of sorted dev_t's with duplicates collapsed so * returned results are consistent. This prevents implementation * artifacts from causing unnecessary changes in SVM namespace. */ /* bubble sort */ for (i = 0; i < (ndevts - 1); i++) { for (j = 0; j < ((ndevts - 1) - i); j++) { if (devts[j + 1] < devts[j]) { tdevt = devts[j]; devts[j] = devts[j + 1]; devts[j + 1] = tdevt; } } } /* determine number of unique values */ for (undevts = ndevts, i = 1; i < ndevts; i++) { if (devts[i - 1] == devts[i]) undevts--; } /* allocate unique */ udevts = kmem_alloc(undevts * sizeof (dev_t), KM_SLEEP); /* copy unique */ udevts[0] = devts[0]; for (i = 1, j = 1; i < ndevts; i++) { if (devts[i - 1] != devts[i]) udevts[j++] = devts[i]; } ASSERT(j == undevts); kmem_free(devts, ndevts_alloced * sizeof (dev_t)); *retndevts = undevts; *retdevts = udevts; return (DDI_SUCCESS); }
/* * Search for cached entries matching a devid * Return two lists: * a list of dev_info nodes, for those devices in the attached state * a list of pathnames whose instances registered the given devid * If the lists passed in are not sufficient to return the matching * references, return the size of lists required. * The dev_info nodes are returned with a hold that the caller must release. */ static int e_devid_cache_devi_path_lists(ddi_devid_t devid, int retmax, int *retndevis, dev_info_t **retdevis, int *retnpaths, char **retpaths) { nvp_devid_t *np; int ndevis, npaths; dev_info_t *dip, *pdip; int circ; int maxdevis = 0; int maxpaths = 0; ndevis = 0; npaths = 0; for (np = NVF_DEVID_LIST(dcfd); np; np = NVP_DEVID_NEXT(np)) { if (np->nvp_devid == NULL) continue; if (ddi_devid_valid(np->nvp_devid) != DDI_SUCCESS) { DEVIDERR((CE_CONT, "find: invalid devid %s\n", np->nvp_devpath)); continue; } if (ddi_devid_compare(devid, np->nvp_devid) == 0) { DEVID_DEBUG2((CE_CONT, "find: devid match: %s 0x%x\n", np->nvp_devpath, np->nvp_flags)); DEVID_LOG_MATCH(("find", devid, np->nvp_devpath)); DEVID_LOG_PATHS((CE_CONT, "%s\n", np->nvp_devpath)); /* * Check if we have a cached devinfo reference for this * devid. Place a hold on it to prevent detach * Otherwise, use the path instead. * Note: returns with a hold on each dev_info * node in the list. */ dip = NULL; if (np->nvp_flags & NVP_DEVID_DIP) { pdip = ddi_get_parent(np->nvp_dip); if (ndi_devi_tryenter(pdip, &circ)) { dip = np->nvp_dip; ndi_hold_devi(dip); ndi_devi_exit(pdip, circ); ASSERT(!DEVI_IS_ATTACHING(dip)); ASSERT(!DEVI_IS_DETACHING(dip)); } else { DEVID_LOG_DETACH((CE_CONT, "may be detaching: %s\n", np->nvp_devpath)); } } if (dip) { if (ndevis < retmax) { retdevis[ndevis++] = dip; } else { ndi_rele_devi(dip); } maxdevis++; } else { if (npaths < retmax) retpaths[npaths++] = np->nvp_devpath; maxpaths++; } } } *retndevis = ndevis; *retnpaths = npaths; return (maxdevis > maxpaths ? maxdevis : maxpaths); }
int e_devid_cache_register(dev_info_t *dip, ddi_devid_t devid) { nvp_devid_t *np; nvp_devid_t *new_nvp; ddi_devid_t new_devid; int new_devid_size; char *path, *fullpath; ddi_devid_t free_devid = NULL; int pathlen; ASSERT(ddi_devid_valid(devid) == DDI_SUCCESS); fullpath = kmem_alloc(MAXPATHLEN, KM_SLEEP); (void) ddi_pathname(dip, fullpath); pathlen = strlen(fullpath) + 1; path = kmem_alloc(pathlen, KM_SLEEP); bcopy(fullpath, path, pathlen); kmem_free(fullpath, MAXPATHLEN); DEVID_LOG_REG(("register", devid, path)); new_nvp = kmem_zalloc(sizeof (nvp_devid_t), KM_SLEEP); new_devid_size = ddi_devid_sizeof(devid); new_devid = kmem_alloc(new_devid_size, KM_SLEEP); (void) bcopy(devid, new_devid, new_devid_size); rw_enter(&dcfd->nvf_lock, RW_WRITER); for (np = NVF_DEVID_LIST(dcfd); np; np = NVP_DEVID_NEXT(np)) { if (strcmp(path, np->nvp_devpath) == 0) { DEVID_DEBUG2((CE_CONT, "register: %s path match\n", path)); if (np->nvp_devid == NULL) { replace: np->nvp_devid = new_devid; np->nvp_flags |= NVP_DEVID_DIP | NVP_DEVID_REGISTERED; np->nvp_dip = dip; NVF_MARK_DIRTY(dcfd); rw_exit(&dcfd->nvf_lock); kmem_free(new_nvp, sizeof (nvp_devid_t)); kmem_free(path, pathlen); goto exit; } if (ddi_devid_valid(np->nvp_devid) != DDI_SUCCESS) { /* replace invalid devid */ free_devid = np->nvp_devid; goto replace; } /* * We're registering an already-cached path * Does the device's devid match the cache? */ if (ddi_devid_compare(devid, np->nvp_devid) != 0) { DEVID_DEBUG((CE_CONT, "devid register: " "devid %s does not match\n", path)); /* * Replace cached devid for this path * with newly registered devid. A devid * may map to multiple paths but one path * should only map to one devid. */ nfd_nvp_free_and_unlink(dcfd, NVPLIST(np)); np = NULL; break; } else { DEVID_DEBUG2((CE_CONT, "devid register: %s devid match\n", path)); np->nvp_flags |= NVP_DEVID_DIP | NVP_DEVID_REGISTERED; np->nvp_dip = dip; rw_exit(&dcfd->nvf_lock); kmem_free(new_nvp, sizeof (nvp_devid_t)); kmem_free(path, pathlen); kmem_free(new_devid, new_devid_size); return (DDI_SUCCESS); } } } /* * Add newly registered devid to the cache */ ASSERT(np == NULL); new_nvp->nvp_devpath = path; new_nvp->nvp_flags = NVP_DEVID_DIP | NVP_DEVID_REGISTERED; new_nvp->nvp_dip = dip; new_nvp->nvp_devid = new_devid; NVF_MARK_DIRTY(dcfd); nfd_nvp_link(dcfd, NVPLIST(new_nvp)); rw_exit(&dcfd->nvf_lock); exit: if (free_devid) kmem_free(free_devid, ddi_devid_sizeof(free_devid)); if (!devid_cache_write_disable) wake_nvpflush_daemon(); return (DDI_SUCCESS); }
static int vdev_disk_open(vdev_t *vd, uint64_t *psize, uint64_t *max_psize, uint64_t *ashift) { spa_t *spa = vd->vdev_spa; vdev_disk_t *dvd = vd->vdev_tsd; ldi_ev_cookie_t ecookie; vdev_disk_ldi_cb_t *lcb; union { struct dk_minfo_ext ude; struct dk_minfo ud; } dks; struct dk_minfo_ext *dkmext = &dks.ude; struct dk_minfo *dkm = &dks.ud; int error; /* XXX Apple - must leave devid unchanged */ #ifdef illumos dev_t dev; int otyp; boolean_t validate_devid = B_FALSE; ddi_devid_t devid; #endif uint64_t capacity = 0, blksz = 0, pbsize; #ifdef __APPLE__ int isssd; #endif /* * We must have a pathname, and it must be absolute. */ if (vd->vdev_path == NULL || vd->vdev_path[0] != '/') { vd->vdev_stat.vs_aux = VDEV_AUX_BAD_LABEL; return (SET_ERROR(EINVAL)); } /* * Reopen the device if it's not currently open. Otherwise, * just update the physical size of the device. */ if (dvd != NULL) { if (dvd->vd_ldi_offline && dvd->vd_lh == NULL) { /* * If we are opening a device in its offline notify * context, the LDI handle was just closed. Clean * up the LDI event callbacks and free vd->vdev_tsd. */ vdev_disk_free(vd); } else { ASSERT(vd->vdev_reopening); goto skip_open; } } /* * Create vd->vdev_tsd. */ vdev_disk_alloc(vd); dvd = vd->vdev_tsd; /* * When opening a disk device, we want to preserve the user's original * intent. We always want to open the device by the path the user gave * us, even if it is one of multiple paths to the same device. But we * also want to be able to survive disks being removed/recabled. * Therefore the sequence of opening devices is: * * 1. Try opening the device by path. For legacy pools without the * 'whole_disk' property, attempt to fix the path by appending 's0'. * * 2. If the devid of the device matches the stored value, return * success. * * 3. Otherwise, the device may have moved. Try opening the device * by the devid instead. */ /* * XXX We must not set or modify the devid as this check would prevent * import on Solaris/illumos. */ #ifdef illumos if (vd->vdev_devid != NULL) { if (ddi_devid_str_decode(vd->vdev_devid, &dvd->vd_devid, &dvd->vd_minor) != 0) { vd->vdev_stat.vs_aux = VDEV_AUX_BAD_LABEL; vdev_dbgmsg(vd, "vdev_disk_open: invalid " "vdev_devid '%s'", vd->vdev_devid); return (SET_ERROR(EINVAL)); } } #endif error = EINVAL; /* presume failure */ if (vd->vdev_path != NULL) { /* * XXX This assumes that if vdev_path refers to a device path /dev/dsk/cNtNdN, * then the whole disk can be found by slice 0 at path /dev/dsk/cNtNdNs0. */ #ifdef illumos if (vd->vdev_wholedisk == -1ULL) { size_t len = strlen(vd->vdev_path) + 3; char *buf = kmem_alloc(len, KM_SLEEP); (void) snprintf(buf, len, "%ss0", vd->vdev_path); error = ldi_open_by_name(buf, spa_mode(spa), kcred, &dvd->vd_lh, zfs_li); if (error == 0) { spa_strfree(vd->vdev_path); vd->vdev_path = buf; vd->vdev_wholedisk = 1ULL; } else { kmem_free(buf, len); } } #endif /* * If we have not yet opened the device, try to open it by the * specified path. */ if (error != 0) { error = ldi_open_by_name(vd->vdev_path, spa_mode(spa), kcred, &dvd->vd_lh, zfs_li); } /* XXX Apple - must leave devid unchanged */ #ifdef illumos /* * Compare the devid to the stored value. */ if (error == 0 && vd->vdev_devid != NULL && ldi_get_devid(dvd->vd_lh, &devid) == 0) { if (ddi_devid_compare(devid, dvd->vd_devid) != 0) { error = SET_ERROR(EINVAL); (void) ldi_close(dvd->vd_lh, spa_mode(spa), kcred); dvd->vd_lh = NULL; } ddi_devid_free(devid); } #endif /* * If we succeeded in opening the device, but 'vdev_wholedisk' * is not yet set, then this must be a slice. */ if (error == 0 && vd->vdev_wholedisk == -1ULL) vd->vdev_wholedisk = 0; } /* XXX Apple - must leave devid unchanged */ #ifdef illumos /* * If we were unable to open by path, or the devid check fails, open by * devid instead. */ if (error != 0 && vd->vdev_devid != NULL) { error = ldi_open_by_devid(dvd->vd_devid, dvd->vd_minor, spa_mode(spa), kcred, &dvd->vd_lh, zfs_li); } #endif /* * If all else fails, then try opening by physical path (if available) * or the logical path (if we failed due to the devid check). While not * as reliable as the devid, this will give us something, and the higher * level vdev validation will prevent us from opening the wrong device. */ if (error) { /* XXX Apple - must leave devid unchanged */ #ifdef illumos if (vd->vdev_devid != NULL) validate_devid = B_TRUE; #endif /* XXX Apple to do - make ddi_ interface for this, using IORegistry path */ #ifdef illumos if (vd->vdev_physpath != NULL && (dev = ddi_pathname_to_dev_t(vd->vdev_physpath)) != NODEV) error = ldi_open_by_dev(&dev, OTYP_BLK, spa_mode(spa), kcred, &dvd->vd_lh, zfs_li); #endif /* * Note that we don't support the legacy auto-wholedisk support * as above. This hasn't been used in a very long time and we * don't need to propagate its oddities to this edge condition. */ if (error && vd->vdev_path != NULL) error = ldi_open_by_name(vd->vdev_path, spa_mode(spa), kcred, &dvd->vd_lh, zfs_li); } if (error) { vd->vdev_stat.vs_aux = VDEV_AUX_OPEN_FAILED; vdev_dbgmsg(vd, "vdev_disk_open: failed to open [error=%d]", error); return (error); } /* * XXX Apple - We must not set or modify the devid. Import on Solaris/illumos * expects a valid devid and fails if it cannot be decoded. */ #ifdef illumos /* * Now that the device has been successfully opened, update the devid * if necessary. */ if (validate_devid && spa_writeable(spa) && ldi_get_devid(dvd->vd_lh, &devid) == 0) { if (ddi_devid_compare(devid, dvd->vd_devid) != 0) { char *vd_devid; vd_devid = ddi_devid_str_encode(devid, dvd->vd_minor); vdev_dbgmsg(vd, "vdev_disk_open: update devid from " "'%s' to '%s'", vd->vdev_devid, vd_devid); spa_strfree(vd->vdev_devid); vd->vdev_devid = spa_strdup(vd_devid); ddi_devid_str_free(vd_devid); } ddi_devid_free(devid); } #endif /* XXX Apple to do, needs IORegistry physpath interface */ #ifdef illumos /* * Once a device is opened, verify that the physical device path (if * available) is up to date. */ if (ldi_get_dev(dvd->vd_lh, &dev) == 0 && ldi_get_otyp(dvd->vd_lh, &otyp) == 0) { char *physpath, *minorname; physpath = kmem_alloc(MAXPATHLEN, KM_SLEEP); minorname = NULL; if (ddi_dev_pathname(dev, otyp, physpath) == 0 && ldi_get_minor_name(dvd->vd_lh, &minorname) == 0 && (vd->vdev_physpath == NULL || strcmp(vd->vdev_physpath, physpath) != 0)) { if (vd->vdev_physpath) spa_strfree(vd->vdev_physpath); (void) strlcat(physpath, ":", MAXPATHLEN); (void) strlcat(physpath, minorname, MAXPATHLEN); vd->vdev_physpath = spa_strdup(physpath); } if (minorname) kmem_free(minorname, strlen(minorname) + 1); kmem_free(physpath, MAXPATHLEN); } #endif /* * Register callbacks for the LDI offline event. */ if (ldi_ev_get_cookie(dvd->vd_lh, LDI_EV_OFFLINE, &ecookie) == LDI_EV_SUCCESS) { lcb = kmem_zalloc(sizeof (vdev_disk_ldi_cb_t), KM_SLEEP); list_insert_tail(&dvd->vd_ldi_cbs, lcb); (void) ldi_ev_register_callbacks(dvd->vd_lh, ecookie, &vdev_disk_off_callb, (void *) vd, &lcb->lcb_id); } /* XXX Apple to do - we could support the degrade event, or just no-op */ #ifdef illumos /* * Register callbacks for the LDI degrade event. */ if (ldi_ev_get_cookie(dvd->vd_lh, LDI_EV_DEGRADE, &ecookie) == LDI_EV_SUCCESS) { lcb = kmem_zalloc(sizeof (vdev_disk_ldi_cb_t), KM_SLEEP); list_insert_tail(&dvd->vd_ldi_cbs, lcb); (void) ldi_ev_register_callbacks(dvd->vd_lh, ecookie, &vdev_disk_dgrd_callb, (void *) vd, &lcb->lcb_id); } #endif #if 0 int len = MAXPATHLEN; if (vn_getpath(devvp, dvd->vd_readlinkname, &len) == 0) { dprintf("ZFS: '%s' resolved name is '%s'\n", vd->vdev_path, dvd->vd_readlinkname); } else { dvd->vd_readlinkname[0] = 0; } #endif skip_open: /* * Determine the actual size of the device. */ if (ldi_get_size(dvd->vd_lh, psize) != 0) { vd->vdev_stat.vs_aux = VDEV_AUX_OPEN_FAILED; vdev_dbgmsg(vd, "vdev_disk_open: failed to get size"); return (SET_ERROR(EINVAL)); } *max_psize = *psize; /* * Determine the device's minimum transfer size. * If the ioctl isn't supported, assume DEV_BSIZE. */ if ((error = ldi_ioctl(dvd->vd_lh, DKIOCGMEDIAINFOEXT, (intptr_t)dkmext, FKIOCTL, kcred, NULL)) == 0) { capacity = dkmext->dki_capacity - 1; blksz = dkmext->dki_lbsize; pbsize = dkmext->dki_pbsize; } else if ((error = ldi_ioctl(dvd->vd_lh, DKIOCGMEDIAINFO, (intptr_t)dkm, FKIOCTL, kcred, NULL)) == 0) { VDEV_DEBUG( "vdev_disk_open(\"%s\"): fallback to DKIOCGMEDIAINFO\n", vd->vdev_path); capacity = dkm->dki_capacity - 1; blksz = dkm->dki_lbsize; pbsize = blksz; } else { VDEV_DEBUG("vdev_disk_open(\"%s\"): " "both DKIOCGMEDIAINFO{,EXT} calls failed, %d\n", vd->vdev_path, error); pbsize = DEV_BSIZE; } *ashift = highbit64(MAX(pbsize, SPA_MINBLOCKSIZE)) - 1; /* XXX Now that we opened the device, determine if it is a whole disk. */ #ifdef __APPLE__ /* * XXX Apple to do - provide an ldi_ mechanism * to report whether this is a whole disk or a * partition. * Return 0 (no), 1 (yes), or -1 (error). */ // vd->vdev_wholedisk = ldi_is_wholedisk(vd->vd_lh); #endif if (vd->vdev_wholedisk == 1) { int wce = 1; /* Gets information about the disk if it has GPT partitions */ #ifdef illumos if (error == 0) { /* * If we have the capability to expand, we'd have * found out via success from DKIOCGMEDIAINFO{,EXT}. * Adjust max_psize upward accordingly since we know * we own the whole disk now. */ *max_psize = capacity * blksz; } #endif /* * Since we own the whole disk, try to enable disk write * caching. We ignore errors because it's OK if we can't do it. */ (void) ldi_ioctl(dvd->vd_lh, DKIOCSETWCE, (intptr_t)&wce, FKIOCTL, kcred, NULL); } /* * Clear the nowritecache bit, so that on a vdev_reopen() we will * try again. */ vd->vdev_nowritecache = B_FALSE; #ifdef __APPLE__ /* Inform the ZIO pipeline that we are non-rotational */ vd->vdev_nonrot = B_FALSE; if (ldi_ioctl(dvd->vd_lh, DKIOCISSOLIDSTATE, (intptr_t)&isssd, FKIOCTL, kcred, NULL) == 0) { vd->vdev_nonrot = (isssd ? B_TRUE : B_FALSE); } #endif //__APPLE__ return (0); }
static int vdev_disk_open(vdev_t *vd, uint64_t *psize, uint64_t *max_psize, uint64_t *ashift) { spa_t *spa = vd->vdev_spa; vdev_disk_t *dvd; ldi_ev_cookie_t ecookie; vdev_disk_ldi_cb_t *lcb; union { struct dk_minfo_ext ude; struct dk_minfo ud; } dks; struct dk_minfo_ext *dkmext = &dks.ude; struct dk_minfo *dkm = &dks.ud; int error; dev_t dev; int otyp; boolean_t validate_devid = B_FALSE; ddi_devid_t devid; uint64_t capacity = 0, blksz = 0, pbsize; /* * We must have a pathname, and it must be absolute. */ if (vd->vdev_path == NULL || vd->vdev_path[0] != '/') { vd->vdev_stat.vs_aux = VDEV_AUX_BAD_LABEL; return (SET_ERROR(EINVAL)); } rw_enter(&vd->vdev_tsd_lock, RW_WRITER); dvd = vd->vdev_tsd; /* * Reopen the device if it's not currently open. Otherwise, * just update the physical size of the device. */ if (dvd != NULL) { ASSERT(vd->vdev_reopening); /* * Here vd_lh is protected by vdev_tsd_lock */ ASSERT(dvd->vd_lh != NULL); /* This should not happen, but let's be safe */ if (dvd->vd_lh == NULL) { /* What are we going to do here??? */ rw_exit(&vd->vdev_tsd_lock); return (SET_ERROR(ENXIO)); } goto skip_open; } /* * Create dvd to be used as vd->vdev_tsd. */ vd->vdev_tsd = dvd = vdev_disk_alloc(); /* * When opening a disk device, we want to preserve the user's original * intent. We always want to open the device by the path the user gave * us, even if it is one of multiple paths to the same device. But we * also want to be able to survive disks being removed/recabled. * Therefore the sequence of opening devices is: * * 1. Try opening the device by path. For legacy pools without the * 'whole_disk' property, attempt to fix the path by appending 's0'. * * 2. If the devid of the device matches the stored value, return * success. * * 3. Otherwise, the device may have moved. Try opening the device * by the devid instead. */ if (vd->vdev_devid != NULL) { if (ddi_devid_str_decode(vd->vdev_devid, &dvd->vd_devid, &dvd->vd_minor) != 0) { vd->vdev_stat.vs_aux = VDEV_AUX_BAD_LABEL; vdev_disk_free_locked(vd); rw_exit(&vd->vdev_tsd_lock); return (SET_ERROR(EINVAL)); } } error = EINVAL; /* presume failure */ if (vd->vdev_path != NULL) { if (vd->vdev_wholedisk == -1ULL) { size_t len = strlen(vd->vdev_path) + 3; char *buf = kmem_alloc(len, KM_SLEEP); (void) snprintf(buf, len, "%ss0", vd->vdev_path); error = ldi_open_by_name(buf, spa_mode(spa), kcred, &dvd->vd_lh, zfs_li); if (error == 0) { spa_strfree(vd->vdev_path); vd->vdev_path = buf; vd->vdev_wholedisk = 1ULL; } else { kmem_free(buf, len); } } /* * If we have not yet opened the device, try to open it by the * specified path. */ if (error != 0) { error = ldi_open_by_name(vd->vdev_path, spa_mode(spa), kcred, &dvd->vd_lh, zfs_li); } /* * Compare the devid to the stored value. */ if (error == 0 && vd->vdev_devid != NULL && ldi_get_devid(dvd->vd_lh, &devid) == 0) { if (ddi_devid_compare(devid, dvd->vd_devid) != 0) { error = SET_ERROR(EINVAL); (void) ldi_close(dvd->vd_lh, spa_mode(spa), kcred); dvd->vd_lh = NULL; } ddi_devid_free(devid); } /* * If we succeeded in opening the device, but 'vdev_wholedisk' * is not yet set, then this must be a slice. */ if (error == 0 && vd->vdev_wholedisk == -1ULL) vd->vdev_wholedisk = 0; } /* * If we were unable to open by path, or the devid check fails, open by * devid instead. */ if (error != 0 && vd->vdev_devid != NULL) { error = ldi_open_by_devid(dvd->vd_devid, dvd->vd_minor, spa_mode(spa), kcred, &dvd->vd_lh, zfs_li); } /* * If all else fails, then try opening by physical path (if available) * or the logical path (if we failed due to the devid check). While not * as reliable as the devid, this will give us something, and the higher * level vdev validation will prevent us from opening the wrong device. */ if (error) { if (vd->vdev_devid != NULL) validate_devid = B_TRUE; if (vd->vdev_physpath != NULL && (dev = ddi_pathname_to_dev_t(vd->vdev_physpath)) != NODEV) error = ldi_open_by_dev(&dev, OTYP_BLK, spa_mode(spa), kcred, &dvd->vd_lh, zfs_li); /* * Note that we don't support the legacy auto-wholedisk support * as above. This hasn't been used in a very long time and we * don't need to propagate its oddities to this edge condition. */ if (error && vd->vdev_path != NULL) error = ldi_open_by_name(vd->vdev_path, spa_mode(spa), kcred, &dvd->vd_lh, zfs_li); } if (error) { vd->vdev_stat.vs_aux = VDEV_AUX_OPEN_FAILED; vdev_disk_free_locked(vd); rw_exit(&vd->vdev_tsd_lock); return (error); } /* * Now that the device has been successfully opened, update the devid * if necessary. */ if (validate_devid && spa_writeable(spa) && ldi_get_devid(dvd->vd_lh, &devid) == 0) { if (ddi_devid_compare(devid, dvd->vd_devid) != 0) { char *vd_devid; vd_devid = ddi_devid_str_encode(devid, dvd->vd_minor); zfs_dbgmsg("vdev %s: update devid from %s, " "to %s", vd->vdev_path, vd->vdev_devid, vd_devid); spa_strfree(vd->vdev_devid); vd->vdev_devid = spa_strdup(vd_devid); ddi_devid_str_free(vd_devid); } ddi_devid_free(devid); } /* * Once a device is opened, verify that the physical device path (if * available) is up to date. */ if (ldi_get_dev(dvd->vd_lh, &dev) == 0 && ldi_get_otyp(dvd->vd_lh, &otyp) == 0) { char *physpath, *minorname; physpath = kmem_alloc(MAXPATHLEN, KM_SLEEP); minorname = NULL; if (ddi_dev_pathname(dev, otyp, physpath) == 0 && ldi_get_minor_name(dvd->vd_lh, &minorname) == 0 && (vd->vdev_physpath == NULL || strcmp(vd->vdev_physpath, physpath) != 0)) { if (vd->vdev_physpath) spa_strfree(vd->vdev_physpath); (void) strlcat(physpath, ":", MAXPATHLEN); (void) strlcat(physpath, minorname, MAXPATHLEN); vd->vdev_physpath = spa_strdup(physpath); } if (minorname) kmem_free(minorname, strlen(minorname) + 1); kmem_free(physpath, MAXPATHLEN); } /* * Register callbacks for the LDI offline event. */ if (ldi_ev_get_cookie(dvd->vd_lh, LDI_EV_OFFLINE, &ecookie) == LDI_EV_SUCCESS) { lcb = kmem_zalloc(sizeof (vdev_disk_ldi_cb_t), KM_SLEEP); list_insert_tail(&dvd->vd_ldi_cbs, lcb); (void) ldi_ev_register_callbacks(dvd->vd_lh, ecookie, &vdev_disk_off_callb, (void *) vd, &lcb->lcb_id); } /* * Register callbacks for the LDI degrade event. */ if (ldi_ev_get_cookie(dvd->vd_lh, LDI_EV_DEGRADE, &ecookie) == LDI_EV_SUCCESS) { lcb = kmem_zalloc(sizeof (vdev_disk_ldi_cb_t), KM_SLEEP); list_insert_tail(&dvd->vd_ldi_cbs, lcb); (void) ldi_ev_register_callbacks(dvd->vd_lh, ecookie, &vdev_disk_dgrd_callb, (void *) vd, &lcb->lcb_id); } /* Reset TRIM flag, as underlying device support may have changed */ vd->vdev_notrim = B_FALSE; skip_open: ASSERT(dvd != NULL); /* * Determine the actual size of the device. */ if (ldi_get_size(dvd->vd_lh, psize) != 0) { vd->vdev_stat.vs_aux = VDEV_AUX_OPEN_FAILED; vdev_disk_free_locked(vd); rw_exit(&vd->vdev_tsd_lock); return (SET_ERROR(EINVAL)); } *max_psize = *psize; /* * Determine the device's minimum transfer size. * If the ioctl isn't supported, assume DEV_BSIZE. */ if ((error = ldi_ioctl(dvd->vd_lh, DKIOCGMEDIAINFOEXT, (intptr_t)dkmext, FKIOCTL, kcred, NULL)) == 0) { capacity = dkmext->dki_capacity - 1; blksz = dkmext->dki_lbsize; pbsize = dkmext->dki_pbsize; } else if ((error = ldi_ioctl(dvd->vd_lh, DKIOCGMEDIAINFO, (intptr_t)dkm, FKIOCTL, kcred, NULL)) == 0) { VDEV_DEBUG( "vdev_disk_open(\"%s\"): fallback to DKIOCGMEDIAINFO\n", vd->vdev_path); capacity = dkm->dki_capacity - 1; blksz = dkm->dki_lbsize; pbsize = blksz; } else { VDEV_DEBUG("vdev_disk_open(\"%s\"): " "both DKIOCGMEDIAINFO{,EXT} calls failed, %d\n", vd->vdev_path, error); pbsize = DEV_BSIZE; } *ashift = highbit64(MAX(pbsize, SPA_MINBLOCKSIZE)) - 1; if (vd->vdev_wholedisk == 1) { int wce = 1; if (error == 0) { /* * If we have the capability to expand, we'd have * found out via success from DKIOCGMEDIAINFO{,EXT}. * Adjust max_psize upward accordingly since we know * we own the whole disk now. */ *max_psize += vdev_disk_get_space(vd, capacity, blksz); zfs_dbgmsg("capacity change: vdev %s, psize %llu, " "max_psize %llu", vd->vdev_path, *psize, *max_psize); } /* * Since we own the whole disk, try to enable disk write * caching. We ignore errors because it's OK if we can't do it. */ (void) ldi_ioctl(dvd->vd_lh, DKIOCSETWCE, (intptr_t)&wce, FKIOCTL, kcred, NULL); } /* * We are done with vd_lh and vdev_tsd, release the vdev_tsd_lock */ rw_exit(&vd->vdev_tsd_lock); /* * Clear the nowritecache bit, so that on a vdev_reopen() we will * try again. */ vd->vdev_nowritecache = B_FALSE; return (0); }
static int vdev_disk_open(vdev_t *vd, uint64_t *psize, uint64_t *ashift) { spa_t *spa = vd->vdev_spa; vdev_disk_t *dvd; struct dk_minfo_ext dkmext; int error; dev_t dev; int otyp; /* * We must have a pathname, and it must be absolute. */ if (vd->vdev_path == NULL || vd->vdev_path[0] != '/') { vd->vdev_stat.vs_aux = VDEV_AUX_BAD_LABEL; return (EINVAL); } /* * Reopen the device if it's not currently open. Otherwise, * just update the physical size of the device. */ if (vd->vdev_tsd != NULL) { ASSERT(vd->vdev_reopening); dvd = vd->vdev_tsd; goto skip_open; } dvd = vd->vdev_tsd = kmem_zalloc(sizeof (vdev_disk_t), KM_SLEEP); /* * When opening a disk device, we want to preserve the user's original * intent. We always want to open the device by the path the user gave * us, even if it is one of multiple paths to the save device. But we * also want to be able to survive disks being removed/recabled. * Therefore the sequence of opening devices is: * * 1. Try opening the device by path. For legacy pools without the * 'whole_disk' property, attempt to fix the path by appending 's0'. * * 2. If the devid of the device matches the stored value, return * success. * * 3. Otherwise, the device may have moved. Try opening the device * by the devid instead. */ if (vd->vdev_devid != NULL) { if (ddi_devid_str_decode(vd->vdev_devid, &dvd->vd_devid, &dvd->vd_minor) != 0) { vd->vdev_stat.vs_aux = VDEV_AUX_BAD_LABEL; return (EINVAL); } } error = EINVAL; /* presume failure */ if (vd->vdev_path != NULL) { ddi_devid_t devid; if (vd->vdev_wholedisk == -1ULL) { size_t len = strlen(vd->vdev_path) + 3; char *buf = kmem_alloc(len, KM_SLEEP); ldi_handle_t lh; (void) snprintf(buf, len, "%ss0", vd->vdev_path); if (ldi_open_by_name(buf, spa_mode(spa), kcred, &lh, zfs_li) == 0) { spa_strfree(vd->vdev_path); vd->vdev_path = buf; vd->vdev_wholedisk = 1ULL; (void) ldi_close(lh, spa_mode(spa), kcred); } else { kmem_free(buf, len); } } error = ldi_open_by_name(vd->vdev_path, spa_mode(spa), kcred, &dvd->vd_lh, zfs_li); /* * Compare the devid to the stored value. */ if (error == 0 && vd->vdev_devid != NULL && ldi_get_devid(dvd->vd_lh, &devid) == 0) { if (ddi_devid_compare(devid, dvd->vd_devid) != 0) { error = EINVAL; (void) ldi_close(dvd->vd_lh, spa_mode(spa), kcred); dvd->vd_lh = NULL; } ddi_devid_free(devid); } /* * If we succeeded in opening the device, but 'vdev_wholedisk' * is not yet set, then this must be a slice. */ if (error == 0 && vd->vdev_wholedisk == -1ULL) vd->vdev_wholedisk = 0; } /* * If we were unable to open by path, or the devid check fails, open by * devid instead. */ if (error != 0 && vd->vdev_devid != NULL) error = ldi_open_by_devid(dvd->vd_devid, dvd->vd_minor, spa_mode(spa), kcred, &dvd->vd_lh, zfs_li); /* * If all else fails, then try opening by physical path (if available) * or the logical path (if we failed due to the devid check). While not * as reliable as the devid, this will give us something, and the higher * level vdev validation will prevent us from opening the wrong device. */ if (error) { if (vd->vdev_physpath != NULL && (dev = ddi_pathname_to_dev_t(vd->vdev_physpath)) != NODEV) error = ldi_open_by_dev(&dev, OTYP_BLK, spa_mode(spa), kcred, &dvd->vd_lh, zfs_li); /* * Note that we don't support the legacy auto-wholedisk support * as above. This hasn't been used in a very long time and we * don't need to propagate its oddities to this edge condition. */ if (error && vd->vdev_path != NULL) error = ldi_open_by_name(vd->vdev_path, spa_mode(spa), kcred, &dvd->vd_lh, zfs_li); } if (error) { vd->vdev_stat.vs_aux = VDEV_AUX_OPEN_FAILED; return (error); } /* * Once a device is opened, verify that the physical device path (if * available) is up to date. */ if (ldi_get_dev(dvd->vd_lh, &dev) == 0 && ldi_get_otyp(dvd->vd_lh, &otyp) == 0) { char *physpath, *minorname; physpath = kmem_alloc(MAXPATHLEN, KM_SLEEP); minorname = NULL; if (ddi_dev_pathname(dev, otyp, physpath) == 0 && ldi_get_minor_name(dvd->vd_lh, &minorname) == 0 && (vd->vdev_physpath == NULL || strcmp(vd->vdev_physpath, physpath) != 0)) { if (vd->vdev_physpath) spa_strfree(vd->vdev_physpath); (void) strlcat(physpath, ":", MAXPATHLEN); (void) strlcat(physpath, minorname, MAXPATHLEN); vd->vdev_physpath = spa_strdup(physpath); } if (minorname) kmem_free(minorname, strlen(minorname) + 1); kmem_free(physpath, MAXPATHLEN); } skip_open: /* * Determine the actual size of the device. */ if (ldi_get_size(dvd->vd_lh, psize) != 0) { vd->vdev_stat.vs_aux = VDEV_AUX_OPEN_FAILED; return (EINVAL); } /* * If we own the whole disk, try to enable disk write caching. * We ignore errors because it's OK if we can't do it. */ if (vd->vdev_wholedisk == 1) { int wce = 1; (void) ldi_ioctl(dvd->vd_lh, DKIOCSETWCE, (intptr_t)&wce, FKIOCTL, kcred, NULL); } /* * Determine the device's minimum transfer size. * If the ioctl isn't supported, assume DEV_BSIZE. */ if (ldi_ioctl(dvd->vd_lh, DKIOCGMEDIAINFOEXT, (intptr_t)&dkmext, FKIOCTL, kcred, NULL) != 0) dkmext.dki_pbsize = DEV_BSIZE; *ashift = highbit(MAX(dkmext.dki_pbsize, SPA_MINBLOCKSIZE)) - 1; /* * Clear the nowritecache bit, so that on a vdev_reopen() we will * try again. */ vd->vdev_nowritecache = B_FALSE; return (0); }