xref: /illumos-gate/usr/src/cmd/pcidr/pcidr.c (revision ed5289f91b9bf164dccd6c75398362be77a4478d)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <strings.h>
33 #include <string.h>
34 #include <errno.h>
35 #include <sys/param.h>
36 #include <sys/systeminfo.h>
37 #include <sys/sysevent/eventdefs.h>
38 #include <sys/sysevent/dr.h>
39 #include <syslog.h>
40 #include <libnvpair.h>
41 #include <stdarg.h>
42 #include <assert.h>
43 #include <sys/stat.h>
44 #include <dlfcn.h>
45 #include <signal.h>
46 #include <pcidr.h>
47 
48 /*
49  * pcidr takes in arguments of the form specified in the help() routine
50  * including a set of name=value pairs, then looks up a plugin (shared object)
51  * based on <plugin_paths> and however find_plugin() operates.  The entry
52  * point of the plugin is <PCIDR_PLUGIN_SYM> and has the type
53  * <pcidr_plugin_t>.  Plugins must use the <PCIDR_PLUGIN_PROTO> macro to
54  * define their entry point.
55  *
56  * The name=value arguments are intended to be used as a mechanism to pass
57  * arbitrary sysevent attributes using the macro expansion capability provided
58  * by the syseventd SLM processing sysevent.conf files (i.e. specifying
59  * "$attribute" arguments for the handler in a .conf file entry). They are
60  * converted into an nvlist_t (see libnvpair(3LIB)) by converting the values
61  * of recognized names into appropriate types using pcidr_name2type() and
62  * leaving all others as string types. Because pcidr is used as a sysevent.conf
63  * handler, the format of the value string for non-string attributes in each
64  * name=value argument must match that used by the syseventd macro capability
65  *
66  * The plugin will be passed this (nvlist_t *) along with a (pcidr_opt_t *) arg
67  * for other options.  While pcidr does some basic checking of arguments, it
68  * leaves any name=value check (after conversion) up to each plugin.  Note
69  * that pcidr_check_attrs() is used by the default plugin and can be used by
70  * any plugin that support the same or a superset of its attributes.  If the
71  * default plugin supports additional publishers, it should be updated in
72  * pcidr_check_attrs().
73  *
74  * See help() for an example of how pcidr can be specified in a sysevent.conf
75  * file.
76  */
77 
78 /*
79  * plugin search paths (searched in order specified);
80  * macros begin MACRO_BEGTOK and end with MACRO_ENDTOK;
81  *
82  * be sure to update parse_path() and its support functions whenever macros
83  * are updated e.g. si_name2cmd(), as well as substring tokens (prefix or
84  * suffix) used to recognize different types of macros e.g. SI_MACRO
85  *
86  * NOTE: if plugin search algorithm is changed starting with find_plugin(),
87  * please update documentation here.
88  *
89  * macros:
90  * SI_PLATFORM = cmd of same name in sysinfo(2)
91  * SI_MACHINE = cmd of same name in sysinfo(2)
92  */
93 #define	MACRO_BEGTOK	"${"
94 #define	MACRO_ENDTOK	"}"
95 #define	SI_MACRO	"SI_"
96 
97 static char *plugin_paths[] = {
98 	"/usr/platform/${SI_PLATFORM}/lib/pci/" PCIDR_PLUGIN_NAME,
99 	"/usr/platform/${SI_MACHINE}/lib/pci/" PCIDR_PLUGIN_NAME,
100 	"/usr/lib/pci/" PCIDR_PLUGIN_NAME,
101 };
102 static int plugin_paths_len = sizeof (plugin_paths) / sizeof (plugin_paths[0]);
103 
104 
105 static nvlist_t *nvlistp = NULL;	/* attribute list */
106 
107 typedef struct {
108 	char *name;
109 	char *beg;
110 	char *end;
111 } macro_list_t;
112 static macro_list_t *parse_macros(char *const, int *);
113 static void free_macros(macro_list_t *, int);
114 static char *parse_path(char *const);
115 static void help();
116 static void exiter();
117 static char *find_plugin(nvlist_t *);
118 static int do_plugin(char *, nvlist_t *, pcidr_opt_t *);
119 static int nvadd(nvlist_t *, char *, char *, data_type_t);
120 static nvlist_t *parse_argv_attr(int, char **, int *);
121 static int si_name2cmd(char *);
122 
123 
124 static void
125 help()
126 {
127 /* since the handler is not public, we don't expose its usage normally */
128 #ifdef DEBUG
129 	(void) printf(
130 "%s [-h] [-s] [-v <level>] [-l <log_file>] <attributes>\n"
131 "	-h	help\n"
132 "\n"
133 "	-s	turn OFF messages to the syslog (use syslog by default)\n"
134 "\n"
135 "	-v	verbose mode; <level> range is %d..%d; default is %d\n"
136 "\n"
137 "	-l	also log messages to <log_file> (in addition to using\n"
138 "		the syslog if that option is not disabled);\n"
139 "		if <log_file> is '-', stdout is used\n"
140 "\n"
141 "	<attributes>\n"
142 "		whitespace seperated strings of <name>=<value> pairs\n"
143 "\n"
144 "Example 1 (command line):\n"
145 "	%s -s -v%d -l- \\\n"
146 "		class=EC_dr subclass=ESC_dr_req publisher=pcie_pci \\\n"
147 "		dr_request_type=dr_request_outgoing_resource \\\n"
148 "		dr_ap_id=/devices/foo/bar\n"
149 "\n"
150 "Example 2 (/etc/sysevent/config/SUNW,sysevent.conf entry):\n"
151 "	EC_dr ESC_dr_req SUNW pcie_pci - - - %s -v%d -l/tmp/log \\\n"
152 "		class=$class subclass=$subclass publisher=$publisher \\\n"
153 "		dr_request_type=$dr_request_type\\\n"
154 "		dr_ap_id=$dr_ap_id\n"
155 "\n",
156 	    prg, MIN_DLVL, MAX_DLVL, dlvl,
157 	    prg, MAX_DLVL, /* Example 1 */
158 	    prg, DWARN); /* Example 2 */
159 #endif
160 }
161 
162 
163 /*
164  * will convert <value> from a string to the type indicated by <type>
165  * and will add it with <name> to nvlist_t <listp>; function returns the same
166  * value as nvlist_add_*()
167  */
168 static int
169 nvadd(nvlist_t *listp, char *name, char *value, data_type_t type)
170 {
171 	char *fn = "nvadd";
172 	int rv = 0;
173 
174 	switch (type) {
175 	case DATA_TYPE_STRING:
176 		rv = nvlist_add_string(listp, name, value);
177 		if (rv != 0) {
178 			dprint(DDEBUG, "%s: nvlist_add_string() failed: "
179 			    "name = %s, value = %s, rv = %d\n",
180 			    fn, name, value, rv);
181 		}
182 		break;
183 	/*
184 	 * Conversion must support whatever string format syseventd uses for
185 	 * its .conf macros; in addition, minimum types supported must match
186 	 * those for pcidr_name2type()
187 	 */
188 	default:
189 		dprint(DDEBUG, "%s: unsupported type: name = %s, value = %s, "
190 		    "type = 0x%x\n", fn, name, value, (int)type);
191 		rv = EINVAL;
192 	}
193 
194 	return (rv);
195 }
196 
197 
198 /*
199  * argc: length of argv
200  * argv: each string starting from index <argip> has the format "name=value"
201  * argip: starting index in <argv>; also used to return ending index
202  *
203  * return: allocated nvlist on success, exits otherwise
204  *
205  * recognized names will have predetermined types, while all others will have
206  * values of type string
207  */
208 static nvlist_t *
209 parse_argv_attr(int argc, char **argv, int *argip)
210 {
211 	char *fn = "parse_argv_attr";
212 	int rv, i;
213 	nvlist_t *attrlistp = NULL;
214 	char *eqp, *name, *value;
215 	data_type_t type;
216 
217 	assert(*argip < argc);
218 
219 	rv = nvlist_alloc(&attrlistp, NV_UNIQUE_NAME_TYPE, 0);
220 	if (rv != 0) {
221 		dprint(DDEBUG, "%s: nvlist_alloc() failed: rv = %d\n", fn, rv);
222 		goto ERR;
223 	}
224 
225 	for (i = *argip; i < argc; i++) {
226 		eqp = strchr(argv[i], '=');
227 		if (eqp == NULL)
228 			goto ERR_ARG;
229 		*eqp = '\0';
230 		name = argv[i];
231 		value = eqp;
232 		value++;
233 		if (*name == '\0' || *value == '\0')
234 			goto ERR_ARG;
235 
236 		if (pcidr_name2type(name, &type) != 0)
237 			type = DATA_TYPE_STRING;
238 
239 		rv = nvadd(attrlistp, name, value, type);
240 		if (rv != 0) {
241 			dprint(DDEBUG, "%s: nvadd() failed: attribute \"%s\", "
242 			    "value = %s, type = %d, rv = %d\n",
243 			    fn, name, value, (int)type, rv);
244 			goto ERR;
245 		}
246 		*eqp = '=';
247 	}
248 
249 	*argip = i;
250 	return (attrlistp);
251 
252 	/*NOTREACHED*/
253 ERR_ARG:
254 	if (eqp != NULL)
255 		*eqp = '=';
256 	dprint(DDEBUG, "%s: bad attribute argv[%d]: \"%s\"\n", fn, i, argv[i]);
257 ERR:
258 	if (attrlistp != NULL)
259 		nvlist_free(attrlistp);
260 	return (NULL);
261 }
262 
263 
264 static struct {
265 	int cmd;
266 	char *name;
267 } si_cmd_nametab[] = {
268 	SI_PLATFORM, "SI_PLATFORM",
269 	SI_MACHINE, "SI_MACHINE",
270 };
271 static int si_cmd_nametab_len =
272     sizeof (si_cmd_nametab) / sizeof (si_cmd_nametab[0]);
273 
274 static int
275 si_name2cmd(char *name)
276 {
277 	int i;
278 
279 	for (i = 0; i < si_cmd_nametab_len; i++) {
280 		if (strcmp(name, si_cmd_nametab[i].name) == 0)
281 			return (si_cmd_nametab[i].cmd);
282 	}
283 	return (-1);
284 }
285 
286 
287 /*
288  * finds occurences of substrings surrounded (delimited) by MACRO_BEGTOK and
289  * MACRO_ENDTOK in <str>;
290  * returns an allocated array of macro_list_t whose length is
291  * returned through <lenp>; array entries will be in order of the occurrence;
292  * else returns NULL if none are found
293  *
294  * macro_list_t members:
295  *	char *name = allocated string containing name without macro delimiters
296  *	char *beg = location in <str> at _first char_ of MACRO_BEGTOK
297  *	char *end = location in <str> at _last char_ of MACRO_ENDTOK
298  */
299 static macro_list_t *
300 parse_macros(char *const str, int *lenp)
301 {
302 	char *beg, *end;
303 	macro_list_t *lp;
304 	size_t size;
305 	int i, begtok_len, endtok_len;
306 
307 	begtok_len = strlen(MACRO_BEGTOK);
308 	endtok_len = strlen(MACRO_ENDTOK);
309 
310 	/* count all occurrences */
311 	for (beg = str, i = 0; beg != NULL; i++) {
312 		beg = strstr(beg, MACRO_BEGTOK);
313 		if (beg == NULL)
314 			break;
315 		end = strstr(beg + begtok_len, MACRO_ENDTOK);
316 		if (end == NULL)
317 			break;
318 		beg = end + endtok_len;
319 	}
320 	if (i <= 0)
321 		return (NULL);
322 
323 	*lenp = i;
324 	lp = pcidr_malloc(sizeof (macro_list_t) * i);
325 
326 	for (beg = str, i = 0; i < *lenp; i++) {
327 		beg = strstr(beg, MACRO_BEGTOK);
328 		assert(beg != NULL);
329 		end = strstr(beg + begtok_len, MACRO_ENDTOK);
330 		assert(end != NULL);
331 
332 		size = (end - (beg + begtok_len)) + 1;
333 		lp[i].name = pcidr_malloc(size * sizeof (char));
334 		(void) strlcpy(lp[i].name, beg + begtok_len, size);
335 
336 		lp[i].beg = beg;
337 		lp[i].end = (end + endtok_len) - 1;
338 
339 		beg = end + endtok_len;
340 	}
341 
342 	return (lp);
343 }
344 
345 static void
346 free_macros(macro_list_t *lp, int len)
347 {
348 	int i;
349 
350 	for (i = 0; i < len; i++)
351 		free(lp[i].name);
352 	free(lp);
353 }
354 
355 
356 /*
357  * evaluates any macros in <opath> and returns allocated string on success;
358  * else NULL
359  */
360 static char *
361 parse_path(char *const opath)
362 {
363 	char *fn = "parse_path";
364 	char buf[MAXPATHLEN + 1];
365 	int bufsize = sizeof (buf) / sizeof (buf[0]);
366 	char sibuf[257];
367 	int sibufsize = sizeof (sibuf) / sizeof (sibuf[0]);
368 	macro_list_t *lp;
369 	char *path, *pathp, *pathend;
370 	int rv, i, lplen, si_cmd, pathlen, okmacro, si_macro_len;
371 	size_t sz;
372 
373 	/*
374 	 * make a copy so we can modify it for easier parsing;
375 	 * lp members will refer to the copy
376 	 */
377 	path = strdup(opath);
378 	lp = parse_macros(path, &lplen);
379 	if (lp == NULL)
380 		return (path);
381 
382 	rv = 0;
383 	si_macro_len = strlen(SI_MACRO);
384 	pathlen = strlen(path);
385 	pathend = &path[pathlen - 1];
386 	pathp = path;
387 	buf[0] = '\0';
388 	for (i = 0; i < lplen; i++) {
389 		lp[i].beg[0] = '\0';
390 		sz = strlcat(buf, pathp, bufsize);
391 		assert(sz < bufsize);
392 
393 		okmacro = 0;
394 		if (strncmp(lp[i].name, SI_MACRO, si_macro_len) == 0) {
395 			si_cmd = si_name2cmd(lp[i].name);
396 			assert(si_cmd >= 0);
397 
398 			rv = sysinfo(si_cmd, sibuf, sibufsize);
399 			if (rv < 0) {
400 				dprint(DDEBUG, "%s: sysinfo cmd %d failed: "
401 				    "errno = %d\n", fn, si_cmd, errno);
402 				goto OUT;
403 			}
404 
405 			sz = strlcat(buf, sibuf, bufsize);
406 			assert(sz < bufsize);
407 			okmacro = 1;
408 		}
409 		/* check for unrecognized macros */
410 		assert(okmacro);
411 		pathp = lp[i].end + 1;
412 	}
413 
414 	rv = 0;
415 	if (pathp < pathend) {
416 		sz = strlcat(buf, pathp, bufsize);
417 		assert(sz < bufsize);
418 	}
419 OUT:
420 	free_macros(lp, lplen);
421 	free(path);
422 	if (rv == 0)
423 		return (strdup(buf));
424 	return (NULL);
425 }
426 
427 
428 /*
429  * returns allocated string containing plugin path which caller must free;
430  * else NULL;  <attrlistp> is for future use if attributes can be used to
431  * determin plugin
432  */
433 /*ARGSUSED*/
434 static char *
435 find_plugin(nvlist_t *attrlistp)
436 {
437 	char *fn = "find_plugin";
438 	char *path = NULL;
439 	int i, rv;
440 	struct stat statbuf;
441 
442 	for (i = 0; i < plugin_paths_len; i++) {
443 		path = parse_path(plugin_paths[i]);
444 		if (path == NULL) {
445 			dprint(DDEBUG, "%s: error parsing path %s\n", fn,
446 			    path);
447 			return (NULL);
448 		}
449 
450 		rv = stat(path, &statbuf);
451 		if (rv < 0)
452 			dprint(DDEBUG, "%s: stat on %s failed: "
453 			    "errno = %d\n", fn, path, errno);
454 		else if ((statbuf.st_mode & S_IFMT) != S_IFREG)
455 			dprint(DDEBUG, "%s: %s is not a regular "
456 			    "file\n", fn, path);
457 		else
458 			return (path);
459 
460 		free(path);
461 	}
462 	return (NULL);
463 }
464 
465 
466 /*
467  * load plugin specified by <path> and pass the proceeding arguments
468  * to the plugin interface;  returns 0 on success (likewise for
469  * the plugin function)
470  */
471 static int
472 do_plugin(char *path, nvlist_t *attrlistp, pcidr_opt_t *optp)
473 {
474 	char *fn = "do_plugin";
475 	int rv;
476 	void *dlh;
477 	sigset_t set, oset;
478 	pcidr_plugin_t fp;
479 
480 	dlh = dlopen(path, RTLD_LAZY | RTLD_GLOBAL);
481 	if (dlh == NULL) {
482 		dprint(DDEBUG, "%s: dlopen() failed: %s\n", fn, dlerror());
483 		rv = EINVAL;
484 		goto OUT;
485 	}
486 
487 	if (sigfillset(&set) != 0) {
488 		dprint(DDEBUG, "%s: sigfillset() failed: errno = %d\n", fn,
489 		    errno);
490 		rv = errno;
491 		goto OUT;
492 	}
493 	if (sigprocmask(SIG_BLOCK, &set, &oset) != 0) {
494 		dprint(DDEBUG, "%s: blocking signals with sigprocmask() "
495 		    "failed: errno = %d\n", fn, errno);
496 		rv = errno;
497 		goto OUT;
498 	}
499 
500 	fp = (pcidr_plugin_t)dlsym(dlh, PCIDR_PLUGIN_SYMSTR);
501 	if (fp == NULL)  {
502 		dprint(DDEBUG, "%s: dlsym() failed: %s\n", fn, dlerror());
503 		rv = EINVAL;
504 		goto OUT;
505 	}
506 	rv = fp(attrlistp, optp);
507 	if (rv != 0)
508 		dprint(DDEBUG, "%s: %s() failed: rv = %d\n", fn,
509 		    PCIDR_PLUGIN_SYMSTR, rv);
510 
511 	if (sigprocmask(SIG_SETMASK, &oset, NULL) != 0) {
512 		dprint(DDEBUG, "%s: unblocking signals with sigprocmask() "
513 		    "failed: errno = %d\n", fn, errno);
514 		rv = errno;
515 		goto OUT;
516 	}
517 OUT:
518 	if (dlh != NULL)
519 		(void) dlclose(dlh);
520 	return (rv);
521 }
522 
523 
524 static void
525 exiter()
526 {
527 	extern FILE *dfp;
528 
529 	if (nvlistp != NULL)
530 		nvlist_free(nvlistp);
531 	if (dfp != NULL)
532 		(void) fclose(dfp);
533 #ifdef DEBUG
534 	closelog();
535 #endif
536 }
537 
538 
539 int
540 main(int argc, char **argv)
541 {
542 	int rv, argi;
543 	char *dfile = NULL, *plugin_path = NULL;
544 	struct stat statbuf;
545 	pcidr_opt_t plugin_opt;
546 	char *optstr = NULL;
547 
548 	extern char *optarg;
549 	extern int optind, optopt;
550 	int c;
551 
552 	/*CONSTCOND*/
553 	assert(MIN_DLVL == 0);
554 	/*CONSTCOND*/
555 	assert(MIN_DLVL == DNONE);
556 	assert(MAX_DLVL == dpritab_len - 1);
557 
558 	(void) atexit(exiter);
559 	prg = argv[0];
560 	dfp = NULL;
561 
562 #ifdef DEBUG
563 	openlog(prg, LOG_PID | LOG_CONS, LOG_DAEMON);
564 	dlvl = DWARN;
565 	dsys = 1;
566 	optstr = "hsv:l:";
567 #else
568 	dlvl = DNONE;
569 	dsys = 0;
570 	optstr = "sv:l:";
571 #endif
572 
573 	while ((c = getopt(argc, argv, optstr)) != -1) {
574 		switch (c) {
575 		case 'h':
576 			help();
577 			exit(0);
578 			break;
579 		case 's':
580 			dsys = 0;
581 			break;
582 		case 'v':
583 			dlvl = atoi(optarg);
584 			break;
585 		case 'l':
586 			dfile = optarg;
587 			break;
588 		default:
589 			dprint(DWARN, "bad option: %c\n", optopt);
590 			return (EINVAL);
591 		}
592 	}
593 
594 	/*
595 	 * [ -l ] do file option first so we can still get msgs if -s is used
596 	 */
597 	if (dfile != NULL) {
598 		if (strcmp(dfile, "-") == 0) {
599 			/* ignore if stdout is not open/valid */
600 			dfp = NULL;
601 			if (stdout != NULL &&
602 			    fstat(fileno(stdout), &statbuf) == 0)
603 				dfp = stdout;
604 		} else {
605 			dfp = fopen(dfile, "a");
606 			if (dfp == NULL) {
607 				dprint(DWARN, "cannot open %s: %s\n",
608 				    dfile, strerror(errno));
609 				return (EINVAL);
610 			}
611 		}
612 	}
613 
614 	/* [ -v ] */
615 	if (dlvl < MIN_DLVL || dlvl > MAX_DLVL) {
616 		dprint(DWARN, "bad arg for -v: %d\n", dlvl);
617 		return (EINVAL);
618 	}
619 
620 	argi = optind;
621 	if (argi >= argc) {
622 		dprint(DWARN, "missing attribute arguments\n");
623 		return (EINVAL);
624 	}
625 
626 	nvlistp = parse_argv_attr(argc, argv, &argi);
627 	if (nvlistp == NULL) {
628 		dprint(DWARN, "attribute parsing error\n");
629 		return (EINVAL);
630 	}
631 
632 	(void) memset(&plugin_opt, 0, sizeof (plugin_opt));
633 	plugin_opt.logopt.dlvl = dlvl;
634 	plugin_opt.logopt.prg = prg;
635 	plugin_opt.logopt.dfp = dfp;
636 	plugin_opt.logopt.dsys = dsys;
637 
638 	dprint(DINFO, "=== sysevent attributes ========================\n");
639 	pcidr_print_attrlist(DINFO, nvlistp, NULL);
640 	dprint(DINFO, "================================================\n");
641 
642 	plugin_path = find_plugin(nvlistp);
643 	if (plugin_path == NULL) {
644 		dprint(DWARN, "cannot find plugin\n");
645 		return (EINVAL);
646 	}
647 	dprint(DINFO, "using plugin: %s\n\n", plugin_path);
648 
649 	rv = do_plugin(plugin_path, nvlistp, &plugin_opt);
650 	if (rv != 0) {
651 		dprint(DWARN, "plugin %s failed\n", plugin_path);
652 	}
653 	if (plugin_path != NULL)
654 		free(plugin_path);
655 	return (rv);
656 }
657