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