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.
26 #pragma ident "%Z%%M% %I% %E% SMI"
29 * RCM module supporting multiplexed I/O controllers (MPxIO).
42 #include <libdevinfo.h>
43 #include <sys/types.h>
44 #include "rcm_module.h"
46 #define MPXIO_PROP_NAME "mpxio-component"
47 #define MPXIO_PROP_CLIENT "client"
55 #define CACHE_REFERENCED 1
58 #define MPXIO_MSG_CACHEFAIL gettext("Internal analysis failure.")
59 #define MPXIO_MSG_LASTPATH gettext("Last path to busy resources.")
60 #define MPXIO_MSG_USAGE gettext("SCSI Multipathing PHCI (%s)")
61 #define MPXIO_MSG_USAGEUNKNOWN gettext("SCSI Multipathing PHCI (<unknown>)")
65 di_path_state_t state
;
68 typedef struct phci_list
{
71 struct phci_list
*next
;
74 typedef struct group
{
83 static int mpxio_register(rcm_handle_t
*);
84 static int mpxio_unregister(rcm_handle_t
*);
85 static int mpxio_getinfo(rcm_handle_t
*, char *, id_t
, uint_t
, char **, char **,
86 nvlist_t
*, rcm_info_t
**);
87 static int mpxio_suspend(rcm_handle_t
*, char *, id_t
, timespec_t
*, uint_t
,
88 char **, rcm_info_t
**);
89 static int mpxio_resume(rcm_handle_t
*, char *, id_t
, uint_t
, char **,
91 static int mpxio_offline(rcm_handle_t
*, char *, id_t
, uint_t
, char **,
93 static int mpxio_online(rcm_handle_t
*, char *, id_t
, uint_t
, char **,
95 static int mpxio_remove(rcm_handle_t
*, char *, id_t
, uint_t
, char **,
97 static int get_nclients(di_node_t
, void *);
98 static int build_groups(di_node_t
, void *);
99 static void refresh_regs(rcm_handle_t
*);
100 static int get_affected_clients(rcm_handle_t
*, char *, int, int, char ***);
101 static int detect_client_change(rcm_handle_t
*, int, int, group_t
*, char *);
102 static int merge_clients(int *, char ***, group_t
*);
103 static phci_list_t
*lookup_phci(char *);
104 static int is_client(di_node_t
);
105 static char *get_rsrcname(di_node_t
);
106 static char *s_state(di_path_state_t
);
107 static int compare_phci(const void *, const void *);
108 static void free_grouplist();
109 static void free_group(group_t
*);
110 static void free_clients(int, char **);
111 static void free_phcis(int, phci_t
*);
113 static struct rcm_mod_ops mpxio_ops
=
129 static group_t
*group_list
;
130 static phci_list_t
*reg_list
;
131 static mutex_t mpxio_lock
;
136 * Return the mod-ops vector for initialization.
141 rcm_log_message(RCM_TRACE1
, "MPXIO: rcm_mod_init()\n");
147 * Return name and version number for mod_info.
152 rcm_log_message(RCM_TRACE1
, "MPXIO: rcm_mod_info()\n");
154 return (gettext("RCM MPxIO module 1.6"));
158 * Destroy the cache and mutex lock when being unloaded.
166 rcm_log_message(RCM_TRACE1
, "MPXIO: rcm_mod_fini()\n");
168 /* Free the cache of MPxIO group information */
171 /* Free the cache of registrants */
175 free(reg
->phci
.path
);
180 /* Destroy the mutex for locking the caches */
181 (void) mutex_destroy(&mpxio_lock
);
183 return (RCM_SUCCESS
);
187 * During each register callback: totally rebuild the group list from a new
188 * libdevinfo snapshot, and then update the registrants.
191 mpxio_register(rcm_handle_t
*hdl
)
196 rcm_log_message(RCM_TRACE1
, "MPXIO: register()\n");
198 (void) mutex_lock(&mpxio_lock
);
200 /* Destroy the previous group list */
203 /* Get a current libdevinfo snapshot */
204 if ((devroot
= di_init("/", DINFOCPYALL
| DINFOPATH
)) == DI_NODE_NIL
) {
205 rcm_log_message(RCM_ERROR
,
206 "MPXIO: libdevinfo initialization failed (%s).\n",
208 (void) mutex_unlock(&mpxio_lock
);
209 return (RCM_FAILURE
);
213 * First count the total number of clients. This'll be a useful
214 * upper bound when allocating client arrays within each group.
216 (void) di_walk_node(devroot
, DI_WALK_CLDFIRST
, &nclients
, get_nclients
);
218 rcm_log_message(RCM_TRACE2
, gettext("MPXIO: found %d clients.\n"),
222 * Then walk the libdevinfo snapshot, building up the new group list
223 * along the way. Pass in the total number of clients (from above) to
224 * assist in group construction.
226 (void) di_walk_node(devroot
, DI_WALK_CLDFIRST
, &nclients
, build_groups
);
228 /* Now with a new group list constructed, refresh the registrants */
231 /* Free the libdevinfo snapshot */
234 (void) mutex_unlock(&mpxio_lock
);
240 * Unregister all PHCIs and mark the whole registrants list as stale.
243 mpxio_unregister(rcm_handle_t
*hdl
)
247 rcm_log_message(RCM_TRACE1
, "MPXIO: unregister()\n");
249 (void) mutex_lock(&mpxio_lock
);
251 for (reg
= reg_list
; reg
!= NULL
; reg
= reg
->next
) {
252 (void) rcm_unregister_interest(hdl
, reg
->phci
.path
, 0);
253 reg
->referenced
= CACHE_STALE
;
256 (void) mutex_unlock(&mpxio_lock
);
258 return (RCM_SUCCESS
);
262 * To return usage information, just lookup the PHCI in the cache and return
263 * a string identifying that it's a PHCI and describing its cached MPxIO state.
264 * Recurse with the cached list of disks if dependents are to be included.
267 mpxio_getinfo(rcm_handle_t
*hdl
, char *rsrc
, id_t id
, uint_t flags
,
268 char **infostr
, char **errstr
, nvlist_t
*props
, rcm_info_t
**infop
)
271 int rv
= RCM_SUCCESS
;
273 char **clients
= NULL
;
277 rcm_log_message(RCM_TRACE1
, "MPXIO: getinfo(%s)\n", rsrc
);
282 (void) mutex_lock(&mpxio_lock
);
284 if ((reg
= lookup_phci(rsrc
)) == NULL
) {
285 *errstr
= strdup(MPXIO_MSG_CACHEFAIL
);
286 (void) mutex_unlock(&mpxio_lock
);
287 return (RCM_FAILURE
);
290 len
= snprintf(&c
, 1, MPXIO_MSG_USAGE
, s_state(reg
->phci
.state
));
291 buf
= calloc(len
+ 1, sizeof (char));
292 if ((buf
== NULL
) || (snprintf(buf
, len
+ 1, MPXIO_MSG_USAGE
,
293 s_state(reg
->phci
.state
)) > len
+ 1)) {
294 *infostr
= strdup(MPXIO_MSG_USAGEUNKNOWN
);
295 *errstr
= strdup(gettext("Cannot construct usage string."));
296 (void) mutex_unlock(&mpxio_lock
);
299 return (RCM_FAILURE
);
303 if (flags
& RCM_INCLUDE_DEPENDENT
) {
304 rcm_log_message(RCM_TRACE2
, "MPXIO: getting clients\n");
305 if (get_affected_clients(hdl
, rsrc
, CMD_GETINFO
, flags
,
307 *errstr
= strdup(gettext("Cannot lookup clients."));
308 (void) mutex_unlock(&mpxio_lock
);
309 return (RCM_FAILURE
);
312 rv
= rcm_get_info_list(hdl
, clients
, flags
, infop
);
315 rcm_log_message(RCM_TRACE2
, "MPXIO: none found\n");
319 (void) mutex_unlock(&mpxio_lock
);
324 * Nothing is implemented for suspend operations.
327 mpxio_suspend(rcm_handle_t
*hdl
, char *rsrc
, id_t id
, timespec_t
*interval
,
328 uint_t flags
, char **errstr
, rcm_info_t
**infop
)
330 rcm_log_message(RCM_TRACE1
, "MPXIO: suspend(%s)\n", rsrc
);
332 return (RCM_SUCCESS
);
336 * Nothing is implemented for resume operations.
339 mpxio_resume(rcm_handle_t
*hdl
, char *rsrc
, id_t id
, uint_t flags
,
340 char **errstr
, rcm_info_t
**infop
)
342 rcm_log_message(RCM_TRACE1
, "MPXIO: resume(%s)\n", rsrc
);
344 return (RCM_SUCCESS
);
348 * MPxIO has no policy against offlining. If disks will be affected, then
349 * base the return value for this request on the results of offlining the
350 * list of disks. Otherwise succeed.
353 mpxio_offline(rcm_handle_t
*hdl
, char *rsrc
, id_t id
, uint_t flags
,
354 char **errstr
, rcm_info_t
**infop
)
356 char **clients
= NULL
;
357 int rv
= RCM_SUCCESS
;
359 rcm_log_message(RCM_TRACE1
, "MPXIO: offline(%s)\n", rsrc
);
361 (void) mutex_lock(&mpxio_lock
);
363 if (get_affected_clients(hdl
, rsrc
, CMD_OFFLINE
, flags
, &clients
) < 0) {
364 *errstr
= strdup(gettext("Cannot lookup clients."));
365 (void) mutex_unlock(&mpxio_lock
);
366 return (RCM_FAILURE
);
370 rv
= rcm_request_offline_list(hdl
, clients
, flags
, infop
);
371 if (rv
!= RCM_SUCCESS
)
372 *errstr
= strdup(MPXIO_MSG_LASTPATH
);
376 (void) mutex_unlock(&mpxio_lock
);
382 * If disks are affected, then they are probably offline and we need to
383 * propagate this online notification to them.
386 mpxio_online(rcm_handle_t
*hdl
, char *rsrc
, id_t id
, uint_t flags
,
387 char **errstr
, rcm_info_t
**infop
)
390 int rv
= RCM_SUCCESS
;
392 rcm_log_message(RCM_TRACE1
, "MPXIO: online(%s)\n", rsrc
);
394 (void) mutex_lock(&mpxio_lock
);
396 if (get_affected_clients(hdl
, rsrc
, CMD_ONLINE
, flags
, &clients
) < 0) {
397 *errstr
= strdup(gettext("Cannot lookup clients."));
398 (void) mutex_unlock(&mpxio_lock
);
399 return (RCM_FAILURE
);
403 rv
= rcm_notify_online_list(hdl
, clients
, flags
, infop
);
407 (void) mutex_unlock(&mpxio_lock
);
413 * If clients are affected, then they are probably offline and we need to
414 * propagate this removal notification to them. We can also remove the
415 * cache entry for this PHCI. If that leaves its group empty, then the
416 * group will be removed during the next register callback.
419 mpxio_remove(rcm_handle_t
*hdl
, char *rsrc
, id_t id
, uint_t flags
,
420 char **errstr
, rcm_info_t
**infop
)
423 int rv
= RCM_SUCCESS
;
425 rcm_log_message(RCM_TRACE1
, "MPXIO: remove(%s)\n", rsrc
);
427 (void) mutex_lock(&mpxio_lock
);
429 if (get_affected_clients(hdl
, rsrc
, CMD_REMOVE
, flags
, &clients
) < 0) {
430 *errstr
= strdup(gettext("Cannot lookup clients."));
431 (void) mutex_unlock(&mpxio_lock
);
432 return (RCM_FAILURE
);
436 rv
= rcm_notify_remove_list(hdl
, clients
, flags
, infop
);
440 (void) mutex_unlock(&mpxio_lock
);
447 * Returns a string representation of a given libdevinfo path state.
450 s_state(di_path_state_t state
)
453 case DI_PATH_STATE_ONLINE
:
455 case DI_PATH_STATE_OFFLINE
:
457 case DI_PATH_STATE_STANDBY
:
459 case DI_PATH_STATE_FAULT
:
462 return ("<unknown>");
467 get_affected_clients(rcm_handle_t
*hdl
, char *rsrc
, int cmd
, int flags
,
473 char **clients
= NULL
;
475 /* Build a dummy phci_t for use with bsearch(). */
478 /* Analyze the effects upon each group. */
479 for (group
= group_list
; group
!= NULL
; group
= group
->next
) {
481 /* If the PHCI isn't in the group, then no effects. Skip. */
482 if (bsearch(&phci
, group
->phcis
, group
->nphcis
, sizeof (phci_t
),
483 compare_phci
) == NULL
)
487 * Merge in the clients. All clients are merged in for getinfo
488 * operations. Otherwise it's contingent upon a state change
489 * being transferred to the clients as a result of changing
492 if ((cmd
== CMD_GETINFO
) ||
493 detect_client_change(hdl
, cmd
, flags
, group
, rsrc
)) {
494 if (merge_clients(&nclients
, &clients
, group
) < 0) {
495 free_clients(nclients
, clients
);
501 /* Return the array of affected disks */
507 * Iterates through the members of a PHCI list, returning the entry
508 * corresponding to the named PHCI resource. Returns NULL when the lookup
512 lookup_phci(char *rsrc
)
516 for (reg
= reg_list
; reg
!= NULL
; reg
= reg
->next
) {
517 if (strcmp(reg
->phci
.path
, rsrc
) == 0)
525 * Tests whether or not an operation on a specific PHCI resource would affect
526 * the array of client devices attached to the PHCI's MPxIO group.
528 * Returns: 1 if clients would be affected, 0 if not.
531 detect_client_change(rcm_handle_t
*hdl
, int cmd
, int flags
, group_t
*group
,
538 * Perform a full set analysis on the set of redundant PHCIs. When
539 * there are no unaffected and online PHCIs, then changing the state
540 * of the named PHCI results in a client state change.
542 for (i
= 0; i
< group
->nphcis
; i
++) {
544 /* Filter the named resource out of the analysis */
545 if (strcmp(group
->phcis
[i
].path
, rsrc
) == 0)
549 * If we find a path that's in the ONLINE or STANDBY state
550 * that would be left over in the system after completing
551 * whatever DR or hotplugging operation is in progress, then
554 if ((group
->phcis
[i
].state
== DI_PATH_STATE_ONLINE
) ||
555 (group
->phcis
[i
].state
== DI_PATH_STATE_STANDBY
)) {
556 if (rcm_get_rsrcstate(hdl
, group
->phcis
[i
].path
, &state
)
558 rcm_log_message(RCM_ERROR
,
559 "MPXIO: Failed to query resource state\n");
562 rcm_log_message(RCM_TRACE2
, "MPXIO: state of %s: %d\n",
563 group
->phcis
[i
].path
, state
);
564 if (state
== RCM_STATE_ONLINE
) {
571 * The analysis above didn't find a redundant path to take over. So
572 * report that the state of the client resources will change.
578 * Merges the client disks connected to a particular MPxIO group in with a
579 * previous array of disk clients. The result is to adjust the 'nclients'
580 * value with the new count of disks in the array, and to adjust the 'disks'
581 * value to be a larger array of disks including its original contents along
582 * with the current group's contents merged in.
585 merge_clients(int *nclients
, char ***clientsp
, group_t
*group
)
591 if (group
->nclients
) {
592 old_nclients
= *nclients
;
593 *nclients
+= group
->nclients
;
594 clients_new
= realloc(*clientsp
,
595 ((*nclients
) + 1) * sizeof (char *));
596 if (clients_new
== NULL
) {
597 rcm_log_message(RCM_ERROR
,
598 "MPXIO: cannot reallocate client array (%s).\n",
602 for (i
= old_nclients
; i
< (*nclients
); i
++) {
604 * Don't allocate space for individual disks in the
605 * merged list. Just make references to the previously
606 * allocated strings in the group_t structs themselves.
608 clients_new
[i
] = group
->clients
[i
- old_nclients
];
610 clients_new
[(*nclients
)] = NULL
;
611 *clientsp
= clients_new
;
618 * A libdevinfo di_walk_node() callback. It's passed an integer pointer as an
619 * argument, and it increments the integer each time it encounters an MPxIO
620 * client. By initializing the integer to zero and doing a libdevinfo walk with
621 * this function, the total count of MPxIO clients in the system can be found.
624 get_nclients(di_node_t dinode
, void *arg
)
628 if (is_client(dinode
))
631 return (DI_WALK_CONTINUE
);
635 * Tests a libdevinfo node to determine if it's an MPxIO client.
637 * Returns: non-zero for true, 0 for false.
640 is_client(di_node_t dinode
)
642 return (di_path_client_next_path(dinode
, DI_PATH_NIL
) != DI_PATH_NIL
);
646 * After a new group_list has been constructed, this refreshes the RCM
647 * registrations and the reg_list contents. It uses a clock like algorithm
648 * with reference bits in the reg_list to know which registrants are new or
652 refresh_regs(rcm_handle_t
*hdl
)
657 phci_list_t
*prev_reg
;
660 * First part of the clock-like algorithm: clear reference bits.
662 for (reg
= reg_list
; reg
!= NULL
; reg
= reg
->next
)
663 reg
->referenced
= CACHE_STALE
;
666 * Second part of the clock-like algorithm: set the reference bits
667 * on every registrant that's still active. (Also add new list nodes
668 * for new registrants.)
670 for (group
= group_list
; group
!= NULL
; group
= group
->next
) {
671 for (i
= 0; i
< group
->nphcis
; i
++) {
674 * If already stale in the registrants list, just set
675 * its reference bit to REFERENCED and update its state.
677 if ((reg
= lookup_phci(group
->phcis
[i
].path
)) != NULL
) {
678 if (reg
->referenced
== CACHE_STALE
)
679 reg
->referenced
= CACHE_REFERENCED
;
680 reg
->phci
.state
= group
->phcis
[i
].state
;
685 * Otherwise, build a new list node and mark it NEW.
687 reg
= (phci_list_t
*)calloc(1, sizeof (*reg
));
689 rcm_log_message(RCM_ERROR
,
690 "MPXIO: cannot allocate phci_list (%s).\n",
694 reg
->phci
.path
= strdup(group
->phcis
[i
].path
);
695 if (reg
->phci
.path
== NULL
) {
697 rcm_log_message(RCM_ERROR
,
698 "MPXIO: cannot allocate phci path (%s).\n",
702 reg
->phci
.state
= group
->phcis
[i
].state
;
703 reg
->referenced
= CACHE_NEW
;
705 /* Link it at the head of reg_list */
706 reg
->next
= reg_list
;
712 * Final part of the clock algorithm: unregister stale entries, and
713 * register new entries. Stale entries get removed from the list.
719 /* Unregister and remove stale entries. */
720 if (reg
->referenced
== CACHE_STALE
) {
721 (void) rcm_unregister_interest(hdl
, reg
->phci
.path
, 0);
722 free(reg
->phci
.path
);
723 if (prev_reg
== NULL
) {
724 reg_list
= reg
->next
;
728 prev_reg
->next
= reg
->next
;
730 reg
= prev_reg
->next
;
735 /* Register new entries. */
736 if (reg
->referenced
== CACHE_NEW
) {
737 if (rcm_register_interest(hdl
, reg
->phci
.path
, 0, NULL
)
739 rcm_log_message(RCM_ERROR
,
740 "MPXIO: failed to register %s (%s).\n",
741 reg
->phci
.path
, strerror(errno
));
752 * A libdevinfo di_walk_node() callback that builds up the MPxIO group list.
754 * Every node encountered that's a client node is added into a group's client
755 * list. Whenever a group doesn't already exist with a matching set of
756 * related PHCIs, then a new group is constructed and put at the head of the
760 build_groups(di_node_t dinode
, void *arg
)
764 int *nclients
= (int *)arg
;
768 di_path_t dipath
= DI_PATH_NIL
;
771 if (nclients
== NULL
)
772 return (DI_WALK_TERMINATE
);
775 * Build a sorted array of PHCIs pertaining to the client.
778 di_path_client_next_path(dinode
, dipath
)) != DI_PATH_NIL
)
781 /* Skip non-clients. */
783 return (DI_WALK_CONTINUE
);
785 if ((phcis
= (phci_t
*)calloc(nphcis
, sizeof (phci_t
))) == NULL
) {
786 rcm_log_message(RCM_ERROR
,
787 "MPXIO: failed to allocate client's PHCIs (%s).\n",
789 return (DI_WALK_TERMINATE
);
792 di_path_client_next_path(dinode
, dipath
)) != DI_PATH_NIL
) {
793 phcinode
= di_path_phci_node(dipath
);
794 if (phcinode
== DI_NODE_NIL
) {
795 free_phcis(i
, phcis
); /* free preceeding PHCIs */
796 rcm_log_message(RCM_ERROR
,
797 "MPXIO: client appears to have no PHCIs.\n");
798 return (DI_WALK_TERMINATE
);
800 if ((phcis
[i
].path
= get_rsrcname(phcinode
)) == NULL
) {
801 free_phcis(i
, phcis
);
802 return (DI_WALK_TERMINATE
);
804 phcis
[i
].state
= di_path_state(dipath
);
807 qsort(phcis
, nphcis
, sizeof (phci_t
), compare_phci
);
810 * Compare that PHCI set to each existing group's set. We just add
811 * the client to the group and exit successfully once a match is made.
812 * Falling out of this loop means no match was found.
814 for (group
= group_list
; group
!= NULL
; group
= group
->next
) {
816 /* There is no match if the number of PHCIs is inequal */
817 if (nphcis
!= group
->nphcis
)
820 /* Compare the PHCIs linearly (which is okay; they're sorted) */
821 for (i
= 0; i
< nphcis
; i
++)
822 if (strcmp(phcis
[i
].path
, group
->phcis
[i
].path
) != 0)
826 * If the loop above completed, we have a match. Add the client
827 * to the group's disk array in that case, and return
831 free_phcis(nphcis
, phcis
);
832 if ((group
->clients
[group
->nclients
] =
833 get_rsrcname(dinode
)) == NULL
)
834 return (DI_WALK_TERMINATE
);
836 return (DI_WALK_CONTINUE
);
840 /* The loop above didn't find a match. So build a new group. */
841 if ((group
= (group_t
*)calloc(1, sizeof (*group
))) == NULL
) {
842 rcm_log_message(RCM_ERROR
,
843 "MPXIO: failed to allocate PHCI group (%s).\n",
845 free_phcis(nphcis
, phcis
);
846 return (DI_WALK_TERMINATE
);
848 if ((group
->clients
= (char **)calloc(*nclients
, sizeof (char *))) ==
851 free_phcis(nphcis
, phcis
);
852 return (DI_WALK_TERMINATE
);
854 group
->nphcis
= nphcis
;
855 group
->phcis
= phcis
;
856 if ((group
->clients
[0] = get_rsrcname(dinode
)) == NULL
) {
858 return (DI_WALK_TERMINATE
);
862 /* Link the group into the group list and return successfully. */
863 group
->next
= group_list
;
865 return (DI_WALK_CONTINUE
);
869 * For bsearch() and qsort(). Returns the results of a strcmp() on the names
873 compare_phci(const void *arg1
, const void *arg2
)
875 phci_t
*p1
= (phci_t
*)arg1
;
876 phci_t
*p2
= (phci_t
*)arg2
;
878 if ((p1
== NULL
) || (p2
== NULL
)) {
886 return (strcmp(p1
->path
, p2
->path
));
890 * Free the whole list of group's in the global group_list.
895 group_t
*group
= group_list
;
908 * Free the contents of a single group_t.
911 free_group(group_t
*group
)
914 free_phcis(group
->nphcis
, group
->phcis
);
915 free_clients(group
->nclients
, group
->clients
);
921 * Free an array of clients.
924 free_clients(int nclients
, char **clients
)
928 if (clients
!= NULL
) {
930 for (i
= 0; i
< nclients
; i
++)
939 * Free an array of phci_t's.
942 free_phcis(int nphcis
, phci_t
*phcis
)
946 if ((phcis
!= NULL
) && (nphcis
> 0)) {
947 for (i
= 0; i
< nphcis
; i
++)
955 * Converts a libdevinfo node into a /devices path. Caller must free results.
958 get_rsrcname(di_node_t dinode
)
963 char name
[MAXPATHLEN
];
965 if ((devfspath
= di_devfs_path(dinode
)) == NULL
) {
966 rcm_log_message(RCM_ERROR
, "MPXIO: resource has null path.\n");
970 len
= snprintf(name
, sizeof (name
), "/devices%s", devfspath
);
971 di_devfs_path_free(devfspath
);
972 if (len
>= sizeof (name
)) {
973 rcm_log_message(RCM_ERROR
, "MPXIO: resource path too long.\n");
977 if ((rsrcname
= strdup(name
)) == NULL
)
978 rcm_log_message(RCM_ERROR
,
979 "MPXIO: failed to allocate resource name (%s).\n",