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.
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.
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]
22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
27 * RCM module supporting multiplexed I/O controllers (MPxIO).
40 #include <libdevinfo.h>
41 #include <sys/types.h>
42 #include "rcm_module.h"
44 #define MPXIO_PROP_NAME "mpxio-component"
45 #define MPXIO_PROP_CLIENT "client"
53 #define CACHE_REFERENCED 1
56 #define MPXIO_MSG_CACHEFAIL gettext("Internal analysis failure.")
57 #define MPXIO_MSG_LASTPATH gettext("Last path to busy resources.")
58 #define MPXIO_MSG_USAGE gettext("SCSI Multipathing PHCI (%s)")
59 #define MPXIO_MSG_USAGEUNKNOWN gettext("SCSI Multipathing PHCI (<unknown>)")
63 di_path_state_t state
;
66 typedef struct phci_list
{
69 struct phci_list
*next
;
72 typedef struct group
{
81 static int mpxio_register(rcm_handle_t
*);
82 static int mpxio_unregister(rcm_handle_t
*);
83 static int mpxio_getinfo(rcm_handle_t
*, char *, id_t
, uint_t
, char **, char **,
84 nvlist_t
*, rcm_info_t
**);
85 static int mpxio_suspend(rcm_handle_t
*, char *, id_t
, timespec_t
*, uint_t
,
86 char **, rcm_info_t
**);
87 static int mpxio_resume(rcm_handle_t
*, char *, id_t
, uint_t
, char **,
89 static int mpxio_offline(rcm_handle_t
*, char *, id_t
, uint_t
, char **,
91 static int mpxio_online(rcm_handle_t
*, char *, id_t
, uint_t
, char **,
93 static int mpxio_remove(rcm_handle_t
*, char *, id_t
, uint_t
, char **,
95 static int get_nclients(di_node_t
, void *);
96 static int build_groups(di_node_t
, void *);
97 static void refresh_regs(rcm_handle_t
*);
98 static int get_affected_clients(rcm_handle_t
*, char *, int, int, char ***);
99 static int detect_client_change(rcm_handle_t
*, int, int, group_t
*, char *);
100 static int merge_clients(int *, char ***, group_t
*);
101 static phci_list_t
*lookup_phci(char *);
102 static int is_client(di_node_t
);
103 static char *get_rsrcname(di_node_t
);
104 static char *s_state(di_path_state_t
);
105 static int compare_phci(const void *, const void *);
106 static void free_grouplist();
107 static void free_group(group_t
*);
108 static void free_clients(int, char **);
109 static void free_phcis(int, phci_t
*);
111 static struct rcm_mod_ops mpxio_ops
=
127 static group_t
*group_list
;
128 static phci_list_t
*reg_list
;
129 static mutex_t mpxio_lock
;
134 * Return the mod-ops vector for initialization.
139 rcm_log_message(RCM_TRACE1
, "MPXIO: rcm_mod_init()\n");
145 * Return name and version number for mod_info.
150 rcm_log_message(RCM_TRACE1
, "MPXIO: rcm_mod_info()\n");
152 return (gettext("RCM MPxIO module 1.6"));
156 * Destroy the cache and mutex lock when being unloaded.
164 rcm_log_message(RCM_TRACE1
, "MPXIO: rcm_mod_fini()\n");
166 /* Free the cache of MPxIO group information */
169 /* Free the cache of registrants */
173 free(reg
->phci
.path
);
178 /* Destroy the mutex for locking the caches */
179 (void) mutex_destroy(&mpxio_lock
);
181 return (RCM_SUCCESS
);
185 * During each register callback: totally rebuild the group list from a new
186 * libdevinfo snapshot, and then update the registrants.
189 mpxio_register(rcm_handle_t
*hdl
)
194 rcm_log_message(RCM_TRACE1
, "MPXIO: register()\n");
196 (void) mutex_lock(&mpxio_lock
);
198 /* Destroy the previous group list */
201 /* Get a current libdevinfo snapshot */
202 if ((devroot
= di_init("/", DINFOCPYALL
| DINFOPATH
)) == DI_NODE_NIL
) {
203 rcm_log_message(RCM_ERROR
,
204 "MPXIO: libdevinfo initialization failed (%s).\n",
206 (void) mutex_unlock(&mpxio_lock
);
207 return (RCM_FAILURE
);
211 * First count the total number of clients. This'll be a useful
212 * upper bound when allocating client arrays within each group.
214 (void) di_walk_node(devroot
, DI_WALK_CLDFIRST
, &nclients
, get_nclients
);
216 rcm_log_message(RCM_TRACE2
, gettext("MPXIO: found %d clients.\n"),
220 * Then walk the libdevinfo snapshot, building up the new group list
221 * along the way. Pass in the total number of clients (from above) to
222 * assist in group construction.
224 (void) di_walk_node(devroot
, DI_WALK_CLDFIRST
, &nclients
, build_groups
);
226 /* Now with a new group list constructed, refresh the registrants */
229 /* Free the libdevinfo snapshot */
232 (void) mutex_unlock(&mpxio_lock
);
238 * Unregister all PHCIs and mark the whole registrants list as stale.
241 mpxio_unregister(rcm_handle_t
*hdl
)
245 rcm_log_message(RCM_TRACE1
, "MPXIO: unregister()\n");
247 (void) mutex_lock(&mpxio_lock
);
249 for (reg
= reg_list
; reg
!= NULL
; reg
= reg
->next
) {
250 (void) rcm_unregister_interest(hdl
, reg
->phci
.path
, 0);
251 reg
->referenced
= CACHE_STALE
;
254 (void) mutex_unlock(&mpxio_lock
);
256 return (RCM_SUCCESS
);
260 * To return usage information, just lookup the PHCI in the cache and return
261 * a string identifying that it's a PHCI and describing its cached MPxIO state.
262 * Recurse with the cached list of disks if dependents are to be included.
265 mpxio_getinfo(rcm_handle_t
*hdl
, char *rsrc
, id_t id
, uint_t flags
,
266 char **infostr
, char **errstr
, nvlist_t
*props
, rcm_info_t
**infop
)
269 int rv
= RCM_SUCCESS
;
271 char **clients
= NULL
;
275 rcm_log_message(RCM_TRACE1
, "MPXIO: getinfo(%s)\n", rsrc
);
280 (void) mutex_lock(&mpxio_lock
);
282 if ((reg
= lookup_phci(rsrc
)) == NULL
) {
283 *errstr
= strdup(MPXIO_MSG_CACHEFAIL
);
284 (void) mutex_unlock(&mpxio_lock
);
285 return (RCM_FAILURE
);
288 len
= snprintf(&c
, 1, MPXIO_MSG_USAGE
, s_state(reg
->phci
.state
));
289 buf
= calloc(len
+ 1, sizeof (char));
290 if ((buf
== NULL
) || (snprintf(buf
, len
+ 1, MPXIO_MSG_USAGE
,
291 s_state(reg
->phci
.state
)) > len
+ 1)) {
292 *infostr
= strdup(MPXIO_MSG_USAGEUNKNOWN
);
293 *errstr
= strdup(gettext("Cannot construct usage string."));
294 (void) mutex_unlock(&mpxio_lock
);
296 return (RCM_FAILURE
);
300 if (flags
& RCM_INCLUDE_DEPENDENT
) {
301 rcm_log_message(RCM_TRACE2
, "MPXIO: getting clients\n");
302 if (get_affected_clients(hdl
, rsrc
, CMD_GETINFO
, flags
,
304 *errstr
= strdup(gettext("Cannot lookup clients."));
305 (void) mutex_unlock(&mpxio_lock
);
306 return (RCM_FAILURE
);
309 rv
= rcm_get_info_list(hdl
, clients
, flags
, infop
);
312 rcm_log_message(RCM_TRACE2
, "MPXIO: none found\n");
316 (void) mutex_unlock(&mpxio_lock
);
321 * Nothing is implemented for suspend operations.
324 mpxio_suspend(rcm_handle_t
*hdl
, char *rsrc
, id_t id
, timespec_t
*interval
,
325 uint_t flags
, char **errstr
, rcm_info_t
**infop
)
327 rcm_log_message(RCM_TRACE1
, "MPXIO: suspend(%s)\n", rsrc
);
329 return (RCM_SUCCESS
);
333 * Nothing is implemented for resume operations.
336 mpxio_resume(rcm_handle_t
*hdl
, char *rsrc
, id_t id
, uint_t flags
,
337 char **errstr
, rcm_info_t
**infop
)
339 rcm_log_message(RCM_TRACE1
, "MPXIO: resume(%s)\n", rsrc
);
341 return (RCM_SUCCESS
);
345 * MPxIO has no policy against offlining. If disks will be affected, then
346 * base the return value for this request on the results of offlining the
347 * list of disks. Otherwise succeed.
350 mpxio_offline(rcm_handle_t
*hdl
, char *rsrc
, id_t id
, uint_t flags
,
351 char **errstr
, rcm_info_t
**infop
)
353 char **clients
= NULL
;
354 int rv
= RCM_SUCCESS
;
356 rcm_log_message(RCM_TRACE1
, "MPXIO: offline(%s)\n", rsrc
);
358 (void) mutex_lock(&mpxio_lock
);
360 if (get_affected_clients(hdl
, rsrc
, CMD_OFFLINE
, flags
, &clients
) < 0) {
361 *errstr
= strdup(gettext("Cannot lookup clients."));
362 (void) mutex_unlock(&mpxio_lock
);
363 return (RCM_FAILURE
);
367 rv
= rcm_request_offline_list(hdl
, clients
, flags
, infop
);
368 if (rv
!= RCM_SUCCESS
)
369 *errstr
= strdup(MPXIO_MSG_LASTPATH
);
373 (void) mutex_unlock(&mpxio_lock
);
379 * If disks are affected, then they are probably offline and we need to
380 * propagate this online notification to them.
383 mpxio_online(rcm_handle_t
*hdl
, char *rsrc
, id_t id
, uint_t flags
,
384 char **errstr
, rcm_info_t
**infop
)
387 int rv
= RCM_SUCCESS
;
389 rcm_log_message(RCM_TRACE1
, "MPXIO: online(%s)\n", rsrc
);
391 (void) mutex_lock(&mpxio_lock
);
393 if (get_affected_clients(hdl
, rsrc
, CMD_ONLINE
, flags
, &clients
) < 0) {
394 *errstr
= strdup(gettext("Cannot lookup clients."));
395 (void) mutex_unlock(&mpxio_lock
);
396 return (RCM_FAILURE
);
400 rv
= rcm_notify_online_list(hdl
, clients
, flags
, infop
);
404 (void) mutex_unlock(&mpxio_lock
);
410 * If clients are affected, then they are probably offline and we need to
411 * propagate this removal notification to them. We can also remove the
412 * cache entry for this PHCI. If that leaves its group empty, then the
413 * group will be removed during the next register callback.
416 mpxio_remove(rcm_handle_t
*hdl
, char *rsrc
, id_t id
, uint_t flags
,
417 char **errstr
, rcm_info_t
**infop
)
420 int rv
= RCM_SUCCESS
;
422 rcm_log_message(RCM_TRACE1
, "MPXIO: remove(%s)\n", rsrc
);
424 (void) mutex_lock(&mpxio_lock
);
426 if (get_affected_clients(hdl
, rsrc
, CMD_REMOVE
, flags
, &clients
) < 0) {
427 *errstr
= strdup(gettext("Cannot lookup clients."));
428 (void) mutex_unlock(&mpxio_lock
);
429 return (RCM_FAILURE
);
433 rv
= rcm_notify_remove_list(hdl
, clients
, flags
, infop
);
437 (void) mutex_unlock(&mpxio_lock
);
444 * Returns a string representation of a given libdevinfo path state.
447 s_state(di_path_state_t state
)
450 case DI_PATH_STATE_ONLINE
:
452 case DI_PATH_STATE_OFFLINE
:
454 case DI_PATH_STATE_STANDBY
:
456 case DI_PATH_STATE_FAULT
:
459 return ("<unknown>");
464 get_affected_clients(rcm_handle_t
*hdl
, char *rsrc
, int cmd
, int flags
,
470 char **clients
= NULL
;
472 /* Build a dummy phci_t for use with bsearch(). */
475 /* Analyze the effects upon each group. */
476 for (group
= group_list
; group
!= NULL
; group
= group
->next
) {
478 /* If the PHCI isn't in the group, then no effects. Skip. */
479 if (bsearch(&phci
, group
->phcis
, group
->nphcis
, sizeof (phci_t
),
480 compare_phci
) == NULL
)
484 * Merge in the clients. All clients are merged in for getinfo
485 * operations. Otherwise it's contingent upon a state change
486 * being transferred to the clients as a result of changing
489 if ((cmd
== CMD_GETINFO
) ||
490 detect_client_change(hdl
, cmd
, flags
, group
, rsrc
)) {
491 if (merge_clients(&nclients
, &clients
, group
) < 0) {
492 free_clients(nclients
, clients
);
498 /* Return the array of affected disks */
504 * Iterates through the members of a PHCI list, returning the entry
505 * corresponding to the named PHCI resource. Returns NULL when the lookup
509 lookup_phci(char *rsrc
)
513 for (reg
= reg_list
; reg
!= NULL
; reg
= reg
->next
) {
514 if (strcmp(reg
->phci
.path
, rsrc
) == 0)
522 * Tests whether or not an operation on a specific PHCI resource would affect
523 * the array of client devices attached to the PHCI's MPxIO group.
525 * Returns: 1 if clients would be affected, 0 if not.
528 detect_client_change(rcm_handle_t
*hdl
, int cmd
, int flags
, group_t
*group
,
535 * Perform a full set analysis on the set of redundant PHCIs. When
536 * there are no unaffected and online PHCIs, then changing the state
537 * of the named PHCI results in a client state change.
539 for (i
= 0; i
< group
->nphcis
; i
++) {
541 /* Filter the named resource out of the analysis */
542 if (strcmp(group
->phcis
[i
].path
, rsrc
) == 0)
546 * If we find a path that's in the ONLINE or STANDBY state
547 * that would be left over in the system after completing
548 * whatever DR or hotplugging operation is in progress, then
551 if ((group
->phcis
[i
].state
== DI_PATH_STATE_ONLINE
) ||
552 (group
->phcis
[i
].state
== DI_PATH_STATE_STANDBY
)) {
553 if (rcm_get_rsrcstate(hdl
, group
->phcis
[i
].path
, &state
)
555 rcm_log_message(RCM_ERROR
,
556 "MPXIO: Failed to query resource state\n");
559 rcm_log_message(RCM_TRACE2
, "MPXIO: state of %s: %d\n",
560 group
->phcis
[i
].path
, state
);
561 if (state
== RCM_STATE_ONLINE
) {
568 * The analysis above didn't find a redundant path to take over. So
569 * report that the state of the client resources will change.
575 * Merges the client disks connected to a particular MPxIO group in with a
576 * previous array of disk clients. The result is to adjust the 'nclients'
577 * value with the new count of disks in the array, and to adjust the 'disks'
578 * value to be a larger array of disks including its original contents along
579 * with the current group's contents merged in.
582 merge_clients(int *nclients
, char ***clientsp
, group_t
*group
)
588 if (group
->nclients
) {
589 old_nclients
= *nclients
;
590 *nclients
+= group
->nclients
;
591 clients_new
= reallocarray(*clientsp
, *nclients
+ 1,
593 if (clients_new
== NULL
) {
594 rcm_log_message(RCM_ERROR
,
595 "MPXIO: cannot reallocate client array (%s).\n",
599 for (i
= old_nclients
; i
< (*nclients
); i
++) {
601 * Don't allocate space for individual disks in the
602 * merged list. Just make references to the previously
603 * allocated strings in the group_t structs themselves.
605 clients_new
[i
] = group
->clients
[i
- old_nclients
];
607 clients_new
[(*nclients
)] = NULL
;
608 *clientsp
= clients_new
;
615 * A libdevinfo di_walk_node() callback. It's passed an integer pointer as an
616 * argument, and it increments the integer each time it encounters an MPxIO
617 * client. By initializing the integer to zero and doing a libdevinfo walk with
618 * this function, the total count of MPxIO clients in the system can be found.
621 get_nclients(di_node_t dinode
, void *arg
)
625 if (is_client(dinode
))
628 return (DI_WALK_CONTINUE
);
632 * Tests a libdevinfo node to determine if it's an MPxIO client.
634 * Returns: non-zero for true, 0 for false.
637 is_client(di_node_t dinode
)
639 return (di_path_client_next_path(dinode
, DI_PATH_NIL
) != DI_PATH_NIL
);
643 * After a new group_list has been constructed, this refreshes the RCM
644 * registrations and the reg_list contents. It uses a clock like algorithm
645 * with reference bits in the reg_list to know which registrants are new or
649 refresh_regs(rcm_handle_t
*hdl
)
654 phci_list_t
*prev_reg
;
657 * First part of the clock-like algorithm: clear reference bits.
659 for (reg
= reg_list
; reg
!= NULL
; reg
= reg
->next
)
660 reg
->referenced
= CACHE_STALE
;
663 * Second part of the clock-like algorithm: set the reference bits
664 * on every registrant that's still active. (Also add new list nodes
665 * for new registrants.)
667 for (group
= group_list
; group
!= NULL
; group
= group
->next
) {
668 for (i
= 0; i
< group
->nphcis
; i
++) {
671 * If already stale in the registrants list, just set
672 * its reference bit to REFERENCED and update its state.
674 if ((reg
= lookup_phci(group
->phcis
[i
].path
)) != NULL
) {
675 if (reg
->referenced
== CACHE_STALE
)
676 reg
->referenced
= CACHE_REFERENCED
;
677 reg
->phci
.state
= group
->phcis
[i
].state
;
682 * Otherwise, build a new list node and mark it NEW.
684 reg
= (phci_list_t
*)calloc(1, sizeof (*reg
));
686 rcm_log_message(RCM_ERROR
,
687 "MPXIO: cannot allocate phci_list (%s).\n",
691 reg
->phci
.path
= strdup(group
->phcis
[i
].path
);
692 if (reg
->phci
.path
== NULL
) {
694 rcm_log_message(RCM_ERROR
,
695 "MPXIO: cannot allocate phci path (%s).\n",
699 reg
->phci
.state
= group
->phcis
[i
].state
;
700 reg
->referenced
= CACHE_NEW
;
702 /* Link it at the head of reg_list */
703 reg
->next
= reg_list
;
709 * Final part of the clock algorithm: unregister stale entries, and
710 * register new entries. Stale entries get removed from the list.
716 /* Unregister and remove stale entries. */
717 if (reg
->referenced
== CACHE_STALE
) {
718 (void) rcm_unregister_interest(hdl
, reg
->phci
.path
, 0);
719 free(reg
->phci
.path
);
720 if (prev_reg
== NULL
) {
721 reg_list
= reg
->next
;
725 prev_reg
->next
= reg
->next
;
727 reg
= prev_reg
->next
;
732 /* Register new entries. */
733 if (reg
->referenced
== CACHE_NEW
) {
734 if (rcm_register_interest(hdl
, reg
->phci
.path
, 0, NULL
)
736 rcm_log_message(RCM_ERROR
,
737 "MPXIO: failed to register %s (%s).\n",
738 reg
->phci
.path
, strerror(errno
));
749 * A libdevinfo di_walk_node() callback that builds up the MPxIO group list.
751 * Every node encountered that's a client node is added into a group's client
752 * list. Whenever a group doesn't already exist with a matching set of
753 * related PHCIs, then a new group is constructed and put at the head of the
757 build_groups(di_node_t dinode
, void *arg
)
761 int *nclients
= (int *)arg
;
765 di_path_t dipath
= DI_PATH_NIL
;
768 if (nclients
== NULL
)
769 return (DI_WALK_TERMINATE
);
772 * Build a sorted array of PHCIs pertaining to the client.
775 di_path_client_next_path(dinode
, dipath
)) != DI_PATH_NIL
)
778 /* Skip non-clients. */
780 return (DI_WALK_CONTINUE
);
782 if ((phcis
= (phci_t
*)calloc(nphcis
, sizeof (phci_t
))) == NULL
) {
783 rcm_log_message(RCM_ERROR
,
784 "MPXIO: failed to allocate client's PHCIs (%s).\n",
786 return (DI_WALK_TERMINATE
);
789 di_path_client_next_path(dinode
, dipath
)) != DI_PATH_NIL
) {
790 phcinode
= di_path_phci_node(dipath
);
791 if (phcinode
== DI_NODE_NIL
) {
792 free_phcis(i
, phcis
); /* free preceeding PHCIs */
793 rcm_log_message(RCM_ERROR
,
794 "MPXIO: client appears to have no PHCIs.\n");
795 return (DI_WALK_TERMINATE
);
797 if ((phcis
[i
].path
= get_rsrcname(phcinode
)) == NULL
) {
798 free_phcis(i
, phcis
);
799 return (DI_WALK_TERMINATE
);
801 phcis
[i
].state
= di_path_state(dipath
);
804 qsort(phcis
, nphcis
, sizeof (phci_t
), compare_phci
);
807 * Compare that PHCI set to each existing group's set. We just add
808 * the client to the group and exit successfully once a match is made.
809 * Falling out of this loop means no match was found.
811 for (group
= group_list
; group
!= NULL
; group
= group
->next
) {
813 /* There is no match if the number of PHCIs is inequal */
814 if (nphcis
!= group
->nphcis
)
817 /* Compare the PHCIs linearly (which is okay; they're sorted) */
818 for (i
= 0; i
< nphcis
; i
++)
819 if (strcmp(phcis
[i
].path
, group
->phcis
[i
].path
) != 0)
823 * If the loop above completed, we have a match. Add the client
824 * to the group's disk array in that case, and return
828 free_phcis(nphcis
, phcis
);
829 if ((group
->clients
[group
->nclients
] =
830 get_rsrcname(dinode
)) == NULL
)
831 return (DI_WALK_TERMINATE
);
833 return (DI_WALK_CONTINUE
);
837 /* The loop above didn't find a match. So build a new group. */
838 if ((group
= (group_t
*)calloc(1, sizeof (*group
))) == NULL
) {
839 rcm_log_message(RCM_ERROR
,
840 "MPXIO: failed to allocate PHCI group (%s).\n",
842 free_phcis(nphcis
, phcis
);
843 return (DI_WALK_TERMINATE
);
845 if ((group
->clients
= (char **)calloc(*nclients
, sizeof (char *))) ==
848 free_phcis(nphcis
, phcis
);
849 return (DI_WALK_TERMINATE
);
851 group
->nphcis
= nphcis
;
852 group
->phcis
= phcis
;
853 if ((group
->clients
[0] = get_rsrcname(dinode
)) == NULL
) {
855 return (DI_WALK_TERMINATE
);
859 /* Link the group into the group list and return successfully. */
860 group
->next
= group_list
;
862 return (DI_WALK_CONTINUE
);
866 * For bsearch() and qsort(). Returns the results of a strcmp() on the names
870 compare_phci(const void *arg1
, const void *arg2
)
872 phci_t
*p1
= (phci_t
*)arg1
;
873 phci_t
*p2
= (phci_t
*)arg2
;
875 if ((p1
== NULL
) || (p2
== NULL
)) {
883 return (strcmp(p1
->path
, p2
->path
));
887 * Free the whole list of group's in the global group_list.
892 group_t
*group
= group_list
;
905 * Free the contents of a single group_t.
908 free_group(group_t
*group
)
911 free_phcis(group
->nphcis
, group
->phcis
);
912 free_clients(group
->nclients
, group
->clients
);
918 * Free an array of clients.
921 free_clients(int nclients
, char **clients
)
925 if (clients
!= NULL
) {
927 for (i
= 0; i
< nclients
; i
++)
935 * Free an array of phci_t's.
938 free_phcis(int nphcis
, phci_t
*phcis
)
942 if ((phcis
!= NULL
) && (nphcis
> 0)) {
943 for (i
= 0; i
< nphcis
; i
++)
950 * Converts a libdevinfo node into a /devices path. Caller must free results.
953 get_rsrcname(di_node_t dinode
)
958 char name
[MAXPATHLEN
];
960 if ((devfspath
= di_devfs_path(dinode
)) == NULL
) {
961 rcm_log_message(RCM_ERROR
, "MPXIO: resource has null path.\n");
965 len
= snprintf(name
, sizeof (name
), "/devices%s", devfspath
);
966 di_devfs_path_free(devfspath
);
967 if (len
>= sizeof (name
)) {
968 rcm_log_message(RCM_ERROR
, "MPXIO: resource path too long.\n");
972 if ((rsrcname
= strdup(name
)) == NULL
)
973 rcm_log_message(RCM_ERROR
,
974 "MPXIO: failed to allocate resource name (%s).\n",