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 }