/* * 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 (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2017, Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libscsi_impl.h" static const libscsi_engine_t * get_engine(libscsi_hdl_t *hp, const char *name) { libscsi_engine_impl_t *eip; const libscsi_engine_t *ep; const char *engine_path, *p, *q; char engine_dir[MAXPATHLEN]; char engine_lib[MAXPATHLEN]; char init_name[MAXPATHLEN]; void *dl_hdl; libscsi_engine_init_f init; boolean_t found_lib = B_FALSE, found_init = B_FALSE; int dirs_tried = 0; char isa[257]; for (eip = hp->lsh_engines; eip != NULL; eip = eip->lsei_next) { if (strcmp(eip->lsei_engine->lse_name, name) == 0) return (eip->lsei_engine); } if ((engine_path = getenv("LIBSCSI_ENGINE_PATH")) == NULL) engine_path = LIBSCSI_DEFAULT_ENGINE_PATH; #if defined(_LP64) if (sysinfo(SI_ARCHITECTURE_64, isa, sizeof (isa)) < 0) isa[0] = '\0'; #else isa[0] = '\0'; #endif for (p = engine_path; p != NULL; p = q) { if ((q = strchr(p, ':')) != NULL) { ptrdiff_t len = q - p; (void) strncpy(engine_dir, p, len); engine_dir[len] = '\0'; while (*q == ':') ++q; if (*q == '\0') q = NULL; if (len == 0) continue; } else { (void) strcpy(engine_dir, p); } if (engine_dir[0] != '/') continue; ++dirs_tried; (void) snprintf(engine_lib, MAXPATHLEN, "%s/%s/%s%s", engine_dir, isa, name, LIBSCSI_ENGINE_EXT); dl_hdl = dlopen(engine_lib, RTLD_LOCAL | RTLD_LAZY | RTLD_PARENT); if (dl_hdl == NULL) { if (!found_lib) (void) libscsi_error(hp, ESCSI_NOENGINE, "unable to dlopen %s: %s", engine_lib, dlerror()); continue; } found_lib = B_TRUE; (void) snprintf(init_name, MAXPATHLEN, "libscsi_%s_init", name); init = (libscsi_engine_init_f)dlsym(dl_hdl, init_name); if (init == NULL) { if (!found_init) (void) libscsi_error(hp, ESCSI_NOENGINE, "failed to find %s in %s: %s", init_name, engine_lib, dlerror()); (void) dlclose(dl_hdl); continue; } if ((ep = init(hp)) == NULL) { (void) dlclose(dl_hdl); /* * libscsi errno set by init. */ return (NULL); } if (ep->lse_libversion != hp->lsh_version) { (void) dlclose(dl_hdl); (void) libscsi_error(hp, ESCSI_ENGINE_VER, "engine " "%s version %u does not match library version %u", engine_lib, ep->lse_libversion, hp->lsh_version); return (NULL); } eip = libscsi_zalloc(hp, sizeof (libscsi_engine_impl_t)); if (eip == NULL) { (void) dlclose(dl_hdl); return (NULL); } eip->lsei_engine = ep; eip->lsei_dl_hdl = dl_hdl; eip->lsei_next = hp->lsh_engines; hp->lsh_engines = eip; return (ep); } if (dirs_tried == 0) (void) libscsi_error(hp, ESCSI_ENGINE_BADPATH, "no valid " "directories found in engine path %s", engine_path); return (NULL); } static void scsi_parse_mtbf(const char *envvar, uint_t *intp) { const char *strval; int intval; if ((strval = getenv(envvar)) != NULL && (intval = atoi(strval)) > 0) { srand48(gethrtime()); *intp = intval; } } libscsi_target_t * libscsi_open(libscsi_hdl_t *hp, const char *engine, const void *target) { const libscsi_engine_t *ep; libscsi_target_t *tp; void *private; if (engine == NULL) { if ((engine = getenv("LIBSCSI_DEFAULT_ENGINE")) == NULL) engine = LIBSCSI_DEFAULT_ENGINE; } if ((ep = get_engine(hp, engine)) == NULL) return (NULL); if ((tp = libscsi_zalloc(hp, sizeof (libscsi_target_t))) == NULL) return (NULL); if ((private = ep->lse_ops->lseo_open(hp, target)) == NULL) { libscsi_free(hp, tp); return (NULL); } scsi_parse_mtbf("LIBSCSI_MTBF_CDB", &tp->lst_mtbf_cdb); scsi_parse_mtbf("LIBSCSI_MTBF_READ", &tp->lst_mtbf_read); scsi_parse_mtbf("LIBSCSI_MTBF_WRITE", &tp->lst_mtbf_write); tp->lst_hdl = hp; tp->lst_engine = ep; tp->lst_priv = private; ++hp->lsh_targets; if (libscsi_get_inquiry(hp, tp) != 0) { libscsi_close(hp, tp); return (NULL); } return (tp); } libscsi_hdl_t * libscsi_get_handle(libscsi_target_t *tp) { return (tp->lst_hdl); } void libscsi_close(libscsi_hdl_t *hp, libscsi_target_t *tp) { tp->lst_engine->lse_ops->lseo_close(hp, tp->lst_priv); libscsi_free(hp, tp->lst_vendor); libscsi_free(hp, tp->lst_product); libscsi_free(hp, tp->lst_revision); libscsi_free(hp, tp); --hp->lsh_targets; } sam4_status_t libscsi_action_get_status(const libscsi_action_t *ap) { const libscsi_action_impl_t *aip = (const libscsi_action_impl_t *)ap; return (aip->lsai_status); } /* * Set the timeout in seconds for this action. If no timeout is specified * or if the timeout is set to 0, an implementation-specific timeout will be * used (which may vary based on the target, command or other variables). * Not all engines support all timeout values. Setting the timeout to a value * not supported by the engine will cause engine-defined behavior when the * action is executed. */ void libscsi_action_set_timeout(libscsi_action_t *ap, uint32_t timeout) { libscsi_action_impl_t *aip = (libscsi_action_impl_t *)ap; aip->lsai_timeout = timeout; } /* * Obtain the timeout setting for this action. */ uint32_t libscsi_action_get_timeout(const libscsi_action_t *ap) { const libscsi_action_impl_t *aip = (const libscsi_action_impl_t *)ap; return (aip->lsai_timeout); } /* * Returns the flags associated with this action. Never fails. */ uint_t libscsi_action_get_flags(const libscsi_action_t *ap) { const libscsi_action_impl_t *aip = (const libscsi_action_impl_t *)ap; return (aip->lsai_flags); } /* * Return the length of the CDB buffer associated with this action. Never * fails. */ size_t libscsi_action_get_cdblen(const libscsi_action_t *ap) { const libscsi_action_impl_t *aip = (const libscsi_action_impl_t *)ap; return (aip->lsai_cdb_len); } /* * Returns the address of the action's CDB. The CDB buffer is guaranteed to * be large enough to hold the complete CDB for the command specified when the * action was allocated. Therefore, changing the command/opcode portion of * the CDB has undefined effects. The remainder of the CDB may be modified. */ uint8_t * libscsi_action_get_cdb(const libscsi_action_t *ap) { const libscsi_action_impl_t *aip = (const libscsi_action_impl_t *)ap; return (aip->lsai_cdb); } /* * Places the address of the action buffer in the location pointed to by bp, * if bp is not NULL. If ap is not NULL, it will contain the allocated size * of the buffer itself. If vp is not NULL, it will contain the number of * bytes of valid data currently stored in the buffer. * * If the action has LIBSCSI_AF_WRITE set and it has not yet been executed * successfully, the entire buffer is assumed to contain valid data. * * If the action has LIBSCSI_AF_READ set and it has not yet been executed * successfully, the amount of valid data is 0. * * If both LIBSCSI_AF_READ and LIBSCSI_AF_WRITE are clear, this function * fails with ESCSI_BADFLAGS to indicate that the action flags are * incompatible with the action data buffer. */ int libscsi_action_get_buffer(const libscsi_action_t *ap, uint8_t **bp, size_t *sp, size_t *vp) { const libscsi_action_impl_t *aip = (const libscsi_action_impl_t *)ap; if ((aip->lsai_flags & (LIBSCSI_AF_READ | LIBSCSI_AF_WRITE)) == 0) return (libscsi_error(aip->lsai_hdl, ESCSI_BADFLAGS, "data buffer not supported for actions with both " "LIBSCSI_AF_READ and LIBSCSI_AF_WRITE clear")); if ((aip->lsai_flags & LIBSCSI_AF_WRITE) && aip->lsai_status == LIBSCSI_STATUS_INVALID) { if (bp != NULL) *bp = aip->lsai_data; if (sp != NULL) *sp = aip->lsai_data_alloc; if (vp != NULL) *vp = aip->lsai_data_alloc; return (0); } if ((aip->lsai_flags & LIBSCSI_AF_READ) && aip->lsai_status != LIBSCSI_STATUS_INVALID) { if (bp != NULL) *bp = aip->lsai_data; if (sp != NULL) *sp = aip->lsai_data_alloc; if (vp != NULL) *vp = aip->lsai_data_len; return (0); } if (aip->lsai_flags & LIBSCSI_AF_WRITE) { if (bp != NULL) *bp = NULL; if (sp != NULL) *sp = 0; if (vp != NULL) *vp = 0; } else { if (bp != NULL) *bp = aip->lsai_data; if (sp != NULL) *sp = aip->lsai_data_alloc; if (vp != NULL) *vp = 0; } return (0); } /* * Obtain a pointer to the sense buffer for this action, if any, along with * the size of the sense buffer and the amount of valid data it contains. */ int libscsi_action_get_sense(const libscsi_action_t *ap, uint8_t **bp, size_t *sp, size_t *vp) { const libscsi_action_impl_t *aip = (const libscsi_action_impl_t *)ap; if (!(aip->lsai_flags & LIBSCSI_AF_RQSENSE)) return (libscsi_error(aip->lsai_hdl, ESCSI_BADFLAGS, "sense data unavailable: LIBSCSI_AF_RQSENSE is clear")); if (vp != NULL) { if (aip->lsai_status == LIBSCSI_STATUS_INVALID) *vp = 0; else *vp = aip->lsai_sense_len; } if (bp != NULL) { ASSERT(aip->lsai_sense_data != NULL); *bp = aip->lsai_sense_data; } if (sp != NULL) *sp = UINT8_MAX; return (0); } /* * Set the SCSI status of the action. * * Engines only. */ void libscsi_action_set_status(libscsi_action_t *ap, sam4_status_t status) { libscsi_action_impl_t *aip = (libscsi_action_impl_t *)ap; ASSERT(aip->lsai_status == LIBSCSI_STATUS_INVALID); aip->lsai_status = status; } /* * Set the length of valid data returned by a READ action. If the action is * not a READ action, or the length exceeds the size of the buffer, an error * results. * * Engines only. */ int libscsi_action_set_datalen(libscsi_action_t *ap, size_t len) { libscsi_action_impl_t *aip = (libscsi_action_impl_t *)ap; if ((aip->lsai_flags & LIBSCSI_AF_READ) == 0) return (libscsi_error(aip->lsai_hdl, ESCSI_BADFLAGS, "data cannot be returned for actions with LIBSCSI_AF_READ " "clear")); if (len > aip->lsai_data_alloc) return (libscsi_error(aip->lsai_hdl, ESCSI_BADLENGTH, "data length %lu exceeds allocated buffer capacity %lu", (ulong_t)len, (ulong_t)aip->lsai_data_alloc)); ASSERT(aip->lsai_data_len == 0); aip->lsai_data_len = len; return (0); } /* * Set the length of the valid sense data returned following the command, if * LIBSCSI_AF_RQSENSE is set for this action. Otherwise, fail. * * Engines only. */ int libscsi_action_set_senselen(libscsi_action_t *ap, size_t len) { libscsi_action_impl_t *aip = (libscsi_action_impl_t *)ap; if (!(aip->lsai_flags & LIBSCSI_AF_RQSENSE)) return (libscsi_error(aip->lsai_hdl, ESCSI_BADFLAGS, "sense data not supported: LIBSCSI_AF_RQSENSE is clear")); if (len > UINT8_MAX) return (libscsi_error(aip->lsai_hdl, ESCSI_BADLENGTH, "sense length %lu exceeds allocated buffer capacity %lu", (ulong_t)len, (ulong_t)UINT8_MAX)); ASSERT(aip->lsai_sense_len == 0); aip->lsai_sense_len = len; return (0); } /* * Allocate an action object. The object will contain a CDB area sufficiently * large to hold a CDB for the given command, and the CDB's opcode will be * filled in. A pointer to this CDB, the contents of which may be modified by * the caller, may be obtained by a subsequent call to libscsi_action_cdb(). * * If flags includes LIBSCSI_AF_READ or LIBSCSI_AF_WRITE, buflen must be * greater than zero. Otherwise, buflen must be 0 and buf must be NULL. * If buflen is nonzero but buf is NULL, a suitably-sized buffer will be * allocated; otherwise, the specified buffer will be used. In either case, * a pointer to the buffer may be obtained via a subsequent call to * libscsi_action_buffer(). * * If flags includes LIBSCSI_AF_RQSENSE, a REQUEST SENSE command will be * issued immediately following the termination of the specified command. * A buffer will be allocated to receive this sense data. Following successful * execution of the action, a pointer to this buffer and the length of * valid sense data may be obtained by a call to libscsi_action_sense(). * If cmd is SPC3_CMD_REQUEST_SENSE, this flag must be clear. */ libscsi_action_t * libscsi_action_alloc_vendor(libscsi_hdl_t *hp, spc3_cmd_t cmd, size_t cdbsz, uint_t flags, void *buf, size_t buflen) { libscsi_action_impl_t *aip; size_t sz; ptrdiff_t off; /* * If there's no buffer, it makes no sense to try to read or write * data. Likewise, if we're neither reading nor writing data, we * should not have a buffer. Both of these are programmer error. */ if (buflen == 0 && (flags & (LIBSCSI_AF_READ | LIBSCSI_AF_WRITE))) { (void) libscsi_error(hp, ESCSI_NEEDBUF, "a buffer is " "required when reading or writing"); return (NULL); } if (buflen > 0 && !(flags & (LIBSCSI_AF_READ | LIBSCSI_AF_WRITE))) { (void) libscsi_error(hp, ESCSI_BADFLAGS, "one of " "LIBSCSI_AF_READ and LIBSCSI_AF_WRITE must be specified " "in order to use a buffer"); return (NULL); } if (cdbsz == 0) { (void) libscsi_error(hp, ESCSI_BADLENGTH, "the supplied CDB " "buffer size has an invalid length, it must be non-zero."); return (NULL); } sz = cdbsz; /* * If the caller has asked for a buffer but has not provided one, we * will allocate it in our internal buffer along with the CDB and * request sense space (if requested). */ if (buf == NULL) sz += buflen; if (flags & LIBSCSI_AF_RQSENSE) sz += UINT8_MAX; sz += offsetof(libscsi_action_impl_t, lsai_buf[0]); if ((aip = libscsi_zalloc(hp, sz)) == NULL) return (NULL); aip->lsai_hdl = hp; aip->lsai_flags = flags; off = 0; aip->lsai_cdb = aip->lsai_buf + off; aip->lsai_cdb_len = cdbsz; off += cdbsz; aip->lsai_cdb[0] = (uint8_t)cmd; if (buflen > 0) { if (buf != NULL) { aip->lsai_data = buf; } else { aip->lsai_data = aip->lsai_buf + off; off += buflen; } aip->lsai_data_alloc = buflen; if (flags & LIBSCSI_AF_WRITE) aip->lsai_data_len = buflen; } if (flags & LIBSCSI_AF_RQSENSE) { aip->lsai_sense_data = aip->lsai_buf + off; off += UINT8_MAX; } aip->lsai_status = LIBSCSI_STATUS_INVALID; return ((libscsi_action_t *)aip); } libscsi_action_t * libscsi_action_alloc(libscsi_hdl_t *hp, spc3_cmd_t cmd, uint_t flags, void *buf, size_t buflen) { size_t cdbsz; if (cmd == SPC3_CMD_REQUEST_SENSE && (flags & LIBSCSI_AF_RQSENSE)) { (void) libscsi_error(hp, ESCSI_BADFLAGS, "request sense " "flag not allowed for request sense command"); return (NULL); } if ((cdbsz = libscsi_cmd_cdblen(hp, cmd)) == 0) return (NULL); return (libscsi_action_alloc_vendor(hp, cmd, cdbsz, flags, buf, buflen)); } void libscsi_action_free(libscsi_action_t *ap) { libscsi_action_impl_t *aip = (libscsi_action_impl_t *)ap; libscsi_free(aip->lsai_hdl, aip); } /* * For testing purposes, we allow data to be corrupted via an environment * variable setting. This helps ensure that higher level software can cope with * arbitrarily broken targets. The mtbf value represents the number of bytes we * will see, on average, in between each failure. Therefore, for each N bytes, * we would expect to see (N / mtbf) bytes of corruption. */ static void scsi_inject_errors(void *data, size_t len, uint_t mtbf) { char *buf = data; double prob; size_t index; if (len == 0) return; prob = (double)len / mtbf; while (prob > 1) { index = lrand48() % len; buf[index] = (lrand48() % 256); prob -= 1; } if (drand48() <= prob) { index = lrand48() % len; buf[index] = (lrand48() % 256); } } int libscsi_exec(libscsi_action_t *ap, libscsi_target_t *tp) { libscsi_action_impl_t *aip = (libscsi_action_impl_t *)ap; libscsi_hdl_t *hp = aip->lsai_hdl; int ret; if (tp->lst_mtbf_write != 0 && (aip->lsai_flags & LIBSCSI_AF_WRITE)) { scsi_inject_errors(aip->lsai_data, aip->lsai_data_len, tp->lst_mtbf_write); } if (tp->lst_mtbf_cdb != 0) { scsi_inject_errors(aip->lsai_cdb, aip->lsai_cdb_len, tp->lst_mtbf_cdb); } ret = tp->lst_engine->lse_ops->lseo_exec(hp, tp->lst_priv, ap); if (ret == 0 && tp->lst_mtbf_read != 0 && (aip->lsai_flags & LIBSCSI_AF_READ)) { scsi_inject_errors(aip->lsai_data, aip->lsai_data_len, tp->lst_mtbf_read); } return (ret); } int libscsi_max_transfer(libscsi_target_t *tp, size_t *sizep) { libscsi_hdl_t *hp = tp->lst_hdl; if (tp->lst_engine->lse_ops->lseo_max_transfer == NULL) { return (libscsi_error(hp, ESCSI_NOTSUP, "max transfer " "request not supported by engine")); } return (tp->lst_engine->lse_ops->lseo_max_transfer(hp, tp->lst_priv, sizep)); }