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
32static boolean_t ses_plugin_dlclose;
33
34/*ARGSUSED*/
35void *
36ses_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
57int
58ses_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
74int
75ses_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*/
127void *
128ses_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
150ses_pagedesc_t *
151ses_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
175int
176ses_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
188void
189ses_plugin_setspecific(ses_plugin_t *sp, void *data)
190{
191	sp->sp_data = data;
192}
193
194void *
195ses_plugin_getspecific(ses_plugin_t *sp)
196{
197	return (sp->sp_data);
198}
199
200static void
201ses_plugin_cleanstr(char *s)
202{
203	while (*s != '\0') {
204		if (*s == ' ' || *s == '/')
205			*s = '-';
206		s++;
207	}
208}
209
210static void
211ses_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
222static int
223ses_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
277static int
278ses_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
348int
349ses_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
386void
387ses_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}
396