diff options
Diffstat (limited to 'usr/src/lib/scsi/libses/common')
-rw-r--r-- | usr/src/lib/scsi/libses/common/libses.h | 132 | ||||
-rw-r--r-- | usr/src/lib/scsi/libses/common/libses_plugin.h | 161 | ||||
-rw-r--r-- | usr/src/lib/scsi/libses/common/mkerrno.sh | 86 | ||||
-rw-r--r-- | usr/src/lib/scsi/libses/common/ses_enclosure.c | 293 | ||||
-rw-r--r-- | usr/src/lib/scsi/libses/common/ses_impl.h | 170 | ||||
-rw-r--r-- | usr/src/lib/scsi/libses/common/ses_node.c | 399 | ||||
-rw-r--r-- | usr/src/lib/scsi/libses/common/ses_plugin.c | 398 | ||||
-rw-r--r-- | usr/src/lib/scsi/libses/common/ses_snap.c | 700 | ||||
-rw-r--r-- | usr/src/lib/scsi/libses/common/ses_subr.c | 376 |
9 files changed, 2715 insertions, 0 deletions
diff --git a/usr/src/lib/scsi/libses/common/libses.h b/usr/src/lib/scsi/libses/common/libses.h new file mode 100644 index 0000000000..7e95cb4b3a --- /dev/null +++ b/usr/src/lib/scsi/libses/common/libses.h @@ -0,0 +1,132 @@ +/* + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _LIBSES_H +#define _LIBSES_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> + +#include <stdarg.h> +#include <libnvpair.h> +#include <pthread.h> + +#include <scsi/libscsi.h> +#include <scsi/plugins/ses/framework/ses2.h> +#include <scsi/plugins/ses/framework/libses.h> + +#define LIBSES_VERSION 1 + +typedef enum ses_node_type { + SES_NODE_NONE = 0x0, + SES_NODE_TARGET = 0x1, + SES_NODE_ENCLOSURE = 0x2, + SES_NODE_AGGREGATE = 0x4, + SES_NODE_ELEMENT = 0x8 +} ses_node_type_t; + +typedef enum ses_errno { + ESES_NONE, /* no error */ + ESES_NOMEM, /* no memory */ + ESES_ZERO_LENGTH, /* zero-length allocation requested */ + ESES_VERSION, /* library version mismatch */ + ESES_NVL, /* nvlist manipulation error */ + ESES_BAD_NODE, /* bad node */ + ESES_INVALID_OP, /* invalid operation */ + ESES_RANGE, /* value out of range */ + ESES_INVALID_PROP, /* nonexistent or immutable property */ + ESES_BAD_TYPE, /* incorrect property type */ + ESES_BAD_PAGE, /* bad page number */ + ESES_BAD_RESPONSE, /* bad response from target */ + ESES_BUSY, /* target busy */ + ESES_TOOMUCHCHANGE, /* target configuration changing too rapidly */ + ESES_LIBSCSI, /* SCSI error */ + ESES_NOTSUP, /* operation not supported */ + ESES_UNKNOWN, /* error of unknown type */ + ESES_CHANGED, /* generation count has changed */ + ESES_PLUGIN, /* invalid or missing plugin */ + ESES_MAX /* maximum libses errno value */ +} ses_errno_t; + +struct ses_target; +typedef struct ses_target ses_target_t; + +struct ses_snap; +typedef struct ses_snap ses_snap_t; + +struct ses_node; +typedef struct ses_node ses_node_t; + +extern ses_target_t *ses_open(uint_t, const char *); +extern ses_target_t *ses_open_scsi(uint_t, libscsi_target_t *); +extern void ses_close(ses_target_t *); + +extern libscsi_target_t *ses_scsi_target(ses_target_t *); + +typedef enum ses_walk_action { + SES_WALK_ACTION_CONTINUE, + SES_WALK_ACTION_PRUNE, + SES_WALK_ACTION_TERMINATE +} ses_walk_action_t; + +typedef ses_walk_action_t (*ses_walk_f)(ses_node_t *, void *); + +extern uint64_t ses_node_id(ses_node_t *); +extern ses_node_t *ses_node_lookup(ses_snap_t *, uint64_t); + +extern ses_node_t *ses_root_node(ses_snap_t *); +extern ses_node_t *ses_node_sibling(ses_node_t *); +extern ses_node_t *ses_node_prev_sibling(ses_node_t *); +extern ses_node_t *ses_node_child(ses_node_t *); +extern ses_node_t *ses_node_parent(ses_node_t *); +extern int ses_walk(ses_snap_t *, ses_walk_f, void *); + +extern ses_snap_t *ses_snap_hold(ses_target_t *); +extern void ses_snap_rele(ses_snap_t *); +extern ses_snap_t *ses_snap_new(ses_target_t *); +extern uint32_t ses_snap_generation(ses_snap_t *); + +extern ses_node_type_t ses_node_type(ses_node_t *); +extern nvlist_t *ses_node_props(ses_node_t *); +extern int ses_node_ctl(ses_node_t *, const char *, nvlist_t *); +extern ses_snap_t *ses_node_snapshot(ses_node_t *); +extern ses_target_t *ses_node_target(ses_node_t *); + +extern ses_errno_t ses_errno(void); +extern const char *ses_errmsg(void); +extern const char *ses_strerror(ses_errno_t); +extern const char *ses_nv_error_member(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBSES_H */ diff --git a/usr/src/lib/scsi/libses/common/libses_plugin.h b/usr/src/lib/scsi/libses/common/libses_plugin.h new file mode 100644 index 0000000000..7156da7bdf --- /dev/null +++ b/usr/src/lib/scsi/libses/common/libses_plugin.h @@ -0,0 +1,161 @@ +/* + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _LIBSES_PLUGIN_H +#define _LIBSES_PLUGIN_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif + +#define LIBSES_PLUGIN_VERSION 1 + +/* + * These are the primary APIs for plugins to interact with libses. + */ + +struct ses_plugin; +typedef struct ses_plugin ses_plugin_t; + +typedef enum { + SES_PAGE_DIAG, + SES_PAGE_CTL +} ses_pagetype_t; + +typedef struct ses_pagedesc { + int spd_pagenum; + size_t (*spd_ctl_len)(uint_t, int, size_t); + void *(*spd_ctl_fill)(ses_plugin_t *, void *, size_t, + ses_node_t *); + void *(*spd_index)(ses_plugin_t *, ses_node_t *, + void *, size_t, size_t *); + int spd_gcoff; +} ses_pagedesc_t; + +typedef struct ses_plugin_config { + ses_pagedesc_t *spc_pages; + int (*spc_node_parse)(ses_plugin_t *, ses_node_t *); + int (*spc_node_ctl)(ses_plugin_t *, ses_node_t *, + const char *, nvlist_t *); +} ses_plugin_config_t; + +extern int ses_plugin_register(ses_plugin_t *, int, ses_plugin_config_t *); + +extern void *ses_plugin_page_lookup(ses_plugin_t *, ses_snap_t *, int, + ses_node_t *, size_t *); + +extern void *ses_plugin_ctlpage_lookup(ses_plugin_t *, ses_snap_t *, int, + size_t, ses_node_t *, boolean_t); + +extern void ses_plugin_setspecific(ses_plugin_t *, void *); +extern void *ses_plugin_getspecific(ses_plugin_t *); + +/* + * The following are support functions provided by libses. + */ + +extern int ses_assert(const char *, const char *, int); + +#define VERIFY(x) ((void)((x) || ses_assert(#x, __FILE__, __LINE__))) + +#ifdef DEBUG +#define ASSERT(x) VERIFY(x) +#else +#define ASSERT(x) +#endif + +#define SES_NV_ADD(_t, _e, _l, _n, ...) \ + if (((_e) = nvlist_add_##_t((_l), (_n), __VA_ARGS__)) != 0) \ + return (ses_set_nverrno((_e), (_n))) + +#define SES_NV_ADD_OR_FREE(_t, _e, _l, _n, ...) \ + if (((_e) = nvlist_add_##_t((_l), (_n), __VA_ARGS__)) != 0) { \ + nvlist_free(_l); return (ses_set_nverrno((_e), (_n))); } + +#define SES_NV_ADD_FS(_e, _l, _name, _buf) \ + SES_NV_ADD(fixed_string, (_e), (_l), (_name), (_buf), sizeof (_buf)) + +#define SES_NV_ADD_FS_TRUNC(_e, _l, _name, _buf) \ + SES_NV_ADD(fixed_string_trunc, (_e), (_l), (_name), (_buf), \ + sizeof (_buf)) + +#define SES_NV_CTLBOOL(_l, _n, _b) \ + { \ + boolean_t v = B_FALSE; \ + (void) nvlist_lookup_boolean_value((_l), (_n), &v); \ + (_b) = v; \ + } + +#define SES_NV_CTLBOOL_INVERT(_l, _n, _b) \ + { \ + boolean_t v = B_FALSE; \ + (void) nvlist_lookup_boolean_value((_l), (_n), &v); \ + (_b) = !v; \ + } + +#define SES_NV_CTL64(_l, _n, _v) \ + { \ + uint64_t v = 0; \ + (void) nvlist_lookup_uint64((_l), (_n), &v); \ + (_v) = v; \ + } + +#define SES_NV_CTL16(_l, _n, _v) \ + { \ + uint16_t v = 0; \ + (void) nvlist_lookup_uint16((_l), (_n), &v); \ + SCSI_WRITE16(&(_v), v); \ + } + +extern void *ses_alloc(size_t); +extern void *ses_zalloc(size_t); +extern char *ses_strdup(const char *); +extern void *ses_realloc(void *, size_t); +extern void ses_free(void *); + +extern int ses_set_errno(ses_errno_t); +extern int ses_set_nverrno(int, const char *); +extern int ses_error(ses_errno_t, const char *, ...); +extern int ses_nverror(int, const char *, const char *, ...); +extern void ses_panic(const char *, ...) __NORETURN; + +extern int nvlist_add_fixed_string(nvlist_t *, const char *, + const char *, size_t); +extern int nvlist_add_fixed_string_trunc(nvlist_t *, const char *, + const char *, size_t); + +#define SES_WITHIN_PAGE(sp, size, data, len) \ + ((char *)(sp) <= (char *)(data) + (len) - (size)) +#define SES_WITHIN_PAGE_STRUCT(sp, data, len) \ + SES_WITHIN_PAGE((sp), sizeof (*(sp)), (data), (len)) + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBSES_PLUGIN_H */ diff --git a/usr/src/lib/scsi/libses/common/mkerrno.sh b/usr/src/lib/scsi/libses/common/mkerrno.sh new file mode 100644 index 0000000000..a796ddee67 --- /dev/null +++ b/usr/src/lib/scsi/libses/common/mkerrno.sh @@ -0,0 +1,86 @@ +#!/bin/sh +# +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#pragma ident "%Z%%M% %I% %E% SMI" + +echo "/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident\t\"%Z%%M%\t%I%\t%E% SMI\" + +#include <strings.h> +#include <scsi/libses.h> + +static const struct { +\tchar *se_name;\t\t/* error name */ +\tchar *se_msg;\t\t/* error message */ +} _ses_errstr[] = {" + +pattern='^ \(ESES_[A-Z0-9_]*\),*' +replace=' { "\1",' +open=' \/\* ' +openrepl='"' +close=' \*\/$' +closerepl='" },' + +( sed -n "s/$pattern/$replace/p" | sed -n "s/$open/$openrepl/p" | + sed -n "s/$close/$closerepl/p" ) || exit 1 + +echo "\ +};\n\ +\n\ +static int _ses_nerrno = sizeof (_ses_errstr) / sizeof (_ses_errstr[0]);\n\ +\n\ +const char * +ses_strerror(ses_errno_t err) +{ + return (err < 0 || err >= _ses_nerrno ? \"unknown error\" : + _ses_errstr[err].se_msg); +} + +const char * +ses_errname(ses_errno_t err) +{ + return (err < 0 || err >= _ses_nerrno ? NULL : + _ses_errstr[err].se_name); +} + +ses_errno_t +ses_errcode(const char *name) +{ + ses_errno_t err; + + for (err = 0; err < _ses_nerrno; err++) { + if (strcmp(name, _ses_errstr[err].se_name) == 0) + return (err); + } + + return (ESES_UNKNOWN); +}" + +exit 0 diff --git a/usr/src/lib/scsi/libses/common/ses_enclosure.c b/usr/src/lib/scsi/libses/common/ses_enclosure.c new file mode 100644 index 0000000000..4d2af5cc55 --- /dev/null +++ b/usr/src/lib/scsi/libses/common/ses_enclosure.c @@ -0,0 +1,293 @@ +/* + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <scsi/libses.h> +#include "ses_impl.h" + +int +enc_parse_td(ses2_td_hdr_impl_t *tip, const char *tp, nvlist_t *nvl) +{ + int nverr; + + if (tp != NULL) + SES_NV_ADD(fixed_string, nverr, nvl, SES_PROP_CLASS_DESCRIPTION, + tp, tip->sthi_text_len); + + return (0); +} + +static int +enc_eid(const ses2_ed_impl_t *tp, nvlist_t *nvl, const char *name) +{ + int nverr; + + SES_NV_ADD(uint64, nverr, nvl, name, tp->st_hdr.sehi_subenclosure_id); + + return (0); +} + +static int +enc_espid(const ses2_ed_impl_t *tp, nvlist_t *nvl, const char *name) +{ + int nverr; + + SES_NV_ADD(uint64, nverr, nvl, name, tp->st_hdr.sehi_rel_esp_id); + + return (0); +} + +static int +enc_nesp(const ses2_ed_impl_t *tp, nvlist_t *nvl, const char *name) +{ + int nverr; + + SES_NV_ADD(uint64, nverr, nvl, name, tp->st_hdr.sehi_n_esps); + + return (0); +} + +static int +enc_lid(const ses2_ed_impl_t *tp, nvlist_t *nvl, const char *name) +{ + nvlist_t *lid; + int nverr; + + if ((nverr = nvlist_alloc(&lid, NV_UNIQUE_NAME, 0)) != 0) + return (ses_set_nverrno(nverr, NULL)); + + SES_NV_ADD_OR_FREE(uint64, nverr, lid, SPC3_NAA_INT, + SCSI_READ64(&tp->st_logical_id)); + + switch (tp->st_logical_id.sni8i_naa) { + case NAA_IEEE_EXT: + SES_NV_ADD_OR_FREE(uint64, nverr, lid, SPC3_NAA_ID_TYPE, + NAA_IEEE_EXT); + SES_NV_ADD_OR_FREE(uint64, nverr, lid, SPC3_NAA_COMPANY_ID, + NAA_IEEE_EXT_COMPANY_ID(&tp->st_logical_id.sni8i_ext_id)); + SES_NV_ADD_OR_FREE(uint64, nverr, lid, SPC3_NAA_VS_A, + NAA_IEEE_EXT_VENDOR_A(&tp->st_logical_id.sni8i_ext_id)); + SES_NV_ADD_OR_FREE(uint64, nverr, lid, SPC3_NAA_VS_B, + NAA_IEEE_EXT_VENDOR_B(&tp->st_logical_id.sni8i_ext_id)); + break; + case NAA_IEEE_REG: + SES_NV_ADD_OR_FREE(uint64, nverr, lid, SPC3_NAA_ID_TYPE, + NAA_IEEE_REG); + SES_NV_ADD_OR_FREE(uint64, nverr, lid, SPC3_NAA_COMPANY_ID, + NAA_IEEE_REG_COMPANY_ID(&tp->st_logical_id.sni8i_reg_id)); + SES_NV_ADD_OR_FREE(uint64, nverr, lid, SPC3_NAA_VS_A, + NAA_IEEE_REG_VENDOR_ID(&tp->st_logical_id.sni8i_reg_id)); + break; + default: + break; + } + + if ((nverr = nvlist_add_nvlist(nvl, name, lid)) != 0) { + nvlist_free(lid); + return (ses_set_nverrno(nverr, name)); + } + + nvlist_free(lid); + + return (0); +} + +static int +enc_vid(const ses2_ed_impl_t *tp, nvlist_t *nvl, + const char *name) +{ + int nverr; + + SES_NV_ADD_FS_TRUNC(nverr, nvl, name, tp->st_vendor_id); + + return (0); +} + +static int +enc_pid(const ses2_ed_impl_t *tp, nvlist_t *nvl, + const char *name) +{ + int nverr; + + SES_NV_ADD_FS_TRUNC(nverr, nvl, name, tp->st_product_id); + + return (0); +} + +static int +enc_rev(const ses2_ed_impl_t *tp, nvlist_t *nvl, + const char *name) +{ + int nverr; + + SES_NV_ADD_FS_TRUNC(nverr, nvl, name, tp->st_product_revision); + + return (0); +} + +static int +enc_vs(const ses2_ed_impl_t *tp, nvlist_t *nvl, const char *name) +{ + int nverr; + + SES_NV_ADD(byte_array, nverr, nvl, name, (uchar_t *)tp->st_priv, + tp->st_hdr.sehi_ed_len - offsetof(ses2_ed_impl_t, st_priv[0])); + + return (0); +} + +/* LINTED - unused */ +static const ses2_ed_impl_t __ed = { 0 }; + +#define ED_REQ_LEN(member) \ + (offsetof(ses2_ed_impl_t, member) - sizeof (ses2_ed_hdr_impl_t) + \ + sizeof (__ed.member)) + +static const struct config_member { + const char *name; + size_t minsz; + int (*func)(const ses2_ed_impl_t *, nvlist_t *, const char *); +} config_members[] = { + { SES_EN_PROP_EID, 0, enc_eid }, + { SES_EN_PROP_ESPID, 0, enc_espid }, + { SES_EN_PROP_NESP, 0, enc_nesp }, + { SES_EN_PROP_LID, ED_REQ_LEN(st_logical_id), enc_lid }, + { SES_EN_PROP_VID, ED_REQ_LEN(st_vendor_id), enc_vid }, + { SES_EN_PROP_PID, ED_REQ_LEN(st_product_id), enc_pid }, + { SES_EN_PROP_REV, ED_REQ_LEN(st_product_revision), enc_rev }, + { SES_EN_PROP_VS, ED_REQ_LEN(st_priv), enc_vs }, + { NULL, 0, NULL } +}; + +int +enc_parse_ed(ses2_ed_impl_t *tp, nvlist_t *nvl) +{ + const struct config_member *mp; + int err; + + if (tp == NULL) + return (0); + + for (mp = &config_members[0]; mp->name != NULL; mp++) { + if (mp->func != NULL && tp->st_hdr.sehi_ed_len >= mp->minsz) { + err = mp->func(tp, nvl, mp->name); + if (err != 0) + return (err); + } + } + + return (0); +} + +ses_target_t * +ses_open_scsi(uint_t version, libscsi_target_t *stp) +{ + ses_target_t *tp; + ses_snap_t *sp; + + if (version != LIBSES_VERSION) { + (void) ses_set_errno(ESES_VERSION); + return (NULL); + } + + if ((tp = ses_zalloc(sizeof (ses_target_t))) == NULL) + return (NULL); + + tp->st_target = stp; + tp->st_scsi_hdl = libscsi_get_handle(stp); + tp->st_truncate = (getenv("LIBSES_TRUNCATE") != NULL); + if (tp->st_truncate) + srand48(gethrtime()); + + (void) pthread_mutex_init(&tp->st_lock, NULL); + + if (ses_plugin_load(tp) != 0) { + ses_close(tp); + return (NULL); + } + + if ((sp = ses_snap_new(tp)) == NULL) { + ses_close(tp); + return (NULL); + } + + ses_snap_rele(sp); + + return (tp); +} + +ses_target_t * +ses_open(uint_t version, const char *target) +{ + ses_target_t *tp; + libscsi_errno_t serr; + libscsi_target_t *stp; + libscsi_hdl_t *hp; + + if ((hp = libscsi_init(LIBSCSI_VERSION, &serr)) == NULL) { + (void) ses_error(ESES_LIBSCSI, "failed to initialize " + "libscsi: %s", libscsi_strerror(serr)); + return (NULL); + } + + if ((stp = libscsi_open(hp, NULL, target)) == NULL) { + (void) ses_libscsi_error(hp, "failed to open SES target"); + libscsi_fini(hp); + return (NULL); + } + + if ((tp = ses_open_scsi(version, stp)) == NULL) { + libscsi_close(hp, stp); + libscsi_fini(hp); + return (NULL); + } + + tp->st_closescsi = B_TRUE; + + return (tp); +} + +libscsi_target_t * +ses_scsi_target(ses_target_t *tp) +{ + return (tp->st_target); +} + +void +ses_close(ses_target_t *tp) +{ + if (tp->st_snapshots != NULL) + ses_snap_rele(tp->st_snapshots); + if (tp->st_snapshots != NULL) + ses_panic("attempt to close SES target with active snapshots"); + ses_plugin_unload(tp); + if (tp->st_closescsi) { + libscsi_close(tp->st_scsi_hdl, tp->st_target); + libscsi_fini(tp->st_scsi_hdl); + } + ses_free(tp); +} diff --git a/usr/src/lib/scsi/libses/common/ses_impl.h b/usr/src/lib/scsi/libses/common/ses_impl.h new file mode 100644 index 0000000000..a820f31787 --- /dev/null +++ b/usr/src/lib/scsi/libses/common/ses_impl.h @@ -0,0 +1,170 @@ +/* + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SES_IMPL_H +#define _SES_IMPL_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif + +#include <alloca.h> +#include <errno.h> +#include <assert.h> +#include <dirent.h> +#include <dlfcn.h> +#include <limits.h> +#include <pthread.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <libnvpair.h> +#include <unistd.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/sysmacros.h> +#include <sys/systeminfo.h> + +#include <scsi/libscsi.h> +#include <scsi/libses_plugin.h> +#include <scsi/plugins/ses/framework/ses2_impl.h> + +#define LIBSES_ERRMSGLEN 512 + +#define LIBSES_DEFAULT_PLUGINDIR "/usr/lib/scsi/plugins/ses" +#define LIBSES_PLUGIN_FRAMEWORK "framework" +#define LIBSES_PLUGIN_VENDOR "vendor" + +#define LIBSES_PLUGIN_EXT ".so" + +struct ses_plugin { + struct ses_plugin *sp_next; /* next plugin in list */ + struct ses_plugin *sp_prev; /* previous plugin in list */ + uint64_t sp_priority; /* plugin priority */ + struct ses_target *sp_target; /* corresponding target */ + void *sp_object; /* shared object */ + void *sp_data; /* module-specific data */ + boolean_t sp_initialized; /* successfully initialized */ + ses_pagedesc_t *sp_pages; /* pages */ + int (*sp_init)(ses_plugin_t *); /* plugin init */ + void (*sp_fini)(ses_plugin_t *); /* plugin fini */ + int (*sp_node_parse)(ses_plugin_t *, ses_node_t *); /* parse node */ + int (*sp_node_ctl)(ses_plugin_t *, ses_node_t *, const char *, + nvlist_t *); /* node control */ +}; + +struct ses_target { + libscsi_hdl_t *st_scsi_hdl; + libscsi_target_t *st_target; + struct ses_plugin *st_plugin_first; + struct ses_plugin *st_plugin_last; + struct ses_snap *st_snapshots; + boolean_t st_closescsi; + boolean_t st_truncate; + pthread_mutex_t st_lock; +}; + +/* + * Maximum number of snapshot retries triggered by generation count changes + */ +#define LIBSES_MAX_GC_RETRIES 10 + +/* + * Maximum number of Enclosure Busy retries + */ +#define LIBSES_MAX_BUSY_RETRIES 3 + +typedef struct ses_snap_page { + ses2_diag_page_t ssp_num; + boolean_t ssp_control; + boolean_t ssp_initialized; + size_t ssp_alloc; + size_t ssp_len; + void *ssp_page; + char *ssp_mmap_base; + size_t ssp_mmap_len; + struct ses_snap_page *ssp_next; + struct ses_snap_page *ssp_unique; +} ses_snap_page_t; + +struct ses_snap { + struct ses_target *ss_target; + uint32_t ss_generation; + hrtime_t ss_time; + struct ses_node *ss_root; + size_t ss_n_elem; + ses_snap_page_t *ss_pages; + size_t ss_n_nodes; + struct ses_node **ss_nodes; + struct ses_snap *ss_next; + struct ses_snap *ss_prev; + uint32_t ss_refcnt; +}; + +struct ses_node { + ses_node_type_t sn_type; + uint64_t sn_rootidx; /* Relative index for enclosure/aggregate */ + size_t sn_id; /* Unique global ID */ + uint64_t sn_enc_num; + struct ses_snap *sn_snapshot; + struct ses_node *sn_parent; + struct ses_node *sn_next_sibling; + struct ses_node *sn_prev_sibling; + struct ses_node *sn_first_child; + struct ses_node *sn_last_child; + nvlist_t *sn_props; +}; + +extern int ses_fill_snap(ses_snap_t *); +extern void ses_node_teardown(ses_node_t *); +extern ses_snap_page_t *ses_snap_find_page(ses_snap_t *, ses2_diag_page_t, + boolean_t); +extern ses_snap_page_t *ses_snap_ctl_page(ses_snap_t *, + ses2_diag_page_t, size_t, boolean_t); +extern int ses_snap_do_ctl(ses_snap_t *); + +extern int ses_libscsi_error(libscsi_hdl_t *, const char *, ...); +extern int ses_scsi_error(libscsi_action_t *, const char *, ...); + +extern int ses_plugin_load(ses_target_t *); +extern void ses_plugin_unload(ses_target_t *); + +extern ses_pagedesc_t *ses_get_pagedesc(ses_target_t *, int, ses_pagetype_t); +extern int ses_fill_node(ses_node_t *); + +extern int enc_parse_ed(ses2_ed_impl_t *, nvlist_t *); +extern int enc_parse_td(ses2_td_hdr_impl_t *, const char *, nvlist_t *); + +#ifdef __cplusplus +} +#endif + +#endif /* _SES_IMPL_H */ diff --git a/usr/src/lib/scsi/libses/common/ses_node.c b/usr/src/lib/scsi/libses/common/ses_node.c new file mode 100644 index 0000000000..6e08a20874 --- /dev/null +++ b/usr/src/lib/scsi/libses/common/ses_node.c @@ -0,0 +1,399 @@ +/* + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <scsi/libses.h> +#include "ses_impl.h" + +#define NEXT_ED(eip) \ + ((ses2_ed_impl_t *)((uint8_t *)(eip) + \ + ((eip)->st_hdr.sehi_ed_len + sizeof (ses2_ed_hdr_impl_t)))) + +static ses_node_t * +ses_find_enclosure(ses_snap_t *sp, uint64_t number) +{ + ses_node_t *np; + + for (np = sp->ss_root->sn_first_child; np != NULL; + np = np->sn_next_sibling) { + ASSERT(np->sn_type == SES_NODE_ENCLOSURE); + if (np->sn_enc_num == number) + return ((ses_node_t *)np); + } + + return (NULL); +} + +void +ses_node_teardown(ses_node_t *np) +{ + ses_node_t *rp; + + if (np == NULL) + return; + + for (; np != NULL; np = rp) { + ses_node_teardown(np->sn_first_child); + rp = np->sn_next_sibling; + nvlist_free(np->sn_props); + ses_free(np); + } +} + +static ses_node_t * +ses_node_alloc(ses_snap_t *sp, ses_node_t *pnp) +{ + ses_node_t *np; + + np = ses_zalloc(sizeof (ses_node_t)); + if (np == NULL) + goto fail; + if (nvlist_alloc(&np->sn_props, NV_UNIQUE_NAME, 0) != 0) + goto fail; + + np->sn_snapshot = sp; + np->sn_id = sp->ss_n_nodes++; + + if (pnp == NULL) { + ASSERT(sp->ss_root == NULL); + sp->ss_root = np; + } else { + np->sn_parent = pnp; + np->sn_prev_sibling = pnp->sn_last_child; + + if (pnp->sn_first_child == NULL) + pnp->sn_first_child = np; + else + pnp->sn_last_child->sn_next_sibling = np; + + pnp->sn_last_child = np; + } + + return (np); + +fail: + ses_free(np); + ses_node_teardown(sp->ss_root); + sp->ss_root = NULL; + return (NULL); +} + +/* + * Parse element type descriptor. + */ +static int +elem_parse_td(ses2_td_hdr_impl_t *tip, const char *tp, nvlist_t *nvl) +{ + int nverr; + + if (tp != NULL) + SES_NV_ADD(fixed_string, nverr, nvl, SES_PROP_CLASS_DESCRIPTION, + tp, tip->sthi_text_len); + + return (0); +} + + +/* + * Build a skeleton tree of nodes in the given snapshot. This is the heart of + * libses, and is responsible for parsing the config page into a tree and + * populating nodes with data from the config page. + */ +static int +ses_build_snap_skel(ses_snap_t *sp) +{ + ses2_ed_impl_t *eip; + ses2_td_hdr_impl_t *tip, *ftip; + ses_node_t *np, *pnp, *cnp, *root; + ses_snap_page_t *pp; + ses2_config_page_impl_t *pip; + int i, j, n_etds = 0; + off_t toff; + char *tp, *text; + int err; + uint64_t idx; + + pp = ses_snap_find_page(sp, SES2_DIAGPAGE_CONFIG, B_FALSE); + if (pp == NULL) + return (ses_error(ESES_BAD_RESPONSE, "target does not support " + "configuration diagnostic page")); + pip = (ses2_config_page_impl_t *)pp->ssp_page; + + if (pp->ssp_len < offsetof(ses2_config_page_impl_t, scpi_data)) + return (ses_error(ESES_BAD_RESPONSE, "no enclosure " + "descriptors found")); + + /* + * Start with the root of the tree, which is a target node, containing + * just the SCSI inquiry properties. + */ + if ((root = ses_node_alloc(sp, sp->ss_root)) == NULL) + return (-1); + + root->sn_type = SES_NODE_TARGET; + SES_NV_ADD(string, err, root->sn_props, SCSI_PROP_VENDOR, + libscsi_vendor(sp->ss_target->st_target)); + SES_NV_ADD(string, err, root->sn_props, SCSI_PROP_PRODUCT, + libscsi_product(sp->ss_target->st_target)); + SES_NV_ADD(string, err, root->sn_props, SCSI_PROP_REVISION, + libscsi_revision(sp->ss_target->st_target)); + + for (eip = (ses2_ed_impl_t *)pip->scpi_data, i = 0; + i < pip->scpi_n_subenclosures + 1; + i++, eip = NEXT_ED(eip)) { + if (!SES_WITHIN_PAGE_STRUCT(eip, pp->ssp_page, pp->ssp_len)) + break; + + n_etds += eip->st_hdr.sehi_n_etd_hdrs; + } + ftip = (ses2_td_hdr_impl_t *)eip; + + /* + * There should really be only one Enclosure element possible for a + * give subenclosure ID. The standard never comes out and says this, + * but it does describe this element as "managing the enclosure itself" + * which implies rather strongly that the subenclosure ID field is that + * of, well, the enclosure itself. Since an enclosure can't contain + * itself, it follows logically that each subenclosure has at most one + * Enclosure type descriptor elements matching its ID. Of course, some + * enclosure firmware is buggy, so this may not always work out; in + * this case we just ignore all but the first Enclosure-type element + * with our subenclosure ID. + */ + for (eip = (ses2_ed_impl_t *)pip->scpi_data, i = 0; + i < pip->scpi_n_subenclosures + 1; + i++, eip = NEXT_ED(eip)) { + if (!SES_WITHIN_PAGE_STRUCT(eip, pp->ssp_page, pp->ssp_len)) + break; + + if ((np = ses_node_alloc(sp, root)) == NULL) + return (-1); + + np->sn_type = SES_NODE_ENCLOSURE; + np->sn_enc_num = eip->st_hdr.sehi_subenclosure_id; + + if (!SES_WITHIN_PAGE(eip, eip->st_hdr.sehi_ed_len + + sizeof (ses2_ed_hdr_impl_t), + pp->ssp_page, pp->ssp_len)) + break; + + if (enc_parse_ed(eip, np->sn_props) != 0) + return (-1); + } + + if (root->sn_first_child == NULL) + return (ses_error(ESES_BAD_RESPONSE, "no enclosure " + "descriptors found")); + + tp = (char *)(ftip + n_etds); + + for (i = 0, toff = 0, idx = 0; i < n_etds; i++) { + tip = ftip + i; + + if (!SES_WITHIN_PAGE_STRUCT(tip, pp->ssp_page, pp->ssp_len)) + break; + + pnp = ses_find_enclosure(sp, + tip->sthi_subenclosure_id); + if (pnp == NULL) { + idx += tip->sthi_max_elements + 1; + toff += tip->sthi_text_len; + continue; + } + + if (tip->sthi_element_type == SES_ET_ENCLOSURE) { + if (tip->sthi_max_elements == 0) { + SES_NV_ADD(uint64, err, pnp->sn_props, + SES_PROP_ELEMENT_INDEX, idx); + pnp->sn_rootidx = idx; + } else { + SES_NV_ADD(uint64, err, pnp->sn_props, + SES_PROP_ELEMENT_INDEX, idx + 1); + pnp->sn_rootidx = idx + 1; + } + + if (tip->sthi_text_len > 0 && + SES_WITHIN_PAGE(tp + toff, tip->sthi_text_len, + pp->ssp_page, pp->ssp_len)) { + text = tp + toff; + toff += tip->sthi_text_len; + } else { + text = NULL; + } + + SES_NV_ADD(uint64, err, pnp->sn_props, + SES_PROP_ELEMENT_TYPE, SES_ET_ENCLOSURE); + if (enc_parse_td(tip, text, pnp->sn_props) != 0) + return (-1); + + idx += tip->sthi_max_elements + 1; + continue; + } + + if ((np = ses_node_alloc(sp, pnp)) == NULL) + return (-1); + + np->sn_type = SES_NODE_AGGREGATE; + np->sn_enc_num = tip->sthi_subenclosure_id; + np->sn_parent = pnp; + np->sn_rootidx = idx; + + SES_NV_ADD(uint64, err, np->sn_props, + SES_PROP_ELEMENT_INDEX, idx); + SES_NV_ADD(uint64, err, np->sn_props, + SES_PROP_ELEMENT_TYPE, tip->sthi_element_type); + + if (tip->sthi_text_len > 0 && + SES_WITHIN_PAGE(tp + toff, tip->sthi_text_len, + pp->ssp_page, pp->ssp_len)) { + text = tp + toff; + toff += tip->sthi_text_len; + } else { + text = NULL; + } + + if (elem_parse_td(tip, text, np->sn_props) != 0) + return (-1); + + idx += tip->sthi_max_elements + 1; + + if (tip->sthi_max_elements == 0) + continue; + + for (j = 0; j < tip->sthi_max_elements; j++) { + cnp = ses_node_alloc(sp, np); + if (cnp == NULL) + return (-1); + + cnp->sn_type = SES_NODE_ELEMENT; + SES_NV_ADD(uint64, err, cnp->sn_props, + SES_PROP_ELEMENT_INDEX, np->sn_rootidx + j + 1); + SES_NV_ADD(uint64, err, cnp->sn_props, + SES_PROP_ELEMENT_CLASS_INDEX, j); + SES_NV_ADD(uint64, err, cnp->sn_props, + SES_PROP_ELEMENT_TYPE, tip->sthi_element_type); + } + } + + np->sn_snapshot->ss_n_elem = idx; + + return (0); +} + +static int +ses_fill_tree(ses_node_t *np) +{ + if (np == NULL) + return (0); + + for (; np != NULL; np = np->sn_next_sibling) { + if (ses_fill_node(np) != 0) + return (-1); + if (ses_fill_tree(np->sn_first_child) != 0) + return (-1); + } + + return (0); +} + +int +ses_fill_snap(ses_snap_t *sp) +{ + if (ses_build_snap_skel(sp) != 0) + return (-1); + + if (ses_fill_tree(sp->ss_root) != 0) + return (-1); + + return (0); +} + +ses_node_t * +ses_root_node(ses_snap_t *sp) +{ + return (sp->ss_root); +} + +ses_node_t * +ses_node_sibling(ses_node_t *np) +{ + return (np->sn_next_sibling); +} + +ses_node_t * +ses_node_prev_sibling(ses_node_t *np) +{ + return (np->sn_prev_sibling); +} + +ses_node_t * +ses_node_parent(ses_node_t *np) +{ + return (np->sn_parent); +} + +ses_node_t * +ses_node_child(ses_node_t *np) +{ + return (np->sn_first_child); +} + +ses_node_type_t +ses_node_type(ses_node_t *np) +{ + return (np->sn_type); +} + +ses_snap_t * +ses_node_snapshot(ses_node_t *np) +{ + return ((ses_snap_t *)np->sn_snapshot); +} + +ses_target_t * +ses_node_target(ses_node_t *np) +{ + return (np->sn_snapshot->ss_target); +} + +nvlist_t * +ses_node_props(ses_node_t *np) +{ + return (np->sn_props); +} + +/* + * A node identifier is a (generation, index) tuple that can be used to lookup a + * node within this target at a later point. This will be valid across + * snapshots, though it will return failure if the generation count has changed. + */ +uint64_t +ses_node_id(ses_node_t *np) +{ + return (((uint64_t)np->sn_snapshot->ss_generation << 32) | + np->sn_id); +} diff --git a/usr/src/lib/scsi/libses/common/ses_plugin.c b/usr/src/lib/scsi/libses/common/ses_plugin.c new file mode 100644 index 0000000000..e419a74475 --- /dev/null +++ b/usr/src/lib/scsi/libses/common/ses_plugin.c @@ -0,0 +1,398 @@ +/* + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <scsi/libses.h> +#include "ses_impl.h" + +static boolean_t ses_plugin_dlclose; + +/*ARGSUSED*/ +void * +ses_plugin_ctlpage_lookup(ses_plugin_t *sp, ses_snap_t *snap, int pagenum, + size_t len, ses_node_t *np, boolean_t unique) +{ + ses_target_t *tp = snap->ss_target; + ses_snap_page_t *pp; + ses_pagedesc_t *dp; + + if ((pp = ses_snap_ctl_page(snap, pagenum, len, unique)) == NULL) + return (NULL); + + if ((dp = ses_get_pagedesc(tp, pagenum, SES_PAGE_CTL)) == NULL) + return (NULL); + + if (dp->spd_ctl_fill != NULL) { + return (dp->spd_ctl_fill(sp, pp->ssp_page, + pp->ssp_len, np)); + } else { + return (pp->ssp_page); + } +} + +int +ses_fill_node(ses_node_t *np) +{ + ses_target_t *tp = np->sn_snapshot->ss_target; + ses_plugin_t *sp; + + for (sp = tp->st_plugin_first; sp != NULL; sp = sp->sp_next) { + if (sp->sp_node_parse == NULL) + continue; + + if (sp->sp_node_parse(sp, np) != 0) + return (-1); + } + + return (0); +} + +int +ses_node_ctl(ses_node_t *np, const char *op, nvlist_t *arg) +{ + ses_target_t *tp = np->sn_snapshot->ss_target; + ses_plugin_t *sp; + nvlist_t *nvl; + nvpair_t *nvp; + int ret; + + if (nvlist_dup(arg, &nvl, 0) != 0) + return (ses_set_errno(ESES_NOMEM)); + + /* + * Technically we could get away with a per-snapshot lock while we fill + * the control page contents, but this doesn't take much time and we + * want actual control operations to be protected per-target, so we just + * take the target lock. + */ + (void) pthread_mutex_lock(&tp->st_lock); + + /* + * We walk the list of plugins backwards, so that a product-specific + * plugin can rewrite the nvlist to control operations in terms of the + * standard mechanisms, if desired. + */ + for (sp = tp->st_plugin_first; sp != NULL; sp = sp->sp_next) { + if (sp->sp_node_ctl == NULL) + continue; + + if (sp->sp_node_ctl(sp, np, op, nvl) != 0) { + nvlist_free(nvl); + (void) pthread_mutex_unlock(&tp->st_lock); + return (-1); + } + } + + if ((nvp = nvlist_next_nvpair(nvl, NULL)) != NULL) { + (void) ses_error(ESES_NOTSUP, "property '%s' invalid for " + "this node", nvpair_name(nvp)); + nvlist_free(nvl); + (void) pthread_mutex_unlock(&tp->st_lock); + return (-1); + } + + nvlist_free(nvl); + + ret = ses_snap_do_ctl(np->sn_snapshot); + (void) pthread_mutex_unlock(&tp->st_lock); + + return (ret); +} + +/*ARGSUSED*/ +void * +ses_plugin_page_lookup(ses_plugin_t *sp, ses_snap_t *snap, int pagenum, + ses_node_t *np, size_t *lenp) +{ + ses_snap_page_t *pp; + ses_target_t *tp = sp->sp_target; + ses_pagedesc_t *dp; + + if ((dp = ses_get_pagedesc(tp, pagenum, SES_PAGE_DIAG)) == NULL) + return (NULL); + + if ((pp = ses_snap_find_page(snap, pagenum, B_FALSE)) == NULL) + return (NULL); + + if (dp->spd_index != NULL) { + return (dp->spd_index(sp, np, pp->ssp_page, pp->ssp_len, + lenp)); + } else { + *lenp = pp->ssp_len; + return (pp->ssp_page); + } +} + +ses_pagedesc_t * +ses_get_pagedesc(ses_target_t *tp, int pagenum, ses_pagetype_t type) +{ + ses_plugin_t *sp; + ses_pagedesc_t *dp; + + for (sp = tp->st_plugin_first; sp != NULL; sp = sp->sp_next) { + if (sp->sp_pages == NULL) + continue; + + for (dp = &sp->sp_pages[0]; dp->spd_pagenum != -1; + dp++) { + if ((type == SES_PAGE_CTL && dp->spd_ctl_len == NULL) || + (type == SES_PAGE_DIAG && dp->spd_ctl_len != NULL)) + continue; + + if (dp->spd_pagenum == pagenum) + return (dp); + } + } + + (void) ses_error(ESES_BAD_PAGE, "failed to find page 0x%x", pagenum); + return (NULL); +} + +int +ses_plugin_register(ses_plugin_t *sp, int version, ses_plugin_config_t *scp) +{ + if (version != LIBSES_PLUGIN_VERSION) + return (ses_set_errno(ESES_VERSION)); + + sp->sp_pages = scp->spc_pages; + sp->sp_node_parse = scp->spc_node_parse; + sp->sp_node_ctl = scp->spc_node_ctl; + + return (0); +} + +void +ses_plugin_setspecific(ses_plugin_t *sp, void *data) +{ + sp->sp_data = data; +} + +void * +ses_plugin_getspecific(ses_plugin_t *sp) +{ + return (sp->sp_data); +} + +static void +ses_plugin_cleanstr(char *s) +{ + while (*s != '\0') { + if (*s == ' ' || *s == '/') + *s = '-'; + s++; + } +} + +static void +ses_plugin_destroy(ses_plugin_t *sp) +{ + if (sp->sp_initialized && sp->sp_fini != NULL) + sp->sp_fini(sp); + + if (!ses_plugin_dlclose) + (void) dlclose(sp->sp_object); + + ses_free(sp); +} + +static int +ses_plugin_loadone(ses_target_t *tp, const char *path, uint32_t pass) +{ + ses_plugin_t *sp, **loc; + void *obj; + int (*ses_priority)(void); + + if ((obj = dlopen(path, RTLD_PARENT | RTLD_LOCAL | RTLD_LAZY)) == NULL) + return (0); + + if ((sp = ses_zalloc(sizeof (ses_plugin_t))) == NULL) { + (void) dlclose(obj); + return (-1); + } + + sp->sp_object = obj; + sp->sp_init = (int (*)())dlsym(obj, "_ses_init"); + sp->sp_fini = (void (*)())dlsym(obj, "_ses_fini"); + sp->sp_target = tp; + + if (sp->sp_init == NULL) { + ses_plugin_destroy(sp); + return (0); + } + + /* + * Framework modules can establish an explicit prioritying by declaring + * the '_ses_priority' symbol, which returns an integer used to create + * an explicit ordering between plugins. + */ + if ((ses_priority = (int (*)())dlsym(obj, "_ses_priority")) != NULL) + sp->sp_priority = ses_priority(); + + sp->sp_priority |= (uint64_t)pass << 32; + + for (loc = &tp->st_plugin_first; *loc != NULL; loc = &(*loc)->sp_next) { + if ((*loc)->sp_priority > sp->sp_priority) + break; + } + + if (*loc != NULL) + (*loc)->sp_prev = sp; + else + tp->st_plugin_last = sp; + + sp->sp_next = *loc; + *loc = sp; + + if (sp->sp_init(sp) != 0) + return (-1); + sp->sp_initialized = B_TRUE; + + return (0); +} + +static int +ses_plugin_load_dir(ses_target_t *tp, const char *pluginroot) +{ + char path[PATH_MAX]; + DIR *dirp; + struct dirent64 *dp; + char *vendor, *product, *revision; + char isa[257]; + + (void) snprintf(path, sizeof (path), "%s/%s", + pluginroot, LIBSES_PLUGIN_FRAMEWORK); + +#if defined(_LP64) + if (sysinfo(SI_ARCHITECTURE_64, isa, sizeof (isa)) < 0) + isa[0] = '\0'; +#else + isa[0] = '\0'; +#endif + + if ((dirp = opendir(path)) != NULL) { + while ((dp = readdir64(dirp)) != NULL) { + if (strcmp(dp->d_name, ".") == 0 || + strcmp(dp->d_name, "..") == 0) + continue; + + (void) snprintf(path, sizeof (path), "%s/%s/%s/%s", + pluginroot, LIBSES_PLUGIN_FRAMEWORK, + isa, dp->d_name); + + if (ses_plugin_loadone(tp, path, 0) != 0) { + (void) closedir(dirp); + return (-1); + } + } + + (void) closedir(dirp); + } + + /* + * Create a local copy of the vendor/product/revision, strip out any + * questionable characters, and then attempt to load each plugin. + */ + vendor = alloca(strlen(libscsi_vendor(tp->st_target)) + 1); + product = alloca(strlen(libscsi_product(tp->st_target)) + 1); + revision = alloca(strlen(libscsi_revision(tp->st_target)) + 1); + (void) strcpy(vendor, libscsi_vendor(tp->st_target)); + (void) strcpy(product, libscsi_product(tp->st_target)); + (void) strcpy(revision, libscsi_revision(tp->st_target)); + + ses_plugin_cleanstr(vendor); + ses_plugin_cleanstr(product); + ses_plugin_cleanstr(revision); + + (void) snprintf(path, sizeof (path), "%s/%s/%s/%s%s", pluginroot, + LIBSES_PLUGIN_VENDOR, isa, vendor, + LIBSES_PLUGIN_EXT); + if (ses_plugin_loadone(tp, path, 1) != 0) + return (-1); + + (void) snprintf(path, sizeof (path), "%s/%s/%s/%s-%s%s", pluginroot, + LIBSES_PLUGIN_VENDOR, isa, vendor, product, + LIBSES_PLUGIN_EXT); + if (ses_plugin_loadone(tp, path, 2) != 0) + return (-1); + + (void) snprintf(path, sizeof (path), "%s/%s/%s/%s-%s-%s%s", pluginroot, + LIBSES_PLUGIN_VENDOR, isa, vendor, product, + revision, LIBSES_PLUGIN_EXT); + if (ses_plugin_loadone(tp, path, 3) != 0) + return (-1); + + return (0); +} + +int +ses_plugin_load(ses_target_t *tp) +{ + char pluginroot[PATH_MAX]; + const char *pluginpath, *p, *q; + + if ((pluginpath = getenv("SES_PLUGINPATH")) == NULL) + pluginpath = LIBSES_DEFAULT_PLUGINDIR; + ses_plugin_dlclose = (getenv("SES_NODLCLOSE") == NULL); + + for (p = pluginpath, q = strchr(p, ':'); p != NULL; p = q) { + if (q != NULL) { + ptrdiff_t len = q - p; + (void) strncpy(pluginroot, p, len); + pluginroot[len] = '\0'; + while (*q == ':') + ++q; + if (*q == '\0') + q = NULL; + if (len == 0) + continue; + } else { + (void) strcpy(pluginroot, p); + } + + if (pluginroot[0] != '/') + continue; + + if (ses_plugin_load_dir(tp, pluginpath) != 0) + return (-1); + } + + if (tp->st_plugin_first == NULL) + return (ses_error(ESES_PLUGIN, "no plugins found")); + + return (0); +} + +void +ses_plugin_unload(ses_target_t *tp) +{ + ses_plugin_t *sp; + + while ((sp = tp->st_plugin_first) != NULL) { + tp->st_plugin_first = sp->sp_next; + ses_plugin_destroy(sp); + } +} diff --git a/usr/src/lib/scsi/libses/common/ses_snap.c b/usr/src/lib/scsi/libses/common/ses_snap.c new file mode 100644 index 0000000000..78e7927061 --- /dev/null +++ b/usr/src/lib/scsi/libses/common/ses_snap.c @@ -0,0 +1,700 @@ +/* + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <scsi/libses.h> +#include "ses_impl.h" + +ses_snap_page_t * +ses_snap_find_page(ses_snap_t *sp, ses2_diag_page_t page, boolean_t ctl) +{ + ses_snap_page_t *pp; + + for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) + if (pp->ssp_num == page && pp->ssp_control == ctl && + (pp->ssp_len > 0 || pp->ssp_control)) + return (pp); + + return (NULL); +} + +static int +grow_snap_page(ses_snap_page_t *pp, size_t min) +{ + uint8_t *newbuf; + + if (min == 0 || min < pp->ssp_alloc) + min = pp->ssp_alloc * 2; + + if ((newbuf = ses_realloc(pp->ssp_page, min)) == NULL) + return (-1); + + pp->ssp_page = newbuf; + pp->ssp_alloc = min; + + bzero(newbuf + pp->ssp_len, pp->ssp_alloc - pp->ssp_len); + + return (0); +} + +static ses_snap_page_t * +alloc_snap_page(void) +{ + ses_snap_page_t *pp; + + if ((pp = ses_zalloc(sizeof (ses_snap_page_t))) == NULL) + return (NULL); + + if ((pp->ssp_page = ses_zalloc(SES2_MIN_DIAGPAGE_ALLOC)) == NULL) { + ses_free(pp); + return (NULL); + } + + pp->ssp_num = -1; + pp->ssp_alloc = SES2_MIN_DIAGPAGE_ALLOC; + + return (pp); +} + +static void +free_snap_page(ses_snap_page_t *pp) +{ + if (pp == NULL) + return; + + if (pp->ssp_mmap_base) + (void) munmap(pp->ssp_mmap_base, pp->ssp_mmap_len); + else + ses_free(pp->ssp_page); + ses_free(pp); +} + +static void +free_all_snap_pages(ses_snap_t *sp) +{ + ses_snap_page_t *pp, *np; + + for (pp = sp->ss_pages; pp != NULL; pp = np) { + np = pp->ssp_next; + free_snap_page(pp); + } + + sp->ss_pages = NULL; +} + +/* + * Grow (if needed) the control page buffer, fill in the page code, page + * length, and generation count, and return a pointer to the page. The + * caller is responsible for filling in the rest of the page data. If 'unique' + * is specified, then a new page instance is created instead of sharing the + * current one. + */ +ses_snap_page_t * +ses_snap_ctl_page(ses_snap_t *sp, ses2_diag_page_t page, size_t dlen, + boolean_t unique) +{ + ses_target_t *tp = sp->ss_target; + spc3_diag_page_impl_t *pip; + ses_snap_page_t *pp, *up, **loc; + ses_pagedesc_t *dp; + size_t len; + + pp = ses_snap_find_page(sp, page, B_TRUE); + if (pp == NULL) { + (void) ses_set_errno(ESES_NOTSUP); + return (NULL); + } + + if (pp->ssp_initialized && !unique) + return (pp); + + if (unique) { + /* + * The user has requested a unique instance of the page. Create + * a new ses_snap_page_t instance and chain it off the + * 'ssp_instances' list of the master page. These must be + * appended to the end of the chain, as the order of operations + * may be important (i.e. microcode download). + */ + if ((up = alloc_snap_page()) == NULL) + return (NULL); + + up->ssp_num = pp->ssp_num; + up->ssp_control = B_TRUE; + + for (loc = &pp->ssp_unique; *loc != NULL; + loc = &(*loc)->ssp_next) + ; + + *loc = up; + pp = up; + } + + dp = ses_get_pagedesc(tp, page, SES_PAGE_CTL); + ASSERT(dp != NULL); + + len = dp->spd_ctl_len(sp->ss_n_elem, page, dlen); + if (pp->ssp_alloc < dlen && grow_snap_page(pp, len) != 0) + return (NULL); + pp->ssp_len = len; + bzero(pp->ssp_page, len); + pp->ssp_initialized = B_TRUE; + + pip = (spc3_diag_page_impl_t *)pp->ssp_page; + pip->sdpi_page_code = (uint8_t)page; + SCSI_WRITE16(&pip->sdpi_page_length, + len - offsetof(spc3_diag_page_impl_t, sdpi_data[0])); + if (dp->spd_gcoff != -1) + SCSI_WRITE32((uint8_t *)pip + dp->spd_gcoff, sp->ss_generation); + + return (pp); +} + +static int +read_status_page(ses_snap_t *sp, ses2_diag_page_t page) +{ + libscsi_action_t *ap; + ses_snap_page_t *pp; + ses_target_t *tp; + spc3_diag_page_impl_t *pip; + spc3_receive_diagnostic_results_cdb_t *cp; + uint_t flags; + uint8_t *buf; + size_t alloc; + uint_t retries = 0; + ses2_diag_page_t retpage; + + for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) + if (pp->ssp_num == page && !pp->ssp_control) + break; + + /* + * No matching page. Since the page number is not under consumer or + * device control, this must be a bug. + */ + ASSERT(pp != NULL); + + tp = sp->ss_target; + + flags = LIBSCSI_AF_READ | LIBSCSI_AF_SILENT | LIBSCSI_AF_DIAGNOSE | + LIBSCSI_AF_RQSENSE; + +again: + ap = libscsi_action_alloc(tp->st_scsi_hdl, + SPC3_CMD_RECEIVE_DIAGNOSTIC_RESULTS, flags, pp->ssp_page, + pp->ssp_alloc); + + if (ap == NULL) + return (ses_libscsi_error(tp->st_scsi_hdl, "failed to " + "allocate SCSI action")); + + cp = (spc3_receive_diagnostic_results_cdb_t *) + libscsi_action_get_cdb(ap); + + cp->rdrc_page_code = pp->ssp_num; + cp->rdrc_pcv = 1; + SCSI_WRITE16(&cp->rdrc_allocation_length, + MIN(pp->ssp_alloc, UINT16_MAX)); + + if (libscsi_exec(ap, tp->st_target) != 0) { + libscsi_action_free(ap); + return (ses_libscsi_error(tp->st_scsi_hdl, + "receive diagnostic results failed")); + } + + if (libscsi_action_get_status(ap) != 0) { + (void) ses_scsi_error(ap, + "receive diagnostic results failed"); + libscsi_action_free(ap); + return (-1); + } + + (void) libscsi_action_get_buffer(ap, &buf, &alloc, &pp->ssp_len); + libscsi_action_free(ap); + + ASSERT(buf == pp->ssp_page); + ASSERT(alloc == pp->ssp_alloc); + + if (pp->ssp_len == pp->ssp_alloc && pp->ssp_alloc < UINT16_MAX) { + bzero(pp->ssp_page, pp->ssp_len); + pp->ssp_len = 0; + if (grow_snap_page(pp, 0) != 0) + return (-1); + goto again; + } + + pip = (spc3_diag_page_impl_t *)buf; + + if (pip->sdpi_page_code == page) + return (0); + + retpage = pip->sdpi_page_code; + + bzero(pp->ssp_page, pp->ssp_len); + pp->ssp_len = 0; + + if (retpage == SES2_DIAGPAGE_ENCLOSURE_BUSY) { + if (++retries > LIBSES_MAX_BUSY_RETRIES) + return (ses_error(ESES_BUSY, "too many " + "enclosure busy responses for page 0x%x", page)); + goto again; + } + + return (ses_error(ESES_BAD_RESPONSE, "target returned page 0x%x " + "instead of the requested page 0x%x", retpage, page)); +} + +static int +send_control_page(ses_snap_t *sp, ses_snap_page_t *pp) +{ + ses_target_t *tp; + libscsi_action_t *ap; + spc3_send_diagnostic_cdb_t *cp; + uint_t flags; + + tp = sp->ss_target; + + flags = LIBSCSI_AF_WRITE | LIBSCSI_AF_SILENT | LIBSCSI_AF_DIAGNOSE | + LIBSCSI_AF_RQSENSE; + + ap = libscsi_action_alloc(tp->st_scsi_hdl, SPC3_CMD_SEND_DIAGNOSTIC, + flags, pp->ssp_page, pp->ssp_len); + + if (ap == NULL) + return (ses_libscsi_error(tp->st_scsi_hdl, "failed to " + "allocate SCSI action")); + + cp = (spc3_send_diagnostic_cdb_t *)libscsi_action_get_cdb(ap); + + cp->sdc_pf = 1; + SCSI_WRITE16(&cp->sdc_parameter_list_length, pp->ssp_len); + + if (libscsi_exec(ap, tp->st_target) != 0) { + libscsi_action_free(ap); + return (ses_libscsi_error(tp->st_scsi_hdl, + "SEND DIAGNOSTIC command failed for page 0x%x", + pp->ssp_num)); + } + + if (libscsi_action_get_status(ap) != 0) { + (void) ses_scsi_error(ap, "SEND DIAGNOSTIC command " + "failed for page 0x%x", pp->ssp_num); + libscsi_action_free(ap); + return (-1); + } + + libscsi_action_free(ap); + + return (0); +} + +static int +pages_skel_create(ses_snap_t *sp) +{ + ses_snap_page_t *pp, *np; + ses_target_t *tp = sp->ss_target; + ses2_supported_ses_diag_page_impl_t *pip; + ses2_diag_page_t page; + size_t npages; + size_t pagelen; + off_t i; + + ASSERT(sp->ss_pages == NULL); + + if ((pp = alloc_snap_page()) == NULL) + return (-1); + + pp->ssp_num = SES2_DIAGPAGE_SUPPORTED_PAGES; + pp->ssp_control = B_FALSE; + sp->ss_pages = pp; + + if (read_status_page(sp, SES2_DIAGPAGE_SUPPORTED_PAGES) != 0) { + free_snap_page(pp); + sp->ss_pages = NULL; + return (-1); + } + + pip = pp->ssp_page; + pagelen = pp->ssp_len; + + npages = SCSI_READ16(&pip->sssdpi_page_length); + + for (i = 0; i < npages; i++) { + if (!SES_WITHIN_PAGE(pip->sssdpi_pages + i, 1, pip, + pagelen)) + break; + + page = (ses2_diag_page_t)pip->sssdpi_pages[i]; + /* + * Skip the page we already added during the bootstrap. + */ + if (page == SES2_DIAGPAGE_SUPPORTED_PAGES) + continue; + /* + * The end of the page list may be padded with zeros; ignore + * them all. + */ + if (page == 0 && i > 0) + break; + if ((np = alloc_snap_page()) == NULL) { + free_all_snap_pages(sp); + return (-1); + } + np->ssp_num = page; + pp->ssp_next = np; + pp = np; + + /* + * Allocate a control page as well, if we can use it. + */ + if (ses_get_pagedesc(tp, page, SES_PAGE_CTL) != NULL) { + if ((np = alloc_snap_page()) == NULL) { + free_all_snap_pages(sp); + return (-1); + } + np->ssp_num = page; + np->ssp_control = B_TRUE; + pp->ssp_next = np; + pp = np; + } + } + + return (0); +} + +static void +ses_snap_free(ses_snap_t *sp) +{ + free_all_snap_pages(sp); + ses_node_teardown(sp->ss_root); + ses_free(sp->ss_nodes); + ses_free(sp); +} + +static void +ses_snap_rele_unlocked(ses_snap_t *sp) +{ + ses_target_t *tp = sp->ss_target; + + if (--sp->ss_refcnt != 0) + return; + + if (sp->ss_next != NULL) + sp->ss_next->ss_prev = sp->ss_prev; + + if (sp->ss_prev != NULL) + sp->ss_prev->ss_next = sp->ss_next; + else + tp->st_snapshots = sp->ss_next; + + ses_snap_free(sp); +} + +ses_snap_t * +ses_snap_hold(ses_target_t *tp) +{ + ses_snap_t *sp; + + (void) pthread_mutex_lock(&tp->st_lock); + sp = tp->st_snapshots; + sp->ss_refcnt++; + (void) pthread_mutex_unlock(&tp->st_lock); + + return (sp); +} + +void +ses_snap_rele(ses_snap_t *sp) +{ + ses_target_t *tp = sp->ss_target; + + (void) pthread_mutex_lock(&tp->st_lock); + ses_snap_rele_unlocked(sp); + (void) pthread_mutex_unlock(&tp->st_lock); +} + +ses_snap_t * +ses_snap_new(ses_target_t *tp) +{ + ses_snap_t *sp; + ses_snap_page_t *pp; + uint32_t gc; + uint_t retries = 0; + ses_pagedesc_t *dp; + size_t pages, pagesize, pagelen; + char *scratch; + + if ((sp = ses_zalloc(sizeof (ses_snap_t))) == NULL) + return (NULL); + + sp->ss_target = tp; + +again: + free_all_snap_pages(sp); + + if (pages_skel_create(sp) != 0) { + free(sp); + return (NULL); + } + + sp->ss_generation = (uint32_t)-1; + sp->ss_time = gethrtime(); + + for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) { + /* + * We skip all of: + * + * - Control pages + * - Pages we've already filled in + * - Pages we don't understand (those with no descriptor) + */ + if (pp->ssp_len > 0 || pp->ssp_control) + continue; + if ((dp = ses_get_pagedesc(tp, pp->ssp_num, + SES_PAGE_DIAG)) == NULL) + continue; + + if (read_status_page(sp, pp->ssp_num) != 0) + continue; + + /* + * If the generation code has changed, we don't have a valid + * snapshot. Start over. + */ + if (dp->spd_gcoff != -1 && + dp->spd_gcoff + 4 <= pp->ssp_len) { + gc = SCSI_READ32((uint8_t *)pp->ssp_page + + dp->spd_gcoff); + if (sp->ss_generation == (uint32_t)-1) { + sp->ss_generation = gc; + } else if (sp->ss_generation != gc) { + if (++retries > LIBSES_MAX_GC_RETRIES) { + (void) ses_error(ESES_TOOMUCHCHANGE, + "too many generation count " + "mismatches: page 0x%x gc %u " + "previous page %u", dp->spd_gcoff, + gc, sp->ss_generation); + ses_snap_free((ses_snap_t *)sp); + return (NULL); + } + goto again; + } + } + } + + /* + * The LIBSES_TRUNCATE environment variable is a debugging tool which, + * if set, randomly truncates all pages (except + * SES2_DIAGPAGE_SUPPORTED_PAGES). In order to be truly evil, we + * mmap() each page with enough space after it so we can move the data + * up to the end of a page and unmap the following page so that any + * attempt to read past the end of the page results in a segfault. + */ + if (sp->ss_target->st_truncate) { + pagesize = PAGESIZE; + + /* + * Count the maximum number of pages we will need and allocate + * the necessary space. + */ + pages = 0; + for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) { + if (pp->ssp_control || pp->ssp_len == 0) + continue; + + pages += (P2ROUNDUP(pp->ssp_len, pagesize) / + pagesize) + 1; + } + + if ((scratch = mmap(NULL, pages * pagesize, + PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, + -1, 0)) == MAP_FAILED) { + (void) ses_error(ESES_NOMEM, + "failed to mmap() pages for truncation"); + ses_snap_free(sp); + return (NULL); + } + + for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) { + if (pp->ssp_control || pp->ssp_len == 0) + continue; + + pages = P2ROUNDUP(pp->ssp_len, pagesize) / pagesize; + pp->ssp_mmap_base = scratch; + pp->ssp_mmap_len = pages * pagesize; + + pagelen = lrand48() % pp->ssp_len; + (void) memcpy(pp->ssp_mmap_base + pp->ssp_mmap_len - + pagelen, pp->ssp_page, pagelen); + ses_free(pp->ssp_page); + pp->ssp_page = pp->ssp_mmap_base + pp->ssp_mmap_len - + pagelen; + pp->ssp_len = pagelen; + + (void) munmap(pp->ssp_mmap_base + pages * pagesize, + pagesize); + scratch += (pages + 1) * pagesize; + } + } + + + if (ses_fill_snap(sp) != 0) { + ses_snap_free(sp); + return (NULL); + } + + (void) pthread_mutex_lock(&tp->st_lock); + if (tp->st_snapshots != NULL) + ses_snap_rele_unlocked(tp->st_snapshots); + sp->ss_next = tp->st_snapshots; + if (tp->st_snapshots != NULL) + tp->st_snapshots->ss_prev = sp; + tp->st_snapshots = sp; + sp->ss_refcnt = 2; + (void) pthread_mutex_unlock(&tp->st_lock); + + return (sp); +} + +int +ses_snap_do_ctl(ses_snap_t *sp) +{ + ses_snap_page_t *pp, *up; + int ret = -1; + + for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) { + if (!pp->ssp_control) + continue; + + if (pp->ssp_initialized && send_control_page(sp, pp) != 0) + goto error; + + for (up = pp->ssp_unique; up != NULL; up = up->ssp_next) { + if (send_control_page(sp, up) != 0) + goto error; + } + } + + ret = 0; +error: + for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) { + if (!pp->ssp_control) + continue; + + pp->ssp_initialized = B_FALSE; + while ((up = pp->ssp_unique) != NULL) { + pp->ssp_unique = up->ssp_next; + free_snap_page(up); + } + } + + + return (ret); +} + +uint32_t +ses_snap_generation(ses_snap_t *sp) +{ + return (sp->ss_generation); +} + +static ses_walk_action_t +ses_walk_node(ses_node_t *np, ses_walk_f func, void *arg) +{ + ses_walk_action_t action; + + for (; np != NULL; np = ses_node_sibling(np)) { + action = func(np, arg); + if (action == SES_WALK_ACTION_TERMINATE) + return (SES_WALK_ACTION_TERMINATE); + if (action == SES_WALK_ACTION_PRUNE || + ses_node_child(np) == NULL) + continue; + if (ses_walk_node(ses_node_child(np), func, arg) == + SES_WALK_ACTION_TERMINATE) + return (SES_WALK_ACTION_TERMINATE); + } + + return (SES_WALK_ACTION_CONTINUE); +} + +int +ses_walk(ses_snap_t *sp, ses_walk_f func, void *arg) +{ + (void) ses_walk_node(ses_root_node(sp), func, arg); + + return (0); +} + +/*ARGSUSED*/ +static ses_walk_action_t +ses_fill_nodes(ses_node_t *np, void *unused) +{ + np->sn_snapshot->ss_nodes[np->sn_id] = np; + + return (SES_WALK_ACTION_CONTINUE); +} + +/* + * Given an ID returned by ses_node_id(), lookup and return the corresponding + * node in the snapshot. If the snapshot generation count has changed, then + * return failure. + */ +ses_node_t * +ses_node_lookup(ses_snap_t *sp, uint64_t id) +{ + uint32_t gen = (id >> 32); + uint32_t idx = (id & 0xFFFFFFFF); + + if (sp->ss_generation != gen) { + (void) ses_set_errno(ESES_CHANGED); + return (NULL); + } + + if (idx >= sp->ss_n_nodes) { + (void) ses_error(ESES_BAD_NODE, + "no such node in snapshot"); + return (NULL); + } + + /* + * If this is our first lookup attempt, construct the array for fast + * lookups. + */ + if (sp->ss_nodes == NULL) { + if ((sp->ss_nodes = ses_zalloc( + sp->ss_n_nodes * sizeof (void *))) == NULL) + return (NULL); + + (void) ses_walk(sp, ses_fill_nodes, NULL); + } + + if (sp->ss_nodes[idx] == NULL) + (void) ses_error(ESES_BAD_NODE, + "no such node in snapshot"); + return (sp->ss_nodes[idx]); +} diff --git a/usr/src/lib/scsi/libses/common/ses_subr.c b/usr/src/lib/scsi/libses/common/ses_subr.c new file mode 100644 index 0000000000..aa1013e678 --- /dev/null +++ b/usr/src/lib/scsi/libses/common/ses_subr.c @@ -0,0 +1,376 @@ +/* + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <scsi/libses.h> +#include "ses_impl.h" + +__thread ses_errno_t _ses_errno; +__thread char _ses_errmsg[1024]; +__thread char _ses_nverr_member[256]; + +static void ses_vpanic(const char *, va_list) __NORETURN; + +static void +ses_vpanic(const char *fmt, va_list ap) +{ + int oserr = errno; + char msg[BUFSIZ]; + size_t len; + + (void) snprintf(msg, sizeof (msg), "ABORT: "); + len = strlen(msg); + (void) vsnprintf(msg + len, sizeof (msg) - len, fmt, ap); + + if (strchr(fmt, '\n') == NULL) { + len = strlen(msg); + (void) snprintf(msg + len, sizeof (msg) - len, ": %s\n", + strerror(oserr)); + } + + (void) write(STDERR_FILENO, msg, strlen(msg)); + +abort: + abort(); + _exit(1); +} + +/*PRINTFLIKE1*/ +void +ses_panic(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + ses_vpanic(fmt, ap); + va_end(ap); +} + +int +ses_assert(const char *expr, const char *file, int line) +{ + ses_panic("\"%s\", line %d: assertion failed: %s\n", file, line, expr); + + /*NOTREACHED*/ + return (0); +} + +int +nvlist_add_fixed_string(nvlist_t *nvl, const char *name, + const char *buf, size_t len) +{ + char *str = alloca(len + 1); + bcopy(buf, str, len); + str[len] = '\0'; + + return (nvlist_add_string(nvl, name, str)); +} + +/* + * Like fixed_string, but clears any leading or trailing spaces. + */ +int +nvlist_add_fixed_string_trunc(nvlist_t *nvl, const char *name, + const char *buf, size_t len) +{ + while (buf[0] == ' ' && len > 0) { + buf++; + len--; + } + + while (len > 0 && buf[len - 1] == ' ') + len--; + + return (nvlist_add_fixed_string(nvl, name, buf, len)); +} + +ses_errno_t +ses_errno(void) +{ + return (_ses_errno); +} + +const char * +ses_errmsg(void) +{ + if (_ses_errmsg[0] == '\0') + (void) snprintf(_ses_errmsg, sizeof (_ses_errmsg), "%s", + ses_strerror(_ses_errno)); + + return (_ses_errmsg); +} + +const char * +ses_nv_error_member(void) +{ + if (_ses_nverr_member[0] != '\0') + return (_ses_nverr_member); + else + return (NULL); +} + +static int +__ses_set_errno(ses_errno_t err, const char *nvm) +{ + if (nvm == NULL) { + _ses_nverr_member[0] = '\0'; + } else { + (void) strlcpy(_ses_nverr_member, nvm, + sizeof (_ses_nverr_member)); + } + _ses_errmsg[0] = '\0'; + _ses_errno = err; + + return (-1); +} + +int +ses_set_errno(ses_errno_t err) +{ + return (__ses_set_errno(err, NULL)); +} + +int +ses_set_nverrno(int err, const char *member) +{ + ses_errno_t se = (err == ENOMEM || err == EAGAIN) ? + ESES_NOMEM : ESES_NVL; + + /* + * If the error is ESES_NVL, then we should always have a member + * available. The only time 'member' is NULL is when nvlist_alloc() + * fails, which should only be possible if memory allocation fails. + */ + assert(se == ESES_NOMEM || member != NULL); + + return (__ses_set_errno(se, member)); +} + +static int +ses_verror(ses_errno_t err, const char *fmt, va_list ap) +{ + int syserr = errno; + size_t n; + char *errmsg; + + errmsg = alloca(sizeof (_ses_errmsg)); + (void) vsnprintf(errmsg, sizeof (_ses_errmsg), fmt, ap); + (void) ses_set_errno(err); + + n = strlen(errmsg); + + while (n != 0 && errmsg[n - 1] == '\n') + errmsg[--n] = '\0'; + + bcopy(errmsg, _ses_errmsg, sizeof (_ses_errmsg)); + errno = syserr; + + return (-1); +} + +static int +ses_vnverror(int err, const char *member, const char *fmt, + va_list ap) +{ + int syserr = errno; + size_t n; + char *errmsg; + + errmsg = alloca(sizeof (_ses_errmsg)); + (void) vsnprintf(errmsg, sizeof (_ses_errmsg), fmt, ap); + (void) ses_set_nverrno(err, member); + + n = strlen(errmsg); + + while (n != 0 && errmsg[n - 1] == '\n') + errmsg[--n] = '\0'; + + (void) snprintf(errmsg + n, sizeof (_ses_errmsg) - n, ": %s", + strerror(err)); + + bcopy(errmsg, _ses_errmsg, sizeof (_ses_errmsg)); + errno = syserr; + + return (-1); +} + +int +ses_error(ses_errno_t err, const char *fmt, ...) +{ + va_list ap; + int rv; + + va_start(ap, fmt); + rv = ses_verror(err, fmt, ap); + va_end(ap); + + return (rv); +} + +int +ses_nverror(int err, const char *member, const char *fmt, ...) +{ + va_list ap; + int rv; + + va_start(ap, fmt); + rv = ses_vnverror(err, member, fmt, ap); + va_end(ap); + + return (rv); +} + +int +ses_libscsi_error(libscsi_hdl_t *shp, const char *fmt, ...) +{ + va_list ap; + char errmsg[LIBSES_ERRMSGLEN]; + libscsi_errno_t se = libscsi_errno(shp); + ses_errno_t e; + + switch (se) { + case ESCSI_NONE: + return (0); + case ESCSI_NOMEM: + e = ESES_NOMEM; + break; + case ESCSI_NOTSUP: + e = ESES_NOTSUP; + break; + case ESCSI_ZERO_LENGTH: + case ESCSI_VERSION: + case ESCSI_BADFLAGS: + case ESCSI_BOGUSFLAGS: + case ESCSI_BADLENGTH: + case ESCSI_NEEDBUF: + va_start(ap, fmt); + (void) vsnprintf(errmsg, sizeof (errmsg), fmt, ap); + va_end(ap); + ses_panic("%s: unexpected libscsi error %s: %s", errmsg, + libscsi_errname(se), libscsi_errmsg(shp)); + break; + case ESCSI_UNKNOWN: + e = ESES_UNKNOWN; + break; + default: + e = ESES_LIBSCSI; + break; + } + + va_start(ap, fmt); + (void) vsnprintf(errmsg, sizeof (errmsg), fmt, ap); + va_end(ap); + + return (ses_error(e, "%s: %s", errmsg, libscsi_errmsg(shp))); +} + +int +ses_scsi_error(libscsi_action_t *ap, const char *fmt, ...) +{ + va_list args; + char errmsg[LIBSES_ERRMSGLEN]; + uint64_t asc = 0, ascq = 0, key = 0; + const char *code, *keystr; + + va_start(args, fmt); + (void) vsnprintf(errmsg, sizeof (errmsg), fmt, args); + va_end(args); + + if (libscsi_action_parse_sense(ap, &key, &asc, &ascq, NULL) != 0) + return (ses_error(ESES_LIBSCSI, + "%s: SCSI status %d (no sense data available)", errmsg, + libscsi_action_get_status(ap))); + + code = libscsi_sense_code_name(asc, ascq); + keystr = libscsi_sense_key_name(key); + + return (ses_error(ESES_LIBSCSI, "%s: SCSI status %d sense key %llu " + "(%s) additional sense code 0x%llx/0x%llx (%s)", errmsg, + libscsi_action_get_status(ap), key, keystr ? keystr : "<unknown>", + asc, ascq, code ? code : "<unknown>")); +} + +void * +ses_alloc(size_t sz) +{ + void *p; + + if (sz == 0) + ses_panic("attempted zero-length allocation"); + + if ((p = malloc(sz)) == NULL) + (void) ses_set_errno(ESES_NOMEM); + + return (p); +} + +void * +ses_zalloc(size_t sz) +{ + void *p; + + if ((p = ses_alloc(sz)) != NULL) + bzero(p, sz); + + return (p); +} + +char * +ses_strdup(const char *s) +{ + char *p; + size_t len; + + if (s == NULL) + ses_panic("attempted zero-length allocation"); + + len = strlen(s) + 1; + + if ((p = ses_alloc(len)) != NULL) + bcopy(s, p, len); + + return (p); +} + +void * +ses_realloc(void *p, size_t sz) +{ + if (sz == 0) + ses_panic("attempted zero-length allocation"); + + if ((p = realloc(p, sz)) == NULL) + (void) ses_set_errno(ESES_NOMEM); + + return (p); +} + +/*ARGSUSED*/ +void +ses_free(void *p) +{ + free(p); +} |