diff options
Diffstat (limited to 'usr/src/lib/libdevinfo')
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(®, re, REG_EXTENDED) != 0) + return (-1); + linkd.regp = ® + } + + 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(®); + } + + 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(®, re, REG_EXTENDED) != 0) + return (-1); + linkd.regp = ® + } + + if (minor_colon(path) == NULL) { + walk_cache_node(hdp, path, &linkd); + } else { + walk_cache_minor(hdp, path, &linkd); + } + + if (re) + regfree(®); + + 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(®ex, 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(®ex, 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(®ex); + } + } + + 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; +} |