1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 
  22 /*
  23  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
  24  */
  25 /*
  26  * Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
  27  */
  28 
  29 #include <scsi/libses.h>
  30 #include "ses_impl.h"
  31 
  32 static boolean_t ses_plugin_dlclose;
  33 
  34 /*ARGSUSED*/
  35 void *
  36 ses_plugin_ctlpage_lookup(ses_plugin_t *sp, ses_snap_t *snap, int pagenum,
  37     size_t len, ses_node_t *np, boolean_t unique)
  38 {
  39         ses_target_t *tp = snap->ss_target;
  40         ses_snap_page_t *pp;
  41         ses_pagedesc_t *dp;
  42 
  43         if ((pp = ses_snap_ctl_page(snap, pagenum, len, unique)) == NULL)
  44                 return (NULL);
  45 
  46         if ((dp = ses_get_pagedesc(tp, pagenum, SES_PAGE_CTL)) == NULL)
  47                 return (NULL);
  48 
  49         if (np != NULL && dp->spd_ctl_fill != NULL) {
  50                 return (dp->spd_ctl_fill(sp, pp->ssp_page,
  51                     pp->ssp_len, np));
  52         } else {
  53                 return (pp->ssp_page);
  54         }
  55 }
  56 
  57 int
  58 ses_fill_node(ses_node_t *np)
  59 {
  60         ses_target_t *tp = np->sn_snapshot->ss_target;
  61         ses_plugin_t *sp;
  62 
  63         for (sp = tp->st_plugin_first; sp != NULL; sp = sp->sp_next) {
  64                 if (sp->sp_node_parse == NULL)
  65                         continue;
  66 
  67                 if (sp->sp_node_parse(sp, np) != 0)
  68                         return (-1);
  69         }
  70 
  71         return (0);
  72 }
  73 
  74 int
  75 ses_node_ctl(ses_node_t *np, const char *op, nvlist_t *arg)
  76 {
  77         ses_target_t *tp = np->sn_snapshot->ss_target;
  78         ses_plugin_t *sp;
  79         nvlist_t *nvl;
  80         nvpair_t *nvp;
  81         int ret;
  82 
  83         if (nvlist_dup(arg, &nvl, 0) != 0)
  84                 return (ses_set_errno(ESES_NOMEM));
  85 
  86         /*
  87          * Technically we could get away with a per-snapshot lock while we fill
  88          * the control page contents, but this doesn't take much time and we
  89          * want actual control operations to be protected per-target, so we just
  90          * take the target lock.
  91          */
  92         (void) pthread_mutex_lock(&tp->st_lock);
  93 
  94         /*
  95          * We walk the list of plugins backwards, so that a product-specific
  96          * plugin can rewrite the nvlist to control operations in terms of the
  97          * standard mechanisms, if desired.
  98          */
  99         for (sp = tp->st_plugin_first; sp != NULL; sp = sp->sp_next) {
 100                 if (sp->sp_node_ctl == NULL)
 101                         continue;
 102 
 103                 if (sp->sp_node_ctl(sp, np, op, nvl) != 0) {
 104                         nvlist_free(nvl);
 105                         (void) pthread_mutex_unlock(&tp->st_lock);
 106                         return (-1);
 107                 }
 108         }
 109 
 110         if ((nvp = nvlist_next_nvpair(nvl, NULL)) != NULL) {
 111                 (void) ses_error(ESES_NOTSUP, "property '%s' invalid for "
 112                     "this node", nvpair_name(nvp));
 113                 nvlist_free(nvl);
 114                 (void) pthread_mutex_unlock(&tp->st_lock);
 115                 return (-1);
 116         }
 117 
 118         nvlist_free(nvl);
 119 
 120         ret = ses_snap_do_ctl(np->sn_snapshot);
 121         (void) pthread_mutex_unlock(&tp->st_lock);
 122 
 123         return (ret);
 124 }
 125 
 126 /*ARGSUSED*/
 127 void *
 128 ses_plugin_page_lookup(ses_plugin_t *sp, ses_snap_t *snap, int pagenum,
 129     ses_node_t *np, size_t *lenp)
 130 {
 131         ses_snap_page_t *pp;
 132         ses_target_t *tp = sp->sp_target;
 133         ses_pagedesc_t *dp;
 134 
 135         if ((dp = ses_get_pagedesc(tp, pagenum, SES_PAGE_DIAG)) == NULL)
 136                 return (NULL);
 137 
 138         if ((pp = ses_snap_find_page(snap, pagenum, B_FALSE)) == NULL)
 139                 return (NULL);
 140 
 141         if (np != NULL && dp->spd_index != NULL) {
 142                 return (dp->spd_index(sp, np, pp->ssp_page, pp->ssp_len,
 143                     lenp));
 144         } else {
 145                 *lenp = pp->ssp_len;
 146                 return (pp->ssp_page);
 147         }
 148 }
 149 
 150 ses_pagedesc_t *
 151 ses_get_pagedesc(ses_target_t *tp, int pagenum, ses_pagetype_t type)
 152 {
 153         ses_plugin_t *sp;
 154         ses_pagedesc_t *dp;
 155 
 156         for (sp = tp->st_plugin_first; sp != NULL; sp = sp->sp_next) {
 157                 if (sp->sp_pages == NULL)
 158                         continue;
 159 
 160                 for (dp = &sp->sp_pages[0]; dp->spd_pagenum != -1;
 161                     dp++) {
 162                         if ((type == SES_PAGE_CTL && dp->spd_ctl_len == NULL) ||
 163                             (type == SES_PAGE_DIAG && dp->spd_ctl_len != NULL))
 164                                 continue;
 165 
 166                         if (dp->spd_pagenum == pagenum)
 167                                 return (dp);
 168                 }
 169         }
 170 
 171         (void) ses_error(ESES_BAD_PAGE, "failed to find page 0x%x", pagenum);
 172         return (NULL);
 173 }
 174 
 175 int
 176 ses_plugin_register(ses_plugin_t *sp, int version, ses_plugin_config_t *scp)
 177 {
 178         if (version != LIBSES_PLUGIN_VERSION)
 179                 return (ses_set_errno(ESES_VERSION));
 180 
 181         sp->sp_pages = scp->spc_pages;
 182         sp->sp_node_parse = scp->spc_node_parse;
 183         sp->sp_node_ctl = scp->spc_node_ctl;
 184 
 185         return (0);
 186 }
 187 
 188 void
 189 ses_plugin_setspecific(ses_plugin_t *sp, void *data)
 190 {
 191         sp->sp_data = data;
 192 }
 193 
 194 void *
 195 ses_plugin_getspecific(ses_plugin_t *sp)
 196 {
 197         return (sp->sp_data);
 198 }
 199 
 200 static void
 201 ses_plugin_cleanstr(char *s)
 202 {
 203         while (*s != '\0') {
 204                 if (*s == ' ' || *s == '/')
 205                         *s = '-';
 206                 s++;
 207         }
 208 }
 209 
 210 static void
 211 ses_plugin_destroy(ses_plugin_t *sp)
 212 {
 213         if (sp->sp_initialized && sp->sp_fini != NULL)
 214                 sp->sp_fini(sp);
 215 
 216         if (ses_plugin_dlclose)
 217                 (void) dlclose(sp->sp_object);
 218 
 219         ses_free(sp);
 220 }
 221 
 222 static int
 223 ses_plugin_loadone(ses_target_t *tp, const char *path, uint32_t pass)
 224 {
 225         ses_plugin_t *sp, **loc;
 226         void *obj;
 227         int (*ses_priority)(void);
 228 
 229         if ((obj = dlopen(path, RTLD_PARENT | RTLD_LOCAL | RTLD_LAZY)) == NULL)
 230                 return (0);
 231 
 232         if ((sp = ses_zalloc(sizeof (ses_plugin_t))) == NULL) {
 233                 (void) dlclose(obj);
 234                 return (-1);
 235         }
 236 
 237         sp->sp_object = obj;
 238         sp->sp_init = (int (*)())dlsym(obj, "_ses_init");
 239         sp->sp_fini = (void (*)())dlsym(obj, "_ses_fini");
 240         sp->sp_target = tp;
 241 
 242         if (sp->sp_init == NULL) {
 243                 ses_plugin_destroy(sp);
 244                 return (0);
 245         }
 246 
 247         /*
 248          * Framework modules can establish an explicit prioritying by declaring
 249          * the '_ses_priority' symbol, which returns an integer used to create
 250          * an explicit ordering between plugins.
 251          */
 252         if ((ses_priority = (int (*)())dlsym(obj, "_ses_priority")) != NULL)
 253                 sp->sp_priority = ses_priority();
 254 
 255         sp->sp_priority |= (uint64_t)pass << 32;
 256 
 257         for (loc = &tp->st_plugin_first; *loc != NULL; loc = &(*loc)->sp_next) {
 258                 if ((*loc)->sp_priority > sp->sp_priority)
 259                         break;
 260         }
 261 
 262         if (*loc != NULL)
 263                 (*loc)->sp_prev = sp;
 264         else
 265                 tp->st_plugin_last = sp;
 266 
 267         sp->sp_next = *loc;
 268         *loc = sp;
 269 
 270         if (sp->sp_init(sp) != 0)
 271                 return (-1);
 272         sp->sp_initialized = B_TRUE;
 273 
 274         return (0);
 275 }
 276 
 277 static int
 278 ses_plugin_load_dir(ses_target_t *tp, const char *pluginroot)
 279 {
 280         char path[PATH_MAX];
 281         DIR *dirp;
 282         struct dirent64 *dp;
 283         char *vendor, *product, *revision;
 284         char isa[257];
 285 
 286         (void) snprintf(path, sizeof (path), "%s/%s",
 287             pluginroot, LIBSES_PLUGIN_FRAMEWORK);
 288 
 289 #if defined(_LP64)
 290         if (sysinfo(SI_ARCHITECTURE_64, isa, sizeof (isa)) < 0)
 291                 isa[0] = '\0';
 292 #else
 293         isa[0] = '\0';
 294 #endif
 295 
 296         if ((dirp = opendir(path)) != NULL) {
 297                 while ((dp = readdir64(dirp)) != NULL) {
 298                         if (strcmp(dp->d_name, ".") == 0 ||
 299                             strcmp(dp->d_name, "..") == 0)
 300                                 continue;
 301 
 302                         (void) snprintf(path, sizeof (path), "%s/%s/%s/%s",
 303                             pluginroot, LIBSES_PLUGIN_FRAMEWORK,
 304                             isa, dp->d_name);
 305 
 306                         if (ses_plugin_loadone(tp, path, 0) != 0) {
 307                                 (void) closedir(dirp);
 308                                 return (-1);
 309                         }
 310                 }
 311 
 312                 (void) closedir(dirp);
 313         }
 314 
 315         /*
 316          * Create a local copy of the vendor/product/revision, strip out any
 317          * questionable characters, and then attempt to load each plugin.
 318          */
 319         vendor = strdupa(libscsi_vendor(tp->st_target));
 320         product = strdupa(libscsi_product(tp->st_target));
 321         revision = strdupa(libscsi_revision(tp->st_target));
 322 
 323         ses_plugin_cleanstr(vendor);
 324         ses_plugin_cleanstr(product);
 325         ses_plugin_cleanstr(revision);
 326 
 327         (void) snprintf(path, sizeof (path), "%s/%s/%s/%s%s", pluginroot,
 328             LIBSES_PLUGIN_VENDOR, isa, vendor,
 329             LIBSES_PLUGIN_EXT);
 330         if (ses_plugin_loadone(tp, path, 1) != 0)
 331                 return (-1);
 332 
 333         (void) snprintf(path, sizeof (path), "%s/%s/%s/%s-%s%s", pluginroot,
 334             LIBSES_PLUGIN_VENDOR, isa, vendor, product,
 335             LIBSES_PLUGIN_EXT);
 336         if (ses_plugin_loadone(tp, path, 2) != 0)
 337                 return (-1);
 338 
 339         (void) snprintf(path, sizeof (path), "%s/%s/%s/%s-%s-%s%s", pluginroot,
 340             LIBSES_PLUGIN_VENDOR, isa, vendor, product,
 341             revision, LIBSES_PLUGIN_EXT);
 342         if (ses_plugin_loadone(tp, path, 3) != 0)
 343                 return (-1);
 344 
 345         return (0);
 346 }
 347 
 348 int
 349 ses_plugin_load(ses_target_t *tp)
 350 {
 351         char pluginroot[PATH_MAX];
 352         const char *pluginpath, *p, *q;
 353 
 354         if ((pluginpath = getenv("SES_PLUGINPATH")) == NULL)
 355                 pluginpath = LIBSES_DEFAULT_PLUGINDIR;
 356         ses_plugin_dlclose = (getenv("SES_NODLCLOSE") == NULL);
 357 
 358         for (p = pluginpath; p != NULL; p = q) {
 359                 if ((q = strchr(p, ':')) != NULL) {
 360                         ptrdiff_t len = q - p;
 361                         (void) strncpy(pluginroot, p, len);
 362                         pluginroot[len] = '\0';
 363                         while (*q == ':')
 364                                 ++q;
 365                         if (*q == '\0')
 366                                 q = NULL;
 367                         if (len == 0)
 368                                 continue;
 369                 } else {
 370                         (void) strcpy(pluginroot, p);
 371                 }
 372 
 373                 if (pluginroot[0] != '/')
 374                         continue;
 375 
 376                 if (ses_plugin_load_dir(tp, pluginroot) != 0)
 377                         return (-1);
 378         }
 379 
 380         if (tp->st_plugin_first == NULL)
 381                 return (ses_error(ESES_PLUGIN, "no plugins found"));
 382 
 383         return (0);
 384 }
 385 
 386 void
 387 ses_plugin_unload(ses_target_t *tp)
 388 {
 389         ses_plugin_t *sp;
 390 
 391         while ((sp = tp->st_plugin_first) != NULL) {
 392                 tp->st_plugin_first = sp->sp_next;
 393                 ses_plugin_destroy(sp);
 394         }
 395 }