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) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
  24  */
  25 
  26 #include <libdevinfo.h>
  27 #include <sys/modctl.h>
  28 #include <sys/stat.h>
  29 #include <string.h>
  30 #include <librcm.h>
  31 #include <dlfcn.h>
  32 
  33 #undef  NDEBUG
  34 #include <assert.h>
  35 
  36 typedef struct rio_path {
  37         char            rpt_path[PATH_MAX];
  38         struct rio_path *rpt_next;
  39 } rio_path_t;
  40 
  41 typedef struct rcm_arg {
  42         char            *rcm_root;
  43         di_node_t       rcm_node;
  44         int             rcm_supp;
  45         rcm_handle_t    *rcm_handle;
  46         int             rcm_retcode;
  47         di_retire_t     *rcm_dp;
  48         rio_path_t      *rcm_cons_nodes;
  49         rio_path_t      *rcm_rsrc_minors;
  50         int             (*rcm_offline)();
  51         int             (*rcm_online)();
  52         int             (*rcm_remove)();
  53 } rcm_arg_t;
  54 
  55 typedef struct selector {
  56         char    *sel_name;
  57         int     (*sel_selector)(di_node_t node, rcm_arg_t *rp);
  58 } di_selector_t;
  59 
  60 static void rio_assert(di_retire_t *dp, const char *EXstr, int line,
  61     const char *file);
  62 
  63 #define LIBRCM_PATH     "/usr/lib/librcm.so"
  64 #define RIO_ASSERT(d, x)        \
  65                 {if (!(x)) rio_assert(d, #x, __LINE__, __FILE__); }
  66 
  67 static int disk_select(di_node_t node, rcm_arg_t *rp);
  68 static int nexus_select(di_node_t node, rcm_arg_t *rp);
  69 static int enclosure_select(di_node_t node, rcm_arg_t *rp);
  70 static int smp_select(di_node_t node, rcm_arg_t *rp);
  71 
  72 di_selector_t supported_devices[] = {
  73         {"disk",        disk_select},
  74         {"nexus",       nexus_select},
  75         {"enclosure",   enclosure_select},
  76         {"smp",         smp_select},
  77         {NULL,          NULL}
  78 };
  79 
  80 void *
  81 s_calloc(size_t nelem, size_t elsize, int fail)
  82 {
  83         if (fail) {
  84                 errno = ENOMEM;
  85                 return (NULL);
  86         } else {
  87                 return (calloc(nelem, elsize));
  88         }
  89 }
  90 
  91 static void
  92 rio_assert(di_retire_t *dp, const char *EXstr, int line, const char *file)
  93 {
  94         char    buf[PATH_MAX];
  95 
  96         if (dp->rt_abort == NULL)
  97                 assert(0);
  98 
  99         (void) snprintf(buf, sizeof (buf),
 100             "Assertion failed: %s, file %s, line %d\n",
 101             EXstr, file, line);
 102         dp->rt_abort(dp->rt_hdl, buf);
 103 }
 104 
 105 /*ARGSUSED*/
 106 static int
 107 enclosure_minor(di_node_t node, di_minor_t minor, void *arg)
 108 {
 109         rcm_arg_t *rp = (rcm_arg_t *)arg;
 110         di_retire_t *dp = rp->rcm_dp;
 111 
 112         rp->rcm_supp = 1;
 113         dp->rt_debug(dp->rt_hdl, "[INFO]: enclosure_minor: "
 114             "IDed this node as enclosure\n");
 115         return (DI_WALK_TERMINATE);
 116 }
 117 
 118 static int
 119 enclosure_select(di_node_t node, rcm_arg_t *rp)
 120 {
 121         rcm_arg_t rarg;
 122         di_retire_t     *dp = rp->rcm_dp;
 123 
 124         rarg.rcm_dp = dp;
 125 
 126         /*
 127          * Check if this is an enclosure minor. If any one minor is DDI_NT_SGEN
 128          * or DDI_NT_SCSI_ENCLOSURE we assume it is an enclosure.
 129          */
 130         rarg.rcm_supp = 0;
 131         if (di_walk_minor(node, DDI_NT_SCSI_ENCLOSURE, 0, &rarg,
 132             enclosure_minor) != 0) {
 133                 dp->rt_debug(dp->rt_hdl, "[INFO]: enclosure_select:"
 134                     "di_walk_minor failed. Returning NOTSUP\n");
 135                 return (0);
 136         }
 137         if (di_walk_minor(node, "ddi_generic:scsi", 0, &rarg,
 138             enclosure_minor) != 0) {
 139                 dp->rt_debug(dp->rt_hdl, "[INFO]: enclosure_select:"
 140                     "di_walk_minor failed. Returning NOTSUP\n");
 141                 return (0);
 142         }
 143 
 144         return (rarg.rcm_supp);
 145 }
 146 
 147 /*ARGSUSED*/
 148 static int
 149 smp_minor(di_node_t node, di_minor_t minor, void *arg)
 150 {
 151         rcm_arg_t *rp = (rcm_arg_t *)arg;
 152         di_retire_t *dp = rp->rcm_dp;
 153 
 154         rp->rcm_supp = 1;
 155         dp->rt_debug(dp->rt_hdl, "[INFO]: smp_minor: "
 156             "IDed this node as smp\n");
 157         return (DI_WALK_TERMINATE);
 158 }
 159 
 160 static int
 161 smp_select(di_node_t node, rcm_arg_t *rp)
 162 {
 163         rcm_arg_t rarg;
 164         di_retire_t     *dp = rp->rcm_dp;
 165 
 166         rarg.rcm_dp = dp;
 167 
 168         /*
 169          * Check if this is an smp minor. If any one minor is DDI_NT_SMP
 170          * we assume it is an smp.
 171          */
 172         rarg.rcm_supp = 0;
 173         if (di_walk_minor(node, DDI_NT_SMP, 0, &rarg, smp_minor) != 0) {
 174                 dp->rt_debug(dp->rt_hdl, "[INFO]: smp_select:"
 175                     "di_walk_minor failed. Returning NOTSUP\n");
 176                 return (0);
 177         }
 178 
 179         return (rarg.rcm_supp);
 180 }
 181 
 182 /*ARGSUSED*/
 183 static int
 184 disk_minor(di_node_t node, di_minor_t minor, void *arg)
 185 {
 186         rcm_arg_t *rp = (rcm_arg_t *)arg;
 187         di_retire_t *dp = rp->rcm_dp;
 188 
 189         if (di_minor_spectype(minor) == S_IFBLK) {
 190                 rp->rcm_supp = 1;
 191                 dp->rt_debug(dp->rt_hdl, "[INFO]: disk_minor: is disk minor. "
 192                     "IDed this node as disk\n");
 193                 return (DI_WALK_TERMINATE);
 194         }
 195 
 196         dp->rt_debug(dp->rt_hdl, "[INFO]: disk_minor: Not a disk minor. "
 197             "Continuing minor walk\n");
 198         return (DI_WALK_CONTINUE);
 199 }
 200 
 201 static int
 202 disk_select(di_node_t node, rcm_arg_t *rp)
 203 {
 204         rcm_arg_t rarg;
 205         di_retire_t     *dp = rp->rcm_dp;
 206 
 207         rarg.rcm_dp = dp;
 208 
 209         /*
 210          * Check if this is a disk minor. If any one minor is DDI_NT_BLOCK
 211          * we assume it is a disk
 212          */
 213         rarg.rcm_supp = 0;
 214         if (di_walk_minor(node, DDI_NT_BLOCK, 0, &rarg, disk_minor) != 0) {
 215                 dp->rt_debug(dp->rt_hdl, "[INFO]: disk_select: di_walk_minor "
 216                     "failed. Returning NOTSUP\n");
 217                 return (0);
 218         }
 219 
 220         return (rarg.rcm_supp);
 221 }
 222 
 223 static int
 224 nexus_select(di_node_t node, rcm_arg_t *rp)
 225 {
 226         int select;
 227         char *path;
 228 
 229         di_retire_t *dp = rp->rcm_dp;
 230 
 231         path = di_devfs_path(node);
 232         if (path == NULL) {
 233                 dp->rt_debug(dp->rt_hdl, "[INFO]: nexus_select: "
 234                     "di_devfs_path() is NULL. Returning NOTSUP\n");
 235                 return (0);
 236         }
 237 
 238         /*
 239          * Check if it is a nexus
 240          */
 241         if (di_driver_ops(node) & DI_BUS_OPS) {
 242                 dp->rt_debug(dp->rt_hdl, "[INFO]: nexus_select: is nexus %s\n",
 243                     path);
 244                 select = 1;
 245         } else {
 246                 dp->rt_debug(dp->rt_hdl, "[INFO]: nexus_select: not nexus %s\n",
 247                     path);
 248                 select = 0;
 249         }
 250 
 251         di_devfs_path_free(path);
 252 
 253         return (select);
 254 }
 255 
 256 static int
 257 node_select(di_node_t node, void *arg)
 258 {
 259         rcm_arg_t *rp = (rcm_arg_t *)arg;
 260         di_retire_t *dp;
 261         int     sel;
 262         int     i;
 263         char    *path;
 264         uint_t  state;
 265 
 266         dp = rp->rcm_dp;
 267 
 268         /* skip pseudo nodes - we only retire real hardware */
 269         path = di_devfs_path(node);
 270         if (strncmp(path, "/pseudo/", strlen("/pseudo/")) == 0 ||
 271             strcmp(path, "/pseudo") == 0) {
 272                 dp->rt_debug(dp->rt_hdl, "[INFO]: node_select: "
 273                     "pseudo device in subtree - returning NOTSUP: %s\n",
 274                     path);
 275                 rp->rcm_supp = 0;
 276                 di_devfs_path_free(path);
 277                 return (DI_WALK_TERMINATE);
 278         }
 279         di_devfs_path_free(path);
 280 
 281         /*
 282          * If a device is offline/detached/down it is
 283          * retireable irrespective of the type of device,
 284          * presumably the system is able to function without
 285          * it.
 286          */
 287         state = di_state(node);
 288         if ((state & DI_DRIVER_DETACHED) || (state & DI_DEVICE_OFFLINE) ||
 289             (state & DI_BUS_DOWN)) {
 290                 dp->rt_debug(dp->rt_hdl, "[INFO]: node_select: device "
 291                     "is offline/detached. Assuming retire supported\n");
 292                 return (DI_WALK_CONTINUE);
 293         }
 294 
 295         sel = 0;
 296         for (i = 0; supported_devices[i].sel_name != NULL; i++) {
 297                 sel = supported_devices[i].sel_selector(node, rp);
 298                 if (sel == 1) {
 299                         dp->rt_debug(dp->rt_hdl, "[INFO]: node_select: "
 300                             "found supported device: %s\n",
 301                             supported_devices[i].sel_name);
 302                         break;
 303                 }
 304         }
 305 
 306         if (sel != 1) {
 307                 /*
 308                  * This node is not a supported device. Retire cannot proceed
 309                  */
 310                 dp->rt_debug(dp->rt_hdl, "[INFO]: node_select: found "
 311                     "unsupported device. Returning NOTSUP\n");
 312                 rp->rcm_supp = 0;
 313                 return (DI_WALK_TERMINATE);
 314         }
 315 
 316         /*
 317          * This node is supported. Check other nodes in this subtree.
 318          */
 319         dp->rt_debug(dp->rt_hdl, "[INFO]: node_select: This node supported. "
 320             "Checking other nodes in subtree: %s\n", rp->rcm_root);
 321         return (DI_WALK_CONTINUE);
 322 }
 323 
 324 
 325 
 326 /*
 327  * when in doubt assume that retire is not supported for this device.
 328  */
 329 static int
 330 retire_supported(rcm_arg_t *rp)
 331 {
 332         di_retire_t     *dp;
 333         di_node_t rnode = rp->rcm_node;
 334 
 335         dp = rp->rcm_dp;
 336 
 337         /*
 338          * We should not be here if devinfo snapshot is NULL.
 339          */
 340         RIO_ASSERT(dp, rnode != DI_NODE_NIL);
 341 
 342         /*
 343          * Note: We initally set supported to 1, then walk the
 344          * subtree rooted at devpath, allowing each node the
 345          * opportunity to veto the support. We cannot do things
 346          * the other way around i.e. assume "not supported" and
 347          * let individual nodes indicate that they are supported.
 348          * In the latter case, the supported flag would be set
 349          * if any one node in the subtree was supported which is
 350          * not what we want.
 351          */
 352         rp->rcm_supp = 1;
 353         if (di_walk_node(rnode, DI_WALK_CLDFIRST, rp, node_select) != 0) {
 354                 dp->rt_debug(dp->rt_hdl, "[ERROR]: retire_supported: "
 355                     "di_walk_node: failed. Returning NOTSUP\n");
 356                 rp->rcm_supp = 0;
 357         }
 358 
 359         if (rp->rcm_supp) {
 360                 dp->rt_debug(dp->rt_hdl, "[INFO]: retire IS supported\n");
 361         }
 362 
 363         return (rp->rcm_supp);
 364 }
 365 
 366 static void
 367 rcm_finalize(rcm_arg_t *rp, int retcode)
 368 {
 369         rio_path_t      *p;
 370         rio_path_t      *tmp;
 371         int             flags = RCM_RETIRE_NOTIFY;
 372         int             retval;
 373         int             error;
 374         di_retire_t     *dp;
 375 
 376         dp = rp->rcm_dp;
 377 
 378         RIO_ASSERT(dp, retcode == 0 || retcode == -1);
 379 
 380         dp->rt_debug(dp->rt_hdl, "[INFO]: rcm_finalize: retcode=%d: dev=%s\n",
 381             retcode, rp->rcm_root);
 382 
 383         for (p = rp->rcm_cons_nodes; p; ) {
 384                 tmp = p;
 385                 p = tmp->rpt_next;
 386                 free(tmp);
 387         }
 388         rp->rcm_cons_nodes = NULL;
 389 
 390         dp->rt_debug(dp->rt_hdl, "[INFO]: rcm_finalize: cons_nodes NULL\n");
 391 
 392         for (p = rp->rcm_rsrc_minors; p; ) {
 393                 tmp = p;
 394                 p = tmp->rpt_next;
 395                 if (retcode == 0) {
 396                         retval = rp->rcm_remove(rp->rcm_handle,
 397                             tmp->rpt_path, flags, NULL);
 398                         error = errno;
 399                 } else {
 400                         RIO_ASSERT(dp, retcode == -1);
 401                         retval = rp->rcm_online(rp->rcm_handle,
 402                             tmp->rpt_path, flags, NULL);
 403                         error = errno;
 404                 }
 405                 if (retval != RCM_SUCCESS) {
 406                         dp->rt_debug(dp->rt_hdl, "[ERROR]: rcm_finalize: "
 407                             "rcm_%s: retval=%d: error=%s: path=%s\n",
 408                             retcode == 0 ? "remove" : "online", retval,
 409                             strerror(error), tmp->rpt_path);
 410                 } else {
 411                         dp->rt_debug(dp->rt_hdl, "[INFO]: rcm_finalize: "
 412                             "rcm_%s: SUCCESS: path=%s\n",
 413                             retcode == 0 ? "remove" : "online", tmp->rpt_path);
 414                 }
 415                 free(tmp);
 416         }
 417         rp->rcm_rsrc_minors = NULL;
 418 }
 419 /*ARGSUSED*/
 420 static int
 421 call_offline(di_node_t node, di_minor_t minor, void *arg)
 422 {
 423         rcm_arg_t       *rp = (rcm_arg_t *)arg;
 424         di_retire_t     *dp = rp->rcm_dp;
 425         char            *mnp;
 426         rio_path_t      *rpt;
 427         int             retval;
 428 
 429         mnp = di_devfs_minor_path(minor);
 430         if (mnp == NULL) {
 431                 dp->rt_debug(dp->rt_hdl, "[ERROR]: di_devfs_minor_path "
 432                     "failed. Returning RCM FAILURE: %s\n", rp->rcm_root);
 433                 rp->rcm_retcode = RCM_FAILURE;
 434                 return (DI_WALK_TERMINATE);
 435         }
 436 
 437         rpt = s_calloc(1, sizeof (rio_path_t), 0);
 438         if (rpt == NULL) {
 439                 dp->rt_debug(dp->rt_hdl, "[ERROR]: calloc failed. "
 440                     "Returning RCM FAILURE: %s\n", rp->rcm_root);
 441                 di_devfs_path_free(mnp);
 442                 rp->rcm_retcode = RCM_FAILURE;
 443                 return (DI_WALK_TERMINATE);
 444         }
 445 
 446         (void) snprintf(rpt->rpt_path, sizeof (rpt->rpt_path),
 447             "/devices%s", mnp);
 448 
 449         di_devfs_path_free(mnp);
 450 
 451         retval = rp->rcm_offline(rp->rcm_handle, rpt->rpt_path,
 452             RCM_RETIRE_REQUEST, NULL);
 453 
 454         rpt->rpt_next = rp->rcm_rsrc_minors;
 455         rp->rcm_rsrc_minors = rpt;
 456 
 457         if (retval == RCM_FAILURE) {
 458                 dp->rt_debug(dp->rt_hdl, "[ERROR]: RCM OFFLINE failed "
 459                     "for: %s\n", rpt->rpt_path);
 460                 rp->rcm_retcode = RCM_FAILURE;
 461                 return (DI_WALK_TERMINATE);
 462         } else if (retval == RCM_SUCCESS) {
 463                 rp->rcm_retcode = RCM_SUCCESS;
 464                 dp->rt_debug(dp->rt_hdl, "[INFO]: RCM OFFLINE returned "
 465                     "RCM_SUCCESS: %s\n", rpt->rpt_path);
 466         } else if (retval != RCM_NO_CONSTRAINT) {
 467                 dp->rt_debug(dp->rt_hdl, "[ERROR]: RCM OFFLINE returned "
 468                     "invalid value for: %s\n", rpt->rpt_path);
 469                 rp->rcm_retcode = RCM_FAILURE;
 470                 return (DI_WALK_TERMINATE);
 471         } else {
 472                 dp->rt_debug(dp->rt_hdl, "[INFO]: RCM OFFLINE returned "
 473                     "RCM_NO_CONSTRAINT: %s\n", rpt->rpt_path);
 474         }
 475 
 476         return (DI_WALK_CONTINUE);
 477 }
 478 
 479 static int
 480 offline_one(di_node_t node, void *arg)
 481 {
 482         rcm_arg_t       *rp = (rcm_arg_t *)arg;
 483         rio_path_t      *rpt;
 484         di_retire_t     *dp = rp->rcm_dp;
 485         char            *path;
 486 
 487         /*
 488          * We should already have terminated the walk
 489          * in case of failure
 490          */
 491         RIO_ASSERT(dp, rp->rcm_retcode == RCM_SUCCESS ||
 492             rp->rcm_retcode == RCM_NO_CONSTRAINT);
 493 
 494         dp->rt_debug(dp->rt_hdl, "[INFO]: offline_one: entered\n");
 495 
 496         rp->rcm_retcode = RCM_NO_CONSTRAINT;
 497 
 498         rpt = s_calloc(1, sizeof (rio_path_t), 0);
 499         if (rpt == NULL) {
 500                 dp->rt_debug(dp->rt_hdl, "[ERROR]: rio_path_t calloc "
 501                     "failed: error: %s\n", strerror(errno));
 502                 goto fail;
 503         }
 504 
 505         path = di_devfs_path(node);
 506         if (path == NULL) {
 507                 dp->rt_debug(dp->rt_hdl, "[ERROR]: di_devfs_path "
 508                     "failed: error: %s\n", strerror(errno));
 509                 free(rpt);
 510                 goto fail;
 511         }
 512 
 513         (void) strlcpy(rpt->rpt_path, path, sizeof (rpt->rpt_path));
 514 
 515         di_devfs_path_free(path);
 516 
 517         if (di_walk_minor(node, NULL, 0, rp, call_offline) != 0) {
 518                 dp->rt_debug(dp->rt_hdl, "[ERROR]: di_walk_minor "
 519                     "failed: error: %s: %s\n", strerror(errno), path);
 520                 free(rpt);
 521                 goto fail;
 522         }
 523 
 524         if (rp->rcm_retcode == RCM_FAILURE) {
 525                 dp->rt_debug(dp->rt_hdl, "[ERROR]: di_walk_minor "
 526                     "returned: RCM_FAILURE: %s\n", rpt->rpt_path);
 527                 free(rpt);
 528                 goto fail;
 529         } else if (rp->rcm_retcode == RCM_SUCCESS) {
 530                 dp->rt_debug(dp->rt_hdl, "[INFO]: di_walk_minor "
 531                     "returned: RCM_SUCCESS: %s\n", rpt->rpt_path);
 532                 rpt->rpt_next = rp->rcm_cons_nodes;
 533                 rp->rcm_cons_nodes = rpt;
 534         } else if (rp->rcm_retcode != RCM_NO_CONSTRAINT) {
 535                 dp->rt_debug(dp->rt_hdl, "[ERROR]: di_walk_minor "
 536                     "returned: unknown RCM error code: %d, %s\n",
 537                     rp->rcm_retcode, rpt->rpt_path);
 538                 free(rpt);
 539                 goto fail;
 540         } else {
 541                 dp->rt_debug(dp->rt_hdl, "[INFO]: di_walk_minor "
 542                     "returned: RCM_NO_CONSTRAINT: %s\n", rpt->rpt_path);
 543                 free(rpt);
 544         }
 545 
 546         /*
 547          * RCM_SUCCESS or RCM_NO_CONSTRAINT.
 548          * RCM_SUCCESS implies we overcame a constraint, so keep walking.
 549          * RCM_NO_CONSTRAINT implies no constraints applied via RCM.
 550          *      Continue walking in the hope that contracts or LDI will
 551          *      apply constraints
 552          * set retcode to RCM_SUCCESS to show that at least 1 node
 553          * completely walked
 554          */
 555         rp->rcm_retcode = RCM_SUCCESS;
 556         return (DI_WALK_CONTINUE);
 557 
 558 fail:
 559         rp->rcm_retcode = RCM_FAILURE;
 560         return (DI_WALK_TERMINATE);
 561 }
 562 
 563 /*
 564  * Returns:
 565  *      RCM_SUCCESS:  RCM constraints (if any) were applied. The
 566  *      device paths for which constraints were applied is passed
 567  *      back via the pp argument
 568  *
 569  *      RCM_FAILURE: Either RCM constraints prevent a retire or
 570  *      an error occurred
 571  */
 572 static int
 573 rcm_notify(rcm_arg_t *rp, char **pp, size_t *clen)
 574 {
 575         size_t  len;
 576         rio_path_t *p;
 577         rio_path_t *tmp;
 578         char *plistp;
 579         char *s;
 580         di_retire_t *dp;
 581         di_node_t rnode;
 582 
 583         dp = rp->rcm_dp;
 584 
 585         dp->rt_debug(dp->rt_hdl, "[INFO]: rcm_notify() entered\n");
 586 
 587         RIO_ASSERT(dp, rp->rcm_root);
 588 
 589         *pp = NULL;
 590 
 591         rnode = rp->rcm_node;
 592         if (rnode == DI_NODE_NIL) {
 593                 dp->rt_debug(dp->rt_hdl, "[ERROR]: devinfo snapshot "
 594                     "NULL. Returning no RCM constraint: %s\n", rp->rcm_root);
 595                 return (RCM_NO_CONSTRAINT);
 596         }
 597 
 598         rp->rcm_retcode = RCM_NO_CONSTRAINT;
 599         rp->rcm_cons_nodes = NULL;
 600         rp->rcm_rsrc_minors = NULL;
 601         if (di_walk_node(rnode, DI_WALK_CLDFIRST, rp, offline_one) != 0) {
 602                 dp->rt_debug(dp->rt_hdl, "[ERROR]: di_walk_node "
 603                     "failed: error: %s: %s\n", strerror(errno), rp->rcm_root);
 604                 /* online is idempotent - safe to online non-offlined nodes */
 605                 rcm_finalize(rp, -1);
 606                 rp->rcm_retcode = RCM_FAILURE;
 607                 goto out;
 608         }
 609 
 610         if (rp->rcm_retcode == RCM_FAILURE) {
 611                 dp->rt_debug(dp->rt_hdl, "[ERROR]: walk_node "
 612                     "returned retcode of RCM_FAILURE: %s\n", rp->rcm_root);
 613                 rcm_finalize(rp, -1);
 614                 goto out;
 615         }
 616 
 617         if (rp->rcm_retcode == RCM_NO_CONSTRAINT) {
 618                 dp->rt_debug(dp->rt_hdl, "[ERROR]: di_walk_node "
 619                     " - no nodes walked: RCM_NO_CONSTRAINT: %s\n",
 620                     rp->rcm_root);
 621         } else {
 622                 dp->rt_debug(dp->rt_hdl, "[INFO]: walk_node: RCM_SUCCESS\n");
 623         }
 624 
 625         /*
 626          * Convert to a sequence of NUL separated strings terminated by '\0'\0'
 627          */
 628         for (len = 0, p = rp->rcm_cons_nodes; p; p = p->rpt_next) {
 629                 RIO_ASSERT(dp, p->rpt_path);
 630                 RIO_ASSERT(dp, strlen(p->rpt_path) > 0);
 631                 len += (strlen(p->rpt_path) + 1);
 632         }
 633         len++;  /* list terminating '\0' */
 634 
 635         dp->rt_debug(dp->rt_hdl, "[INFO]: len of constraint str = %lu\n", len);
 636 
 637         plistp = s_calloc(1, len, 0);
 638         if (plistp == NULL) {
 639                 dp->rt_debug(dp->rt_hdl, "[ERROR]: fail to alloc "
 640                     "constraint list: error: %s: %s\n", strerror(errno),
 641                     rp->rcm_root);
 642                 rcm_finalize(rp, -1);
 643                 rp->rcm_retcode = RCM_FAILURE;
 644                 goto out;
 645         }
 646 
 647         for (s = plistp, p = rp->rcm_cons_nodes; p; ) {
 648                 tmp = p;
 649                 p = tmp->rpt_next;
 650                 (void) strcpy(s, tmp->rpt_path);
 651                 s += strlen(s) + 1;
 652                 RIO_ASSERT(dp, s - plistp < len);
 653                 free(tmp);
 654         }
 655         rp->rcm_cons_nodes = NULL;
 656         RIO_ASSERT(dp, s - plistp == len - 1);
 657         *s = '\0';
 658 
 659         dp->rt_debug(dp->rt_hdl, "[INFO]: constraint str = %p\n", plistp);
 660 
 661         *pp = plistp;
 662         *clen = len;
 663 
 664         rp->rcm_retcode = RCM_SUCCESS;
 665 out:
 666         return (rp->rcm_retcode);
 667 }
 668 
 669 
 670 /*ARGSUSED*/
 671 int
 672 di_retire_device(char *devpath, di_retire_t *dp, int flags)
 673 {
 674         char path[PATH_MAX];
 675         struct stat sb;
 676         int retval = EINVAL;
 677         char *constraint = NULL;
 678         size_t clen;
 679         void *librcm_hdl;
 680         rcm_arg_t rarg = {0};
 681         int (*librcm_alloc_handle)();
 682         int (*librcm_free_handle)();
 683 
 684         if (dp == NULL || dp->rt_debug == NULL || dp->rt_hdl == NULL)
 685                 return (EINVAL);
 686 
 687         if (devpath == NULL || devpath[0] == '\0') {
 688                 dp->rt_debug(dp->rt_hdl, "[ERROR]: NULL argument(s)\n");
 689                 return (EINVAL);
 690         }
 691 
 692         if (devpath[0] != '/' || strlen(devpath) >= PATH_MAX ||
 693             strncmp(devpath, "/devices/", strlen("/devices/")) == 0 ||
 694             strstr(devpath, "../devices/") || strrchr(devpath, ':')) {
 695                 dp->rt_debug(dp->rt_hdl, "[ERROR]: invalid devpath: %s\n",
 696                     devpath);
 697                 return (EINVAL);
 698         }
 699 
 700         if (flags != 0) {
 701                 dp->rt_debug(dp->rt_hdl, "[ERROR]: flags should be 0: %d\n",
 702                     flags);
 703                 return (EINVAL);
 704         }
 705 
 706         /*
 707          * dlopen rather than link against librcm since libdevinfo
 708          * resides in / and librcm resides in /usr. The dlopen is
 709          * safe to do since fmd which invokes the retire code
 710          * resides on /usr and will not come here until /usr is
 711          * mounted.
 712          */
 713         librcm_hdl = dlopen(LIBRCM_PATH, RTLD_LAZY);
 714         if (librcm_hdl == NULL) {
 715                 char *errstr = dlerror();
 716                 dp->rt_debug(dp->rt_hdl, "[ERROR]: Cannot dlopen librcm: %s\n",
 717                     errstr ? errstr : "Unknown error");
 718                 return (ENOSYS);
 719         }
 720 
 721         librcm_alloc_handle = (int (*)())dlsym(librcm_hdl, "rcm_alloc_handle");
 722         rarg.rcm_offline = (int (*)())dlsym(librcm_hdl, "rcm_request_offline");
 723         rarg.rcm_online = (int (*)())dlsym(librcm_hdl, "rcm_notify_online");
 724         rarg.rcm_remove = (int (*)())dlsym(librcm_hdl, "rcm_notify_remove");
 725         librcm_free_handle = (int (*)())dlsym(librcm_hdl, "rcm_free_handle");
 726 
 727         if (librcm_alloc_handle == NULL ||
 728             rarg.rcm_offline == NULL ||
 729             rarg.rcm_online == NULL ||
 730             rarg.rcm_remove == NULL ||
 731             librcm_free_handle == NULL) {
 732                 dp->rt_debug(dp->rt_hdl, "[ERROR]: dlsym failed\n");
 733                 retval = ENOSYS;
 734                 goto out;
 735         }
 736 
 737         /*
 738          * Take a libdevinfo snapshot here because we cannot do so
 739          * after device is retired. If device doesn't attach, we retire
 740          * anyway i.e. it is not fatal.
 741          */
 742         rarg.rcm_node = di_init(devpath, DINFOCPYALL);
 743         if (rarg.rcm_node == DI_NODE_NIL) {
 744                 dp->rt_debug(dp->rt_hdl, "[ERROR]: device doesn't attach, "
 745                     "retiring anyway: %s\n", devpath);
 746         }
 747 
 748         rarg.rcm_handle = NULL;
 749         if (librcm_alloc_handle(NULL, 0,  NULL, &rarg.rcm_handle)
 750             != RCM_SUCCESS) {
 751                 retval = errno;
 752                 dp->rt_debug(dp->rt_hdl, "[ERROR]: failed to alloc "
 753                     "RCM handle. Returning RCM failure: %s\n", devpath);
 754                 rarg.rcm_handle = NULL;
 755                 goto out;
 756         }
 757 
 758         rarg.rcm_root = devpath;
 759         rarg.rcm_dp = dp;
 760 
 761         /*
 762          * If device is already detached/nonexistent and cannot be
 763          * attached, allow retire without checking device type.
 764          * XXX
 765          * Else, check if retire is supported for this device type.
 766          */
 767         (void) snprintf(path, sizeof (path), "/devices%s", devpath);
 768         if (stat(path, &sb) == -1 || !S_ISDIR(sb.st_mode)) {
 769                 dp->rt_debug(dp->rt_hdl, "[ERROR]: detached or nonexistent "
 770                     "device. Bypassing retire_supported: %s\n", devpath);
 771         } else if (!retire_supported(&rarg)) {
 772                 dp->rt_debug(dp->rt_hdl, "[ERROR]: retire not supported for "
 773                     "device type: %s\n", devpath);
 774                 retval = ENOTSUP;
 775                 goto out;
 776         }
 777 
 778         clen = 0;
 779         constraint = NULL;
 780         retval = rcm_notify(&rarg, &constraint, &clen);
 781         if (retval == RCM_FAILURE) {
 782                 /* retire not permitted */
 783                 dp->rt_debug(dp->rt_hdl, "[ERROR]: RCM constraints block "
 784                     "retire: %s\n", devpath);
 785                 retval = EBUSY;
 786                 goto out;
 787         } else if (retval == RCM_SUCCESS) {
 788                 dp->rt_debug(dp->rt_hdl, "[INFO]: RCM constraints applied"
 789                     ": %s\n", devpath);
 790         } else if (retval == RCM_NO_CONSTRAINT) {
 791                 dp->rt_debug(dp->rt_hdl, "[INFO]: No RCM constraints applied"
 792                     ": %s\n", devpath);
 793         } else {
 794                 dp->rt_debug(dp->rt_hdl, "[ERROR]: notify returned unknown "
 795                     "return code: %d: %s\n", retval, devpath);
 796                 retval = ESRCH;
 797                 goto out;
 798         }
 799 
 800         if (modctl(MODRETIRE, devpath, constraint, clen) != 0) {
 801                 retval = errno;
 802                 dp->rt_debug(dp->rt_hdl, "[ERROR]: retire modctl() failed: "
 803                     "%s: %s\n", devpath, strerror(retval));
 804                 rcm_finalize(&rarg, -1);
 805                 goto out;
 806         }
 807 
 808         dp->rt_debug(dp->rt_hdl, "[INFO]: retire modctl() succeeded: %s\n",
 809             devpath);
 810 
 811         rcm_finalize(&rarg, 0);
 812 
 813         retval = 0;
 814 
 815 out:
 816         if (rarg.rcm_handle)
 817                 (void) librcm_free_handle(rarg.rcm_handle);
 818 
 819         RIO_ASSERT(dp, rarg.rcm_cons_nodes == NULL);
 820         RIO_ASSERT(dp, rarg.rcm_rsrc_minors == NULL);
 821 
 822         (void) dlclose(librcm_hdl);
 823 
 824         free(constraint);
 825 
 826         if (rarg.rcm_node != DI_NODE_NIL)
 827                 di_fini(rarg.rcm_node);
 828 
 829         return (retval);
 830 }
 831 
 832 /*ARGSUSED*/
 833 int
 834 di_unretire_device(char *devpath, di_retire_t *dp)
 835 {
 836         if (dp == NULL || dp->rt_debug == NULL || dp->rt_hdl == NULL)
 837                 return (EINVAL);
 838 
 839         if (devpath == NULL || devpath[0] == '\0') {
 840                 dp->rt_debug(dp->rt_hdl, "[ERROR]: NULL devpath\n");
 841                 return (EINVAL);
 842         }
 843 
 844         if (devpath[0] != '/' || strlen(devpath) >= PATH_MAX ||
 845             strncmp(devpath, "/devices/", strlen("/devices/")) == 0 ||
 846             strstr(devpath, "../devices/") || strrchr(devpath, ':')) {
 847                 dp->rt_debug(dp->rt_hdl, "[ERROR]: invalid devpath: %s\n",
 848                     devpath);
 849                 return (EINVAL);
 850         }
 851 
 852         if (modctl(MODUNRETIRE, devpath) != 0) {
 853                 int err = errno;
 854                 dp->rt_debug(dp->rt_hdl, "[ERROR]: unretire modctl() failed: "
 855                     "%s: %s\n", devpath, strerror(err));
 856                 return (err);
 857         }
 858 
 859         dp->rt_debug(dp->rt_hdl, "[INFO]: unretire modctl() done: %s\n",
 860             devpath);
 861 
 862         return (0);
 863 }