summaryrefslogtreecommitdiff
path: root/usr/src/lib/libdevinfo
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/lib/libdevinfo')
-rw-r--r--usr/src/lib/libdevinfo/Makefile65
-rw-r--r--usr/src/lib/libdevinfo/Makefile.com55
-rw-r--r--usr/src/lib/libdevinfo/amd64/Makefile35
-rw-r--r--usr/src/lib/libdevinfo/devfsinfo.c2113
-rw-r--r--usr/src/lib/libdevinfo/devfsmap.c1936
-rw-r--r--usr/src/lib/libdevinfo/device_info.h182
-rw-r--r--usr/src/lib/libdevinfo/devinfo.c3080
-rw-r--r--usr/src/lib/libdevinfo/devinfo_devlink.c3618
-rw-r--r--usr/src/lib/libdevinfo/devinfo_devlink.h428
-rw-r--r--usr/src/lib/libdevinfo/devinfo_devperm.c1027
-rw-r--r--usr/src/lib/libdevinfo/devinfo_prop_decode.c935
-rw-r--r--usr/src/lib/libdevinfo/i386/Makefile35
-rw-r--r--usr/src/lib/libdevinfo/inc.flg26
-rw-r--r--usr/src/lib/libdevinfo/libdevinfo.h388
-rw-r--r--usr/src/lib/libdevinfo/llib-ldevinfo34
-rw-r--r--usr/src/lib/libdevinfo/sparc/Makefile35
-rw-r--r--usr/src/lib/libdevinfo/sparcv9/Makefile36
-rw-r--r--usr/src/lib/libdevinfo/spec/Makefile30
-rw-r--r--usr/src/lib/libdevinfo/spec/Makefile.targ35
-rw-r--r--usr/src/lib/libdevinfo/spec/amd64/Makefile42
-rw-r--r--usr/src/lib/libdevinfo/spec/devinfo.spec951
-rw-r--r--usr/src/lib/libdevinfo/spec/i386/Makefile42
-rw-r--r--usr/src/lib/libdevinfo/spec/sparc/Makefile44
-rw-r--r--usr/src/lib/libdevinfo/spec/sparcv9/Makefile43
-rw-r--r--usr/src/lib/libdevinfo/spec/versions55
25 files changed, 15270 insertions, 0 deletions
diff --git a/usr/src/lib/libdevinfo/Makefile b/usr/src/lib/libdevinfo/Makefile
new file mode 100644
index 0000000000..96ce3a2dcd
--- /dev/null
+++ b/usr/src/lib/libdevinfo/Makefile
@@ -0,0 +1,65 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# Copyright (c) 1990-1999 by Sun Microsystems, Inc.
+# All rights reserved.
+#
+# lib/libdevinfo/Makefile
+#
+
+include ../Makefile.lib
+
+SUBDIRS = spec .WAIT $(MACH) $(BUILD64) $(MACH64)
+
+# conditional assignments
+all := TARGET= all
+install := TARGET= install
+clean := TARGET= clean
+clobber := TARGET= clobber
+lint := TARGET= lint
+test := TARGET= test
+
+# definitions for install_h target
+HDRS= device_info.h libdevinfo.h
+ROOTHDRDIR= $(ROOT)/usr/include
+ROOTHDRS= $(HDRS:%=$(ROOTHDRDIR)/%)
+CHECKHDRS= $(HDRS:%.h=%.check)
+
+.KEEP_STATE:
+
+all install clean clobber lint: $(SUBDIRS)
+
+# install rule for install_h target
+
+$(ROOTHDRDIR)/%: %
+ $(INS.file)
+
+install_h: $(ROOTHDRS)
+
+check: $(CHECKHDRS)
+
+$(MACH) $(MACH64) spec: FRC
+ @cd $@; pwd; $(MAKE) $(TARGET)
+
+FRC:
+
diff --git a/usr/src/lib/libdevinfo/Makefile.com b/usr/src/lib/libdevinfo/Makefile.com
new file mode 100644
index 0000000000..96ebbf9a7c
--- /dev/null
+++ b/usr/src/lib/libdevinfo/Makefile.com
@@ -0,0 +1,55 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+LIBRARY= libdevinfo.a
+VERS= .1
+
+OBJECTS= devfsinfo.o devinfo.o devinfo_prop_decode.o devinfo_devlink.o \
+ devinfo_devperm.o devfsmap.o
+
+include ../../Makefile.lib
+include ../../Makefile.rootfs
+
+LIBS = $(DYNLIB) $(LINTLIB)
+LDLIBS += -lnvpair -lc
+$(LINTLIB) := SRCS = $(SRCDIR)/$(LINTSRC)
+
+SRCDIR = ..
+MAPDIR = ../spec/$(TRANSMACH)
+SPECMAPFILE = $(MAPDIR)/mapfile
+
+CFLAGS += $(CCVERBOSE)
+CPPFLAGS += -I..
+
+.KEEP_STATE:
+
+all: $(LIBS)
+
+lint: lintcheck
+
+include ../../Makefile.targ
diff --git a/usr/src/lib/libdevinfo/amd64/Makefile b/usr/src/lib/libdevinfo/amd64/Makefile
new file mode 100644
index 0000000000..45561f6e73
--- /dev/null
+++ b/usr/src/lib/libdevinfo/amd64/Makefile
@@ -0,0 +1,35 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+MAPDIR= ../spec/amd64
+include ../Makefile.com
+include ../../Makefile.lib.64
+
+all: $(LIBS)
+
+install: all $(ROOTLIBS64) $(ROOTLINKS64)
diff --git a/usr/src/lib/libdevinfo/devfsinfo.c b/usr/src/lib/libdevinfo/devfsinfo.c
new file mode 100644
index 0000000000..22dceb2b24
--- /dev/null
+++ b/usr/src/lib/libdevinfo/devfsinfo.c
@@ -0,0 +1,2113 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <thread.h>
+#include <synch.h>
+#include <sys/types.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/modctl.h>
+#include <errno.h>
+#include <sys/openpromio.h>
+#include <ftw.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <limits.h>
+
+#include "device_info.h"
+
+/*
+ * #define's
+ */
+
+/* alias node searching return values */
+#define NO_MATCH -1
+#define EXACT_MATCH 1
+#define INEXACT_MATCH 2
+
+/* for prom io operations */
+#define BUFSIZE 4096
+#define MAXPROPSIZE 256
+#define MAXVALSIZE (BUFSIZE - MAXPROPSIZE - sizeof (uint_t))
+
+#define OBP_OF 0x4 /* versions OBP 3.x */
+
+/* for nftw call */
+#define FT_DEPTH 15
+
+/* default logical and physical device name space */
+#define DEV "/dev"
+#define DEVICES "/devices"
+
+/* for boot device identification on x86 */
+#define CREATE_DISKMAP "/boot/solaris/bin/create_diskmap"
+#define GRUBDISK_MAP "/var/run/solaris_grubdisk.map"
+
+/*
+ * internal structure declarations
+ */
+
+/* for prom io functions */
+typedef union {
+ char buf[BUFSIZE];
+ struct openpromio opp;
+} Oppbuf;
+
+/* used to manage lists of devices and aliases */
+struct name_list {
+ char *name;
+ struct name_list *next;
+};
+
+/*
+ * internal global data
+ */
+
+/* global since nftw does not let you pass args to be updated */
+static struct name_list **dev_list;
+
+/* global since nftw does not let you pass args to be updated */
+static struct boot_dev **bootdev_list;
+
+/* mutex to protect bootdev_list and dev_list */
+static mutex_t dev_lists_lk = DEFAULTMUTEX;
+
+/*
+ * internal function prototypes
+ */
+
+static int prom_open(int);
+static void prom_close(int);
+static int is_openprom(int);
+
+static int prom_dev_to_alias(char *dev, uint_t options, char ***ret_buf);
+static int prom_srch_aliases_by_def(char *, struct name_list **,
+ struct name_list **, int);
+static int prom_compare_devs(char *prom_dev1, char *prom_dev2);
+static int _prom_strcmp(char *s1, char *s2);
+
+static int prom_obp_vers(void);
+
+static void parse_name(char *, char **, char **, char **);
+static int process_bootdev(const char *, const char *, struct boot_dev ***);
+static int process_minor_name(char *dev_path, const char *default_root);
+static void options_override(char *prom_path, char *alias_name);
+static int devfs_phys_to_logical(struct boot_dev **bootdev_array,
+ const int array_size, const char *default_root);
+static int check_logical_dev(const char *, const struct stat *, int,
+ struct FTW *);
+static struct boot_dev *alloc_bootdev(char *);
+static void free_name_list(struct name_list *list, int free_name);
+static int insert_alias_list(struct name_list **list,
+ char *alias_name);
+static int get_boot_dev_var(struct openpromio *opp);
+static int set_boot_dev_var(struct openpromio *opp, char *bootdev);
+static int devfs_prom_to_dev_name(char *prom_path, char *dev_path);
+static int devfs_dev_to_prom_names(char *dev_path, char *prom_path, size_t len);
+
+/*
+ * frees a list of paths from devfs_get_prom_name_list
+ */
+static void
+prom_list_free(char **prom_list)
+{
+ int i = 0;
+
+ if (!prom_list)
+ return;
+
+ while (prom_list[i]) {
+ free(prom_list[i]);
+ i++;
+ }
+ free(prom_list);
+}
+
+static int
+devfs_get_prom_name_list(const char *dev_name, char ***prom_list)
+{
+ char *prom_path = NULL;
+ int count = 0; /* # of slots we will need in prom_list */
+ int ret, i, len;
+ char **list;
+ char *ptr;
+
+ if (dev_name == NULL)
+ return (DEVFS_INVAL);
+ if (*dev_name != '/')
+ return (DEVFS_INVAL);
+ if (prom_list == NULL)
+ return (DEVFS_INVAL);
+
+ /*
+ * make sure we are on a machine which supports a prom
+ * and we have permission to use /dev/openprom
+ */
+ if ((ret = prom_obp_vers()) < 0)
+ return (ret);
+ if ((prom_path = (char *)malloc(MAXVALSIZE)) == NULL)
+ return (DEVFS_NOMEM);
+ /*
+ * get the prom path name
+ */
+ ret = devfs_dev_to_prom_names((char *)dev_name, prom_path, MAXVALSIZE);
+ if (ret < 0) {
+ free(prom_path);
+ return (ret);
+ }
+ /* deal with list of names */
+ for (i = 0; i < ret; i++)
+ if (prom_path[i] == '\0')
+ count++;
+
+ if ((list = (char **)calloc(count + 1, sizeof (char *))) == NULL) {
+ free(prom_path);
+ return (DEVFS_NOMEM);
+ }
+
+ ptr = prom_path;
+ for (i = 0; i < count; i++) {
+ len = strlen(ptr) + 1;
+ if ((list[i] = (char *)malloc(len)) == NULL) {
+ free(prom_path);
+ free(list);
+ return (DEVFS_NOMEM);
+ }
+ (void) snprintf(list[i], len, "%s", ptr);
+ ptr += len;
+ }
+
+ free(prom_path);
+
+ *prom_list = list;
+ return (0);
+}
+
+/*
+ * retrieve the list of prom representations for a given device name
+ * the list will be sorted in the following order: exact aliases,
+ * inexact aliases, prom device path name. If multiple matches occur
+ * for exact or inexact aliases, then these are sorted in collating
+ * order. The list is returned in prom_list
+ *
+ * the list may be restricted by specifying the correct flags in options.
+ */
+int
+devfs_get_prom_names(const char *dev_name, uint_t options, char ***prom_list)
+{
+ char *prom_path = NULL;
+ int count = 0; /* # of slots we will need in prom_list */
+ char **alias_list = NULL;
+ char **list;
+ int ret;
+
+ if (dev_name == NULL) {
+ return (DEVFS_INVAL);
+ }
+ if (*dev_name != '/') {
+ return (DEVFS_INVAL);
+ }
+ if (prom_list == NULL) {
+ return (DEVFS_INVAL);
+ }
+ /*
+ * make sure we are on a machine which supports a prom
+ * and we have permission to use /dev/openprom
+ */
+ if ((ret = prom_obp_vers()) < 0) {
+ return (ret);
+ }
+ if ((prom_path = (char *)malloc(MAXPATHLEN)) == NULL) {
+ return (DEVFS_NOMEM);
+ }
+ /*
+ * get the prom path name
+ */
+ ret = devfs_dev_to_prom_name((char *)dev_name, prom_path);
+ if (ret < 0) {
+ free(prom_path);
+ return (ret);
+ }
+ /* get the list of aliases (exact and inexact) */
+ if ((ret = prom_dev_to_alias(prom_path, options, &alias_list)) < 0) {
+ free(prom_path);
+ return (ret);
+ }
+ /* now figure out how big the return array must be */
+ if (alias_list != NULL) {
+ while (alias_list[count] != NULL) {
+ count++;
+ }
+ }
+ if ((options & BOOTDEV_NO_PROM_PATH) == 0) {
+ count++; /* # of slots we will need in prom_list */
+ }
+ count++; /* for the null terminator */
+
+ /* allocate space for the list */
+ if ((list = (char **)calloc(count, sizeof (char *))) == NULL) {
+ count = 0;
+ while ((alias_list) && (alias_list[count] != NULL)) {
+ free(alias_list[count]);
+ count++;
+ }
+ free(alias_list);
+ free(prom_path);
+ return (DEVFS_NOMEM);
+ }
+ /* fill in the array and free the name list of aliases. */
+ count = 0;
+ while ((alias_list) && (alias_list[count] != NULL)) {
+ list[count] = alias_list[count];
+ count++;
+ }
+ if ((options & BOOTDEV_NO_PROM_PATH) == 0) {
+ list[count] = prom_path;
+ }
+ if (alias_list != NULL) {
+ free(alias_list);
+ }
+ *prom_list = list;
+ return (0);
+}
+
+/*
+ * Get a list prom-path translations for a solaris device.
+ *
+ * Returns the number of and all OBP paths and alias variants that
+ * reference the Solaris device path passed in.
+ */
+int
+devfs_get_all_prom_names(const char *solaris_path, uint_t flags,
+ struct devfs_prom_path **paths)
+{
+ int ret, len, i, count = 0;
+ char *ptr, *prom_path;
+ struct devfs_prom_path *cur = NULL, *new;
+
+ if ((ret = prom_obp_vers()) < 0)
+ return (ret);
+ if ((prom_path = (char *)malloc(MAXVALSIZE)) == NULL)
+ return (DEVFS_NOMEM);
+
+ if ((ret = devfs_dev_to_prom_names((char *)solaris_path,
+ prom_path, MAXVALSIZE)) < 0) {
+ free(prom_path);
+ return (ret);
+ }
+
+ for (i = 0; i < ret; i++)
+ if (prom_path[i] == '\0')
+ count++;
+
+ *paths = NULL;
+ ptr = prom_path;
+ for (i = 0; i < count; i++) {
+ if ((new = (struct devfs_prom_path *)calloc(
+ sizeof (struct devfs_prom_path), 1)) == NULL) {
+ free(prom_path);
+ devfs_free_all_prom_names(*paths);
+ return (DEVFS_NOMEM);
+ }
+
+ if (cur == NULL)
+ *paths = new;
+ else
+ cur->next = new;
+ cur = new;
+
+ len = strlen(ptr) + 1;
+ if ((cur->obp_path = (char *)calloc(len, 1)) == NULL) {
+ free(prom_path);
+ devfs_free_all_prom_names(*paths);
+ return (DEVFS_NOMEM);
+ }
+
+ (void) snprintf(cur->obp_path, len, "%s", ptr);
+ ptr += len;
+ if ((ret = prom_dev_to_alias(cur->obp_path, flags,
+ &(cur->alias_list))) < 0) {
+ free(prom_path);
+ devfs_free_all_prom_names(*paths);
+ return (ret);
+ }
+ }
+
+ free(prom_path);
+ return (count);
+}
+
+void
+devfs_free_all_prom_names(struct devfs_prom_path *paths)
+{
+ int i;
+
+ if (paths == NULL)
+ return;
+
+ devfs_free_all_prom_names(paths->next);
+
+ if (paths->obp_path != NULL)
+ free(paths->obp_path);
+
+ if (paths->alias_list != NULL) {
+ for (i = 0; paths->alias_list[i] != NULL; i++)
+ if (paths->alias_list[i] != NULL)
+ free(paths->alias_list[i]);
+
+ free(paths->alias_list);
+ }
+
+ free(paths);
+}
+
+/*
+ * Accepts a device name as an input argument. Uses this to set the
+ * boot-device (or like) variable
+ *
+ * By default, this routine prepends to the list and converts the
+ * logical device name to its most compact prom representation.
+ * Available options include: converting the device name to a prom
+ * path name (but not an alias) or performing no conversion at all;
+ * overwriting the existing contents of boot-device rather than
+ * prepending.
+ */
+int
+devfs_bootdev_set_list(const char *dev_name, const uint_t options)
+{
+ char *prom_path;
+ char *new_bootdev;
+ char *ptr;
+ char **alias_list = NULL;
+ char **prom_list = NULL;
+ Oppbuf oppbuf;
+ struct openpromio *opp = &(oppbuf.opp);
+ int ret, len, i, j;
+
+ if (devfs_bootdev_modifiable() != 0) {
+ return (DEVFS_NOTSUP);
+ }
+ if (dev_name == NULL) {
+ return (DEVFS_INVAL);
+ }
+ if (strlen(dev_name) >= MAXPATHLEN)
+ return (DEVFS_INVAL);
+
+ if ((*dev_name != '/') && !(options & BOOTDEV_LITERAL)) {
+ return (DEVFS_INVAL);
+ }
+ if ((options & BOOTDEV_LITERAL) && (options & BOOTDEV_PROMDEV)) {
+ return (DEVFS_INVAL);
+ }
+ /*
+ * if we are prepending, make sure that this obp rev
+ * supports multiple boot device entries.
+ */
+ ret = prom_obp_vers();
+ if (ret < 0) {
+ return (ret);
+ }
+
+ if ((prom_path = (char *)malloc(MAXVALSIZE)) == NULL) {
+ return (DEVFS_NOMEM);
+ }
+ if (options & BOOTDEV_LITERAL) {
+ (void) strcpy(prom_path, dev_name);
+ } else {
+ /* need to convert to prom representation */
+ ret = devfs_get_prom_name_list(dev_name, &prom_list);
+ if (ret < 0) {
+ free(prom_path);
+ return (ret);
+ }
+
+ len = MAXVALSIZE;
+ i = 0;
+ ptr = prom_path;
+ while (prom_list && prom_list[i]) {
+ if (!(options & BOOTDEV_PROMDEV)) {
+ ret = prom_dev_to_alias(prom_list[i], 0,
+ &alias_list);
+ if (ret < 0) {
+ free(prom_path);
+ prom_list_free(prom_list);
+ return (ret);
+ }
+ if ((alias_list != NULL) &&
+ (alias_list[0] != NULL)) {
+ (void) snprintf(ptr, len, "%s ",
+ alias_list[0]);
+ for (ret = 0; alias_list[ret] != NULL;
+ ret++)
+ free(alias_list[ret]);
+ } else {
+ (void) snprintf(ptr, len, "%s ",
+ prom_list[i]);
+ }
+ if (alias_list != NULL)
+ free(alias_list);
+ } else {
+ (void) snprintf(ptr, len, "%s ", prom_list[i]);
+ }
+ j = strlen(ptr);
+ len -= j;
+ ptr += j;
+ i++;
+ }
+ ptr--;
+ *ptr = NULL;
+
+ prom_list_free(prom_list);
+ }
+ if (options & BOOTDEV_OVERWRITE) {
+ new_bootdev = prom_path;
+ } else {
+ /* retrieve the current value of boot-device */
+ ret = get_boot_dev_var(opp);
+ if (ret < 0) {
+ free(prom_path);
+ return (ret);
+ }
+ /* prepend new entry - deal with duplicates */
+ new_bootdev = (char *)malloc(strlen(opp->oprom_array)
+ + strlen(prom_path) + 2);
+ if (new_bootdev == NULL) {
+ free(prom_path);
+ return (DEVFS_NOMEM);
+ }
+ (void) strcpy(new_bootdev, prom_path);
+ if (opp->oprom_size > 0) {
+ for (ptr = strtok(opp->oprom_array, " "); ptr != NULL;
+ ptr = strtok(NULL, " ")) {
+ /* we strip out duplicates */
+ if (strcmp(prom_path, ptr) == 0) {
+ continue;
+ }
+ (void) strcat(new_bootdev, " ");
+ (void) strcat(new_bootdev, ptr);
+ }
+ }
+ }
+
+ /* now set the new value */
+ ret = set_boot_dev_var(opp, new_bootdev);
+
+ if (options & BOOTDEV_OVERWRITE) {
+ free(prom_path);
+ } else {
+ free(new_bootdev);
+ free(prom_path);
+ }
+
+ return (ret);
+}
+
+/*
+ * sets the string bootdev as the new value for boot-device
+ */
+static int
+set_boot_dev_var(struct openpromio *opp, char *bootdev)
+{
+ int prom_fd;
+ int i;
+ int ret;
+ char *valbuf;
+ char *save_bootdev;
+ char *bootdev_variables[] = {
+ "boot-device",
+ "bootdev",
+ "boot-from",
+ NULL
+ };
+ int found = 0;
+ int *ip = (int *)((void *)opp->oprom_array);
+
+ /* query the prom */
+ prom_fd = prom_open(O_RDWR);
+ if (prom_fd < 0) {
+ return (prom_fd);
+ }
+
+ /* get the diagnostic-mode? property */
+ (void) strcpy(opp->oprom_array, "diagnostic-mode?");
+ opp->oprom_size = MAXVALSIZE;
+ if (ioctl(prom_fd, OPROMGETOPT, opp) >= 0) {
+ if ((opp->oprom_size > 0) &&
+ (strcmp(opp->oprom_array, "true") == 0)) {
+ prom_close(prom_fd);
+ return (DEVFS_ERR);
+ }
+ }
+ /* get the diag-switch? property */
+ (void) strcpy(opp->oprom_array, "diag-switch?");
+ opp->oprom_size = MAXVALSIZE;
+ if (ioctl(prom_fd, OPROMGETOPT, opp) >= 0) {
+ if ((opp->oprom_size > 0) &&
+ (strcmp(opp->oprom_array, "true") == 0)) {
+ prom_close(prom_fd);
+ return (DEVFS_ERR);
+ }
+ }
+ /*
+ * look for one of the following properties in order:
+ * boot-device
+ * bootdev
+ * boot-from
+ *
+ * Use the first one that we find.
+ */
+ *ip = 0;
+ opp->oprom_size = MAXPROPSIZE;
+ while ((opp->oprom_size != 0) && (!found)) {
+ opp->oprom_size = MAXPROPSIZE;
+ if (ioctl(prom_fd, OPROMNXTOPT, opp) < 0) {
+ break;
+ }
+ for (i = 0; bootdev_variables[i] != NULL; i++) {
+ if (strcmp(opp->oprom_array, bootdev_variables[i])
+ == 0) {
+ found = 1;
+ break;
+ }
+ }
+ }
+ if (found) {
+ (void) strcpy(opp->oprom_array, bootdev_variables[i]);
+ opp->oprom_size = MAXVALSIZE;
+ if (ioctl(prom_fd, OPROMGETOPT, opp) < 0) {
+ prom_close(prom_fd);
+ return (DEVFS_NOTSUP);
+ }
+ } else {
+ prom_close(prom_fd);
+ return (DEVFS_NOTSUP);
+ }
+
+ /* save the old copy in case we fail */
+ if ((save_bootdev = strdup(opp->oprom_array)) == NULL) {
+ prom_close(prom_fd);
+ return (DEVFS_NOMEM);
+ }
+ /* set up the new value of boot-device */
+ (void) strcpy(opp->oprom_array, bootdev_variables[i]);
+ valbuf = opp->oprom_array + strlen(opp->oprom_array) + 1;
+ (void) strcpy(valbuf, bootdev);
+
+ opp->oprom_size = strlen(valbuf) + strlen(opp->oprom_array) + 2;
+
+ if (ioctl(prom_fd, OPROMSETOPT, opp) < 0) {
+ free(save_bootdev);
+ prom_close(prom_fd);
+ return (DEVFS_ERR);
+ }
+
+ /*
+ * now read it back to make sure it took
+ */
+ (void) strcpy(opp->oprom_array, bootdev_variables[i]);
+ opp->oprom_size = MAXVALSIZE;
+ if (ioctl(prom_fd, OPROMGETOPT, opp) >= 0) {
+ if (_prom_strcmp(opp->oprom_array, bootdev) == 0) {
+ /* success */
+ free(save_bootdev);
+ prom_close(prom_fd);
+ return (0);
+ }
+ /* deal with setting it to "" */
+ if ((strlen(bootdev) == 0) && (opp->oprom_size == 0)) {
+ /* success */
+ free(save_bootdev);
+ prom_close(prom_fd);
+ return (0);
+ }
+ }
+ /*
+ * something did not take - write out the old value and
+ * hope that we can restore things...
+ *
+ * unfortunately, there is no way for us to differentiate
+ * whether we exceeded the maximum number of characters
+ * allowable. The limit varies from prom rev to prom
+ * rev, and on some proms, when the limit is
+ * exceeded, whatever was in the
+ * boot-device variable becomes unreadable.
+ *
+ * so if we fail, we will assume we ran out of room. If we
+ * not able to restore the original setting, then we will
+ * return DEVFS_ERR instead.
+ */
+ ret = DEVFS_LIMIT;
+ (void) strcpy(opp->oprom_array, bootdev_variables[i]);
+ valbuf = opp->oprom_array + strlen(opp->oprom_array) + 1;
+ (void) strcpy(valbuf, save_bootdev);
+
+ opp->oprom_size = strlen(valbuf) + strlen(opp->oprom_array) + 2;
+
+ if (ioctl(prom_fd, OPROMSETOPT, opp) < 0) {
+ ret = DEVFS_ERR;
+ }
+ free(save_bootdev);
+ prom_close(prom_fd);
+ return (ret);
+}
+/*
+ * retrieve the current value for boot-device
+ */
+static int
+get_boot_dev_var(struct openpromio *opp)
+{
+ int prom_fd;
+ int i;
+ char *bootdev_variables[] = {
+ "boot-device",
+ "bootdev",
+ "boot-from",
+ NULL
+ };
+ int found = 0;
+ int *ip = (int *)((void *)opp->oprom_array);
+
+ /* query the prom */
+ prom_fd = prom_open(O_RDONLY);
+ if (prom_fd < 0) {
+ return (prom_fd);
+ }
+
+ /* get the diagnostic-mode? property */
+ (void) strcpy(opp->oprom_array, "diagnostic-mode?");
+ opp->oprom_size = MAXVALSIZE;
+ if (ioctl(prom_fd, OPROMGETOPT, opp) >= 0) {
+ if ((opp->oprom_size > 0) &&
+ (strcmp(opp->oprom_array, "true") == 0)) {
+ prom_close(prom_fd);
+ return (DEVFS_ERR);
+ }
+ }
+ /* get the diag-switch? property */
+ (void) strcpy(opp->oprom_array, "diag-switch?");
+ opp->oprom_size = MAXVALSIZE;
+ if (ioctl(prom_fd, OPROMGETOPT, opp) >= 0) {
+ if ((opp->oprom_size > 0) &&
+ (strcmp(opp->oprom_array, "true") == 0)) {
+ prom_close(prom_fd);
+ return (DEVFS_ERR);
+ }
+ }
+ /*
+ * look for one of the following properties in order:
+ * boot-device
+ * bootdev
+ * boot-from
+ *
+ * Use the first one that we find.
+ */
+ *ip = 0;
+ opp->oprom_size = MAXPROPSIZE;
+ while ((opp->oprom_size != 0) && (!found)) {
+ opp->oprom_size = MAXPROPSIZE;
+ if (ioctl(prom_fd, OPROMNXTOPT, opp) < 0) {
+ break;
+ }
+ for (i = 0; bootdev_variables[i] != NULL; i++) {
+ if (strcmp(opp->oprom_array, bootdev_variables[i])
+ == 0) {
+ found = 1;
+ break;
+ }
+ }
+ }
+ if (found) {
+ (void) strcpy(opp->oprom_array, bootdev_variables[i]);
+ opp->oprom_size = MAXVALSIZE;
+ if (ioctl(prom_fd, OPROMGETOPT, opp) < 0) {
+ prom_close(prom_fd);
+ return (DEVFS_ERR);
+ }
+ /* boot-device exists but contains nothing */
+ if (opp->oprom_size == 0) {
+ *opp->oprom_array = '\0';
+ }
+ } else {
+ prom_close(prom_fd);
+ return (DEVFS_NOTSUP);
+ }
+ prom_close(prom_fd);
+ return (0);
+}
+
+#ifndef __sparc
+static FILE *
+open_diskmap(void)
+{
+ FILE *fp;
+ char cmd[PATH_MAX];
+
+ /* make sure we have a map file */
+ fp = fopen(GRUBDISK_MAP, "r");
+ if (fp == NULL) {
+ (void) snprintf(cmd, sizeof (cmd),
+ "%s > /dev/null", CREATE_DISKMAP);
+ (void) system(cmd);
+ fp = fopen(GRUBDISK_MAP, "r");
+ }
+ return (fp);
+}
+
+static int
+find_x86_boot_device(struct openpromio *opp)
+{
+ int ret = DEVFS_ERR;
+ char *cp, line[MAXVALSIZE + 6];
+ FILE *file;
+
+ file = open_diskmap();
+ if (file == NULL)
+ return (DEVFS_ERR);
+
+ while (fgets(line, MAXVALSIZE + 6, file)) {
+ if (strncmp(line, "0 ", 2) != 0)
+ continue;
+ /* drop new-line */
+ line[strlen(line) - 1] = '\0';
+ /*
+ * an x86 BIOS only boots a disk, not a partition
+ * or a slice, so hard-code :q (p0)
+ */
+ cp = strchr(line + 2, ' ');
+ if (cp == NULL)
+ break;
+ (void) snprintf(opp->oprom_array, MAXVALSIZE,
+ "%s:q", cp + 1);
+ opp->oprom_size = MAXVALSIZE;
+ ret = 0;
+ break;
+ }
+ (void) fclose(file);
+ return (ret);
+}
+#endif /* ndef __sparc */
+
+/*
+ * retrieve the list of entries in the boot-device configuration
+ * variable. An array of boot_dev structs will be created, one entry
+ * for each device name in the boot-device variable. Each entry
+ * in the array will contain the logical device representation of the
+ * boot-device entry, if any.
+ *
+ * default_root. if set, is used to locate logical device entries in
+ * directories other than /dev
+ */
+int
+devfs_bootdev_get_list(const char *default_root,
+ struct boot_dev ***bootdev_list)
+{
+ Oppbuf oppbuf;
+ struct openpromio *opp = &(oppbuf.opp);
+ int i;
+ struct boot_dev **tmp_list;
+
+ if (default_root == NULL) {
+ default_root = "";
+ } else if (*default_root != '/') {
+ return (DEVFS_INVAL);
+ }
+
+ if (bootdev_list == NULL) {
+ return (DEVFS_INVAL);
+ }
+
+ /* get the boot-device variable */
+#if defined(sparc)
+ i = get_boot_dev_var(opp);
+#else
+ i = find_x86_boot_device(opp);
+#endif
+ if (i < 0) {
+ return (i);
+ }
+ /* now try to translate each entry to a logical device. */
+ i = process_bootdev(opp->oprom_array, default_root, &tmp_list);
+ if (i == 0) {
+ *bootdev_list = tmp_list;
+ return (0);
+ } else {
+ return (i);
+ }
+}
+
+/*
+ * loop thru the list of entries in a boot-device configuration
+ * variable.
+ */
+static int
+process_bootdev(const char *bootdevice, const char *default_root,
+ struct boot_dev ***list)
+{
+ int i;
+ char *entry, *ptr;
+ char prom_path[MAXPATHLEN];
+ char ret_buf[MAXPATHLEN];
+ struct boot_dev **bootdev_array;
+ int num_entries = 0;
+ int found = 0;
+ int vers;
+
+ if ((entry = (char *)malloc(strlen(bootdevice) + 1)) == NULL) {
+ return (DEVFS_NOMEM);
+ }
+ /* count the number of entries */
+ (void) strcpy(entry, bootdevice);
+ for (ptr = strtok(entry, " "); ptr != NULL;
+ ptr = strtok(NULL, " ")) {
+ num_entries++;
+ }
+ (void) strcpy(entry, bootdevice);
+
+ bootdev_array = (struct boot_dev **)
+ calloc((size_t)num_entries + 1, sizeof (struct boot_dev *));
+
+ if (bootdev_array == NULL) {
+ free(entry);
+ return (DEVFS_NOMEM);
+ }
+
+ vers = prom_obp_vers();
+ if (vers < 0) {
+ free(entry);
+ return (vers);
+ }
+
+ /* for each entry in boot-device, do... */
+ for (ptr = strtok(entry, " "), i = 0; ptr != NULL;
+ ptr = strtok(NULL, " "), i++) {
+
+ if ((bootdev_array[i] = alloc_bootdev(ptr)) == NULL) {
+ devfs_bootdev_free_list(bootdev_array);
+ free(entry);
+ return (DEVFS_NOMEM);
+ }
+
+ (void) strcpy(prom_path, ptr);
+ /* now we have a prom device path - convert to a devfs name */
+ if (devfs_prom_to_dev_name(prom_path, ret_buf) < 0) {
+ continue;
+ }
+ /* append any default minor names necessary */
+ if (process_minor_name(ret_buf, default_root) < 0) {
+ continue;
+ }
+
+ found = 1;
+ /*
+ * store the physical device path for now - when
+ * we are all done with the entries, we will convert
+ * these to their logical device name equivalents
+ */
+ bootdev_array[i]->bootdev_trans[0] = strdup(ret_buf);
+ }
+ /*
+ * Convert all of the boot-device entries that translated to a
+ * physical device path in /devices to a logical device path
+ * in /dev (note that there may be several logical device paths
+ * associated with a single physical device path - return them all
+ */
+ if (found) {
+ if (devfs_phys_to_logical(bootdev_array, num_entries,
+ default_root) < 0) {
+ devfs_bootdev_free_list(bootdev_array);
+ bootdev_array = NULL;
+ }
+ }
+ free(entry);
+ *list = bootdev_array;
+ return (0);
+}
+
+/*
+ * We may get a device path from the prom that has no minor name
+ * information included in it. Since this device name will not
+ * correspond directly to a physical device in /devices, we do our
+ * best to append what the default minor name should be and try this.
+ *
+ * For sparc: we append slice 0 (:a).
+ * For x86: we append fdisk partition 0 (:q).
+ */
+static int
+process_minor_name(char *dev_path, const char *root)
+{
+ char *cp;
+#if defined(sparc)
+ const char *default_minor_name = "a";
+#else
+ const char *default_minor_name = "q";
+#endif
+ int n;
+ struct stat stat_buf;
+ char path[MAXPATHLEN];
+
+ (void) snprintf(path, sizeof (path), "%s%s%s", root, DEVICES, dev_path);
+ /*
+ * if the device file already exists as given to us, there
+ * is nothing to do but return.
+ */
+ if (stat(path, &stat_buf) == 0) {
+ return (0);
+ }
+ /*
+ * if there is no ':' after the last '/' character, or if there is
+ * a ':' with no specifier, append the default segment specifier
+ * ; if there is a ':' followed by a digit, this indicates
+ * a partition number (which does not map into the /devices name
+ * space), so strip the number and replace it with the letter
+ * that represents the partition index
+ */
+ if ((cp = strrchr(dev_path, '/')) != NULL) {
+ if ((cp = strchr(cp, ':')) == NULL) {
+ (void) strcat(dev_path, ":");
+ (void) strcat(dev_path, default_minor_name);
+ } else if (*++cp == '\0') {
+ (void) strcat(dev_path, default_minor_name);
+ } else if (isdigit(*cp)) {
+ n = atoi(cp);
+ /* make sure to squash the digit */
+ *cp = '\0';
+ switch (n) {
+ case 0: (void) strcat(dev_path, "q");
+ break;
+ case 1: (void) strcat(dev_path, "r");
+ break;
+ case 2: (void) strcat(dev_path, "s");
+ break;
+ case 3: (void) strcat(dev_path, "t");
+ break;
+ case 4: (void) strcat(dev_path, "u");
+ break;
+ default: (void) strcat(dev_path, "a");
+ break;
+ }
+ }
+ }
+ /*
+ * see if we can find something now.
+ */
+ (void) snprintf(path, sizeof (path), "%s%s%s", root, DEVICES, dev_path);
+
+ if (stat(path, &stat_buf) == 0) {
+ return (0);
+ } else {
+ return (-1);
+ }
+}
+
+/*
+ * for each entry in bootdev_array, convert the physical device
+ * representation of the boot-device entry to one or more logical device
+ * entries. We use the hammer method - walk through the logical device
+ * name space looking for matches (/dev). We use nftw to do this.
+ */
+static int
+devfs_phys_to_logical(struct boot_dev **bootdev_array, const int array_size,
+ const char *default_root)
+{
+ int walk_flags = FTW_PHYS | FTW_MOUNT;
+ char *full_path;
+ struct name_list *list;
+ int count, i;
+ char **dev_name_array;
+ size_t default_root_len;
+ char *dev_dir = DEV;
+ int len;
+
+ if (array_size < 0) {
+ return (-1);
+ }
+
+ if (bootdev_array == NULL) {
+ return (-1);
+ }
+ if (default_root == NULL) {
+ return (-1);
+ }
+ default_root_len = strlen(default_root);
+ if ((default_root_len != 0) && (*default_root != '/')) {
+ return (-1);
+ }
+ /* short cut for an empty array */
+ if (*bootdev_array == NULL) {
+ return (0);
+ }
+
+ /* tell nftw where to start (default: /dev) */
+ len = default_root_len + strlen(dev_dir) + 1;
+ if ((full_path = (char *)malloc(len)) == NULL) {
+ return (-1);
+ }
+ /*
+ * if the default root path is terminated with a /, we have to
+ * make sure we don't end up with one too many slashes in the
+ * path we are building.
+ */
+ if ((default_root_len > (size_t)0) &&
+ (default_root[default_root_len - 1] == '/')) {
+ (void) snprintf(full_path, len, "%s%s", default_root,
+ &dev_dir[1]);
+ } else {
+ (void) snprintf(full_path, len, "%s%s", default_root, dev_dir);
+ }
+
+ /*
+ * we need to muck with global data to make nftw work
+ * so single thread access
+ */
+ (void) mutex_lock(&dev_lists_lk);
+
+ /*
+ * set the global vars bootdev_list and dev_list for use by nftw
+ * dev_list is an array of lists - one for each boot-device
+ * entry. The nftw function will create a list of logical device
+ * entries for each boot-device and put all of the lists in
+ * dev_list.
+ */
+ dev_list = (struct name_list **)
+ calloc(array_size, sizeof (struct name_list *));
+ if (dev_list == NULL) {
+ free(full_path);
+ (void) mutex_unlock(&dev_lists_lk);
+ return (-1);
+ }
+ bootdev_list = bootdev_array;
+
+ if (nftw(full_path, check_logical_dev, FT_DEPTH, walk_flags) == -1) {
+ bootdev_list = NULL;
+ free(full_path);
+ for (i = 0; i < array_size; i++) {
+ free_name_list(dev_list[i], 1);
+ }
+ /* don't free dev_list here because it's been handed off */
+ dev_list = NULL;
+ (void) mutex_unlock(&dev_lists_lk);
+ return (-1);
+ }
+ /*
+ * now we have a filled in dev_list. So for each logical device
+ * list in dev_list, count the number of entries in the list,
+ * create an array of strings of logical devices, and save in the
+ * corresponding boot_dev structure.
+ */
+ for (i = 0; i < array_size; i++) {
+ /* get the next list */
+ list = dev_list[i];
+ count = 0;
+
+ /* count the number of entries in the list */
+ while (list != NULL) {
+ count++;
+ list = list->next;
+ }
+ if ((dev_name_array =
+ (char **)malloc((count + 1) * sizeof (char *)))
+ == NULL) {
+ continue;
+ }
+
+ list = dev_list[i];
+ count = 0;
+
+ /* fill in the array */
+ while (list != NULL) {
+ dev_name_array[count] = list->name;
+ count++;
+ list = list->next;
+ }
+ /*
+ * null terminate the array
+ */
+ dev_name_array[count] = NULL;
+
+ if (bootdev_array[i]->bootdev_trans[0] != NULL) {
+ free(bootdev_array[i]->bootdev_trans[0]);
+ }
+ free(bootdev_array[i]->bootdev_trans);
+ bootdev_array[i]->bootdev_trans = dev_name_array;
+ }
+ bootdev_list = NULL;
+ free(full_path);
+ for (i = 0; i < array_size; i++) {
+ free_name_list(dev_list[i], 0);
+ }
+ free(dev_list);
+ dev_list = NULL;
+ (void) mutex_unlock(&dev_lists_lk);
+ return (0);
+}
+/*
+ * nftw function
+ * for a logical dev entry, it walks the list of boot-devices and
+ * sees if there are any matches. If so, it saves the logical device
+ * name off in the appropriate list in dev_list
+ */
+/* ARGSUSED */
+static int
+check_logical_dev(const char *node, const struct stat *node_stat, int flags,
+ struct FTW *ftw_info)
+{
+ char link_buf[MAXPATHLEN];
+ int link_buf_len;
+ char *name;
+ struct name_list *dev;
+ char *physdev;
+ int i;
+
+ if (flags != FTW_SL) {
+ return (0);
+ }
+
+ if ((link_buf_len = readlink(node, (void *)link_buf, MAXPATHLEN))
+ == -1) {
+ return (0);
+ }
+ link_buf[link_buf_len] = '\0';
+ if ((name = strstr(link_buf, DEVICES)) == NULL) {
+ return (0);
+ }
+ name = (char *)(name + strlen(DEVICES));
+
+ for (i = 0; bootdev_list[i] != NULL; i++) {
+ if (bootdev_list[i]->bootdev_trans[0] == NULL) {
+ continue;
+ }
+ /*
+ * compare the contents of the link with the physical
+ * device representation of this boot device
+ */
+ physdev = bootdev_list[i]->bootdev_trans[0];
+ if ((strcmp(name, physdev) == 0) &&
+ (strlen(name) == strlen(physdev))) {
+ if ((dev = (struct name_list *)
+ malloc(sizeof (struct name_list))) == NULL) {
+ return (-1);
+ }
+ if ((dev->name = strdup(node)) == NULL) {
+ free(dev);
+ return (-1);
+ }
+ if (dev_list[i] == NULL) {
+ dev_list[i] = dev;
+ dev_list[i]->next = NULL;
+ } else {
+ dev->next = dev_list[i];
+ dev_list[i] = dev;
+ }
+ }
+ }
+ return (0);
+}
+
+/*
+ * frees a list of boot_dev struct pointers
+ */
+void
+devfs_bootdev_free_list(struct boot_dev **array)
+{
+ int i = 0;
+ int j;
+
+ if (array == NULL) {
+ return;
+ }
+
+ while (array[i] != NULL) {
+ free(array[i]->bootdev_element);
+ j = 0;
+ while (array[i]->bootdev_trans[j] != NULL) {
+ free(array[i]->bootdev_trans[j++]);
+ }
+ free(array[i]->bootdev_trans);
+ free(array[i]);
+ i++;
+ }
+ free(array);
+}
+/*
+ * allocates a boot_dev struct and fills in the bootdev_element portion
+ */
+static struct boot_dev *
+alloc_bootdev(char *entry_name)
+{
+ struct boot_dev *entry;
+
+ entry = (struct boot_dev *)calloc(1, sizeof (struct boot_dev));
+
+ if (entry == NULL) {
+ return (NULL);
+ }
+ if ((entry->bootdev_element = strdup(entry_name)) == NULL) {
+ free(entry);
+ return (NULL);
+ }
+ /*
+ * Allocate room for 1 name and a null terminator - the caller of
+ * this function will need the first slot right away.
+ */
+ if ((entry->bootdev_trans = (char **)calloc(2, sizeof (char *)))
+ == NULL) {
+ free(entry->bootdev_element);
+ free(entry);
+ return (NULL);
+ }
+ return (entry);
+}
+
+/*
+ * will come back with a concatenated list of paths
+ */
+int
+devfs_dev_to_prom_names(char *dev_path, char *prom_path, size_t len)
+{
+ Oppbuf oppbuf;
+ struct openpromio *opp = &(oppbuf.opp);
+ int prom_fd;
+ int ret = DEVFS_INVAL;
+ int i;
+
+ if (prom_path == NULL) {
+ return (DEVFS_INVAL);
+ }
+ if (dev_path == NULL) {
+ return (DEVFS_INVAL);
+ }
+ if (strlen(dev_path) >= MAXPATHLEN)
+ return (DEVFS_INVAL);
+
+ if (*dev_path != '/')
+ return (DEVFS_INVAL);
+
+ prom_fd = prom_open(O_RDONLY);
+ if (prom_fd < 0) {
+ return (prom_fd);
+ }
+
+ /* query the prom */
+ (void) snprintf(opp->oprom_array, MAXVALSIZE, "%s", dev_path);
+ opp->oprom_size = MAXVALSIZE;
+
+ if (ioctl(prom_fd, OPROMDEV2PROMNAME, opp) == 0) {
+ prom_close(prom_fd);
+
+ /* return the prom path in prom_path */
+
+ i = len - opp->oprom_size;
+ if (i < 0) {
+ bcopy(opp->oprom_array, prom_path, len);
+ prom_path[len - 1] = NULL;
+ return (len);
+ } else {
+ bcopy(opp->oprom_array, prom_path, len);
+ return (opp->oprom_size);
+ }
+ }
+ /*
+ * either the prom does not support this ioctl or the argument
+ * was invalid.
+ */
+ if (errno == ENXIO) {
+ ret = DEVFS_NOTSUP;
+ }
+ prom_close(prom_fd);
+ return (ret);
+}
+
+/*
+ * Convert a physical or logical device name to a name the prom would
+ * understand. Fail if this platform does not support a prom or if
+ * the device does not correspond to a valid prom device.
+ * dev_path should be the name of a device in the logical or
+ * physical device namespace.
+ * prom_path is the prom version of the device name
+ * prom_path must be large enough to contain the result and is
+ * supplied by the user.
+ *
+ * This routine only supports converting leaf device paths
+ */
+int
+devfs_dev_to_prom_name(char *dev_path, char *prom_path)
+{
+ int rval;
+
+ rval = devfs_dev_to_prom_names(dev_path, prom_path, MAXPATHLEN);
+
+ if (rval < 0)
+ return (rval);
+ else
+ return (0);
+}
+
+/*
+ * Use the openprom driver's OPROMPATH2DRV ioctl to convert a devfs
+ * path to a driver name.
+ * devfs_path - the pathname of interest. This must be the physcical device
+ * path with the mount point prefix (ie. /devices) stripped off.
+ * drv_buf - user supplied buffer - the driver name will be stored here.
+ *
+ * If the prom lookup fails, we return the name of the last component in
+ * the pathname. This routine is useful for looking up driver names
+ * associated with generically named devices.
+ *
+ * This routine returns driver names that have aliases resolved.
+ */
+int
+devfs_path_to_drv(char *devfs_path, char *drv_buf)
+{
+ Oppbuf oppbuf;
+ struct openpromio *opp = &(oppbuf.opp);
+ char *slash, *colon, *dev_addr;
+ char driver_path[MAXPATHLEN];
+ int prom_fd;
+
+ if (drv_buf == NULL) {
+ return (-1);
+ }
+ if (devfs_path == NULL) {
+ return (-1);
+ }
+
+ if (strlen(devfs_path) >= MAXPATHLEN)
+ return (-1);
+
+ if (*devfs_path != '/')
+ return (-1);
+
+
+ /* strip off any minor node info at the end of the path */
+ (void) strcpy(driver_path, devfs_path);
+ slash = strrchr(driver_path, '/');
+ if (slash == NULL)
+ return (-1);
+ colon = strrchr(slash, ':');
+ if (colon != NULL)
+ *colon = '\0';
+
+ /* query the prom */
+ if ((prom_fd = prom_open(O_RDONLY)) >= 0) {
+ (void) strcpy(opp->oprom_array, driver_path);
+ opp->oprom_size = MAXVALSIZE;
+
+ if (ioctl(prom_fd, OPROMPATH2DRV, opp) == 0) {
+ prom_close(prom_fd);
+ /* return the driver name in drv_buf */
+ (void) strcpy(drv_buf, opp->oprom_array);
+ return (0);
+ }
+ prom_close(prom_fd);
+ } else if (prom_fd != DEVFS_NOTSUP)
+ return (-1);
+ /*
+ * If we get here, then either:
+ * 1. this platform does not support an openprom driver
+ * 2. we were asked to look up a device the prom does
+ * not know about (e.g. a pseudo device)
+ * In this case, we use the last component of the devfs path
+ * name and try to derive the driver name
+ */
+
+ /* use the last component of devfs_path as the driver name */
+ if ((dev_addr = strrchr(slash, '@')) != NULL)
+ *dev_addr = '\0';
+ slash++;
+
+ /* use opp->oprom_array as a buffer */
+ (void) strcpy(opp->oprom_array, slash);
+ if (devfs_resolve_aliases(opp->oprom_array) == NULL)
+ return (-1);
+ (void) strcpy(drv_buf, opp->oprom_array);
+ return (0);
+}
+
+/*
+ * These modctl calls do the equivalent of:
+ * ddi_name_to_major()
+ * ddi_major_to_name()
+ * This results in two things:
+ * - the driver name must be a valid one
+ * - any driver aliases are resolved.
+ * drv is overwritten with the resulting name.
+ */
+char *
+devfs_resolve_aliases(char *drv)
+{
+ major_t maj;
+ char driver_name[MAXNAMELEN + 1];
+
+ if (drv == NULL) {
+ return (NULL);
+ }
+
+ if (modctl(MODGETMAJBIND, drv, strlen(drv) + 1, &maj) < 0)
+ return (NULL);
+ else if (modctl(MODGETNAME, driver_name, sizeof (driver_name), &maj)
+ < 0) {
+ return (NULL);
+ } else {
+ (void) strcpy(drv, driver_name);
+ return (drv);
+ }
+}
+
+/*
+ * open the openprom device. and verify that we are on an
+ * OBP/1275 OF machine. If the prom does not exist, then we
+ * return an error
+ */
+static int
+prom_open(int oflag)
+{
+ int prom_fd = -1;
+ char *promdev = "/dev/openprom";
+
+ while (prom_fd < 0) {
+ if ((prom_fd = open(promdev, oflag)) < 0) {
+ if (errno == EAGAIN) {
+ (void) sleep(5);
+ continue;
+ }
+ if ((errno == ENXIO) || (errno == ENOENT)) {
+ return (DEVFS_NOTSUP);
+ }
+ if ((errno == EPERM) || (errno == EACCES)) {
+ return (DEVFS_PERM);
+ }
+ return (DEVFS_ERR);
+ } else
+ break;
+ }
+ if (is_openprom(prom_fd))
+ return (prom_fd);
+ else {
+ prom_close(prom_fd);
+ return (DEVFS_ERR);
+ }
+}
+
+static void
+prom_close(int prom_fd)
+{
+ (void) close(prom_fd);
+}
+
+/*
+ * is this an OBP/1275 OF machine?
+ */
+static int
+is_openprom(int prom_fd)
+{
+ Oppbuf oppbuf;
+ struct openpromio *opp = &(oppbuf.opp);
+ unsigned int i;
+
+ opp->oprom_size = MAXVALSIZE;
+ if (ioctl(prom_fd, OPROMGETCONS, opp) < 0)
+ return (0);
+
+ i = (unsigned int)((unsigned char)opp->oprom_array[0]);
+ return ((i & OPROMCONS_OPENPROM) == OPROMCONS_OPENPROM);
+}
+
+/*
+ * convert a prom device path name to an equivalent physical device
+ * path in the kernel.
+ */
+static int
+devfs_prom_to_dev_name(char *prom_path, char *dev_path)
+{
+ Oppbuf oppbuf;
+ struct openpromio *opp = &(oppbuf.opp);
+ int prom_fd;
+ int ret = DEVFS_INVAL;
+
+ if (dev_path == NULL) {
+ return (DEVFS_INVAL);
+ }
+ if (prom_path == NULL) {
+ return (DEVFS_INVAL);
+ }
+ if (strlen(prom_path) >= MAXPATHLEN)
+ return (DEVFS_INVAL);
+
+ if (*prom_path != '/') {
+ return (DEVFS_INVAL);
+ }
+
+ /* query the prom */
+ prom_fd = prom_open(O_RDONLY);
+ if (prom_fd < 0) {
+ return (prom_fd);
+ }
+ (void) strcpy(opp->oprom_array, prom_path);
+ opp->oprom_size = MAXVALSIZE;
+
+ if (ioctl(prom_fd, OPROMPROM2DEVNAME, opp) == 0) {
+ prom_close(prom_fd);
+ /*
+ * success
+ * return the prom path in prom_path
+ */
+ (void) strcpy(dev_path, opp->oprom_array);
+ return (0);
+ }
+ /*
+ * either the argument was not a valid name or the openprom
+ * driver does not support this ioctl.
+ */
+ if (errno == ENXIO) {
+ ret = DEVFS_NOTSUP;
+ }
+ prom_close(prom_fd);
+ return (ret);
+}
+/*
+ * convert a prom device path to a list of equivalent alias names
+ * If there is no alias node, or there are no aliases that correspond
+ * to dev, we return empty lists.
+ */
+static int
+prom_dev_to_alias(char *dev, uint_t options, char ***ret_buf)
+{
+ struct name_list *exact_list;
+ struct name_list *inexact_list;
+ struct name_list *list;
+ char *ptr;
+ char **array;
+ int prom_fd;
+ int count;
+ int vers;
+
+ vers = prom_obp_vers();
+ if (vers < 0) {
+ return (vers);
+ }
+
+ if (dev == NULL) {
+ return (DEVFS_INVAL);
+ }
+
+ if (*dev != '/')
+ return (DEVFS_INVAL);
+
+ if (strlen(dev) >= MAXPATHLEN)
+ return (DEVFS_INVAL);
+
+ if ((ptr = strchr(dev, ':')) != NULL) {
+ if (strchr(ptr, '/') != NULL)
+ return (DEVFS_INVAL);
+ }
+ if (ret_buf == NULL) {
+ return (DEVFS_INVAL);
+ }
+
+ prom_fd = prom_open(O_RDONLY);
+ if (prom_fd < 0) {
+ return (prom_fd);
+ }
+
+ (void) prom_srch_aliases_by_def(dev, &exact_list,
+ &inexact_list, prom_fd);
+
+ prom_close(prom_fd);
+
+ if ((options & BOOTDEV_NO_EXACT_ALIAS) != 0) {
+ free_name_list(exact_list, 1);
+ exact_list = NULL;
+ }
+
+ if ((options & BOOTDEV_NO_INEXACT_ALIAS) != 0) {
+ free_name_list(inexact_list, 1);
+ inexact_list = NULL;
+ }
+
+ count = 0;
+ list = exact_list;
+ while (list != NULL) {
+ list = list->next;
+ count++;
+ }
+ list = inexact_list;
+ while (list != NULL) {
+ list = list->next;
+ count++;
+ }
+
+ if ((*ret_buf = (char **)malloc((count + 1) * sizeof (char *)))
+ == NULL) {
+ free_name_list(inexact_list, 1);
+ free_name_list(exact_list, 1);
+ return (DEVFS_NOMEM);
+ }
+
+ array = *ret_buf;
+ count = 0;
+ list = exact_list;
+ while (list != NULL) {
+ array[count] = list->name;
+ list = list->next;
+ count++;
+ }
+ list = inexact_list;
+ while (list != NULL) {
+ array[count] = list->name;
+ list = list->next;
+ count++;
+ }
+ array[count] = NULL;
+ free_name_list(inexact_list, 0);
+ free_name_list(exact_list, 0);
+
+ return (0);
+}
+
+/*
+ * determine the version of prom we are running on.
+ * Also include any prom revision specific information.
+ */
+static int
+prom_obp_vers(void)
+{
+ Oppbuf oppbuf;
+ struct openpromio *opp = &(oppbuf.opp);
+ int prom_fd;
+ static int version = 0;
+
+ /* cache version */
+ if (version > 0) {
+ return (version);
+ }
+
+ prom_fd = prom_open(O_RDONLY);
+ if (prom_fd < 0) {
+ return (prom_fd);
+ }
+
+ opp->oprom_size = MAXVALSIZE;
+
+ if ((ioctl(prom_fd, OPROMGETVERSION, opp)) < 0) {
+ prom_close(prom_fd);
+ return (DEVFS_ERR);
+ }
+ prom_close(prom_fd);
+
+ version |= OBP_OF;
+
+ return (version);
+}
+/*
+ * search the aliases node by definition - compile a list of
+ * alias names that are both exact and inexact matches.
+ */
+static int
+prom_srch_aliases_by_def(char *promdev_def, struct name_list **exact_list,
+ struct name_list **inexact_list, int prom_fd)
+{
+ Oppbuf oppbuf;
+ Oppbuf propdef_oppbuf;
+ struct openpromio *opp = &(oppbuf.opp);
+ struct openpromio *propdef_opp = &(propdef_oppbuf.opp);
+ int *ip = (int *)((void *)opp->oprom_array);
+ int ret;
+ struct name_list *inexact_match = *inexact_list = NULL;
+ struct name_list *exact_match = *exact_list = NULL;
+ char alias_buf[MAXNAMELEN];
+ int found = 0;
+
+ (void) memset(oppbuf.buf, 0, BUFSIZE);
+ opp->oprom_size = MAXPROPSIZE;
+ *ip = 0;
+
+ if ((ret = ioctl(prom_fd, OPROMNXTPROP, opp)) < 0)
+ return (0);
+ if (opp->oprom_size == 0)
+ return (0);
+
+ while ((ret >= 0) && (opp->oprom_size > 0)) {
+ (void) strcpy(propdef_opp->oprom_array, opp->oprom_array);
+ opp->oprom_size = MAXPROPSIZE;
+ propdef_opp->oprom_size = MAXVALSIZE;
+ if ((ioctl(prom_fd, OPROMGETPROP, propdef_opp) < 0) ||
+ (propdef_opp->oprom_size == 0)) {
+ ret = ioctl(prom_fd, OPROMNXTPROP, opp);
+ continue;
+ }
+ ret = prom_compare_devs(promdev_def, propdef_opp->oprom_array);
+ if (ret == EXACT_MATCH) {
+ found++;
+ if (insert_alias_list(exact_list, opp->oprom_array)
+ != 0) {
+ free_name_list(exact_match, 1);
+ free_name_list(inexact_match, 1);
+ return (-1);
+ }
+ }
+ if (ret == INEXACT_MATCH) {
+ found++;
+ (void) strcpy(alias_buf, opp->oprom_array);
+ options_override(promdev_def, alias_buf);
+ if (insert_alias_list(inexact_list, alias_buf)
+ != 0) {
+ free_name_list(exact_match, 1);
+ free_name_list(inexact_match, 1);
+ return (-1);
+ }
+ }
+ ret = ioctl(prom_fd, OPROMNXTPROP, opp);
+ }
+ if (found) {
+ return (0);
+ } else {
+ return (-1);
+ }
+}
+
+/*
+ * free a list of name_list structs and optionally
+ * free the strings they contain.
+ */
+static void
+free_name_list(struct name_list *list, int free_name)
+{
+ struct name_list *next = list;
+
+ while (next != NULL) {
+ list = list->next;
+ if (free_name)
+ free(next->name);
+ free(next);
+ next = list;
+ }
+}
+
+/*
+ * insert a new alias in a list of aliases - the list is sorted
+ * in collating order (ignoring anything that comes after the
+ * ':' in the name).
+ */
+static int
+insert_alias_list(struct name_list **list, char *alias_name)
+{
+ struct name_list *entry = *list;
+ struct name_list *new_entry, *prev_entry;
+ int ret;
+ char *colon1, *colon2;
+
+ if ((new_entry =
+ (struct name_list *)malloc(sizeof (struct name_list)))
+ == NULL) {
+ return (-1);
+ }
+ if ((new_entry->name = strdup(alias_name)) == NULL) {
+ free(new_entry);
+ return (-1);
+ }
+ new_entry->next = NULL;
+
+ if (entry == NULL) {
+ *list = new_entry;
+ return (0);
+ }
+
+ if ((colon1 = strchr(alias_name, ':')) != NULL) {
+ *colon1 = '\0';
+ }
+ prev_entry = NULL;
+ while (entry != NULL) {
+ if ((colon2 = strchr(entry->name, ':')) != NULL) {
+ *colon2 = '\0';
+ }
+ ret = strcmp(alias_name, entry->name);
+ if (colon2 != NULL) {
+ *colon2 = ':';
+ }
+ /* duplicate */
+ if (ret == 0) {
+ free(new_entry->name);
+ free(new_entry);
+ if (colon1 != NULL) {
+ *colon1 = ':';
+ }
+ return (0);
+ }
+ if (ret < 0) {
+ new_entry->next = entry;
+ if (prev_entry == NULL) {
+ /* in beginning of list */
+ *list = new_entry;
+ } else {
+ /* in middle of list */
+ prev_entry->next = new_entry;
+ }
+ if (colon1 != NULL) {
+ *colon1 = ':';
+ }
+ return (0);
+ }
+ prev_entry = entry;
+ entry = entry->next;
+ }
+ /* at end of list */
+ prev_entry->next = new_entry;
+ new_entry->next = NULL;
+ if (colon1 != NULL) {
+ *colon1 = ':';
+ }
+ return (0);
+}
+/*
+ * append :x to alias_name to override any default minor name options
+ */
+static void
+options_override(char *prom_path, char *alias_name)
+{
+ char *colon;
+
+ if ((colon = strrchr(alias_name, ':')) != NULL) {
+ /*
+ * XXX - should alias names in /aliases ever have a
+ * : embedded in them?
+ * If so we ignore it.
+ */
+ *colon = '\0';
+ }
+
+ if ((colon = strrchr(prom_path, ':')) != NULL) {
+ (void) strcat(alias_name, colon);
+ }
+}
+
+/*
+ * compare to prom device names.
+ * if the device names are not fully qualified. we convert them -
+ * we only do this as a last resort though since it requires
+ * jumping into the kernel.
+ */
+static int
+prom_compare_devs(char *prom_dev1, char *prom_dev2)
+{
+ char *dev1, *dev2;
+ char *ptr1, *ptr2;
+ char *drvname1, *addrname1, *minorname1;
+ char *drvname2, *addrname2, *minorname2;
+ char component1[MAXNAMELEN], component2[MAXNAMELEN];
+ char devname1[MAXPATHLEN], devname2[MAXPATHLEN];
+ int unqualified_name = 0;
+ int error = EXACT_MATCH;
+ int len1, len2;
+ char *wildcard = ",0";
+
+ ptr1 = prom_dev1;
+ ptr2 = prom_dev2;
+
+ if ((ptr1 == NULL) || (*ptr1 != '/')) {
+ return (NO_MATCH);
+ }
+ if ((ptr2 == NULL) || (*ptr2 != '/')) {
+ return (NO_MATCH);
+ }
+
+ /*
+ * compare device names one component at a time.
+ */
+ while ((ptr1 != NULL) && (ptr2 != NULL)) {
+ *ptr1 = *ptr2 = '/';
+ dev1 = ptr1 + 1;
+ dev2 = ptr2 + 1;
+ if ((ptr1 = strchr(dev1, '/')) != NULL)
+ *ptr1 = '\0';
+ if ((ptr2 = strchr(dev2, '/')) != NULL)
+ *ptr2 = '\0';
+
+ (void) strcpy(component1, dev1);
+ (void) strcpy(component2, dev2);
+
+ parse_name(component1, &drvname1, &addrname1, &minorname1);
+ parse_name(component2, &drvname2, &addrname2, &minorname2);
+
+ if ((drvname1 == NULL) && (addrname1 == NULL)) {
+ error = NO_MATCH;
+ break;
+ }
+
+ if ((drvname2 == NULL) && (addrname2 == NULL)) {
+ error = NO_MATCH;
+ break;
+ }
+
+ if (_prom_strcmp(drvname1, drvname2) != 0) {
+ error = NO_MATCH;
+ break;
+ }
+
+ /*
+ * a possible name is driver_name@address. The address
+ * portion is optional (i.e. the name is not fully
+ * qualified.). We have to deal with the case where
+ * the component name is either driver_name or
+ * driver_name@address
+ */
+ if ((addrname1 == NULL) ^ (addrname2 == NULL)) {
+ unqualified_name = 1;
+ } else if (addrname1 &&
+ (_prom_strcmp(addrname1, addrname2) != 0)) {
+ /*
+ * check to see if appending a ",0" to the
+ * shorter address causes a match to occur.
+ * If so succeed.
+ */
+ len1 = strlen(addrname1);
+ len2 = strlen(addrname2);
+ if ((len1 < len2) &&
+ (strncmp(addrname1, addrname2, len1) == 0) &&
+ (strcmp(wildcard, &addrname2[len1]) == 0)) {
+ continue;
+ } else if ((len2 < len1) &&
+ (strncmp(addrname1, addrname2, len2) == 0) &&
+ (strcmp(wildcard, &addrname1[len2]) == 0)) {
+ continue;
+ }
+ error = NO_MATCH;
+ break;
+ }
+ }
+
+ /*
+ * if either of the two device paths still has more components,
+ * then we do not have a match.
+ */
+ if (ptr1 != NULL) {
+ *ptr1 = '/';
+ error = NO_MATCH;
+ }
+ if (ptr2 != NULL) {
+ *ptr2 = '/';
+ error = NO_MATCH;
+ }
+ if (error == NO_MATCH) {
+ return (error);
+ }
+
+ /*
+ * OK - we found a possible match but one or more of the
+ * path components was not fully qualified (did not have any
+ * address information. So we need to convert it to a form
+ * that is fully qualified and then compare the resulting
+ * strings.
+ */
+ if (unqualified_name != 0) {
+ if ((devfs_prom_to_dev_name(prom_dev1, devname1) < 0) ||
+ (devfs_prom_to_dev_name(prom_dev2, devname2) < 0)) {
+ return (NO_MATCH);
+ }
+ if ((dev1 = strrchr(devname1, ':')) != NULL) {
+ *dev1 = '\0';
+ }
+ if ((dev2 = strrchr(devname2, ':')) != NULL) {
+ *dev2 = '\0';
+ }
+ if (strcmp(devname1, devname2) != 0) {
+ return (NO_MATCH);
+ }
+ }
+ /*
+ * the resulting strings matched. If the minorname information
+ * matches, then we have an exact match, otherwise an inexact match
+ */
+ if (_prom_strcmp(minorname1, minorname2) == 0) {
+ return (EXACT_MATCH);
+ } else {
+ return (INEXACT_MATCH);
+ }
+}
+
+/*
+ * wrapper or strcmp - deals with null strings.
+ */
+static int
+_prom_strcmp(char *s1, char *s2)
+{
+ if ((s1 == NULL) && (s2 == NULL))
+ return (0);
+ if ((s1 == NULL) && (s2 != NULL)) {
+ return (-1);
+ }
+ if ((s1 != NULL) && (s2 == NULL)) {
+ return (1);
+ }
+ return (strcmp(s1, s2));
+}
+/*
+ * break device@a,b:minor into components
+ */
+static void
+parse_name(char *name, char **drvname, char **addrname, char **minorname)
+{
+ char *cp, ch;
+
+ cp = *drvname = name;
+ *addrname = *minorname = NULL;
+ if (*name == '@')
+ *drvname = NULL;
+
+ while ((ch = *cp) != '\0') {
+ if (ch == '@')
+ *addrname = ++cp;
+ else if (ch == ':')
+ *minorname = ++cp;
+ ++cp;
+ }
+ if (*addrname) {
+ *((*addrname)-1) = '\0';
+ }
+ if (*minorname) {
+ *((*minorname)-1) = '\0';
+ }
+}
+
+/*
+ * only on sparc for now
+ */
+int
+devfs_bootdev_modifiable(void)
+{
+#if defined(sparc)
+ return (0);
+#else
+ return (DEVFS_NOTSUP);
+#endif
+}
diff --git a/usr/src/lib/libdevinfo/devfsmap.c b/usr/src/lib/libdevinfo/devfsmap.c
new file mode 100644
index 0000000000..0645f60794
--- /dev/null
+++ b/usr/src/lib/libdevinfo/devfsmap.c
@@ -0,0 +1,1936 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifdef lint
+#define _REENTRANT /* for localtime_r */
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <strings.h>
+#include <stdarg.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stropts.h>
+#include <time.h>
+#include <sys/param.h>
+#include <sys/vfstab.h>
+#include <dirent.h>
+#ifdef __sparc
+#include <sys/scsi/adapters/scsi_vhci.h>
+#include <sys/sunmdi.h>
+#endif /* __sparc */
+#include "libdevinfo.h"
+#include "device_info.h"
+
+#define isnewline(ch) ((ch) == '\n' || (ch) == '\r' || (ch) == '\f')
+#define isnamechar(ch) (isalpha(ch) || isdigit(ch) || (ch) == '_' ||\
+ (ch) == '-')
+#define MAX_TOKEN_SIZE 1024
+#define BUFSIZE 1024
+#define STRVAL(s) ((s) ? (s) : "NULL")
+
+#define SCSI_VHCI_CONF "/kernel/drv/scsi_vhci.conf"
+#define QLC_CONF "/kernel/drv/qlc.conf"
+#define FP_CONF "/kernel/drv/fp.conf"
+#define DRIVER_CLASSES "/etc/driver_classes"
+#define FP_AT "fp@"
+#define VHCI_CTL_NODE "/devices/scsi_vhci:devctl"
+#define SLASH_DEVICES "/devices"
+#define SLASH_DEVICES_SLASH "/devices/"
+#define SLASH_FP_AT "/fp@"
+#define SLASH_SCSI_VHCI "/scsi_vhci"
+#define META_DEV "/dev/md/dsk/"
+#define SLASH_DEV_SLASH "/dev/"
+
+/*
+ * Macros to produce a quoted string containing the value of a
+ * preprocessor macro. For example, if SIZE is defined to be 256,
+ * VAL2STR(SIZE) is "256". This is used to construct format
+ * strings for scanf-family functions below.
+ */
+#define QUOTE(x) #x
+#define VAL2STR(x) QUOTE(x)
+
+#ifdef __sparc
+
+typedef enum {
+ CLIENT_TYPE_UNKNOWN,
+ CLIENT_TYPE_PHCI,
+ CLIENT_TYPE_VHCI
+} client_type_t;
+
+typedef enum {
+ T_EQUALS,
+ T_AMPERSAND,
+ T_BIT_OR,
+ T_STAR,
+ T_POUND,
+ T_COLON,
+ T_SEMICOLON,
+ T_COMMA,
+ T_SLASH,
+ T_WHITE_SPACE,
+ T_NEWLINE,
+ T_EOF,
+ T_STRING,
+ T_HEXVAL,
+ T_DECVAL,
+ T_NAME
+} token_t;
+
+typedef enum {
+ begin, parent, drvname, drvclass, prop,
+ parent_equals, name_equals, drvclass_equals,
+ parent_equals_string, name_equals_string,
+ drvclass_equals_string,
+ prop_equals, prop_equals_string, prop_equals_integer,
+ prop_equals_string_comma, prop_equals_integer_comma
+} conf_state_t;
+
+/* structure to hold entries with mpxio-disable property in driver.conf file */
+struct conf_entry {
+ char *name;
+ char *parent;
+ char *class;
+ char *unit_address;
+ int port;
+ int mpxio_disable;
+ struct conf_entry *next;
+};
+
+struct conf_file {
+ char *filename;
+ FILE *fp;
+ int linenum;
+};
+
+static char *tok_err = "Unexpected token '%s'\n";
+
+#endif /* __sparc */
+
+/* #define DEBUG */
+
+#ifdef DEBUG
+
+int devfsmap_debug = 0;
+/* /var/run is not mounted at install time. Therefore use /tmp */
+char *devfsmap_logfile = "/tmp/devfsmap.log";
+static FILE *logfp;
+#define logdmsg(args) log_debug_msg args
+static void vlog_debug_msg(char *, va_list);
+static void log_debug_msg(char *, ...);
+#ifdef __sparc
+static void log_confent_list(char *, struct conf_entry *, int);
+static void log_pathlist(char **);
+#endif /* __sparc */
+
+#else /* DEBUG */
+#define logdmsg(args) /* nothing */
+#endif /* DEBUG */
+
+#ifdef __sparc
+
+/*
+ * Leave NEWLINE as the next character.
+ */
+static void
+find_eol(FILE *fp)
+{
+ int ch;
+
+ while ((ch = getc(fp)) != EOF) {
+ if (isnewline(ch)) {
+ (void) ungetc(ch, fp);
+ break;
+ }
+ }
+}
+
+/* ignore parsing errors */
+/*ARGSUSED*/
+static void
+file_err(struct conf_file *filep, char *fmt, ...)
+{
+#ifdef DEBUG
+ va_list ap;
+
+ va_start(ap, fmt);
+ log_debug_msg("WARNING: %s line # %d: ",
+ filep->filename, filep->linenum);
+ vlog_debug_msg(fmt, ap);
+ va_end(ap);
+#endif /* DEBUG */
+}
+
+/* return the next token from the given driver.conf file, or -1 on error */
+static token_t
+lex(struct conf_file *filep, char *val, size_t size)
+{
+ char *cp;
+ int ch, oval, badquote;
+ size_t remain;
+ token_t token;
+ FILE *fp = filep->fp;
+
+ if (size < 2)
+ return (-1);
+
+ cp = val;
+ while ((ch = getc(fp)) == ' ' || ch == '\t')
+ ;
+
+ remain = size - 1;
+ *cp++ = (char)ch;
+ switch (ch) {
+ case '=':
+ token = T_EQUALS;
+ break;
+ case '&':
+ token = T_AMPERSAND;
+ break;
+ case '|':
+ token = T_BIT_OR;
+ break;
+ case '*':
+ token = T_STAR;
+ break;
+ case '#':
+ token = T_POUND;
+ break;
+ case ':':
+ token = T_COLON;
+ break;
+ case ';':
+ token = T_SEMICOLON;
+ break;
+ case ',':
+ token = T_COMMA;
+ break;
+ case '/':
+ token = T_SLASH;
+ break;
+ case ' ':
+ case '\t':
+ case '\f':
+ while ((ch = getc(fp)) == ' ' ||
+ ch == '\t' || ch == '\f') {
+ if (--remain == 0) {
+ *cp = '\0';
+ return (-1);
+ }
+ *cp++ = (char)ch;
+ }
+ (void) ungetc(ch, fp);
+ token = T_WHITE_SPACE;
+ break;
+ case '\n':
+ case '\r':
+ token = T_NEWLINE;
+ break;
+ case '"':
+ remain++;
+ cp--;
+ badquote = 0;
+ while (!badquote && (ch = getc(fp)) != '"') {
+ switch (ch) {
+ case '\n':
+ case EOF:
+ file_err(filep, "Missing \"\n");
+ remain = size - 1;
+ cp = val;
+ *cp++ = '\n';
+ badquote = 1;
+ /* since we consumed the newline/EOF */
+ (void) ungetc(ch, fp);
+ break;
+
+ case '\\':
+ if (--remain == 0) {
+ *cp = '\0';
+ return (-1);
+ }
+ ch = (char)getc(fp);
+ if (!isdigit(ch)) {
+ /* escape the character */
+ *cp++ = (char)ch;
+ break;
+ }
+ oval = 0;
+ while (ch >= '0' && ch <= '7') {
+ ch -= '0';
+ oval = (oval << 3) + ch;
+ ch = (char)getc(fp);
+ }
+ (void) ungetc(ch, fp);
+ /* check for character overflow? */
+ if (oval > 127) {
+ file_err(filep,
+ "Character "
+ "overflow detected.\n");
+ }
+ *cp++ = (char)oval;
+ break;
+ default:
+ if (--remain == 0) {
+ *cp = '\0';
+ return (-1);
+ }
+ *cp++ = (char)ch;
+ break;
+ }
+ }
+ token = T_STRING;
+ break;
+
+ case EOF:
+ token = T_EOF;
+ break;
+
+ default:
+ /*
+ * detect a lone '-' (including at the end of a line), and
+ * identify it as a 'name'
+ */
+ if (ch == '-') {
+ if (--remain == 0) {
+ *cp = '\0';
+ return (-1);
+ }
+ *cp++ = (char)(ch = getc(fp));
+ if (ch == ' ' || ch == '\t' || ch == '\n') {
+ (void) ungetc(ch, fp);
+ remain++;
+ cp--;
+ token = T_NAME;
+ break;
+ }
+ } else if (ch == '~' || ch == '-') {
+ if (--remain == 0) {
+ *cp = '\0';
+ return (-1);
+ }
+ *cp++ = (char)(ch = getc(fp));
+ }
+
+
+ if (isdigit(ch)) {
+ if (ch == '0') {
+ if ((ch = getc(fp)) == 'x') {
+ if (--remain == 0) {
+ *cp = '\0';
+ return (-1);
+ }
+ *cp++ = (char)ch;
+ ch = getc(fp);
+ while (isxdigit(ch)) {
+ if (--remain == 0) {
+ *cp = '\0';
+ return (-1);
+ }
+ *cp++ = (char)ch;
+ ch = getc(fp);
+ }
+ (void) ungetc(ch, fp);
+ token = T_HEXVAL;
+ } else {
+ goto digit;
+ }
+ } else {
+ ch = getc(fp);
+digit:
+ while (isdigit(ch)) {
+ if (--remain == 0) {
+ *cp = '\0';
+ return (-1);
+ }
+ *cp++ = (char)ch;
+ ch = getc(fp);
+ }
+ (void) ungetc(ch, fp);
+ token = T_DECVAL;
+ }
+ } else if (isalpha(ch) || ch == '\\') {
+ if (ch != '\\') {
+ ch = getc(fp);
+ } else {
+ /*
+ * if the character was a backslash,
+ * back up so we can overwrite it with
+ * the next (i.e. escaped) character.
+ */
+ remain++;
+ cp--;
+ }
+ while (isnamechar(ch) || ch == '\\') {
+ if (ch == '\\')
+ ch = getc(fp);
+ if (--remain == 0) {
+ *cp = '\0';
+ return (-1);
+ }
+ *cp++ = (char)ch;
+ ch = getc(fp);
+ }
+ (void) ungetc(ch, fp);
+ token = T_NAME;
+ } else {
+ return (-1);
+ }
+ break;
+ }
+
+ *cp = '\0';
+
+ return (token);
+}
+
+static void
+free_confent(struct conf_entry *confent)
+{
+ if (confent->name)
+ free(confent->name);
+ if (confent->parent)
+ free(confent->parent);
+ if (confent->class)
+ free(confent->class);
+ if (confent->unit_address)
+ free(confent->unit_address);
+ free(confent);
+}
+
+static void
+free_confent_list(struct conf_entry *confent_list)
+{
+ struct conf_entry *confent, *next;
+
+ for (confent = confent_list; confent != NULL; confent = next) {
+ next = confent->next;
+ free_confent(confent);
+ }
+}
+
+/*
+ * Parse the next entry from the driver.conf file and return in the form of
+ * a pointer to the conf_entry.
+ */
+static struct conf_entry *
+parse_conf_entry(struct conf_file *filep, char *tokbuf, size_t linesize)
+{
+ char *prop_name, *string;
+ token_t token;
+ struct conf_entry *confent;
+ conf_state_t state;
+ int failed = 1;
+
+ if ((confent = calloc(1, sizeof (*confent))) == NULL)
+ return (NULL);
+
+ confent->port = -1;
+ confent->mpxio_disable = -1;
+
+ state = begin;
+ token = T_NAME;
+ prop_name = NULL;
+ string = NULL;
+ do {
+ switch (token) {
+ case T_NAME:
+ switch (state) {
+ case prop_equals_string:
+ case prop_equals_integer:
+ case begin:
+ state = prop;
+ if ((prop_name = strdup(tokbuf)) == NULL)
+ goto bad;
+ break;
+ default:
+ file_err(filep, tok_err, tokbuf);
+ }
+ break;
+ case T_EQUALS:
+ switch (state) {
+ case prop:
+ state = prop_equals;
+ break;
+ default:
+ file_err(filep, tok_err, tokbuf);
+ }
+ break;
+ case T_STRING:
+ switch (state) {
+ case prop_equals:
+ if ((string = strdup(tokbuf)) == NULL)
+ goto bad;
+
+ state = begin;
+ if (strcmp(prop_name, "PARENT") == 0 ||
+ strcmp(prop_name, "parent") == 0) {
+ if (confent->parent) {
+ file_err(filep,
+ "'parent' property already specified\n");
+ goto bad;
+ }
+ confent->parent = string;
+ } else if (strcmp(prop_name, "NAME") == 0 ||
+ strcmp(prop_name, "name") == 0) {
+ if (confent->name) {
+ file_err(filep,
+ "'name' property already specified\n");
+ goto bad;
+ }
+ confent->name = string;
+ } else if (strcmp(prop_name, "CLASS") == 0 ||
+ strcmp(prop_name, "class") == 0) {
+ if (confent->class) {
+ file_err(filep,
+ "'class' property already specified\n");
+ goto bad;
+ }
+ confent->class = string;
+ } else if (strcmp(prop_name, "unit-address")
+ == 0) {
+ if (confent->unit_address) {
+ file_err(filep,
+ "'unit-address' property already specified\n");
+ goto bad;
+ }
+ confent->unit_address = string;
+ } else if (strcmp(prop_name, "mpxio-disable")
+ == 0) {
+ if (confent->mpxio_disable != -1) {
+ file_err(filep,
+ "'mpxio-disable' property already specified\n");
+ goto bad;
+ }
+ if (strcmp(string, "yes") == 0)
+ confent->mpxio_disable = 1;
+ else if (strcmp(string, "no") == 0)
+ confent->mpxio_disable = 0;
+ else {
+ file_err(filep,
+ "'mpxio-disable' property setting is invalid. "
+ "The value must be either \"yes\" or \"no\"\n");
+ goto bad;
+ }
+ free(string);
+ } else {
+ free(string);
+ state = prop_equals_string;
+ }
+ string = NULL;
+ free(prop_name);
+ prop_name = NULL;
+ break;
+
+ case prop_equals_string_comma:
+ state = prop_equals_string;
+ break;
+ default:
+ file_err(filep, tok_err, tokbuf);
+ }
+ break;
+ case T_HEXVAL:
+ case T_DECVAL:
+ switch (state) {
+ case prop_equals:
+ if (strcmp(prop_name, "port") == 0) {
+ if (confent->port != -1) {
+ file_err(filep,
+ "'port' property already specified\n");
+ goto bad;
+ }
+ confent->port =
+ (int)strtol(tokbuf, NULL, 0);
+ state = begin;
+ } else
+ state = prop_equals_integer;
+ free(prop_name);
+ prop_name = NULL;
+ break;
+
+ case prop_equals_integer_comma:
+ state = prop_equals_integer;
+ break;
+ default:
+ file_err(filep, tok_err, tokbuf);
+ }
+ break;
+ case T_COMMA:
+ switch (state) {
+ case prop_equals_string:
+ state = prop_equals_string_comma;
+ break;
+ case prop_equals_integer:
+ state = prop_equals_integer_comma;
+ break;
+ default:
+ file_err(filep, tok_err, tokbuf);
+ }
+ break;
+ case T_NEWLINE:
+ filep->linenum++;
+ break;
+ case T_POUND:
+ find_eol(filep->fp);
+ break;
+ case T_EOF:
+ file_err(filep, "Unexpected EOF\n");
+ goto bad;
+ default:
+ file_err(filep, tok_err, tokbuf);
+ goto bad;
+ }
+ } while ((token = lex(filep, tokbuf, linesize)) != T_SEMICOLON);
+
+ failed = 0;
+
+bad:
+ if (prop_name)
+ free(prop_name);
+ if (string)
+ free(string);
+ if (failed == 1) {
+ free_confent(confent);
+ return (NULL);
+ }
+ return (confent);
+}
+
+/*
+ * Parse all entries with mpxio-disable property in the given driver.conf
+ * file.
+ *
+ * fname driver.conf file name
+ * confent_list on return *confent_list will contain the list of
+ * driver.conf file entries with mpxio-disable property.
+ * mpxio_disable on return *mpxio_disable is set to the setting of the
+ * driver global mpxio-dissable property as follows.
+ * 0 if driver mpxio-disable="no"
+ * 1 if driver mpxio-disable="yes"
+ * -1 if driver mpxio-disable property isn't specified.
+ */
+static void
+parse_conf_file(char *fname, struct conf_entry **confent_list,
+ int *mpxio_disable)
+{
+ struct conf_entry *confent, *tail = NULL;
+ token_t token;
+ struct conf_file file;
+ char tokval[MAX_TOKEN_SIZE];
+
+ *confent_list = NULL;
+ *mpxio_disable = -1;
+ if ((file.fp = fopen(fname, "r")) == NULL)
+ return;
+
+ file.filename = fname;
+ file.linenum = 1;
+
+ while ((token = lex(&file, tokval, MAX_TOKEN_SIZE)) != T_EOF) {
+ switch (token) {
+ case T_POUND:
+ /*
+ * Skip comments.
+ */
+ find_eol(file.fp);
+ break;
+ case T_NAME:
+ if ((confent = parse_conf_entry(&file, tokval,
+ MAX_TOKEN_SIZE)) == NULL)
+ break;
+ /*
+ * No name indicates global property.
+ * Make sure parent and class not NULL.
+ */
+ if (confent->name == NULL) {
+ if (confent->parent ||
+ confent->class) {
+ file_err(&file,
+ "missing name attribute\n");
+ } else if (confent->mpxio_disable != -1) {
+ if (*mpxio_disable == -1)
+ *mpxio_disable =
+ confent->mpxio_disable;
+ else
+ file_err(&file,
+ "'mpxio-disable' property already specified\n");
+ }
+ free_confent(confent);
+ break;
+ }
+
+ /*
+ * This is a node spec, either parent or class
+ * must be specified.
+ */
+ if (confent->parent == NULL && confent->class == NULL) {
+ file_err(&file,
+ "missing parent or class attribute\n");
+ free_confent(confent);
+ break;
+ }
+
+ /* only need entries with mpxio_disable property */
+ if (confent->mpxio_disable == -1) {
+ free_confent(confent);
+ break;
+ }
+
+ if (tail)
+ tail->next = confent;
+ else
+ *confent_list = confent;
+ tail = confent;
+ break;
+
+ case T_NEWLINE:
+ file.linenum++;
+ break;
+ default:
+ break;
+ }
+ }
+
+ (void) fclose(file.fp);
+}
+
+/*
+ * Return the driver class of the given driver_name.
+ * The memory for the driver class is allocated by this function and the
+ * caller must free it.
+ */
+static char *
+get_driver_class(char *rootdir, char *driver_name)
+{
+ FILE *fp;
+ char buf[BUFSIZE];
+ char driver[BUFSIZE];
+ char class_name[BUFSIZE];
+
+ logdmsg(("get_driver_class: rootdir = %s, driver name = %s\n",
+ rootdir, driver_name));
+
+ (void) snprintf(buf, sizeof (buf), "%s%s", rootdir, DRIVER_CLASSES);
+
+ if ((fp = fopen(buf, "r")) == NULL) {
+ logdmsg(("get_driver_class: failed to open %s: %s\n",
+ buf, strerror(errno)));
+ return (NULL);
+ }
+
+ while (fgets(buf, sizeof (buf), fp) != NULL) {
+ /* LINTED - unbounded string specifier */
+ if ((sscanf(buf, "%s %s", driver, class_name) == 2) &&
+ driver[0] != '#' && strcmp(driver, driver_name) == 0) {
+ logdmsg(("get_driver_class: driver class = %s\n",
+ class_name));
+ (void) fclose(fp);
+ return (strdup(class_name));
+ }
+ }
+
+ (void) fclose(fp);
+ return (NULL);
+}
+
+static int
+lookup_in_confent_list(struct conf_entry *confent_list,
+ int match_class, char *parent, char *unit_addr, int port)
+{
+ struct conf_entry *confent;
+ char *par;
+
+ logdmsg(("lookup_in_confent_list: %s = \"%s\", unit_addr = \"%s\", "
+ "port = %d\n", (match_class) ? "class" : "parent", parent,
+ STRVAL(unit_addr), port));
+
+ for (confent = confent_list; confent != NULL; confent = confent->next) {
+ par = (match_class) ? confent->class : confent->parent;
+ if (unit_addr) {
+ if (confent->unit_address != NULL &&
+ strcmp(confent->unit_address, unit_addr) == 0 &&
+ par != NULL && strcmp(par, parent) == 0)
+ return (confent->mpxio_disable);
+ } else {
+ if (confent->port == port &&
+ par != NULL && strcmp(par, parent) == 0)
+ return (confent->mpxio_disable);
+ }
+ }
+ return (-1);
+}
+
+/*
+ * lookup mpxio-disabled property setting for the given path in the given
+ * driver.conf file. Match the entries from most specific to least specific.
+ *
+ * conf_file the path name of either fp.conf, qlc.conf or scsi_vhci.conf
+ * path /devices node path without the /devices prefix.
+ * If the conf_file is fp.conf, path must be a fp node path
+ * if the conf_file is qlc.conf, path must be a qlc node path.
+ * if the conf_file is scsi_vhci.conf, path must be NULL.
+ * ex: /pci@8,600000/SUNW,qlc@4/fp@0,0
+ * /pci@8,600000/SUNW,qlc@4
+ *
+ * returns:
+ * 0 if mpxio-disable="no"
+ * 1 if mpxio-disable="yes"
+ * -1 if mpxio-disable property isn't specified.
+ */
+static int
+lookup_in_conf_file(char *rootdir, char *conf_file, char *path)
+{
+ struct conf_entry *confent_list = NULL;
+ int mpxio_disable;
+ di_node_t par_node = DI_NODE_NIL;
+ char *node_name = NULL, *node_addr = NULL;
+ char *unit_addr = NULL;
+ int port = -1;
+ char *par_node_name = NULL, *par_node_addr = NULL;
+ char *par_binding_name = NULL, *par_driver_name = NULL;
+ char *par_driver_class = NULL, *par_node_name_addr;
+ int rv = -1;
+ char buf[MAXPATHLEN];
+
+ logdmsg(("lookup_in_conf_file: rootdir = \"%s\", conf_file = \"%s\", "
+ "path = \"%s\"\n", rootdir, conf_file, STRVAL(path)));
+
+ (void) snprintf(buf, MAXPATHLEN, "%s%s", rootdir, conf_file);
+ parse_conf_file(buf, &confent_list, &mpxio_disable);
+#ifdef DEBUG
+ log_confent_list(buf, confent_list, mpxio_disable);
+#endif
+
+ /* if path is NULL, return driver global mpxio-disable setting */
+ if (path == NULL) {
+ rv = mpxio_disable;
+ goto done;
+ }
+
+ if ((node_name = strrchr(path, '/')) == NULL)
+ goto done;
+
+ *node_name = '\0';
+ node_name++;
+
+ if ((node_addr = strchr(node_name, '@')) == NULL)
+ goto done;
+
+ *node_addr = '\0';
+ node_addr++;
+
+ if (strcmp(node_name, "fp") == 0) {
+ /* get port number; encoded in the node addr as a hex number */
+ port = (int)strtol(node_addr, NULL, 16);
+ } else
+ unit_addr = node_addr;
+
+ /*
+ * Match from most specific to least specific;
+ * first, start the lookup based on full path.
+ */
+ if ((rv = lookup_in_confent_list(confent_list, 0, path,
+ unit_addr, port)) != -1)
+ goto done;
+
+ /* lookup nodename@address */
+ if ((par_node_name_addr = strrchr(path, '/')) != NULL) {
+ par_node_name_addr++;
+ if ((rv = lookup_in_confent_list(confent_list, 0,
+ par_node_name_addr, unit_addr, port)) != -1)
+ goto done;
+ }
+
+ /* di_init() doesn't work when 0 is passed in flags */
+ par_node = di_init(path, DINFOMINOR);
+ if (par_node != DI_NODE_NIL) {
+ par_node_name = di_node_name(par_node);
+ par_node_addr = di_bus_addr(par_node);
+ par_binding_name = di_binding_name(par_node);
+ par_driver_name = di_driver_name(par_node);
+ }
+
+ logdmsg(("par_node_name = %s\n", STRVAL(par_node_name)));
+ logdmsg(("par_node_addr = %s\n", STRVAL(par_node_addr)));
+ logdmsg(("par_binding_name = %s\n", STRVAL(par_binding_name)));
+ logdmsg(("par_driver_name = %s\n", STRVAL(par_driver_name)));
+
+ /* lookup bindingname@address */
+ if (par_binding_name != NULL && par_binding_name != par_node_name &&
+ par_node_addr != NULL) {
+ (void) snprintf(buf, sizeof (buf), "%s@%s", par_binding_name,
+ par_node_addr);
+ if ((rv = lookup_in_confent_list(confent_list, 0,
+ buf, unit_addr, port)) != -1)
+ goto done;
+ }
+
+ /* lookup binding name */
+ if (par_binding_name != NULL) {
+ if ((rv = lookup_in_confent_list(confent_list, 0,
+ par_binding_name, unit_addr, port)) != -1)
+ goto done;
+ }
+
+ if (par_driver_name != NULL) {
+ /* lookup driver name */
+ if ((rv = lookup_in_confent_list(confent_list, 0,
+ par_driver_name, unit_addr, port)) != -1)
+ goto done;
+
+ /* finally, lookup class name */
+ par_driver_class = get_driver_class(rootdir, par_driver_name);
+ if (par_driver_class != NULL) {
+ if ((rv = lookup_in_confent_list(confent_list, 1,
+ par_driver_class, unit_addr, port)) != -1)
+ goto done;
+ }
+ }
+
+ /*
+ * no match so far;
+ * use the driver global mpxio-disable setting if exists.
+ */
+ rv = mpxio_disable;
+
+done:
+ if (node_name != NULL)
+ *(node_name - 1) = '/';
+ if (node_addr != NULL)
+ *(node_addr - 1) = '@';
+ if (par_driver_class != NULL)
+ free(par_driver_class);
+ if (confent_list != NULL)
+ free_confent_list(confent_list);
+ if (par_node != DI_NODE_NIL)
+ di_fini(par_node);
+
+ return (rv);
+}
+
+/*
+ * Given client_name return whether it is a phci or vhci based name.
+ * client_name is /devices name of a client without the /devices prefix.
+ *
+ * client_name Return value
+ * .../fp@xxx/ssd@yyy CLIENT_TYPE_PHCI
+ * .../scsi_vhci/ssd@yyy CLIENT_TYPE_VHCI
+ * other CLIENT_TYPE_UNKNOWN
+ */
+static client_type_t
+client_name_type(char *client_name)
+{
+ client_type_t client_type;
+ char *p1, *p2;
+
+ logdmsg(("client_name_type: client_name = %s\n", client_name));
+
+ if (strncmp(client_name, SLASH_SCSI_VHCI,
+ sizeof (SLASH_SCSI_VHCI) - 1) == 0)
+ return (CLIENT_TYPE_VHCI);
+
+ if (*client_name != '/')
+ return (CLIENT_TYPE_UNKNOWN);
+
+ if ((p1 = strrchr(client_name, '/')) == NULL)
+ return (CLIENT_TYPE_UNKNOWN);
+
+ *p1 = '\0';
+
+ if ((p2 = strrchr(client_name, '/')) != NULL &&
+ strncmp(p2, SLASH_FP_AT, sizeof (SLASH_FP_AT) - 1) == 0)
+ client_type = CLIENT_TYPE_PHCI;
+ else
+ client_type = CLIENT_TYPE_UNKNOWN;
+
+ *p1 = '/';
+ return (client_type);
+}
+
+/*
+ * Compare controller name portion of dev1 and dev2.
+ *
+ * rootdir root directory of the target environment
+ * dev1 can be either a /dev link or /devices name in the target
+ * environemnt
+ * dev2 /devices name of a device without the /devices prefix
+ *
+ * Returns:
+ * 0 if controller names match
+ * 1 if controller names don't match
+ * -1 an error occurred.
+ */
+static int
+compare_controller(char *rootdir, char *dev1, char *dev2)
+{
+ int linksize;
+ char *p1, *p;
+ char physdev1[MAXPATHLEN];
+ char buf[MAXPATHLEN];
+
+ logdmsg(("compare_controller: rootdir = %s, dev1 = %s, dev2 = %s\n",
+ rootdir, dev1, dev2));
+
+ if (strncmp(dev1, SLASH_DEV_SLASH, sizeof (SLASH_DEV_SLASH) - 1)
+ == 0) {
+ (void) snprintf(buf, MAXPATHLEN, "%s%s", rootdir, dev1);
+ if ((linksize = readlink(buf, physdev1, MAXPATHLEN)) > 0 &&
+ linksize < (MAXPATHLEN - 1)) {
+ physdev1[linksize] = '\0';
+ logdmsg(("compare_controller: physdev1 = %s\n",
+ physdev1));
+ } else
+ return (-1);
+ } else
+ (void) strlcpy(physdev1, dev1, MAXPATHLEN);
+
+ if ((p1 = strstr(physdev1, SLASH_DEVICES)) == NULL)
+ return (-1);
+
+ p1 += sizeof (SLASH_DEVICES) - 1;
+ /* strip the device portion */
+ if ((p = strrchr(p1, '/')) == NULL)
+ return (-1);
+ *p = '\0';
+
+ if ((p = strrchr(dev2, '/')) == NULL)
+ return (-1);
+ *p = '\0';
+
+ logdmsg(("compare_controller: path1 = %s, path2 = %s\n",
+ p1, dev2));
+ if (strcmp(p1, dev2) == 0) {
+ *p = '/';
+ return (0);
+ } else {
+ *p = '/';
+ return (1);
+ }
+}
+
+/*
+ * Check if the specified device path is on the root controller.
+ *
+ * rootdir root directory of the target environment
+ * path /devices name of a device without the /devices prefix
+ *
+ * Returns
+ * 1 if the path is on the root controller
+ * 0 if the path is not on the root controller
+ * -1 if an error occurs
+ */
+static int
+is_root_controller(char *rootdir, char *path)
+{
+ FILE *fp;
+ char *tmpfile;
+ int rv = -1;
+ struct vfstab vfsent;
+ char buf[MAXPATHLEN];
+ char ctd[MAXNAMELEN + 1];
+
+ logdmsg(("is_root_controller: rootdir = %s, path = %s\n", rootdir,
+ path));
+
+ (void) snprintf(buf, MAXPATHLEN, "%s%s", rootdir, VFSTAB);
+
+ if ((fp = fopen(buf, "r")) == NULL) {
+ logdmsg(("is_root_controller: failed to open %s: %s\n",
+ buf, strerror(errno)));
+ return (-1);
+ }
+
+ if (getvfsfile(fp, &vfsent, "/") != 0) {
+ logdmsg(("is_root_controller: getvfsfile: failed to read "
+ "vfstab entry for mount point \"/\": %s\n",
+ strerror(errno)));
+ (void) fclose(fp);
+ return (-1);
+ }
+ (void) fclose(fp);
+
+ /* check if the root is an svm metadisk */
+ if (strncmp(vfsent.vfs_special, META_DEV, sizeof (META_DEV) - 1) != 0) {
+ if (compare_controller(rootdir, vfsent.vfs_special, path) == 0)
+ return (1);
+ else
+ return (0);
+ }
+
+ /* Don't use /var/run as it is not mounted in miniroot */
+ if ((tmpfile = tempnam("/tmp", "diirc")) == NULL) {
+ logdmsg(("is_root_controller: tempnam: failed: %s\n",
+ strerror(errno)));
+ return (-1);
+ }
+
+ /* get metadisk components using metastat command */
+ (void) snprintf(buf, MAXPATHLEN,
+ "/usr/sbin/metastat -p %s 2>/dev/null | "
+ "/usr/bin/grep ' 1 1 ' | "
+ "/usr/bin/sed -e 's/^.* 1 1 //' | "
+ "/usr/bin/cut -f1 -d ' ' > %s",
+ vfsent.vfs_special + sizeof (META_DEV) - 1, tmpfile);
+
+ logdmsg(("is_root_controller: command = %s\n", buf));
+ fp = NULL;
+ if (system(buf) == 0 && (fp = fopen(tmpfile, "r")) != NULL) {
+ while (fscanf(fp, "%" VAL2STR(MAXNAMELEN) "s", ctd) == 1) {
+ (void) snprintf(buf, MAXPATHLEN, "/dev/dsk/%s", ctd);
+ if (compare_controller(rootdir, buf, path) == 0) {
+ rv = 1;
+ goto out;
+ }
+ }
+ rv = 0;
+ }
+
+out:
+ if (fp)
+ (void) fclose(fp);
+ (void) unlink(tmpfile);
+ free(tmpfile);
+ return (rv);
+}
+
+static int
+file_exists(char *rootdir, char *path)
+{
+ struct stat stbuf;
+ char fullpath[MAXPATHLEN];
+ int x;
+
+ (void) snprintf(fullpath, MAXPATHLEN, "%s%s", rootdir, path);
+
+ x = stat(fullpath, &stbuf);
+ logdmsg(("file_exists: %s: %s\n", fullpath, (x == 0) ? "yes" : "no"));
+ if (x == 0)
+ return (1);
+ else
+ return (0);
+}
+
+/*
+ * Check if mpxio is enabled or disabled on the specified device path.
+ * Looks through the .conf files to determine the mpxio setting.
+ *
+ * rootdir root directory of the target environment
+ * path /devices name of a device without the /devices prefix and
+ * minor name component.
+ *
+ * Returns
+ * 1 if mpxio is disabled
+ * 0 if mpxio is enabled
+ * -1 if an error occurs
+ */
+static int
+is_mpxio_disabled(char *rootdir, char *path)
+{
+ int mpxio_disable;
+ char *p;
+ int check_root_controller;
+
+ logdmsg(("is_mpxio_disabled: rootdir = %s, path = %s\n",
+ rootdir, path));
+
+ if (file_exists(rootdir, SCSI_VHCI_CONF) == 0) {
+ /*
+ * scsi_vhci.conf doesn't exist:
+ * if upgrading from a pre solaris 9 release. or
+ * if this function is called during fresh or flash install
+ * prior to installing scsi_vhci.conf file.
+ */
+ if (file_exists(rootdir, "/kernel/drv"))
+ /* upgrading from pre solaris 9 */
+ return (1);
+ else
+ /* fresh or flash install */
+ return (0);
+ }
+
+ mpxio_disable = lookup_in_conf_file(rootdir, SCSI_VHCI_CONF, NULL);
+
+ /*
+ * scsi_vhci.conf contains mpxio-disable property only in s9 and
+ * s8+sfkpatch. This property is no longer present from s10 onwards.
+ */
+ if (mpxio_disable == 1) {
+ /* upgrading from s8 or s9 with mpxio globally disabled */
+ return (1);
+ } else if (mpxio_disable == 0) {
+ /* upgrading from s8 or s9 with mpxio globally enabled */
+ check_root_controller = 1;
+ } else {
+ /*
+ * We are looking at the s10 version of the file. This is
+ * the case if this function is called after installing the
+ * new scsi_vhci.conf file.
+ */
+ check_root_controller = 0;
+ }
+
+ if ((mpxio_disable = lookup_in_conf_file(rootdir, FP_CONF, path))
+ != -1)
+ return (mpxio_disable);
+
+ if ((p = strrchr(path, '/')) == NULL)
+ return (-1);
+
+ *p = '\0';
+ if ((mpxio_disable = lookup_in_conf_file(rootdir, QLC_CONF, path))
+ != -1) {
+ *p = '/';
+ return (mpxio_disable);
+ }
+ *p = '/';
+
+ /*
+ * mpxio-disable setting is not found in the .conf files.
+ * The default is to enable mpxio, except if the path is on the root
+ * controller.
+ *
+ * In s8 and s9 mpxio is not supported on the root controller.
+ * NWS supplies a patch to enable root controller support in s8 and s9.
+ * If the system had the patch installed, the fp.conf file would have
+ * explicit "mpxio-disable=no" for the root controller. So we would
+ * have found the mpxio-disable setting when we looked up this property
+ * in the fp.conf file.
+ */
+ if (check_root_controller) {
+ mpxio_disable = is_root_controller(rootdir, path);
+ logdmsg(("is_mpxio_disabled: is_root_controller returned %d\n",
+ mpxio_disable));
+ } else
+ mpxio_disable = 0;
+
+ return (mpxio_disable);
+}
+
+static int
+vhci_ctl(sv_iocdata_t *iocp, int cmd)
+{
+ int fd, rv;
+
+ if ((fd = open(VHCI_CTL_NODE, O_RDWR)) < 0)
+ return (-1);
+ rv = ioctl(fd, cmd, iocp);
+ (void) close(fd);
+ return (rv);
+}
+
+/*
+ * Convert a phci client name to vhci client name.
+ *
+ * phci_name phci client /devices name without the /devices prefix and
+ * minor name component.
+ * ex: /pci@8,600000/SUNW,qlc@4/fp@0,0/ssd@w2100002037cd9f72,0
+ *
+ * Returns on success, vhci client name is returned. The memory for
+ * the vhci name is allocated by this function and the caller
+ * must free it.
+ * on failure, NULL is returned.
+ */
+static char *
+phci_to_vhci(char *phci_name)
+{
+ sv_iocdata_t ioc;
+ char *slash, *addr, *retp;
+ char vhci_name_buf[MAXPATHLEN];
+ char phci_name_buf[MAXPATHLEN];
+ char addr_buf[MAXNAMELEN];
+
+ logdmsg(("phci_to_vhci: pchi_name = %s\n", phci_name));
+ (void) strlcpy(phci_name_buf, phci_name, MAXPATHLEN);
+
+ if ((slash = strrchr(phci_name_buf, '/')) == NULL ||
+ (addr = strchr(slash, '@')) == NULL)
+ return (NULL);
+
+ *slash = '\0';
+ addr++;
+ (void) strlcpy(addr_buf, addr, MAXNAMELEN);
+
+ bzero(&ioc, sizeof (sv_iocdata_t));
+ ioc.client = vhci_name_buf;
+ ioc.phci = phci_name_buf;
+ ioc.addr = addr_buf;
+ if (vhci_ctl(&ioc, SCSI_VHCI_GET_CLIENT_NAME) != 0) {
+ logdmsg(("phci_to_vhci: vhci_ctl failed: %s\n",
+ strerror(errno)));
+ return (NULL);
+ }
+
+ retp = strdup(vhci_name_buf);
+ logdmsg(("phci_to_vhci: vhci name = %s\n", STRVAL(retp)));
+ return (retp);
+}
+
+static int
+add_to_phci_list(char **phci_list, sv_path_info_t *pi, int npaths, int state,
+ char *node_name)
+{
+ int rv = 0;
+ char name[MAXPATHLEN];
+
+ while (npaths--) {
+ if (state == pi->ret_state) {
+ (void) snprintf(name, MAXPATHLEN, "%s/%s@w%s",
+ pi->device.ret_phci, node_name, pi->ret_addr);
+ if ((*phci_list = strdup(name)) == NULL)
+ return (-1);
+ phci_list++;
+ rv++;
+ }
+ pi++;
+ }
+
+ return (rv);
+}
+
+static void
+free_pathlist(char **pathlist)
+{
+ char **p;
+
+ if (pathlist != NULL) {
+ for (p = pathlist; *p != NULL; p++)
+ free(*p);
+ free(pathlist);
+ }
+}
+
+
+/*
+ * Convert a vhci client name to phci client names.
+ *
+ * vhci_name vhci client /devices name without the /devices prefix and
+ * minor name component.
+ * num_paths On return, *num_paths is set to the number paths in the
+ * returned path list.
+ *
+ * Returns NULL terminated path list containing phci client paths is
+ * returned on success. The memory for the path list is
+ * allocated by this function and the caller must free it by
+ * calling free_pathlist().
+ * NULL is returned on failure.
+ */
+static char **
+vhci_to_phci(char *vhci_name, int *num_paths)
+{
+ sv_iocdata_t ioc;
+ uint_t npaths;
+ int n;
+ char **phci_list = NULL;
+ char *node_name, *at;
+ char vhci_name_buf[MAXPATHLEN];
+
+ logdmsg(("vhci_to_phci: vchi_name = %s\n", vhci_name));
+
+ *num_paths = 0;
+ (void) strlcpy(vhci_name_buf, vhci_name, MAXPATHLEN);
+
+ /* first get the number paths */
+ bzero(&ioc, sizeof (sv_iocdata_t));
+ ioc.client = vhci_name_buf;
+ ioc.ret_elem = &npaths;
+ if (vhci_ctl(&ioc, SCSI_VHCI_GET_CLIENT_MULTIPATH_INFO) != 0 ||
+ npaths == 0) {
+ logdmsg(("vhci_to_phci: vhci_ctl failed to get npaths: %s\n",
+ strerror(errno)));
+ return (NULL);
+ }
+
+ /* now allocate memory for the path information and get all paths */
+ bzero(&ioc, sizeof (sv_iocdata_t));
+ ioc.client = vhci_name_buf;
+ ioc.buf_elem = npaths;
+ ioc.ret_elem = &npaths;
+ if ((ioc.ret_buf = (sv_path_info_t *)calloc(npaths,
+ sizeof (sv_path_info_t))) == NULL)
+ return (NULL);
+ if (vhci_ctl(&ioc, SCSI_VHCI_GET_CLIENT_MULTIPATH_INFO) != 0 ||
+ npaths == 0) {
+ logdmsg(("vhci_to_phci: vhci_ctl failed: %s\n",
+ strerror(errno)));
+ goto out;
+ }
+
+ if (ioc.buf_elem < npaths)
+ npaths = ioc.buf_elem;
+
+ if ((node_name = strrchr(vhci_name_buf, '/')) == NULL ||
+ (at = strchr(node_name, '@')) == NULL)
+ goto out;
+
+ node_name++;
+ *at = '\0';
+
+ /* allocate one more (than npaths) for the terminating NULL pointer */
+ if ((phci_list = calloc(npaths + 1, sizeof (char *))) == NULL)
+ goto out;
+
+ /*
+ * add only online paths as non-online paths may not be accessible
+ * in the target environment.
+ */
+ if ((n = add_to_phci_list(phci_list, ioc.ret_buf, npaths,
+ MDI_PATHINFO_STATE_ONLINE, node_name)) <= 0)
+ goto out;
+
+ free(ioc.ret_buf);
+ *num_paths = n;
+
+#ifdef DEBUG
+ logdmsg(("vhci_to_phci: phci list:\n"));
+ log_pathlist(phci_list);
+#endif
+ return (phci_list);
+
+out:
+ free(ioc.ret_buf);
+ if (phci_list)
+ free_pathlist(phci_list);
+ return (NULL);
+}
+
+/*
+ * build list of paths accessible from the target environment
+ */
+static int
+build_pathlist(char *rootdir, char *vhcipath, char **pathlist, int npaths)
+{
+ int mpxio_disabled;
+ int i, j;
+ char *vpath = NULL;
+
+ for (i = 0; i < npaths; i++) {
+ mpxio_disabled = is_mpxio_disabled(rootdir, pathlist[i]);
+ logdmsg(("build_pathlist: mpxio_disabled = %d "
+ "on path %s\n", mpxio_disabled, pathlist[i]));
+ if (mpxio_disabled == -1)
+ return (-1);
+ if (mpxio_disabled == 0) {
+ /*
+ * mpxio is enabled on this phci path.
+ * So use vhci path instead of phci path.
+ */
+ if (vpath == NULL) {
+ if ((vpath = strdup(vhcipath)) == NULL)
+ return (-1);
+ free(pathlist[i]);
+ /* keep vhci path at beginning of the list */
+ for (j = i; j > 0; j--)
+ pathlist[j] = pathlist[j - 1];
+ pathlist[0] = vpath;
+ } else {
+ free(pathlist[i]);
+ npaths--;
+ for (j = i; j < npaths; j++)
+ pathlist[j] = pathlist[j + 1];
+ pathlist[npaths] = NULL;
+ /* compensate for i++ in the for loop */
+ i--;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ logdmsg(("build_pathlist: returning npaths = %d, pathlist:\n", npaths));
+ log_pathlist(pathlist);
+#endif
+ return (npaths);
+}
+
+/*
+ * Check if the specified device is refenced in the vfstab file.
+ * Return 1 if referenced, 0 if not.
+ *
+ * rootdir root directory of the target environment
+ * nodepath /devices path of a device in the target environment without
+ * the /devices prefix and minor component.
+ */
+static int
+is_dev_in_vfstab(char *rootdir, char *nodepath)
+{
+ FILE *fp;
+ int linksize;
+ struct vfstab vfsent;
+ char *abspath, *minor;
+ char physpath[MAXPATHLEN];
+ char buf[MAXPATHLEN];
+
+ logdmsg(("is_dev_in_vfstab: rootdir = %s, nodepath = %s\n",
+ rootdir, nodepath));
+
+ (void) snprintf(buf, sizeof (buf), "%s%s", rootdir, VFSTAB);
+
+ if ((fp = fopen(buf, "r")) == NULL)
+ return (0);
+
+ /*
+ * read device specials from vfstab and compare names at physical
+ * node path level.
+ */
+ while (getvfsent(fp, &vfsent) == 0) {
+ if (strncmp(vfsent.vfs_special, SLASH_DEV_SLASH,
+ sizeof (SLASH_DEV_SLASH) - 1) == 0) {
+ (void) snprintf(buf, MAXPATHLEN, "%s%s",
+ rootdir, vfsent.vfs_special);
+ if ((linksize = readlink(buf, physpath,
+ MAXPATHLEN)) > 0 && linksize < (MAXPATHLEN - 1)) {
+ physpath[linksize] = '\0';
+ if ((abspath = strstr(physpath,
+ SLASH_DEVICES_SLASH)) == NULL)
+ continue;
+ } else
+ continue;
+ } else if (strncmp(vfsent.vfs_special, SLASH_DEVICES_SLASH,
+ sizeof (SLASH_DEVICES_SLASH) - 1) == 0) {
+ (void) strlcpy(physpath, vfsent.vfs_special,
+ MAXPATHLEN);
+ abspath = physpath;
+ } else
+ continue;
+
+ /* point to / after /devices */
+ abspath += sizeof (SLASH_DEVICES_SLASH) - 2;
+ /* strip minor component */
+ if ((minor = strrchr(abspath, ':')) != NULL)
+ *minor = '\0';
+
+ if (strcmp(nodepath, abspath) == 0) {
+ (void) fclose(fp);
+ logdmsg(("is_dev_in_vfstab: returning 1\n"));
+ return (1);
+ }
+ }
+
+ (void) fclose(fp);
+ return (0);
+}
+
+#endif /* __sparc */
+
+static int
+devlink_callback(di_devlink_t devlink, void *argp)
+{
+ const char *link;
+
+ if ((link = di_devlink_path(devlink)) != NULL)
+ (void) strlcpy((char *)argp, link, MAXPATHLEN);
+
+ return (DI_WALK_CONTINUE);
+}
+
+/*
+ * Get the /dev name in the install environment corresponding to physpath.
+ *
+ * physpath /devices path in the install environment without the /devices
+ * prefix.
+ * buf caller supplied buffer where the /dev name is placed on return
+ * bufsz length of the buffer
+ *
+ * Returns strlen of the /dev name on success, -1 on failure.
+ */
+static int
+get_install_devlink(char *physpath, char *buf, size_t bufsz)
+{
+ di_devlink_handle_t devlink_hdl;
+ char devname[MAXPATHLEN];
+
+ logdmsg(("get_install_devlink: physpath = %s\n", physpath));
+
+ if ((devlink_hdl = di_devlink_init(NULL, 0)) == NULL) {
+ logdmsg(("get_install_devlink: di_devlink_init() failed: %s\n",
+ strerror(errno)));
+ return (-1);
+ }
+
+ devname[0] = '\0';
+ if (di_devlink_walk(devlink_hdl, NULL, physpath, DI_PRIMARY_LINK,
+ devname, devlink_callback) != 0 || devname[0] == '\0') {
+ logdmsg(("get_install_devlink: di_devlink_walk failed: %s\n",
+ strerror(errno)));
+ (void) di_devlink_fini(&devlink_hdl);
+ return (-1);
+ }
+
+ (void) di_devlink_fini(&devlink_hdl);
+
+ logdmsg(("get_install_devlink: devlink = %s\n", devname));
+ return (strlcpy(buf, devname, bufsz));
+}
+
+/*
+ * Get the /dev name in the target environment corresponding to physpath.
+ *
+ * rootdir root directory of the target environment
+ * physpath /devices path in the target environment without the /devices
+ * prefix.
+ * buf caller supplied buffer where the /dev name is placed on return
+ * bufsz length of the buffer
+ *
+ * Returns strlen of the /dev name on success, -1 on failure.
+ */
+static int
+get_target_devlink(char *rootdir, char *physpath, char *buf, size_t bufsz)
+{
+ char *p;
+ int linksize;
+ DIR *dirp;
+ struct dirent *direntry;
+ char dirpath[MAXPATHLEN];
+ char devname[MAXPATHLEN];
+ char physdev[MAXPATHLEN];
+
+ logdmsg(("get_target_devlink: rootdir = %s, physpath = %s\n",
+ rootdir, physpath));
+
+ if ((p = strrchr(physpath, '/')) == NULL)
+ return (-1);
+
+ if (strstr(p, ",raw") != NULL) {
+ (void) snprintf(dirpath, MAXPATHLEN, "%s/dev/rdsk", rootdir);
+ } else {
+ (void) snprintf(dirpath, MAXPATHLEN, "%s/dev/dsk", rootdir);
+ }
+
+ if ((dirp = opendir(dirpath)) == NULL)
+ return (-1);
+
+ while ((direntry = readdir(dirp)) != NULL) {
+ if (strcmp(direntry->d_name, ".") == 0 ||
+ strcmp(direntry->d_name, "..") == 0)
+ continue;
+
+ (void) snprintf(devname, MAXPATHLEN, "%s/%s",
+ dirpath, direntry->d_name);
+
+ if ((linksize = readlink(devname, physdev, MAXPATHLEN)) > 0 &&
+ linksize < (MAXPATHLEN - 1)) {
+ physdev[linksize] = '\0';
+ if ((p = strstr(physdev, SLASH_DEVICES_SLASH)) !=
+ NULL && strcmp(p + sizeof (SLASH_DEVICES) - 1,
+ physpath) == 0) {
+ (void) closedir(dirp);
+ logdmsg(("get_target_devlink: devlink = %s\n",
+ devname + strlen(rootdir)));
+ return (strlcpy(buf, devname + strlen(rootdir),
+ bufsz));
+ }
+ }
+ }
+
+ (void) closedir(dirp);
+ return (-1);
+}
+
+/*
+ * Convert device name to physpath.
+ *
+ * rootdir root directory
+ * devname a /dev name or /devices name under rootdir
+ * physpath caller supplied buffer where the /devices path will be placed
+ * on return (without the /devices prefix).
+ * physpathlen length of the physpath buffer
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int
+devname2physpath(char *rootdir, char *devname, char *physpath, int physpathlen)
+{
+ int linksize;
+ char *p;
+ char devlink[MAXPATHLEN];
+ char tmpphyspath[MAXPATHLEN];
+
+ logdmsg(("devname2physpath: rootdir = %s, devname = %s\n",
+ rootdir, devname));
+
+ if (strncmp(devname, SLASH_DEVICES_SLASH,
+ sizeof (SLASH_DEVICES_SLASH) - 1) != 0) {
+ if (*rootdir == '\0')
+ linksize = readlink(devname, tmpphyspath, MAXPATHLEN);
+ else {
+ (void) snprintf(devlink, MAXPATHLEN, "%s%s",
+ rootdir, devname);
+ linksize = readlink(devlink, tmpphyspath, MAXPATHLEN);
+ }
+ if (linksize > 0 && linksize < (MAXPATHLEN - 1)) {
+ tmpphyspath[linksize] = '\0';
+ if ((p = strstr(tmpphyspath, SLASH_DEVICES_SLASH))
+ == NULL)
+ return (-1);
+ } else
+ return (-1);
+ } else
+ p = devname;
+
+ (void) strlcpy(physpath, p + sizeof (SLASH_DEVICES) - 1, physpathlen);
+ logdmsg(("devname2physpath: physpath = %s\n", physpath));
+ return (0);
+}
+
+/*
+ * Map a device name (devname) from the target environment to the
+ * install environment.
+ *
+ * rootdir root directory of the target environment
+ * devname /dev or /devices name under the target environment
+ * buf caller supplied buffer where the mapped /dev name is placed
+ * on return
+ * bufsz length of the buffer
+ *
+ * Returns strlen of the mapped /dev name on success, -1 on failure.
+ */
+int
+devfs_target2install(const char *rootdir, const char *devname, char *buf,
+ size_t bufsz)
+{
+ char physpath[MAXPATHLEN];
+
+ logdmsg(("devfs_target2install: rootdir = %s, devname = %s\n",
+ STRVAL(rootdir), STRVAL(devname)));
+
+ if (rootdir == NULL || devname == NULL || buf == NULL || bufsz == 0)
+ return (-1);
+
+ if (strcmp(rootdir, "/") == 0)
+ rootdir = "";
+
+ if (devname2physpath((char *)rootdir, (char *)devname, physpath,
+ MAXPATHLEN) != 0)
+ return (-1);
+
+#ifdef __sparc
+ if (client_name_type(physpath) == CLIENT_TYPE_PHCI) {
+ char *mapped_node_path, *minor;
+ char minorbuf[MAXNAMELEN];
+
+ /* strip minor component if present */
+ if ((minor = strrchr(physpath, ':')) != NULL) {
+ *minor = '\0';
+ minor++;
+ (void) strlcpy(minorbuf, minor, MAXNAMELEN);
+ }
+ if ((mapped_node_path = phci_to_vhci(physpath)) != NULL) {
+ if (minor)
+ (void) snprintf(physpath, MAXPATHLEN,
+ "%s:%s", mapped_node_path, minorbuf);
+ else
+ (void) strlcpy(physpath, mapped_node_path,
+ MAXPATHLEN);
+ free(mapped_node_path);
+ logdmsg(("devfs_target2install: mapped physpath: %s\n",
+ physpath));
+
+ } else if (minor)
+ *(minor - 1) = ':';
+ }
+#endif /* __sparc */
+
+ return (get_install_devlink(physpath, buf, bufsz));
+}
+
+/*
+ * Map a device name (devname) from the install environment to the target
+ * environment.
+ *
+ * rootdir root directory of the target environment
+ * devname /dev or /devices name under the install environment
+ * buf caller supplied buffer where the mapped /dev name is placed
+ * on return
+ * bufsz length of the buffer
+ *
+ * Returns strlen of the mapped /dev name on success, -1 on failure.
+ */
+int
+devfs_install2target(const char *rootdir, const char *devname, char *buf,
+ size_t bufsz)
+{
+ char physpath[MAXPATHLEN];
+
+ logdmsg(("devfs_install2target: rootdir = %s, devname = %s\n",
+ STRVAL(rootdir), STRVAL(devname)));
+
+ if (rootdir == NULL || devname == NULL || buf == NULL || bufsz == 0)
+ return (-1);
+
+ if (strcmp(rootdir, "/") == 0)
+ rootdir = "";
+
+ if (devname2physpath("", (char *)devname, physpath, MAXPATHLEN) != 0)
+ return (-1);
+
+#ifdef __sparc
+ if (client_name_type(physpath) == CLIENT_TYPE_VHCI) {
+ char **pathlist;
+ int npaths, i, j;
+ char *minor;
+ char minorbuf[MAXNAMELEN];
+
+ /* strip minor component if present */
+ if ((minor = strrchr(physpath, ':')) != NULL) {
+ *minor = '\0';
+ minor++;
+ (void) strlcpy(minorbuf, minor, MAXNAMELEN);
+ }
+
+ if ((pathlist = vhci_to_phci(physpath, &npaths)) == NULL)
+ return (-1);
+
+ if ((npaths = build_pathlist((char *)rootdir, physpath,
+ pathlist, npaths)) <= 0) {
+ free_pathlist(pathlist);
+ return (-1);
+ }
+
+ /*
+ * in case of more than one path, try to use the path
+ * referenced in the vfstab file, otherwise use the first path.
+ */
+ j = 0;
+ if (npaths > 1) {
+ for (i = 0; i < npaths; i++) {
+ if (is_dev_in_vfstab((char *)rootdir,
+ pathlist[i])) {
+ j = i;
+ break;
+ }
+ }
+ }
+
+ if (minor)
+ (void) snprintf(physpath, MAXPATHLEN,
+ "%s:%s", pathlist[j], minorbuf);
+ else
+ (void) strlcpy(physpath, pathlist[j], MAXPATHLEN);
+ free_pathlist(pathlist);
+ }
+#endif /* __sparc */
+
+ return (get_target_devlink((char *)rootdir, physpath, buf, bufsz));
+}
+
+#ifdef DEBUG
+
+static void
+vlog_debug_msg(char *fmt, va_list ap)
+{
+ time_t clock;
+ struct tm t;
+
+ if (!devfsmap_debug)
+ return;
+
+ if (logfp == NULL) {
+ if (*devfsmap_logfile != '\0') {
+ logfp = fopen(devfsmap_logfile, "a");
+ if (logfp)
+ (void) fprintf(logfp, "\nNew Log:\n");
+ }
+
+ if (logfp == NULL)
+ logfp = stdout;
+ }
+
+ clock = time(NULL);
+ (void) localtime_r(&clock, &t);
+ (void) fprintf(logfp, "%02d:%02d:%02d ", t.tm_hour, t.tm_min,
+ t.tm_sec);
+ (void) vfprintf(logfp, fmt, ap);
+ (void) fflush(logfp);
+}
+
+static void
+log_debug_msg(char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog_debug_msg(fmt, ap);
+ va_end(ap);
+}
+
+#ifdef __sparc
+
+static char *
+mpxio_disable_string(int mpxio_disable)
+{
+ if (mpxio_disable == 0)
+ return ("no");
+ else if (mpxio_disable == 1)
+ return ("yes");
+ else
+ return ("not specified");
+}
+
+static void
+log_confent_list(char *filename, struct conf_entry *confent_list,
+ int global_mpxio_disable)
+{
+ struct conf_entry *confent;
+
+ log_debug_msg("log_confent_list: filename = %s:\n", filename);
+ if (global_mpxio_disable != -1)
+ log_debug_msg("\tdriver global mpxio_disable = \"%s\"\n\n",
+ mpxio_disable_string(global_mpxio_disable));
+
+ for (confent = confent_list; confent != NULL; confent = confent->next) {
+ if (confent->name)
+ log_debug_msg("\tname = %s\n", confent->name);
+ if (confent->parent)
+ log_debug_msg("\tparent = %s\n", confent->parent);
+ if (confent->class)
+ log_debug_msg("\tclass = %s\n", confent->class);
+ if (confent->unit_address)
+ log_debug_msg("\tunit_address = %s\n",
+ confent->unit_address);
+ if (confent->port != -1)
+ log_debug_msg("\tport = %d\n", confent->port);
+ log_debug_msg("\tmpxio_disable = \"%s\"\n\n",
+ mpxio_disable_string(confent->mpxio_disable));
+ }
+}
+
+static void
+log_pathlist(char **pathlist)
+{
+ char **p;
+
+ for (p = pathlist; *p != NULL; p++)
+ log_debug_msg("\t%s\n", *p);
+}
+
+#endif /* __sparc */
+
+#endif /* DEBUG */
diff --git a/usr/src/lib/libdevinfo/device_info.h b/usr/src/lib/libdevinfo/device_info.h
new file mode 100644
index 0000000000..d58b235612
--- /dev/null
+++ b/usr/src/lib/libdevinfo/device_info.h
@@ -0,0 +1,182 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ * WARNING:
+ * The interfaces defined in this header file are for Sun private use only.
+ * The contents of this file are subject to change without notice in
+ * future releases.
+ */
+
+#ifndef _DEVICE_INFO_H
+#define _DEVICE_INFO_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* error return values */
+#define DEVFS_ERR -1 /* operation not successful */
+#define DEVFS_INVAL -2 /* invalid argument */
+#define DEVFS_NOMEM -3 /* out of memory */
+#define DEVFS_PERM -4 /* permission denied - not root */
+#define DEVFS_NOTSUP -5 /* operation not supported */
+#define DEVFS_LIMIT -6 /* exceeded maximum size of property value */
+#define DEVFS_NOTFOUND -7 /* request not found */
+
+/*
+ * for devfs_set_boot_dev()
+ * default behavior is to translate the input logical device name
+ * to most compact prom name(i.e. a prom alias, if one exists)
+ * as possible. And to prepend the new entry to the existing
+ * list.
+ */
+
+/* perform no translation on the input device path */
+#define BOOTDEV_LITERAL 0x1
+/* convert the input device path only a prom device path; not an alias */
+#define BOOTDEV_PROMDEV 0x2
+/* overwrite the existing entry in boot-device - default is to prepend */
+#define BOOTDEV_OVERWRITE 0x4
+
+/*
+ * for devfs_get_prom_names()
+ * returns a list of prom names for a given logical device name.
+ * the list is sorted first in order of exact aliases, inexact alias
+ * matches (where an option override was needed), and finally the
+ * equivalent prom device path. Each sublist is sorted in collating
+ * order.
+ */
+#define BOOTDEV_NO_PROM_PATH 0x1
+#define BOOTDEV_NO_INEXACT_ALIAS 0x2
+#define BOOTDEV_NO_EXACT_ALIAS 0x4
+
+/* for devfs_get_boot_dev() */
+struct boot_dev {
+ char *bootdev_element; /* an entry from the boot-device variable */
+ char **bootdev_trans; /* 0 or more logical dev translations */
+};
+
+/* for devfs_get_all_prom_names() */
+struct devfs_prom_path {
+ char *obp_path;
+ char **alias_list;
+ struct devfs_prom_path *next;
+};
+
+/* prototypes */
+
+/* return the driver for a given device path */
+extern int devfs_path_to_drv(char *devfs_path, char *drv_buf);
+
+/* convert a logical or physical device name to the equivalent prom path */
+extern int devfs_dev_to_prom_name(char *, char *);
+
+/* return the driver name after resolving any aliases */
+extern char *devfs_resolve_aliases(char *drv);
+
+/* set the boot-device configuration variable */
+extern int devfs_bootdev_set_list(const char *, const uint_t);
+
+/* is the boot-device variable modifiable on this platform? */
+extern int devfs_bootdev_modifiable(void);
+
+/*
+ * retrieve the boot-device config variable and corresponding logical
+ * device names
+ */
+extern int devfs_bootdev_get_list(const char *, struct boot_dev ***);
+/*
+ * free a list of bootdev structs
+ */
+extern void devfs_bootdev_free_list(struct boot_dev **);
+/*
+ * given a logical device name, return a list of equivalent
+ * prom names (aliases and device paths)
+ */
+extern int devfs_get_prom_names(const char *, uint_t, char ***);
+/*
+ * like devfs_get_prom_names(), but deals with 1 to many mappings
+ * introduced by mpxio devices
+ */
+extern int devfs_get_all_prom_names(const char *, uint_t,
+ struct devfs_prom_path **);
+/*
+ * free a list of devfs_prom_path structures
+ */
+extern void devfs_free_all_prom_names(struct devfs_prom_path *);
+
+/*
+ * map a device name from install OS environment to target OS environment or
+ * vice-versa.
+ */
+extern int devfs_target2install(const char *, const char *, char *, size_t);
+extern int devfs_install2target(const char *, const char *, char *, size_t);
+
+/*
+ * Minor perm parsing library support for devfsadm, add_drv etc.
+ */
+#define MINOR_PERM_FILE "/etc/minor_perm"
+#define MAX_MINOR_PERM_LINE 256
+#define DEFAULT_DEV_USER "root"
+#define DEFAULT_DEV_GROUP "sys"
+
+/*
+ * Possible errors the callers of devfs_read_minor_perm() need
+ * to be prepared to deal with via callback.
+ */
+typedef enum {
+ MP_FOPEN_ERR,
+ MP_FCLOSE_ERR,
+ MP_IGNORING_LINE_ERR,
+ MP_ALLOC_ERR,
+ MP_NVLIST_ERR,
+ MP_CANT_FIND_USER_ERR,
+ MP_CANT_FIND_GROUP_ERR
+} minorperm_err_t;
+
+
+/*
+ * Create/free mperm list of minor perm entries
+ */
+extern struct mperm *devfs_read_minor_perm(void (*)(minorperm_err_t, int));
+extern void devfs_free_minor_perm(struct mperm *);
+
+/*
+ * Load all minor perm entries, and add/remove minor perm entry
+ */
+extern int devfs_load_minor_perm(struct mperm *,
+ void (*)(minorperm_err_t, int));
+extern int devfs_add_minor_perm(char *, void (*)(minorperm_err_t, int));
+extern int devfs_rm_minor_perm(char *, void (*)(minorperm_err_t, int));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _DEVICE_INFO_H */
diff --git a/usr/src/lib/libdevinfo/devinfo.c b/usr/src/lib/libdevinfo/devinfo.c
new file mode 100644
index 0000000000..7da077cfba
--- /dev/null
+++ b/usr/src/lib/libdevinfo/devinfo.c
@@ -0,0 +1,3080 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Interfaces for getting device configuration data from kernel
+ * through the devinfo driver.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <stropts.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <synch.h>
+#include <unistd.h>
+#include <sys/mkdev.h>
+#include <sys/obpdefs.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/autoconf.h>
+#include <stdarg.h>
+
+#define NDEBUG 1
+#include <assert.h>
+
+#include "libdevinfo.h"
+
+/*
+ * Debug message levels
+ */
+typedef enum {
+ DI_QUIET = 0, /* No debug messages - the default */
+ DI_ERR = 1,
+ DI_INFO,
+ DI_TRACE,
+ DI_TRACE1,
+ DI_TRACE2
+} di_debug_t;
+
+int di_debug = DI_QUIET;
+
+#define DPRINTF(args) { if (di_debug != DI_QUIET) dprint args; }
+
+void dprint(di_debug_t msglevel, const char *fmt, ...);
+
+
+#pragma init(_libdevinfo_init)
+
+void
+_libdevinfo_init()
+{
+ char *debug_str = getenv("_LIBDEVINFO_DEBUG");
+
+ if (debug_str) {
+ errno = 0;
+ di_debug = atoi(debug_str);
+ if (errno || di_debug < DI_QUIET)
+ di_debug = DI_QUIET;
+ }
+}
+
+di_node_t
+di_init(const char *phys_path, uint_t flag)
+{
+ return (di_init_impl(phys_path, flag, NULL));
+}
+
+/*
+ * We use blocking_open() to guarantee access to the devinfo device, if open()
+ * is failing with EAGAIN.
+ */
+static int
+blocking_open(const char *path, int oflag)
+{
+ int fd;
+
+ while ((fd = open(path, oflag)) == -1 && errno == EAGAIN)
+ (void) poll(NULL, 0, 1 * MILLISEC);
+
+ return (fd);
+}
+
+/* private interface */
+di_node_t
+di_init_driver(const char *drv_name, uint_t flag)
+{
+ int fd;
+ char driver[MAXPATHLEN];
+
+ /*
+ * Don't allow drv_name to exceed MAXPATHLEN - 1, or 1023,
+ * which should be sufficient for any sensible programmer.
+ */
+ if ((drv_name == NULL) || (strlen(drv_name) >= MAXPATHLEN)) {
+ errno = EINVAL;
+ return (DI_NODE_NIL);
+ }
+ (void) strcpy(driver, drv_name);
+
+ /*
+ * open the devinfo driver
+ */
+ if ((fd = blocking_open("/devices/pseudo/devinfo@0:devinfo",
+ O_RDONLY)) == -1) {
+ DPRINTF((DI_ERR, "devinfo open failed: errno = %d\n", errno));
+ return (DI_NODE_NIL);
+ }
+
+ if (ioctl(fd, DINFOLODRV, driver) != 0) {
+ DPRINTF((DI_ERR, "failed to load driver %s\n", driver));
+ (void) close(fd);
+ errno = ENXIO;
+ return (DI_NODE_NIL);
+ }
+ (void) close(fd);
+
+ /*
+ * Driver load succeeded, return a snapshot
+ */
+ return (di_init("/", flag));
+}
+
+di_node_t
+di_init_impl(const char *phys_path, uint_t flag,
+ struct di_priv_data *priv)
+{
+ caddr_t pa;
+ int fd, map_size;
+ struct di_all *dap;
+ struct dinfo_io dinfo_io;
+
+ uint_t pageoffset = sysconf(_SC_PAGESIZE) - 1;
+ uint_t pagemask = ~pageoffset;
+
+ DPRINTF((DI_INFO, "di_init: taking a snapshot\n"));
+
+ /*
+ * Make sure there is no minor name in the path
+ * and the path do not start with /devices....
+ */
+ if (strchr(phys_path, ':') ||
+ (strncmp(phys_path, "/devices", 8) == 0) ||
+ (strlen(phys_path) > MAXPATHLEN)) {
+ errno = EINVAL;
+ return (DI_NODE_NIL);
+ }
+
+ if (strlen(phys_path) == 0)
+ (void) sprintf(dinfo_io.root_path, "/");
+ else if (*phys_path != '/')
+ (void) snprintf(dinfo_io.root_path, sizeof (dinfo_io.root_path),
+ "/%s", phys_path);
+ else
+ (void) snprintf(dinfo_io.root_path, sizeof (dinfo_io.root_path),
+ "%s", phys_path);
+
+ /*
+ * If private data is requested, copy the format specification
+ */
+ if (flag & DINFOPRIVDATA & 0xff) {
+ if (priv)
+ bcopy(priv, &dinfo_io.priv,
+ sizeof (struct di_priv_data));
+ else {
+ errno = EINVAL;
+ return (DI_NODE_NIL);
+ }
+ }
+
+ /*
+ * Attempt to open the devinfo driver. Make a second attempt at the
+ * read-only minor node if we don't have privileges to open the full
+ * version _and_ if we're not requesting operations that the read-only
+ * node can't perform. (Setgid processes would fail an access() test,
+ * of course.)
+ */
+ if ((fd = blocking_open("/devices/pseudo/devinfo@0:devinfo",
+ O_RDONLY)) == -1) {
+ if ((flag & DINFOFORCE) == DINFOFORCE ||
+ (flag & DINFOPRIVDATA) == DINFOPRIVDATA) {
+ /*
+ * We wanted to perform a privileged operation, but the
+ * privileged node isn't available. Don't modify errno
+ * on our way out (but display it if we're running with
+ * di_debug set).
+ */
+ DPRINTF((DI_ERR, "devinfo open failed: errno = %d\n",
+ errno));
+ return (DI_NODE_NIL);
+ }
+
+ if ((fd = blocking_open("/devices/pseudo/devinfo@0:devinfo,ro",
+ O_RDONLY)) == -1) {
+ DPRINTF((DI_ERR, "devinfo open failed: errno = %d\n",
+ errno));
+ return (DI_NODE_NIL);
+ }
+ }
+
+ /*
+ * Verify that there is no major conflict, i.e., we are indeed opening
+ * the devinfo driver.
+ */
+ if (ioctl(fd, DINFOIDENT, NULL) != DI_MAGIC) {
+ DPRINTF((DI_ERR,
+ "driver ID failed; check for major conflict\n"));
+ (void) close(fd);
+ return (DI_NODE_NIL);
+ }
+
+ /*
+ * create snapshot
+ */
+ if ((map_size = ioctl(fd, flag, &dinfo_io)) < 0) {
+ DPRINTF((DI_ERR, "devinfo ioctl failed with "
+ "error: %d\n", errno));
+ (void) close(fd);
+ return (DI_NODE_NIL);
+ } else if (map_size == 0) {
+ DPRINTF((DI_ERR, "%s not found\n", phys_path));
+ errno = ENXIO;
+ (void) close(fd);
+ return (DI_NODE_NIL);
+ }
+
+ /*
+ * copy snapshot to userland
+ */
+ map_size = (map_size + pageoffset) & pagemask;
+ if ((pa = valloc(map_size)) == NULL) {
+ DPRINTF((DI_ERR, "valloc failed for snapshot\n"));
+ (void) close(fd);
+ return (DI_NODE_NIL);
+ }
+
+ if (ioctl(fd, DINFOUSRLD, pa) != map_size) {
+ DPRINTF((DI_ERR, "failed to copy snapshot to usrld\n"));
+ (void) close(fd);
+ free(pa);
+ errno = EFAULT;
+ return (DI_NODE_NIL);
+ }
+
+ (void) close(fd);
+
+ dap = DI_ALL(pa);
+ if (dap->top_devinfo == 0) { /* phys_path not found */
+ DPRINTF((DI_ERR, "%s not found\n", phys_path));
+ free(pa);
+ errno = EINVAL;
+ return (DI_NODE_NIL);
+ }
+
+ return (DI_NODE(pa + dap->top_devinfo));
+}
+
+void
+di_fini(di_node_t root)
+{
+ caddr_t pa; /* starting address of map */
+
+ DPRINTF((DI_INFO, "di_fini: freeing a snapshot\n"));
+
+ /*
+ * paranoid checking
+ */
+ if (root == DI_NODE_NIL) {
+ DPRINTF((DI_ERR, "di_fini called with NIL arg\n"));
+ return;
+ }
+
+ /*
+ * The root contains its own offset--self.
+ * Subtracting it from root address, we get the starting addr.
+ * The map_size is stored at the beginning of snapshot.
+ * Once we have starting address and size, we can free().
+ */
+ pa = (caddr_t)root - DI_NODE(root)->self;
+
+ free(pa);
+}
+
+di_node_t
+di_parent_node(di_node_t node)
+{
+ caddr_t pa; /* starting address of map */
+
+ if (node == DI_NODE_NIL) {
+ errno = EINVAL;
+ return (DI_NODE_NIL);
+ }
+
+ DPRINTF((DI_TRACE, "Get parent of node %s\n", di_node_name(node)));
+
+ pa = (caddr_t)node - DI_NODE(node)->self;
+
+ if (DI_NODE(node)->parent) {
+ return (DI_NODE(pa + DI_NODE(node)->parent));
+ }
+
+ /*
+ * Deal with error condition:
+ * If parent doesn't exist and node is not the root,
+ * set errno to ENOTSUP. Otherwise, set errno to ENXIO.
+ */
+ if (strcmp(DI_ALL(pa)->root_path, "/") != 0)
+ errno = ENOTSUP;
+ else
+ errno = ENXIO;
+
+ return (DI_NODE_NIL);
+}
+
+di_node_t
+di_sibling_node(di_node_t node)
+{
+ caddr_t pa; /* starting address of map */
+
+ if (node == DI_NODE_NIL) {
+ errno = EINVAL;
+ return (DI_NODE_NIL);
+ }
+
+ DPRINTF((DI_TRACE, "Get sibling of node %s\n", di_node_name(node)));
+
+ pa = (caddr_t)node - DI_NODE(node)->self;
+
+ if (DI_NODE(node)->sibling) {
+ return (DI_NODE(pa + DI_NODE(node)->sibling));
+ }
+
+ /*
+ * Deal with error condition:
+ * Sibling doesn't exist, figure out if ioctl command
+ * has DINFOSUBTREE set. If it doesn't, set errno to
+ * ENOTSUP.
+ */
+ if (!(DI_ALL(pa)->command & DINFOSUBTREE))
+ errno = ENOTSUP;
+ else
+ errno = ENXIO;
+
+ return (DI_NODE_NIL);
+}
+
+di_node_t
+di_child_node(di_node_t node)
+{
+ caddr_t pa; /* starting address of map */
+
+ DPRINTF((DI_TRACE, "Get child of node %s\n", di_node_name(node)));
+
+ if (node == DI_NODE_NIL) {
+ errno = EINVAL;
+ return (DI_NODE_NIL);
+ }
+
+ pa = (caddr_t)node - DI_NODE(node)->self;
+
+ if (DI_NODE(node)->child) {
+ return (DI_NODE(pa + DI_NODE(node)->child));
+ }
+
+ /*
+ * Deal with error condition:
+ * Child doesn't exist, figure out if DINFOSUBTREE is set.
+ * If it isn't, set errno to ENOTSUP.
+ */
+ if (!(DI_ALL(pa)->command & DINFOSUBTREE))
+ errno = ENOTSUP;
+ else
+ errno = ENXIO;
+
+ return (DI_NODE_NIL);
+}
+
+di_node_t
+di_drv_first_node(const char *drv_name, di_node_t root)
+{
+ caddr_t pa; /* starting address of map */
+ int major, devcnt;
+ struct di_devnm *devnm;
+
+ DPRINTF((DI_INFO, "Get first node of driver %s\n", drv_name));
+
+ if (root == DI_NODE_NIL) {
+ errno = EINVAL;
+ return (DI_NODE_NIL);
+ }
+
+ /*
+ * get major number of driver
+ */
+ pa = (caddr_t)root - DI_NODE(root)->self;
+ devcnt = DI_ALL(pa)->devcnt;
+ devnm = DI_DEVNM(pa + DI_ALL(pa)->devnames);
+
+ for (major = 0; major < devcnt; major++)
+ if (devnm[major].name && (strcmp(drv_name,
+ (char *)(pa + devnm[major].name)) == 0))
+ break;
+
+ if (major >= devcnt) {
+ errno = EINVAL;
+ return (DI_NODE_NIL);
+ }
+
+ if (!(devnm[major].head)) {
+ errno = ENXIO;
+ return (DI_NODE_NIL);
+ }
+
+ return (DI_NODE(pa + devnm[major].head));
+}
+
+di_node_t
+di_drv_next_node(di_node_t node)
+{
+ caddr_t pa; /* starting address of map */
+
+ if (node == DI_NODE_NIL) {
+ errno = EINVAL;
+ return (DI_NODE_NIL);
+ }
+
+ DPRINTF((DI_TRACE, "next node on per driver list:"
+ " current=%s, driver=%s\n",
+ di_node_name(node), di_driver_name(node)));
+
+ if (DI_NODE(node)->next == (di_off_t)-1) {
+ errno = ENOTSUP;
+ return (DI_NODE_NIL);
+ }
+
+ pa = (caddr_t)node - DI_NODE(node)->self;
+
+ if (DI_NODE(node)->next == NULL) {
+ errno = ENXIO;
+ return (DI_NODE_NIL);
+ }
+
+ return (DI_NODE(pa + DI_NODE(node)->next));
+}
+
+/*
+ * Internal library interfaces:
+ * node_list etc. for node walking
+ */
+struct node_list {
+ struct node_list *next;
+ di_node_t node;
+};
+
+static void
+free_node_list(struct node_list **headp)
+{
+ struct node_list *tmp;
+
+ while (*headp) {
+ tmp = *headp;
+ *headp = (*headp)->next;
+ free(tmp);
+ }
+}
+
+static void
+append_node_list(struct node_list **headp, struct node_list *list)
+{
+ struct node_list *tmp;
+
+ if (*headp == NULL) {
+ *headp = list;
+ return;
+ }
+
+ if (list == NULL) /* a minor optimization */
+ return;
+
+ tmp = *headp;
+ while (tmp->next)
+ tmp = tmp->next;
+
+ tmp->next = list;
+}
+
+static void
+prepend_node_list(struct node_list **headp, struct node_list *list)
+{
+ struct node_list *tmp;
+
+ if (list == NULL)
+ return;
+
+ tmp = *headp;
+ *headp = list;
+
+ if (tmp == NULL) /* a minor optimization */
+ return;
+
+ while (list->next)
+ list = list->next;
+
+ list->next = tmp;
+}
+
+/*
+ * returns 1 if node is a descendant of parent, 0 otherwise
+ */
+static int
+is_descendant(di_node_t node, di_node_t parent)
+{
+ /*
+ * DI_NODE_NIL is parent of root, so it is
+ * the parent of all nodes.
+ */
+ if (parent == DI_NODE_NIL) {
+ return (1);
+ }
+
+ do {
+ node = di_parent_node(node);
+ } while ((node != DI_NODE_NIL) && (node != parent));
+
+ return (node != DI_NODE_NIL);
+}
+
+/*
+ * Insert list before the first node which is NOT a descendent of parent.
+ * This is needed to reproduce the exact walking order of link generators.
+ */
+static void
+insert_node_list(struct node_list **headp, struct node_list *list,
+ di_node_t parent)
+{
+ struct node_list *tmp, *tmp1;
+
+ if (list == NULL)
+ return;
+
+ tmp = *headp;
+ if (tmp == NULL) { /* a minor optimization */
+ *headp = list;
+ return;
+ }
+
+ if (!is_descendant(tmp->node, parent)) {
+ prepend_node_list(headp, list);
+ return;
+ }
+
+ /*
+ * Find first node which is not a descendant
+ */
+ while (tmp->next && is_descendant(tmp->next->node, parent)) {
+ tmp = tmp->next;
+ }
+
+ tmp1 = tmp->next;
+ tmp->next = list;
+ append_node_list(headp, tmp1);
+}
+
+/*
+ * Get a linked list of handles of all children
+ */
+static struct node_list *
+get_children(di_node_t node)
+{
+ di_node_t child;
+ struct node_list *result, *tmp;
+
+ DPRINTF((DI_TRACE1, "Get children of node %s\n", di_node_name(node)));
+
+ if ((child = di_child_node(node)) == DI_NODE_NIL) {
+ return (NULL);
+ }
+
+ if ((result = malloc(sizeof (struct node_list))) == NULL) {
+ DPRINTF((DI_ERR, "malloc of node_list failed\n"));
+ return (NULL);
+ }
+
+ result->node = child;
+ tmp = result;
+
+ while ((child = di_sibling_node(tmp->node)) != DI_NODE_NIL) {
+ if ((tmp->next = malloc(sizeof (struct node_list))) == NULL) {
+ DPRINTF((DI_ERR, "malloc of node_list failed\n"));
+ free_node_list(&result);
+ return (NULL);
+ }
+ tmp = tmp->next;
+ tmp->node = child;
+ }
+
+ tmp->next = NULL;
+
+ return (result);
+}
+
+/*
+ * Internal library interface:
+ * Delete all siblings of the first node from the node_list, along with
+ * the first node itself.
+ */
+static void
+prune_sib(struct node_list **headp)
+{
+ di_node_t parent, curr_par, curr_gpar;
+ struct node_list *curr, *prev;
+
+ /*
+ * get handle to parent of first node
+ */
+ if ((parent = di_parent_node((*headp)->node)) == DI_NODE_NIL) {
+ /*
+ * This must be the root of the snapshot, so can't
+ * have any siblings.
+ *
+ * XXX Put a check here just in case.
+ */
+ if ((*headp)->next)
+ DPRINTF((DI_ERR, "Unexpected err in di_walk_node.\n"));
+
+ free(*headp);
+ *headp = NULL;
+ return;
+ }
+
+ /*
+ * To be complete, we should also delete the children
+ * of siblings that have already been visited.
+ * This happens for DI_WALK_SIBFIRST when the first node
+ * is NOT the first in the linked list of siblings.
+ *
+ * Hence, we compare parent with BOTH the parent and grandparent
+ * of nodes, and delete node is a match is found.
+ */
+ prev = *headp;
+ curr = prev->next;
+ while (curr) {
+ if (((curr_par = di_parent_node(curr->node)) != DI_NODE_NIL) &&
+ ((curr_par == parent) || ((curr_gpar =
+ di_parent_node(curr_par)) != DI_NODE_NIL) &&
+ (curr_gpar == parent))) {
+ /*
+ * match parent/grandparent: delete curr
+ */
+ prev->next = curr->next;
+ free(curr);
+ curr = prev->next;
+ } else
+ curr = curr->next;
+ }
+
+ /*
+ * delete the first node
+ */
+ curr = *headp;
+ *headp = curr->next;
+ free(curr);
+}
+
+/*
+ * Internal library function:
+ * Update node list based on action (return code from callback)
+ * and flag specifying walking behavior.
+ */
+static void
+update_node_list(int action, uint_t flag, struct node_list **headp)
+{
+ struct node_list *children, *tmp;
+ di_node_t parent = di_parent_node((*headp)->node);
+
+ switch (action) {
+ case DI_WALK_TERMINATE:
+ /*
+ * free the node list and be done
+ */
+ children = NULL;
+ free_node_list(headp);
+ break;
+
+ case DI_WALK_PRUNESIB:
+ /*
+ * Get list of children and prune siblings
+ */
+ children = get_children((*headp)->node);
+ prune_sib(headp);
+ break;
+
+ case DI_WALK_PRUNECHILD:
+ /*
+ * Set children to NULL and pop first node
+ */
+ children = NULL;
+ tmp = *headp;
+ *headp = tmp->next;
+ free(tmp);
+ break;
+
+ case DI_WALK_CONTINUE:
+ default:
+ /*
+ * Get list of children and pop first node
+ */
+ children = get_children((*headp)->node);
+ tmp = *headp;
+ *headp = tmp->next;
+ free(tmp);
+ break;
+ }
+
+ /*
+ * insert the list of children
+ */
+ switch (flag) {
+ case DI_WALK_CLDFIRST:
+ prepend_node_list(headp, children);
+ break;
+
+ case DI_WALK_SIBFIRST:
+ append_node_list(headp, children);
+ break;
+
+ case DI_WALK_LINKGEN:
+ default:
+ insert_node_list(headp, children, parent);
+ break;
+ }
+}
+
+/*
+ * Internal library function:
+ * Invoke callback on one node and update the list of nodes to be walked
+ * based on the flag and return code.
+ */
+static void
+walk_one_node(struct node_list **headp, uint_t flag, void *arg,
+ int (*callback)(di_node_t, void *))
+{
+ DPRINTF((DI_TRACE, "Walking node %s\n", di_node_name((*headp)->node)));
+
+ update_node_list(callback((*headp)->node, arg),
+ flag & DI_WALK_MASK, headp);
+}
+
+int
+di_walk_node(di_node_t root, uint_t flag, void *arg,
+ int (*node_callback)(di_node_t, void *))
+{
+ struct node_list *head; /* node_list for tree walk */
+
+ if (root == NULL) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ if ((head = malloc(sizeof (struct node_list))) == NULL) {
+ DPRINTF((DI_ERR, "malloc of node_list failed\n"));
+ return (-1);
+ }
+
+ head->next = NULL;
+ head->node = root;
+
+ DPRINTF((DI_INFO, "Start node walking from node %s\n",
+ di_node_name(root)));
+
+ while (head != NULL)
+ walk_one_node(&head, flag, arg, node_callback);
+
+ return (0);
+}
+
+/*
+ * Internal library function:
+ * Invoke callback for each minor on the minor list of first node
+ * on node_list headp, and place children of first node on the list.
+ *
+ * This is similar to walk_one_node, except we only walk in child
+ * first mode.
+ */
+static void
+walk_one_minor_list(struct node_list **headp, const char *desired_type,
+ uint_t flag, void *arg, int (*callback)(di_node_t, di_minor_t, void *))
+{
+ int ddm_type;
+ int action = DI_WALK_CONTINUE;
+ char *node_type;
+ di_minor_t minor = DI_MINOR_NIL;
+ di_node_t node = (*headp)->node;
+
+ while ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) {
+ ddm_type = di_minor_type(minor);
+
+ if ((ddm_type == DDM_ALIAS) && !(flag & DI_CHECK_ALIAS))
+ continue;
+
+ if ((ddm_type == DDM_INTERNAL_PATH) &&
+ !(flag & DI_CHECK_INTERNAL_PATH))
+ continue;
+
+ node_type = di_minor_nodetype(minor);
+ if ((desired_type != NULL) && ((node_type == NULL) ||
+ strncmp(desired_type, node_type, strlen(desired_type))
+ != 0))
+ continue;
+
+ if ((action = callback(node, minor, arg)) ==
+ DI_WALK_TERMINATE) {
+ break;
+ }
+ }
+
+ update_node_list(action, DI_WALK_LINKGEN, headp);
+}
+
+int
+di_walk_minor(di_node_t root, const char *minor_type, uint_t flag, void *arg,
+ int (*minor_callback)(di_node_t, di_minor_t, void *))
+{
+ struct node_list *head; /* node_list for tree walk */
+
+#ifdef DEBUG
+ char *path = di_devfs_path(root);
+ DPRINTF((DI_INFO, "walking minor nodes under %s\n", path));
+ di_devfs_path_free(path);
+#endif
+
+ if (root == NULL) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ if ((head = malloc(sizeof (struct node_list))) == NULL) {
+ DPRINTF((DI_ERR, "malloc of node_list failed\n"));
+ return (-1);
+ }
+
+ head->next = NULL;
+ head->node = root;
+
+ DPRINTF((DI_INFO, "Start minor walking from node %s\n",
+ di_node_name(root)));
+
+ while (head != NULL)
+ walk_one_minor_list(&head, minor_type, flag, arg,
+ minor_callback);
+
+ return (0);
+}
+
+/*
+ * generic node parameters
+ * Calling these routines always succeeds.
+ */
+char *
+di_node_name(di_node_t node)
+{
+ return ((caddr_t)node + DI_NODE(node)->node_name - DI_NODE(node)->self);
+}
+
+/* returns NULL ptr or a valid ptr to non-NULL string */
+char *
+di_bus_addr(di_node_t node)
+{
+ caddr_t pa = (caddr_t)node - DI_NODE(node)->self;
+
+ if (DI_NODE(node)->address == 0)
+ return (NULL);
+
+ return ((char *)(pa + DI_NODE(node)->address));
+}
+
+char *
+di_binding_name(di_node_t node)
+{
+ caddr_t pa = (caddr_t)node - DI_NODE(node)->self;
+
+ if (DI_NODE(node)->bind_name == 0)
+ return (NULL);
+
+ return ((char *)(pa + DI_NODE(node)->bind_name));
+}
+
+int
+di_compatible_names(di_node_t node, char **names)
+{
+ char *c;
+ int len, size, entries = 0;
+
+ if (DI_NODE(node)->compat_names == 0) {
+ *names = NULL;
+ return (0);
+ }
+
+ *names = (caddr_t)node +
+ DI_NODE(node)->compat_names - DI_NODE(node)->self;
+
+ c = *names;
+ len = DI_NODE(node)->compat_length;
+ while (len > 0) {
+ entries++;
+ size = strlen(c) + 1;
+ len -= size;
+ c += size;
+ }
+
+ return (entries);
+}
+
+int
+di_instance(di_node_t node)
+{
+ return (DI_NODE(node)->instance);
+}
+
+/*
+ * XXX: emulate the return value of the old implementation
+ * using info from devi_node_class and devi_node_attributes.
+ */
+int
+di_nodeid(di_node_t node)
+{
+ if (DI_NODE(node)->node_class == DDI_NC_PROM)
+ return (DI_PROM_NODEID);
+
+ if (DI_NODE(node)->attributes & DDI_PERSISTENT)
+ return (DI_SID_NODEID);
+
+ return (DI_PSEUDO_NODEID);
+}
+
+uint_t
+di_state(di_node_t node)
+{
+ uint_t result = 0;
+
+ if (di_node_state(node) < DS_ATTACHED)
+ result |= DI_DRIVER_DETACHED;
+ if (DI_NODE(node)->state & DEVI_DEVICE_OFFLINE)
+ result |= DI_DEVICE_OFFLINE;
+ if (DI_NODE(node)->state & DEVI_DEVICE_DOWN)
+ result |= DI_DEVICE_OFFLINE;
+ if (DI_NODE(node)->state & DEVI_BUS_QUIESCED)
+ result |= DI_BUS_QUIESCED;
+ if (DI_NODE(node)->state & DEVI_BUS_DOWN)
+ result |= DI_BUS_DOWN;
+
+ return (result);
+}
+
+ddi_node_state_t
+di_node_state(di_node_t node)
+{
+ return (DI_NODE(node)->node_state);
+}
+
+ddi_devid_t
+di_devid(di_node_t node)
+{
+ if (DI_NODE(node)->devid == 0)
+ return (NULL);
+
+ return ((ddi_devid_t)((caddr_t)node +
+ DI_NODE(node)->devid - DI_NODE(node)->self));
+}
+
+int
+di_driver_major(di_node_t node)
+{
+ int major;
+
+ major = DI_NODE(node)->drv_major;
+ if (major < 0)
+ return (-1);
+ return (major);
+}
+
+char *
+di_driver_name(di_node_t node)
+{
+ int major;
+ caddr_t pa;
+ struct di_devnm *devnm;
+
+ major = DI_NODE(node)->drv_major;
+ if (major < 0)
+ return (NULL);
+
+ pa = (caddr_t)node - DI_NODE(node)->self;
+ devnm = DI_DEVNM(pa + DI_ALL(pa)->devnames);
+
+ if (devnm[major].name)
+ return (pa + devnm[major].name);
+ else
+ return (NULL);
+}
+
+uint_t
+di_driver_ops(di_node_t node)
+{
+ int major;
+ caddr_t pa;
+ struct di_devnm *devnm;
+
+ major = DI_NODE(node)->drv_major;
+ if (major < 0)
+ return (0);
+
+ pa = (caddr_t)node - DI_NODE(node)->self;
+ devnm = DI_DEVNM(pa + DI_ALL(pa)->devnames);
+
+ return (devnm[major].ops);
+}
+
+/*
+ * returns the length of the path, caller must free memory
+ */
+char *
+di_devfs_path(di_node_t node)
+{
+ caddr_t pa;
+ di_node_t parent;
+ int depth = 0, len = 0;
+ char *buf, *name[MAX_TREE_DEPTH], *addr[MAX_TREE_DEPTH];
+
+ if (node == DI_NODE_NIL) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ /*
+ * trace back to root, note the node_name & address
+ */
+ while ((parent = di_parent_node(node)) != DI_NODE_NIL) {
+ name[depth] = di_node_name(node);
+ len += strlen(name[depth]) + 1; /* 1 for '/' */
+
+ if ((addr[depth] = di_bus_addr(node)) != NULL)
+ len += strlen(addr[depth]) + 1; /* 1 for '@' */
+
+ node = parent;
+ depth++;
+ }
+
+ /*
+ * get the path to the root of snapshot
+ */
+ pa = (caddr_t)node - DI_NODE(node)->self;
+ name[depth] = DI_ALL(pa)->root_path;
+ len += strlen(name[depth]) + 1;
+
+ /*
+ * allocate buffer and assemble path
+ */
+ if ((buf = malloc(len)) == NULL) {
+ return (NULL);
+ }
+
+ (void) strcpy(buf, name[depth]);
+ len = strlen(buf);
+ if (buf[len - 1] == '/')
+ len--; /* delete trailing '/' */
+
+ while (depth) {
+ depth--;
+ buf[len] = '/';
+ (void) strcpy(buf + len + 1, name[depth]);
+ len += strlen(name[depth]) + 1;
+ if (addr[depth] && addr[depth][0] != '\0') {
+ buf[len] = '@';
+ (void) strcpy(buf + len + 1, addr[depth]);
+ len += strlen(addr[depth]) + 1;
+ }
+ }
+
+ return (buf);
+}
+
+char *
+di_devfs_minor_path(di_minor_t minor)
+{
+ di_node_t node;
+ char *full_path, *name, *path;
+ int full_path_len;
+
+ if (minor == DI_MINOR_NIL) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ name = di_minor_name(minor);
+ node = di_minor_devinfo(minor);
+ path = di_devfs_path(node);
+ if (path == NULL)
+ return (NULL);
+
+ /* make the full path to the device minor node */
+ full_path_len = strlen(path) + strlen(name) + 2;
+ full_path = (char *)calloc(1, full_path_len);
+ if (full_path != NULL)
+ (void) snprintf(full_path, full_path_len, "%s:%s", path, name);
+
+ di_devfs_path_free(path);
+ return (full_path);
+}
+
+void
+di_devfs_path_free(char *buf)
+{
+ if (buf == NULL) {
+ DPRINTF((DI_ERR, "di_devfs_path_free NULL arg!\n"));
+ return;
+ }
+
+ free(buf);
+}
+
+/* minor data access */
+di_minor_t
+di_minor_next(di_node_t node, di_minor_t minor)
+{
+ caddr_t pa;
+
+ /*
+ * paranoid error checking
+ */
+ if (node == DI_NODE_NIL) {
+ errno = EINVAL;
+ return (DI_MINOR_NIL);
+ }
+
+ /*
+ * minor is not NIL
+ */
+ if (minor != DI_MINOR_NIL) {
+ if (DI_MINOR(minor)->next != 0)
+ return ((di_minor_t)((void *)((caddr_t)minor -
+ DI_MINOR(minor)->self + DI_MINOR(minor)->next)));
+ else {
+ errno = ENXIO;
+ return (DI_MINOR_NIL);
+ }
+ }
+
+ /*
+ * minor is NIL-->caller asks for first minor node
+ */
+ if (DI_NODE(node)->minor_data != 0) {
+ return (DI_MINOR((caddr_t)node - DI_NODE(node)->self +
+ DI_NODE(node)->minor_data));
+ }
+
+ /*
+ * no minor data-->check if snapshot includes minor data
+ * in order to set the correct errno
+ */
+ pa = (caddr_t)node - DI_NODE(node)->self;
+ if (DINFOMINOR & DI_ALL(pa)->command)
+ errno = ENXIO;
+ else
+ errno = ENOTSUP;
+
+ return (DI_MINOR_NIL);
+}
+
+/* private interface for dealing with alias minor link generation */
+di_node_t
+di_minor_devinfo(di_minor_t minor)
+{
+ if (minor == DI_MINOR_NIL) {
+ errno = EINVAL;
+ return (DI_NODE_NIL);
+ }
+
+ return (DI_NODE((caddr_t)minor - DI_MINOR(minor)->self +
+ DI_MINOR(minor)->node));
+}
+
+ddi_minor_type
+di_minor_type(di_minor_t minor)
+{
+ return (DI_MINOR(minor)->type);
+}
+
+char *
+di_minor_name(di_minor_t minor)
+{
+ if (DI_MINOR(minor)->name == 0)
+ return (NULL);
+
+ return ((caddr_t)minor - DI_MINOR(minor)->self + DI_MINOR(minor)->name);
+}
+
+dev_t
+di_minor_devt(di_minor_t minor)
+{
+ return (makedev(DI_MINOR(minor)->dev_major,
+ DI_MINOR(minor)->dev_minor));
+}
+
+int
+di_minor_spectype(di_minor_t minor)
+{
+ return (DI_MINOR(minor)->spec_type);
+}
+
+char *
+di_minor_nodetype(di_minor_t minor)
+{
+ if (DI_MINOR(minor)->node_type == 0)
+ return (NULL);
+
+ return ((caddr_t)minor -
+ DI_MINOR(minor)->self + DI_MINOR(minor)->node_type);
+}
+
+/*
+ * Single public interface for accessing software properties
+ */
+di_prop_t
+di_prop_next(di_node_t node, di_prop_t prop)
+{
+ int list = DI_PROP_DRV_LIST;
+
+ /*
+ * paranoid check
+ */
+ if (node == DI_NODE_NIL) {
+ errno = EINVAL;
+ return (DI_PROP_NIL);
+ }
+
+ /*
+ * Find which prop list we are at
+ */
+ if (prop != DI_PROP_NIL)
+ list = DI_PROP(prop)->prop_list;
+
+ do {
+ switch (list++) {
+ case DI_PROP_DRV_LIST:
+ prop = di_prop_drv_next(node, prop);
+ break;
+ case DI_PROP_SYS_LIST:
+ prop = di_prop_sys_next(node, prop);
+ break;
+ case DI_PROP_GLB_LIST:
+ prop = di_prop_global_next(node, prop);
+ break;
+ case DI_PROP_HW_LIST:
+ prop = di_prop_hw_next(node, prop);
+ break;
+ default: /* shouldn't happen */
+ errno = EFAULT;
+ return (DI_PROP_NIL);
+ }
+ } while ((prop == DI_PROP_NIL) && (list <= DI_PROP_HW_LIST));
+
+ return (prop);
+}
+
+dev_t
+di_prop_devt(di_prop_t prop)
+{
+ return (makedev(DI_PROP(prop)->dev_major, DI_PROP(prop)->dev_minor));
+}
+
+char *
+di_prop_name(di_prop_t prop)
+{
+ if (DI_PROP(prop)->prop_name == 0)
+ return (NULL);
+
+ return ((caddr_t)prop - DI_PROP(prop)->self + DI_PROP(prop)->prop_name);
+}
+
+int
+di_prop_type(di_prop_t prop)
+{
+ uint_t flags = DI_PROP(prop)->prop_flags;
+
+ if (flags & DDI_PROP_UNDEF_IT)
+ return (DI_PROP_TYPE_UNDEF_IT);
+
+ if (DI_PROP(prop)->prop_len == 0)
+ return (DI_PROP_TYPE_BOOLEAN);
+
+ if ((flags & DDI_PROP_TYPE_MASK) == DDI_PROP_TYPE_ANY)
+ return (DI_PROP_TYPE_UNKNOWN);
+
+ if (flags & DDI_PROP_TYPE_INT)
+ return (DI_PROP_TYPE_INT);
+
+ if (flags & DDI_PROP_TYPE_INT64)
+ return (DI_PROP_TYPE_INT64);
+
+ if (flags & DDI_PROP_TYPE_STRING)
+ return (DI_PROP_TYPE_STRING);
+
+ if (flags & DDI_PROP_TYPE_BYTE)
+ return (DI_PROP_TYPE_BYTE);
+
+ /*
+ * Shouldn't get here. In case we do, return unknown type.
+ *
+ * XXX--When DDI_PROP_TYPE_COMPOSITE is implemented, we need
+ * to add DI_PROP_TYPE_COMPOSITE.
+ */
+ DPRINTF((DI_ERR, "Unimplemented property type: 0x%x\n", flags));
+
+ return (DI_PROP_TYPE_UNKNOWN);
+}
+
+/*
+ * Extract type-specific values of an property
+ */
+extern int di_prop_decode_common(void *prop_data, int len,
+ int ddi_type, int prom);
+
+int
+di_prop_ints(di_prop_t prop, int **prop_data)
+{
+ if (DI_PROP(prop)->prop_len == 0)
+ return (0); /* boolean property */
+
+ if ((DI_PROP(prop)->prop_data == 0) ||
+ (DI_PROP(prop)->prop_data == (di_off_t)-1)) {
+ errno = EFAULT;
+ *prop_data = NULL;
+ return (-1);
+ }
+
+ *prop_data = (int *)((void *)((caddr_t)prop - DI_PROP(prop)->self
+ + DI_PROP(prop)->prop_data));
+
+ return (di_prop_decode_common((void *)prop_data,
+ DI_PROP(prop)->prop_len, DI_PROP_TYPE_INT, 0));
+}
+
+int
+di_prop_int64(di_prop_t prop, int64_t **prop_data)
+{
+ if (DI_PROP(prop)->prop_len == 0)
+ return (0); /* boolean property */
+
+ if ((DI_PROP(prop)->prop_data == 0) ||
+ (DI_PROP(prop)->prop_data == (di_off_t)-1)) {
+ errno = EFAULT;
+ *prop_data = NULL;
+ return (-1);
+ }
+
+ *prop_data = (int64_t *)((void *)((caddr_t)prop - DI_PROP(prop)->self
+ + DI_PROP(prop)->prop_data));
+
+ return (di_prop_decode_common((void *)prop_data,
+ DI_PROP(prop)->prop_len, DI_PROP_TYPE_INT64, 0));
+}
+
+int
+di_prop_strings(di_prop_t prop, char **prop_data)
+{
+ if (DI_PROP(prop)->prop_len == 0)
+ return (0); /* boolean property */
+
+ if ((DI_PROP(prop)->prop_data == 0) ||
+ (DI_PROP(prop)->prop_data == (di_off_t)-1)) {
+ errno = EFAULT;
+ *prop_data = NULL;
+ return (-1);
+ }
+
+ *prop_data = (char *)((caddr_t)prop - DI_PROP(prop)->self
+ + DI_PROP(prop)->prop_data);
+
+ return (di_prop_decode_common((void *)prop_data,
+ DI_PROP(prop)->prop_len, DI_PROP_TYPE_STRING, 0));
+}
+
+int
+di_prop_bytes(di_prop_t prop, uchar_t **prop_data)
+{
+ if (DI_PROP(prop)->prop_len == 0)
+ return (0); /* boolean property */
+
+ if ((DI_PROP(prop)->prop_data == 0) ||
+ (DI_PROP(prop)->prop_data == (di_off_t)-1)) {
+ errno = EFAULT;
+ *prop_data = NULL;
+ return (-1);
+ }
+
+ *prop_data = (uchar_t *)((caddr_t)prop - DI_PROP(prop)->self
+ + DI_PROP(prop)->prop_data);
+
+ return (di_prop_decode_common((void *)prop_data,
+ DI_PROP(prop)->prop_len, DI_PROP_TYPE_BYTE, 0));
+}
+
+/*
+ * returns 1 for match, 0 for no match
+ */
+static int
+match_prop(di_prop_t prop, dev_t match_dev, const char *name, int type)
+{
+ int prop_type;
+
+#ifdef DEBUG
+ if (di_prop_name(prop) == NULL) {
+ DPRINTF((DI_ERR, "libdevinfo: property has no name!\n"));
+ return (0);
+ }
+#endif /* DEBUG */
+
+ if (strcmp(name, di_prop_name(prop)) != 0)
+ return (0);
+
+ if ((match_dev != DDI_DEV_T_ANY) && (di_prop_devt(prop) != match_dev))
+ return (0);
+
+ /*
+ * XXX prop_type is different from DDI_*. See PSARC 1997/127.
+ */
+ prop_type = di_prop_type(prop);
+ if ((prop_type != DI_PROP_TYPE_UNKNOWN) && (prop_type != type) &&
+ (prop_type != DI_PROP_TYPE_BOOLEAN))
+ return (0);
+
+ return (1);
+}
+
+static di_prop_t
+di_prop_search(dev_t match_dev, di_node_t node, const char *name,
+ int type)
+{
+ di_prop_t prop = DI_PROP_NIL;
+
+ /*
+ * The check on match_dev follows ddi_prop_lookup_common().
+ * Other checks are libdevinfo specific implementation.
+ */
+ if ((node == DI_NODE_NIL) || (name == NULL) || (strlen(name) == 0) ||
+ (match_dev == DDI_DEV_T_NONE) || !DI_PROP_TYPE_VALID(type)) {
+ errno = EINVAL;
+ return (DI_PROP_NIL);
+ }
+
+ while ((prop = di_prop_next(node, prop)) != DI_PROP_NIL) {
+ DPRINTF((DI_TRACE1, "match prop name %s, devt 0x%lx, type %d\n",
+ di_prop_name(prop), di_prop_devt(prop),
+ di_prop_type(prop)));
+ if (match_prop(prop, match_dev, name, type))
+ return (prop);
+ }
+
+ return (DI_PROP_NIL);
+}
+
+int
+di_prop_lookup_ints(dev_t dev, di_node_t node, const char *prop_name,
+ int **prop_data)
+{
+ di_prop_t prop;
+
+ if ((prop = di_prop_search(dev, node, prop_name,
+ DI_PROP_TYPE_INT)) == DI_PROP_NIL)
+ return (-1);
+
+ return (di_prop_ints(prop, (void *)prop_data));
+}
+
+int
+di_prop_lookup_int64(dev_t dev, di_node_t node, const char *prop_name,
+ int64_t **prop_data)
+{
+ di_prop_t prop;
+
+ if ((prop = di_prop_search(dev, node, prop_name,
+ DI_PROP_TYPE_INT64)) == DI_PROP_NIL)
+ return (-1);
+
+ return (di_prop_int64(prop, (void *)prop_data));
+}
+
+int
+di_prop_lookup_strings(dev_t dev, di_node_t node, const char *prop_name,
+ char **prop_data)
+{
+ di_prop_t prop;
+
+ if ((prop = di_prop_search(dev, node, prop_name,
+ DI_PROP_TYPE_STRING)) == DI_PROP_NIL)
+ return (-1);
+
+ return (di_prop_strings(prop, (void *)prop_data));
+}
+
+int
+di_prop_lookup_bytes(dev_t dev, di_node_t node, const char *prop_name,
+ uchar_t **prop_data)
+{
+ di_prop_t prop;
+
+ if ((prop = di_prop_search(dev, node, prop_name,
+ DI_PROP_TYPE_BYTE)) == DI_PROP_NIL)
+ return (-1);
+
+ return (di_prop_bytes(prop, (void *)prop_data));
+}
+
+/*
+ * Consolidation private property access functions
+ */
+enum prop_type {
+ PROP_TYPE_DRV,
+ PROP_TYPE_SYS,
+ PROP_TYPE_GLOB,
+ PROP_TYPE_HW
+};
+
+static di_prop_t
+di_prop_next_common(di_node_t node, di_prop_t prop, int prop_type)
+{
+ caddr_t pa;
+ di_off_t prop_off = 0;
+
+ if (prop != DI_PROP_NIL) {
+ if (DI_PROP(prop)->next) {
+ return (DI_PROP((caddr_t)prop -
+ DI_PROP(prop)->self + DI_PROP(prop)->next));
+ } else {
+ return (DI_PROP_NIL);
+ }
+ }
+
+
+ /*
+ * prop is NIL, caller asks for first property
+ */
+ pa = (caddr_t)node - DI_NODE(node)->self;
+ switch (prop_type) {
+ case PROP_TYPE_DRV:
+ prop_off = DI_NODE(node)->drv_prop;
+ break;
+ case PROP_TYPE_SYS:
+ prop_off = DI_NODE(node)->sys_prop;
+ break;
+ case PROP_TYPE_HW:
+ prop_off = DI_NODE(node)->hw_prop;
+ break;
+ case PROP_TYPE_GLOB:
+ prop_off = DI_NODE(node)->glob_prop;
+ if (prop_off == -1) {
+ /* no global property */
+ prop_off = 0;
+ } else if ((prop_off == 0) && (DI_NODE(node)->drv_major >= 0)) {
+ /* refer to devnames array */
+ struct di_devnm *devnm = DI_DEVNM(pa +
+ DI_ALL(pa)->devnames + (DI_NODE(node)->drv_major *
+ sizeof (struct di_devnm)));
+ prop_off = devnm->global_prop;
+ }
+ break;
+ }
+
+ if (prop_off) {
+ return (DI_PROP(pa + prop_off));
+ }
+
+ /*
+ * no prop found. Check the reason for not found
+ */
+ if (DINFOPROP & DI_ALL(pa)->command)
+ errno = ENXIO;
+ else
+ errno = ENOTSUP;
+
+ return (DI_PROP_NIL);
+}
+
+di_prop_t
+di_prop_drv_next(di_node_t node, di_prop_t prop)
+{
+ return (di_prop_next_common(node, prop, PROP_TYPE_DRV));
+}
+
+di_prop_t
+di_prop_sys_next(di_node_t node, di_prop_t prop)
+{
+ return (di_prop_next_common(node, prop, PROP_TYPE_SYS));
+}
+
+di_prop_t
+di_prop_global_next(di_node_t node, di_prop_t prop)
+{
+ return (di_prop_next_common(node, prop, PROP_TYPE_GLOB));
+}
+
+di_prop_t
+di_prop_hw_next(di_node_t node, di_prop_t prop)
+{
+ return (di_prop_next_common(node, prop, PROP_TYPE_HW));
+}
+
+int
+di_prop_rawdata(di_prop_t prop, uchar_t **prop_data)
+{
+#ifdef DEBUG
+ if (prop == DI_PROP_NIL) {
+ errno = EINVAL;
+ return (-1);
+ }
+#endif /* DEBUG */
+
+ if (DI_PROP(prop)->prop_len == 0) {
+ *prop_data = NULL;
+ return (0);
+ }
+
+ if ((DI_PROP(prop)->prop_data == 0) ||
+ (DI_PROP(prop)->prop_data == (di_off_t)-1)) {
+ errno = EFAULT;
+ *prop_data = NULL;
+ return (-1);
+ }
+
+ /*
+ * No memory allocation.
+ */
+ *prop_data = (uchar_t *)((caddr_t)prop - DI_PROP(prop)->self +
+ DI_PROP(prop)->prop_data);
+
+ return (DI_PROP(prop)->prop_len);
+}
+
+/*
+ * Consolidation private interfaces for accessing I/O multipathing data
+ */
+di_path_t
+di_path_next_client(di_node_t node, di_path_t path)
+{
+ caddr_t pa;
+
+ /*
+ * path is not NIL
+ */
+ if (path != DI_PATH_NIL) {
+ if (DI_PATH(path)->path_p_link != 0)
+ return (DI_PATH((void *)((caddr_t)path -
+ DI_PATH(path)->self + DI_PATH(path)->path_p_link)));
+ else {
+ errno = ENXIO;
+ return (DI_PATH_NIL);
+ }
+ }
+
+ /*
+ * Path is NIL; the caller is asking for the first path info node
+ */
+ if (DI_NODE(node)->multipath_phci != 0) {
+ DPRINTF((DI_INFO, "phci: returning %p\n", ((caddr_t)node -
+ DI_NODE(node)->self + DI_NODE(node)->multipath_phci)));
+ return (DI_PATH((caddr_t)node - DI_NODE(node)->self +
+ DI_NODE(node)->multipath_phci));
+ }
+
+ /*
+ * No pathing data; check if the snapshot includes path data in order
+ * to set errno properly.
+ */
+ pa = (caddr_t)node - DI_NODE(node)->self;
+ if (DINFOPATH & (DI_ALL(pa)->command))
+ errno = ENXIO;
+ else
+ errno = ENOTSUP;
+
+ return (DI_PATH_NIL);
+}
+
+di_path_t
+di_path_next_phci(di_node_t node, di_path_t path)
+{
+ caddr_t pa;
+
+ /*
+ * path is not NIL
+ */
+ if (path != DI_PATH_NIL) {
+ if (DI_PATH(path)->path_c_link != 0)
+ return (DI_PATH((caddr_t)path - DI_PATH(path)->self
+ + DI_PATH(path)->path_c_link));
+ else {
+ errno = ENXIO;
+ return (DI_PATH_NIL);
+ }
+ }
+
+ /*
+ * Path is NIL; the caller is asking for the first path info node
+ */
+ if (DI_NODE(node)->multipath_client != 0) {
+ DPRINTF((DI_INFO, "client: returning %p\n", ((caddr_t)node -
+ DI_NODE(node)->self + DI_NODE(node)->multipath_client)));
+ return (DI_PATH((caddr_t)node - DI_NODE(node)->self +
+ DI_NODE(node)->multipath_client));
+ }
+
+ /*
+ * No pathing data; check if the snapshot includes path data in order
+ * to set errno properly.
+ */
+ pa = (caddr_t)node - DI_NODE(node)->self;
+ if (DINFOPATH & (DI_ALL(pa)->command))
+ errno = ENXIO;
+ else
+ errno = ENOTSUP;
+
+ return (DI_PATH_NIL);
+}
+
+/*
+ * XXX Obsolete wrapper to be removed. Won't work under multilevel.
+ */
+di_path_t
+di_path_next(di_node_t node, di_path_t path)
+{
+ if (node == DI_NODE_NIL) {
+ errno = EINVAL;
+ return (DI_PATH_NIL);
+ }
+
+ if (DI_NODE(node)->multipath_client) {
+ return (di_path_next_phci(node, path));
+ } else if (DI_NODE(node)->multipath_phci) {
+ return (di_path_next_client(node, path));
+ } else {
+ /*
+ * The node had multipathing data but didn't appear to be a
+ * phci *or* a client; probably a programmer error.
+ */
+ errno = EINVAL;
+ return (DI_PATH_NIL);
+ }
+}
+
+di_path_state_t
+di_path_state(di_path_t path)
+{
+ return ((di_path_state_t)DI_PATH(path)->path_state);
+}
+
+char *
+di_path_addr(di_path_t path, char *buf)
+{
+ caddr_t pa; /* starting address of map */
+
+ pa = (caddr_t)path - DI_PATH(path)->self;
+
+ (void) strncpy(buf, (char *)(pa + DI_PATH(path)->path_addr),
+ MAXPATHLEN);
+ return (buf);
+}
+
+di_node_t
+di_path_client_node(di_path_t path)
+{
+ caddr_t pa; /* starting address of map */
+
+ if (path == DI_PATH_NIL) {
+ errno = EINVAL;
+ return (DI_PATH_NIL);
+ }
+
+ DPRINTF((DI_TRACE, "Get client node for path %p\n", path));
+
+ pa = (caddr_t)path - DI_PATH(path)->self;
+
+ if (DI_PATH(path)->path_client) {
+ return (DI_NODE(pa + DI_PATH(path)->path_client));
+ }
+
+ /*
+ * Deal with error condition:
+ * If parent doesn't exist and node is not the root,
+ * set errno to ENOTSUP. Otherwise, set errno to ENXIO.
+ */
+ if ((DI_PATH(path)->path_snap_state & DI_PATH_SNAP_NOCLIENT) == 0)
+ errno = ENOTSUP;
+ else
+ errno = ENXIO;
+
+ return (DI_NODE_NIL);
+}
+
+di_node_t
+di_path_phci_node(di_path_t path)
+{
+ caddr_t pa; /* starting address of map */
+
+ if (path == DI_PATH_NIL) {
+ errno = EINVAL;
+ return (DI_PATH_NIL);
+ }
+
+ DPRINTF((DI_TRACE, "Get phci node for path %p\n", path));
+
+ pa = (caddr_t)path - DI_PATH(path)->self;
+
+ if (DI_PATH(path)->path_phci) {
+ return (DI_NODE(pa + DI_PATH(path)->path_phci));
+ }
+
+ /*
+ * Deal with error condition:
+ * If parent doesn't exist and node is not the root,
+ * set errno to ENOTSUP. Otherwise, set errno to ENXIO.
+ */
+ if ((DI_PATH(path)->path_snap_state & DI_PATH_SNAP_NOPHCI) == 0)
+ errno = ENOTSUP;
+ else
+ errno = ENXIO;
+
+ return (DI_NODE_NIL);
+}
+
+di_path_prop_t
+di_path_prop_next(di_path_t path, di_path_prop_t prop)
+{
+ caddr_t pa;
+
+ if (path == DI_PATH_NIL) {
+ errno = EINVAL;
+ return (DI_PROP_NIL);
+ }
+
+ /*
+ * prop is not NIL
+ */
+ if (prop != DI_PROP_NIL) {
+ if (DI_PROP(prop)->next != 0)
+ return (DI_PATHPROP((caddr_t)prop -
+ DI_PROP(prop)->self + DI_PROP(prop)->next));
+ else {
+ errno = ENXIO;
+ return (DI_PROP_NIL);
+ }
+ }
+
+ /*
+ * prop is NIL-->caller asks for first property
+ */
+ pa = (caddr_t)path - DI_PATH(path)->self;
+ if (DI_PATH(path)->path_prop != 0) {
+ return (DI_PATHPROP(pa + DI_PATH(path)->path_prop));
+ }
+
+ /*
+ * no property data-->check if snapshot includes props
+ * in order to set the correct errno
+ */
+ if (DINFOPROP & (DI_ALL(pa)->command))
+ errno = ENXIO;
+ else
+ errno = ENOTSUP;
+
+ return (DI_PROP_NIL);
+}
+
+char *
+di_path_prop_name(di_path_prop_t prop)
+{
+ caddr_t pa; /* starting address of map */
+ pa = (caddr_t)prop - DI_PATHPROP(prop)->self;
+ return ((char *)(pa + DI_PATHPROP(prop)->prop_name));
+}
+
+int
+di_path_prop_len(di_path_prop_t prop)
+{
+ return (DI_PATHPROP(prop)->prop_len);
+}
+
+int
+di_path_prop_type(di_path_prop_t prop)
+{
+ switch (DI_PATHPROP(prop)->prop_type) {
+ case DDI_PROP_TYPE_INT:
+ return (DI_PROP_TYPE_INT);
+ case DDI_PROP_TYPE_INT64:
+ return (DI_PROP_TYPE_INT64);
+ case DDI_PROP_TYPE_BYTE:
+ return (DI_PROP_TYPE_BYTE);
+ case DDI_PROP_TYPE_STRING:
+ return (DI_PROP_TYPE_STRING);
+ }
+ return (DI_PROP_TYPE_UNKNOWN);
+}
+
+int
+di_path_prop_bytes(di_path_prop_t prop, uchar_t **prop_data)
+{
+ if ((DI_PATHPROP(prop)->prop_data == 0) ||
+ (DI_PATHPROP(prop)->prop_data == (di_off_t)-1)) {
+ errno = EFAULT;
+ *prop_data = NULL;
+ return (-1);
+ }
+
+ *prop_data = (uchar_t *)((caddr_t)prop - DI_PATHPROP(prop)->self
+ + DI_PATHPROP(prop)->prop_data);
+
+ return (di_prop_decode_common((void *)prop_data,
+ DI_PATHPROP(prop)->prop_len, DI_PROP_TYPE_BYTE, 0));
+}
+
+int
+di_path_prop_ints(di_path_prop_t prop, int **prop_data)
+{
+ if (DI_PATHPROP(prop)->prop_len == 0)
+ return (0);
+
+ if ((DI_PATHPROP(prop)->prop_data == 0) ||
+ (DI_PATHPROP(prop)->prop_data == (di_off_t)-1)) {
+ errno = EFAULT;
+ *prop_data = NULL;
+ return (-1);
+ }
+
+ *prop_data = (int *)((void *)((caddr_t)prop - DI_PATHPROP(prop)->self
+ + DI_PATHPROP(prop)->prop_data));
+
+ return (di_prop_decode_common((void *)prop_data,
+ DI_PATHPROP(prop)->prop_len, DI_PROP_TYPE_INT, 0));
+}
+
+int
+di_path_prop_int64s(di_path_prop_t prop, int64_t **prop_data)
+{
+ if (DI_PATHPROP(prop)->prop_len == 0)
+ return (0);
+
+ if ((DI_PATHPROP(prop)->prop_data == 0) ||
+ (DI_PATHPROP(prop)->prop_data == (di_off_t)-1)) {
+ errno = EFAULT;
+ *prop_data = NULL;
+ return (-1);
+ }
+
+ *prop_data = (int64_t *)((void *)((caddr_t)prop -
+ DI_PATHPROP(prop)->self + DI_PATHPROP(prop)->prop_data));
+
+ return (di_prop_decode_common((void *)prop_data,
+ DI_PATHPROP(prop)->prop_len, DI_PROP_TYPE_INT64, 0));
+}
+
+int
+di_path_prop_strings(di_path_prop_t prop, char **prop_data)
+{
+ if (DI_PATHPROP(prop)->prop_len == 0)
+ return (0);
+
+ if ((DI_PATHPROP(prop)->prop_data == 0) ||
+ (DI_PATHPROP(prop)->prop_data == (di_off_t)-1)) {
+ errno = EFAULT;
+ *prop_data = NULL;
+ return (-1);
+ }
+
+ *prop_data = (char *)((caddr_t)prop - DI_PATHPROP(prop)->self
+ + DI_PATHPROP(prop)->prop_data);
+
+ return (di_prop_decode_common((void *)prop_data,
+ DI_PATHPROP(prop)->prop_len, DI_PROP_TYPE_STRING, 0));
+}
+
+static di_path_prop_t
+di_path_prop_search(di_path_t path, const char *name, int type)
+{
+ di_path_prop_t prop = DI_PROP_NIL;
+
+ /*
+ * Sanity check arguments
+ */
+ if ((path == DI_PATH_NIL) || (name == NULL) || (strlen(name) == 0) ||
+ !DI_PROP_TYPE_VALID(type)) {
+ errno = EINVAL;
+ return (DI_PROP_NIL);
+ }
+
+ while ((prop = di_path_prop_next(path, prop)) != DI_PROP_NIL) {
+ int prop_type = di_path_prop_type(prop);
+
+ DPRINTF((DI_TRACE1, "match path prop name %s, type %d\n",
+ di_path_prop_name(prop), prop_type));
+
+ if (strcmp(name, di_path_prop_name(prop)) != 0)
+ continue;
+
+ if ((prop_type != DI_PROP_TYPE_UNKNOWN) && (prop_type != type))
+ continue;
+
+ return (prop);
+ }
+
+ return (DI_PROP_NIL);
+}
+
+int
+di_path_prop_lookup_bytes(di_path_t path, const char *prop_name,
+ uchar_t **prop_data)
+{
+ di_path_prop_t prop;
+
+ if ((prop = di_path_prop_search(path, prop_name,
+ DI_PROP_TYPE_BYTE)) == DI_PROP_NIL)
+ return (-1);
+
+ return (di_path_prop_bytes(prop, prop_data));
+}
+
+int
+di_path_prop_lookup_ints(di_path_t path, const char *prop_name,
+ int **prop_data)
+{
+ di_path_prop_t prop;
+
+ if ((prop = di_path_prop_search(path, prop_name,
+ DI_PROP_TYPE_INT)) == DI_PROP_NIL)
+ return (-1);
+
+ return (di_path_prop_ints(prop, prop_data));
+}
+
+int
+di_path_prop_lookup_int64s(di_path_t path, const char *prop_name,
+ int64_t **prop_data)
+{
+ di_path_prop_t prop;
+
+ if ((prop = di_path_prop_search(path, prop_name,
+ DI_PROP_TYPE_INT64)) == DI_PROP_NIL)
+ return (-1);
+
+ return (di_path_prop_int64s(prop, prop_data));
+}
+
+int di_path_prop_lookup_strings(di_path_t path, const char *prop_name,
+ char **prop_data)
+{
+ di_path_prop_t prop;
+
+ if ((prop = di_path_prop_search(path, prop_name,
+ DI_PROP_TYPE_STRING)) == DI_PROP_NIL)
+ return (-1);
+
+ return (di_path_prop_strings(prop, prop_data));
+}
+
+
+/*
+ * Consolidation private interfaces for private data
+ */
+void *
+di_parent_private_data(di_node_t node)
+{
+ caddr_t pa;
+
+ if (DI_NODE(node)->parent_data == 0) {
+ errno = ENXIO;
+ return (NULL);
+ }
+
+ if (DI_NODE(node)->parent_data == (di_off_t)-1) {
+ /*
+ * Private data requested, but not obtained due to a memory
+ * error (e.g. wrong format specified)
+ */
+ errno = EFAULT;
+ return (NULL);
+ }
+
+ pa = (caddr_t)node - DI_NODE(node)->self;
+ if (DI_NODE(node)->parent_data)
+ return (pa + DI_NODE(node)->parent_data);
+
+ if (DI_ALL(pa)->command & DINFOPRIVDATA)
+ errno = ENXIO;
+ else
+ errno = ENOTSUP;
+
+ return (NULL);
+}
+
+void *
+di_driver_private_data(di_node_t node)
+{
+ caddr_t pa;
+
+ if (DI_NODE(node)->driver_data == 0) {
+ errno = ENXIO;
+ return (NULL);
+ }
+
+ if (DI_NODE(node)->driver_data == (di_off_t)-1) {
+ /*
+ * Private data requested, but not obtained due to a memory
+ * error (e.g. wrong format specified)
+ */
+ errno = EFAULT;
+ return (NULL);
+ }
+
+ pa = (caddr_t)node - DI_NODE(node)->self;
+ if (DI_NODE(node)->driver_data)
+ return (pa + DI_NODE(node)->driver_data);
+
+ if (DI_ALL(pa)->command & DINFOPRIVDATA)
+ errno = ENXIO;
+ else
+ errno = ENOTSUP;
+
+ return (NULL);
+}
+
+/*
+ * PROM property access
+ */
+
+/*
+ * openprom driver stuff:
+ * The maximum property length depends on the buffer size. We use
+ * OPROMMAXPARAM defined in <sys/openpromio.h>
+ *
+ * MAXNAMESZ is max property name. obpdefs.h defines it as 32 based on 1275
+ * MAXVALSZ is maximum value size, which is whatever space left in buf
+ */
+
+#define OBP_MAXBUF OPROMMAXPARAM - sizeof (int)
+#define OBP_MAXPROPLEN OBP_MAXBUF - OBP_MAXPROPNAME;
+
+struct di_prom_prop {
+ char *name;
+ int len;
+ uchar_t *data;
+ struct di_prom_prop *next; /* form a linked list */
+};
+
+struct di_prom_handle { /* handle to prom */
+ mutex_t lock; /* synchronize access to openprom fd */
+ int fd; /* /dev/openprom file descriptor */
+ struct di_prom_prop *list; /* linked list of prop */
+ union {
+ char buf[OPROMMAXPARAM];
+ struct openpromio opp;
+ } oppbuf;
+};
+
+di_prom_handle_t
+di_prom_init()
+{
+ struct di_prom_handle *p;
+
+ if ((p = malloc(sizeof (struct di_prom_handle))) == NULL)
+ return (DI_PROM_HANDLE_NIL);
+
+ DPRINTF((DI_INFO, "di_prom_init: get prom handle 0x%p\n", p));
+
+ (void) mutex_init(&p->lock, USYNC_THREAD, NULL);
+ if ((p->fd = open("/dev/openprom", O_RDONLY)) < 0) {
+ free(p);
+ return (DI_PROM_HANDLE_NIL);
+ }
+ p->list = NULL;
+
+ return ((di_prom_handle_t)p);
+}
+
+static void
+di_prom_prop_free(struct di_prom_prop *list)
+{
+ struct di_prom_prop *tmp = list;
+
+ while (tmp != NULL) {
+ list = tmp->next;
+ if (tmp->name != NULL) {
+ free(tmp->name);
+ }
+ if (tmp->data != NULL) {
+ free(tmp->data);
+ }
+ free(tmp);
+ tmp = list;
+ }
+}
+
+void
+di_prom_fini(di_prom_handle_t ph)
+{
+ struct di_prom_handle *p = (struct di_prom_handle *)ph;
+
+ DPRINTF((DI_INFO, "di_prom_fini: free prom handle 0x%p\n", p));
+
+ (void) close(p->fd);
+ (void) mutex_destroy(&p->lock);
+ di_prom_prop_free(p->list);
+
+ free(p);
+}
+
+/*
+ * Internal library interface for locating the property
+ * XXX: ph->lock must be held for the duration of call.
+ */
+static di_prom_prop_t
+di_prom_prop_found(di_prom_handle_t ph, int nodeid,
+ di_prom_prop_t prom_prop)
+{
+ struct di_prom_handle *p = (struct di_prom_handle *)ph;
+ struct openpromio *opp = &p->oppbuf.opp;
+ int *ip = (int *)((void *)opp->oprom_array);
+ struct di_prom_prop *prop = (struct di_prom_prop *)prom_prop;
+
+ DPRINTF((DI_TRACE1, "Looking for nodeid 0x%x\n", nodeid));
+
+ /*
+ * Set "current" nodeid in the openprom driver
+ */
+ opp->oprom_size = sizeof (int);
+ *ip = nodeid;
+ if (ioctl(p->fd, OPROMSETNODEID, opp) < 0) {
+ DPRINTF((DI_ERR, "*** Nodeid not found 0x%x\n", nodeid));
+ return (DI_PROM_PROP_NIL);
+ }
+
+ DPRINTF((DI_TRACE, "Found nodeid 0x%x\n", nodeid));
+
+ bzero(opp, OBP_MAXBUF);
+ opp->oprom_size = OBP_MAXPROPNAME;
+ if (prom_prop != DI_PROM_PROP_NIL)
+ (void) strcpy(opp->oprom_array, prop->name);
+
+ if ((ioctl(p->fd, OPROMNXTPROP, opp) < 0) || (opp->oprom_size == 0))
+ return (DI_PROM_PROP_NIL);
+
+ /*
+ * Prom property found. Allocate struct for storing prop
+ * (reuse variable prop)
+ */
+ if ((prop = malloc(sizeof (struct di_prom_prop))) == NULL)
+ return (DI_PROM_PROP_NIL);
+
+ /*
+ * Get a copy of property name
+ */
+ if ((prop->name = strdup(opp->oprom_array)) == NULL) {
+ free(prop);
+ return (DI_PROM_PROP_NIL);
+ }
+
+ /*
+ * get property value and length
+ */
+ opp->oprom_size = OBP_MAXPROPLEN;
+
+ if ((ioctl(p->fd, OPROMGETPROP, opp) < 0) ||
+ (opp->oprom_size == (uint_t)-1)) {
+ free(prop->name);
+ free(prop);
+ return (DI_PROM_PROP_NIL);
+ }
+
+ /*
+ * make a copy of the property value
+ */
+ prop->len = opp->oprom_size;
+
+ if (prop->len == 0)
+ prop->data = NULL;
+ else if ((prop->data = malloc(prop->len)) == NULL) {
+ free(prop->name);
+ free(prop);
+ return (DI_PROM_PROP_NIL);
+ }
+
+ bcopy(opp->oprom_array, prop->data, prop->len);
+
+ /*
+ * Prepend prop to list in prom handle
+ */
+ prop->next = p->list;
+ p->list = prop;
+
+ return ((di_prom_prop_t)prop);
+}
+
+di_prom_prop_t
+di_prom_prop_next(di_prom_handle_t ph, di_node_t node, di_prom_prop_t prom_prop)
+{
+ struct di_prom_handle *p = (struct di_prom_handle *)ph;
+
+ DPRINTF((DI_TRACE1, "Search next prop for node 0x%p with ph 0x%p\n",
+ node, p));
+
+ /*
+ * paranoid check
+ */
+ if ((ph == DI_PROM_HANDLE_NIL) || (node == DI_NODE_NIL)) {
+ errno = EINVAL;
+ return (DI_PROM_PROP_NIL);
+ }
+
+ if (di_nodeid(node) != DI_PROM_NODEID) {
+ errno = ENXIO;
+ return (DI_PROM_PROP_NIL);
+ }
+
+ /*
+ * synchronize access to prom file descriptor
+ */
+ (void) mutex_lock(&p->lock);
+
+ /*
+ * look for next property
+ */
+ prom_prop = di_prom_prop_found(ph, DI_NODE(node)->nodeid, prom_prop);
+
+ (void) mutex_unlock(&p->lock);
+
+ return (prom_prop);
+}
+
+char *
+di_prom_prop_name(di_prom_prop_t prom_prop)
+{
+ /*
+ * paranoid check
+ */
+ if (prom_prop == DI_PROM_PROP_NIL) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ return (((struct di_prom_prop *)prom_prop)->name);
+}
+
+int
+di_prom_prop_data(di_prom_prop_t prom_prop, uchar_t **prom_prop_data)
+{
+ /*
+ * paranoid check
+ */
+ if (prom_prop == DI_PROM_PROP_NIL) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ *prom_prop_data = ((struct di_prom_prop *)prom_prop)->data;
+
+ return (((struct di_prom_prop *)prom_prop)->len);
+}
+
+/*
+ * Internal library interface for locating the property
+ * Returns length if found, -1 if prop doesn't exist.
+ */
+static struct di_prom_prop *
+di_prom_prop_lookup_common(di_prom_handle_t ph, di_node_t node,
+ const char *prom_prop_name)
+{
+ struct openpromio *opp;
+ struct di_prom_prop *prop;
+ struct di_prom_handle *p = (struct di_prom_handle *)ph;
+
+ /*
+ * paranoid check
+ */
+ if ((ph == DI_PROM_HANDLE_NIL) || (node == DI_NODE_NIL)) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ if (di_nodeid(node) != DI_PROM_NODEID) {
+ errno = ENXIO;
+ return (NULL);
+ }
+
+ opp = &p->oppbuf.opp;
+
+ (void) mutex_lock(&p->lock);
+
+ opp->oprom_size = sizeof (int);
+ opp->oprom_node = DI_NODE(node)->nodeid;
+ if (ioctl(p->fd, OPROMSETNODEID, opp) < 0) {
+ errno = ENXIO;
+ DPRINTF((DI_ERR, "*** Nodeid not found 0x%x\n",
+ DI_NODE(node)->nodeid));
+ (void) mutex_unlock(&p->lock);
+ return (NULL);
+ }
+
+ /*
+ * get property length
+ */
+ bzero(opp, OBP_MAXBUF);
+ opp->oprom_size = OBP_MAXPROPLEN;
+ (void) strcpy(opp->oprom_array, prom_prop_name);
+
+ if ((ioctl(p->fd, OPROMGETPROPLEN, opp) < 0) ||
+ (opp->oprom_len == -1)) {
+ /* no such property */
+ (void) mutex_unlock(&p->lock);
+ return (NULL);
+ }
+
+ /*
+ * Prom property found. Allocate struct for storing prop
+ */
+ if ((prop = malloc(sizeof (struct di_prom_prop))) == NULL) {
+ (void) mutex_unlock(&p->lock);
+ return (NULL);
+ }
+ prop->name = NULL; /* we don't need the name */
+ prop->len = opp->oprom_len;
+
+ if (prop->len == 0) { /* boolean property */
+ prop->data = NULL;
+ prop->next = p->list;
+ p->list = prop;
+ (void) mutex_unlock(&p->lock);
+ return (prop);
+ }
+
+ /*
+ * retrieve the property value
+ */
+ bzero(opp, OBP_MAXBUF);
+ opp->oprom_size = OBP_MAXPROPLEN;
+ (void) strcpy(opp->oprom_array, prom_prop_name);
+
+ if ((ioctl(p->fd, OPROMGETPROP, opp) < 0) ||
+ (opp->oprom_size == (uint_t)-1)) {
+ /* error retrieving property value */
+ (void) mutex_unlock(&p->lock);
+ free(prop);
+ return (NULL);
+ }
+
+ /*
+ * make a copy of the property value, stick in ph->list
+ */
+ if ((prop->data = malloc(prop->len)) == NULL) {
+ (void) mutex_unlock(&p->lock);
+ free(prop);
+ return (NULL);
+ }
+
+ bcopy(opp->oprom_array, prop->data, prop->len);
+
+ prop->next = p->list;
+ p->list = prop;
+ (void) mutex_unlock(&p->lock);
+
+ return (prop);
+}
+
+int
+di_prom_prop_lookup_ints(di_prom_handle_t ph, di_node_t node,
+ const char *prom_prop_name, int **prom_prop_data)
+{
+ int len;
+ struct di_prom_prop *prop;
+
+ prop = di_prom_prop_lookup_common(ph, node, prom_prop_name);
+
+ if (prop == NULL) {
+ *prom_prop_data = NULL;
+ return (-1);
+ }
+
+ if (prop->len == 0) { /* boolean property */
+ *prom_prop_data = NULL;
+ return (0);
+ }
+
+ len = di_prop_decode_common((void *)&prop->data, prop->len,
+ DI_PROP_TYPE_INT, 1);
+ *prom_prop_data = (int *)((void *)prop->data);
+
+ return (len);
+}
+
+int
+di_prom_prop_lookup_strings(di_prom_handle_t ph, di_node_t node,
+ const char *prom_prop_name, char **prom_prop_data)
+{
+ int len;
+ struct di_prom_prop *prop;
+
+ prop = di_prom_prop_lookup_common(ph, node, prom_prop_name);
+
+ if (prop == NULL) {
+ *prom_prop_data = NULL;
+ return (-1);
+ }
+
+ if (prop->len == 0) { /* boolean property */
+ *prom_prop_data = NULL;
+ return (0);
+ }
+
+ /*
+ * Fix an openprom bug (OBP string not NULL terminated).
+ * XXX This should really be fixed in promif.
+ */
+ if (((char *)prop->data)[prop->len - 1] != '\0') {
+ uchar_t *tmp;
+ prop->len++;
+ if ((tmp = realloc(prop->data, prop->len)) == NULL)
+ return (-1);
+
+ prop->data = tmp;
+ ((char *)prop->data)[prop->len - 1] = '\0';
+ DPRINTF((DI_INFO, "OBP string not NULL terminated: "
+ "node=%s, prop=%s, val=%s\n",
+ di_node_name(node), prom_prop_name, prop->data));
+ }
+
+ len = di_prop_decode_common((void *)&prop->data, prop->len,
+ DI_PROP_TYPE_STRING, 1);
+ *prom_prop_data = (char *)prop->data;
+
+ return (len);
+}
+
+int
+di_prom_prop_lookup_bytes(di_prom_handle_t ph, di_node_t node,
+ const char *prom_prop_name, uchar_t **prom_prop_data)
+{
+ int len;
+ struct di_prom_prop *prop;
+
+ prop = di_prom_prop_lookup_common(ph, node, prom_prop_name);
+
+ if (prop == NULL) {
+ *prom_prop_data = NULL;
+ return (-1);
+ }
+
+ if (prop->len == 0) { /* boolean property */
+ *prom_prop_data = NULL;
+ return (0);
+ }
+
+ len = di_prop_decode_common((void *)&prop->data, prop->len,
+ DI_PROP_TYPE_BYTE, 1);
+ *prom_prop_data = prop->data;
+
+ return (len);
+}
+
+di_lnode_t
+di_link_to_lnode(di_link_t link, uint_t endpoint)
+{
+ struct di_all *di_all;
+
+ if ((link == DI_LINK_NIL) ||
+ ((endpoint != DI_LINK_SRC) && (endpoint != DI_LINK_TGT))) {
+ errno = EINVAL;
+ return (DI_LNODE_NIL);
+ }
+
+ di_all = DI_ALL((caddr_t)link - DI_LINK(link)->self);
+
+ if (endpoint == DI_LINK_SRC) {
+ return (DI_LNODE((caddr_t)di_all + DI_LINK(link)->src_lnode));
+ } else {
+ return (DI_LNODE((caddr_t)di_all + DI_LINK(link)->tgt_lnode));
+ }
+ /* NOTREACHED */
+}
+
+char *
+di_lnode_name(di_lnode_t lnode)
+{
+ return (di_driver_name(di_lnode_devinfo(lnode)));
+}
+
+di_node_t
+di_lnode_devinfo(di_lnode_t lnode)
+{
+ struct di_all *di_all;
+
+ di_all = DI_ALL((caddr_t)lnode - DI_LNODE(lnode)->self);
+ return (DI_NODE((caddr_t)di_all + DI_LNODE(lnode)->node));
+}
+
+int
+di_lnode_devt(di_lnode_t lnode, dev_t *devt)
+{
+ if ((lnode == DI_LNODE_NIL) || (devt == NULL)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ if ((DI_LNODE(lnode)->dev_major == (major_t)-1) &&
+ (DI_LNODE(lnode)->dev_minor == (minor_t)-1))
+ return (-1);
+
+ *devt = makedev(DI_LNODE(lnode)->dev_major, DI_LNODE(lnode)->dev_minor);
+ return (0);
+}
+
+int
+di_link_spectype(di_link_t link)
+{
+ return (DI_LINK(link)->spec_type);
+}
+
+void
+di_minor_private_set(di_minor_t minor, void *data)
+{
+ DI_MINOR(minor)->user_private_data = (uintptr_t)data;
+}
+
+void *
+di_minor_private_get(di_minor_t minor)
+{
+ return ((void *)DI_MINOR(minor)->user_private_data);
+}
+
+void
+di_node_private_set(di_node_t node, void *data)
+{
+ DI_NODE(node)->user_private_data = (uintptr_t)data;
+}
+
+void *
+di_node_private_get(di_node_t node)
+{
+ return ((void *)DI_NODE(node)->user_private_data);
+}
+
+void
+di_lnode_private_set(di_lnode_t lnode, void *data)
+{
+ DI_LNODE(lnode)->user_private_data = (uintptr_t)data;
+}
+
+void *
+di_lnode_private_get(di_lnode_t lnode)
+{
+ return ((void *)DI_LNODE(lnode)->user_private_data);
+}
+
+void
+di_link_private_set(di_link_t link, void *data)
+{
+ DI_LINK(link)->user_private_data = (uintptr_t)data;
+}
+
+void *
+di_link_private_get(di_link_t link)
+{
+ return ((void *)DI_LINK(link)->user_private_data);
+}
+
+di_lnode_t
+di_lnode_next(di_node_t node, di_lnode_t lnode)
+{
+ struct di_all *di_all;
+
+ /*
+ * paranoid error checking
+ */
+ if (node == DI_NODE_NIL) {
+ errno = EINVAL;
+ return (DI_LNODE_NIL);
+ }
+
+ di_all = DI_ALL((caddr_t)node - DI_NODE(node)->self);
+
+ if (lnode == DI_NODE_NIL) {
+ if (DI_NODE(node)->lnodes != NULL)
+ return (DI_LNODE((caddr_t)di_all +
+ DI_NODE(node)->lnodes));
+ } else {
+ if (DI_LNODE(lnode)->node_next != NULL)
+ return (DI_LNODE((caddr_t)di_all +
+ DI_LNODE(lnode)->node_next));
+ }
+
+ if (DINFOLYR & DI_ALL(di_all)->command)
+ errno = ENXIO;
+ else
+ errno = ENOTSUP;
+
+ return (DI_LNODE_NIL);
+}
+
+di_link_t
+di_link_next_by_node(di_node_t node, di_link_t link, uint_t endpoint)
+{
+ struct di_all *di_all;
+
+ /*
+ * paranoid error checking
+ */
+ if ((node == DI_NODE_NIL) ||
+ ((endpoint != DI_LINK_SRC) && (endpoint != DI_LINK_TGT))) {
+ errno = EINVAL;
+ return (DI_LINK_NIL);
+ }
+
+ di_all = DI_ALL((caddr_t)node - DI_NODE(node)->self);
+
+ if (endpoint == DI_LINK_SRC) {
+ if (link == DI_LINK_NIL) {
+ if (DI_NODE(node)->src_links != NULL)
+ return (DI_LINK((caddr_t)di_all +
+ DI_NODE(node)->src_links));
+ } else {
+ if (DI_LINK(link)->src_node_next != NULL)
+ return (DI_LINK((caddr_t)di_all +
+ DI_LINK(link)->src_node_next));
+ }
+ } else {
+ if (link == DI_LINK_NIL) {
+ if (DI_NODE(node)->tgt_links != NULL)
+ return (DI_LINK((caddr_t)di_all +
+ DI_NODE(node)->tgt_links));
+ } else {
+ if (DI_LINK(link)->tgt_node_next != NULL)
+ return (DI_LINK((caddr_t)di_all +
+ DI_LINK(link)->tgt_node_next));
+ }
+ }
+
+ if (DINFOLYR & DI_ALL(di_all)->command)
+ errno = ENXIO;
+ else
+ errno = ENOTSUP;
+
+ return (DI_LINK_NIL);
+}
+
+di_link_t
+di_link_next_by_lnode(di_lnode_t lnode, di_link_t link, uint_t endpoint)
+{
+ struct di_all *di_all;
+
+ /*
+ * paranoid error checking
+ */
+ if ((lnode == DI_LNODE_NIL) ||
+ ((endpoint != DI_LINK_SRC) && (endpoint != DI_LINK_TGT))) {
+ errno = EINVAL;
+ return (DI_LINK_NIL);
+ }
+
+ di_all = DI_ALL((caddr_t)lnode - DI_LNODE(lnode)->self);
+
+ if (endpoint == DI_LINK_SRC) {
+ if (link == DI_LINK_NIL) {
+ if (DI_LNODE(lnode)->link_out == NULL)
+ return (DI_LINK_NIL);
+ return (DI_LINK((caddr_t)di_all +
+ DI_LNODE(lnode)->link_out));
+ } else {
+ if (DI_LINK(link)->src_link_next == NULL)
+ return (DI_LINK_NIL);
+ return (DI_LINK((caddr_t)di_all +
+ DI_LINK(link)->src_link_next));
+ }
+ } else {
+ if (link == DI_LINK_NIL) {
+ if (DI_LNODE(lnode)->link_in == NULL)
+ return (DI_LINK_NIL);
+ return (DI_LINK((caddr_t)di_all +
+ DI_LNODE(lnode)->link_in));
+ } else {
+ if (DI_LINK(link)->tgt_link_next == NULL)
+ return (DI_LINK_NIL);
+ return (DI_LINK((caddr_t)di_all +
+ DI_LINK(link)->tgt_link_next));
+ }
+ }
+ /* NOTREACHED */
+}
+
+/*
+ * Internal library function:
+ * Invoke callback for each link data on the link list of first node
+ * on node_list headp, and place children of first node on the list.
+ *
+ * This is similar to walk_one_node, except we only walk in child
+ * first mode.
+ */
+static void
+walk_one_link(struct node_list **headp, uint_t ep,
+ void *arg, int (*callback)(di_link_t link, void *arg))
+{
+ int action = DI_WALK_CONTINUE;
+ di_link_t link = DI_LINK_NIL;
+ di_node_t node = (*headp)->node;
+
+ while ((link = di_link_next_by_node(node, link, ep)) != DI_LINK_NIL) {
+ action = callback(link, arg);
+ if (action == DI_WALK_TERMINATE) {
+ break;
+ }
+ }
+
+ update_node_list(action, DI_WALK_LINKGEN, headp);
+}
+
+int
+di_walk_link(di_node_t root, uint_t flag, uint_t endpoint, void *arg,
+ int (*link_callback)(di_link_t link, void *arg))
+{
+ struct node_list *head; /* node_list for tree walk */
+
+#ifdef DEBUG
+ char *path = di_devfs_path(root);
+ DPRINTF((DI_INFO, "walking %s link data under %s\n",
+ (endpoint == DI_LINK_SRC) ? "src" : "tgt", path));
+ di_devfs_path_free(path);
+#endif
+
+ /*
+ * paranoid error checking
+ */
+ if ((root == DI_NODE_NIL) || (link_callback == NULL) || (flag != 0) ||
+ ((endpoint != DI_LINK_SRC) && (endpoint != DI_LINK_TGT))) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ if ((head = malloc(sizeof (struct node_list))) == NULL) {
+ DPRINTF((DI_ERR, "malloc of node_list failed\n"));
+ return (-1);
+ }
+
+ head->next = NULL;
+ head->node = root;
+
+ DPRINTF((DI_INFO, "Start link data walking from node %s\n",
+ di_node_name(root)));
+
+ while (head != NULL)
+ walk_one_link(&head, endpoint, arg, link_callback);
+
+ return (0);
+}
+
+/*
+ * Internal library function:
+ * Invoke callback for each link data on the link list of first node
+ * on node_list headp, and place children of first node on the list.
+ *
+ * This is similar to walk_one_node, except we only walk in child
+ * first mode.
+ */
+static void
+walk_one_lnode(struct node_list **headp, void *arg,
+ int (*callback)(di_lnode_t lnode, void *arg))
+{
+ int action = DI_WALK_CONTINUE;
+ di_lnode_t lnode = DI_LNODE_NIL;
+ di_node_t node = (*headp)->node;
+
+ while ((lnode = di_lnode_next(node, lnode)) != DI_LNODE_NIL) {
+ action = callback(lnode, arg);
+ if (action == DI_WALK_TERMINATE) {
+ break;
+ }
+ }
+
+ update_node_list(action, DI_WALK_LINKGEN, headp);
+}
+
+int
+di_walk_lnode(di_node_t root, uint_t flag, void *arg,
+ int (*lnode_callback)(di_lnode_t lnode, void *arg))
+{
+ struct node_list *head; /* node_list for tree walk */
+
+#ifdef DEBUG
+ char *path = di_devfs_path(root);
+ DPRINTF((DI_INFO, "walking lnode data under %s\n", path));
+ di_devfs_path_free(path);
+#endif
+
+ /*
+ * paranoid error checking
+ */
+ if ((root == DI_NODE_NIL) || (lnode_callback == NULL) || (flag != 0)) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ if ((head = malloc(sizeof (struct node_list))) == NULL) {
+ DPRINTF((DI_ERR, "malloc of node_list failed\n"));
+ return (-1);
+ }
+
+ head->next = NULL;
+ head->node = root;
+
+ DPRINTF((DI_INFO, "Start lnode data walking from node %s\n",
+ di_node_name(root)));
+
+ while (head != NULL)
+ walk_one_lnode(&head, arg, lnode_callback);
+
+ return (0);
+}
+
+di_node_t
+di_lookup_node(di_node_t root, char *path)
+{
+ struct di_all *dap;
+ di_node_t node;
+ char copy[MAXPATHLEN];
+ char *slash, *pname, *paddr;
+
+ /*
+ * Path must be absolute and musn't have duplicate slashes
+ */
+ if (*path != '/' || strstr(path, "//")) {
+ DPRINTF((DI_ERR, "Invalid path: %s\n", path));
+ return (DI_NODE_NIL);
+ }
+
+ if (root == DI_NODE_NIL) {
+ DPRINTF((DI_ERR, "root node is DI_NODE_NIL\n"));
+ return (DI_NODE_NIL);
+ }
+
+ dap = DI_ALL((caddr_t)root - DI_NODE(root)->self);
+ if (strcmp(dap->root_path, "/") != 0) {
+ DPRINTF((DI_ERR, "snapshot root not / : %s\n", dap->root_path));
+ return (DI_NODE_NIL);
+ }
+
+ if (strlcpy(copy, path, sizeof (copy)) >= sizeof (copy)) {
+ DPRINTF((DI_ERR, "path too long: %s\n", path));
+ return (DI_NODE_NIL);
+ }
+
+ for (slash = copy, node = root; slash; ) {
+
+ /*
+ * Handle path = "/" case as well as trailing '/'
+ */
+ if (*(slash + 1) == '\0')
+ break;
+
+ /*
+ * More path-components exist. Deal with the next one
+ */
+ pname = slash + 1;
+ node = di_child_node(node);
+
+ if (slash = strchr(pname, '/'))
+ *slash = '\0';
+ if (paddr = strchr(pname, '@'))
+ *paddr++ = '\0';
+
+ for (; node != DI_NODE_NIL; node = di_sibling_node(node)) {
+ char *name, *baddr;
+
+ name = di_node_name(node);
+ baddr = di_bus_addr(node);
+
+ if (strcmp(pname, name) != 0)
+ continue;
+
+ /*
+ * Mappings between a "path-address" and bus-addr
+ *
+ * paddr baddr
+ * ---------------------
+ * NULL NULL
+ * NULL ""
+ * "" N/A (invalid paddr)
+ */
+ if (paddr && baddr && strcmp(paddr, baddr) == 0)
+ break;
+ if (paddr == NULL && (baddr == NULL || *baddr == '\0'))
+ break;
+ }
+
+ /*
+ * No nodes in the sibling list or there was no match
+ */
+ if (node == DI_NODE_NIL) {
+ DPRINTF((DI_ERR, "%s@%s: no node\n", pname, paddr));
+ return (DI_NODE_NIL);
+ }
+ }
+
+ assert(node != DI_NODE_NIL);
+ return (node);
+}
+
+static char *
+msglevel2str(di_debug_t msglevel)
+{
+ switch (msglevel) {
+ case DI_ERR:
+ return ("ERROR");
+ case DI_INFO:
+ return ("Info");
+ case DI_TRACE:
+ return ("Trace");
+ case DI_TRACE1:
+ return ("Trace1");
+ case DI_TRACE2:
+ return ("Trace2");
+ default:
+ return ("UNKNOWN");
+ }
+}
+
+void
+dprint(di_debug_t msglevel, const char *fmt, ...)
+{
+ va_list ap;
+ char *estr;
+
+ if (di_debug <= DI_QUIET)
+ return;
+
+ if (di_debug < msglevel)
+ return;
+
+ estr = msglevel2str(msglevel);
+
+ assert(estr);
+
+ va_start(ap, fmt);
+
+ (void) fprintf(stderr, "libdevinfo[%lu]: %s: ",
+ (ulong_t)getpid(), estr);
+ (void) vfprintf(stderr, fmt, ap);
+
+ va_end(ap);
+}
+
+/* end of devinfo.c */
diff --git a/usr/src/lib/libdevinfo/devinfo_devlink.c b/usr/src/lib/libdevinfo/devinfo_devlink.c
new file mode 100644
index 0000000000..f020e8f8a4
--- /dev/null
+++ b/usr/src/lib/libdevinfo/devinfo_devlink.c
@@ -0,0 +1,3618 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "devinfo_devlink.h"
+
+#undef DEBUG
+#ifndef DEBUG
+#define NDEBUG 1
+#else
+#undef NDEBUG
+#endif
+
+#include <assert.h>
+
+static mutex_t update_mutex = DEFAULTMUTEX; /* Protects update record lock */
+
+static const size_t elem_sizes[DB_TYPES] = {
+ sizeof (struct db_node),
+ sizeof (struct db_minor),
+ sizeof (struct db_link),
+ sizeof (char)
+};
+
+/*
+ * List of directories/files skipped while physically walking /dev
+ * Paths are relative to "<root>/dev/"
+ */
+static const char *skip_dirs[] = {"fd"};
+static const char *skip_files[] = {
+ "stdout",
+ "stdin",
+ "stderr"
+};
+
+#define N_SKIP_DIRS (sizeof (skip_dirs) / sizeof (skip_dirs[0]))
+#define N_SKIP_FILES (sizeof (skip_files) / sizeof (skip_files[0]))
+
+/*
+ *
+ * This file contains two sets of interfaces which operate on the reverse
+ * links database. One set (which includes di_devlink_open()/_close())
+ * allows link generators like devfsadm(1M) and ucblinks(1B) (writers) to
+ * populate the database with /devices -> /dev mappings. Another set
+ * of interfaces (which includes di_devlink_init()/_fini()) allows
+ * applications (readers) to lookup the database for /dev links corresponding
+ * to a given minor.
+ *
+ * Writers operate on a cached version of the database. The cache is created
+ * when di_devlink_open() is called. As links in /dev are created and removed,
+ * the cache is updated to keep it in synch with /dev. When the /dev updates
+ * are complete, the link generator calls di_devlink_close() which writes
+ * out the cache to the database.
+ *
+ * Applications which need to lookup the database, call di_devlink_init().
+ * di_devlink_init() checks the database file (if one exists). If the
+ * database is valid, it is mapped into the address space of the
+ * application. The database file consists of several segments. Each
+ * segment can be mapped in independently and is mapped on demand.
+ *
+ * Database Layout
+ *
+ * ---------------------
+ * | Magic # |
+ * | ----------------- |
+ * | Version | HEADER
+ * | ----------------- |
+ * | ... |
+ * ---------------------
+ * | |
+ * | | NODES
+ * | |
+ * | |
+ * ---------------------
+ * | |
+ * | | MINORS
+ * | |
+ * | |
+ * ---------------------
+ * | |
+ * | | LINKS
+ * | |
+ * | |
+ * ---------------------
+ * | |
+ * | | STRINGS
+ * | |
+ * | |
+ * ---------------------
+ *
+ * Readers can lookup /dev links for a specific minor or
+ * lookup all /dev links. In the latter case, the node
+ * and minor segments are not mapped in and the reader
+ * walks through every link in the link segment.
+ *
+ */
+
+di_devlink_handle_t
+di_devlink_open(const char *root_dir, uint_t flags)
+{
+ int err;
+ char path[PATH_MAX];
+ struct di_devlink_handle *hdp;
+ int retried = 0;
+
+retry:
+ /*
+ * Allocate a read-write handle but open the DB in readonly
+ * mode. We do writes only to a temporary copy of the database.
+ */
+ if ((hdp = handle_alloc(root_dir, OPEN_RDWR)) == NULL) {
+ return (NULL);
+ }
+
+ err = open_db(hdp, OPEN_RDONLY);
+
+ /*
+ * Unlink the database, so that consumers don't get
+ * out of date information as the database is being updated.
+ */
+ get_db_path(hdp, DB_FILE, path, sizeof (path));
+ (void) unlink(path);
+
+ /*
+ * The flags argument is reserved for future use.
+ */
+ if (flags != 0) {
+ handle_free(&hdp); /* also closes the DB */
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ if (cache_alloc(hdp) != 0) {
+ handle_free(&hdp);
+ return (NULL);
+ }
+
+ if (err) {
+ /*
+ * Failed to open DB.
+ * The most likely cause is that DB file did not exist.
+ * Call di_devlink_close() to recreate the DB file and
+ * retry di_devlink_open().
+ */
+ if (retried == 0) {
+ (void) di_devlink_close(&hdp, 0);
+ retried = 1;
+ goto retry;
+ }
+
+ /*
+ * DB cannot be opened, just return the
+ * handle. We will recreate the DB later.
+ */
+ return (hdp);
+ }
+
+ /* Read the database into the cache */
+ CACHE(hdp)->update_count = DB_HDR(hdp)->update_count;
+ (void) read_nodes(hdp, NULL, DB_HDR(hdp)->root_idx);
+ (void) read_links(hdp, NULL, DB_HDR(hdp)->dngl_idx);
+
+ (void) close_db(hdp);
+
+ return (hdp);
+}
+
+static void
+get_db_path(
+ struct di_devlink_handle *hdp,
+ const char *fname,
+ char *buf,
+ size_t blen)
+{
+ char *dir = NULL;
+
+#ifdef DEBUG
+ if (dir = getenv(ALT_DB_DIR)) {
+ (void) dprintf(DBG_INFO, "get_db_path: alternate db dir: %s\n",
+ dir);
+ }
+#endif
+ if (dir == NULL) {
+ dir = hdp->dev_dir;
+ }
+
+ (void) snprintf(buf, blen, "%s/%s", dir, fname);
+}
+
+static int
+open_db(struct di_devlink_handle *hdp, int flags)
+{
+ size_t sz;
+ long page_sz;
+ int fd, rv, flg;
+ struct stat sbuf;
+ uint32_t count[DB_TYPES] = {0};
+ char path[PATH_MAX];
+ void *cp;
+
+ assert(!DB_OPEN(hdp));
+
+#ifdef DEBUG
+ if (getenv(SKIP_DB)) {
+ (void) dprintf(DBG_INFO, "open_db: skipping database\n");
+ return (-1);
+ }
+#endif
+ if ((page_sz = sysconf(_SC_PAGE_SIZE)) == -1) {
+ return (-1);
+ }
+
+ /*
+ * Use O_TRUNC flag for write access, so that the subsequent ftruncate()
+ * call will zero-fill the entire file
+ */
+ if (IS_RDONLY(flags)) {
+ flg = O_RDONLY;
+ get_db_path(hdp, DB_FILE, path, sizeof (path));
+ } else {
+ flg = O_RDWR|O_CREAT|O_TRUNC;
+ get_db_path(hdp, DB_TMP, path, sizeof (path));
+ }
+
+ if ((fd = open(path, flg, DB_PERMS)) == -1) {
+ return (-1);
+ }
+
+ if (IS_RDONLY(flags)) {
+ flg = PROT_READ;
+ rv = fstat(fd, &sbuf);
+ sz = sbuf.st_size;
+ } else {
+ flg = PROT_READ | PROT_WRITE;
+ sz = size_db(hdp, page_sz, count);
+ rv = ftruncate(fd, sz);
+ }
+
+ if (rv == -1 || sz < HDR_LEN) {
+ if (rv != -1)
+ errno = EINVAL;
+ (void) close(fd);
+ return (-1);
+ }
+
+ cp = mmap(0, HDR_LEN, flg, MAP_SHARED, fd, 0);
+ if (cp == MAP_FAILED) {
+ (void) close(fd);
+ return (-1);
+ }
+ DB(hdp)->hdr = (struct db_hdr *)cp;
+ DB(hdp)->db_fd = fd;
+ DB(hdp)->flags = flags;
+
+ if (IS_RDONLY(flags)) {
+ rv = invalid_db(hdp, sz, page_sz);
+ } else {
+ rv = init_hdr(hdp, page_sz, count);
+ }
+
+ if (rv) {
+ (void) dprintf(DBG_ERR, "open_db: invalid DB(%s)\n", path);
+ (void) close_db(hdp);
+ return (-1);
+ } else {
+ (void) dprintf(DBG_STEP, "open_db: DB(%s): opened\n", path);
+ return (0);
+ }
+}
+
+/*
+ * A handle can be allocated for read-only or read-write access
+ */
+static struct di_devlink_handle *
+handle_alloc(const char *root_dir, uint_t flags)
+{
+ char dev_dir[PATH_MAX], path[PATH_MAX];
+ struct di_devlink_handle *hdp, proto = {0};
+
+ assert(flags == OPEN_RDWR || flags == OPEN_RDONLY);
+
+ dev_dir[0] = '\0';
+
+ /*
+ * NULL and the empty string are equivalent to "/"
+ */
+ if (root_dir && root_dir[0] != '\0') {
+
+ if (root_dir[0] != '/') {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+#ifdef DEBUG
+ /*LINTED*/
+ assert(sizeof (dev_dir) >= PATH_MAX);
+#endif
+ if (realpath(root_dir, dev_dir) == NULL) {
+ return (NULL);
+ }
+ }
+
+ if (strcmp(dev_dir, "/") == 0) {
+ (void) strlcpy(dev_dir, DEV, sizeof (dev_dir));
+ } else {
+ (void) strlcat(dev_dir, DEV, sizeof (dev_dir));
+ }
+
+ proto.dev_dir = dev_dir;
+ proto.flags = flags;
+ proto.lock_fd = -1;
+
+ /*
+ * Lock database if a read-write handle is being allocated.
+ * Locks are needed to protect against multiple writers.
+ * Readers don't need locks.
+ */
+ if (HDL_RDWR(&proto)) {
+ if (enter_update_lock(&proto) != 0) {
+ return (NULL);
+ }
+ }
+
+ DB(&proto)->db_fd = -1;
+
+ hdp = calloc(1, sizeof (struct di_devlink_handle));
+ if (hdp == NULL) {
+ goto error;
+ }
+
+ *hdp = proto;
+
+ /*
+ * The handle hdp now contains a pointer to local storage
+ * in the dev_dir field (obtained from the proto handle).
+ * In the following line, a dynamically allocated version
+ * is substituted.
+ */
+
+ if ((hdp->dev_dir = strdup(proto.dev_dir)) == NULL) {
+ free(hdp);
+ goto error;
+ }
+
+
+ return (hdp);
+
+error:
+ if (HDL_RDWR(&proto)) {
+ /* Unlink DB file on error */
+ get_db_path(&proto, DB_FILE, path, sizeof (path));
+ (void) unlink(path);
+ exit_update_lock(&proto);
+ }
+ return (NULL);
+}
+
+
+static int
+cache_alloc(struct di_devlink_handle *hdp)
+{
+ size_t hash_sz = 0;
+
+ assert(HDL_RDWR(hdp));
+
+ if (DB_OPEN(hdp)) {
+ hash_sz = DB_NUM(hdp, DB_LINK) / AVG_CHAIN_SIZE;
+ }
+ hash_sz = (hash_sz >= MIN_HASH_SIZE) ? hash_sz : MIN_HASH_SIZE;
+
+ CACHE(hdp)->hash = calloc(hash_sz, sizeof (cache_link_t *));
+ if (CACHE(hdp)->hash == NULL) {
+ return (-1);
+ }
+ CACHE(hdp)->hash_sz = hash_sz;
+
+ return (0);
+}
+
+
+static int
+invalid_db(struct di_devlink_handle *hdp, size_t fsize, long page_sz)
+{
+ int i;
+ char *cp;
+ size_t sz;
+
+ if (DB_HDR(hdp)->magic != DB_MAGIC || DB_HDR(hdp)->vers != DB_VERSION) {
+ return (1);
+ }
+
+ if (DB_HDR(hdp)->page_sz == 0 || DB_HDR(hdp)->page_sz != page_sz) {
+ return (1);
+ }
+
+ sz = seg_size(hdp, DB_HEADER);
+ for (i = 0; i < DB_TYPES; i++) {
+ (void) dprintf(DBG_INFO, "N[%u] = %u\n", i, DB_NUM(hdp, i));
+ /* There must be at least 1 element of each type */
+ if (DB_NUM(hdp, i) < 1) {
+ return (1);
+ }
+ sz += seg_size(hdp, i);
+ assert(sz % page_sz == 0);
+ }
+
+ if (sz != fsize) {
+ return (1);
+ }
+
+ if (!VALID_INDEX(hdp, DB_NODE, DB_HDR(hdp)->root_idx)) {
+ return (1);
+ }
+
+ if (!VALID_INDEX(hdp, DB_LINK, DB_HDR(hdp)->dngl_idx)) {
+ return (1);
+ }
+
+ if (DB_EMPTY(hdp)) {
+ return (1);
+ }
+
+ /*
+ * The last character in the string segment must be a NUL char.
+ */
+ cp = get_string(hdp, DB_NUM(hdp, DB_STR) - 1);
+ if (cp == NULL || *cp != '\0') {
+ return (1);
+ }
+
+ return (0);
+}
+
+static int
+read_nodes(struct di_devlink_handle *hdp, cache_node_t *pcnp, uint32_t nidx)
+{
+ char *path;
+ cache_node_t *cnp;
+ struct db_node *dnp;
+ const char *fcn = "read_nodes";
+
+ assert(HDL_RDWR(hdp));
+
+ /*
+ * parent node should be NULL only for the root node
+ */
+ if ((pcnp == NULL) ^ (nidx == DB_HDR(hdp)->root_idx)) {
+ (void) dprintf(DBG_ERR, "%s: invalid parent or index(%u)\n",
+ fcn, nidx);
+ SET_DB_ERR(hdp);
+ return (-1);
+ }
+
+ for (; dnp = get_node(hdp, nidx); nidx = dnp->sib) {
+
+ path = get_string(hdp, dnp->path);
+
+ /*
+ * Insert at head of list to recreate original order
+ */
+ cnp = node_insert(hdp, pcnp, path, INSERT_HEAD);
+ if (cnp == NULL) {
+ SET_DB_ERR(hdp);
+ break;
+ }
+
+ assert(strcmp(path, "/") ^ (nidx == DB_HDR(hdp)->root_idx));
+ assert(strcmp(path, "/") != 0 || dnp->sib == DB_NIL);
+
+ if (read_minors(hdp, cnp, dnp->minor) != 0 ||
+ read_nodes(hdp, cnp, dnp->child) != 0) {
+ break;
+ }
+
+ (void) dprintf(DBG_STEP, "%s: node[%u]: %s\n", fcn, nidx,
+ cnp->path);
+ }
+
+ return (dnp ? -1 : 0);
+}
+
+static int
+read_minors(struct di_devlink_handle *hdp, cache_node_t *pcnp, uint32_t nidx)
+{
+ cache_minor_t *cmnp;
+ struct db_minor *dmp;
+ char *name, *nodetype;
+ const char *fcn = "read_minors";
+
+ assert(HDL_RDWR(hdp));
+
+ if (pcnp == NULL) {
+ (void) dprintf(DBG_ERR, "%s: minor[%u]: orphan minor\n", fcn,
+ nidx);
+ SET_DB_ERR(hdp);
+ return (-1);
+ }
+
+ for (; dmp = get_minor(hdp, nidx); nidx = dmp->sib) {
+
+ name = get_string(hdp, dmp->name);
+ nodetype = get_string(hdp, dmp->nodetype);
+
+ cmnp = minor_insert(hdp, pcnp, name, nodetype, NULL);
+ if (cmnp == NULL) {
+ SET_DB_ERR(hdp);
+ break;
+ }
+
+ (void) dprintf(DBG_STEP, "%s: minor[%u]: %s\n", fcn, nidx,
+ cmnp->name);
+
+ if (read_links(hdp, cmnp, dmp->link) != 0) {
+ break;
+ }
+ }
+
+ return (dmp ? -1 : 0);
+}
+
+/*
+ * If the link is dangling the corresponding minor will be absent.
+ */
+static int
+read_links(struct di_devlink_handle *hdp, cache_minor_t *pcmp, uint32_t nidx)
+{
+ cache_link_t *clp;
+ struct db_link *dlp;
+ char *path, *content;
+
+ assert(HDL_RDWR(hdp));
+
+ if (nidx != DB_NIL &&
+ ((pcmp == NULL) ^ (nidx == DB_HDR(hdp)->dngl_idx))) {
+ (void) dprintf(DBG_ERR, "read_links: invalid minor or"
+ " index(%u)\n", nidx);
+ SET_DB_ERR(hdp);
+ return (-1);
+ }
+
+ for (; dlp = get_link(hdp, nidx); nidx = dlp->sib) {
+
+ path = get_string(hdp, dlp->path);
+ content = get_string(hdp, dlp->content);
+
+ clp = link_insert(hdp, pcmp, path, content, dlp->attr);
+ if (clp == NULL) {
+ SET_DB_ERR(hdp);
+ break;
+ }
+
+ (void) dprintf(DBG_STEP, "read_links: link[%u]: %s%s\n",
+ nidx, clp->path, pcmp == NULL ? "(DANGLING)" : "");
+ }
+
+ return (dlp ? -1 : 0);
+}
+
+int
+di_devlink_close(di_devlink_handle_t *pp, int flag)
+{
+ int i, rv;
+ char tmp[PATH_MAX];
+ char file[PATH_MAX];
+ uint32_t next[DB_TYPES] = {0};
+ struct di_devlink_handle *hdp;
+
+ if (pp == NULL || *pp == NULL || !HDL_RDWR(*pp)) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ hdp = *pp;
+ *pp = NULL;
+
+ /*
+ * The caller encountered some error in their processing.
+ * so handle isn't valid. Discard it and return success.
+ */
+ if (flag == DI_LINK_ERROR) {
+ handle_free(&hdp);
+ return (0);
+ }
+
+ if (DB_ERR(hdp)) {
+ handle_free(&hdp);
+ errno = EINVAL;
+ return (-1);
+ }
+
+ /*
+ * Extract the DB path before the handle is freed.
+ */
+ get_db_path(hdp, DB_FILE, file, sizeof (file));
+ get_db_path(hdp, DB_TMP, tmp, sizeof (tmp));
+
+ /*
+ * update database with actual contents of /dev
+ */
+ (void) dprintf(DBG_INFO, "di_devlink_close: update_count = %u\n",
+ CACHE(hdp)->update_count);
+
+ /*
+ * For performance reasons, synchronization of the database
+ * with /dev is turned off by default. However, applications
+ * with appropriate permissions can request a "sync" by
+ * calling di_devlink_update().
+ */
+ if (CACHE(hdp)->update_count == 0) {
+ CACHE(hdp)->update_count = 1;
+ (void) dprintf(DBG_INFO,
+ "di_devlink_close: synchronizing DB\n");
+ (void) synchronize_db(hdp);
+ }
+
+ /*
+ * Resolve dangling links AFTER synchronizing DB with /dev as the
+ * synchronization process may create dangling links.
+ */
+ resolve_dangling_links(hdp);
+
+ /*
+ * All changes to the cache are complete. Write out the cache
+ * to the database only if it is not empty.
+ */
+ if (CACHE_EMPTY(hdp)) {
+ (void) dprintf(DBG_INFO, "di_devlink_close: skipping write\n");
+ (void) unlink(file);
+ handle_free(&hdp);
+ return (0);
+ }
+
+ if (open_db(hdp, OPEN_RDWR) != 0) {
+ handle_free(&hdp);
+ return (-1);
+ }
+
+ /*
+ * Keep track of array assignments. There is atleast
+ * 1 element (the "NIL" element) per type.
+ */
+ for (i = 0; i < DB_TYPES; i++) {
+ next[i] = 1;
+ }
+
+ (void) write_nodes(hdp, NULL, CACHE_ROOT(hdp), next);
+ (void) write_links(hdp, NULL, CACHE(hdp)->dngl, next);
+ DB_HDR(hdp)->update_count = CACHE(hdp)->update_count;
+
+ rv = close_db(hdp);
+
+ if (rv != 0 || DB_ERR(hdp) || rename(tmp, file) != 0) {
+ (void) dprintf(DBG_ERR, "di_devlink_close: %s error: %s\n",
+ rv ? "close_db" : "DB or rename", strerror(errno));
+ (void) unlink(tmp);
+ (void) unlink(file);
+ handle_free(&hdp);
+ return (-1);
+ }
+
+ handle_free(&hdp);
+
+ (void) dprintf(DBG_INFO, "di_devlink_close: wrote DB(%s)\n", file);
+
+ return (0);
+}
+
+/*
+ * Inits the database header.
+ */
+static int
+init_hdr(struct di_devlink_handle *hdp, long page_sz, uint32_t *count)
+{
+ int i;
+
+ DB_HDR(hdp)->magic = DB_MAGIC;
+ DB_HDR(hdp)->vers = DB_VERSION;
+ DB_HDR(hdp)->root_idx = DB_NIL;
+ DB_HDR(hdp)->dngl_idx = DB_NIL;
+ DB_HDR(hdp)->page_sz = (uint32_t)page_sz;
+
+ for (i = 0; i < DB_TYPES; i++) {
+ assert(count[i] >= 1);
+ DB_NUM(hdp, i) = count[i];
+ }
+
+ return (0);
+}
+
+static int
+write_nodes(
+ struct di_devlink_handle *hdp,
+ struct db_node *pdnp,
+ cache_node_t *cnp,
+ uint32_t *next)
+{
+ uint32_t idx;
+ struct db_node *dnp;
+ const char *fcn = "write_nodes";
+
+ assert(HDL_RDWR(hdp));
+
+ for (; cnp != NULL; cnp = cnp->sib) {
+
+ assert(cnp->path != NULL);
+
+ /* parent node should only be NULL for root node */
+ if ((pdnp == NULL) ^ (cnp == CACHE_ROOT(hdp))) {
+ (void) dprintf(DBG_ERR, "%s: invalid parent for: %s\n",
+ fcn, cnp->path);
+ SET_DB_ERR(hdp);
+ break;
+ }
+
+ assert((strcmp(cnp->path, "/") != 0) ^
+ (cnp == CACHE_ROOT(hdp)));
+
+ idx = next[DB_NODE];
+ if ((dnp = set_node(hdp, idx)) == NULL) {
+ SET_DB_ERR(hdp);
+ break;
+ }
+
+ dnp->path = write_string(hdp, cnp->path, next);
+ if (dnp->path == DB_NIL) {
+ SET_DB_ERR(hdp);
+ break;
+ }
+ /* commit write for this node */
+ next[DB_NODE]++;
+
+ if (pdnp == NULL) {
+ assert(DB_HDR(hdp)->root_idx == DB_NIL);
+ DB_HDR(hdp)->root_idx = idx;
+ } else {
+ dnp->sib = pdnp->child;
+ pdnp->child = idx;
+ }
+
+ (void) dprintf(DBG_STEP, "%s: node[%u]: %s\n", fcn, idx,
+ cnp->path);
+
+ if (write_minors(hdp, dnp, cnp->minor, next) != 0 ||
+ write_nodes(hdp, dnp, cnp->child, next) != 0) {
+ break;
+ }
+ }
+
+ return (cnp ? -1 : 0);
+}
+
+static int
+write_minors(
+ struct di_devlink_handle *hdp,
+ struct db_node *pdnp,
+ cache_minor_t *cmnp,
+ uint32_t *next)
+{
+ uint32_t idx;
+ struct db_minor *dmp;
+ const char *fcn = "write_minors";
+
+ assert(HDL_RDWR(hdp));
+
+ if (pdnp == NULL) {
+ (void) dprintf(DBG_ERR, "%s: no node for minor: %s\n", fcn,
+ cmnp ? cmnp->name : "<NULL>");
+ SET_DB_ERR(hdp);
+ return (-1);
+ }
+
+ for (; cmnp != NULL; cmnp = cmnp->sib) {
+
+ assert(cmnp->name != NULL);
+
+ idx = next[DB_MINOR];
+ if ((dmp = set_minor(hdp, idx)) == NULL) {
+ SET_DB_ERR(hdp);
+ break;
+ }
+
+ dmp->name = write_string(hdp, cmnp->name, next);
+ dmp->nodetype = write_string(hdp, cmnp->nodetype, next);
+ if (dmp->name == DB_NIL || dmp->nodetype == DB_NIL) {
+ dmp->name = dmp->nodetype = DB_NIL;
+ SET_DB_ERR(hdp);
+ break;
+ }
+
+ /* Commit writes to this minor */
+ next[DB_MINOR]++;
+
+ dmp->sib = pdnp->minor;
+ pdnp->minor = idx;
+
+ (void) dprintf(DBG_STEP, "%s: minor[%u]: %s\n", fcn, idx,
+ cmnp->name);
+
+ if (write_links(hdp, dmp, cmnp->link, next) != 0) {
+ break;
+ }
+ }
+
+ return (cmnp ? -1 : 0);
+}
+
+static int
+write_links(
+ struct di_devlink_handle *hdp,
+ struct db_minor *pdmp,
+ cache_link_t *clp,
+ uint32_t *next)
+{
+ uint32_t idx;
+ struct db_link *dlp;
+ const char *fcn = "write_links";
+
+ assert(HDL_RDWR(hdp));
+
+ /* A NULL minor if and only if the links are dangling */
+ if (clp != NULL && ((pdmp == NULL) ^ (clp == CACHE(hdp)->dngl))) {
+ (void) dprintf(DBG_ERR, "%s: invalid minor for link\n", fcn);
+ SET_DB_ERR(hdp);
+ return (-1);
+ }
+
+ for (; clp != NULL; clp = clp->sib) {
+
+ assert(clp->path != NULL);
+
+ if ((pdmp == NULL) ^ (clp->minor == NULL)) {
+ (void) dprintf(DBG_ERR, "%s: invalid minor for link"
+ "(%s)\n", fcn, clp->path);
+ SET_DB_ERR(hdp);
+ break;
+ }
+
+ idx = next[DB_LINK];
+ if ((dlp = set_link(hdp, idx)) == NULL) {
+ SET_DB_ERR(hdp);
+ break;
+ }
+
+ dlp->path = write_string(hdp, clp->path, next);
+ dlp->content = write_string(hdp, clp->content, next);
+ if (dlp->path == DB_NIL || dlp->content == DB_NIL) {
+ dlp->path = dlp->content = DB_NIL;
+ SET_DB_ERR(hdp);
+ break;
+ }
+
+ dlp->attr = clp->attr;
+
+ /* Commit writes to this link */
+ next[DB_LINK]++;
+
+ if (pdmp != NULL) {
+ dlp->sib = pdmp->link;
+ pdmp->link = idx;
+ } else {
+ dlp->sib = DB_HDR(hdp)->dngl_idx;
+ DB_HDR(hdp)->dngl_idx = idx;
+ }
+
+ (void) dprintf(DBG_STEP, "%s: link[%u]: %s%s\n", fcn, idx,
+ clp->path, pdmp == NULL ? "(DANGLING)" : "");
+ }
+
+ return (clp ? -1 : 0);
+}
+
+
+static uint32_t
+write_string(struct di_devlink_handle *hdp, const char *str, uint32_t *next)
+{
+ char *dstr;
+ uint32_t idx;
+
+ assert(HDL_RDWR(hdp));
+
+ if (str == NULL) {
+ (void) dprintf(DBG_ERR, "write_string: NULL argument\n");
+ return (DB_NIL);
+ }
+
+ idx = next[DB_STR];
+ if (!VALID_STR(hdp, idx, str)) {
+ (void) dprintf(DBG_ERR, "write_string: invalid index[%u],"
+ " string(%s)\n", idx, str);
+ return (DB_NIL);
+ }
+
+ if ((dstr = set_string(hdp, idx)) == NULL) {
+ return (DB_NIL);
+ }
+
+ (void) strcpy(dstr, str);
+
+ next[DB_STR] += strlen(dstr) + 1;
+
+ return (idx);
+}
+
+static int
+close_db(struct di_devlink_handle *hdp)
+{
+ int i, rv = 0;
+ size_t sz;
+
+ if (!DB_OPEN(hdp)) {
+#ifdef DEBUG
+ assert(DB(hdp)->db_fd == -1);
+ assert(DB(hdp)->flags == 0);
+ for (i = 0; i < DB_TYPES; i++) {
+ assert(DB_SEG(hdp, i) == NULL);
+ assert(DB_SEG_PROT(hdp, i) == 0);
+ }
+#endif
+ return (0);
+ }
+
+ /* Unmap header after unmapping all other mapped segments */
+ for (i = 0; i < DB_TYPES; i++) {
+ if (DB_SEG(hdp, i)) {
+ sz = seg_size(hdp, i);
+ if (DB_RDWR(hdp))
+ rv += msync(DB_SEG(hdp, i), sz, MS_SYNC);
+ (void) munmap(DB_SEG(hdp, i), sz);
+ DB_SEG(hdp, i) = NULL;
+ DB_SEG_PROT(hdp, i) = 0;
+ }
+ }
+
+ if (DB_RDWR(hdp))
+ rv += msync((caddr_t)DB_HDR(hdp), HDR_LEN, MS_SYNC);
+ (void) munmap((caddr_t)DB_HDR(hdp), HDR_LEN);
+ DB(hdp)->hdr = NULL;
+
+ (void) close(DB(hdp)->db_fd);
+ DB(hdp)->db_fd = -1;
+ DB(hdp)->flags = 0;
+
+ return (rv ? -1 : 0);
+}
+
+
+static void
+cache_free(struct di_devlink_handle *hdp)
+{
+ cache_link_t *clp;
+
+ subtree_free(hdp, &(CACHE_ROOT(hdp)));
+ assert(CACHE_LAST(hdp) == NULL);
+
+ /*
+ * Don't bother removing links from hash table chains,
+ * as we are freeing the hash table itself.
+ */
+ while (CACHE(hdp)->dngl != NULL) {
+ clp = CACHE(hdp)->dngl;
+ CACHE(hdp)->dngl = clp->sib;
+ assert(clp->minor == NULL);
+ link_free(&clp);
+ }
+
+ assert((CACHE(hdp)->hash == NULL) ^ (CACHE(hdp)->hash_sz != 0));
+
+ free(CACHE(hdp)->hash);
+ CACHE(hdp)->hash = NULL;
+ CACHE(hdp)->hash_sz = 0;
+}
+
+static void
+handle_free(struct di_devlink_handle **pp)
+{
+ struct di_devlink_handle *hdp = *pp;
+
+ *pp = NULL;
+
+ if (hdp == NULL)
+ return;
+
+ (void) close_db(hdp);
+ cache_free(hdp);
+
+ if (HDL_RDWR(hdp))
+ exit_update_lock(hdp);
+ assert(hdp->lock_fd == -1);
+
+ free(hdp->dev_dir);
+ free(hdp);
+}
+
+/*
+ * Frees the tree rooted at a node. Siblings of the subtree root
+ * have to be handled by the caller.
+ */
+static void
+subtree_free(struct di_devlink_handle *hdp, cache_node_t **pp)
+{
+ cache_node_t *np;
+ cache_link_t *clp;
+ cache_minor_t *cmnp;
+
+ if (pp == NULL || *pp == NULL)
+ return;
+
+ while ((*pp)->child != NULL) {
+ np = (*pp)->child;
+ (*pp)->child = np->sib;
+ subtree_free(hdp, &np);
+ }
+
+ while ((*pp)->minor != NULL) {
+ cmnp = (*pp)->minor;
+ (*pp)->minor = cmnp->sib;
+
+ while (cmnp->link != NULL) {
+ clp = cmnp->link;
+ cmnp->link = clp->sib;
+ rm_link_from_hash(hdp, clp);
+ link_free(&clp);
+ }
+ minor_free(hdp, &cmnp);
+ }
+
+ node_free(pp);
+}
+
+static void
+rm_link_from_hash(struct di_devlink_handle *hdp, cache_link_t *clp)
+{
+ int hval;
+ cache_link_t **pp;
+
+ if (clp == NULL)
+ return;
+
+ if (clp->path == NULL)
+ return;
+
+ hval = hashfn(hdp, clp->path);
+ pp = &(CACHE_HASH(hdp, hval));
+ for (; *pp != NULL; pp = &(*pp)->hash) {
+ if (*pp == clp) {
+ *pp = clp->hash;
+ clp->hash = NULL;
+ return;
+ }
+ }
+
+ dprintf(DBG_ERR, "rm_link_from_hash: link(%s) not found\n", clp->path);
+}
+
+static cache_link_t *
+link_hash(di_devlink_handle_t hdp, const char *link, uint_t flags)
+{
+ int hval;
+ cache_link_t **pp, *clp;
+
+ if (link == NULL)
+ return (NULL);
+
+ hval = hashfn(hdp, link);
+ pp = &(CACHE_HASH(hdp, hval));
+ for (; (clp = *pp) != NULL; pp = &clp->hash) {
+ if (strcmp(clp->path, link) == 0) {
+ break;
+ }
+ }
+
+ if (clp == NULL)
+ return (NULL);
+
+ if ((flags & UNLINK_FROM_HASH) == UNLINK_FROM_HASH) {
+ *pp = clp->hash;
+ clp->hash = NULL;
+ }
+
+ return (clp);
+}
+
+static cache_minor_t *
+link2minor(struct di_devlink_handle *hdp, cache_link_t *clp)
+{
+ cache_link_t *plp;
+ const char *minor_path;
+ char *cp, buf[PATH_MAX], link[PATH_MAX];
+
+ if (TYPE_PRI(attr2type(clp->attr))) {
+ /*
+ * For primary link, content should point to a /devices node.
+ */
+ if (!is_minor_node(clp->content, &minor_path)) {
+ return (NULL);
+ }
+
+ return (lookup_minor(hdp, minor_path, NULL,
+ TYPE_CACHE|CREATE_FLAG));
+
+ }
+
+ /*
+ * If secondary, the primary link is derived from the secondary
+ * link contents. Secondary link contents can have two formats:
+ * audio -> /dev/sound/0
+ * fb0 -> fbs/afb0
+ */
+
+ buf[0] = '\0';
+ if (strncmp(clp->content, DEV"/", strlen(DEV"/")) == 0) {
+ cp = &clp->content[strlen(DEV"/")];
+ } else if (clp->content[0] != '/') {
+ if ((cp = strrchr(clp->path, '/')) != NULL) {
+ char savechar = *(cp + 1);
+ *(cp + 1) = '\0';
+ (void) snprintf(buf, sizeof (buf), "%s", clp->path);
+ *(cp + 1) = savechar;
+ }
+ (void) strlcat(buf, clp->content, sizeof (buf));
+ cp = buf;
+ } else {
+ goto follow_link;
+ }
+
+ /*
+ * Lookup the primary link if possible and find its minor.
+ */
+ if ((plp = link_hash(hdp, cp, 0)) != NULL && plp->minor != NULL) {
+ return (plp->minor);
+ }
+
+ /* realpath() used only as a last resort because it is expensive */
+follow_link:
+ (void) snprintf(link, sizeof (link), "%s/%s", hdp->dev_dir, clp->path);
+
+#ifdef DEBUG
+ /*LINTED*/
+ assert(sizeof (buf) >= PATH_MAX);
+#endif
+ if (realpath(link, buf) == NULL || !is_minor_node(buf, &minor_path)) {
+ return (NULL);
+ }
+ return (lookup_minor(hdp, minor_path, NULL, TYPE_CACHE|CREATE_FLAG));
+}
+
+
+static void
+resolve_dangling_links(struct di_devlink_handle *hdp)
+{
+ cache_minor_t *cmnp;
+ cache_link_t *clp, **pp;
+
+ for (pp = &(CACHE(hdp)->dngl); *pp != NULL; ) {
+ clp = *pp;
+ if ((cmnp = link2minor(hdp, clp)) != NULL) {
+ *pp = clp->sib;
+ clp->sib = cmnp->link;
+ cmnp->link = clp;
+ assert(clp->minor == NULL);
+ clp->minor = cmnp;
+ } else {
+ dprintf(DBG_INFO, "resolve_dangling_links: link(%s):"
+ " unresolved\n", clp->path);
+ pp = &clp->sib;
+ }
+ }
+}
+
+
+/*
+ * The elements are assumed to be detached from the cache tree.
+ */
+static void
+node_free(cache_node_t **pp)
+{
+ cache_node_t *cnp = *pp;
+
+ *pp = NULL;
+
+ if (cnp == NULL)
+ return;
+
+ free(cnp->path);
+ free(cnp);
+}
+
+static void
+minor_free(struct di_devlink_handle *hdp, cache_minor_t **pp)
+{
+ cache_minor_t *cmnp = *pp;
+
+ *pp = NULL;
+
+ if (cmnp == NULL)
+ return;
+
+ if (CACHE_LAST(hdp) == cmnp) {
+ dprintf(DBG_STEP, "minor_free: last_minor(%s)\n", cmnp->name);
+ CACHE_LAST(hdp) = NULL;
+ }
+
+ free(cmnp->name);
+ free(cmnp->nodetype);
+ free(cmnp);
+}
+
+static void
+link_free(cache_link_t **pp)
+{
+ cache_link_t *clp = *pp;
+
+ *pp = NULL;
+
+ if (clp == NULL)
+ return;
+
+ free(clp->path);
+ free(clp->content);
+ free(clp);
+}
+
+/*
+ * Returns the ':' preceding the minor name
+ */
+static char *
+minor_colon(const char *path)
+{
+ char *cp;
+
+ if ((cp = strrchr(path, '/')) == NULL) {
+ return (NULL);
+ }
+
+ return (strchr(cp, ':'));
+}
+
+static void *
+lookup_minor(
+ struct di_devlink_handle *hdp,
+ const char *minor_path,
+ const char *nodetype,
+ const int flags)
+{
+ void *vp;
+ char *colon;
+ char pdup[PATH_MAX];
+ const char *fcn = "lookup_minor";
+
+ if (minor_path == NULL) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ (void) snprintf(pdup, sizeof (pdup), "%s", minor_path);
+
+ if ((colon = minor_colon(pdup)) == NULL) {
+ (void) dprintf(DBG_ERR, "%s: invalid minor path(%s)\n", fcn,
+ minor_path);
+ errno = EINVAL;
+ return (NULL);
+ }
+ *colon = '\0';
+
+ if ((vp = get_last_minor(hdp, pdup, colon + 1, flags)) != NULL) {
+ return (vp);
+ }
+
+ if ((vp = lookup_node(hdp, pdup, flags)) == NULL) {
+ (void) dprintf(DBG_ERR, "%s: node(%s) not found\n", fcn, pdup);
+ return (NULL);
+ }
+ *colon = ':';
+
+ if (LOOKUP_CACHE(flags)) {
+ cache_minor_t **pp;
+
+ pp = &((cache_node_t *)vp)->minor;
+ for (; *pp != NULL; pp = &(*pp)->sib) {
+ if (strcmp((*pp)->name, colon + 1) == 0)
+ break;
+ }
+
+ if (*pp == NULL && CREATE_ELEM(flags)) {
+ *pp = minor_insert(hdp, vp, colon + 1, nodetype, pp);
+ }
+ set_last_minor(hdp, *pp, flags);
+
+ return (*pp);
+ } else {
+ char *cp;
+ uint32_t nidx;
+ struct db_minor *dmp;
+
+ nidx = (((struct db_node *)vp)->minor);
+ for (; dmp = get_minor(hdp, nidx); nidx = dmp->sib) {
+ cp = get_string(hdp, dmp->name);
+ if (cp && strcmp(cp, colon + 1) == 0)
+ break;
+ }
+ return (dmp);
+ }
+}
+
+static void *
+lookup_node(struct di_devlink_handle *hdp, char *path, const int flags)
+{
+ struct tnode tnd = {NULL};
+
+ if (tnd.node = get_last_node(hdp, path, flags))
+ return (tnd.node);
+
+ tnd.handle = hdp;
+ tnd.flags = flags;
+
+ if (walk_tree(path, &tnd, visit_node) != 0)
+ return (NULL);
+
+ return (tnd.node);
+}
+
+/*
+ * last_minor is used for nodes of TYPE_CACHE only.
+ */
+static void *
+get_last_node(struct di_devlink_handle *hdp, const char *path, int flags)
+{
+ cache_node_t *cnp;
+
+#ifdef DEBUG
+ if (getenv(SKIP_LAST_CACHE)) {
+ (void) dprintf(DBG_INFO, "get_last_node: SKIPPING \"last\" "
+ "node cache\n");
+ return (NULL);
+ }
+#endif
+
+ if (!LOOKUP_CACHE(flags) || CACHE_LAST(hdp) == NULL ||
+ CACHE_LAST(hdp)->node == NULL) {
+ return (NULL);
+ }
+
+ cnp = CACHE_LAST(hdp)->node;
+ if (strcmp(cnp->path, path) == 0) {
+ return (cnp);
+ }
+
+ cnp = cnp->sib;
+ if (cnp && strcmp(cnp->path, path) == 0) {
+ return (cnp);
+ }
+
+ return (NULL);
+}
+
+static void *
+get_last_minor(
+ struct di_devlink_handle *hdp,
+ const char *devfs_path,
+ const char *minor_name,
+ int flags)
+{
+ cache_minor_t *cmnp;
+
+#ifdef DEBUG
+ if (getenv(SKIP_LAST_CACHE)) {
+ (void) dprintf(DBG_INFO, "get_last_minor: SKIPPING \"last\" "
+ "minor cache\n");
+ return (NULL);
+ }
+#endif
+
+ if (!LOOKUP_CACHE(flags) || CACHE_LAST(hdp) == NULL) {
+ return (NULL);
+ }
+
+ cmnp = CACHE_LAST(hdp);
+ if (strcmp(cmnp->name, minor_name) == 0 && cmnp->node &&
+ strcmp(cmnp->node->path, devfs_path) == 0) {
+ return (cmnp);
+ }
+
+ cmnp = cmnp->sib;
+ if (cmnp && strcmp(cmnp->name, minor_name) == 0 && cmnp->node &&
+ strcmp(cmnp->node->path, devfs_path) == 0) {
+ set_last_minor(hdp, cmnp, TYPE_CACHE);
+ return (cmnp);
+ }
+
+ return (NULL);
+}
+
+static void
+set_last_minor(struct di_devlink_handle *hdp, cache_minor_t *cmnp, int flags)
+{
+#ifdef DEBUG
+ if (getenv(SKIP_LAST_CACHE)) {
+ (void) dprintf(DBG_INFO, "set_last_minor: SKIPPING \"last\" "
+ "minor cache\n");
+ return;
+ }
+#endif
+
+ if (LOOKUP_CACHE(flags) && cmnp) {
+ CACHE_LAST(hdp) = cmnp;
+ }
+}
+
+
+/*
+ * Returns 0 if normal return or -1 otherwise.
+ */
+static int
+walk_tree(
+ char *cur,
+ void *arg,
+ int (*node_callback)(const char *path, void *arg))
+{
+ char *slash, buf[PATH_MAX];
+
+ if (cur == NULL || cur[0] != '/' || strlen(cur) > sizeof (buf) - 1) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ (void) strcpy(buf, "/");
+
+ for (;;) {
+
+ if (node_callback(buf, arg) != DI_WALK_CONTINUE)
+ break;
+
+ while (*cur == '/')
+ cur++;
+
+ if (*cur == '\0')
+ break;
+
+ /*
+ * There is a next component(s). Append a "/" separator for all
+ * but the first (root) component.
+ */
+ if (buf[1] != '\0') {
+ (void) strlcat(buf, "/", sizeof (buf));
+ }
+
+ if (slash = strchr(cur, '/')) {
+ *slash = '\0';
+ (void) strlcat(buf, cur, sizeof (buf));
+ *slash = '/';
+ cur = slash;
+ } else {
+ (void) strlcat(buf, cur, sizeof (buf));
+ cur += strlen(cur);
+ }
+
+ }
+
+ return (0);
+}
+
+
+static int
+visit_node(const char *path, void *arg)
+{
+ struct tnode *tnp = arg;
+
+ if (LOOKUP_CACHE(tnp->flags)) {
+
+ cache_node_t *cnp = tnp->node;
+
+ cnp = (cnp) ? cnp->child : CACHE_ROOT(tnp->handle);
+
+ for (; cnp != NULL; cnp = cnp->sib) {
+ if (strcmp(cnp->path, path) == 0)
+ break;
+ }
+ if (cnp == NULL && CREATE_ELEM(tnp->flags)) {
+ cnp = node_insert(tnp->handle, tnp->node, path,
+ INSERT_TAIL);
+ }
+ tnp->node = cnp;
+ } else {
+ char *cp;
+ struct db_node *dnp = tnp->node;
+
+ dnp = (dnp) ? get_node(tnp->handle, dnp->child)
+ : get_node(tnp->handle, DB_HDR(tnp->handle)->root_idx);
+
+ for (; dnp != NULL; dnp = get_node(tnp->handle, dnp->sib)) {
+ cp = get_string(tnp->handle, dnp->path);
+ if (cp && strcmp(cp, path) == 0) {
+ break;
+ }
+ }
+ tnp->node = dnp;
+ }
+
+ /*
+ * Terminate walk if node is not found for a path component.
+ */
+ return (tnp->node ? DI_WALK_CONTINUE : DI_WALK_TERMINATE);
+}
+
+static void
+minor_delete(di_devlink_handle_t hdp, cache_minor_t *cmnp)
+{
+ cache_link_t **lpp;
+ cache_minor_t **mpp;
+ const char *fcn = "minor_delete";
+
+ (void) dprintf(DBG_STEP, "%s: removing minor: %s\n", fcn, cmnp->name);
+
+ /* detach minor from node */
+ if (cmnp->node != NULL) {
+ mpp = &cmnp->node->minor;
+ for (; *mpp != NULL; mpp = &(*mpp)->sib) {
+ if (*mpp == cmnp)
+ break;
+ }
+
+ if (*mpp == NULL) {
+ (void) dprintf(DBG_ERR, "%s: dangling minor: %s\n",
+ fcn, cmnp->name);
+ } else {
+ *mpp = cmnp->sib;
+ }
+ } else {
+ (void) dprintf(DBG_ERR, "%s: orphan minor(%s)\n", fcn,
+ cmnp->name);
+ }
+
+ delete_unused_nodes(hdp, cmnp->node);
+
+ cmnp->node = NULL;
+ cmnp->sib = NULL;
+
+ /* Move all remaining links to dangling list */
+ for (lpp = &cmnp->link; *lpp != NULL; lpp = &(*lpp)->sib) {
+ (*lpp)->minor = NULL;
+ }
+ *lpp = CACHE(hdp)->dngl;
+ CACHE(hdp)->dngl = cmnp->link;
+ cmnp->link = NULL;
+
+ minor_free(hdp, &cmnp);
+}
+
+static void
+delete_unused_nodes(di_devlink_handle_t hdp, cache_node_t *cnp)
+{
+ cache_node_t **npp;
+ const char *fcn = "delete_unused_nodes";
+
+ if (cnp == NULL)
+ return;
+
+ if (cnp->minor != NULL || cnp->child != NULL)
+ return;
+
+ (void) dprintf(DBG_INFO, "%s: removing unused node: %s\n", fcn,
+ cnp->path);
+
+ /* Unlink node from tree */
+ if (cnp->parent != NULL) {
+ npp = &cnp->parent->child;
+ for (; *npp != NULL; npp = &(*npp)->sib) {
+ if (*npp == cnp)
+ break;
+ }
+
+ if (*npp == NULL) {
+ (void) dprintf(DBG_ERR, "%s: dangling node: %s\n", fcn,
+ cnp->path);
+ } else {
+ *npp = cnp->sib;
+ }
+ } else if (cnp == CACHE_ROOT(hdp)) {
+ CACHE_ROOT(hdp) = NULL;
+ } else {
+ (void) dprintf(DBG_ERR, "%s: orphan node (%s)\n", fcn,
+ cnp->path);
+ }
+
+ delete_unused_nodes(hdp, cnp->parent);
+
+ cnp->parent = cnp->sib = NULL;
+
+ node_free(&cnp);
+}
+
+static int
+rm_link(di_devlink_handle_t hdp, const char *link)
+{
+ cache_link_t *clp;
+ const char *fcn = "rm_link";
+
+ if (hdp == NULL || DB_ERR(hdp) || link == NULL || link[0] == '/' ||
+ (!HDL_RDWR(hdp) && !HDL_RDONLY(hdp))) {
+ dprintf(DBG_ERR, "%s: %s: invalid args\n",
+ fcn, link ? link : "<NULL>");
+ errno = EINVAL;
+ return (-1);
+ }
+
+ dprintf(DBG_STEP, "%s: link(%s)\n", fcn, link);
+
+ if ((clp = link_hash(hdp, link, UNLINK_FROM_HASH)) == NULL) {
+ return (0);
+ }
+
+ link_delete(hdp, clp);
+
+ return (0);
+}
+
+int
+di_devlink_rm_link(di_devlink_handle_t hdp, const char *link)
+{
+ if (hdp == NULL || !HDL_RDWR(hdp)) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ return (rm_link(hdp, link));
+}
+
+static void
+link_delete(di_devlink_handle_t hdp, cache_link_t *clp)
+{
+ cache_link_t **pp;
+ const char *fcn = "link_delete";
+
+ (void) dprintf(DBG_STEP, "%s: removing link: %s\n", fcn, clp->path);
+
+ if (clp->minor == NULL)
+ pp = &(CACHE(hdp)->dngl);
+ else
+ pp = &clp->minor->link;
+
+ for (; *pp != NULL; pp = &(*pp)->sib) {
+ if (*pp == clp)
+ break;
+ }
+
+ if (*pp == NULL) {
+ (void) dprintf(DBG_ERR, "%s: link(%s) not on list\n",
+ fcn, clp->path);
+ } else {
+ *pp = clp->sib;
+ }
+
+ delete_unused_minor(hdp, clp->minor);
+
+ clp->minor = NULL;
+
+ link_free(&clp);
+}
+
+static void
+delete_unused_minor(di_devlink_handle_t hdp, cache_minor_t *cmnp)
+{
+ if (cmnp == NULL)
+ return;
+
+ if (cmnp->link != NULL)
+ return;
+
+ dprintf(DBG_STEP, "delete_unused_minor: removing minor(%s)\n",
+ cmnp->name);
+
+ minor_delete(hdp, cmnp);
+}
+
+int
+di_devlink_add_link(
+ di_devlink_handle_t hdp,
+ const char *link,
+ const char *content,
+ int flags)
+{
+ return (add_link(hdp, link, content, flags) != NULL ? 0 : -1);
+}
+
+static cache_link_t *
+add_link(
+ struct di_devlink_handle *hdp,
+ const char *link,
+ const char *content,
+ int flags)
+{
+ uint32_t attr;
+ cache_link_t *clp;
+ cache_minor_t *cmnp;
+ const char *fcn = "add_link";
+
+ if (hdp == NULL || DB_ERR(hdp) || link == NULL ||
+ link[0] == '/' || content == NULL || !link_flag(flags) ||
+ (!HDL_RDWR(hdp) && !HDL_RDONLY(hdp))) {
+ dprintf(DBG_ERR, "%s: %s: invalid args\n",
+ fcn, link ? link : "<NULL>");
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ if ((clp = link_hash(hdp, link, 0)) != NULL) {
+ if (link_cmp(clp, content, LINK_TYPE(flags)) != 0) {
+ (void) rm_link(hdp, link);
+ } else {
+ return (clp);
+ }
+ }
+
+ if (TYPE_PRI(flags)) {
+ const char *minor_path = NULL;
+
+ if (!is_minor_node(content, &minor_path)) {
+ (void) dprintf(DBG_ERR, "%s: invalid content(%s)"
+ " for primary link\n", fcn, content);
+ errno = EINVAL;
+ return (NULL);
+ }
+ if ((cmnp = lookup_minor(hdp, minor_path, NULL,
+ TYPE_CACHE|CREATE_FLAG)) == NULL) {
+ return (NULL);
+ }
+ attr = A_PRIMARY;
+ } else {
+ /*
+ * Defer resolving a secondary link to a minor until the
+ * database is closed. This ensures that the primary link
+ * (required for a successful resolve) has also been created.
+ */
+ cmnp = NULL;
+ attr = A_SECONDARY;
+ }
+
+ return (link_insert(hdp, cmnp, link, content, attr));
+}
+
+/*
+ * Returns 0 on match or 1 otherwise.
+ */
+static int
+link_cmp(cache_link_t *clp, const char *content, int type)
+{
+ if (strcmp(clp->content, content) != 0)
+ return (1);
+
+ if (attr2type(clp->attr) != type)
+ return (1);
+
+ return (0);
+}
+
+int
+di_devlink_update(di_devlink_handle_t hdp)
+{
+ if (hdp == NULL || !HDL_RDWR(hdp) || DB_ERR(hdp)) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ /*
+ * Reset the counter to schedule a synchronization with /dev on the next
+ * di_devlink_close().
+ */
+ CACHE(hdp)->update_count = 0;
+
+ return (0);
+}
+
+static int
+synchronize_db(di_devlink_handle_t hdp)
+{
+ int hval;
+ cache_link_t *clp;
+ char pdup[PATH_MAX];
+ recurse_t rec = {NULL};
+ const char *fcn = "synchronize_db";
+
+ rec.data = NULL;
+ rec.fcn = cache_dev_link;
+
+ /*
+ * Walk through $ROOT/dev, reading every link and marking the
+ * corresponding cached version as valid(adding new links as needed).
+ * Then walk through the cache and remove all unmarked links.
+ */
+ if (recurse_dev(hdp, &rec) != 0) {
+ return (-1);
+ }
+
+ for (hval = 0; hval < CACHE(hdp)->hash_sz; hval++) {
+ for (clp = CACHE_HASH(hdp, hval); clp != NULL; ) {
+ if (GET_VALID_ATTR(clp->attr)) {
+ CLR_VALID_ATTR(clp->attr);
+ clp = clp->hash;
+ continue;
+ }
+
+ /*
+ * The link is stale, so remove it. Since the link
+ * will be destroyed, use a copy of the link path to
+ * invoke the remove function.
+ */
+ (void) snprintf(pdup, sizeof (pdup), "%s", clp->path);
+ clp = clp->hash;
+ (void) dprintf(DBG_STEP, "%s: removing invalid link:"
+ " %s\n", fcn, pdup);
+ (void) di_devlink_rm_link(hdp, pdup);
+ }
+ }
+
+ (void) dprintf(DBG_STEP, "%s: update completed\n", fcn);
+
+ return (0);
+}
+
+static di_devlink_handle_t
+di_devlink_init_impl(const char *root, const char *name, uint_t flags)
+{
+ int err = 0;
+
+ if ((flags != 0 && flags != DI_MAKE_LINK) ||
+ (flags == 0 && name != NULL)) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ if (flags == DI_MAKE_LINK && (err = devlink_create(root, name))) {
+ errno = err;
+ return (NULL);
+ }
+
+ (void) dprintf(DBG_INFO, "devlink_init_impl: success\n");
+
+ return (devlink_snapshot(root));
+}
+
+di_devlink_handle_t
+di_devlink_init(const char *name, uint_t flags)
+{
+ return (di_devlink_init_impl("/", name, flags));
+}
+
+di_devlink_handle_t
+di_devlink_init_root(const char *root, const char *name, uint_t flags)
+{
+ return (di_devlink_init_impl(root, name, flags));
+}
+
+static di_devlink_handle_t
+devlink_snapshot(const char *root_dir)
+{
+ struct di_devlink_handle *hdp;
+
+ if ((hdp = handle_alloc(root_dir, OPEN_RDONLY)) == NULL) {
+ return (NULL);
+ }
+
+ /*
+ * If we cannot open the DB below, we will walk /dev
+ * in di_devlink_walk.
+ */
+ (void) open_db(hdp, OPEN_RDONLY);
+
+ return (hdp);
+}
+
+int
+di_devlink_fini(di_devlink_handle_t *pp)
+{
+ if (pp == NULL || *pp == NULL || !HDL_RDONLY(*pp)) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ /* Freeing the handle also closes the DB */
+ handle_free(pp);
+
+ return (0);
+}
+
+int
+di_devlink_walk(
+ di_devlink_handle_t hdp,
+ const char *re,
+ const char *minor_path,
+ uint_t flags,
+ void *arg,
+ int (*devlink_callback)(di_devlink_t, void *))
+{
+ int rv;
+ regex_t reg;
+ link_desc_t linkd = {NULL};
+
+ if (hdp == NULL || !HDL_RDONLY(hdp)) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ linkd.minor_path = minor_path;
+ linkd.flags = flags;
+ linkd.arg = arg;
+ linkd.fcn = devlink_callback;
+
+ if (re) {
+ if (regcomp(&reg, re, REG_EXTENDED) != 0)
+ return (-1);
+ linkd.regp = &reg;
+ }
+
+ if (check_args(&linkd)) {
+ errno = EINVAL;
+ rv = -1;
+ goto out;
+ }
+
+ if (DB_OPEN(hdp)) {
+ rv = walk_db(hdp, &linkd);
+ } else {
+ rv = walk_dev(hdp, &linkd);
+ }
+
+out:
+ if (re) {
+ regfree(&reg);
+ }
+
+ return (rv ? -1 : 0);
+}
+
+static int
+link_flag(uint_t flags)
+{
+ if (flags != 0 && flags != DI_PRIMARY_LINK &&
+ flags != DI_SECONDARY_LINK) {
+ return (0);
+ }
+
+ return (1);
+}
+
+/*
+ * Currently allowed flags are:
+ * DI_PRIMARY_LINK
+ * DI_SECONDARY_LINK
+ */
+static int
+check_args(link_desc_t *linkp)
+{
+ if (linkp->fcn == NULL)
+ return (-1);
+
+ if (!link_flag(linkp->flags)) {
+ return (-1);
+ }
+
+ /*
+ * Minor path can be NULL. In that case, all links will be
+ * selected.
+ */
+ if (linkp->minor_path) {
+ if (linkp->minor_path[0] != '/' ||
+ minor_colon(linkp->minor_path) == NULL) {
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+
+/*
+ * Walk all links in database if no minor path is specified.
+ */
+static int
+walk_db(struct di_devlink_handle *hdp, link_desc_t *linkp)
+{
+ assert(DB_OPEN(hdp));
+
+ if (linkp->minor_path == NULL) {
+ return (walk_all_links(hdp, linkp));
+ } else {
+ return (walk_matching_links(hdp, linkp));
+ }
+}
+
+static int
+cache_dev(struct di_devlink_handle *hdp)
+{
+ size_t sz;
+ recurse_t rec = {NULL};
+
+ assert(hdp);
+ assert(HDL_RDONLY(hdp));
+
+ if (hdp == NULL || !HDL_RDONLY(hdp)) {
+ dprintf(DBG_ERR, "cache_dev: invalid arg\n");
+ return (-1);
+ }
+
+ sz = MIN_HASH_SIZE;
+
+ CACHE(hdp)->hash = calloc(sz, sizeof (cache_link_t *));
+ if (CACHE(hdp)->hash == NULL) {
+ return (-1);
+ }
+ CACHE(hdp)->hash_sz = sz;
+
+ rec.data = NULL;
+ rec.fcn = cache_dev_link;
+
+ return (recurse_dev(hdp, &rec));
+}
+
+static int
+walk_dev(struct di_devlink_handle *hdp, link_desc_t *linkp)
+{
+ assert(hdp && linkp);
+ assert(!DB_OPEN(hdp));
+ assert(HDL_RDONLY(hdp));
+
+ if (hdp == NULL || !HDL_RDONLY(hdp) || DB_OPEN(hdp)) {
+ dprintf(DBG_ERR, "walk_dev: invalid args\n");
+ return (-1);
+ }
+
+ if (CACHE_EMPTY(hdp) && cache_dev(hdp) != 0) {
+ dprintf(DBG_ERR, "walk_dev: /dev caching failed\n");
+ return (-1);
+ }
+
+ if (linkp->minor_path)
+ walk_cache_minor(hdp, linkp->minor_path, linkp);
+ else
+ walk_all_cache(hdp, linkp);
+
+ return (linkp->retval);
+}
+
+/* ARGSUSED */
+static int
+cache_dev_link(struct di_devlink_handle *hdp, void *data, const char *link)
+{
+ int flags;
+ cache_link_t *clp;
+ char content[PATH_MAX];
+
+ assert(HDL_RDWR(hdp) || HDL_RDONLY(hdp));
+
+ if (s_readlink(link, content, sizeof (content)) < 0) {
+ return (DI_WALK_CONTINUE);
+ }
+
+ if (is_minor_node(content, NULL)) {
+ flags = DI_PRIMARY_LINK;
+ } else {
+ flags = DI_SECONDARY_LINK;
+ }
+
+ assert(strncmp(link, hdp->dev_dir, strlen(hdp->dev_dir)) == 0);
+
+ /*
+ * Store only the part after <root-dir>/dev/
+ */
+ link += strlen(hdp->dev_dir) + 1;
+
+ if ((clp = add_link(hdp, link, content, flags)) != NULL) {
+ SET_VALID_ATTR(clp->attr);
+ }
+
+ return (DI_WALK_CONTINUE);
+}
+
+
+static int
+walk_all_links(struct di_devlink_handle *hdp, link_desc_t *linkp)
+{
+ struct db_link *dlp;
+ uint32_t nidx, eidx;
+
+ assert(DB_NUM(hdp, DB_LINK) >= 1);
+
+ eidx = DB_NUM(hdp, DB_LINK);
+
+ /* Skip the "NIL" (index == 0) link. */
+ for (nidx = 1; nidx < eidx; nidx++) {
+ /*
+ * Declare this local to the block with zero
+ * initializer so that it gets rezeroed
+ * for each iteration.
+ */
+ struct di_devlink vlink = {NULL};
+
+ if ((dlp = get_link(hdp, nidx)) == NULL)
+ continue;
+
+ vlink.rel_path = get_string(hdp, dlp->path);
+ vlink.content = get_string(hdp, dlp->content);
+ vlink.type = attr2type(dlp->attr);
+
+ if (visit_link(hdp, linkp, &vlink) != DI_WALK_CONTINUE) {
+ break;
+ }
+ }
+
+ return (linkp->retval);
+}
+
+static int
+walk_matching_links(struct di_devlink_handle *hdp, link_desc_t *linkp)
+{
+ uint32_t nidx;
+ struct db_link *dlp;
+ struct db_minor *dmp;
+
+ assert(linkp->minor_path != NULL);
+
+ dmp = lookup_minor(hdp, linkp->minor_path, NULL, TYPE_DB);
+
+ /*
+ * If a minor matching the path exists, walk that minor's devlinks list.
+ * Then walk the dangling devlinks list. Non-matching devlinks will be
+ * filtered out in visit_link.
+ */
+ for (;;) {
+ nidx = dmp ? dmp->link : DB_HDR(hdp)->dngl_idx;
+ for (; dlp = get_link(hdp, nidx); nidx = dlp->sib) {
+ struct di_devlink vlink = {NULL};
+
+ vlink.rel_path = get_string(hdp, dlp->path);
+ vlink.content = get_string(hdp, dlp->content);
+ vlink.type = attr2type(dlp->attr);
+
+ if (visit_link(hdp, linkp, &vlink) != DI_WALK_CONTINUE)
+ goto out;
+ }
+ if (dmp == NULL) {
+ break;
+ } else {
+ dmp = NULL;
+ }
+ }
+
+out:
+ return (linkp->retval);
+}
+
+static int
+visit_link(
+ struct di_devlink_handle *hdp,
+ link_desc_t *linkp,
+ struct di_devlink *vlp)
+{
+ struct stat sbuf;
+ const char *minor_path = NULL;
+ char abs_path[PATH_MAX], cont[PATH_MAX];
+
+ /*
+ * It is legal for the link's content and type to be unknown.
+ * but one of absolute or relative path must be set.
+ */
+ if (vlp->rel_path == NULL && vlp->abs_path == NULL) {
+ (void) dprintf(DBG_ERR, "visit_link: invalid arguments\n");
+ return (DI_WALK_CONTINUE);
+ }
+
+ if (vlp->rel_path == NULL) {
+ vlp->rel_path = (char *)rel_path(hdp, vlp->abs_path);
+ if (vlp->rel_path == NULL || vlp->rel_path[0] == '\0')
+ return (DI_WALK_CONTINUE);
+ }
+
+ if (linkp->regp) {
+ if (regexec(linkp->regp, vlp->rel_path, 0, NULL, 0) != 0)
+ return (DI_WALK_CONTINUE);
+ }
+
+ if (vlp->abs_path == NULL) {
+ assert(vlp->rel_path[0] != '/');
+ (void) snprintf(abs_path, sizeof (abs_path), "%s/%s",
+ hdp->dev_dir, vlp->rel_path);
+ vlp->abs_path = abs_path;
+ }
+
+ if (vlp->content == NULL) {
+ if (s_readlink(vlp->abs_path, cont, sizeof (cont)) < 0) {
+ return (DI_WALK_CONTINUE);
+ }
+ vlp->content = cont;
+ }
+
+
+ if (vlp->type == 0) {
+ if (is_minor_node(vlp->content, &minor_path)) {
+ vlp->type = DI_PRIMARY_LINK;
+ } else {
+ vlp->type = DI_SECONDARY_LINK;
+ }
+ }
+
+ /*
+ * Filter based on minor path
+ */
+ if (linkp->minor_path) {
+ char tmp[PATH_MAX];
+
+ /*
+ * derive minor path
+ */
+ if (vlp->type == DI_SECONDARY_LINK) {
+
+#ifdef DEBUG
+ /*LINTED*/
+ assert(sizeof (tmp) >= PATH_MAX);
+#endif
+ if (realpath(vlp->abs_path, tmp) == NULL)
+ return (DI_WALK_CONTINUE);
+
+ if (!is_minor_node(tmp, &minor_path))
+ return (DI_WALK_CONTINUE);
+
+ } else if (minor_path == NULL) {
+ if (!is_minor_node(vlp->content, &minor_path))
+ return (DI_WALK_CONTINUE);
+ }
+
+ assert(minor_path != NULL);
+
+ if (strcmp(linkp->minor_path, minor_path) != 0)
+ return (DI_WALK_CONTINUE);
+ }
+
+ /*
+ * Filter based on link type
+ */
+ if (!TYPE_NONE(linkp->flags) && LINK_TYPE(linkp->flags) != vlp->type) {
+ return (DI_WALK_CONTINUE);
+ }
+
+ if (lstat(vlp->abs_path, &sbuf) < 0) {
+ dprintf(DBG_ERR, "visit_link: %s: lstat failed: %s\n",
+ vlp->abs_path, strerror(errno));
+ return (DI_WALK_CONTINUE);
+ }
+
+ return (linkp->fcn(vlp, linkp->arg));
+}
+
+static int
+devlink_valid(di_devlink_t devlink)
+{
+ if (devlink == NULL || devlink->rel_path == NULL ||
+ devlink->abs_path == NULL || devlink->content == NULL ||
+ TYPE_NONE(devlink->type)) {
+ return (0);
+ }
+
+ return (1);
+}
+
+const char *
+di_devlink_path(di_devlink_t devlink)
+{
+ if (!devlink_valid(devlink)) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ return (devlink->abs_path);
+}
+
+const char *
+di_devlink_content(di_devlink_t devlink)
+{
+ if (!devlink_valid(devlink)) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ return (devlink->content);
+}
+
+int
+di_devlink_type(di_devlink_t devlink)
+{
+ if (!devlink_valid(devlink)) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ return (devlink->type);
+}
+
+di_devlink_t
+di_devlink_dup(di_devlink_t devlink)
+{
+ struct di_devlink *duplink;
+
+ if (!devlink_valid(devlink)) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ if ((duplink = calloc(1, sizeof (struct di_devlink))) == NULL) {
+ return (NULL);
+ }
+
+ duplink->rel_path = strdup(devlink->rel_path);
+ duplink->abs_path = strdup(devlink->abs_path);
+ duplink->content = strdup(devlink->content);
+ duplink->type = devlink->type;
+
+ if (!devlink_valid(duplink)) {
+ (void) di_devlink_free(duplink);
+ errno = ENOMEM;
+ return (NULL);
+ }
+
+ return (duplink);
+}
+
+int
+di_devlink_free(di_devlink_t devlink)
+{
+ if (devlink == NULL) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ free(devlink->rel_path);
+ free(devlink->abs_path);
+ free(devlink->content);
+ free(devlink);
+
+ return (0);
+}
+
+/*
+ * Obtain path relative to dev_dir
+ */
+static const char *
+rel_path(struct di_devlink_handle *hdp, const char *path)
+{
+ const size_t len = strlen(hdp->dev_dir);
+
+ if (strncmp(path, hdp->dev_dir, len) != 0)
+ return (NULL);
+
+ if (path[len] == '\0')
+ return (&path[len]);
+
+ if (path[len] != '/')
+ return (NULL);
+
+ return (&path[len+1]);
+}
+
+static int
+recurse_dev(struct di_devlink_handle *hdp, recurse_t *rp)
+{
+ int ret = 0;
+
+ (void) do_recurse(hdp->dev_dir, hdp, rp, &ret);
+
+ return (ret);
+}
+
+static int
+do_recurse(
+ const char *dir,
+ struct di_devlink_handle *hdp,
+ recurse_t *rp,
+ int *retp)
+{
+ DIR *dp;
+ size_t len;
+ const char *rel;
+ struct stat sbuf;
+ char cur[PATH_MAX], *cp;
+ int i, rv = DI_WALK_CONTINUE;
+ struct dirent *entp, *result;
+
+
+ if ((rel = rel_path(hdp, dir)) == NULL)
+ return (DI_WALK_CONTINUE);
+
+ /*
+ * Skip directories we are not interested in.
+ */
+ for (i = 0; i < N_SKIP_DIRS; i++) {
+ if (strcmp(rel, skip_dirs[i]) == 0) {
+ (void) dprintf(DBG_STEP, "do_recurse: skipping %s\n",
+ dir);
+ return (DI_WALK_CONTINUE);
+ }
+ }
+
+ (void) dprintf(DBG_STEP, "do_recurse: dir = %s\n", dir);
+
+ if ((dp = opendir(dir)) == NULL)
+ return (DI_WALK_CONTINUE);
+
+ entp = malloc(sizeof (struct dirent) + PATH_MAX + 1);
+ if (entp == NULL) {
+ SET_DB_ERR(hdp);
+ (void) closedir(dp);
+ *retp = -1;
+ return (DI_WALK_TERMINATE);
+ }
+
+ (void) snprintf(cur, sizeof (cur), "%s/", dir);
+ len = strlen(cur);
+ cp = cur + len;
+ len = sizeof (cur) - len;
+
+ while (readdir_r(dp, entp, &result) == 0) {
+
+ if (result == NULL)
+ break;
+
+ if (strcmp(entp->d_name, ".") == 0 ||
+ strcmp(entp->d_name, "..") == 0) {
+ continue;
+ }
+
+ (void) snprintf(cp, len, "%s", entp->d_name);
+
+ /*
+ * Skip files we are not interested in.
+ */
+ for (i = 0; i < N_SKIP_FILES; i++) {
+
+ rel = rel_path(hdp, cur);
+ if (rel == NULL || strcmp(rel, skip_files[i]) == 0) {
+ (void) dprintf(DBG_STEP,
+ "do_recurse: skipping %s\n", cur);
+ goto next_entry;
+ }
+ }
+
+ if (lstat(cur, &sbuf) == 0) {
+ if (S_ISDIR(sbuf.st_mode)) {
+ rv = do_recurse(cur, hdp, rp, retp);
+ } else if (S_ISLNK(sbuf.st_mode)) {
+ rv = rp->fcn(hdp, rp->data, cur);
+ } else {
+ (void) dprintf(DBG_STEP,
+ "do_recurse: Skipping entry: %s\n", cur);
+ }
+ } else {
+ (void) dprintf(DBG_ERR, "do_recurse: cur(%s): lstat"
+ " failed: %s\n", cur, strerror(errno));
+ }
+
+next_entry:
+ *cp = '\0';
+
+ if (rv != DI_WALK_CONTINUE)
+ break;
+ }
+
+ free(entp);
+ (void) closedir(dp);
+
+ return (rv);
+}
+
+
+static int
+check_attr(uint32_t attr)
+{
+ switch (attr & A_LINK_TYPES) {
+ case A_PRIMARY:
+ case A_SECONDARY:
+ return (1);
+ default:
+ dprintf(DBG_ERR, "check_attr: incorrect attr(%u)\n",
+ attr);
+ return (0);
+ }
+}
+
+static int
+attr2type(uint32_t attr)
+{
+ switch (attr & A_LINK_TYPES) {
+ case A_PRIMARY:
+ return (DI_PRIMARY_LINK);
+ case A_SECONDARY:
+ return (DI_SECONDARY_LINK);
+ default:
+ dprintf(DBG_ERR, "attr2type: incorrect attr(%u)\n",
+ attr);
+ return (0);
+ }
+}
+
+/* Allocate new node and link it in */
+static cache_node_t *
+node_insert(
+ struct di_devlink_handle *hdp,
+ cache_node_t *pcnp,
+ const char *path,
+ int insert)
+{
+ cache_node_t *cnp;
+
+ if (path == NULL) {
+ errno = EINVAL;
+ SET_DB_ERR(hdp);
+ return (NULL);
+ }
+
+ if ((cnp = calloc(1, sizeof (cache_node_t))) == NULL) {
+ SET_DB_ERR(hdp);
+ return (NULL);
+ }
+
+ if ((cnp->path = strdup(path)) == NULL) {
+ SET_DB_ERR(hdp);
+ free(cnp);
+ return (NULL);
+ }
+
+ cnp->parent = pcnp;
+
+ if (pcnp == NULL) {
+ assert(strcmp(path, "/") == 0);
+ assert(CACHE(hdp)->root == NULL);
+ CACHE(hdp)->root = cnp;
+ } else if (insert == INSERT_HEAD) {
+ cnp->sib = pcnp->child;
+ pcnp->child = cnp;
+ } else if (CACHE_LAST(hdp) && CACHE_LAST(hdp)->node &&
+ CACHE_LAST(hdp)->node->parent == pcnp &&
+ CACHE_LAST(hdp)->node->sib == NULL) {
+
+ CACHE_LAST(hdp)->node->sib = cnp;
+
+ } else {
+ cache_node_t **pp;
+
+ for (pp = &pcnp->child; *pp != NULL; pp = &(*pp)->sib)
+ ;
+ *pp = cnp;
+ }
+
+ return (cnp);
+}
+
+/*
+ * Allocate a new minor and link it in either at the tail or head
+ * of the minor list depending on the value of "prev".
+ */
+static cache_minor_t *
+minor_insert(
+ struct di_devlink_handle *hdp,
+ cache_node_t *pcnp,
+ const char *name,
+ const char *nodetype,
+ cache_minor_t **prev)
+{
+ cache_minor_t *cmnp;
+
+ if (pcnp == NULL || name == NULL) {
+ errno = EINVAL;
+ SET_DB_ERR(hdp);
+ return (NULL);
+ }
+
+ /*
+ * Some pseudo drivers don't specify nodetype. Assume pseudo if
+ * nodetype is not specified.
+ */
+ if (nodetype == NULL)
+ nodetype = DDI_PSEUDO;
+
+ if ((cmnp = calloc(1, sizeof (cache_minor_t))) == NULL) {
+ SET_DB_ERR(hdp);
+ return (NULL);
+ }
+
+ cmnp->name = strdup(name);
+ cmnp->nodetype = strdup(nodetype);
+ if (cmnp->name == NULL || cmnp->nodetype == NULL) {
+ SET_DB_ERR(hdp);
+ free(cmnp->name);
+ free(cmnp->nodetype);
+ free(cmnp);
+ return (NULL);
+ }
+
+ cmnp->node = pcnp;
+
+ /* Add to node's minor list */
+ if (prev == NULL) {
+ cmnp->sib = pcnp->minor;
+ pcnp->minor = cmnp;
+ } else {
+ assert(*prev == NULL);
+ *prev = cmnp;
+ }
+
+ return (cmnp);
+}
+
+static cache_link_t *
+link_insert(
+ struct di_devlink_handle *hdp,
+ cache_minor_t *cmnp,
+ const char *path,
+ const char *content,
+ uint32_t attr)
+{
+ cache_link_t *clp;
+
+ if (path == NULL || content == NULL || !check_attr(attr)) {
+ errno = EINVAL;
+ SET_DB_ERR(hdp);
+ return (NULL);
+ }
+
+ if ((clp = calloc(1, sizeof (cache_link_t))) == NULL) {
+ SET_DB_ERR(hdp);
+ return (NULL);
+ }
+
+ clp->path = strdup(path);
+ clp->content = strdup(content);
+ if (clp->path == NULL || clp->content == NULL) {
+ SET_DB_ERR(hdp);
+ link_free(&clp);
+ return (NULL);
+ }
+
+ clp->attr = attr;
+ hash_insert(hdp, clp);
+ clp->minor = cmnp;
+
+ /* Add to minor's link list */
+ if (cmnp != NULL) {
+ clp->sib = cmnp->link;
+ cmnp->link = clp;
+ } else {
+ clp->sib = CACHE(hdp)->dngl;
+ CACHE(hdp)->dngl = clp;
+ }
+
+ return (clp);
+}
+
+static void
+hash_insert(struct di_devlink_handle *hdp, cache_link_t *clp)
+{
+ uint_t hval;
+
+ hval = hashfn(hdp, clp->path);
+ clp->hash = CACHE_HASH(hdp, hval);
+ CACHE_HASH(hdp, hval) = clp;
+}
+
+
+static struct db_node *
+get_node(struct di_devlink_handle *hdp, uint32_t idx)
+{
+ return (map_seg(hdp, idx, PROT_READ, DB_NODE));
+}
+
+static struct db_node *
+set_node(struct di_devlink_handle *hdp, uint32_t idx)
+{
+ return (map_seg(hdp, idx, PROT_READ | PROT_WRITE, DB_NODE));
+}
+
+static struct db_minor *
+get_minor(struct di_devlink_handle *hdp, uint32_t idx)
+{
+ return (map_seg(hdp, idx, PROT_READ, DB_MINOR));
+}
+
+static struct db_minor *
+set_minor(struct di_devlink_handle *hdp, uint32_t idx)
+{
+ return (map_seg(hdp, idx, PROT_READ | PROT_WRITE, DB_MINOR));
+}
+
+static struct db_link *
+get_link(struct di_devlink_handle *hdp, uint32_t idx)
+{
+ return (map_seg(hdp, idx, PROT_READ, DB_LINK));
+}
+
+static struct db_link *
+set_link(struct di_devlink_handle *hdp, uint32_t idx)
+{
+ return (map_seg(hdp, idx, PROT_READ | PROT_WRITE, DB_LINK));
+}
+
+static char *
+get_string(struct di_devlink_handle *hdp, uint32_t idx)
+{
+ return (map_seg(hdp, idx, PROT_READ, DB_STR));
+}
+
+static char *
+set_string(struct di_devlink_handle *hdp, uint32_t idx)
+{
+ return (map_seg(hdp, idx, PROT_READ | PROT_WRITE, DB_STR));
+}
+
+
+/*
+ * Returns the element corresponding to idx. If the portion of file involved
+ * is not yet mapped, does an mmap() as well. Existing mappings are not changed.
+ */
+static void *
+map_seg(
+ struct di_devlink_handle *hdp,
+ uint32_t idx,
+ int prot,
+ db_seg_t seg)
+{
+ int s;
+ off_t off;
+ size_t slen;
+ caddr_t addr;
+
+ if (idx == DB_NIL) {
+ return (NULL);
+ }
+
+ if (!VALID_INDEX(hdp, seg, idx)) {
+ (void) dprintf(DBG_ERR, "map_seg: seg(%d): invalid idx(%u)\n",
+ seg, idx);
+ return (NULL);
+ }
+
+ /*
+ * If the seg is already mapped in, use it if the access type is
+ * valid.
+ */
+ if (DB_SEG(hdp, seg) != NULL) {
+ if (DB_SEG_PROT(hdp, seg) != prot) {
+ (void) dprintf(DBG_ERR, "map_seg: illegal access: "
+ "seg[%d]: idx=%u, seg_prot=%d, access=%d\n",
+ seg, idx, DB_SEG_PROT(hdp, seg), prot);
+ return (NULL);
+ }
+ return (DB_SEG(hdp, seg) + idx * elem_sizes[seg]);
+ }
+
+ /*
+ * Segment is not mapped. Mmap() the segment.
+ */
+ off = seg_size(hdp, DB_HEADER);
+ for (s = 0; s < seg; s++) {
+ off += seg_size(hdp, s);
+ }
+ slen = seg_size(hdp, seg);
+
+ addr = mmap(0, slen, prot, MAP_SHARED, DB(hdp)->db_fd, off);
+ if (addr == MAP_FAILED) {
+ (void) dprintf(DBG_ERR, "map_seg: seg[%d]: mmap failed: %s\n",
+ seg, strerror(errno));
+ (void) dprintf(DBG_ERR, "map_seg: args: len=%lu, prot=%d,"
+ " fd=%d, off=%ld\n", (ulong_t)slen, prot, DB(hdp)->db_fd,
+ off);
+ return (NULL);
+ }
+
+ DB_SEG(hdp, seg) = addr;
+ DB_SEG_PROT(hdp, seg) = prot;
+
+ (void) dprintf(DBG_STEP, "map_seg: seg[%d]: len=%lu, prot=%d, fd=%d, "
+ "off=%ld, seg_base=%p\n", seg, (ulong_t)slen, prot, DB(hdp)->db_fd,
+ off, (void *)addr);
+
+ return (DB_SEG(hdp, seg) + idx * elem_sizes[seg]);
+}
+
+/*
+ * Computes the size of a segment rounded up to the nearest page boundary.
+ */
+static size_t
+seg_size(struct di_devlink_handle *hdp, int seg)
+{
+ size_t sz;
+
+ assert(DB_HDR(hdp)->page_sz);
+
+ if (seg == DB_HEADER) {
+ sz = HDR_LEN;
+ } else {
+ assert(DB_NUM(hdp, seg) >= 1);
+ sz = DB_NUM(hdp, seg) * elem_sizes[seg];
+ }
+
+ sz = (sz / DB_HDR(hdp)->page_sz) + 1;
+
+ sz *= DB_HDR(hdp)->page_sz;
+
+ return (sz);
+}
+
+static size_t
+size_db(struct di_devlink_handle *hdp, long page_sz, uint32_t *count)
+{
+ int i;
+ size_t sz;
+ cache_link_t *clp;
+
+ assert(page_sz > 0);
+
+ /* Take "NIL" element into account */
+ for (i = 0; i < DB_TYPES; i++) {
+ count[i] = 1;
+ }
+
+ count_node(CACHE(hdp)->root, count);
+
+ for (clp = CACHE(hdp)->dngl; clp != NULL; clp = clp->sib) {
+ count_link(clp, count);
+ }
+
+ sz = ((HDR_LEN / page_sz) + 1) * page_sz;
+ for (i = 0; i < DB_TYPES; i++) {
+ assert(count[i] >= 1);
+ sz += (((count[i] * elem_sizes[i]) / page_sz) + 1) * page_sz;
+ (void) dprintf(DBG_INFO, "N[%u]=%u\n", i, count[i]);
+ }
+ (void) dprintf(DBG_INFO, "DB size=%lu\n", (ulong_t)sz);
+
+ return (sz);
+}
+
+
+static void
+count_node(cache_node_t *cnp, uint32_t *count)
+{
+ cache_minor_t *cmnp;
+
+ if (cnp == NULL)
+ return;
+
+ count[DB_NODE]++;
+ count_string(cnp->path, count);
+
+ for (cmnp = cnp->minor; cmnp != NULL; cmnp = cmnp->sib) {
+ count_minor(cmnp, count);
+ }
+
+ for (cnp = cnp->child; cnp != NULL; cnp = cnp->sib) {
+ count_node(cnp, count);
+ }
+
+}
+
+static void
+count_minor(cache_minor_t *cmnp, uint32_t *count)
+{
+ cache_link_t *clp;
+
+ if (cmnp == NULL)
+ return;
+
+ count[DB_MINOR]++;
+ count_string(cmnp->name, count);
+ count_string(cmnp->nodetype, count);
+
+ for (clp = cmnp->link; clp != NULL; clp = clp->sib) {
+ count_link(clp, count);
+ }
+}
+
+static void
+count_link(cache_link_t *clp, uint32_t *count)
+{
+ if (clp == NULL)
+ return;
+
+ count[DB_LINK]++;
+ count_string(clp->path, count);
+ count_string(clp->content, count);
+}
+
+
+static void
+count_string(const char *str, uint32_t *count)
+{
+ if (str == NULL) {
+ (void) dprintf(DBG_ERR, "count_string: NULL argument\n");
+ return;
+ }
+
+ count[DB_STR] += strlen(str) + 1;
+}
+
+static uint_t
+hashfn(struct di_devlink_handle *hdp, const char *str)
+{
+ const char *cp;
+ ulong_t hval = 0;
+
+ if (str == NULL) {
+ return (0);
+ }
+
+ assert(CACHE(hdp)->hash_sz >= MIN_HASH_SIZE);
+
+ for (cp = str; *cp != '\0'; cp++) {
+ hval += *cp;
+ }
+
+ return (hval % CACHE(hdp)->hash_sz);
+}
+
+static int
+enter_update_lock(struct di_devlink_handle *hdp)
+{
+ int i, fd, rv;
+ struct flock lock;
+ char lockfile[PATH_MAX];
+
+ assert(hdp->lock_fd < 0);
+
+ get_db_path(hdp, DB_LOCK, lockfile, sizeof (lockfile));
+
+ /*
+ * Record locks are per-process. Protect against multiple threads.
+ */
+ (void) mutex_lock(&update_mutex);
+
+ if ((fd = open(lockfile, O_RDWR|O_CREAT, DB_LOCK_PERMS)) < 0) {
+ goto error;
+ }
+
+ lock.l_type = F_WRLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 0;
+
+ i = 1;
+ while ((rv = fcntl(fd, F_SETLKW, &lock)) == -1 && errno == EINTR) {
+ if (i < MAX_LOCK_RETRY) {
+ i++;
+ } else {
+ break;
+ }
+ }
+
+ if (rv == 0) {
+ hdp->lock_fd = fd;
+ return (0);
+ } else {
+ (void) close(fd);
+ }
+
+error:
+ (void) mutex_unlock(&update_mutex);
+
+ dprintf(DBG_ERR, "lockfile(%s): lock failed: %s\n", lockfile,
+ strerror(errno));
+ return (-1);
+}
+
+/*
+ * Close and re-open lock file every time so that it is recreated if deleted.
+ */
+static void
+exit_update_lock(struct di_devlink_handle *hdp)
+{
+ struct flock unlock;
+
+ if (hdp->lock_fd < 0) {
+ return;
+ }
+
+ unlock.l_type = F_UNLCK;
+ unlock.l_whence = SEEK_SET;
+ unlock.l_start = 0;
+ unlock.l_len = 0;
+
+ if (fcntl(hdp->lock_fd, F_SETLK, &unlock) == -1) {
+ dprintf(DBG_ERR, "update lockfile: unlock failed: %s\n",
+ strerror(errno));
+ }
+
+ (void) close(hdp->lock_fd);
+
+ hdp->lock_fd = -1;
+
+ (void) mutex_unlock(&update_mutex);
+}
+
+/*
+ * returns 1 if contents is a minor node in /devices.
+ * If mn_root is not NULL, mn_root is set to:
+ * if contents is a /dev node, mn_root = contents
+ * OR
+ * if contents is a /devices node, mn_root set to the '/'
+ * following /devices.
+ */
+int
+is_minor_node(const char *contents, const char **mn_root)
+{
+ char *ptr, *prefix;
+
+ prefix = "../devices/";
+
+ if ((ptr = strstr(contents, prefix)) != NULL) {
+
+ /* mn_root should point to the / following /devices */
+ if (mn_root != NULL) {
+ *mn_root = ptr += strlen(prefix) - 1;
+ }
+ return (1);
+ }
+
+ prefix = "/devices/";
+
+ if (strncmp(contents, prefix, strlen(prefix)) == 0) {
+
+ /* mn_root should point to the / following /devices/ */
+ if (mn_root != NULL) {
+ *mn_root = contents + strlen(prefix) - 1;
+ }
+ return (1);
+ }
+
+ if (mn_root != NULL) {
+ *mn_root = contents;
+ }
+ return (0);
+}
+
+static int
+s_readlink(const char *link, char *buf, size_t blen)
+{
+ int rv;
+
+ if ((rv = readlink(link, buf, blen)) == -1)
+ goto bad;
+
+ if (rv >= blen && buf[blen - 1] != '\0') {
+ errno = ENAMETOOLONG;
+ goto bad;
+ } else if (rv < blen) {
+ buf[rv] = '\0';
+ }
+
+ return (0);
+bad:
+ dprintf(DBG_ERR, "s_readlink: %s: failed: %s\n",
+ link, strerror(errno));
+ return (-1);
+}
+
+/*
+ * Synchronous link creation interface routines
+ * The scope of the operation is determined by the "name" arg.
+ * "name" can be NULL, a driver name or a devfs pathname (without /devices)
+ *
+ * "name" creates
+ * ====== =======
+ *
+ * NULL => All devlinks in system
+ * <driver> => devlinks for named driver
+ * /pci@1 => devlinks for subtree rooted at pci@1
+ * /pseudo/foo@0:X => devlinks for minor X
+ *
+ * devlink_create() returns 0 on success or an errno value on failure
+ */
+
+#define MAX_DAEMON_ATTEMPTS 2
+
+static int
+devlink_create(const char *root, const char *name)
+{
+ int i;
+ struct dca_off dca;
+
+ assert(root);
+
+ /*
+ * Convert name into arg for door_call
+ */
+ if (dca_init(name, &dca) != 0)
+ return (EINVAL);
+
+ /*
+ * Attempt to use the daemon first
+ */
+ i = 0;
+ do {
+ daemon_call(root, &dca);
+
+ dprintf(DBG_INFO, "daemon_call() retval=%d\n", dca.dca_error);
+
+ /*
+ * Retry only if door server isn't running
+ */
+ if (dca.dca_error != ENOENT && dca.dca_error != EBADF) {
+ return (dca.dca_error);
+ }
+
+ dca.dca_error = 0;
+
+ /*
+ * To improve performance defer this check until the first
+ * failure. Safe to defer as door server checks perms.
+ */
+ if (geteuid() != 0)
+ return (EPERM);
+ /*
+ * Daemon may not be running. Try to start it.
+ */
+ } while ((++i < MAX_DAEMON_ATTEMPTS) && start_daemon(root) == 0);
+
+ dprintf(DBG_INFO, "devlink_create: can't start daemon\n");
+
+ assert(dca.dca_error == 0);
+
+ /*
+ * If the daemon cannot be started execute the devfsadm command.
+ */
+ exec_cmd(root, &dca);
+
+ return (dca.dca_error);
+}
+
+/*
+ * The "name" member of "struct dca" contains data in the following order
+ * root'\0'minor'\0'driver'\0'
+ * The root component is always present at offset 0 in the "name" field.
+ * The driver and minor are optional. If present they have a non-zero
+ * offset in the "name" member.
+ */
+static int
+dca_init(const char *name, struct dca_off *dcp)
+{
+ char *cp;
+
+ dcp->dca_root = 0;
+ dcp->dca_minor = 0;
+ dcp->dca_driver = 0;
+ dcp->dca_error = 0;
+ dcp->dca_flags = 0;
+ dcp->dca_name[0] = '\0';
+
+ name = name ? name : "/";
+
+ /*
+ * Check if name is a driver name
+ */
+ if (*name != '/') {
+ (void) snprintf(dcp->dca_name, sizeof (dcp->dca_name),
+ "/ %s", name);
+ dcp->dca_root = 0;
+ *(dcp->dca_name + 1) = '\0';
+ dcp->dca_driver = 2;
+ return (0);
+ }
+
+ (void) snprintf(dcp->dca_name, sizeof (dcp->dca_name), "%s", name);
+
+ /*
+ * "/devices" not allowed in devfs pathname
+ */
+ if (is_minor_node(name, NULL))
+ return (-1);
+
+ dcp->dca_root = 0;
+ if (cp = strrchr(dcp->dca_name, ':')) {
+ *cp++ = '\0';
+ dcp->dca_minor = cp - dcp->dca_name;
+ }
+
+ return (0);
+}
+
+
+#define DAEMON_STARTUP_TIME 1 /* 1 second. This may need to be adjusted */
+
+static void
+daemon_call(const char *root, struct dca_off *dcp)
+{
+ door_arg_t arg;
+ int fd, door_error;
+ sigset_t oset, nset;
+ char synch_door[PATH_MAX];
+
+ (void) snprintf(synch_door, sizeof (synch_door),
+ "%s/dev/%s", root, DEVFSADM_SYNCH_DOOR);
+
+ if ((fd = open(synch_door, O_RDONLY)) == -1) {
+ dcp->dca_error = errno;
+ dprintf(DBG_ERR, "open of %s failed: %s\n",
+ synch_door, strerror(errno));
+ return;
+ }
+
+ arg.data_ptr = (char *)dcp;
+ arg.data_size = sizeof (*dcp);
+ arg.desc_ptr = NULL;
+ arg.desc_num = 0;
+ arg.rbuf = (char *)dcp;
+ arg.rsize = sizeof (*dcp);
+
+ /*
+ * Block signals to this thread until door call
+ * completes.
+ */
+ (void) sigfillset(&nset);
+ (void) sigemptyset(&oset);
+ (void) sigprocmask(SIG_SETMASK, &nset, &oset);
+ if (door_call(fd, &arg)) {
+ door_error = 1;
+ dcp->dca_error = errno;
+ }
+ (void) sigprocmask(SIG_SETMASK, &oset, NULL);
+
+ (void) close(fd);
+
+ if (door_error)
+ return;
+
+ assert(arg.data_ptr);
+
+ /*LINTED*/
+ dcp->dca_error = ((struct dca_off *)arg.data_ptr)->dca_error;
+
+ /*
+ * The doors interface may return data in a different buffer
+ * If that happens, deallocate buffer via munmap()
+ */
+ if (arg.rbuf != (char *)dcp)
+ (void) munmap(arg.rbuf, arg.rsize);
+}
+
+#define DEVFSADM_PATH "/usr/sbin/devfsadm"
+#define DEVFSADM "devfsadm"
+
+#define DEVFSADMD_PATH "/usr/lib/devfsadm/devfsadmd"
+#define DEVFSADM_DAEMON "devfsadmd"
+
+static int
+start_daemon(const char *root)
+{
+ int rv, i = 0;
+ char *argv[20];
+
+ argv[i++] = DEVFSADM_DAEMON;
+ if (strcmp(root, "/")) {
+ argv[i++] = "-r";
+ argv[i++] = (char *)root;
+ }
+ argv[i++] = NULL;
+
+ rv = do_exec(DEVFSADMD_PATH, argv);
+
+ (void) sleep(DAEMON_STARTUP_TIME);
+
+ return (rv);
+}
+
+static void
+exec_cmd(const char *root, struct dca_off *dcp)
+{
+ int i;
+ char *argv[20];
+
+ i = 0;
+ argv[i++] = DEVFSADM;
+
+ /*
+ * Load drivers only if -i is specified
+ */
+ if (dcp->dca_driver) {
+ argv[i++] = "-i";
+ argv[i++] = &dcp->dca_name[dcp->dca_driver];
+ } else {
+ argv[i++] = "-n";
+ }
+
+ if (root != NULL && strcmp(root, "/") != 0) {
+ argv[i++] = "-r";
+ argv[i++] = (char *)root;
+ }
+
+ argv[i] = NULL;
+
+ if (do_exec(DEVFSADM_PATH, argv))
+ dcp->dca_error = errno;
+}
+
+static int
+do_exec(const char *path, char *const argv[])
+{
+ int i;
+ pid_t cpid;
+
+#ifdef DEBUG
+ dprintf(DBG_INFO, "Executing %s\n\tArgument list:", path);
+ for (i = 0; argv[i] != NULL; i++) {
+ dprintf(DBG_INFO, " %s", argv[i]);
+ }
+ dprintf(DBG_INFO, "\n");
+#endif
+
+ if ((cpid = fork1()) == -1) {
+ dprintf(DBG_ERR, "fork1 failed: %s\n", strerror(errno));
+ return (-1);
+ }
+
+ if (cpid == 0) { /* child process */
+ int fd;
+
+ if ((fd = open("/dev/null", O_RDWR)) >= 0) {
+ (void) dup2(fd, fileno(stdout));
+ (void) dup2(fd, fileno(stderr));
+ (void) close(fd);
+
+ (void) execv(path, argv);
+ } else {
+ dprintf(DBG_ERR, "open of /dev/null failed: %s\n",
+ strerror(errno));
+ }
+
+ _exit(-1);
+ }
+
+ /* Parent process */
+ if (waitpid(cpid, &i, 0) == cpid) {
+ if (WIFEXITED(i)) {
+ if (WEXITSTATUS(i) == 0) {
+ dprintf(DBG_STEP,
+ "do_exec: child exited normally\n");
+ return (0);
+ } else
+ errno = EINVAL;
+ } else {
+ /*
+ * The child was interrupted by a signal
+ */
+ errno = EINTR;
+ }
+ dprintf(DBG_ERR, "child terminated abnormally: %s\n",
+ strerror(errno));
+ } else {
+ dprintf(DBG_ERR, "waitpid failed: %s\n", strerror(errno));
+ }
+
+ return (-1);
+}
+
+static int
+walk_cache_links(di_devlink_handle_t hdp, cache_link_t *clp, link_desc_t *linkp)
+{
+ int i;
+
+ assert(HDL_RDWR(hdp) || HDL_RDONLY(hdp));
+
+ dprintf(DBG_INFO, "walk_cache_links: initial link: %s\n",
+ clp ? clp->path : "<NULL>");
+
+ /*
+ * First search the links under the specified minor. On the
+ * 2nd pass, search the dangling list - secondary links may
+ * exist on this list since they are not resolved during the
+ * /dev walk.
+ */
+ for (i = 0; i < 2; i++) {
+ for (; clp != NULL; clp = clp->sib) {
+ struct di_devlink vlink = {NULL};
+
+ assert(clp->path[0] != '/');
+
+ vlink.rel_path = clp->path;
+ vlink.content = clp->content;
+ vlink.type = attr2type(clp->attr);
+
+ if (visit_link(hdp, linkp, &vlink)
+ != DI_WALK_CONTINUE) {
+ dprintf(DBG_INFO, "walk_cache_links: "
+ "terminating at link: %s\n", clp->path);
+ goto out;
+ }
+ }
+
+ clp = CACHE(hdp)->dngl;
+ }
+
+out:
+
+ /* If i < 2, we terminated the walk prematurely */
+ return (i < 2 ? DI_WALK_TERMINATE : DI_WALK_CONTINUE);
+}
+
+static void
+walk_all_cache(di_devlink_handle_t hdp, link_desc_t *linkp)
+{
+ int i;
+ cache_link_t *clp;
+
+ dprintf(DBG_INFO, "walk_all_cache: entered\n");
+
+ for (i = 0; i < CACHE(hdp)->hash_sz; i++) {
+ clp = CACHE_HASH(hdp, i);
+ for (; clp; clp = clp->hash) {
+ struct di_devlink vlink = {NULL};
+
+ assert(clp->path[0] != '/');
+
+ vlink.rel_path = clp->path;
+ vlink.content = clp->content;
+ vlink.type = attr2type(clp->attr);
+ if (visit_link(hdp, linkp, &vlink) !=
+ DI_WALK_CONTINUE) {
+ dprintf(DBG_INFO, "walk_all_cache: terminating "
+ "walk at link: %s\n", clp->path);
+ return;
+ }
+ }
+ }
+}
+
+static void
+walk_cache_minor(di_devlink_handle_t hdp, const char *mpath, link_desc_t *linkp)
+{
+ cache_minor_t *cmnp;
+
+ assert(mpath);
+
+ if ((cmnp = lookup_minor(hdp, mpath, NULL, TYPE_CACHE)) != NULL) {
+ (void) walk_cache_links(hdp, cmnp->link, linkp);
+ } else {
+ dprintf(DBG_ERR, "lookup minor failed: %s\n", mpath);
+ }
+}
+
+static void
+walk_cache_node(di_devlink_handle_t hdp, const char *path, link_desc_t *linkp)
+{
+ cache_minor_t *cmnp;
+ cache_node_t *cnp;
+
+ assert(path);
+
+ if ((cnp = lookup_node(hdp, (char *)path, TYPE_CACHE)) == NULL) {
+ dprintf(DBG_ERR, "lookup node failed: %s\n", path);
+ return;
+ }
+
+ for (cmnp = cnp->minor; cmnp != NULL; cmnp = cmnp->sib) {
+ if (walk_cache_links(hdp, cmnp->link, linkp)
+ == DI_WALK_TERMINATE)
+ break;
+ }
+}
+
+/*
+ * Private function
+ *
+ * Walk cached links corresponding to the given path.
+ *
+ * path path to a node or minor node.
+ *
+ * flags specifies the type of devlinks to be selected.
+ * If DI_PRIMARY_LINK is used, only primary links are selected.
+ * If DI_SECONDARY_LINK is specified, only secondary links
+ * are selected.
+ * If neither flag is specified, all devlinks are selected.
+ *
+ * re An extended regular expression in regex(5) format which
+ * selects the /dev links to be returned. The regular
+ * expression should use link pathnames relative to
+ * /dev. i.e. without the leading "/dev/" prefix.
+ * A NULL value matches all devlinks.
+ */
+int
+di_devlink_cache_walk(di_devlink_handle_t hdp,
+ const char *re,
+ const char *path,
+ uint_t flags,
+ void *arg,
+ int (*devlink_callback)(di_devlink_t, void *))
+{
+ regex_t reg;
+ link_desc_t linkd = {NULL};
+
+ if (hdp == NULL || path == NULL || !link_flag(flags) ||
+ !HDL_RDWR(hdp) || devlink_callback == NULL) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ linkd.flags = flags;
+ linkd.arg = arg;
+ linkd.fcn = devlink_callback;
+
+ if (re) {
+ if (regcomp(&reg, re, REG_EXTENDED) != 0)
+ return (-1);
+ linkd.regp = &reg;
+ }
+
+ if (minor_colon(path) == NULL) {
+ walk_cache_node(hdp, path, &linkd);
+ } else {
+ walk_cache_minor(hdp, path, &linkd);
+ }
+
+ if (re)
+ regfree(&reg);
+
+ return (0);
+}
+
+#define DEBUG_ENV_VAR "_DEVLINK_DEBUG"
+static int _devlink_debug = -1;
+
+/*
+ * debug level is initialized to -1.
+ * On first call into this routine, debug level is set.
+ * If debug level is zero, debugging msgs are disabled.
+ */
+static void
+debug_print(debug_level_t msglevel, const char *fmt, va_list ap)
+{
+ char *cp;
+ int save;
+
+ /*
+ * We shouldn't be here if debug is disabled
+ */
+ assert(_devlink_debug != 0);
+
+ /*
+ * Set debug level on first call into this routine
+ */
+ if (_devlink_debug < 0) {
+ if ((cp = getenv(DEBUG_ENV_VAR)) == NULL) {
+ _devlink_debug = 0;
+ return;
+ }
+
+ save = errno;
+ errno = 0;
+ _devlink_debug = strtol(cp, NULL, 10);
+ if (errno != 0 || _devlink_debug < 0) {
+ _devlink_debug = 0;
+ errno = save;
+ return;
+ }
+ errno = save;
+
+ if (!_devlink_debug)
+ return;
+ }
+
+ /* debug msgs are enabled */
+ assert(_devlink_debug > 0);
+
+ if (_devlink_debug < msglevel)
+ return;
+
+
+ /* Print a distinctive label for error msgs */
+ if (msglevel == DBG_ERR) {
+ (void) fprintf(stderr, "[ERROR]: ");
+ }
+
+ (void) vfprintf(stderr, fmt, ap);
+}
+
+/* ARGSUSED */
+/* PRINTFLIKE2 */
+void
+dprintf(debug_level_t msglevel, const char *fmt, ...)
+{
+ va_list ap;
+
+ assert(msglevel > 0);
+
+ if (!_devlink_debug)
+ return;
+
+ va_start(ap, fmt);
+ debug_print(msglevel, fmt, ap);
+ va_end(ap);
+}
diff --git a/usr/src/lib/libdevinfo/devinfo_devlink.h b/usr/src/lib/libdevinfo/devinfo_devlink.h
new file mode 100644
index 0000000000..0bee7f4c39
--- /dev/null
+++ b/usr/src/lib/libdevinfo/devinfo_devlink.h
@@ -0,0 +1,428 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+
+#ifndef _DEVINFO_DEVLINK_H
+#define _DEVINFO_DEVLINK_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define _POSIX_PTHREAD_SEMANTICS /* For readdir_r */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <thread.h>
+#include <synch.h>
+#include <libdevinfo.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <regex.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <sys/uio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <door.h>
+#include <signal.h>
+
+struct db_link {
+ uint32_t attr; /* primary or secondary */
+ uint32_t path; /* link path */
+ uint32_t content; /* link content */
+ uint32_t sib; /* next link for same minor */
+};
+
+struct db_minor {
+ uint32_t name; /* minor name */
+ uint32_t nodetype; /* minor node type */
+ uint32_t sib; /* next minor for same node */
+ uint32_t link; /* next minor for same node */
+};
+
+struct db_node {
+ uint32_t path; /* node path */
+ uint32_t sib; /* node's sibling */
+ uint32_t child; /* first child for this node */
+ uint32_t minor; /* first minor for node */
+};
+
+typedef enum db_seg {
+ DB_NODE = 0,
+ DB_MINOR,
+ DB_LINK,
+ DB_STR,
+ DB_TYPES, /* Number of non-header segments */
+ DB_HEADER
+} db_seg_t;
+
+struct db_hdr {
+ uint32_t magic; /* Magic number */
+ uint32_t vers; /* database format version */
+ uint32_t root_idx; /* index for root node */
+ uint32_t dngl_idx; /* head of DB dangling links */
+ uint32_t page_sz; /* page size for mmap alignment */
+ uint32_t update_count; /* updates since last /dev synch up */
+ uint32_t nelems[DB_TYPES]; /* Number of elements of each type */
+};
+
+
+typedef struct cache_link {
+ char *path; /* link path */
+ char *content; /* link content */
+ uint_t attr; /* link attributes */
+ struct cache_link *hash; /* next link on same hash chain */
+ struct cache_link *sib; /* next link for same minor */
+ struct cache_minor *minor; /* minor for this link */
+} cache_link_t;
+
+typedef struct cache_minor {
+ char *name; /* minor name */
+ char *nodetype; /* minor nodetype */
+ struct cache_node *node; /* node for this minor */
+ struct cache_minor *sib; /* next minor for same node */
+ struct cache_link *link; /* first link pointing to minor */
+} cache_minor_t;
+
+typedef struct cache_node {
+ char *path; /* path */
+ struct cache_node *parent; /* node's parent */
+ struct cache_node *sib; /* node's sibling */
+ struct cache_node *child; /* first child for this node */
+ struct cache_minor *minor; /* first minor for node */
+} cache_node_t;
+
+struct cache {
+ uint_t flags; /* cache state */
+ uint_t update_count; /* updates since /dev synchronization */
+ uint_t hash_sz; /* number of hash chains */
+ cache_link_t **hash; /* hash table */
+ cache_node_t *root; /* root of cache tree */
+ cache_link_t *dngl; /* list of dangling links */
+ cache_minor_t *last_minor; /* last minor looked up */
+};
+
+struct db {
+ int db_fd; /* database file */
+ uint_t flags; /* database open mode */
+ struct db_hdr *hdr; /* DB header */
+ int seg_prot[DB_TYPES]; /* protection for segments */
+ caddr_t seg_base[DB_TYPES]; /* base address for segments */
+};
+
+struct di_devlink_handle {
+ char *dev_dir; /* <root-dir>/dev */
+ uint_t flags; /* handle flags */
+ uint_t error; /* records errors encountered */
+ int lock_fd; /* lock file for updates */
+ struct cache cache;
+ struct db db;
+};
+
+typedef struct link_desc {
+ regex_t *regp;
+ const char *minor_path;
+ uint_t flags;
+ void *arg;
+ int (*fcn)(di_devlink_t, void *);
+ int retval;
+} link_desc_t;
+
+struct tnode {
+ void *node;
+ int flags;
+ struct di_devlink_handle *handle;
+};
+
+struct di_devlink {
+ char *rel_path;
+ char *abs_path;
+ char *content;
+ int type;
+};
+
+typedef struct recurse {
+ void *data;
+ int (*fcn)(struct di_devlink_handle *, void *, const char *);
+} recurse_t;
+
+/*
+ * Debug levels currently defined.
+ */
+typedef enum {
+ DBG_ERR = 1,
+ DBG_INFO,
+ DBG_STEP,
+ DBG_ALL
+} debug_level_t;
+
+
+#define DB_MAGIC 0xBAC2ACAB
+#define DB_FILE ".devlink_db"
+#define DB_TMP ".devlink_db_tmp"
+#define DB_LOCK ".devlink_db_lock"
+#define DB_PERMS (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR)
+#define DB_LOCK_PERMS DB_PERMS
+#define DB_VERSION 1
+
+#define DB_NIL 0
+
+#define DEV "/dev"
+#define DEVICES_SUFFIX "ices"
+
+#define HDR_LEN sizeof (struct db_hdr)
+
+#define AVG_CHAIN_SIZE 20 /* Average number of links per chain */
+#define MIN_HASH_SIZE 1024 /* Min number of chains in hash table */
+#define MAX_UPDATE_INTERVAL 5 /* Max DB writes before synching with /dev */
+#define MAX_LOCK_RETRY 5 /* Max attempts at locking the update lock */
+
+
+/*
+ * Various flags private to the implementation
+ */
+#define A_PRIMARY 0x0001U
+#define A_SECONDARY 0x0002U
+#define A_LINK_TYPES 0x0003U /* Mask */
+#define A_VALID 0x0004U
+
+#define TYPE_DB 0x0008U
+#define TYPE_CACHE 0x0010U
+#define CREATE_FLAG 0x0020U
+
+#define INSERT_HEAD 0x0040U
+#define INSERT_TAIL 0x0080U
+#define OPEN_RDWR 0x0100U
+#define OPEN_RDONLY 0x0200U
+#define OPEN_FLAGS 0x0300U /* Mask */
+#define UNLINK_FROM_HASH 0x0400U
+
+#define SET_VALID_ATTR(a) ((a) |= A_VALID)
+#define CLR_VALID_ATTR(a) ((a) &= ~A_VALID)
+#define GET_VALID_ATTR(a) ((a) & A_VALID)
+
+#define SET_DB_ERR(h) ((h)->error = 1)
+#define DB_ERR(h) ((h)->error)
+
+#define LOOKUP_DB(f) ((f) & TYPE_DB)
+#define LOOKUP_CACHE(f) ((f) & TYPE_CACHE)
+#define CREATE_ELEM(f) ((f) & CREATE_FLAG)
+
+#define IS_RDWR(f) (((f) & OPEN_FLAGS) == OPEN_RDWR)
+#define IS_RDONLY(f) (((f) & OPEN_FLAGS) == OPEN_RDONLY)
+
+#define HDL_RDWR(h) (((h)->flags & OPEN_FLAGS) == OPEN_RDWR)
+#define HDL_RDONLY(h) (((h)->flags & OPEN_FLAGS) == OPEN_RDONLY)
+
+#define CACHE(h) (&(h)->cache)
+#define CACHE_ROOT(h) (CACHE(h)->root)
+#define CACHE_HASH(h, i) (CACHE(h)->hash[i])
+#define CACHE_LAST(h) (CACHE(h)->last_minor)
+#define CACHE_EMPTY(h) (CACHE(h)->root == NULL && CACHE(h)->dngl == NULL)
+
+#define DB(h) (&(h)->db)
+#define DB_HDR(h) (DB(h)->hdr)
+#define DB_NUM(h, t) (DB_HDR(h)->nelems[t])
+#define DB_SEG(h, t) (DB(h)->seg_base[t])
+#define DB_SEG_PROT(h, t) (DB(h)->seg_prot[t])
+
+#define DB_OPEN(h) (DB_HDR(h) != NULL)
+#define DB_RDWR(h) ((DB(h)->flags & OPEN_FLAGS) == OPEN_RDWR)
+#define DB_RDONLY(h) ((DB(h)->flags & OPEN_FLAGS) == OPEN_RDONLY)
+
+#define DB_EMPTY(h) (DB_HDR(h)->root_idx == DB_NIL && \
+ DB_HDR(h)->dngl_idx == DB_NIL)
+
+#define TYPE_NONE(f) (((f) & DI_LINK_TYPES) == 0)
+#define TYPE_PRI(f) (((f) & DI_LINK_TYPES) == DI_PRIMARY_LINK)
+#define TYPE_SEC(f) (((f) & DI_LINK_TYPES) == DI_SECONDARY_LINK)
+#define LINK_TYPE(f) ((f) & DI_LINK_TYPES)
+#define VALID_TYPE(f) (TYPE_NONE(f) || TYPE_PRI(f) || TYPE_SEC(f))
+
+#define VALID_STR(h, i, s) ((i) + strlen(s) + 1 <= DB_HDR(h)->nelems[DB_STR])
+#define VALID_INDEX(h, t, i) ((i) < DB_HDR(h)->nelems[t])
+
+/*
+ * Environment variables used by DEBUG version of code.
+ */
+#define SKIP_DB "DEBUG_SKIP_DB"
+#define SKIP_LAST_CACHE "DEBUG_SKIP_LAST_CACHE"
+#define ALT_DB_DIR "DEBUG_ALT_DB_DIR"
+
+/*
+ * Function prototypes
+ */
+static struct di_devlink_handle *handle_alloc(const char *dev_dir,
+ uint_t flags);
+static int cache_alloc(struct di_devlink_handle *hdp);
+static int open_db(struct di_devlink_handle *hdp, int flags);
+static int invalid_db(struct di_devlink_handle *hdp, size_t fsize, long pg_sz);
+static int read_nodes(struct di_devlink_handle *hdp, cache_node_t *pcnp,
+ uint32_t nidx);
+static int read_minors(struct di_devlink_handle *hdp, cache_node_t *pcnp,
+ uint32_t nidx);
+static int read_links(struct di_devlink_handle *hdp, cache_minor_t *pcmp,
+ uint32_t nidx);
+static int init_hdr(struct di_devlink_handle *hdp, long page_sz,
+ uint32_t *count);
+static size_t size_db(struct di_devlink_handle *hdp, long page_sz,
+ uint32_t *count);
+static size_t seg_size(struct di_devlink_handle *hdp, int seg);
+
+static cache_node_t *node_insert(struct di_devlink_handle *hdp,
+ cache_node_t *pcnp, const char *path, int insert);
+static cache_minor_t *minor_insert(struct di_devlink_handle *hdp,
+ cache_node_t *pcnp, const char *name, const char *nodetype,
+ cache_minor_t **prev);
+static cache_link_t *link_insert(struct di_devlink_handle *hdp,
+ cache_minor_t *mnp, const char *path, const char *content, uint32_t attr);
+
+static void minor_delete(di_devlink_handle_t hdp, cache_minor_t *cmnp);
+static void link_delete(di_devlink_handle_t hdp, cache_link_t *clp);
+
+static int write_nodes(struct di_devlink_handle *hdp, struct db_node *pdnp,
+ cache_node_t *cnp, uint32_t *next);
+static int write_minors(struct di_devlink_handle *hdp, struct db_node *pdnp,
+ cache_minor_t *cmnp, uint32_t *next);
+static int write_links(struct di_devlink_handle *hdp, struct db_minor *pdmp,
+ cache_link_t *clp, uint32_t *next);
+static void rm_link_from_hash(struct di_devlink_handle *hdp, cache_link_t *clp);
+static uint32_t write_string(struct di_devlink_handle *hdp, const char *str,
+ uint32_t *next);
+static int close_db(struct di_devlink_handle *hdp);
+static void cache_free(struct di_devlink_handle *hdp);
+static void handle_free(struct di_devlink_handle **pp);
+static void resolve_dangling_links(struct di_devlink_handle *hdp);
+static void subtree_free(struct di_devlink_handle *hdp, cache_node_t **pp);
+static void node_free(cache_node_t **pp);
+static void minor_free(struct di_devlink_handle *hdp, cache_minor_t **pp);
+static void link_free(cache_link_t **pp);
+static void count_node(cache_node_t *cnp, uint32_t *count);
+static void count_minor(cache_minor_t *mnp, uint32_t *count);
+static void count_link(cache_link_t *clp, uint32_t *count);
+static void count_string(const char *str, uint32_t *count);
+static int visit_node(const char *path, void *arg);
+static int walk_tree(char *cur, void *arg,
+ int (*node_callback)(const char *path, void *arg));
+static void *lookup_node(struct di_devlink_handle *hdp, char *path,
+ const int flags);
+static cache_link_t *add_link(struct di_devlink_handle *hdp, const char *link,
+ const char *content, int primary);
+
+static void *lookup_minor(struct di_devlink_handle *hdp, const char *minor_path,
+ const char *nodetype, const int flags);
+static cache_link_t *link_hash(di_devlink_handle_t hdp, const char *link,
+ uint_t flags);
+
+static void hash_insert(struct di_devlink_handle *hdp, cache_link_t *clp);
+static uint_t hashfn(struct di_devlink_handle *hdp, const char *str);
+static void get_db_path(struct di_devlink_handle *hdp, const char *fname,
+ char *buf, size_t blen);
+
+static struct db_node *get_node(struct di_devlink_handle *hdp, uint32_t idx);
+static struct db_node *set_node(struct di_devlink_handle *hdp, uint32_t idx);
+
+static struct db_minor *get_minor(struct di_devlink_handle *hdp, uint32_t idx);
+static struct db_minor *set_minor(struct di_devlink_handle *hdp, uint32_t idx);
+
+static struct db_link *get_link(struct di_devlink_handle *hdp, uint32_t idx);
+static struct db_link *set_link(struct di_devlink_handle *hdp, uint32_t idx);
+
+static char *get_string(struct di_devlink_handle *hdp, uint32_t idx);
+static char *set_string(struct di_devlink_handle *hdp, uint32_t idx);
+
+static void *map_seg(struct di_devlink_handle *hdp, uint32_t idx, int prot,
+ db_seg_t seg);
+
+static int walk_db(struct di_devlink_handle *hdp, link_desc_t *linkp);
+static int walk_all_links(struct di_devlink_handle *hdp, link_desc_t *linkp);
+static int walk_matching_links(struct di_devlink_handle *hdp,
+ link_desc_t *linkp);
+static int visit_link(struct di_devlink_handle *hdp, link_desc_t *linkp,
+ struct di_devlink *vlp);
+
+static void walk_cache_minor(di_devlink_handle_t hdp, const char *mpath,
+ link_desc_t *linkp);
+static int walk_cache_links(di_devlink_handle_t hdp, cache_link_t *clp,
+ link_desc_t *linkp);
+static void walk_all_cache(di_devlink_handle_t hdp, link_desc_t *linkp);
+static int cache_dev_link(struct di_devlink_handle *hdp, void *data,
+ const char *link_path);
+
+static int walk_dev(struct di_devlink_handle *hdp, link_desc_t *linkp);
+static int recurse_dev(struct di_devlink_handle *hdp, recurse_t *rp);
+static int do_recurse(const char *dir, struct di_devlink_handle *hdp,
+ recurse_t *rp, int *retp);
+
+static int check_attr(uint32_t attr);
+static int attr2type(uint32_t attr);
+
+static int check_args(link_desc_t *linkp);
+
+static void *get_last_node(struct di_devlink_handle *hdp, const char *path,
+ int flags);
+static void *get_last_minor(struct di_devlink_handle *hdp,
+ const char *devfs_path, const char *minor_name, int flags);
+static void set_last_minor(struct di_devlink_handle *hdp, cache_minor_t *cmnp,
+ int flags);
+
+static int enter_update_lock(struct di_devlink_handle *hdp);
+static void exit_update_lock(struct di_devlink_handle *hdp);
+
+static char *minor_colon(const char *path);
+static const char *rel_path(struct di_devlink_handle *hdp, const char *path);
+static int link_flag(uint_t flags);
+static int s_readlink(const char *link, char *buf, size_t blen);
+static cache_minor_t *link2minor(struct di_devlink_handle *hdp,
+ cache_link_t *clp);
+static int link_cmp(cache_link_t *clp, const char *content, int type);
+static void delete_unused_nodes(di_devlink_handle_t hdp, cache_node_t *cnp);
+static void delete_unused_minor(di_devlink_handle_t hdp, cache_minor_t *cmnp);
+static int synchronize_db(di_devlink_handle_t hdp);
+static void dprintf(debug_level_t msglevel, const char *fmt, ...);
+static di_devlink_handle_t devlink_snapshot(const char *root_dir);
+static int devlink_create(const char *root, const char *name);
+static int dca_init(const char *name, struct dca_off *dcp);
+static void exec_cmd(const char *root, struct dca_off *dcp);
+static int do_exec(const char *path, char *const argv[]);
+static int start_daemon(const char *root);
+static void daemon_call(const char *root, struct dca_off *dcp);
+
+int is_minor_node(const char *contents, const char **mn_root);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _DEVINFO_DEVLINK_H */
diff --git a/usr/src/lib/libdevinfo/devinfo_devperm.c b/usr/src/lib/libdevinfo/devinfo_devperm.c
new file mode 100644
index 0000000000..df1f5dff3c
--- /dev/null
+++ b/usr/src/lib/libdevinfo/devinfo_devperm.c
@@ -0,0 +1,1027 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#define _POSIX_PTHREAD_SEMANTICS /* for readdir_r */
+#ifdef lint
+#define _REENTRANT /* for strtok_r */
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <alloca.h>
+#include <nss_dbdefs.h>
+#include <stdarg.h>
+#include <syslog.h>
+#include <sys/acl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/devinfo_impl.h>
+#include <sys/hwconf.h>
+#include <sys/modctl.h>
+#include <libnvpair.h>
+#include <device_info.h>
+#include <regex.h>
+#include <strings.h>
+#include <libdevinfo.h>
+
+extern int is_minor_node(const char *, const char **);
+
+static int logindevperm(const char *, uid_t, gid_t, void (*)());
+static int dir_dev_acc(char *, char *, uid_t, gid_t, mode_t, char *line,
+ void (*)());
+static int setdevaccess(char *, uid_t, gid_t, mode_t, void (*)());
+static void logerror(char *);
+
+#define MAX_LINELEN 256
+#define LOGINDEVPERM "/etc/logindevperm"
+#define DIRWILD "/*" /* directory wildcard */
+#define DIRWLDLEN 2 /* strlen(DIRWILD) */
+
+/*
+ * Revoke all access to a device node and make sure that there are
+ * no interposed streams devices attached. Must be called before a
+ * device is actually opened.
+ * When fdetach is called, the underlying device node is revealed; it
+ * will have the previous owner and that owner can re-attach; so we
+ * retry until we win.
+ * Ignore non-existent devices.
+ */
+static int
+setdevaccess(char *dev, uid_t uid, gid_t gid, mode_t mode,
+ void (*errmsg)(char *))
+{
+ int err = 0, local_errno;
+ aclent_t acls[4];
+ char errstring[MAX_LINELEN];
+
+ if (chown(dev, uid, gid) == -1) {
+ if (errno == ENOENT) /* no such file */
+ return (0);
+ err = -1;
+ }
+
+ while (fdetach(dev) == 0) {
+ if (chown(dev, uid, gid) == -1) {
+ err = -1;
+ local_errno = errno;
+ }
+ }
+ if (err && errmsg) {
+ (void) snprintf(errstring, MAX_LINELEN,
+ "failed to chown device %s: %s\n",
+ dev, strerror(local_errno));
+ (*errmsg)(errstring);
+ }
+
+ acls[0].a_type = USER_OBJ;
+ acls[0].a_id = uid;
+ acls[0].a_perm = ((mode & 0700) >> 6);
+
+ acls[1].a_type = GROUP_OBJ;
+ acls[1].a_id = gid;
+ acls[1].a_perm = ((mode & 0070) >> 3);
+
+ acls[2].a_type = CLASS_OBJ;
+ acls[2].a_id = (uid_t)-1;
+ acls[2].a_perm = ((mode & 0070) >> 3);
+
+ acls[3].a_type = OTHER_OBJ;
+ acls[3].a_id = (uid_t)-1;
+ acls[3].a_perm = (mode & 0007);
+
+ /* Remove ACLs */
+ if (acl(dev, SETACL, 4, acls) < 0) {
+ /*
+ * If the file system returned ENOSYS, we know that it
+ * doesn't support ACLs, therefore, we must assume that
+ * there were no ACLs to remove in the first place.
+ */
+ if (errno != ENOSYS) {
+ err = -1;
+
+ if (errmsg) {
+ (void) snprintf(errstring, MAX_LINELEN,
+ "failed to set acl on device %s: %s\n",
+ dev, strerror(errno));
+ (*errmsg)(errstring);
+ }
+ }
+ }
+
+ if (chmod(dev, mode) == -1) {
+ err = -1;
+ if (errmsg) {
+ (void) snprintf(errstring, MAX_LINELEN,
+ "failed to chmod device %s: %s\n",
+ dev, strerror(errno));
+ (*errmsg)(errstring);
+ }
+ }
+
+ return (err);
+}
+
+/*
+ * logindevperm - change owner/group/permissions of devices
+ * list in /etc/logindevperm.
+ */
+static int
+logindevperm(const char *ttyn, uid_t uid, gid_t gid, void (*errmsg)(char *))
+{
+ int err = 0, lineno = 0;
+ const char *field_delims = " \t\n";
+ char line[MAX_LINELEN], errstring[MAX_LINELEN];
+ char saveline[MAX_LINELEN];
+ char *console;
+ char *mode_str;
+ char *dev_list;
+ char *device;
+ char *ptr;
+ int mode;
+ FILE *fp;
+
+ if ((fp = fopen(LOGINDEVPERM, "r")) == NULL) {
+ if (errmsg) {
+ (void) snprintf(errstring, MAX_LINELEN,
+ LOGINDEVPERM ": open failed: %s\n",
+ strerror(errno));
+ (*errmsg)(errstring);
+ }
+ return (-1);
+ }
+
+ while (fgets(line, MAX_LINELEN, fp) != NULL) {
+ char *last;
+ lineno++;
+
+ if ((ptr = strchr(line, '#')) != NULL)
+ *ptr = '\0'; /* handle comments */
+
+ (void) strcpy(saveline, line);
+
+ console = strtok_r(line, field_delims, &last);
+ if (console == NULL)
+ continue; /* ignore blank lines */
+
+ if (strcmp(console, ttyn) != 0)
+ continue; /* not our tty, skip */
+
+ mode_str = strtok_r(last, field_delims, &last);
+ if (mode_str == NULL) {
+ err = -1; /* invalid entry, skip */
+ if (errmsg) {
+ (void) snprintf(errstring, MAX_LINELEN,
+ LOGINDEVPERM
+ ": line %d, invalid entry -- %s\n",
+ lineno, line);
+ (*errmsg)(errstring);
+ }
+ continue;
+ }
+
+ /* convert string to octal value */
+ mode = strtol(mode_str, &ptr, 8);
+ if (mode < 0 || mode > 0777 || *ptr != '\0') {
+ err = -1; /* invalid mode, skip */
+ if (errmsg) {
+ (void) snprintf(errstring, MAX_LINELEN,
+ LOGINDEVPERM
+ ": line %d, invalid mode -- %s\n",
+ lineno, mode_str);
+ (*errmsg)(errstring);
+ }
+ continue;
+ }
+
+ dev_list = strtok_r(last, field_delims, &last);
+ if (dev_list == NULL) {
+ err = -1; /* empty device list, skip */
+ if (errmsg) {
+ (void) snprintf(errstring, MAX_LINELEN,
+ LOGINDEVPERM
+ ": line %d, empty device list -- %s\n",
+ lineno, line);
+ (*errmsg)(errstring);
+ }
+ continue;
+ }
+
+ device = strtok_r(dev_list, ":", &last);
+ while (device != NULL) {
+ if ((device[0] != '/') || (strlen(device) <= 1)) {
+ err = -1;
+ } else if (dir_dev_acc("/", &device[1], uid, gid, mode,
+ saveline, errmsg)) {
+ err = -1;
+ }
+ device = strtok_r(last, ":", &last);
+ }
+ }
+ (void) fclose(fp);
+ return (err);
+}
+
+/*
+ * returns 0 if resolved, -1 otherwise.
+ * devpath: Absolute path to /dev link
+ * devfs_path: Returns malloced string: /devices path w/out "/devices"
+ */
+static int
+resolve_link(char *devpath, char **devfs_path)
+{
+ char contents[PATH_MAX + 1];
+ char stage_link[PATH_MAX + 1];
+ char *ptr;
+ int linksize;
+ char *slashdev = "/dev/";
+
+ if (devfs_path) {
+ *devfs_path = NULL;
+ }
+
+ linksize = readlink(devpath, contents, PATH_MAX);
+
+ if (linksize <= 0) {
+ return (-1);
+ } else {
+ contents[linksize] = '\0';
+ }
+
+ /*
+ * if the link contents is not a minor node assume
+ * that link contents is really a pointer to another
+ * link, and if so recurse and read its link contents.
+ */
+ if (is_minor_node((const char *)contents, (const char **)&ptr) !=
+ 1) {
+ if (strncmp(contents, slashdev, strlen(slashdev)) == 0) {
+ /* absolute path, starting with /dev */
+ (void) strcpy(stage_link, contents);
+ } else {
+ /* relative path, prefix devpath */
+ if ((ptr = strrchr(devpath, '/')) == NULL) {
+ /* invalid link */
+ return (-1);
+ }
+ *ptr = '\0';
+ (void) strcpy(stage_link, devpath);
+ *ptr = '/';
+ (void) strcat(stage_link, "/");
+ (void) strcat(stage_link, contents);
+
+ }
+ return (resolve_link(stage_link, devfs_path));
+ }
+
+ if (devfs_path) {
+ *devfs_path = strdup(ptr);
+ if (*devfs_path == NULL) {
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * check a logindevperm line for a driver list and match this against
+ * the driver of the minor node
+ * returns 0 if no drivers were specified or a driver match
+ */
+static int
+check_driver_match(char *path, char *line)
+{
+ char *drv, *driver, *lasts;
+ char *devfs_path = NULL;
+ char saveline[MAX_LINELEN];
+ char *p;
+
+ if (resolve_link(path, &devfs_path) == 0) {
+ char *p;
+ char pwd_buf[PATH_MAX];
+ di_node_t node;
+
+ /* truncate on : so we can take a snapshot */
+ (void) strcpy(pwd_buf, devfs_path);
+ p = strrchr(pwd_buf, ':');
+ *p = '\0';
+
+ node = di_init(pwd_buf, DINFOMINOR);
+ free(devfs_path);
+
+ if (node) {
+ drv = di_driver_name(node);
+ di_fini(node);
+ } else {
+ return (0);
+ }
+ } else {
+ return (0);
+ }
+
+ (void) strcpy(saveline, line);
+
+ p = strstr(saveline, "driver");
+ if (p == NULL) {
+ return (0);
+ }
+
+ driver = strtok_r(p, "=", &lasts);
+ if (driver) {
+ if (strcmp(driver, "driver") == 0) {
+ driver = strtok_r(NULL, ", \t\n", &lasts);
+ while (driver) {
+ if (strcmp(driver, drv) == 0) {
+ return (0);
+ }
+ driver = strtok_r(NULL, ", \t\n", &lasts);
+ }
+ }
+ }
+
+ return (-1);
+}
+
+/*
+ * Apply owner/group/perms to all files (except "." and "..")
+ * in a directory.
+ * This function is recursive. We start with "/" and the rest of the pathname
+ * in left_to_do argument, and we walk the entire pathname which may contain
+ * regular expressions or '*' for each directory name or basename.
+ */
+static int
+dir_dev_acc(char *path, char *left_to_do, uid_t uid, gid_t gid, mode_t mode,
+ char *line, void (*errmsg)(char *))
+{
+ struct stat stat_buf;
+ int err = 0;
+ DIR *dirp;
+ struct dirent *direntp, *result;
+
+ /* path must be a valid name */
+ if (stat(path, &stat_buf) == -1) {
+ return (-1);
+ } else {
+ if (!S_ISDIR(stat_buf.st_mode)) {
+ if (strlen(left_to_do) == 0) {
+ /* finally check the driver matches */
+ if (check_driver_match(path, line) == 0) {
+ /* we are done, set the permissions */
+ if (setdevaccess(path,
+ uid, gid, mode, errmsg)) {
+
+ return (-1);
+ }
+ }
+ }
+ return (0);
+ }
+ }
+
+ dirp = opendir(path);
+ if (dirp == NULL) {
+ return (0);
+ } else {
+ char *p = strchr(left_to_do, '/');
+ regex_t regex;
+ int alwaysmatch = 0;
+ char *match;
+ char *name, *newpath, *remainder_path;
+
+ newpath = (char *)malloc(MAXPATHLEN);
+ if (newpath == NULL) {
+ return (-1);
+ }
+ match = (char *)calloc(MAXPATHLEN, 1);
+ if (match == NULL) {
+ free(newpath);
+ return (-1);
+ }
+
+ if (p) {
+ (void) strncpy(match, left_to_do, p - left_to_do);
+ } else {
+ (void) strcpy(match, left_to_do);
+ }
+
+ if (strcmp(match, "*") == 0) {
+ alwaysmatch = 1;
+ } else {
+ if (regcomp(&regex, match, REG_EXTENDED) != 0) {
+ free(newpath);
+ free(match);
+ return (-1);
+ }
+ }
+
+ direntp = alloca(sizeof (struct dirent) + MAXPATHLEN);
+ while (readdir_r(dirp, direntp, &result) == 0) {
+ if (result == NULL)
+ break;
+
+ name = direntp->d_name;
+ if ((strcmp(name, ".") == 0) ||
+ (strcmp(name, "..") == 0))
+ continue;
+
+ if (alwaysmatch ||
+ regexec(&regex, name, 0, NULL, 0) == 0) {
+ if (strcmp(path, "/") == 0) {
+ (void) snprintf(newpath,
+ MAXPATHLEN, "%s%s", path, name);
+ } else {
+ (void) snprintf(newpath,
+ MAXPATHLEN, "%s/%s", path, name);
+ }
+
+ /*
+ * recurse but adjust what is still left to do
+ */
+ remainder_path = (p ?
+ left_to_do + (p - left_to_do) + 1 :
+ &left_to_do[strlen(left_to_do)]);
+ if (dir_dev_acc(newpath, remainder_path,
+ uid, gid, mode, line, errmsg)) {
+ err = -1;
+ break;
+ }
+ }
+ }
+ (void) closedir(dirp);
+ free(newpath);
+ free(match);
+ if (!alwaysmatch) {
+ regfree(&regex);
+ }
+ }
+
+ return (err);
+}
+
+/*
+ * di_devperm_login - modify access of devices in /etc/logindevperm
+ * by changing owner/group/permissions to that of ttyn.
+ */
+int
+di_devperm_login(const char *ttyn, uid_t uid, gid_t gid,
+ void (*errmsg)(char *))
+{
+ int err;
+ struct group grp, *grpp;
+ gid_t tty_gid;
+ char grbuf[NSS_BUFLEN_GROUP];
+
+ if (errmsg == NULL)
+ errmsg = logerror;
+
+ if (ttyn == NULL) {
+ (*errmsg)("di_devperm_login: NULL tty device\n");
+ return (-1);
+ }
+
+ if (getgrnam_r("tty", &grp, grbuf, NSS_BUFLEN_GROUP, &grpp) != 0) {
+ tty_gid = grpp->gr_gid;
+ } else {
+ /*
+ * this should never happen, but if it does set
+ * group to tty's traditional value.
+ */
+ tty_gid = 7;
+ }
+
+ /* set the login console device permission */
+ err = setdevaccess((char *)ttyn, uid, tty_gid,
+ S_IRUSR|S_IWUSR|S_IWGRP, errmsg);
+ if (err) {
+ return (err);
+ }
+
+ /* set the device permissions */
+ return (logindevperm(ttyn, uid, gid, errmsg));
+}
+
+/*
+ * di_devperm_logout - clean up access of devices in /etc/logindevperm
+ * by resetting owner/group/permissions.
+ */
+int
+di_devperm_logout(const char *ttyn)
+{
+ struct passwd *pwd;
+ uid_t root_uid;
+ gid_t root_gid;
+
+ if (ttyn == NULL)
+ return (-1);
+
+ pwd = getpwnam("root");
+ if (pwd != NULL) {
+ root_uid = pwd->pw_uid;
+ root_gid = pwd->pw_gid;
+ } else {
+ /*
+ * this should never happen, but if it does set user
+ * and group to root's traditional values.
+ */
+ root_uid = 0;
+ root_gid = 0;
+ }
+
+ return (logindevperm(ttyn, root_uid, root_gid, NULL));
+}
+
+static void
+logerror(char *errstring)
+{
+ syslog(LOG_AUTH | LOG_CRIT, "%s", errstring);
+}
+
+
+/*
+ * Tokens are separated by ' ', '\t', ':', '=', '&', '|', ';', '\n', or '\0'
+ */
+static int
+getnexttoken(char *next, char **nextp, char **tokenpp, char *tchar)
+{
+ char *cp;
+ char *cp1;
+ char *tokenp;
+
+ cp = next;
+ while (*cp == ' ' || *cp == '\t') {
+ cp++; /* skip leading spaces */
+ }
+ tokenp = cp; /* start of token */
+ while (*cp != '\0' && *cp != '\n' && *cp != ' ' && *cp != '\t' &&
+ *cp != ':' && *cp != '=' && *cp != '&' &&
+ *cp != '|' && *cp != ';') {
+ cp++; /* point to next character */
+ }
+ /*
+ * If terminating character is a space or tab, look ahead to see if
+ * there's another terminator that's not a space or a tab.
+ * (This code handles trailing spaces.)
+ */
+ if (*cp == ' ' || *cp == '\t') {
+ cp1 = cp;
+ while (*++cp1 == ' ' || *cp1 == '\t')
+ ;
+ if (*cp1 == '=' || *cp1 == ':' || *cp1 == '&' || *cp1 == '|' ||
+ *cp1 == ';' || *cp1 == '\n' || *cp1 == '\0') {
+ *cp = NULL; /* terminate token */
+ cp = cp1;
+ }
+ }
+ if (tchar != NULL) {
+ *tchar = *cp; /* save terminating character */
+ if (*tchar == '\0') {
+ *tchar = '\n';
+ }
+ }
+ *cp++ = '\0'; /* terminate token, point to next */
+ *nextp = cp; /* set pointer to next character */
+ if (cp - tokenp - 1 == 0) {
+ return (0);
+ }
+ *tokenpp = tokenp;
+ return (1);
+}
+
+/*
+ * get a decimal octal or hex number. Handle '~' for one's complement.
+ */
+static int
+getvalue(char *token, int *valuep)
+{
+ int radix;
+ int retval = 0;
+ int onescompl = 0;
+ int negate = 0;
+ char c;
+
+ if (*token == '~') {
+ onescompl++; /* perform one's complement on result */
+ token++;
+ } else if (*token == '-') {
+ negate++;
+ token++;
+ }
+ if (*token == '0') {
+ token++;
+ c = *token;
+
+ if (c == '\0') {
+ *valuep = 0; /* value is 0 */
+ return (0);
+ }
+
+ if (c == 'x' || c == 'X') {
+ radix = 16;
+ token++;
+ } else {
+ radix = 8;
+ }
+ } else
+ radix = 10;
+
+ while ((c = *token++)) {
+ switch (radix) {
+ case 8:
+ if (c >= '0' && c <= '7') {
+ c -= '0';
+ } else {
+ /* invalid number */
+ return (0);
+ }
+ retval = (retval << 3) + c;
+ break;
+ case 10:
+ if (c >= '0' && c <= '9') {
+ c -= '0';
+ } else {
+ /* invalid number */
+ return (0);
+ }
+ retval = (retval * 10) + c;
+ break;
+ case 16:
+ if (c >= 'a' && c <= 'f') {
+ c = c - 'a' + 10;
+ } else if (c >= 'A' && c <= 'F') {
+ c = c - 'A' + 10;
+ } else if (c >= '0' && c <= '9') {
+ c -= '0';
+ } else {
+ /* invalid number */
+ return (0);
+ }
+ retval = (retval << 4) + c;
+ break;
+ }
+ }
+ if (onescompl) {
+ retval = ~retval;
+ }
+ if (negate) {
+ retval = -retval;
+ }
+ *valuep = retval;
+ return (1);
+}
+
+/*
+ * Read /etc/minor_perm, return mperm list of entries
+ */
+struct mperm *
+i_devfs_read_minor_perm(char *drvname, void (*errcb)(minorperm_err_t, int))
+{
+ FILE *pfd;
+ struct mperm *mp;
+ char line[MAX_MINOR_PERM_LINE];
+ char *cp, *p, t;
+ struct mperm *minor_perms = NULL;
+ struct mperm *mptail = NULL;
+ struct passwd *pw;
+ struct group *gp;
+ uid_t root_uid;
+ gid_t sys_gid;
+ int ln = 0;
+
+ /*
+ * Get root/sys ids, these being the most common
+ */
+ if ((pw = getpwnam(DEFAULT_DEV_USER)) != NULL) {
+ root_uid = pw->pw_uid;
+ } else {
+ (*errcb)(MP_CANT_FIND_USER_ERR, 0);
+ root_uid = (uid_t)0; /* assume 0 is root */
+ }
+ if ((gp = getgrnam(DEFAULT_DEV_GROUP)) != NULL) {
+ sys_gid = gp->gr_gid;
+ } else {
+ (*errcb)(MP_CANT_FIND_GROUP_ERR, 0);
+ sys_gid = (gid_t)3; /* assume 3 is sys */
+ }
+
+ if ((pfd = fopen(MINOR_PERM_FILE, "r")) == NULL) {
+ (*errcb)(MP_FOPEN_ERR, errno);
+ return (NULL);
+ }
+ while (fgets(line, MAX_MINOR_PERM_LINE - 1, pfd) != NULL) {
+ ln++;
+ mp = (struct mperm *)calloc(1, sizeof (struct mperm));
+ if (mp == NULL) {
+ (*errcb)(MP_ALLOC_ERR, sizeof (struct mperm));
+ continue;
+ }
+ cp = line;
+ if (getnexttoken(cp, &cp, &p, &t) == 0) {
+ (*errcb)(MP_IGNORING_LINE_ERR, ln);
+ devfs_free_minor_perm(mp);
+ continue;
+ }
+ mp->mp_drvname = strdup(p);
+ if (mp->mp_drvname == NULL) {
+ (*errcb)(MP_ALLOC_ERR, strlen(p)+1);
+ devfs_free_minor_perm(mp);
+ continue;
+ } else if (t == '\n' || t == '\0') {
+ (*errcb)(MP_IGNORING_LINE_ERR, ln);
+ devfs_free_minor_perm(mp);
+ continue;
+ }
+ if (t == ':') {
+ if (getnexttoken(cp, &cp, &p, &t) == 0) {
+ (*errcb)(MP_IGNORING_LINE_ERR, ln);
+ devfs_free_minor_perm(mp);
+ }
+ mp->mp_minorname = strdup(p);
+ if (mp->mp_minorname == NULL) {
+ (*errcb)(MP_ALLOC_ERR, strlen(p)+1);
+ devfs_free_minor_perm(mp);
+ continue;
+ }
+ } else {
+ mp->mp_minorname = NULL;
+ }
+
+ if (t == '\n' || t == '\0') {
+ devfs_free_minor_perm(mp);
+ (*errcb)(MP_IGNORING_LINE_ERR, ln);
+ continue;
+ }
+ if (getnexttoken(cp, &cp, &p, &t) == 0) {
+ goto link;
+ }
+ if (getvalue(p, (int *)&mp->mp_mode) == 0) {
+ goto link;
+ }
+ if (t == '\n' || t == '\0') { /* no owner or group */
+ goto link;
+ }
+ if (getnexttoken(cp, &cp, &p, &t) == 0) {
+ goto link;
+ }
+ mp->mp_owner = strdup(p);
+ if (mp->mp_owner == NULL) {
+ (*errcb)(MP_ALLOC_ERR, strlen(p)+1);
+ devfs_free_minor_perm(mp);
+ continue;
+ } else if (t == '\n' || t == '\0') { /* no group */
+ goto link;
+ }
+ if (getnexttoken(cp, &cp, &p, 0) == 0) {
+ goto link;
+ }
+ mp->mp_group = strdup(p);
+ if (mp->mp_group == NULL) {
+ (*errcb)(MP_ALLOC_ERR, strlen(p)+1);
+ devfs_free_minor_perm(mp);
+ continue;
+ }
+link:
+ if (drvname != NULL) {
+ /*
+ * We only want the minor perm entry for a
+ * the named driver. The driver name is the
+ * minor in the clone case.
+ */
+ if (strcmp(mp->mp_drvname, "clone") == 0) {
+ if (mp->mp_minorname == NULL ||
+ strcmp(drvname, mp->mp_minorname) != 0) {
+ devfs_free_minor_perm(mp);
+ continue;
+ }
+ } else {
+ if (strcmp(drvname, mp->mp_drvname) != 0) {
+ devfs_free_minor_perm(mp);
+ continue;
+ }
+ }
+ }
+ if (minor_perms == NULL) {
+ minor_perms = mp;
+ } else {
+ mptail->mp_next = mp;
+ }
+ mptail = mp;
+
+ /*
+ * Compute the uid's and gid's here - there are
+ * fewer lines in the /etc/minor_perm file than there
+ * are devices to be stat(2)ed. And almost every
+ * device is 'root sys'. See 1135520.
+ */
+ if (mp->mp_owner == NULL ||
+ strcmp(mp->mp_owner, DEFAULT_DEV_USER) == 0 ||
+ (pw = getpwnam(mp->mp_owner)) == NULL) {
+ mp->mp_uid = root_uid;
+ } else {
+ mp->mp_uid = pw->pw_uid;
+ }
+
+ if (mp->mp_group == NULL ||
+ strcmp(mp->mp_group, DEFAULT_DEV_GROUP) == 0 ||
+ (gp = getgrnam(mp->mp_group)) == NULL) {
+ mp->mp_gid = sys_gid;
+ } else {
+ mp->mp_gid = gp->gr_gid;
+ }
+ }
+
+ if (fclose(pfd) == EOF) {
+ (*errcb)(MP_FCLOSE_ERR, errno);
+ }
+
+ return (minor_perms);
+}
+
+struct mperm *
+devfs_read_minor_perm(void (*errcb)(minorperm_err_t, int))
+{
+ return (i_devfs_read_minor_perm(NULL, errcb));
+}
+
+static struct mperm *
+i_devfs_read_minor_perm_by_driver(char *drvname,
+ void (*errcb)(minorperm_err_t mp_err, int key))
+{
+ return (i_devfs_read_minor_perm(drvname, errcb));
+}
+
+/*
+ * Free mperm list of entries
+ */
+void
+devfs_free_minor_perm(struct mperm *mplist)
+{
+ struct mperm *mp, *next;
+
+ for (mp = mplist; mp != NULL; mp = next) {
+ next = mp->mp_next;
+
+ if (mp->mp_drvname)
+ free(mp->mp_drvname);
+ if (mp->mp_minorname)
+ free(mp->mp_minorname);
+ if (mp->mp_owner)
+ free(mp->mp_owner);
+ if (mp->mp_group)
+ free(mp->mp_group);
+ free(mp);
+ }
+}
+
+static int
+i_devfs_add_perm_entry(nvlist_t *nvl, struct mperm *mp)
+{
+ int err;
+
+ err = nvlist_add_string(nvl, mp->mp_drvname, mp->mp_minorname);
+ if (err != 0)
+ return (err);
+
+ err = nvlist_add_int32(nvl, "mode", (int32_t)mp->mp_mode);
+ if (err != 0)
+ return (err);
+
+ err = nvlist_add_int32(nvl, "uid", (int32_t)mp->mp_uid);
+ if (err != 0)
+ return (err);
+
+ err = nvlist_add_int32(nvl, "gid", (int32_t)mp->mp_gid);
+ return (err);
+}
+
+static nvlist_t *
+i_devfs_minor_perm_nvlist(struct mperm *mplist,
+ void (*errcb)(minorperm_err_t, int))
+{
+ int err;
+ struct mperm *mp;
+ nvlist_t *nvl = NULL;
+
+ if ((err = nvlist_alloc(&nvl, 0, 0)) != 0) {
+ (*errcb)(MP_NVLIST_ERR, err);
+ return (NULL);
+ }
+
+ for (mp = mplist; mp != NULL; mp = mp->mp_next) {
+ if ((err = i_devfs_add_perm_entry(nvl, mp)) != 0) {
+ (*errcb)(MP_NVLIST_ERR, err);
+ nvlist_free(nvl);
+ return (NULL);
+ }
+ }
+
+ return (nvl);
+}
+
+/*
+ * Load all minor perm entries into the kernel
+ * Done at boot time via devfsadm
+ */
+int
+devfs_load_minor_perm(struct mperm *mplist,
+ void (*errcb)(minorperm_err_t, int))
+{
+ int err;
+ char *buf = NULL;
+ size_t buflen;
+ nvlist_t *nvl;
+
+ nvl = i_devfs_minor_perm_nvlist(mplist, errcb);
+ if (nvl == NULL)
+ return (-1);
+
+ if (nvlist_pack(nvl, &buf, &buflen, NV_ENCODE_NATIVE, 0) != 0) {
+ nvlist_free(nvl);
+ return (-1);
+ }
+
+ err = modctl(MODLOADMINORPERM, buf, buflen);
+ nvlist_free(nvl);
+ free(buf);
+
+ return (err);
+}
+
+/*
+ * Add/remove minor perm entry for a driver
+ */
+static int
+i_devfs_update_minor_perm(char *drv, int ctl,
+ void (*errcb)(minorperm_err_t, int))
+{
+ int err;
+ char *buf;
+ size_t buflen;
+ nvlist_t *nvl;
+ struct mperm *mplist;
+
+ mplist = i_devfs_read_minor_perm_by_driver(drv, errcb);
+
+ nvl = i_devfs_minor_perm_nvlist(mplist, errcb);
+ if (nvl == NULL)
+ return (-1);
+
+ buf = NULL;
+ if (nvlist_pack(nvl, &buf, &buflen, NV_ENCODE_NATIVE, 0) != 0) {
+ nvlist_free(nvl);
+ return (-1);
+ }
+
+ err = modctl(ctl, buf, buflen);
+ nvlist_free(nvl);
+ devfs_free_minor_perm(mplist);
+ free(buf);
+
+ return (err);
+}
+
+int
+devfs_add_minor_perm(char *drv,
+ void (*errcb)(minorperm_err_t, int))
+{
+ return (i_devfs_update_minor_perm(drv, MODADDMINORPERM, errcb));
+}
+
+int
+devfs_rm_minor_perm(char *drv,
+ void (*errcb)(minorperm_err_t, int))
+{
+ return (i_devfs_update_minor_perm(drv, MODREMMINORPERM, errcb));
+}
diff --git a/usr/src/lib/libdevinfo/devinfo_prop_decode.c b/usr/src/lib/libdevinfo/devinfo_prop_decode.c
new file mode 100644
index 0000000000..542cb54d52
--- /dev/null
+++ b/usr/src/lib/libdevinfo/devinfo_prop_decode.c
@@ -0,0 +1,935 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * This file contains kernel property decode routines adopted from
+ * sunddi.c and ddi_impl.c. The following changes have been applied.
+ *
+ * (1) Replace kmem_alloc by malloc. Remove negative indexing
+ * (2) Decoding applies only to prom properties.
+ * (3) For strings, the return value is a composite string, not a string array.
+ * (4) impl_ddi_prop_int_from_prom() uses _LITTLE_ENDIAN from isa_defs.h
+ *
+ * XXX This file should be kept in sync with kernel property encoding.
+ */
+
+#include <stdlib.h>
+#include <strings.h>
+#include <synch.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/dditypes.h>
+#include <sys/ddipropdefs.h>
+#include <sys/isa_defs.h>
+
+#include "libdevinfo.h"
+
+/*
+ * Return an integer in native machine format from an OBP 1275 integer
+ * representation, which is big-endian, with no particular alignment
+ * guarantees. intp points to the OBP data, and n the number of bytes.
+ *
+ * Byte-swapping may be needed on some implementations.
+ */
+int
+impl_di_prop_int_from_prom(uchar_t *intp, int n)
+{
+ int i = 0;
+
+#if defined(_LITTLE_ENDIAN)
+ intp += n;
+ while (n-- > 0) {
+ i = (i << 8) | *(--intp);
+ }
+#else
+ while (n-- > 0) {
+ i = (i << 8) | *intp++;
+ }
+#endif /* defined(_LITTLE_ENDIAN) */
+
+ return (i);
+}
+
+/*
+ * Reset the current location pointer in the property handle to the
+ * beginning of the data.
+ */
+void
+di_prop_reset_pos(prop_handle_t *ph)
+{
+ ph->ph_cur_pos = ph->ph_data;
+ ph->ph_save_pos = ph->ph_data;
+}
+
+/*
+ * Restore the current location pointer in the property handle to the
+ * saved position.
+ */
+void
+di_prop_save_pos(prop_handle_t *ph)
+{
+ ph->ph_save_pos = ph->ph_cur_pos;
+}
+
+/*
+ * Save the location that the current location poiner is pointing to..
+ */
+void
+di_prop_restore_pos(prop_handle_t *ph)
+{
+ ph->ph_cur_pos = ph->ph_save_pos;
+}
+
+/*
+ * Property encode/decode functions
+ */
+
+/*
+ * Decode an array of integers property
+ */
+static int
+di_prop_fm_decode_ints(prop_handle_t *ph, void *data, uint_t *nelements)
+{
+ int i;
+ int cnt = 0;
+ int *tmp;
+ int *intp;
+ int n;
+
+ /*
+ * Figure out how many array elements there are by going through the
+ * data without decoding it first and counting.
+ */
+ for (;;) {
+ i = DDI_PROP_INT(ph, DDI_PROP_CMD_SKIP, NULL);
+ if (i < 0)
+ break;
+ cnt++;
+ }
+
+ /*
+ * If there are no elements return an error
+ */
+ if (cnt == 0)
+ return (DDI_PROP_END_OF_DATA);
+
+ /*
+ * If we cannot skip through the data, we cannot decode it
+ */
+ if (i == DDI_PROP_RESULT_ERROR)
+ return (DDI_PROP_CANNOT_DECODE);
+
+ /*
+ * Reset the data pointer to the beginning of the encoded data
+ */
+ di_prop_reset_pos(ph);
+
+ /*
+ * Allocated memory to store the decoded value in.
+ */
+ if ((intp = malloc(cnt * sizeof (int))) == NULL) {
+ return (DDI_PROP_CANNOT_DECODE);
+ }
+
+
+ /*
+ * Decode each elemente and place it in the space we just allocated
+ */
+ tmp = intp;
+ for (n = 0; n < cnt; n++, tmp++) {
+ i = DDI_PROP_INT(ph, DDI_PROP_CMD_DECODE, tmp);
+ if (i < DDI_PROP_RESULT_OK) {
+ /*
+ * Free the space we just allocated
+ * and return an error.
+ */
+ free(intp);
+ switch (i) {
+ case DDI_PROP_RESULT_EOF:
+ return (DDI_PROP_END_OF_DATA);
+
+ case DDI_PROP_RESULT_ERROR:
+ return (DDI_PROP_CANNOT_DECODE);
+ }
+ }
+ }
+
+ *nelements = cnt;
+ *(int **)data = intp;
+
+ return (DDI_PROP_SUCCESS);
+}
+
+/*
+ * Decode an array of strings.
+ */
+static int
+di_prop_fm_decode_strings(prop_handle_t *ph, void *data, uint_t *nelements)
+{
+ int cnt = 0;
+ char *strs;
+ char *tmp;
+ int size;
+ int i;
+ int n;
+ int nbytes;
+
+ /*
+ * Figure out how much memory we need for the sum total
+ */
+ nbytes = 0;
+
+ for (;;) {
+ /*
+ * Get the decoded size of the current encoded string.
+ */
+ size = DDI_PROP_STR(ph, DDI_PROP_CMD_GET_DSIZE, NULL);
+ if (size < 0)
+ break;
+
+ cnt++;
+ nbytes += size;
+ }
+
+ /*
+ * If there are no elements return an error
+ */
+ if (cnt == 0)
+ return (DDI_PROP_END_OF_DATA);
+
+ /*
+ * If we cannot skip through the data, we cannot decode it
+ */
+ if (size == DDI_PROP_RESULT_ERROR)
+ return (DDI_PROP_CANNOT_DECODE);
+
+ /*
+ * Allocate memory in which to store the decoded strings.
+ */
+ if ((strs = malloc(nbytes)) == NULL) {
+ return (DDI_PROP_CANNOT_DECODE);
+ }
+
+ /*
+ * Finally, we can decode each string
+ */
+ di_prop_reset_pos(ph);
+ tmp = strs;
+ for (n = 0; n < cnt; n++) {
+ i = DDI_PROP_STR(ph, DDI_PROP_CMD_DECODE, tmp);
+ if (i < DDI_PROP_RESULT_OK) {
+ /*
+ * Free the space we just allocated
+ * and return an error
+ */
+ free(strs);
+ switch (i) {
+ case DDI_PROP_RESULT_EOF:
+ return (DDI_PROP_END_OF_DATA);
+
+ case DDI_PROP_RESULT_ERROR:
+ return (DDI_PROP_CANNOT_DECODE);
+ }
+ }
+ tmp += strlen(tmp) + 1;
+ }
+
+ *(char **)data = strs;
+ *nelements = cnt;
+
+ return (DDI_PROP_SUCCESS);
+}
+
+/*
+ * Decode an array of bytes.
+ */
+static int
+di_prop_fm_decode_bytes(prop_handle_t *ph, void *data, uint_t *nelements)
+{
+ uchar_t *tmp;
+ int nbytes;
+ int i;
+
+ /*
+ * If there are no elements return an error
+ */
+ if (ph->ph_size == 0)
+ return (DDI_PROP_END_OF_DATA);
+
+ /*
+ * Get the size of the encoded array of bytes.
+ */
+ nbytes = DDI_PROP_BYTES(ph, DDI_PROP_CMD_GET_DSIZE,
+ data, ph->ph_size);
+ if (nbytes < DDI_PROP_RESULT_OK) {
+ switch (nbytes) {
+ case DDI_PROP_RESULT_EOF:
+ return (DDI_PROP_END_OF_DATA);
+
+ case DDI_PROP_RESULT_ERROR:
+ return (DDI_PROP_CANNOT_DECODE);
+ }
+ }
+
+ /*
+ * Allocated memory to store the decoded value in.
+ */
+ if ((tmp = malloc(nbytes)) == NULL) {
+ return (DDI_PROP_CANNOT_DECODE);
+ }
+
+ /*
+ * Decode each element and place it in the space we just allocated
+ */
+ i = DDI_PROP_BYTES(ph, DDI_PROP_CMD_DECODE, tmp, nbytes);
+ if (i < DDI_PROP_RESULT_OK) {
+ /*
+ * Free the space we just allocated
+ * and return an error
+ */
+ free(tmp);
+ switch (i) {
+ case DDI_PROP_RESULT_EOF:
+ return (DDI_PROP_END_OF_DATA);
+
+ case DDI_PROP_RESULT_ERROR:
+ return (DDI_PROP_CANNOT_DECODE);
+ }
+ }
+
+ *(uchar_t **)data = tmp;
+ *nelements = nbytes;
+
+ return (DDI_PROP_SUCCESS);
+}
+
+/*
+ * OBP 1275 integer, string and byte operators.
+ *
+ * DDI_PROP_CMD_DECODE:
+ *
+ * DDI_PROP_RESULT_ERROR: cannot decode the data
+ * DDI_PROP_RESULT_EOF: end of data
+ * DDI_PROP_OK: data was decoded
+ *
+ * DDI_PROP_CMD_ENCODE:
+ *
+ * DDI_PROP_RESULT_ERROR: cannot encode the data
+ * DDI_PROP_RESULT_EOF: end of data
+ * DDI_PROP_OK: data was encoded
+ *
+ * DDI_PROP_CMD_SKIP:
+ *
+ * DDI_PROP_RESULT_ERROR: cannot skip the data
+ * DDI_PROP_RESULT_EOF: end of data
+ * DDI_PROP_OK: data was skipped
+ *
+ * DDI_PROP_CMD_GET_ESIZE:
+ *
+ * DDI_PROP_RESULT_ERROR: cannot get encoded size
+ * DDI_PROP_RESULT_EOF: end of data
+ * > 0: the encoded size
+ *
+ * DDI_PROP_CMD_GET_DSIZE:
+ *
+ * DDI_PROP_RESULT_ERROR: cannot get decoded size
+ * DDI_PROP_RESULT_EOF: end of data
+ * > 0: the decoded size
+ */
+
+/*
+ * OBP 1275 integer operator
+ *
+ * OBP properties are a byte stream of data, so integers may not be
+ * properly aligned. Therefore we need to copy them one byte at a time.
+ */
+int
+di_prop_1275_int(prop_handle_t *ph, uint_t cmd, int *data)
+{
+ int i;
+
+ switch (cmd) {
+ case DDI_PROP_CMD_DECODE:
+ /*
+ * Check that there is encoded data
+ */
+ if (ph->ph_cur_pos == NULL || ph->ph_size == 0)
+ return (DDI_PROP_RESULT_ERROR);
+ if (ph->ph_flags & PH_FROM_PROM) {
+ i = ph->ph_size < PROP_1275_INT_SIZE ?
+ ph->ph_size : PROP_1275_INT_SIZE;
+ if ((int *)ph->ph_cur_pos > ((int *)ph->ph_data +
+ ph->ph_size - i))
+ return (DDI_PROP_RESULT_ERROR);
+ } else if (ph->ph_size < sizeof (int) ||
+ ((int *)ph->ph_cur_pos > ((int *)ph->ph_data +
+ ph->ph_size - sizeof (int)))) {
+ return (DDI_PROP_RESULT_ERROR);
+ }
+
+ /*
+ * Copy the integer, using the implementation-specific
+ * copy function if the property is coming from the PROM.
+ */
+ if (ph->ph_flags & PH_FROM_PROM) {
+ *data = impl_di_prop_int_from_prom(
+ (uchar_t *)ph->ph_cur_pos,
+ (ph->ph_size < PROP_1275_INT_SIZE) ?
+ ph->ph_size : PROP_1275_INT_SIZE);
+ } else {
+ bcopy(ph->ph_cur_pos, (caddr_t)data, sizeof (int));
+ }
+
+ /*
+ * Move the current location to the start of the next
+ * bit of undecoded data.
+ */
+ ph->ph_cur_pos = (uchar_t *)ph->ph_cur_pos + PROP_1275_INT_SIZE;
+ return (DDI_PROP_RESULT_OK);
+
+ case DDI_PROP_CMD_ENCODE:
+ /*
+ * Check that there is room to encoded the data
+ */
+ if (ph->ph_cur_pos == NULL || ph->ph_size == 0 ||
+ ph->ph_size < PROP_1275_INT_SIZE ||
+ ((int *)ph->ph_cur_pos > ((int *)ph->ph_data +
+ ph->ph_size - sizeof (int))))
+ return (DDI_PROP_RESULT_ERROR);
+
+ /*
+ * Encode the integer into the byte stream one byte at a
+ * time.
+ */
+ bcopy((caddr_t)data, ph->ph_cur_pos, sizeof (int));
+
+ /*
+ * Move the current location to the start of the next bit of
+ * space where we can store encoded data.
+ */
+ ph->ph_cur_pos = (uchar_t *)ph->ph_cur_pos + PROP_1275_INT_SIZE;
+ return (DDI_PROP_RESULT_OK);
+
+ case DDI_PROP_CMD_SKIP:
+ /*
+ * Check that there is encoded data
+ */
+ if (ph->ph_cur_pos == NULL || ph->ph_size == 0 ||
+ ph->ph_size < PROP_1275_INT_SIZE)
+ return (DDI_PROP_RESULT_ERROR);
+
+
+ if ((caddr_t)ph->ph_cur_pos ==
+ (caddr_t)ph->ph_data + ph->ph_size) {
+ return (DDI_PROP_RESULT_EOF);
+ } else if ((caddr_t)ph->ph_cur_pos >
+ (caddr_t)ph->ph_data + ph->ph_size) {
+ return (DDI_PROP_RESULT_EOF);
+ }
+
+ /*
+ * Move the current location to the start of the next bit of
+ * undecoded data.
+ */
+ ph->ph_cur_pos = (uchar_t *)ph->ph_cur_pos + PROP_1275_INT_SIZE;
+ return (DDI_PROP_RESULT_OK);
+
+ case DDI_PROP_CMD_GET_ESIZE:
+ /*
+ * Return the size of an encoded integer on OBP
+ */
+ return (PROP_1275_INT_SIZE);
+
+ case DDI_PROP_CMD_GET_DSIZE:
+ /*
+ * Return the size of a decoded integer on the system.
+ */
+ return (sizeof (int));
+ }
+
+ /*NOTREACHED*/
+ return (0); /* keep gcc happy */
+}
+
+/*
+ * 64 bit integer operator
+ *
+ * This is an extension, defined by Sun, to the 1275 integer
+ * operator. This routine handles the encoding/decoding of
+ * 64 bit integer properties.
+ */
+int
+di_prop_int64_op(prop_handle_t *ph, uint_t cmd, int64_t *data)
+{
+ switch (cmd) {
+ case DDI_PROP_CMD_DECODE:
+ /*
+ * Check that there is encoded data
+ */
+ if (ph->ph_cur_pos == NULL || ph->ph_size == 0)
+ return (DDI_PROP_RESULT_ERROR);
+ if (ph->ph_flags & PH_FROM_PROM) {
+ return (DDI_PROP_RESULT_ERROR);
+ } else if (ph->ph_size < sizeof (int64_t) ||
+ ((int64_t *)ph->ph_cur_pos > ((int64_t *)ph->ph_data +
+ ph->ph_size - sizeof (int64_t)))) {
+ return (DDI_PROP_RESULT_ERROR);
+ }
+
+ /*
+ * Copy the integer, using the implementation-specific
+ * copy function if the property is coming from the PROM.
+ */
+ bcopy(ph->ph_cur_pos, (caddr_t)data, sizeof (int64_t));
+
+ /*
+ * Move the current location to the start of the next
+ * bit of undecoded data.
+ */
+ ph->ph_cur_pos = (uchar_t *)ph->ph_cur_pos +
+ sizeof (int64_t);
+ return (DDI_PROP_RESULT_OK);
+
+ case DDI_PROP_CMD_ENCODE:
+ /*
+ * Check that there is room to encoded the data
+ */
+ if (ph->ph_cur_pos == NULL || ph->ph_size == 0 ||
+ ph->ph_size < sizeof (int64_t) ||
+ ((int64_t *)ph->ph_cur_pos > ((int64_t *)ph->ph_data +
+ ph->ph_size - sizeof (int64_t))))
+ return (DDI_PROP_RESULT_ERROR);
+
+ /*
+ * Encode the integer into the byte stream one byte at a
+ * time.
+ */
+ bcopy((caddr_t)data, ph->ph_cur_pos, sizeof (int64_t));
+
+ /*
+ * Move the current location to the start of the next bit of
+ * space where we can store encoded data.
+ */
+ ph->ph_cur_pos = (uchar_t *)ph->ph_cur_pos +
+ sizeof (int64_t);
+ return (DDI_PROP_RESULT_OK);
+
+ case DDI_PROP_CMD_SKIP:
+ /*
+ * Check that there is encoded data
+ */
+ if (ph->ph_cur_pos == NULL || ph->ph_size == 0 ||
+ ph->ph_size < sizeof (int64_t))
+ return (DDI_PROP_RESULT_ERROR);
+
+
+ if ((caddr_t)ph->ph_cur_pos ==
+ (caddr_t)ph->ph_data + ph->ph_size) {
+ return (DDI_PROP_RESULT_EOF);
+ } else if ((caddr_t)ph->ph_cur_pos >
+ (caddr_t)ph->ph_data + ph->ph_size) {
+ return (DDI_PROP_RESULT_EOF);
+ }
+
+ /*
+ * Move the current location to the start of the next bit of
+ * undecoded data.
+ */
+ ph->ph_cur_pos = (uchar_t *)ph->ph_cur_pos +
+ sizeof (int64_t);
+ return (DDI_PROP_RESULT_OK);
+
+ case DDI_PROP_CMD_GET_ESIZE:
+ /*
+ * Return the size of an encoded integer on OBP
+ */
+ return (sizeof (int64_t));
+
+ case DDI_PROP_CMD_GET_DSIZE:
+ /*
+ * Return the size of a decoded integer on the system.
+ */
+ return (sizeof (int64_t));
+ }
+
+ /*NOTREACHED*/
+ return (0); /* keep gcc happy */
+}
+
+/*
+ * OBP 1275 string operator.
+ *
+ * OBP strings are NULL terminated.
+ */
+int
+di_prop_1275_string(prop_handle_t *ph, uint_t cmd, char *data)
+{
+ int n;
+ char *p;
+ char *end;
+
+ switch (cmd) {
+ case DDI_PROP_CMD_DECODE:
+ /*
+ * Check that there is encoded data
+ */
+ if (ph->ph_cur_pos == NULL || ph->ph_size == 0) {
+ return (DDI_PROP_RESULT_ERROR);
+ }
+
+ n = strlen((char *)ph->ph_cur_pos) + 1;
+ if ((char *)ph->ph_cur_pos > ((char *)ph->ph_data +
+ ph->ph_size - n)) {
+ return (DDI_PROP_RESULT_ERROR);
+ }
+
+ /*
+ * Copy the NULL terminated string
+ */
+ bcopy((char *)ph->ph_cur_pos, data, n);
+
+ /*
+ * Move the current location to the start of the next bit of
+ * undecoded data.
+ */
+ ph->ph_cur_pos = (char *)ph->ph_cur_pos + n;
+ return (DDI_PROP_RESULT_OK);
+
+ case DDI_PROP_CMD_ENCODE:
+ /*
+ * Check that there is room to encoded the data
+ */
+ if (ph->ph_cur_pos == NULL || ph->ph_size == 0) {
+ return (DDI_PROP_RESULT_ERROR);
+ }
+
+ n = strlen(data) + 1;
+ if ((char *)ph->ph_cur_pos > ((char *)ph->ph_data +
+ ph->ph_size - n)) {
+ return (DDI_PROP_RESULT_ERROR);
+ }
+
+ /*
+ * Copy the NULL terminated string
+ */
+ bcopy(data, (char *)ph->ph_cur_pos, n);
+
+ /*
+ * Move the current location to the start of the next bit of
+ * space where we can store encoded data.
+ */
+ ph->ph_cur_pos = (char *)ph->ph_cur_pos + n;
+ return (DDI_PROP_RESULT_OK);
+
+ case DDI_PROP_CMD_SKIP:
+ /*
+ * Check that there is encoded data
+ */
+ if (ph->ph_cur_pos == NULL || ph->ph_size == 0) {
+ return (DDI_PROP_RESULT_ERROR);
+ }
+
+ /*
+ * Return the string length plus one for the NULL
+ * We know the size of the property, we need to
+ * ensure that the string is properly formatted,
+ * since we may be looking up random OBP data.
+ */
+ p = (char *)ph->ph_cur_pos;
+ end = (char *)ph->ph_data + ph->ph_size;
+
+ if (p == end) {
+ return (DDI_PROP_RESULT_EOF);
+ }
+
+ /*
+ * Make sure each char is printable
+ */
+ for (n = 0; p < end && isascii(*p) && !iscntrl(*p); n++, p++)
+ ;
+
+ /* Check termination and non-zero length */
+ if ((*p == 0) && (n != 0)) {
+ ph->ph_cur_pos = p + 1;
+ return (DDI_PROP_RESULT_OK);
+ }
+
+ return (DDI_PROP_RESULT_ERROR);
+
+ case DDI_PROP_CMD_GET_ESIZE:
+ /*
+ * Return the size of the encoded string on OBP.
+ */
+ return (strlen(data) + 1);
+
+ case DDI_PROP_CMD_GET_DSIZE:
+ /*
+ * Return the string length plus one for the NULL
+ * We know the size of the property, we need to
+ * ensure that the string is properly formatted,
+ * since we may be looking up random OBP data.
+ */
+ p = (char *)ph->ph_cur_pos;
+ end = (char *)ph->ph_data + ph->ph_size;
+ for (n = 0; p < end; n++) {
+ if (*p++ == '\0') {
+ ph->ph_cur_pos = p;
+ return (n+1);
+ }
+ }
+
+ /*
+ * Add check here to separate EOF and ERROR.
+ */
+ if (p == end)
+ return (DDI_PROP_RESULT_EOF);
+
+ return (DDI_PROP_RESULT_ERROR);
+
+ }
+
+ /*NOTREACHED*/
+ return (0); /* keep gcc happy */
+}
+
+/*
+ * OBP 1275 byte operator
+ *
+ * Caller must specify the number of bytes to get. OBP encodes bytes
+ * as a byte so there is a 1-to-1 translation.
+ */
+int
+di_prop_1275_bytes(prop_handle_t *ph, uint_t cmd, uchar_t *data,
+ uint_t nelements)
+{
+ switch (cmd) {
+ case DDI_PROP_CMD_DECODE:
+ /*
+ * Check that there is encoded data
+ */
+ if (ph->ph_cur_pos == NULL || ph->ph_size == 0 ||
+ ph->ph_size < nelements ||
+ ((char *)ph->ph_cur_pos > ((char *)ph->ph_data +
+ ph->ph_size - nelements)))
+ return (DDI_PROP_RESULT_ERROR);
+
+ /*
+ * Copy out the bytes
+ */
+ bcopy((char *)ph->ph_cur_pos, (char *)data, nelements);
+
+ /*
+ * Move the current location
+ */
+ ph->ph_cur_pos = (char *)ph->ph_cur_pos + nelements;
+ return (DDI_PROP_RESULT_OK);
+
+ case DDI_PROP_CMD_ENCODE:
+ /*
+ * Check that there is room to encode the data
+ */
+ if (ph->ph_cur_pos == NULL || ph->ph_size == 0 ||
+ ph->ph_size < nelements ||
+ ((char *)ph->ph_cur_pos > ((char *)ph->ph_data +
+ ph->ph_size - nelements)))
+ return (DDI_PROP_RESULT_ERROR);
+
+ /*
+ * Copy in the bytes
+ */
+ bcopy((char *)data, (char *)ph->ph_cur_pos, nelements);
+
+ /*
+ * Move the current location to the start of the next bit of
+ * space where we can store encoded data.
+ */
+ ph->ph_cur_pos = (char *)ph->ph_cur_pos + nelements;
+ return (DDI_PROP_RESULT_OK);
+
+ case DDI_PROP_CMD_SKIP:
+ /*
+ * Check that there is encoded data
+ */
+ if (ph->ph_cur_pos == NULL || ph->ph_size == 0 ||
+ ph->ph_size < nelements)
+ return (DDI_PROP_RESULT_ERROR);
+
+ if ((char *)ph->ph_cur_pos > ((char *)ph->ph_data +
+ ph->ph_size - nelements))
+ return (DDI_PROP_RESULT_EOF);
+
+ /*
+ * Move the current location
+ */
+ ph->ph_cur_pos = (char *)ph->ph_cur_pos + nelements;
+ return (DDI_PROP_RESULT_OK);
+
+ case DDI_PROP_CMD_GET_ESIZE:
+ /*
+ * The size in bytes of the encoded size is the
+ * same as the decoded size provided by the caller.
+ */
+ return (nelements);
+
+ case DDI_PROP_CMD_GET_DSIZE:
+ /*
+ * Just return the number of bytes specified by the caller.
+ */
+ return (nelements);
+
+ }
+
+ /*NOTREACHED*/
+ return (0); /* keep gcc happy */
+}
+
+/*
+ * Used for properties that come from the OBP, hardware configuration files,
+ * or that are created by calls to ddi_prop_update(9F).
+ */
+static struct prop_handle_ops prop_1275_ops = {
+ di_prop_1275_int,
+ di_prop_1275_string,
+ di_prop_1275_bytes,
+ di_prop_int64_op
+};
+
+/*
+ * Now the real thing:
+ * Extract type-specific values of an property
+ */
+int
+di_prop_decode_common(void *data, int size, int prop_type, int prom)
+{
+ int n;
+ int nelements;
+ char *cp, *end;
+ prop_handle_t ph;
+ int (*prop_decoder)(prop_handle_t *, void *, uint_t *);
+
+ /*
+ * If the encoded data came from software, no decoding needed
+ */
+ if (!prom) {
+ switch (prop_type) {
+ case DI_PROP_TYPE_INT:
+ if (size % sizeof (int))
+ nelements = -1;
+ else
+ nelements = size / sizeof (int);
+ break;
+
+ case DI_PROP_TYPE_INT64:
+ if (size % sizeof (int64_t))
+ nelements = -1;
+ else
+ nelements = size / sizeof (int64_t);
+ break;
+
+ case DI_PROP_TYPE_STRING:
+ nelements = 0;
+ cp = *(char **)data;
+ end = cp + size;
+ /*
+ * Don't trust the data passed in by the caller.
+ * Check every char to make sure it is indeed a
+ * string property.
+ */
+ while (cp < end) {
+ /* skip to next non-printable char */
+ for (n = 0; cp < end &&
+ isascii(*cp) && !iscntrl(*cp); n++, cp++)
+ ;
+
+ /*
+ * Fail if reached end (i.e. last char != 0),
+ * or has a non-printable char. A zero length
+ * string is acceptable.
+ */
+ if (cp == end || *cp != 0) {
+ nelements = -1;
+ break;
+ }
+ /*
+ * Increment # strings and keep going
+ */
+ nelements++;
+ cp++;
+ }
+
+ break;
+
+ case DI_PROP_TYPE_BYTE:
+ nelements = size;
+ }
+
+ return (nelements);
+ }
+
+ /*
+ * Get the encoded data
+ */
+ bzero((caddr_t)&ph, sizeof (prop_handle_t));
+ ph.ph_data = *(uchar_t **)data;
+ ph.ph_size = size;
+
+ /*
+ * The data came from prom, use the 1275 OBP decode/encode routines.
+ */
+ ph.ph_cur_pos = ph.ph_data;
+ ph.ph_save_pos = ph.ph_data;
+ ph.ph_ops = &prop_1275_ops;
+ ph.ph_flags = PH_FROM_PROM;
+
+ switch (prop_type) {
+ case DI_PROP_TYPE_INT:
+ prop_decoder = di_prop_fm_decode_ints;
+ break;
+ case DI_PROP_TYPE_STRING:
+ prop_decoder = di_prop_fm_decode_strings;
+ break;
+ case DI_PROP_TYPE_BYTE:
+ default:
+ prop_decoder = di_prop_fm_decode_bytes;
+ break;
+ }
+
+ if ((*prop_decoder)(&ph, data, (uint_t *)&nelements)
+ != DDI_PROP_SUCCESS)
+ return (-1);
+
+ /*
+ * Free the encoded data
+ */
+ if (size != 0)
+ free(ph.ph_data);
+
+ return (nelements);
+}
+
+/* end of devinfo_prop_decode.c */
diff --git a/usr/src/lib/libdevinfo/i386/Makefile b/usr/src/lib/libdevinfo/i386/Makefile
new file mode 100644
index 0000000000..821c7b0afd
--- /dev/null
+++ b/usr/src/lib/libdevinfo/i386/Makefile
@@ -0,0 +1,35 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 1996-2003 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# lib/libdevinfo/i386/Makefile
+
+MAPDIR= ../spec/i386
+include ../Makefile.com
+
+all: $(LIBS)
+
+install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT)
diff --git a/usr/src/lib/libdevinfo/inc.flg b/usr/src/lib/libdevinfo/inc.flg
new file mode 100644
index 0000000000..65794fc31d
--- /dev/null
+++ b/usr/src/lib/libdevinfo/inc.flg
@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# ident "%Z%%M% %I% %E% SMI"
+
+echo_file usr/src/lib/Makefile.lib
+echo_file usr/src/lib/Makefile.targ
diff --git a/usr/src/lib/libdevinfo/libdevinfo.h b/usr/src/lib/libdevinfo/libdevinfo.h
new file mode 100644
index 0000000000..b277ff4ae5
--- /dev/null
+++ b/usr/src/lib/libdevinfo/libdevinfo.h
@@ -0,0 +1,388 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBDEVINFO_H
+#define _LIBDEVINFO_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <errno.h>
+#include <sys/param.h>
+#include <sys/sunddi.h>
+#include <sys/sunmdi.h>
+#include <sys/openpromio.h>
+#include <sys/ddi_impldefs.h>
+#include <sys/devinfo_impl.h>
+#include <limits.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * flags for di_walk_node
+ */
+#define DI_WALK_CLDFIRST 0
+#define DI_WALK_SIBFIRST 1
+#define DI_WALK_LINKGEN 2
+
+#define DI_WALK_MASK 0xf
+
+/*
+ * flags for di_walk_link
+ */
+#define DI_LINK_SRC 1
+#define DI_LINK_TGT 2
+
+/*
+ * return code for node_callback
+ */
+#define DI_WALK_CONTINUE 0
+#define DI_WALK_PRUNESIB -1
+#define DI_WALK_PRUNECHILD -2
+#define DI_WALK_TERMINATE -3
+
+/*
+ * flags for di_walk_minor
+ */
+#define DI_CHECK_ALIAS 0x10
+#define DI_CHECK_INTERNAL_PATH 0x20
+
+#define DI_CHECK_MASK 0xf0
+
+/* nodeid types */
+#define DI_PSEUDO_NODEID -1
+#define DI_SID_NODEID -2
+#define DI_PROM_NODEID -3
+
+/* node & device states */
+#define DI_DRIVER_DETACHED 0x8000
+#define DI_DEVICE_OFFLINE 0x1
+#define DI_DEVICE_DOWN 0x2
+#define DI_BUS_QUIESCED 0x100
+#define DI_BUS_DOWN 0x200
+
+/* property types */
+#define DI_PROP_TYPE_BOOLEAN 0
+#define DI_PROP_TYPE_INT 1
+#define DI_PROP_TYPE_STRING 2
+#define DI_PROP_TYPE_BYTE 3
+#define DI_PROP_TYPE_UNKNOWN 4
+#define DI_PROP_TYPE_UNDEF_IT 5
+#define DI_PROP_TYPE_INT64 6
+
+/* private macro for checking if a prop type is valid */
+#define DI_PROP_TYPE_VALID(type) \
+ ((((type) >= DI_PROP_TYPE_INT) && ((type) <= DI_PROP_TYPE_BYTE)) || \
+ ((type) == DI_PROP_TYPE_INT64))
+
+/* opaque handles */
+
+typedef struct di_node *di_node_t; /* opaque handle to node */
+typedef struct di_minor *di_minor_t; /* opaque handle to minor node */
+typedef struct di_prop *di_prop_t; /* opaque handle to property */
+typedef struct di_prom_prop *di_prom_prop_t; /* opaque handle to prom prop */
+typedef struct di_prom_handle *di_prom_handle_t; /* opaque handle */
+typedef struct di_path *di_path_t; /* opaque handle */
+typedef struct di_path_prop *di_path_prop_t; /* opaque handle */
+
+typedef struct di_devlink_handle *di_devlink_handle_t; /* devlink snapshot */
+typedef struct di_devlink *di_devlink_t; /* opaque handle to devlink */
+typedef struct di_link *di_link_t; /* opaque handle to link */
+typedef struct di_lnode *di_lnode_t; /* opaque handle to endpoint */
+
+/*
+ * Null handles to make handles really opaque
+ */
+#define DI_NODE_NIL NULL
+#define DI_LINK_NIL NULL
+#define DI_LNODE_NIL NULL
+#define DI_MINOR_NIL NULL
+#define DI_PROP_NIL NULL
+#define DI_PROM_PROP_NIL NULL
+#define DI_PROM_HANDLE_NIL NULL
+#define DI_PATH_NIL NULL
+
+/* Interface Prototypes */
+
+/*
+ * Snapshot initialization and cleanup
+ */
+extern di_node_t di_init(const char *phys_path, uint_t flag);
+extern void di_fini(di_node_t root);
+
+/*
+ * tree traversal
+ */
+extern di_node_t di_parent_node(di_node_t node);
+extern di_node_t di_sibling_node(di_node_t node);
+extern di_node_t di_child_node(di_node_t node);
+extern di_node_t di_drv_first_node(const char *drv_name, di_node_t root);
+extern di_node_t di_drv_next_node(di_node_t node);
+
+/*
+ * tree walking assistants
+ */
+extern int di_walk_node(di_node_t root, uint_t flag, void *arg,
+ int (*node_callback)(di_node_t node, void *arg));
+extern int di_walk_minor(di_node_t root, const char *minortype, uint_t flag,
+ void *arg, int (*minor_callback)(di_node_t node, di_minor_t minor,
+ void *arg));
+extern int di_walk_link(di_node_t root, uint_t flag, uint_t endpoint,
+ void *arg, int (*link_callback)(di_link_t link, void *arg));
+extern int di_walk_lnode(di_node_t root, uint_t flag,
+ void *arg, int (*lnode_callback)(di_lnode_t lnode, void *arg));
+
+extern void di_node_private_set(di_node_t node, void *data);
+extern void *di_node_private_get(di_node_t node);
+extern void di_minor_private_set(di_minor_t minor, void *data);
+extern void *di_minor_private_get(di_minor_t minor);
+extern void di_lnode_private_set(di_lnode_t lnode, void *data);
+extern void *di_lnode_private_get(di_lnode_t lnode);
+extern void di_link_private_set(di_link_t link, void *data);
+extern void *di_link_private_get(di_link_t link);
+
+/*
+ * generic node parameters
+ */
+extern char *di_node_name(di_node_t node);
+extern char *di_bus_addr(di_node_t node);
+extern char *di_binding_name(di_node_t node);
+extern int di_compatible_names(di_node_t, char **names);
+extern int di_instance(di_node_t node);
+extern int di_nodeid(di_node_t node);
+extern int di_driver_major(di_node_t node);
+extern uint_t di_state(di_node_t node);
+extern ddi_node_state_t di_node_state(di_node_t node);
+extern ddi_devid_t di_devid(di_node_t node);
+
+extern char *di_driver_name(di_node_t node);
+extern uint_t di_driver_ops(di_node_t node);
+
+extern char *di_devfs_path(di_node_t node);
+extern char *di_devfs_minor_path(di_minor_t minor);
+
+extern void di_devfs_path_free(char *path_buf);
+
+/*
+ * layering data access
+ */
+extern di_link_t di_link_next_by_node(di_node_t node,
+ di_link_t link, uint_t endpoint);
+extern di_link_t di_link_next_by_lnode(di_lnode_t lnode,
+ di_link_t link, uint_t endpoint);
+extern di_lnode_t di_link_to_lnode(di_link_t link, uint_t endpoint);
+
+extern di_lnode_t di_lnode_next(di_node_t node, di_lnode_t lnode);
+extern char *di_lnode_name(di_lnode_t lnode);
+extern di_node_t di_lnode_devinfo(di_lnode_t lnode);
+extern int di_lnode_devt(di_lnode_t lnode, dev_t *devt);
+
+extern int di_link_spectype(di_link_t link);
+
+/*
+ * minor data access
+ */
+extern di_minor_t di_minor_next(di_node_t node, di_minor_t minor);
+extern di_node_t di_minor_devinfo(di_minor_t minor);
+extern ddi_minor_type di_minor_type(di_minor_t minor);
+extern char *di_minor_name(di_minor_t minor);
+extern dev_t di_minor_devt(di_minor_t minor);
+extern int di_minor_spectype(di_minor_t minor);
+extern char *di_minor_nodetype(di_minor_t node);
+
+/*
+ * Software property access
+ */
+extern di_prop_t di_prop_next(di_node_t node, di_prop_t prop);
+extern dev_t di_prop_devt(di_prop_t prop);
+extern char *di_prop_name(di_prop_t prop);
+extern int di_prop_type(di_prop_t prop);
+extern int di_prop_ints(di_prop_t prop, int **prop_data);
+extern int di_prop_int64(di_prop_t prop, int64_t **prop_data);
+extern int di_prop_strings(di_prop_t prop, char **prop_data);
+extern int di_prop_bytes(di_prop_t prop, uchar_t **prop_data);
+extern int di_prop_lookup_ints(dev_t dev, di_node_t node,
+ const char *prop_name, int **prop_data);
+extern int di_prop_lookup_int64(dev_t dev, di_node_t node,
+ const char *prop_name, int64_t **prop_data);
+extern int di_prop_lookup_strings(dev_t dev, di_node_t node,
+ const char *prop_name, char **prop_data);
+extern int di_prop_lookup_bytes(dev_t dev, di_node_t node,
+ const char *prop_name, uchar_t **prop_data);
+
+/*
+ * PROM property access
+ */
+extern di_prom_handle_t di_prom_init(void);
+extern void di_prom_fini(di_prom_handle_t ph);
+
+extern di_prom_prop_t di_prom_prop_next(di_prom_handle_t ph, di_node_t node,
+ di_prom_prop_t prom_prop);
+
+extern char *di_prom_prop_name(di_prom_prop_t prom_prop);
+extern int di_prom_prop_data(di_prom_prop_t prop, uchar_t **prom_prop_data);
+
+extern int di_prom_prop_lookup_ints(di_prom_handle_t prom, di_node_t node,
+ const char *prom_prop_name, int **prom_prop_data);
+extern int di_prom_prop_lookup_strings(di_prom_handle_t prom, di_node_t node,
+ const char *prom_prop_name, char **prom_prop_data);
+extern int di_prom_prop_lookup_bytes(di_prom_handle_t prom, di_node_t node,
+ const char *prom_prop_name, uchar_t **prom_prop_data);
+
+/*
+ * Private interfaces
+ *
+ * The interfaces and structures below are private to this implementation
+ * of Solaris and are subject to change at any time without notice.
+ *
+ * Applications and drivers using these interfaces will fail
+ * to run on future releases.
+ */
+
+/*
+ * Interfaces for accessing I/O multipathing data
+ */
+/* XXX remove di_path_next() after updating NWS consolidation */
+extern di_path_t di_path_next(di_node_t node, di_path_t path);
+extern di_path_t di_path_next_phci(di_node_t node, di_path_t path);
+extern di_path_t di_path_next_client(di_node_t node, di_path_t path);
+extern di_path_state_t di_path_state(di_path_t path);
+extern char *di_path_addr(di_path_t path, char *buf);
+extern di_node_t di_path_client_node(di_path_t path);
+extern void di_path_client_path(di_path_t path, char *buf);
+extern di_node_t di_path_phci_node(di_path_t path);
+extern void di_path_phci_path(di_path_t path, char *buf);
+extern di_path_prop_t di_path_prop_next(di_path_t path, di_path_prop_t prop);
+extern char *di_path_prop_name(di_path_prop_t prop);
+extern int di_path_prop_type(di_path_prop_t prop);
+extern int di_path_prop_len(di_path_prop_t prop);
+extern int di_path_prop_bytes(di_path_prop_t prop, uchar_t **prop_data);
+extern int di_path_prop_ints(di_path_prop_t prop, int **prop_data);
+extern int di_path_prop_int64s(di_path_prop_t prop, int64_t **prop_data);
+extern int di_path_prop_strings(di_path_prop_t prop, char **prop_data);
+extern int di_path_prop_lookup_bytes(di_path_t path, const char *prop_name,
+ uchar_t **prop_data);
+extern int di_path_prop_lookup_ints(di_path_t path, const char *prop_name,
+ int **prop_data);
+extern int di_path_prop_lookup_int64s(di_path_t path, const char *prop_name,
+ int64_t **prop_data);
+extern int di_path_prop_lookup_strings(di_path_t path, const char *prop_name,
+ char **prop_data);
+
+
+/*
+ * Interfaces for private data
+ */
+extern di_node_t di_init_driver(const char *drv_name, uint_t flag);
+extern di_node_t di_init_impl(const char *phys_path, uint_t flag,
+ struct di_priv_data *priv_data);
+
+/*
+ * Prtconf needs to know property lists, raw prop_data, and private data
+ */
+extern di_prop_t di_prop_drv_next(di_node_t node, di_prop_t prop);
+extern di_prop_t di_prop_sys_next(di_node_t node, di_prop_t prop);
+extern di_prop_t di_prop_global_next(di_node_t node, di_prop_t prop);
+extern di_prop_t di_prop_hw_next(di_node_t node, di_prop_t prop);
+
+extern int di_prop_rawdata(di_prop_t prop, uchar_t **prop_data);
+extern void *di_parent_private_data(di_node_t node);
+extern void *di_driver_private_data(di_node_t node);
+
+/*
+ * Types of links for devlink lookup
+ */
+#define DI_PRIMARY_LINK 0x01
+#define DI_SECONDARY_LINK 0x02
+#define DI_LINK_TYPES 0x03
+
+/*
+ * Flag for di_devlink_init()
+ */
+#define DI_MAKE_LINK 0x01
+
+/*
+ * Flag for di_devlink_close()
+ */
+#define DI_LINK_ERROR 0x01
+
+/*
+ * For devfsadm synchronous link creation interfaces
+ */
+#define DEVFSADM_SYNCH_DOOR ".devfsadm_synch_door"
+
+/*
+ * devlink create argument
+ */
+struct dca_off {
+ uint32_t dca_root;
+ uint32_t dca_minor;
+ uint32_t dca_driver;
+ int dca_error;
+ int dca_flags;
+ char dca_name[PATH_MAX+MAXNAMELEN];
+};
+
+extern di_devlink_handle_t di_devlink_init(const char *name, uint_t flags);
+extern int di_devlink_walk(di_devlink_handle_t hdl, const char *re,
+ const char *minor_path, uint_t flags, void *arg,
+ int (*devlink_callback)(di_devlink_t, void *));
+extern const char *di_devlink_path(di_devlink_t devlink);
+extern const char *di_devlink_content(di_devlink_t devlink);
+extern int di_devlink_type(di_devlink_t devlink);
+extern di_devlink_t di_devlink_dup(di_devlink_t devlink);
+extern int di_devlink_free(di_devlink_t devlink);
+extern int di_devlink_fini(di_devlink_handle_t *hdlp);
+
+extern di_devlink_handle_t di_devlink_open(const char *root_dir, uint_t flags);
+extern int di_devlink_close(di_devlink_handle_t *pp, int flag);
+extern int di_devlink_rm_link(di_devlink_handle_t hdp, const char *link);
+extern int di_devlink_add_link(di_devlink_handle_t hdp, const char *link,
+ const char *content, int flags);
+extern int di_devlink_update(di_devlink_handle_t hdp);
+extern di_devlink_handle_t di_devlink_init_root(const char *root,
+ const char *name, uint_t flags);
+extern int di_devlink_cache_walk(di_devlink_handle_t hdp, const char *re,
+ const char *path, uint_t flags, void *arg,
+ int (*devlink_callback)(di_devlink_t, void *));
+
+/*
+ * Private interfaces for /etc/logindevperm
+ */
+extern int di_devperm_login(const char *, uid_t, gid_t, void (*)(char *));
+extern int di_devperm_logout(const char *);
+
+/*
+ * Private interface for looking up a node in a snapshot
+ */
+extern di_node_t di_lookup_node(di_node_t root, char *path);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBDEVINFO_H */
diff --git a/usr/src/lib/libdevinfo/llib-ldevinfo b/usr/src/lib/libdevinfo/llib-ldevinfo
new file mode 100644
index 0000000000..ce8d147fbd
--- /dev/null
+++ b/usr/src/lib/libdevinfo/llib-ldevinfo
@@ -0,0 +1,34 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 1999-2003 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ * usr/src/lib/libdevinfo/llib-ldevinfo
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*LINTLIBRARY*/
+/*PROTOLIB1*/
+
+#include <libdevinfo.h>
diff --git a/usr/src/lib/libdevinfo/sparc/Makefile b/usr/src/lib/libdevinfo/sparc/Makefile
new file mode 100644
index 0000000000..0c9bd6da6d
--- /dev/null
+++ b/usr/src/lib/libdevinfo/sparc/Makefile
@@ -0,0 +1,35 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 1996-2003 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# lib/libdevinfo/sparc/Makefile
+
+MAPDIR= ../spec/sparc
+include ../Makefile.com
+
+all: $(LIBS)
+
+install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT)
diff --git a/usr/src/lib/libdevinfo/sparcv9/Makefile b/usr/src/lib/libdevinfo/sparcv9/Makefile
new file mode 100644
index 0000000000..2d0a109adf
--- /dev/null
+++ b/usr/src/lib/libdevinfo/sparcv9/Makefile
@@ -0,0 +1,36 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 1996-2003 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# lib/libdevinfo/sparcv9/Makefile
+
+MAPDIR= ../spec/sparcv9
+include ../Makefile.com
+include ../../Makefile.lib.64
+
+all: $(LIBS)
+
+install: all $(ROOTLIBS64) $(ROOTLINKS64)
diff --git a/usr/src/lib/libdevinfo/spec/Makefile b/usr/src/lib/libdevinfo/spec/Makefile
new file mode 100644
index 0000000000..17a9843469
--- /dev/null
+++ b/usr/src/lib/libdevinfo/spec/Makefile
@@ -0,0 +1,30 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright (c) 1997-1999 by Sun Microsystems, Inc.
+# All rights reserved.
+#
+#pragma ident "%Z%%M% %I% %E% SMI"
+#
+# lib/libdevinfo/spec/Makefile
+
+include $(SRC)/lib/Makefile.spec.arch
diff --git a/usr/src/lib/libdevinfo/spec/Makefile.targ b/usr/src/lib/libdevinfo/spec/Makefile.targ
new file mode 100644
index 0000000000..7c57fa8369
--- /dev/null
+++ b/usr/src/lib/libdevinfo/spec/Makefile.targ
@@ -0,0 +1,35 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright (c) 1997-1999 by Sun Microsystems, Inc.
+# All rights reserved.
+#
+#pragma ident "%Z%%M% %I% %E% SMI"
+#
+# lib/libdevinfo/spec/Makefile.targ
+
+LIBRARY = libdevinfo.a
+VERS = .1
+
+OBJECTS = devinfo.o
+
+SPECCPP = -I../..
diff --git a/usr/src/lib/libdevinfo/spec/amd64/Makefile b/usr/src/lib/libdevinfo/spec/amd64/Makefile
new file mode 100644
index 0000000000..2a38c1cba9
--- /dev/null
+++ b/usr/src/lib/libdevinfo/spec/amd64/Makefile
@@ -0,0 +1,42 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+include ../Makefile.targ
+
+# Add arch specific objects here
+OBJECTS +=
+
+include $(SRC)/lib/Makefile.lib
+include $(SRC)/lib/Makefile.lib.64
+
+# Uncomment the following if the linker complains
+#amd64_C_PICFLAGS = -K PIC
+
+include $(SRC)/lib/Makefile.spec
+
+install: $(ROOTABILIB64)
diff --git a/usr/src/lib/libdevinfo/spec/devinfo.spec b/usr/src/lib/libdevinfo/spec/devinfo.spec
new file mode 100644
index 0000000000..66a4de49d8
--- /dev/null
+++ b/usr/src/lib/libdevinfo/spec/devinfo.spec
@@ -0,0 +1,951 @@
+#
+# Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#pragma ident "%Z%%M% %I% %E% SMI"
+#
+# lib/libdevinfo/spec/devinfo.spec
+
+function di_init
+include <libdevinfo.h>
+declaration di_node_t di_init(const char *phys_path, uint_t flag)
+version SUNW_1.1
+end
+
+function di_fini
+include <libdevinfo.h>
+declaration void di_fini(di_node_t root)
+version SUNW_1.1
+end
+
+function di_parent_node
+include <libdevinfo.h>
+declaration di_node_t di_parent_node(di_node_t node)
+version SUNW_1.1
+end
+
+function di_sibling_node
+include <libdevinfo.h>
+declaration di_node_t di_sibling_node(di_node_t node)
+version SUNW_1.1
+end
+
+function di_child_node
+include <libdevinfo.h>
+declaration di_node_t di_child_node(di_node_t node)
+version SUNW_1.1
+end
+
+function di_drv_first_node
+include <libdevinfo.h>
+declaration di_node_t di_drv_first_node(const char *drv_name, \
+ di_node_t root)
+version SUNW_1.1
+end
+
+function di_drv_next_node
+include <libdevinfo.h>
+declaration di_node_t di_drv_next_node(di_node_t node)
+version SUNW_1.1
+end
+
+function di_walk_node
+include <libdevinfo.h>
+declaration int di_walk_node(di_node_t root, uint_t flag, void *arg, \
+ int (*node_callback)(di_node_t, void *))
+version SUNW_1.1
+end
+
+function di_walk_minor
+include <libdevinfo.h>
+declaration int di_walk_minor(di_node_t root, const char *minor_type, \
+ uint_t flag, void *arg, \
+ int (*minor_callback)(di_node_t, di_minor_t, void *))
+version SUNW_1.1
+end
+
+function di_node_name
+include <libdevinfo.h>
+declaration char * di_node_name(di_node_t node)
+version SUNW_1.1
+end
+
+function di_bus_addr
+include <libdevinfo.h>
+declaration char * di_bus_addr(di_node_t node)
+version SUNW_1.1
+end
+
+function di_binding_name
+include <libdevinfo.h>
+declaration char * di_binding_name(di_node_t node)
+version SUNW_1.1
+end
+
+function di_compatible_names
+include <libdevinfo.h>
+declaration int di_compatible_names(di_node_t node, char **names)
+version SUNW_1.1
+end
+
+function di_instance
+include <libdevinfo.h>
+declaration int di_instance(di_node_t node)
+version SUNW_1.1
+end
+
+function di_nodeid
+include <libdevinfo.h>
+declaration int di_nodeid(di_node_t node)
+version SUNW_1.1
+end
+
+function di_state
+include <libdevinfo.h>
+declaration uint_t di_state(di_node_t node)
+version SUNW_1.1
+end
+
+function di_devid
+include <libdevinfo.h>
+declaration ddi_devid_t di_devid(di_node_t node)
+version SUNW_1.1
+end
+
+function di_driver_name
+include <libdevinfo.h>
+declaration char * di_driver_name(di_node_t node)
+version SUNW_1.1
+end
+
+function di_driver_ops
+include <libdevinfo.h>
+declaration uint_t di_driver_ops(di_node_t node)
+version SUNW_1.1
+end
+
+function di_devfs_path
+include <libdevinfo.h>
+declaration char * di_devfs_path(di_node_t node)
+version SUNW_1.1
+end
+
+function di_devfs_path_free
+include <libdevinfo.h>
+declaration void di_devfs_path_free(char *buf)
+version SUNW_1.1
+end
+
+function di_minor_next
+include <libdevinfo.h>
+declaration di_minor_t di_minor_next(di_node_t node, di_minor_t minor)
+version SUNW_1.1
+end
+
+function di_minor_type
+include <libdevinfo.h>
+declaration ddi_minor_type di_minor_type(di_minor_t minor)
+version SUNW_1.1
+end
+
+function di_minor_name
+include <libdevinfo.h>
+declaration char * di_minor_name(di_minor_t minor)
+version SUNW_1.1
+end
+
+function di_minor_devt
+include <libdevinfo.h>
+declaration dev_t di_minor_devt(di_minor_t minor)
+version SUNW_1.1
+end
+
+function di_minor_spectype
+include <libdevinfo.h>
+declaration int di_minor_spectype(di_minor_t minor)
+version SUNW_1.1
+end
+
+function di_minor_nodetype
+include <libdevinfo.h>
+declaration char * di_minor_nodetype(di_minor_t minor)
+version SUNW_1.1
+end
+
+function di_prop_next
+include <libdevinfo.h>
+declaration di_prop_t di_prop_next(di_node_t node, di_prop_t prop)
+version SUNW_1.1
+end
+
+function di_prop_devt
+include <libdevinfo.h>
+declaration dev_t di_prop_devt(di_prop_t prop)
+version SUNW_1.1
+end
+
+function di_prop_name
+include <libdevinfo.h>
+declaration char * di_prop_name(di_prop_t prop)
+version SUNW_1.1
+end
+
+function di_prop_type
+include <libdevinfo.h>
+declaration int di_prop_type(di_prop_t prop)
+version SUNW_1.1
+end
+
+function di_prop_ints
+include <libdevinfo.h>
+declaration int di_prop_ints(di_prop_t prop, int **prop_data)
+version SUNW_1.1
+end
+
+function di_prop_int64
+include <libdevinfo.h>
+declaration int di_prop_int64(di_prop_t prop, int64_t **prop_data)
+version SUNW_1.1
+end
+
+function di_prop_strings
+include <libdevinfo.h>
+declaration int di_prop_strings(di_prop_t prop, char **prop_data)
+version SUNW_1.1
+end
+
+function di_prop_bytes
+include <libdevinfo.h>
+declaration int di_prop_bytes(di_prop_t prop, uchar_t **prop_data)
+version SUNW_1.1
+end
+
+function di_prop_lookup_ints
+include <libdevinfo.h>
+declaration int di_prop_lookup_ints(dev_t dev, di_node_t node, \
+ const char *prop_name, int **prop_data)
+version SUNW_1.1
+end
+
+function di_prop_lookup_int64
+include <libdevinfo.h>
+declaration int di_prop_lookup_int64(dev_t dev, di_node_t node, \
+ const char *prop_name, int64_t **prop_data)
+version SUNW_1.1
+end
+
+function di_prop_lookup_strings
+include <libdevinfo.h>
+declaration int di_prop_lookup_strings(dev_t dev, di_node_t node, \
+ const char *prop_name, char **prop_data)
+version SUNW_1.1
+end
+
+function di_prop_lookup_bytes
+include <libdevinfo.h>
+declaration int di_prop_lookup_bytes(dev_t dev, di_node_t node, \
+ const char *prop_name, uchar_t **prop_data)
+version SUNW_1.1
+end
+
+function di_prom_init
+include <libdevinfo.h>
+declaration di_prom_handle_t di_prom_init(void)
+version SUNW_1.1
+end
+
+function di_prom_fini
+include <libdevinfo.h>
+declaration void di_prom_fini(di_prom_handle_t ph)
+version SUNW_1.1
+end
+
+function di_prom_prop_next
+include <libdevinfo.h>
+declaration di_prom_prop_t di_prom_prop_next(di_prom_handle_t ph, \
+ di_node_t node, di_prom_prop_t prom_prop)
+version SUNW_1.1
+end
+
+function di_prom_prop_name
+include <libdevinfo.h>
+declaration char * di_prom_prop_name(di_prom_prop_t prom_prop)
+version SUNW_1.1
+end
+
+function di_prom_prop_data
+include <libdevinfo.h>
+declaration int di_prom_prop_data(di_prom_prop_t prom_prop, \
+ uchar_t **prom_prop_data)
+version SUNW_1.1
+end
+
+function di_prom_prop_lookup_ints
+include <libdevinfo.h>
+declaration int di_prom_prop_lookup_ints(di_prom_handle_t ph, \
+ di_node_t node, const char *prom_prop_name, \
+ int **prom_prop_data)
+version SUNW_1.1
+end
+
+function di_prom_prop_lookup_strings
+include <libdevinfo.h>
+declaration int di_prom_prop_lookup_strings(di_prom_handle_t ph, \
+ di_node_t node, const char *prom_prop_name, \
+ char **prom_prop_data)
+version SUNW_1.1
+end
+
+function di_prom_prop_lookup_bytes
+include <libdevinfo.h>
+declaration int di_prom_prop_lookup_bytes(di_prom_handle_t ph, \
+ di_node_t node, const char *prom_prop_name, \
+ uchar_t **prom_prop_data)
+version SUNW_1.1
+end
+
+function devfs_path_to_drv
+include <libdevinfo.h>, <device_info.h>
+declaration int devfs_path_to_drv(char *devfs_path, char *drv_buf)
+version SUNWprivate_1.1
+end
+
+function devfs_dev_to_prom_name
+include <libdevinfo.h>
+declaration int devfs_dev_to_prom_name(char *dev_path, char *prom_path)
+version SUNWprivate_1.1
+end
+
+function devfs_resolve_aliases
+include <libdevinfo.h>
+declaration char * devfs_resolve_aliases(char *drv)
+version SUNWprivate_1.1
+end
+
+function devfs_bootdev_set_list
+include <libdevinfo.h>
+declaration int devfs_bootdev_set_list(const char *dev_name, \
+ const u_int options)
+version SUNWprivate_1.1
+end
+
+function devfs_bootdev_modifiable
+include <libdevinfo.h>
+declaration int devfs_bootdev_modifiable(void)
+version SUNWprivate_1.1
+end
+
+function devfs_bootdev_get_list
+include <libdevinfo.h>
+declaration int devfs_bootdev_get_list(const char *default_root, \
+ struct boot_dev ***bootdev_list)
+version SUNWprivate_1.1
+end
+
+function devfs_bootdev_free_list
+include <libdevinfo.h>
+declaration void devfs_bootdev_free_list(struct boot_dev **array)
+version SUNWprivate_1.1
+end
+
+function devfs_get_all_prom_names
+include <libdevinfo.h>
+declaration int devfs_get_all_prom_names(const char *, uint_t, \
+ struct devfs_prom_path **)
+version SUNWprivate_1.1
+end
+
+function devfs_free_all_prom_names
+include <libdevinfo.h>
+declaration void devfs_free_all_prom_names(struct devfs_prom_path *)
+version SUNWprivate_1.1
+end
+
+function devfs_get_prom_names
+include <libdevinfo.h>
+declaration int devfs_get_prom_names(const char *dev_name, \
+ u_int options, char ***prom_list)
+version SUNWprivate_1.1
+end
+
+
+#
+# Evolving (LDI PSARC/2001/769 and PSARC/2003/537)
+#
+function di_node_private_set
+include <libdevinfo.h>
+declaration void di_node_private_set(di_node_t node, void *data)
+version SUNW_1.3
+end
+
+function di_node_private_get
+include <libdevinfo.h>
+declaration void *di_node_private_get(di_node_t node)
+version SUNW_1.3
+end
+
+function di_minor_private_set
+include <libdevinfo.h>
+declaration void di_minor_private_set(di_minor_t minor, void *data)
+version SUNW_1.3
+end
+
+function di_minor_private_get
+include <libdevinfo.h>
+declaration void *di_minor_private_get(di_minor_t minor)
+version SUNW_1.3
+end
+
+function di_lnode_private_set
+include <libdevinfo.h>
+declaration void di_lnode_private_set(di_lnode_t lnode, void *data)
+version SUNW_1.3
+end
+
+function di_lnode_private_get
+include <libdevinfo.h>
+declaration void *di_lnode_private_get(di_lnode_t lnode)
+version SUNW_1.3
+end
+
+function di_link_private_set
+include <libdevinfo.h>
+declaration void di_link_private_set(di_link_t link, void *data)
+version SUNW_1.3
+end
+
+function di_link_private_get
+include <libdevinfo.h>
+declaration void *di_link_private_get(di_link_t link)
+version SUNW_1.3
+end
+
+function di_walk_link
+include <libdevinfo.h>
+declaration int di_walk_link(di_node_t root, uint_t flag, uint_t endpoint, \
+ void *arg, int (*link_callback)(di_link_t, void *))
+version SUNW_1.3
+end
+
+function di_walk_lnode
+include <libdevinfo.h>
+declaration int di_walk_lnode(di_node_t root, uint_t flag, void *arg, \
+ int (*lnode_callback)(di_lnode_t, void *))
+version SUNW_1.3
+end
+
+function di_link_next_by_node
+include <libdevinfo.h>
+declaration di_link_t di_link_next_by_node(di_node_t node, di_link_t link, \
+ uint_t endpoint)
+version SUNW_1.3
+end
+
+function di_link_next_by_lnode
+include <libdevinfo.h>
+declaration di_link_t di_link_next_by_lnode(di_lnode_t lnode, \
+ di_link_t link, uint_t endpoint)
+version SUNW_1.3
+end
+
+function di_link_to_lnode
+include <libdevinfo.h>
+declaration di_lnode_t di_link_to_lnode(di_link_t link, uint_t endpoint)
+version SUNW_1.3
+end
+
+function di_lnode_next
+include <libdevinfo.h>
+declaration di_lnode_t di_lnode_next(di_node_t node, di_lnode_t lnode)
+version SUNW_1.3
+end
+
+function di_lnode_name
+include <libdevinfo.h>
+declaration char *di_lnode_name(di_lnode_t lnode)
+version SUNW_1.3
+end
+
+function di_lnode_devinfo
+include <libdevinfo.h>
+declaration di_node_t di_lnode_devinfo(di_lnode_t lnode)
+version SUNW_1.3
+end
+
+function di_lnode_devt
+include <libdevinfo.h>
+declaration int di_lnode_devt(di_lnode_t lnode, dev_t *devt)
+version SUNW_1.3
+end
+
+function di_link_spectype
+include <libdevinfo.h>
+declaration int di_link_spectype(di_link_t link)
+version SUNW_1.3
+end
+
+function di_driver_major
+include <libdevinfo.h>
+declaration int di_driver_major(di_node_t node)
+version SUNW_1.3
+end
+
+function di_devfs_minor_path
+include <libdevinfo.h>
+declaration char * di_devfs_minor_path(di_minor_t minor)
+version SUNW_1.3
+end
+
+
+#
+# Sun private devlinks interfaces
+#
+function di_devlink_init
+include <libdevinfo.h>
+declaration di_devlink_handle_t di_devlink_init(const char *name, \
+ uint_t flags)
+version SUNWprivate_1.1
+end
+
+function di_devlink_fini
+include <libdevinfo.h>
+declaration int di_devlink_fini(di_devlink_handle_t *hdlp)
+version SUNWprivate_1.1
+end
+
+function di_devlink_walk
+include <libdevinfo.h>
+declaration int di_devlink_walk(di_devlink_handle_t hdl, \
+ const char *re, const char *minor_path, \
+ uint_t flags, void *arg, \
+ int (*fcn)(di_devlink_t, void *))
+
+version SUNWprivate_1.1
+end
+
+function di_devlink_path
+include <libdevinfo.h>
+declaration const char *di_devlink_path(di_devlink_t devlink)
+version SUNWprivate_1.1
+end
+
+function di_devlink_content
+include <libdevinfo.h>
+declaration const char *di_devlink_content(di_devlink_t devlink)
+version SUNWprivate_1.1
+end
+
+function di_devlink_type
+include <libdevinfo.h>
+declaration int di_devlink_type(di_devlink_t devlink)
+version SUNWprivate_1.1
+end
+
+function di_devlink_dup
+include <libdevinfo.h>
+declaration di_devlink_t di_devlink_dup(di_devlink_t devlink)
+version SUNWprivate_1.1
+end
+
+function di_devlink_free
+include <libdevinfo.h>
+declaration int di_devlink_free(di_devlink_t devlink)
+version SUNWprivate_1.1
+end
+
+#
+# Project private devlinks interfaces
+#
+function di_devlink_open
+include <libdevinfo.h>
+declaration di_devlink_handle_t di_devlink_open(const char *root_dir, \
+ uint_t flags)
+version SUNWprivate_1.1
+end
+
+function di_devlink_close
+include <libdevinfo.h>
+declaration int di_devlink_close(di_devlink_handle_t *hdlp, int flag)
+version SUNWprivate_1.1
+end
+
+function di_devlink_rm_link
+include <libdevinfo.h>
+declaration int di_devlink_rm_link(di_devlink_handle_t hdl, \
+ const char *link)
+version SUNWprivate_1.1
+end
+
+function di_devlink_add_link
+include <libdevinfo.h>
+declaration int di_devlink_add_link(di_devlink_handle_t hdl, \
+ const char *link, const char *content, int flags)
+version SUNWprivate_1.1
+end
+
+function di_devlink_update
+include <libdevinfo.h>
+declaration int di_devlink_update(di_devlink_handle_t hdl)
+version SUNWprivate_1.1
+end
+
+function di_devlink_init_root
+include <libdevinfo.h>
+declaration di_devlink_handle_t di_devlink_init_root(const char *root, \
+ const char *name, uint_t flags)
+version SUNWprivate_1.1
+end
+#
+# Consolidation private PSARC 1997/127
+#
+function di_init_impl
+include <libdevinfo.h>
+declaration di_node_t di_init_impl(const char *phys_path, uint_t flag, \
+ struct di_priv_data *priv)
+version SUNWprivate_1.1
+end
+
+function di_init_driver
+include <libdevinfo.h>
+declaration di_node_t di_init_driver(const char *drv_name, uint_t flag)
+version SUNWprivate_1.1
+end
+
+function di_prop_drv_next
+include <libdevinfo.h>
+declaration di_prop_t di_prop_drv_next(di_node_t node, di_prop_t prop)
+version SUNWprivate_1.1
+end
+
+function di_prop_sys_next
+include <libdevinfo.h>
+declaration di_prop_t di_prop_sys_next(di_node_t node, di_prop_t prop)
+version SUNWprivate_1.1
+end
+
+function di_prop_global_next
+include <libdevinfo.h>
+declaration di_prop_t di_prop_global_next(di_node_t node, di_prop_t prop)
+version SUNWprivate_1.1
+end
+
+function di_prop_hw_next
+include <libdevinfo.h>
+declaration di_prop_t di_prop_hw_next(di_node_t node, di_prop_t prop)
+version SUNWprivate_1.1
+end
+
+function di_prop_rawdata
+include <libdevinfo.h>
+declaration int di_prop_rawdata(di_prop_t prop, uchar_t **prop_data)
+version SUNWprivate_1.1
+end
+
+function di_parent_private_data
+include <libdevinfo.h>
+declaration void * di_parent_private_data(di_node_t node)
+version SUNWprivate_1.1
+end
+
+function di_driver_private_data
+include <libdevinfo.h>
+declaration void * di_driver_private_data(di_node_t node)
+version SUNWprivate_1.1
+end
+
+function di_node_state
+include <libdevinfo.h>
+declaration ddi_node_state_t di_node_state(di_node_t node)
+version SUNWprivate_1.1
+end
+
+
+#
+# Consolidation private PSARC 1999/647
+#
+# di_path_next is replaced by di_path_next_phci/client
+#
+function di_path_next
+include <libdevinfo.h>
+declaration di_path_t di_path_next(di_node_t node, di_path_t path)
+version SUNWprivate_1.1
+end
+
+function di_path_next_phci
+include <libdevinfo.h>
+declaration di_path_t di_path_next_phci(di_node_t node, di_path_t path)
+version SUNWprivate_1.1
+end
+
+function di_path_next_client
+include <libdevinfo.h>
+declaration di_path_t di_path_next_client(di_node_t node, di_path_t path)
+version SUNWprivate_1.1
+end
+
+#
+# Consolidation private PSARC 1999/647
+#
+function di_path_state
+include <libdevinfo.h>
+declaration di_path_state_t di_path_state(di_path_t path)
+version SUNWprivate_1.1
+end
+
+#
+# Consolidation private PSARC 1999/647
+#
+function di_path_addr
+include <libdevinfo.h>
+declaration char *di_path_addr(di_path_t path, char *buf)
+version SUNWprivate_1.1
+end
+
+#
+# Consolidation private PSARC 1999/647
+#
+function di_path_client_node
+include <libdevinfo.h>
+declaration di_node_t di_path_client_node(di_path_t path)
+version SUNWprivate_1.1
+end
+
+#
+# Consolidation private PSARC 1999/647
+#
+function di_path_phci_node
+include <libdevinfo.h>
+declaration di_node_t di_path_phci_node(di_path_t path)
+version SUNWprivate_1.1
+end
+
+#
+# Consolidation private PSARC 1999/647
+#
+function di_path_prop_next
+include <libdevinfo.h>
+declaration di_path_prop_t di_path_prop_next(di_path_t path, \
+ di_path_prop_t prop)
+version SUNWprivate_1.1
+end
+
+#
+# Consolidation private PSARC 1999/647
+#
+function di_path_prop_name
+include <libdevinfo.h>
+declaration char* di_path_prop_name(di_path_prop_t prop)
+version SUNWprivate_1.1
+end
+
+#
+# Consolidation private PSARC 1999/647
+#
+function di_path_prop_type
+include <libdevinfo.h>
+declaration int di_path_prop_type(di_path_prop_t prop)
+version SUNWprivate_1.1
+end
+
+#
+# Consolidation private PSARC 1999/647
+#
+function di_path_prop_len
+include <libdevinfo.h>
+declaration int di_path_prop_len(di_path_prop_t prop)
+version SUNWprivate_1.1
+end
+
+#
+# Consolidation private PSARC 1999/647
+#
+function di_path_prop_bytes
+include <libdevinfo.h>
+declaration int di_path_prop_bytes(di_path_prop_t prop, uchar_t **prop_data)
+version SUNWprivate_1.1
+end
+
+#
+# Consolidation private PSARC 1999/647
+#
+function di_path_prop_ints
+include <libdevinfo.h>
+declaration int di_path_prop_ints(di_path_prop_t prop, int **prop_data)
+version SUNWprivate_1.1
+end
+
+#
+# Consolidation private PSARC 1999/647
+#
+function di_path_prop_int64s
+include <libdevinfo.h>
+declaration int di_path_prop_int64s(di_path_prop_t prop, \
+ int64_t **prop_data)
+version SUNWprivate_1.1
+end
+
+#
+# Consolidation private PSARC 1999/647
+#
+function di_path_prop_strings
+include <libdevinfo.h>
+declaration int di_path_prop_strings(di_path_prop_t prop, char **prop_data)
+version SUNWprivate_1.1
+end
+
+#
+# Consolidation private PSARC 1999/647
+#
+function di_path_prop_lookup_bytes
+include <libdevinfo.h>
+declaration int di_path_prop_lookup_bytes(di_path_t path, \
+ const char *prop_name, uchar_t **prop_data)
+version SUNWprivate_1.1
+end
+
+#
+# Consolidation private PSARC 1999/647
+#
+function di_path_prop_lookup_ints
+include <libdevinfo.h>
+declaration int di_path_prop_lookup_ints(di_path_t path, \
+ const char *prop_name, int **prop_data)
+version SUNWprivate_1.1
+end
+
+#
+# Consolidation private PSARC 1999/647
+#
+function di_path_prop_lookup_int64s
+include <libdevinfo.h>
+declaration int di_path_prop_lookup_int64s(di_path_t path, \
+ const char *prop_name, int64_t **prop_data)
+version SUNWprivate_1.1
+end
+
+#
+# Consolidation private PSARC 1999/647
+#
+function di_path_prop_lookup_strings
+include <libdevinfo.h>
+declaration int di_path_prop_lookup_strings(di_path_t path, \
+ const char *prop_name, char **prop_data)
+version SUNWprivate_1.1
+end
+
+#
+# Project private (devfs project)
+#
+function di_minor_devinfo
+include <libdevinfo.h>
+declaration di_node_t di_minor_devinfo(di_minor_t minor)
+version SUNWprivate_1.1
+end
+
+#
+# Project private function (PSARC/2004/169)
+#
+function di_lookup_node
+include <libdevinfo.h>
+declaration di_node_t di_lookup_node(di_node_t root, char *path)
+version SUNWprivate_1.1
+end
+
+#
+# Project private function (devfsadmd)
+#
+function di_devlink_cache_walk
+include <libdevinfo.h>
+declaration int di_devlink_cache_walk(di_devlink_handle_t hdp, \
+ const char *re, const char *path, \
+ uint_t flags, void *arg, \
+ int (*devlink_callback)(di_devlink_t, void *))
+version SUNWprivate_1.1
+end
+
+#
+# Consolidation private PSARC 2003/612
+#
+function di_devperm_login
+include <libdevinfo.h>
+declaration int di_devperm_login(const char *ttyn, uid_t uid, gid_t gid, \
+ void (*errmsg)(char *errstring))
+version SUNWprivate_1.1
+end
+
+function di_devperm_logout
+include <libdevinfo.h>
+declaration int di_devperm_logout(const char *ttyn)
+version SUNWprivate_1.1
+end
+
+#
+# Private functions for solaris installation programs.
+#
+function devfs_target2install
+include <device_info.h>
+declaration int devfs_target2install(const char *rootdir, \
+ const char *devname, char *buf, size_t bufsz)
+version SUNWprivate_1.1
+end
+
+function devfs_install2target
+include <device_info.h>
+declaration int devfs_install2target(const char *rootdir, \
+ const char *devname, char *buf, size_t bufsz)
+version SUNWprivate_1.1
+end
+
+function devfs_read_minor_perm
+include <device_info.h>
+declaration struct mperm *devfs_read_minor_perm( \
+ void (*cb)(minorperm_err_t, int))
+version SUNWprivate_1.1
+end
+
+function devfs_free_minor_perm
+include <device_info.h>
+declaration void devfs_free_minor_perm(struct mperm *)
+version SUNWprivate_1.1
+end
+
+function devfs_load_minor_perm
+include <device_info.h>
+declaration int devfs_load_minor_perm(struct mperm *, \
+ void (*cb)(minorperm_err_t, int))
+version SUNWprivate_1.1
+end
+
+function devfs_add_minor_perm
+include <device_info.h>
+declaration int devfs_add_minor_perm(char *drv, \
+ void (*cb)(minorperm_err_t, int))
+version SUNWprivate_1.1
+end
+
+function devfs_rm_minor_perm
+include <device_info.h>
+declaration int devfs_rm_minor_perm(char *drv, \
+ void (*cb)(minorperm_err_t, int))
+version SUNWprivate_1.1
+end
diff --git a/usr/src/lib/libdevinfo/spec/i386/Makefile b/usr/src/lib/libdevinfo/spec/i386/Makefile
new file mode 100644
index 0000000000..2ac46c39dc
--- /dev/null
+++ b/usr/src/lib/libdevinfo/spec/i386/Makefile
@@ -0,0 +1,42 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright (c) 1997-1999 by Sun Microsystems, Inc.
+# All rights reserved.
+#
+#pragma ident "%Z%%M% %I% %E% SMI"
+#
+# lib/libdevinfo/spec/i386/Makefile
+
+include ../Makefile.targ
+
+# Add arch specific objects here
+OBJECTS +=
+
+include $(SRC)/lib/Makefile.lib
+
+# Uncomment the following if the linker complains
+#i386_C_PICFLAGS = -K PIC
+
+include $(SRC)/lib/Makefile.spec
+
+install: $(ROOTABILIB)
diff --git a/usr/src/lib/libdevinfo/spec/sparc/Makefile b/usr/src/lib/libdevinfo/spec/sparc/Makefile
new file mode 100644
index 0000000000..1b69ee2c55
--- /dev/null
+++ b/usr/src/lib/libdevinfo/spec/sparc/Makefile
@@ -0,0 +1,44 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright (c) 1997-1999 by Sun Microsystems, Inc.
+# All rights reserved.
+#
+#pragma ident "%Z%%M% %I% %E% SMI"
+#
+# lib/libdevinfo/spec/sparc/Makefile
+
+.KEEP_STATE:
+
+include ../Makefile.targ
+
+# Add arch specific objects here
+OBJECTS +=
+
+include $(SRC)/lib/Makefile.lib
+
+# Uncomment the following if the linker complains
+#sparc_C_PICFLAGS = -K PIC
+
+include $(SRC)/lib/Makefile.spec
+
+install: $(ROOTABILIB)
diff --git a/usr/src/lib/libdevinfo/spec/sparcv9/Makefile b/usr/src/lib/libdevinfo/spec/sparcv9/Makefile
new file mode 100644
index 0000000000..c0625153f3
--- /dev/null
+++ b/usr/src/lib/libdevinfo/spec/sparcv9/Makefile
@@ -0,0 +1,43 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright (c) 1997-1999 by Sun Microsystems, Inc.
+# All rights reserved.
+#
+#pragma ident "%Z%%M% %I% %E% SMI"
+#
+# lib/libdevinfo/spec/sparcv9/Makefile
+
+include ../Makefile.targ
+
+# Add arch specific objects here
+OBJECTS +=
+
+include $(SRC)/lib/Makefile.lib
+include $(SRC)/lib/Makefile.lib.64
+
+# Uncomment the following if the linker complains
+#sparcv9_C_PICFLAGS = -K PIC
+
+include $(SRC)/lib/Makefile.spec
+
+install: $(ROOTABILIB64)
diff --git a/usr/src/lib/libdevinfo/spec/versions b/usr/src/lib/libdevinfo/spec/versions
new file mode 100644
index 0000000000..1a698fbb9f
--- /dev/null
+++ b/usr/src/lib/libdevinfo/spec/versions
@@ -0,0 +1,55 @@
+#
+# Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# SUNW_1.3: Public (evolving) PSARC 2001/769 and PSARC 2003/537 (LDI)
+# SUNW_1.2: Public (evolving) PSARC 1997/302 (clustering DDI hooks)
+# SUNW_1.1: Public (evolving) PSARC 1997/127
+#
+
+sparc {
+ SUNW_1.3: {SUNW_1.2};
+ SUNW_1.2: {SUNW_1.1};
+ SUNW_1.1;
+ SUNWprivate_1.1;
+}
+sparcv9 {
+ SUNW_1.3: {SUNW_1.2};
+ SUNW_1.2: {SUNW_1.1};
+ SUNW_1.1;
+ SUNWprivate_1.1;
+}
+i386 {
+ SUNW_1.3: {SUNW_1.2};
+ SUNW_1.2: {SUNW_1.1};
+ SUNW_1.1;
+ SUNWprivate_1.1;
+}
+amd64 {
+ SUNW_1.3: {SUNW_1.2};
+ SUNW_1.2: {SUNW_1.1};
+ SUNW_1.1;
+ SUNWprivate_1.1;
+}