2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A/*
2N/A * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A#include "libdevinfo.h"
2N/A#include "devinfo_devlink.h"
2N/A#include "device_info.h"
2N/A#include <syslog.h>
2N/A#include <sys/modctl.h>
2N/A#include <sys/wait.h>
2N/A#include <ucred.h>
2N/A#include <priv.h>
2N/A
2N/A#undef DEBUG
2N/A#ifndef DEBUG
2N/A#define NDEBUG 1
2N/A#else
2N/A#undef NDEBUG
2N/A#endif
2N/A
2N/A#include <assert.h>
2N/A
2N/Astatic mutex_t update_mutex = DEFAULTMUTEX; /* Protects update record lock */
2N/Astatic mutex_t temp_file_mutex = DEFAULTMUTEX; /* for file creation tests */
2N/A
2N/Astatic const size_t elem_sizes[DB_TYPES] = {
2N/A sizeof (struct db_node),
2N/A sizeof (struct db_minor),
2N/A sizeof (struct db_link),
2N/A sizeof (char)
2N/A};
2N/A
2N/A/*
2N/A * List of directories/files skipped while physically walking /dev
2N/A * Paths are relative to "<root>/dev/"
2N/A */
2N/Astatic const char *skip_dirs[] = {"fd"};
2N/Astatic const char *skip_files[] = {
2N/A "stdout",
2N/A "stdin",
2N/A "stderr"
2N/A};
2N/A
2N/A#define N_SKIP_DIRS (sizeof (skip_dirs) / sizeof (skip_dirs[0]))
2N/A#define N_SKIP_FILES (sizeof (skip_files) / sizeof (skip_files[0]))
2N/A
2N/A#define DI_TEST_DB ETCDEV "di_test_db"
2N/A
2N/A/*
2N/A *
2N/A * This file contains two sets of interfaces which operate on the reverse
2N/A * links database. One set (which includes di_devlink_open()/_close())
2N/A * allows link generators like devfsadm(1M) and ucblinks(1B) (writers) to
2N/A * populate the database with /devices -> /dev mappings. Another set
2N/A * of interfaces (which includes di_devlink_init()/_fini()) allows
2N/A * applications (readers) to lookup the database for /dev links corresponding
2N/A * to a given minor.
2N/A *
2N/A * Writers operate on a cached version of the database. The cache is created
2N/A * when di_devlink_open() is called. As links in /dev are created and removed,
2N/A * the cache is updated to keep it in synch with /dev. When the /dev updates
2N/A * are complete, the link generator calls di_devlink_close() which writes
2N/A * out the cache to the database.
2N/A *
2N/A * Applications which need to lookup the database, call di_devlink_init().
2N/A * di_devlink_init() checks the database file (if one exists). If the
2N/A * database is valid, it is mapped into the address space of the
2N/A * application. The database file consists of several segments. Each
2N/A * segment can be mapped in independently and is mapped on demand.
2N/A *
2N/A * Database Layout
2N/A *
2N/A * ---------------------
2N/A * | Magic # |
2N/A * | ----------------- |
2N/A * | Version | HEADER
2N/A * | ----------------- |
2N/A * | ... |
2N/A * ---------------------
2N/A * | |
2N/A * | | NODES
2N/A * | |
2N/A * | |
2N/A * ---------------------
2N/A * | |
2N/A * | | MINORS
2N/A * | |
2N/A * | |
2N/A * ---------------------
2N/A * | |
2N/A * | | LINKS
2N/A * | |
2N/A * | |
2N/A * ---------------------
2N/A * | |
2N/A * | | STRINGS
2N/A * | |
2N/A * | |
2N/A * ---------------------
2N/A *
2N/A * Readers can lookup /dev links for a specific minor or
2N/A * lookup all /dev links. In the latter case, the node
2N/A * and minor segments are not mapped in and the reader
2N/A * walks through every link in the link segment.
2N/A *
2N/A */
2N/Adi_devlink_handle_t
2N/Adi_devlink_open(const char *root_dir, uint_t flags)
2N/A{
2N/A int err;
2N/A char path[PATH_MAX];
2N/A struct di_devlink_handle *hdp;
2N/A int retried = 0;
2N/A
2N/Aretry:
2N/A /*
2N/A * Allocate a read-write handle but open the DB in readonly
2N/A * mode. We do writes only to a temporary copy of the database.
2N/A */
2N/A if ((hdp = handle_alloc(root_dir, OPEN_RDWR)) == NULL) {
2N/A return (NULL);
2N/A }
2N/A
2N/A err = open_db(hdp, OPEN_RDONLY);
2N/A
2N/A /*
2N/A * We don't want to unlink the db at this point - if we did we
2N/A * would be creating a window where consumers would take a slow
2N/A * code path (and those consumers might also trigger requests for
2N/A * db creation, which we are already in the process of doing).
2N/A * When we are done with our update, we use rename to install the
2N/A * latest version of the db file.
2N/A */
2N/A get_db_path(hdp, DB_FILE, path, sizeof (path));
2N/A
2N/A /*
2N/A * The flags argument is reserved for future use.
2N/A */
2N/A if (flags != 0) {
2N/A handle_free(&hdp); /* also closes the DB */
2N/A errno = EINVAL;
2N/A return (NULL);
2N/A }
2N/A
2N/A if (cache_alloc(hdp) != 0) {
2N/A handle_free(&hdp);
2N/A return (NULL);
2N/A }
2N/A
2N/A if (err) {
2N/A /*
2N/A * Failed to open DB.
2N/A * The most likely cause is that DB file did not exist.
2N/A * Call di_devlink_close() to recreate the DB file and
2N/A * retry di_devlink_open().
2N/A */
2N/A if (retried == 0) {
2N/A (void) di_devlink_close(&hdp, 0);
2N/A retried = 1;
2N/A goto retry;
2N/A }
2N/A
2N/A /*
2N/A * DB cannot be opened, just return the
2N/A * handle. We will recreate the DB later.
2N/A */
2N/A return (hdp);
2N/A }
2N/A
2N/A /* Read the database into the cache */
2N/A CACHE(hdp)->update_count = DB_HDR(hdp)->update_count;
2N/A (void) read_nodes(hdp, NULL, DB_HDR(hdp)->root_idx);
2N/A (void) read_links(hdp, NULL, DB_HDR(hdp)->dngl_idx);
2N/A
2N/A (void) close_db(hdp);
2N/A
2N/A return (hdp);
2N/A}
2N/A
2N/Astatic void
2N/Aget_db_path(
2N/A struct di_devlink_handle *hdp,
2N/A const char *fname,
2N/A char *buf,
2N/A size_t blen)
2N/A{
2N/A char *dir = NULL;
2N/A
2N/A#ifdef DEBUG
2N/A if (dir = getenv(ALT_DB_DIR)) {
2N/A (void) dprintf(DBG_INFO, "get_db_path: alternate db dir: %s\n",
2N/A dir);
2N/A }
2N/A#endif
2N/A if (dir == NULL) {
2N/A dir = hdp->db_dir;
2N/A }
2N/A
2N/A (void) snprintf(buf, blen, "%s/%s", dir, fname);
2N/A}
2N/A
2N/Astatic int
2N/Aopen_db(struct di_devlink_handle *hdp, int flags)
2N/A{
2N/A size_t sz;
2N/A long page_sz;
2N/A int fd, rv, flg;
2N/A struct stat sbuf;
2N/A uint32_t count[DB_TYPES] = {0};
2N/A char path[PATH_MAX];
2N/A void *cp;
2N/A
2N/A assert(!DB_OPEN(hdp));
2N/A
2N/A#ifdef DEBUG
2N/A if (getenv(SKIP_DB)) {
2N/A (void) dprintf(DBG_INFO, "open_db: skipping database\n");
2N/A return (-1);
2N/A }
2N/A#endif
2N/A if ((page_sz = sysconf(_SC_PAGE_SIZE)) == -1) {
2N/A return (-1);
2N/A }
2N/A
2N/A /*
2N/A * Use O_TRUNC flag for write access, so that the subsequent ftruncate()
2N/A * call will zero-fill the entire file
2N/A */
2N/A if (IS_RDONLY(flags)) {
2N/A flg = O_RDONLY;
2N/A get_db_path(hdp, DB_FILE, path, sizeof (path));
2N/A } else {
2N/A flg = O_RDWR|O_CREAT|O_TRUNC;
2N/A get_db_path(hdp, DB_TMP, path, sizeof (path));
2N/A }
2N/A
2N/A /*
2N/A * Avoid triggering /dev reconfigure for read when not present
2N/A */
2N/A if (IS_RDONLY(flags) &&
2N/A (strncmp(path, "/dev/", 5) == 0) && !device_exists(path)) {
2N/A return (-1);
2N/A }
2N/A
2N/A if ((fd = open(path, flg, DB_PERMS)) == -1) {
2N/A return (-1);
2N/A }
2N/A
2N/A if (IS_RDONLY(flags)) {
2N/A flg = PROT_READ;
2N/A rv = fstat(fd, &sbuf);
2N/A sz = sbuf.st_size;
2N/A } else {
2N/A flg = PROT_READ | PROT_WRITE;
2N/A sz = size_db(hdp, page_sz, count);
2N/A rv = ftruncate(fd, sz);
2N/A }
2N/A
2N/A if (rv == -1 || sz < HDR_LEN) {
2N/A if (rv != -1)
2N/A errno = EINVAL;
2N/A (void) close(fd);
2N/A return (-1);
2N/A }
2N/A
2N/A cp = mmap(0, HDR_LEN, flg, MAP_SHARED, fd, 0);
2N/A if (cp == MAP_FAILED) {
2N/A (void) close(fd);
2N/A return (-1);
2N/A }
2N/A DB(hdp)->hdr = (struct db_hdr *)cp;
2N/A DB(hdp)->db_fd = fd;
2N/A DB(hdp)->flags = flags;
2N/A
2N/A if (IS_RDONLY(flags)) {
2N/A rv = invalid_db(hdp, sz, page_sz);
2N/A } else {
2N/A rv = init_hdr(hdp, page_sz, count);
2N/A }
2N/A
2N/A if (rv) {
2N/A (void) dprintf(DBG_ERR, "open_db: invalid DB(%s)\n", path);
2N/A (void) close_db(hdp);
2N/A return (-1);
2N/A } else {
2N/A (void) dprintf(DBG_STEP, "open_db: DB(%s): opened\n", path);
2N/A return (0);
2N/A }
2N/A}
2N/A
2N/A/*
2N/A * A handle can be allocated for read-only or read-write access
2N/A */
2N/Astatic struct di_devlink_handle *
2N/Ahandle_alloc(const char *root_dir, uint_t flags)
2N/A{
2N/A char dev_dir[PATH_MAX], path[PATH_MAX], db_dir[PATH_MAX];
2N/A struct di_devlink_handle *hdp, proto = {0};
2N/A int install = 0;
2N/A int isroot = 0;
2N/A struct stat sb;
2N/A char can_path[PATH_MAX];
2N/A
2N/A assert(flags == OPEN_RDWR || flags == OPEN_RDONLY);
2N/A
2N/A dev_dir[0] = '\0';
2N/A db_dir[0] = '\0';
2N/A
2N/A /*
2N/A * NULL and the empty string are equivalent to "/"
2N/A */
2N/A if (root_dir && root_dir[0] != '\0') {
2N/A
2N/A if (root_dir[0] != '/') {
2N/A errno = EINVAL;
2N/A return (NULL);
2N/A }
2N/A
2N/A#ifdef DEBUG
2N/A /*LINTED*/
2N/A assert(sizeof (dev_dir) >= PATH_MAX);
2N/A#endif
2N/A if ((realpath(root_dir, dev_dir) == NULL) ||
2N/A (realpath(root_dir, db_dir) == NULL)) {
2N/A return (NULL);
2N/A }
2N/A } else {
2N/A /*
2N/A * The dev dir is at /dev i.e. we are not doing a -r /altroot
2N/A */
2N/A isroot = 1;
2N/A }
2N/A
2N/A if (strcmp(dev_dir, "/") == 0) {
2N/A dev_dir[0] = 0;
2N/A db_dir[0] = 0;
2N/A } else {
2N/A (void) strlcpy(db_dir, dev_dir, sizeof (db_dir));
2N/A }
2N/A
2N/A (void) strlcat(dev_dir, DEV, sizeof (dev_dir));
2N/A (void) strlcat(db_dir, ETCDEV, sizeof (db_dir));
2N/A
2N/A /*
2N/A * The following code is for install. Readers and writers need
2N/A * to be redirected to /tmp/etc/dev for the database file.
2N/A * Note that we test for readonly /etc by actually creating a
2N/A * file since statvfs is not a reliable method for determining
2N/A * readonly filesystems.
2N/A */
2N/A install = 0;
2N/A (void) snprintf(can_path, sizeof (can_path), "%s/%s", ETCDEV, DB_FILE);
2N/A if (flags == OPEN_RDWR && isroot) {
2N/A char di_test_db[PATH_MAX];
2N/A int fd;
2N/A (void) mutex_lock(&temp_file_mutex);
2N/A (void) snprintf(di_test_db, sizeof (di_test_db), "%s.%d",
2N/A DI_TEST_DB, getpid());
2N/A fd = open(di_test_db, O_CREAT|O_RDWR|O_EXCL, 0644);
2N/A if (fd == -1 && errno == EROFS && stat(can_path, &sb) == -1)
2N/A install = 1;
2N/A if (fd != -1) {
2N/A (void) close(fd);
2N/A (void) unlink(di_test_db);
2N/A }
2N/A (void) mutex_unlock(&temp_file_mutex);
2N/A } else if (isroot) {
2N/A /*
2N/A * Readers can be non-privileged so we cannot test by creating
2N/A * a file in /etc/dev. Instead we check if the database
2N/A * file is missing in /etc/dev and is present in /tmp/etc/dev
2N/A * and is owned by root.
2N/A */
2N/A char install_path[PATH_MAX];
2N/A
2N/A (void) snprintf(install_path, sizeof (install_path),
2N/A "/tmp%s/%s", ETCDEV, DB_FILE);
2N/A if (stat(can_path, &sb) == -1 && stat(install_path, &sb)
2N/A != -1 && sb.st_uid == 0) {
2N/A install = 1;
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * Check if we are in install. If we are, the database will be in
2N/A * /tmp/etc/dev
2N/A */
2N/A if (install)
2N/A (void) snprintf(db_dir, sizeof (db_dir), "/tmp%s", ETCDEV);
2N/A
2N/A proto.dev_dir = dev_dir;
2N/A proto.db_dir = db_dir;
2N/A proto.flags = flags;
2N/A proto.lock_fd = -1;
2N/A
2N/A /*
2N/A * Lock database if a read-write handle is being allocated.
2N/A * Locks are needed to protect against multiple writers.
2N/A * Readers don't need locks.
2N/A */
2N/A if (HDL_RDWR(&proto)) {
2N/A if (enter_db_lock(&proto, root_dir) != 1) {
2N/A return (NULL);
2N/A }
2N/A }
2N/A
2N/A DB(&proto)->db_fd = -1;
2N/A
2N/A hdp = calloc(1, sizeof (struct di_devlink_handle));
2N/A if (hdp == NULL) {
2N/A goto error;
2N/A }
2N/A
2N/A *hdp = proto;
2N/A
2N/A /*
2N/A * The handle hdp now contains a pointer to local storage
2N/A * in the dev_dir field (obtained from the proto handle).
2N/A * In the following line, a dynamically allocated version
2N/A * is substituted.
2N/A */
2N/A
2N/A if ((hdp->dev_dir = strdup(proto.dev_dir)) == NULL) {
2N/A free(hdp);
2N/A goto error;
2N/A }
2N/A
2N/A if ((hdp->db_dir = strdup(proto.db_dir)) == NULL) {
2N/A free(hdp->dev_dir);
2N/A free(hdp);
2N/A goto error;
2N/A }
2N/A
2N/A return (hdp);
2N/A
2N/Aerror:
2N/A if (HDL_RDWR(&proto)) {
2N/A /* Unlink DB file on error */
2N/A get_db_path(&proto, DB_FILE, path, sizeof (path));
2N/A (void) unlink(path);
2N/A exit_db_lock(&proto);
2N/A }
2N/A return (NULL);
2N/A}
2N/A
2N/A
2N/Astatic int
2N/Acache_alloc(struct di_devlink_handle *hdp)
2N/A{
2N/A size_t hash_sz = 0;
2N/A
2N/A assert(HDL_RDWR(hdp));
2N/A
2N/A if (DB_OPEN(hdp)) {
2N/A hash_sz = DB_NUM(hdp, DB_LINK) / AVG_CHAIN_SIZE;
2N/A }
2N/A hash_sz = (hash_sz >= MIN_HASH_SIZE) ? hash_sz : MIN_HASH_SIZE;
2N/A
2N/A CACHE(hdp)->hash = calloc(hash_sz, sizeof (cache_link_t *));
2N/A if (CACHE(hdp)->hash == NULL) {
2N/A return (-1);
2N/A }
2N/A CACHE(hdp)->hash_sz = hash_sz;
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A
2N/Astatic int
2N/Ainvalid_db(struct di_devlink_handle *hdp, size_t fsize, long page_sz)
2N/A{
2N/A int i;
2N/A char *cp;
2N/A size_t sz;
2N/A
2N/A if (DB_HDR(hdp)->magic != DB_MAGIC || DB_HDR(hdp)->vers != DB_VERSION) {
2N/A return (1);
2N/A }
2N/A
2N/A if (DB_HDR(hdp)->page_sz == 0 || DB_HDR(hdp)->page_sz != page_sz) {
2N/A return (1);
2N/A }
2N/A
2N/A sz = seg_size(hdp, DB_HEADER);
2N/A for (i = 0; i < DB_TYPES; i++) {
2N/A (void) dprintf(DBG_INFO, "N[%u] = %u\n", i, DB_NUM(hdp, i));
2N/A /* There must be at least 1 element of each type */
2N/A if (DB_NUM(hdp, i) < 1) {
2N/A return (1);
2N/A }
2N/A sz += seg_size(hdp, i);
2N/A assert(sz % page_sz == 0);
2N/A }
2N/A
2N/A if (sz != fsize) {
2N/A return (1);
2N/A }
2N/A
2N/A if (!VALID_INDEX(hdp, DB_NODE, DB_HDR(hdp)->root_idx)) {
2N/A return (1);
2N/A }
2N/A
2N/A if (!VALID_INDEX(hdp, DB_LINK, DB_HDR(hdp)->dngl_idx)) {
2N/A return (1);
2N/A }
2N/A
2N/A if (DB_EMPTY(hdp)) {
2N/A return (1);
2N/A }
2N/A
2N/A /*
2N/A * The last character in the string segment must be a NUL char.
2N/A */
2N/A cp = get_string(hdp, DB_NUM(hdp, DB_STR) - 1);
2N/A if (cp == NULL || *cp != '\0') {
2N/A return (1);
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/Astatic int
2N/Aread_nodes(struct di_devlink_handle *hdp, cache_node_t *pcnp, uint32_t nidx)
2N/A{
2N/A char *path;
2N/A cache_node_t *cnp;
2N/A struct db_node *dnp;
2N/A const char *fcn = "read_nodes";
2N/A
2N/A assert(HDL_RDWR(hdp));
2N/A
2N/A /*
2N/A * parent node should be NULL only for the root node
2N/A */
2N/A if ((pcnp == NULL) ^ (nidx == DB_HDR(hdp)->root_idx)) {
2N/A (void) dprintf(DBG_ERR, "%s: invalid parent or index(%u)\n",
2N/A fcn, nidx);
2N/A SET_DB_ERR(hdp);
2N/A return (-1);
2N/A }
2N/A
2N/A for (; dnp = get_node(hdp, nidx); nidx = dnp->sib) {
2N/A
2N/A path = get_string(hdp, dnp->path);
2N/A
2N/A /*
2N/A * Insert at head of list to recreate original order
2N/A */
2N/A cnp = node_insert(hdp, pcnp, path, INSERT_HEAD);
2N/A if (cnp == NULL) {
2N/A SET_DB_ERR(hdp);
2N/A break;
2N/A }
2N/A
2N/A assert(strcmp(path, "/") ^ (nidx == DB_HDR(hdp)->root_idx));
2N/A assert(strcmp(path, "/") != 0 || dnp->sib == DB_NIL);
2N/A
2N/A if (read_minors(hdp, cnp, dnp->minor) != 0 ||
2N/A read_nodes(hdp, cnp, dnp->child) != 0) {
2N/A break;
2N/A }
2N/A
2N/A (void) dprintf(DBG_STEP, "%s: node[%u]: %s\n", fcn, nidx,
2N/A cnp->path);
2N/A }
2N/A
2N/A return (dnp ? -1 : 0);
2N/A}
2N/A
2N/Astatic int
2N/Aread_minors(struct di_devlink_handle *hdp, cache_node_t *pcnp, uint32_t nidx)
2N/A{
2N/A cache_minor_t *cmnp;
2N/A struct db_minor *dmp;
2N/A char *name, *nodetype;
2N/A const char *fcn = "read_minors";
2N/A
2N/A assert(HDL_RDWR(hdp));
2N/A
2N/A if (pcnp == NULL) {
2N/A (void) dprintf(DBG_ERR, "%s: minor[%u]: orphan minor\n", fcn,
2N/A nidx);
2N/A SET_DB_ERR(hdp);
2N/A return (-1);
2N/A }
2N/A
2N/A for (; dmp = get_minor(hdp, nidx); nidx = dmp->sib) {
2N/A
2N/A name = get_string(hdp, dmp->name);
2N/A nodetype = get_string(hdp, dmp->nodetype);
2N/A
2N/A cmnp = minor_insert(hdp, pcnp, name, nodetype, NULL);
2N/A if (cmnp == NULL) {
2N/A SET_DB_ERR(hdp);
2N/A break;
2N/A }
2N/A
2N/A (void) dprintf(DBG_STEP, "%s: minor[%u]: %s\n", fcn, nidx,
2N/A cmnp->name);
2N/A
2N/A if (read_links(hdp, cmnp, dmp->link) != 0) {
2N/A break;
2N/A }
2N/A }
2N/A
2N/A return (dmp ? -1 : 0);
2N/A}
2N/A
2N/A/*
2N/A * If the link is dangling the corresponding minor will be absent.
2N/A */
2N/Astatic int
2N/Aread_links(struct di_devlink_handle *hdp, cache_minor_t *pcmp, uint32_t nidx)
2N/A{
2N/A cache_link_t *clp;
2N/A struct db_link *dlp;
2N/A char *path, *content;
2N/A
2N/A assert(HDL_RDWR(hdp));
2N/A
2N/A if (nidx != DB_NIL &&
2N/A ((pcmp == NULL) ^ (nidx == DB_HDR(hdp)->dngl_idx))) {
2N/A (void) dprintf(DBG_ERR, "read_links: invalid minor or"
2N/A " index(%u)\n", nidx);
2N/A SET_DB_ERR(hdp);
2N/A return (-1);
2N/A }
2N/A
2N/A for (; dlp = get_link(hdp, nidx); nidx = dlp->sib) {
2N/A
2N/A path = get_string(hdp, dlp->path);
2N/A content = get_string(hdp, dlp->content);
2N/A
2N/A clp = link_insert(hdp, pcmp, path, content, dlp->attr);
2N/A if (clp == NULL) {
2N/A SET_DB_ERR(hdp);
2N/A break;
2N/A }
2N/A
2N/A (void) dprintf(DBG_STEP, "read_links: link[%u]: %s%s\n",
2N/A nidx, clp->path, pcmp == NULL ? "(DANGLING)" : "");
2N/A }
2N/A
2N/A return (dlp ? -1 : 0);
2N/A}
2N/A
2N/Aint
2N/Adi_devlink_close(di_devlink_handle_t *pp, int flag)
2N/A{
2N/A int i, rv;
2N/A char tmp[PATH_MAX];
2N/A char file[PATH_MAX];
2N/A uint32_t next[DB_TYPES] = {0};
2N/A struct di_devlink_handle *hdp;
2N/A
2N/A if (pp == NULL || *pp == NULL || !HDL_RDWR(*pp)) {
2N/A errno = EINVAL;
2N/A return (-1);
2N/A }
2N/A
2N/A hdp = *pp;
2N/A *pp = NULL;
2N/A
2N/A /*
2N/A * The caller encountered some error in their processing.
2N/A * so handle isn't valid. Discard it and return success.
2N/A */
2N/A if (flag == DI_LINK_ERROR) {
2N/A handle_free(&hdp);
2N/A return (0);
2N/A }
2N/A
2N/A if (DB_ERR(hdp)) {
2N/A handle_free(&hdp);
2N/A errno = EINVAL;
2N/A return (-1);
2N/A }
2N/A
2N/A /*
2N/A * Extract the DB path before the handle is freed.
2N/A */
2N/A get_db_path(hdp, DB_FILE, file, sizeof (file));
2N/A get_db_path(hdp, DB_TMP, tmp, sizeof (tmp));
2N/A
2N/A /*
2N/A * update database with actual contents of /dev
2N/A */
2N/A (void) dprintf(DBG_INFO, "di_devlink_close: update_count = %u\n",
2N/A CACHE(hdp)->update_count);
2N/A
2N/A /*
2N/A * For performance reasons, synchronization of the database
2N/A * with /dev is turned off by default. However, applications
2N/A * with appropriate permissions can request a "sync" by
2N/A * calling di_devlink_update().
2N/A */
2N/A if (CACHE(hdp)->update_count == 0) {
2N/A CACHE(hdp)->update_count = 1;
2N/A (void) dprintf(DBG_INFO,
2N/A "di_devlink_close: synchronizing DB\n");
2N/A (void) synchronize_db(hdp);
2N/A }
2N/A
2N/A /*
2N/A * Resolve dangling links AFTER synchronizing DB with /dev as the
2N/A * synchronization process may create dangling links.
2N/A */
2N/A resolve_dangling_links(hdp);
2N/A
2N/A /*
2N/A * All changes to the cache are complete. Write out the cache
2N/A * to the database only if it is not empty.
2N/A */
2N/A if (CACHE_EMPTY(hdp)) {
2N/A (void) dprintf(DBG_INFO, "di_devlink_close: skipping write\n");
2N/A (void) unlink(file);
2N/A handle_free(&hdp);
2N/A return (0);
2N/A }
2N/A
2N/A if (open_db(hdp, OPEN_RDWR) != 0) {
2N/A handle_free(&hdp);
2N/A return (-1);
2N/A }
2N/A
2N/A /*
2N/A * Keep track of array assignments. There is at least
2N/A * 1 element (the "NIL" element) per type.
2N/A */
2N/A for (i = 0; i < DB_TYPES; i++) {
2N/A next[i] = 1;
2N/A }
2N/A
2N/A (void) write_nodes(hdp, NULL, CACHE_ROOT(hdp), next);
2N/A (void) write_links(hdp, NULL, CACHE(hdp)->dngl, next);
2N/A DB_HDR(hdp)->update_count = CACHE(hdp)->update_count;
2N/A
2N/A rv = close_db(hdp);
2N/A
2N/A if (rv != 0 || DB_ERR(hdp) || rename(tmp, file) != 0) {
2N/A (void) dprintf(DBG_ERR, "di_devlink_close: %s error: %s\n",
2N/A rv ? "close_db" : "DB or rename", strerror(errno));
2N/A (void) unlink(tmp);
2N/A (void) unlink(file);
2N/A handle_free(&hdp);
2N/A return (-1);
2N/A }
2N/A
2N/A handle_free(&hdp);
2N/A
2N/A (void) dprintf(DBG_INFO, "di_devlink_close: wrote DB(%s)\n", file);
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Inits the database header.
2N/A */
2N/Astatic int
2N/Ainit_hdr(struct di_devlink_handle *hdp, long page_sz, uint32_t *count)
2N/A{
2N/A int i;
2N/A
2N/A DB_HDR(hdp)->magic = DB_MAGIC;
2N/A DB_HDR(hdp)->vers = DB_VERSION;
2N/A DB_HDR(hdp)->root_idx = DB_NIL;
2N/A DB_HDR(hdp)->dngl_idx = DB_NIL;
2N/A DB_HDR(hdp)->page_sz = (uint32_t)page_sz;
2N/A
2N/A for (i = 0; i < DB_TYPES; i++) {
2N/A assert(count[i] >= 1);
2N/A DB_NUM(hdp, i) = count[i];
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/Astatic int
2N/Awrite_nodes(
2N/A struct di_devlink_handle *hdp,
2N/A struct db_node *pdnp,
2N/A cache_node_t *cnp,
2N/A uint32_t *next)
2N/A{
2N/A uint32_t idx;
2N/A struct db_node *dnp;
2N/A const char *fcn = "write_nodes";
2N/A
2N/A assert(HDL_RDWR(hdp));
2N/A
2N/A for (; cnp != NULL; cnp = cnp->sib) {
2N/A
2N/A assert(cnp->path != NULL);
2N/A
2N/A /* parent node should only be NULL for root node */
2N/A if ((pdnp == NULL) ^ (cnp == CACHE_ROOT(hdp))) {
2N/A (void) dprintf(DBG_ERR, "%s: invalid parent for: %s\n",
2N/A fcn, cnp->path);
2N/A SET_DB_ERR(hdp);
2N/A break;
2N/A }
2N/A
2N/A assert((strcmp(cnp->path, "/") != 0) ^
2N/A (cnp == CACHE_ROOT(hdp)));
2N/A
2N/A idx = next[DB_NODE];
2N/A if ((dnp = set_node(hdp, idx)) == NULL) {
2N/A SET_DB_ERR(hdp);
2N/A break;
2N/A }
2N/A
2N/A dnp->path = write_string(hdp, cnp->path, next);
2N/A if (dnp->path == DB_NIL) {
2N/A SET_DB_ERR(hdp);
2N/A break;
2N/A }
2N/A /* commit write for this node */
2N/A next[DB_NODE]++;
2N/A
2N/A if (pdnp == NULL) {
2N/A assert(DB_HDR(hdp)->root_idx == DB_NIL);
2N/A DB_HDR(hdp)->root_idx = idx;
2N/A } else {
2N/A dnp->sib = pdnp->child;
2N/A pdnp->child = idx;
2N/A }
2N/A
2N/A (void) dprintf(DBG_STEP, "%s: node[%u]: %s\n", fcn, idx,
2N/A cnp->path);
2N/A
2N/A if (write_minors(hdp, dnp, cnp->minor, next) != 0 ||
2N/A write_nodes(hdp, dnp, cnp->child, next) != 0) {
2N/A break;
2N/A }
2N/A }
2N/A
2N/A return (cnp ? -1 : 0);
2N/A}
2N/A
2N/Astatic int
2N/Awrite_minors(
2N/A struct di_devlink_handle *hdp,
2N/A struct db_node *pdnp,
2N/A cache_minor_t *cmnp,
2N/A uint32_t *next)
2N/A{
2N/A uint32_t idx;
2N/A struct db_minor *dmp;
2N/A const char *fcn = "write_minors";
2N/A
2N/A assert(HDL_RDWR(hdp));
2N/A
2N/A if (pdnp == NULL) {
2N/A (void) dprintf(DBG_ERR, "%s: no node for minor: %s\n", fcn,
2N/A cmnp ? cmnp->name : "<NULL>");
2N/A SET_DB_ERR(hdp);
2N/A return (-1);
2N/A }
2N/A
2N/A for (; cmnp != NULL; cmnp = cmnp->sib) {
2N/A
2N/A assert(cmnp->name != NULL);
2N/A
2N/A idx = next[DB_MINOR];
2N/A if ((dmp = set_minor(hdp, idx)) == NULL) {
2N/A SET_DB_ERR(hdp);
2N/A break;
2N/A }
2N/A
2N/A dmp->name = write_string(hdp, cmnp->name, next);
2N/A dmp->nodetype = write_string(hdp, cmnp->nodetype, next);
2N/A if (dmp->name == DB_NIL || dmp->nodetype == DB_NIL) {
2N/A dmp->name = dmp->nodetype = DB_NIL;
2N/A SET_DB_ERR(hdp);
2N/A break;
2N/A }
2N/A
2N/A /* Commit writes to this minor */
2N/A next[DB_MINOR]++;
2N/A
2N/A dmp->sib = pdnp->minor;
2N/A pdnp->minor = idx;
2N/A
2N/A (void) dprintf(DBG_STEP, "%s: minor[%u]: %s\n", fcn, idx,
2N/A cmnp->name);
2N/A
2N/A if (write_links(hdp, dmp, cmnp->link, next) != 0) {
2N/A break;
2N/A }
2N/A }
2N/A
2N/A return (cmnp ? -1 : 0);
2N/A}
2N/A
2N/Astatic int
2N/Awrite_links(
2N/A struct di_devlink_handle *hdp,
2N/A struct db_minor *pdmp,
2N/A cache_link_t *clp,
2N/A uint32_t *next)
2N/A{
2N/A uint32_t idx;
2N/A struct db_link *dlp;
2N/A const char *fcn = "write_links";
2N/A
2N/A assert(HDL_RDWR(hdp));
2N/A
2N/A /* A NULL minor if and only if the links are dangling */
2N/A if (clp != NULL && ((pdmp == NULL) ^ (clp == CACHE(hdp)->dngl))) {
2N/A (void) dprintf(DBG_ERR, "%s: invalid minor for link\n", fcn);
2N/A SET_DB_ERR(hdp);
2N/A return (-1);
2N/A }
2N/A
2N/A for (; clp != NULL; clp = clp->sib) {
2N/A
2N/A assert(clp->path != NULL);
2N/A
2N/A if ((pdmp == NULL) ^ (clp->minor == NULL)) {
2N/A (void) dprintf(DBG_ERR, "%s: invalid minor for link"
2N/A "(%s)\n", fcn, clp->path);
2N/A SET_DB_ERR(hdp);
2N/A break;
2N/A }
2N/A
2N/A idx = next[DB_LINK];
2N/A if ((dlp = set_link(hdp, idx)) == NULL) {
2N/A SET_DB_ERR(hdp);
2N/A break;
2N/A }
2N/A
2N/A dlp->path = write_string(hdp, clp->path, next);
2N/A dlp->content = write_string(hdp, clp->content, next);
2N/A if (dlp->path == DB_NIL || dlp->content == DB_NIL) {
2N/A dlp->path = dlp->content = DB_NIL;
2N/A SET_DB_ERR(hdp);
2N/A break;
2N/A }
2N/A
2N/A dlp->attr = clp->attr;
2N/A
2N/A /* Commit writes to this link */
2N/A next[DB_LINK]++;
2N/A
2N/A if (pdmp != NULL) {
2N/A dlp->sib = pdmp->link;
2N/A pdmp->link = idx;
2N/A } else {
2N/A dlp->sib = DB_HDR(hdp)->dngl_idx;
2N/A DB_HDR(hdp)->dngl_idx = idx;
2N/A }
2N/A
2N/A (void) dprintf(DBG_STEP, "%s: link[%u]: %s%s\n", fcn, idx,
2N/A clp->path, pdmp == NULL ? "(DANGLING)" : "");
2N/A }
2N/A
2N/A return (clp ? -1 : 0);
2N/A}
2N/A
2N/A
2N/Astatic uint32_t
2N/Awrite_string(struct di_devlink_handle *hdp, const char *str, uint32_t *next)
2N/A{
2N/A char *dstr;
2N/A uint32_t idx;
2N/A
2N/A assert(HDL_RDWR(hdp));
2N/A
2N/A if (str == NULL) {
2N/A (void) dprintf(DBG_ERR, "write_string: NULL argument\n");
2N/A return (DB_NIL);
2N/A }
2N/A
2N/A idx = next[DB_STR];
2N/A if (!VALID_STR(hdp, idx, str)) {
2N/A (void) dprintf(DBG_ERR, "write_string: invalid index[%u],"
2N/A " string(%s)\n", idx, str);
2N/A return (DB_NIL);
2N/A }
2N/A
2N/A if ((dstr = set_string(hdp, idx)) == NULL) {
2N/A return (DB_NIL);
2N/A }
2N/A
2N/A (void) strcpy(dstr, str);
2N/A
2N/A next[DB_STR] += strlen(dstr) + 1;
2N/A
2N/A return (idx);
2N/A}
2N/A
2N/Astatic int
2N/Aclose_db(struct di_devlink_handle *hdp)
2N/A{
2N/A int i, rv = 0;
2N/A size_t sz;
2N/A
2N/A if (!DB_OPEN(hdp)) {
2N/A#ifdef DEBUG
2N/A assert(DB(hdp)->db_fd == -1);
2N/A assert(DB(hdp)->flags == 0);
2N/A for (i = 0; i < DB_TYPES; i++) {
2N/A assert(DB_SEG(hdp, i) == NULL);
2N/A assert(DB_SEG_PROT(hdp, i) == 0);
2N/A }
2N/A#endif
2N/A return (0);
2N/A }
2N/A
2N/A /* Unmap header after unmapping all other mapped segments */
2N/A for (i = 0; i < DB_TYPES; i++) {
2N/A if (DB_SEG(hdp, i)) {
2N/A sz = seg_size(hdp, i);
2N/A if (DB_RDWR(hdp))
2N/A rv += msync(DB_SEG(hdp, i), sz, MS_SYNC);
2N/A (void) munmap(DB_SEG(hdp, i), sz);
2N/A DB_SEG(hdp, i) = NULL;
2N/A DB_SEG_PROT(hdp, i) = 0;
2N/A }
2N/A }
2N/A
2N/A if (DB_RDWR(hdp))
2N/A rv += msync((caddr_t)DB_HDR(hdp), HDR_LEN, MS_SYNC);
2N/A (void) munmap((caddr_t)DB_HDR(hdp), HDR_LEN);
2N/A DB(hdp)->hdr = NULL;
2N/A
2N/A (void) close(DB(hdp)->db_fd);
2N/A DB(hdp)->db_fd = -1;
2N/A DB(hdp)->flags = 0;
2N/A
2N/A return (rv ? -1 : 0);
2N/A}
2N/A
2N/A
2N/Astatic void
2N/Acache_free(struct di_devlink_handle *hdp)
2N/A{
2N/A cache_link_t *clp;
2N/A
2N/A subtree_free(hdp, &(CACHE_ROOT(hdp)));
2N/A assert(CACHE_LAST(hdp) == NULL);
2N/A
2N/A /*
2N/A * Don't bother removing links from hash table chains,
2N/A * as we are freeing the hash table itself.
2N/A */
2N/A while (CACHE(hdp)->dngl != NULL) {
2N/A clp = CACHE(hdp)->dngl;
2N/A CACHE(hdp)->dngl = clp->sib;
2N/A assert(clp->minor == NULL);
2N/A link_free(&clp);
2N/A }
2N/A
2N/A assert((CACHE(hdp)->hash == NULL) ^ (CACHE(hdp)->hash_sz != 0));
2N/A
2N/A free(CACHE(hdp)->hash);
2N/A CACHE(hdp)->hash = NULL;
2N/A CACHE(hdp)->hash_sz = 0;
2N/A}
2N/A
2N/Astatic void
2N/Ahandle_free(struct di_devlink_handle **pp)
2N/A{
2N/A struct di_devlink_handle *hdp = *pp;
2N/A
2N/A *pp = NULL;
2N/A
2N/A if (hdp == NULL)
2N/A return;
2N/A
2N/A (void) close_db(hdp);
2N/A cache_free(hdp);
2N/A
2N/A if (HDL_RDWR(hdp))
2N/A exit_db_lock(hdp);
2N/A assert(hdp->lock_fd == -1);
2N/A
2N/A free(hdp->dev_dir);
2N/A free(hdp->db_dir);
2N/A free(hdp);
2N/A}
2N/A
2N/A/*
2N/A * Frees the tree rooted at a node. Siblings of the subtree root
2N/A * have to be handled by the caller.
2N/A */
2N/Astatic void
2N/Asubtree_free(struct di_devlink_handle *hdp, cache_node_t **pp)
2N/A{
2N/A cache_node_t *np;
2N/A cache_link_t *clp;
2N/A cache_minor_t *cmnp;
2N/A
2N/A if (pp == NULL || *pp == NULL)
2N/A return;
2N/A
2N/A while ((*pp)->child != NULL) {
2N/A np = (*pp)->child;
2N/A (*pp)->child = np->sib;
2N/A subtree_free(hdp, &np);
2N/A }
2N/A
2N/A while ((*pp)->minor != NULL) {
2N/A cmnp = (*pp)->minor;
2N/A (*pp)->minor = cmnp->sib;
2N/A
2N/A while (cmnp->link != NULL) {
2N/A clp = cmnp->link;
2N/A cmnp->link = clp->sib;
2N/A rm_link_from_hash(hdp, clp);
2N/A link_free(&clp);
2N/A }
2N/A minor_free(hdp, &cmnp);
2N/A }
2N/A
2N/A node_free(pp);
2N/A}
2N/A
2N/Astatic void
2N/Arm_link_from_hash(struct di_devlink_handle *hdp, cache_link_t *clp)
2N/A{
2N/A int hval;
2N/A cache_link_t **pp;
2N/A
2N/A if (clp == NULL)
2N/A return;
2N/A
2N/A if (clp->path == NULL)
2N/A return;
2N/A
2N/A hval = hashfn(hdp, clp->path);
2N/A pp = &(CACHE_HASH(hdp, hval));
2N/A for (; *pp != NULL; pp = &(*pp)->hash) {
2N/A if (*pp == clp) {
2N/A *pp = clp->hash;
2N/A clp->hash = NULL;
2N/A return;
2N/A }
2N/A }
2N/A
2N/A dprintf(DBG_ERR, "rm_link_from_hash: link(%s) not found\n", clp->path);
2N/A}
2N/A
2N/Astatic cache_link_t *
2N/Alink_hash(di_devlink_handle_t hdp, const char *link, uint_t flags)
2N/A{
2N/A int hval;
2N/A cache_link_t **pp, *clp;
2N/A
2N/A if (link == NULL)
2N/A return (NULL);
2N/A
2N/A hval = hashfn(hdp, link);
2N/A pp = &(CACHE_HASH(hdp, hval));
2N/A for (; (clp = *pp) != NULL; pp = &clp->hash) {
2N/A if (strcmp(clp->path, link) == 0) {
2N/A break;
2N/A }
2N/A }
2N/A
2N/A if (clp == NULL)
2N/A return (NULL);
2N/A
2N/A if ((flags & UNLINK_FROM_HASH) == UNLINK_FROM_HASH) {
2N/A *pp = clp->hash;
2N/A clp->hash = NULL;
2N/A }
2N/A
2N/A return (clp);
2N/A}
2N/A
2N/Astatic cache_minor_t *
2N/Alink2minor(struct di_devlink_handle *hdp, cache_link_t *clp)
2N/A{
2N/A cache_link_t *plp;
2N/A const char *minor_path;
2N/A char *cp, buf[PATH_MAX], link[PATH_MAX];
2N/A char abspath[PATH_MAX];
2N/A struct stat st;
2N/A
2N/A if (TYPE_PRI(attr2type(clp->attr))) {
2N/A /*
2N/A * For primary link, content should point to a /devices node.
2N/A */
2N/A if (!is_minor_node(clp->content, &minor_path)) {
2N/A return (NULL);
2N/A }
2N/A
2N/A return (lookup_minor(hdp, minor_path, NULL,
2N/A TYPE_CACHE|CREATE_FLAG));
2N/A
2N/A }
2N/A
2N/A /*
2N/A * If secondary, the primary link is derived from the secondary
2N/A * link contents. Secondary link contents can have two formats:
2N/A * audio -> /dev/sound/0
2N/A * fb0 -> fbs/afb0
2N/A */
2N/A
2N/A buf[0] = '\0';
2N/A if (strncmp(clp->content, DEV"/", strlen(DEV"/")) == 0) {
2N/A cp = &clp->content[strlen(DEV"/")];
2N/A } else if (clp->content[0] != '/') {
2N/A if ((cp = strrchr(clp->path, '/')) != NULL) {
2N/A char savechar = *(cp + 1);
2N/A *(cp + 1) = '\0';
2N/A (void) snprintf(buf, sizeof (buf), "%s", clp->path);
2N/A *(cp + 1) = savechar;
2N/A }
2N/A (void) strlcat(buf, clp->content, sizeof (buf));
2N/A cp = buf;
2N/A } else {
2N/A goto follow_link;
2N/A }
2N/A
2N/A /*
2N/A * Lookup the primary link if possible and find its minor.
2N/A */
2N/A if ((plp = link_hash(hdp, cp, 0)) != NULL && plp->minor != NULL) {
2N/A return (plp->minor);
2N/A }
2N/A
2N/A /* realpath() used only as a last resort because it is expensive */
2N/Afollow_link:
2N/A (void) snprintf(link, sizeof (link), "%s/%s", hdp->dev_dir, clp->path);
2N/A
2N/A#ifdef DEBUG
2N/A /*LINTED*/
2N/A assert(sizeof (buf) >= PATH_MAX);
2N/A#endif
2N/A
2N/A /*
2N/A * A realpath attempt to lookup a dangling link can invoke implicit
2N/A * reconfig so verify there's an actual device behind the link first.
2N/A */
2N/A if (lstat(link, &st) == -1)
2N/A return (NULL);
2N/A if (S_ISLNK(st.st_mode)) {
2N/A if (s_readlink(link, buf, sizeof (buf)) < 0)
2N/A return (NULL);
2N/A if (buf[0] != '/') {
2N/A char *p;
2N/A size_t n = sizeof (abspath);
2N/A if (strlcpy(abspath, link, n) >= n)
2N/A return (NULL);
2N/A p = strrchr(abspath, '/') + 1;
2N/A *p = 0;
2N/A n = sizeof (abspath) - strlen(p);
2N/A if (strlcpy(p, buf, n) >= n)
2N/A return (NULL);
2N/A } else {
2N/A if (strlcpy(abspath, buf, sizeof (abspath)) >=
2N/A sizeof (abspath))
2N/A return (NULL);
2N/A }
2N/A if (!device_exists(abspath))
2N/A return (NULL);
2N/A }
2N/A
2N/A if (s_realpath(link, buf) == NULL || !is_minor_node(buf, &minor_path)) {
2N/A return (NULL);
2N/A }
2N/A return (lookup_minor(hdp, minor_path, NULL, TYPE_CACHE|CREATE_FLAG));
2N/A}
2N/A
2N/A
2N/Astatic void
2N/Aresolve_dangling_links(struct di_devlink_handle *hdp)
2N/A{
2N/A cache_minor_t *cmnp;
2N/A cache_link_t *clp, **pp;
2N/A
2N/A for (pp = &(CACHE(hdp)->dngl); *pp != NULL; ) {
2N/A clp = *pp;
2N/A if ((cmnp = link2minor(hdp, clp)) != NULL) {
2N/A *pp = clp->sib;
2N/A clp->sib = cmnp->link;
2N/A cmnp->link = clp;
2N/A assert(clp->minor == NULL);
2N/A clp->minor = cmnp;
2N/A } else {
2N/A dprintf(DBG_INFO, "resolve_dangling_links: link(%s):"
2N/A " unresolved\n", clp->path);
2N/A pp = &clp->sib;
2N/A }
2N/A }
2N/A}
2N/A
2N/A
2N/A/*
2N/A * The elements are assumed to be detached from the cache tree.
2N/A */
2N/Astatic void
2N/Anode_free(cache_node_t **pp)
2N/A{
2N/A cache_node_t *cnp = *pp;
2N/A
2N/A *pp = NULL;
2N/A
2N/A if (cnp == NULL)
2N/A return;
2N/A
2N/A free(cnp->path);
2N/A free(cnp);
2N/A}
2N/A
2N/Astatic void
2N/Aminor_free(struct di_devlink_handle *hdp, cache_minor_t **pp)
2N/A{
2N/A cache_minor_t *cmnp = *pp;
2N/A
2N/A *pp = NULL;
2N/A
2N/A if (cmnp == NULL)
2N/A return;
2N/A
2N/A if (CACHE_LAST(hdp) == cmnp) {
2N/A dprintf(DBG_STEP, "minor_free: last_minor(%s)\n", cmnp->name);
2N/A CACHE_LAST(hdp) = NULL;
2N/A }
2N/A
2N/A free(cmnp->name);
2N/A free(cmnp->nodetype);
2N/A free(cmnp);
2N/A}
2N/A
2N/Astatic void
2N/Alink_free(cache_link_t **pp)
2N/A{
2N/A cache_link_t *clp = *pp;
2N/A
2N/A *pp = NULL;
2N/A
2N/A if (clp == NULL)
2N/A return;
2N/A
2N/A free(clp->path);
2N/A free(clp->content);
2N/A free(clp);
2N/A}
2N/A
2N/A/*
2N/A * Returns the ':' preceding the minor name
2N/A */
2N/Astatic char *
2N/Aminor_colon(const char *path)
2N/A{
2N/A char *cp;
2N/A
2N/A if ((cp = strrchr(path, '/')) == NULL) {
2N/A return (NULL);
2N/A }
2N/A
2N/A return (strchr(cp, ':'));
2N/A}
2N/A
2N/Astatic void *
2N/Alookup_minor(
2N/A struct di_devlink_handle *hdp,
2N/A const char *minor_path,
2N/A const char *nodetype,
2N/A const int flags)
2N/A{
2N/A void *vp;
2N/A char *colon;
2N/A char pdup[PATH_MAX];
2N/A const char *fcn = "lookup_minor";
2N/A
2N/A if (minor_path == NULL) {
2N/A errno = EINVAL;
2N/A return (NULL);
2N/A }
2N/A
2N/A (void) snprintf(pdup, sizeof (pdup), "%s", minor_path);
2N/A
2N/A if ((colon = minor_colon(pdup)) == NULL) {
2N/A (void) dprintf(DBG_ERR, "%s: invalid minor path(%s)\n", fcn,
2N/A minor_path);
2N/A errno = EINVAL;
2N/A return (NULL);
2N/A }
2N/A *colon = '\0';
2N/A
2N/A if ((vp = get_last_minor(hdp, pdup, colon + 1, flags)) != NULL) {
2N/A return (vp);
2N/A }
2N/A
2N/A if ((vp = lookup_node(hdp, pdup, flags)) == NULL) {
2N/A (void) dprintf(DBG_ERR, "%s: node(%s) not found\n", fcn, pdup);
2N/A return (NULL);
2N/A }
2N/A *colon = ':';
2N/A
2N/A if (LOOKUP_CACHE(flags)) {
2N/A cache_minor_t **pp;
2N/A
2N/A pp = &((cache_node_t *)vp)->minor;
2N/A for (; *pp != NULL; pp = &(*pp)->sib) {
2N/A if (strcmp((*pp)->name, colon + 1) == 0)
2N/A break;
2N/A }
2N/A
2N/A if (*pp == NULL && CREATE_ELEM(flags)) {
2N/A *pp = minor_insert(hdp, vp, colon + 1, nodetype, pp);
2N/A }
2N/A set_last_minor(hdp, *pp, flags);
2N/A
2N/A return (*pp);
2N/A } else {
2N/A char *cp;
2N/A uint32_t nidx;
2N/A struct db_minor *dmp;
2N/A
2N/A nidx = (((struct db_node *)vp)->minor);
2N/A for (; dmp = get_minor(hdp, nidx); nidx = dmp->sib) {
2N/A cp = get_string(hdp, dmp->name);
2N/A if (cp && strcmp(cp, colon + 1) == 0)
2N/A break;
2N/A }
2N/A return (dmp);
2N/A }
2N/A}
2N/A
2N/Astatic void *
2N/Alookup_node(struct di_devlink_handle *hdp, char *path, const int flags)
2N/A{
2N/A struct tnode tnd = {NULL};
2N/A
2N/A if (tnd.node = get_last_node(hdp, path, flags))
2N/A return (tnd.node);
2N/A
2N/A tnd.handle = hdp;
2N/A tnd.flags = flags;
2N/A
2N/A if (walk_tree(path, &tnd, visit_node) != 0)
2N/A return (NULL);
2N/A
2N/A return (tnd.node);
2N/A}
2N/A
2N/A/*
2N/A * last_minor is used for nodes of TYPE_CACHE only.
2N/A */
2N/Astatic void *
2N/Aget_last_node(struct di_devlink_handle *hdp, const char *path, int flags)
2N/A{
2N/A cache_node_t *cnp;
2N/A
2N/A#ifdef DEBUG
2N/A if (getenv(SKIP_LAST_CACHE)) {
2N/A (void) dprintf(DBG_INFO, "get_last_node: SKIPPING \"last\" "
2N/A "node cache\n");
2N/A return (NULL);
2N/A }
2N/A#endif
2N/A
2N/A if (!LOOKUP_CACHE(flags) || CACHE_LAST(hdp) == NULL ||
2N/A CACHE_LAST(hdp)->node == NULL) {
2N/A return (NULL);
2N/A }
2N/A
2N/A cnp = CACHE_LAST(hdp)->node;
2N/A if (strcmp(cnp->path, path) == 0) {
2N/A return (cnp);
2N/A }
2N/A
2N/A cnp = cnp->sib;
2N/A if (cnp && strcmp(cnp->path, path) == 0) {
2N/A return (cnp);
2N/A }
2N/A
2N/A return (NULL);
2N/A}
2N/A
2N/Astatic void *
2N/Aget_last_minor(
2N/A struct di_devlink_handle *hdp,
2N/A const char *devfs_path,
2N/A const char *minor_name,
2N/A int flags)
2N/A{
2N/A cache_minor_t *cmnp;
2N/A
2N/A#ifdef DEBUG
2N/A if (getenv(SKIP_LAST_CACHE)) {
2N/A (void) dprintf(DBG_INFO, "get_last_minor: SKIPPING \"last\" "
2N/A "minor cache\n");
2N/A return (NULL);
2N/A }
2N/A#endif
2N/A
2N/A if (!LOOKUP_CACHE(flags) || CACHE_LAST(hdp) == NULL) {
2N/A return (NULL);
2N/A }
2N/A
2N/A cmnp = CACHE_LAST(hdp);
2N/A if (strcmp(cmnp->name, minor_name) == 0 && cmnp->node &&
2N/A strcmp(cmnp->node->path, devfs_path) == 0) {
2N/A return (cmnp);
2N/A }
2N/A
2N/A cmnp = cmnp->sib;
2N/A if (cmnp && strcmp(cmnp->name, minor_name) == 0 && cmnp->node &&
2N/A strcmp(cmnp->node->path, devfs_path) == 0) {
2N/A set_last_minor(hdp, cmnp, TYPE_CACHE);
2N/A return (cmnp);
2N/A }
2N/A
2N/A return (NULL);
2N/A}
2N/A
2N/Astatic void
2N/Aset_last_minor(struct di_devlink_handle *hdp, cache_minor_t *cmnp, int flags)
2N/A{
2N/A#ifdef DEBUG
2N/A if (getenv(SKIP_LAST_CACHE)) {
2N/A (void) dprintf(DBG_INFO, "set_last_minor: SKIPPING \"last\" "
2N/A "minor cache\n");
2N/A return;
2N/A }
2N/A#endif
2N/A
2N/A if (LOOKUP_CACHE(flags) && cmnp) {
2N/A CACHE_LAST(hdp) = cmnp;
2N/A }
2N/A}
2N/A
2N/A
2N/A/*
2N/A * Returns 0 if normal return or -1 otherwise.
2N/A */
2N/Astatic int
2N/Awalk_tree(
2N/A char *cur,
2N/A void *arg,
2N/A int (*node_callback)(const char *path, void *arg))
2N/A{
2N/A char *slash, buf[PATH_MAX];
2N/A
2N/A if (cur == NULL || cur[0] != '/' || strlen(cur) > sizeof (buf) - 1) {
2N/A errno = EINVAL;
2N/A return (-1);
2N/A }
2N/A
2N/A (void) strcpy(buf, "/");
2N/A
2N/A for (;;) {
2N/A
2N/A if (node_callback(buf, arg) != DI_WALK_CONTINUE)
2N/A break;
2N/A
2N/A while (*cur == '/')
2N/A cur++;
2N/A
2N/A if (*cur == '\0')
2N/A break;
2N/A
2N/A /*
2N/A * There is a next component(s). Append a "/" separator for all
2N/A * but the first (root) component.
2N/A */
2N/A if (buf[1] != '\0') {
2N/A (void) strlcat(buf, "/", sizeof (buf));
2N/A }
2N/A
2N/A if (slash = strchr(cur, '/')) {
2N/A *slash = '\0';
2N/A (void) strlcat(buf, cur, sizeof (buf));
2N/A *slash = '/';
2N/A cur = slash;
2N/A } else {
2N/A (void) strlcat(buf, cur, sizeof (buf));
2N/A cur += strlen(cur);
2N/A }
2N/A
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A
2N/Astatic int
2N/Avisit_node(const char *path, void *arg)
2N/A{
2N/A struct tnode *tnp = arg;
2N/A
2N/A if (LOOKUP_CACHE(tnp->flags)) {
2N/A
2N/A cache_node_t *cnp = tnp->node;
2N/A
2N/A cnp = (cnp) ? cnp->child : CACHE_ROOT(tnp->handle);
2N/A
2N/A for (; cnp != NULL; cnp = cnp->sib) {
2N/A if (strcmp(cnp->path, path) == 0)
2N/A break;
2N/A }
2N/A if (cnp == NULL && CREATE_ELEM(tnp->flags)) {
2N/A cnp = node_insert(tnp->handle, tnp->node, path,
2N/A INSERT_TAIL);
2N/A }
2N/A tnp->node = cnp;
2N/A } else {
2N/A char *cp;
2N/A struct db_node *dnp = tnp->node;
2N/A
2N/A dnp = (dnp) ? get_node(tnp->handle, dnp->child)
2N/A : get_node(tnp->handle, DB_HDR(tnp->handle)->root_idx);
2N/A
2N/A for (; dnp != NULL; dnp = get_node(tnp->handle, dnp->sib)) {
2N/A cp = get_string(tnp->handle, dnp->path);
2N/A if (cp && strcmp(cp, path) == 0) {
2N/A break;
2N/A }
2N/A }
2N/A tnp->node = dnp;
2N/A }
2N/A
2N/A /*
2N/A * Terminate walk if node is not found for a path component.
2N/A */
2N/A return (tnp->node ? DI_WALK_CONTINUE : DI_WALK_TERMINATE);
2N/A}
2N/A
2N/Astatic void
2N/Aminor_delete(di_devlink_handle_t hdp, cache_minor_t *cmnp)
2N/A{
2N/A cache_link_t **lpp;
2N/A cache_minor_t **mpp;
2N/A const char *fcn = "minor_delete";
2N/A
2N/A (void) dprintf(DBG_STEP, "%s: removing minor: %s\n", fcn, cmnp->name);
2N/A
2N/A /* detach minor from node */
2N/A if (cmnp->node != NULL) {
2N/A mpp = &cmnp->node->minor;
2N/A for (; *mpp != NULL; mpp = &(*mpp)->sib) {
2N/A if (*mpp == cmnp)
2N/A break;
2N/A }
2N/A
2N/A if (*mpp == NULL) {
2N/A (void) dprintf(DBG_ERR, "%s: dangling minor: %s\n",
2N/A fcn, cmnp->name);
2N/A } else {
2N/A *mpp = cmnp->sib;
2N/A }
2N/A } else {
2N/A (void) dprintf(DBG_ERR, "%s: orphan minor(%s)\n", fcn,
2N/A cmnp->name);
2N/A }
2N/A
2N/A delete_unused_nodes(hdp, cmnp->node);
2N/A
2N/A cmnp->node = NULL;
2N/A cmnp->sib = NULL;
2N/A
2N/A /* Move all remaining links to dangling list */
2N/A for (lpp = &cmnp->link; *lpp != NULL; lpp = &(*lpp)->sib) {
2N/A (*lpp)->minor = NULL;
2N/A }
2N/A *lpp = CACHE(hdp)->dngl;
2N/A CACHE(hdp)->dngl = cmnp->link;
2N/A cmnp->link = NULL;
2N/A
2N/A minor_free(hdp, &cmnp);
2N/A}
2N/A
2N/Astatic void
2N/Adelete_unused_nodes(di_devlink_handle_t hdp, cache_node_t *cnp)
2N/A{
2N/A cache_node_t **npp;
2N/A const char *fcn = "delete_unused_nodes";
2N/A
2N/A if (cnp == NULL)
2N/A return;
2N/A
2N/A if (cnp->minor != NULL || cnp->child != NULL)
2N/A return;
2N/A
2N/A (void) dprintf(DBG_INFO, "%s: removing unused node: %s\n", fcn,
2N/A cnp->path);
2N/A
2N/A /* Unlink node from tree */
2N/A if (cnp->parent != NULL) {
2N/A npp = &cnp->parent->child;
2N/A for (; *npp != NULL; npp = &(*npp)->sib) {
2N/A if (*npp == cnp)
2N/A break;
2N/A }
2N/A
2N/A if (*npp == NULL) {
2N/A (void) dprintf(DBG_ERR, "%s: dangling node: %s\n", fcn,
2N/A cnp->path);
2N/A } else {
2N/A *npp = cnp->sib;
2N/A }
2N/A } else if (cnp == CACHE_ROOT(hdp)) {
2N/A CACHE_ROOT(hdp) = NULL;
2N/A } else {
2N/A (void) dprintf(DBG_ERR, "%s: orphan node (%s)\n", fcn,
2N/A cnp->path);
2N/A }
2N/A
2N/A delete_unused_nodes(hdp, cnp->parent);
2N/A
2N/A cnp->parent = cnp->sib = NULL;
2N/A
2N/A node_free(&cnp);
2N/A}
2N/A
2N/Astatic int
2N/Arm_link(di_devlink_handle_t hdp, const char *link)
2N/A{
2N/A cache_link_t *clp;
2N/A const char *fcn = "rm_link";
2N/A
2N/A if (hdp == NULL || DB_ERR(hdp) || link == NULL || link[0] == '/' ||
2N/A (!HDL_RDWR(hdp) && !HDL_RDONLY(hdp))) {
2N/A dprintf(DBG_ERR, "%s: %s: invalid args\n",
2N/A fcn, link ? link : "<NULL>");
2N/A errno = EINVAL;
2N/A return (-1);
2N/A }
2N/A
2N/A dprintf(DBG_STEP, "%s: link(%s)\n", fcn, link);
2N/A
2N/A if ((clp = link_hash(hdp, link, UNLINK_FROM_HASH)) == NULL) {
2N/A return (0);
2N/A }
2N/A
2N/A link_delete(hdp, clp);
2N/A
2N/A return (0);
2N/A}
2N/A
2N/Aint
2N/Adi_devlink_rm_link(di_devlink_handle_t hdp, const char *link)
2N/A{
2N/A if (hdp == NULL || !HDL_RDWR(hdp)) {
2N/A errno = EINVAL;
2N/A return (-1);
2N/A }
2N/A
2N/A return (rm_link(hdp, link));
2N/A}
2N/A
2N/Astatic void
2N/Alink_delete(di_devlink_handle_t hdp, cache_link_t *clp)
2N/A{
2N/A cache_link_t **pp;
2N/A const char *fcn = "link_delete";
2N/A
2N/A (void) dprintf(DBG_STEP, "%s: removing link: %s\n", fcn, clp->path);
2N/A
2N/A if (clp->minor == NULL)
2N/A pp = &(CACHE(hdp)->dngl);
2N/A else
2N/A pp = &clp->minor->link;
2N/A
2N/A for (; *pp != NULL; pp = &(*pp)->sib) {
2N/A if (*pp == clp)
2N/A break;
2N/A }
2N/A
2N/A if (*pp == NULL) {
2N/A (void) dprintf(DBG_ERR, "%s: link(%s) not on list\n",
2N/A fcn, clp->path);
2N/A } else {
2N/A *pp = clp->sib;
2N/A }
2N/A
2N/A delete_unused_minor(hdp, clp->minor);
2N/A
2N/A clp->minor = NULL;
2N/A
2N/A link_free(&clp);
2N/A}
2N/A
2N/Astatic void
2N/Adelete_unused_minor(di_devlink_handle_t hdp, cache_minor_t *cmnp)
2N/A{
2N/A if (cmnp == NULL)
2N/A return;
2N/A
2N/A if (cmnp->link != NULL)
2N/A return;
2N/A
2N/A dprintf(DBG_STEP, "delete_unused_minor: removing minor(%s)\n",
2N/A cmnp->name);
2N/A
2N/A minor_delete(hdp, cmnp);
2N/A}
2N/A
2N/Aint
2N/Adi_devlink_add_link(
2N/A di_devlink_handle_t hdp,
2N/A const char *link,
2N/A const char *content,
2N/A int flags)
2N/A{
2N/A return (add_link(hdp, link, content, flags) != NULL ? 0 : -1);
2N/A}
2N/A
2N/Astatic cache_link_t *
2N/Aadd_link(
2N/A struct di_devlink_handle *hdp,
2N/A const char *link,
2N/A const char *content,
2N/A int flags)
2N/A{
2N/A uint32_t attr;
2N/A cache_link_t *clp;
2N/A cache_minor_t *cmnp;
2N/A const char *fcn = "add_link";
2N/A
2N/A if (hdp == NULL || DB_ERR(hdp) || link == NULL ||
2N/A link[0] == '/' || content == NULL || !link_flag(flags) ||
2N/A (!HDL_RDWR(hdp) && !HDL_RDONLY(hdp))) {
2N/A dprintf(DBG_ERR, "%s: %s: invalid args\n",
2N/A fcn, link ? link : "<NULL>");
2N/A errno = EINVAL;
2N/A return (NULL);
2N/A }
2N/A
2N/A if ((clp = link_hash(hdp, link, 0)) != NULL) {
2N/A if (link_cmp(clp, content, LINK_TYPE(flags)) != 0) {
2N/A (void) rm_link(hdp, link);
2N/A } else {
2N/A return (clp);
2N/A }
2N/A }
2N/A
2N/A if (TYPE_PRI(flags)) {
2N/A const char *minor_path = NULL;
2N/A
2N/A if (!is_minor_node(content, &minor_path)) {
2N/A (void) dprintf(DBG_ERR, "%s: invalid content(%s)"
2N/A " for primary link\n", fcn, content);
2N/A errno = EINVAL;
2N/A return (NULL);
2N/A }
2N/A if ((cmnp = lookup_minor(hdp, minor_path, NULL,
2N/A TYPE_CACHE|CREATE_FLAG)) == NULL) {
2N/A return (NULL);
2N/A }
2N/A attr = A_PRIMARY;
2N/A } else {
2N/A /*
2N/A * Defer resolving a secondary link to a minor until the
2N/A * database is closed. This ensures that the primary link
2N/A * (required for a successful resolve) has also been created.
2N/A */
2N/A cmnp = NULL;
2N/A attr = A_SECONDARY;
2N/A }
2N/A
2N/A return (link_insert(hdp, cmnp, link, content, attr));
2N/A}
2N/A
2N/A/*
2N/A * Returns 0 on match or 1 otherwise.
2N/A */
2N/Astatic int
2N/Alink_cmp(cache_link_t *clp, const char *content, int type)
2N/A{
2N/A if (strcmp(clp->content, content) != 0)
2N/A return (1);
2N/A
2N/A if (attr2type(clp->attr) != type)
2N/A return (1);
2N/A
2N/A return (0);
2N/A}
2N/A
2N/Aint
2N/Adi_devlink_update(di_devlink_handle_t hdp)
2N/A{
2N/A if (hdp == NULL || !HDL_RDWR(hdp) || DB_ERR(hdp)) {
2N/A errno = EINVAL;
2N/A return (-1);
2N/A }
2N/A
2N/A /*
2N/A * Reset the counter to schedule a synchronization with /dev on the next
2N/A * di_devlink_close().
2N/A */
2N/A CACHE(hdp)->update_count = 0;
2N/A
2N/A return (0);
2N/A}
2N/A
2N/Astatic int
2N/Asynchronize_db(di_devlink_handle_t hdp)
2N/A{
2N/A int hval;
2N/A cache_link_t *clp;
2N/A char pdup[PATH_MAX];
2N/A recurse_t rec = {NULL};
2N/A const char *fcn = "synchronize_db";
2N/A
2N/A rec.data = NULL;
2N/A rec.fcn = cache_dev_link;
2N/A
2N/A /*
2N/A * Walk through $ROOT/dev, reading every link and marking the
2N/A * corresponding cached version as valid(adding new links as needed).
2N/A * Then walk through the cache and remove all unmarked links.
2N/A */
2N/A if (recurse_dev(hdp, &rec) != 0) {
2N/A return (-1);
2N/A }
2N/A
2N/A for (hval = 0; hval < CACHE(hdp)->hash_sz; hval++) {
2N/A for (clp = CACHE_HASH(hdp, hval); clp != NULL; ) {
2N/A if (GET_VALID_ATTR(clp->attr)) {
2N/A CLR_VALID_ATTR(clp->attr);
2N/A clp = clp->hash;
2N/A continue;
2N/A }
2N/A
2N/A /*
2N/A * The link is stale, so remove it. Since the link
2N/A * will be destroyed, use a copy of the link path to
2N/A * invoke the remove function.
2N/A */
2N/A (void) snprintf(pdup, sizeof (pdup), "%s", clp->path);
2N/A clp = clp->hash;
2N/A (void) dprintf(DBG_STEP, "%s: removing invalid link:"
2N/A " %s\n", fcn, pdup);
2N/A (void) di_devlink_rm_link(hdp, pdup);
2N/A }
2N/A }
2N/A
2N/A (void) dprintf(DBG_STEP, "%s: update completed\n", fcn);
2N/A
2N/A return (0);
2N/A}
2N/A
2N/Astatic di_devlink_handle_t
2N/Adi_devlink_init_impl(const char *root, const char *name, uint_t flags)
2N/A{
2N/A int err = 0;
2N/A
2N/A if ((flags != 0 && flags != DI_MAKE_LINK) ||
2N/A (flags == 0 && name != NULL)) {
2N/A errno = EINVAL;
2N/A return (NULL);
2N/A }
2N/A
2N/A if ((flags == DI_MAKE_LINK) &&
2N/A (err = devlink_create(root, name, DCA_DEVLINK_SYNC))) {
2N/A errno = err;
2N/A return (NULL);
2N/A }
2N/A
2N/A (void) dprintf(DBG_INFO, "devlink_init_impl: success\n");
2N/A
2N/A return (devlink_snapshot(root));
2N/A}
2N/A
2N/Adi_devlink_handle_t
2N/Adi_devlink_init(const char *name, uint_t flags)
2N/A{
2N/A return (di_devlink_init_impl("/", name, flags));
2N/A}
2N/A
2N/Adi_devlink_handle_t
2N/Adi_devlink_init_root(const char *root, const char *name, uint_t flags)
2N/A{
2N/A return (di_devlink_init_impl(root, name, flags));
2N/A}
2N/A
2N/Astatic di_devlink_handle_t
2N/Adevlink_snapshot(const char *root_dir)
2N/A{
2N/A struct di_devlink_handle *hdp;
2N/A int err;
2N/A static int retried = 0;
2N/A
2N/A if ((hdp = handle_alloc(root_dir, OPEN_RDONLY)) == NULL) {
2N/A return (NULL);
2N/A }
2N/A
2N/A /*
2N/A * We don't need to lock. If a consumer wants the very latest db
2N/A * then he must perform a di_devlink_init with the DI_MAKE_LINK
2N/A * flag to force a sync with devfsadm first. Otherwise, the
2N/A * current database file is opened and mmaped on demand: the rename
2N/A * associated with a db update does not change the contents
2N/A * of files already opened.
2N/A */
2N/Aagain: err = open_db(hdp, OPEN_RDONLY);
2N/A
2N/A /*
2N/A * If we failed to open DB the most likely cause is that DB file did
2N/A * not exist. If we have not done a retry, signal devfsadmd to
2N/A * recreate the DB file and retry. If we fail to open the DB after
2N/A * retry, we will walk /dev in di_devlink_walk.
2N/A */
2N/A if (err && (retried == 0)) {
2N/A retried++;
2N/A (void) devlink_create(root_dir, NULL, DCA_DEVLINK_CACHE);
2N/A goto again;
2N/A }
2N/A return (hdp);
2N/A}
2N/A
2N/Aint
2N/Adi_devlink_fini(di_devlink_handle_t *pp)
2N/A{
2N/A if (pp == NULL || *pp == NULL || !HDL_RDONLY(*pp)) {
2N/A errno = EINVAL;
2N/A return (-1);
2N/A }
2N/A
2N/A /* Freeing the handle also closes the DB */
2N/A handle_free(pp);
2N/A
2N/A return (0);
2N/A}
2N/A
2N/Aint
2N/Adi_devlink_walk(
2N/A di_devlink_handle_t hdp,
2N/A const char *re,
2N/A const char *minor_path,
2N/A uint_t flags,
2N/A void *arg,
2N/A int (*devlink_callback)(di_devlink_t, void *))
2N/A{
2N/A int rv;
2N/A regex_t reg;
2N/A link_desc_t linkd = {NULL};
2N/A
2N/A if (hdp == NULL || !HDL_RDONLY(hdp)) {
2N/A errno = EINVAL;
2N/A return (-1);
2N/A }
2N/A
2N/A linkd.minor_path = minor_path;
2N/A linkd.flags = flags;
2N/A linkd.arg = arg;
2N/A linkd.fcn = devlink_callback;
2N/A
2N/A if (re) {
2N/A if (regcomp(&reg, re, REG_EXTENDED) != 0)
2N/A return (-1);
2N/A linkd.regp = &reg;
2N/A }
2N/A
2N/A if (check_args(&linkd)) {
2N/A errno = EINVAL;
2N/A rv = -1;
2N/A goto out;
2N/A }
2N/A
2N/A if (DB_OPEN(hdp)) {
2N/A rv = walk_db(hdp, &linkd);
2N/A } else {
2N/A rv = walk_dev(hdp, &linkd);
2N/A }
2N/A
2N/Aout:
2N/A if (re) {
2N/A regfree(&reg);
2N/A }
2N/A
2N/A return (rv ? -1 : 0);
2N/A}
2N/A
2N/Astatic int
2N/Alink_flag(uint_t flags)
2N/A{
2N/A if (flags != 0 && flags != DI_PRIMARY_LINK &&
2N/A flags != DI_SECONDARY_LINK) {
2N/A return (0);
2N/A }
2N/A
2N/A return (1);
2N/A}
2N/A
2N/A/*
2N/A * Currently allowed flags are:
2N/A * DI_PRIMARY_LINK
2N/A * DI_SECONDARY_LINK
2N/A */
2N/Astatic int
2N/Acheck_args(link_desc_t *linkp)
2N/A{
2N/A if (linkp->fcn == NULL)
2N/A return (-1);
2N/A
2N/A if (!link_flag(linkp->flags)) {
2N/A return (-1);
2N/A }
2N/A
2N/A /*
2N/A * Minor path can be NULL. In that case, all links will be
2N/A * selected.
2N/A */
2N/A if (linkp->minor_path) {
2N/A if (linkp->minor_path[0] != '/' ||
2N/A minor_colon(linkp->minor_path) == NULL) {
2N/A return (-1);
2N/A }
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * Walk all links in database if no minor path is specified.
2N/A */
2N/Astatic int
2N/Awalk_db(struct di_devlink_handle *hdp, link_desc_t *linkp)
2N/A{
2N/A assert(DB_OPEN(hdp));
2N/A
2N/A if (linkp->minor_path == NULL) {
2N/A return (walk_all_links(hdp, linkp));
2N/A } else {
2N/A return (walk_matching_links(hdp, linkp));
2N/A }
2N/A}
2N/A
2N/Astatic int
2N/Acache_dev(struct di_devlink_handle *hdp)
2N/A{
2N/A size_t sz;
2N/A recurse_t rec = {NULL};
2N/A
2N/A assert(hdp);
2N/A assert(HDL_RDONLY(hdp));
2N/A
2N/A if (hdp == NULL || !HDL_RDONLY(hdp)) {
2N/A dprintf(DBG_ERR, "cache_dev: invalid arg\n");
2N/A return (-1);
2N/A }
2N/A
2N/A sz = MIN_HASH_SIZE;
2N/A
2N/A CACHE(hdp)->hash = calloc(sz, sizeof (cache_link_t *));
2N/A if (CACHE(hdp)->hash == NULL) {
2N/A return (-1);
2N/A }
2N/A CACHE(hdp)->hash_sz = sz;
2N/A
2N/A rec.data = NULL;
2N/A rec.fcn = cache_dev_link;
2N/A
2N/A return (recurse_dev(hdp, &rec));
2N/A}
2N/A
2N/Astatic int
2N/Awalk_dev(struct di_devlink_handle *hdp, link_desc_t *linkp)
2N/A{
2N/A assert(hdp && linkp);
2N/A assert(!DB_OPEN(hdp));
2N/A assert(HDL_RDONLY(hdp));
2N/A
2N/A if (hdp == NULL || !HDL_RDONLY(hdp) || DB_OPEN(hdp)) {
2N/A dprintf(DBG_ERR, "walk_dev: invalid args\n");
2N/A return (-1);
2N/A }
2N/A
2N/A if (CACHE_EMPTY(hdp) && cache_dev(hdp) != 0) {
2N/A dprintf(DBG_ERR, "walk_dev: /dev caching failed\n");
2N/A return (-1);
2N/A }
2N/A
2N/A if (linkp->minor_path)
2N/A walk_cache_minor(hdp, linkp->minor_path, linkp);
2N/A else
2N/A walk_all_cache(hdp, linkp);
2N/A
2N/A return (linkp->retval);
2N/A}
2N/A
2N/A/* ARGSUSED */
2N/Astatic int
2N/Acache_dev_link(struct di_devlink_handle *hdp, void *data, const char *link)
2N/A{
2N/A int flags;
2N/A cache_link_t *clp;
2N/A char content[PATH_MAX];
2N/A
2N/A assert(HDL_RDWR(hdp) || HDL_RDONLY(hdp));
2N/A
2N/A if (s_readlink(link, content, sizeof (content)) < 0) {
2N/A return (DI_WALK_CONTINUE);
2N/A }
2N/A
2N/A if (is_minor_node(content, NULL)) {
2N/A flags = DI_PRIMARY_LINK;
2N/A } else {
2N/A flags = DI_SECONDARY_LINK;
2N/A }
2N/A
2N/A assert(strncmp(link, hdp->dev_dir, strlen(hdp->dev_dir)) == 0);
2N/A
2N/A /*
2N/A * Store only the part after <root-dir>/dev/
2N/A */
2N/A link += strlen(hdp->dev_dir) + 1;
2N/A
2N/A if ((clp = add_link(hdp, link, content, flags)) != NULL) {
2N/A SET_VALID_ATTR(clp->attr);
2N/A }
2N/A
2N/A return (DI_WALK_CONTINUE);
2N/A}
2N/A
2N/A
2N/Astatic int
2N/Awalk_all_links(struct di_devlink_handle *hdp, link_desc_t *linkp)
2N/A{
2N/A struct db_link *dlp;
2N/A uint32_t nidx, eidx;
2N/A
2N/A assert(DB_NUM(hdp, DB_LINK) >= 1);
2N/A
2N/A eidx = DB_NUM(hdp, DB_LINK);
2N/A
2N/A /* Skip the "NIL" (index == 0) link. */
2N/A for (nidx = 1; nidx < eidx; nidx++) {
2N/A /*
2N/A * Declare this local to the block with zero
2N/A * initializer so that it gets rezeroed
2N/A * for each iteration.
2N/A */
2N/A struct di_devlink vlink = {NULL};
2N/A
2N/A if ((dlp = get_link(hdp, nidx)) == NULL)
2N/A continue;
2N/A
2N/A vlink.rel_path = get_string(hdp, dlp->path);
2N/A vlink.content = get_string(hdp, dlp->content);
2N/A vlink.type = attr2type(dlp->attr);
2N/A
2N/A if (visit_link(hdp, linkp, &vlink) != DI_WALK_CONTINUE) {
2N/A break;
2N/A }
2N/A }
2N/A
2N/A return (linkp->retval);
2N/A}
2N/A
2N/Astatic int
2N/Awalk_matching_links(struct di_devlink_handle *hdp, link_desc_t *linkp)
2N/A{
2N/A uint32_t nidx;
2N/A struct db_link *dlp;
2N/A struct db_minor *dmp;
2N/A
2N/A assert(linkp->minor_path != NULL);
2N/A
2N/A dmp = lookup_minor(hdp, linkp->minor_path, NULL, TYPE_DB);
2N/A
2N/A /*
2N/A * If a minor matching the path exists, walk that minor's devlinks list.
2N/A * Then walk the dangling devlinks list. Non-matching devlinks will be
2N/A * filtered out in visit_link.
2N/A */
2N/A for (;;) {
2N/A nidx = dmp ? dmp->link : DB_HDR(hdp)->dngl_idx;
2N/A for (; dlp = get_link(hdp, nidx); nidx = dlp->sib) {
2N/A struct di_devlink vlink = {NULL};
2N/A
2N/A vlink.rel_path = get_string(hdp, dlp->path);
2N/A vlink.content = get_string(hdp, dlp->content);
2N/A vlink.type = attr2type(dlp->attr);
2N/A
2N/A if (visit_link(hdp, linkp, &vlink) != DI_WALK_CONTINUE)
2N/A goto out;
2N/A }
2N/A if (dmp == NULL) {
2N/A break;
2N/A } else {
2N/A dmp = NULL;
2N/A }
2N/A }
2N/A
2N/Aout:
2N/A return (linkp->retval);
2N/A}
2N/A
2N/Astatic int
2N/Avisit_link(
2N/A struct di_devlink_handle *hdp,
2N/A link_desc_t *linkp,
2N/A struct di_devlink *vlp)
2N/A{
2N/A struct stat sbuf;
2N/A const char *minor_path = NULL;
2N/A char abs_path[PATH_MAX], cont[PATH_MAX];
2N/A
2N/A /*
2N/A * It is legal for the link's content and type to be unknown.
2N/A * but one of absolute or relative path must be set.
2N/A */
2N/A if (vlp->rel_path == NULL && vlp->abs_path == NULL) {
2N/A (void) dprintf(DBG_ERR, "visit_link: invalid arguments\n");
2N/A return (DI_WALK_CONTINUE);
2N/A }
2N/A
2N/A if (vlp->rel_path == NULL) {
2N/A vlp->rel_path = (char *)rel_path(hdp, vlp->abs_path);
2N/A if (vlp->rel_path == NULL || vlp->rel_path[0] == '\0')
2N/A return (DI_WALK_CONTINUE);
2N/A }
2N/A
2N/A if (linkp->regp) {
2N/A if (regexec(linkp->regp, vlp->rel_path, 0, NULL, 0) != 0)
2N/A return (DI_WALK_CONTINUE);
2N/A }
2N/A
2N/A if (vlp->abs_path == NULL) {
2N/A assert(vlp->rel_path[0] != '/');
2N/A (void) snprintf(abs_path, sizeof (abs_path), "%s/%s",
2N/A hdp->dev_dir, vlp->rel_path);
2N/A vlp->abs_path = abs_path;
2N/A }
2N/A
2N/A if (vlp->content == NULL) {
2N/A if (s_readlink(vlp->abs_path, cont, sizeof (cont)) < 0) {
2N/A return (DI_WALK_CONTINUE);
2N/A }
2N/A vlp->content = cont;
2N/A }
2N/A
2N/A
2N/A if (vlp->type == 0) {
2N/A if (is_minor_node(vlp->content, &minor_path)) {
2N/A vlp->type = DI_PRIMARY_LINK;
2N/A } else {
2N/A vlp->type = DI_SECONDARY_LINK;
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * Filter based on minor path
2N/A */
2N/A if (linkp->minor_path) {
2N/A char tmp[PATH_MAX];
2N/A
2N/A /*
2N/A * derive minor path
2N/A */
2N/A if (vlp->type == DI_SECONDARY_LINK) {
2N/A
2N/A#ifdef DEBUG
2N/A /*LINTED*/
2N/A assert(sizeof (tmp) >= PATH_MAX);
2N/A#endif
2N/A if (s_realpath(vlp->abs_path, tmp) == NULL)
2N/A return (DI_WALK_CONTINUE);
2N/A
2N/A if (!is_minor_node(tmp, &minor_path))
2N/A return (DI_WALK_CONTINUE);
2N/A
2N/A } else if (minor_path == NULL) {
2N/A if (!is_minor_node(vlp->content, &minor_path))
2N/A return (DI_WALK_CONTINUE);
2N/A }
2N/A
2N/A assert(minor_path != NULL);
2N/A
2N/A if (strcmp(linkp->minor_path, minor_path) != 0)
2N/A return (DI_WALK_CONTINUE);
2N/A }
2N/A
2N/A /*
2N/A * Filter based on link type
2N/A */
2N/A if (!TYPE_NONE(linkp->flags) && LINK_TYPE(linkp->flags) != vlp->type) {
2N/A return (DI_WALK_CONTINUE);
2N/A }
2N/A
2N/A if (lstat(vlp->abs_path, &sbuf) < 0) {
2N/A dprintf(DBG_ERR, "visit_link: %s: lstat failed: %s\n",
2N/A vlp->abs_path, strerror(errno));
2N/A return (DI_WALK_CONTINUE);
2N/A }
2N/A
2N/A return (linkp->fcn(vlp, linkp->arg));
2N/A}
2N/A
2N/Astatic int
2N/Adevlink_valid(di_devlink_t devlink)
2N/A{
2N/A if (devlink == NULL || devlink->rel_path == NULL ||
2N/A devlink->abs_path == NULL || devlink->content == NULL ||
2N/A TYPE_NONE(devlink->type)) {
2N/A return (0);
2N/A }
2N/A
2N/A return (1);
2N/A}
2N/A
2N/Aconst char *
2N/Adi_devlink_path(di_devlink_t devlink)
2N/A{
2N/A if (!devlink_valid(devlink)) {
2N/A errno = EINVAL;
2N/A return (NULL);
2N/A }
2N/A
2N/A return (devlink->abs_path);
2N/A}
2N/A
2N/Aconst char *
2N/Adi_devlink_content(di_devlink_t devlink)
2N/A{
2N/A if (!devlink_valid(devlink)) {
2N/A errno = EINVAL;
2N/A return (NULL);
2N/A }
2N/A
2N/A return (devlink->content);
2N/A}
2N/A
2N/Aint
2N/Adi_devlink_type(di_devlink_t devlink)
2N/A{
2N/A if (!devlink_valid(devlink)) {
2N/A errno = EINVAL;
2N/A return (-1);
2N/A }
2N/A
2N/A return (devlink->type);
2N/A}
2N/A
2N/Adi_devlink_t
2N/Adi_devlink_dup(di_devlink_t devlink)
2N/A{
2N/A struct di_devlink *duplink;
2N/A
2N/A if (!devlink_valid(devlink)) {
2N/A errno = EINVAL;
2N/A return (NULL);
2N/A }
2N/A
2N/A if ((duplink = calloc(1, sizeof (struct di_devlink))) == NULL) {
2N/A return (NULL);
2N/A }
2N/A
2N/A duplink->rel_path = strdup(devlink->rel_path);
2N/A duplink->abs_path = strdup(devlink->abs_path);
2N/A duplink->content = strdup(devlink->content);
2N/A duplink->type = devlink->type;
2N/A
2N/A if (!devlink_valid(duplink)) {
2N/A (void) di_devlink_free(duplink);
2N/A errno = ENOMEM;
2N/A return (NULL);
2N/A }
2N/A
2N/A return (duplink);
2N/A}
2N/A
2N/Aint
2N/Adi_devlink_free(di_devlink_t devlink)
2N/A{
2N/A if (devlink == NULL) {
2N/A errno = EINVAL;
2N/A return (-1);
2N/A }
2N/A
2N/A free(devlink->rel_path);
2N/A free(devlink->abs_path);
2N/A free(devlink->content);
2N/A free(devlink);
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Obtain path relative to dev_dir
2N/A */
2N/Astatic const char *
2N/Arel_path(struct di_devlink_handle *hdp, const char *path)
2N/A{
2N/A const size_t len = strlen(hdp->dev_dir);
2N/A
2N/A if (strncmp(path, hdp->dev_dir, len) != 0)
2N/A return (NULL);
2N/A
2N/A if (path[len] == '\0')
2N/A return (&path[len]);
2N/A
2N/A if (path[len] != '/')
2N/A return (NULL);
2N/A
2N/A return (&path[len+1]);
2N/A}
2N/A
2N/Astatic int
2N/Arecurse_dev(struct di_devlink_handle *hdp, recurse_t *rp)
2N/A{
2N/A int ret = 0;
2N/A
2N/A (void) do_recurse(hdp->dev_dir, hdp, rp, &ret);
2N/A
2N/A return (ret);
2N/A}
2N/A
2N/Astatic int
2N/Ado_recurse(
2N/A const char *dir,
2N/A struct di_devlink_handle *hdp,
2N/A recurse_t *rp,
2N/A int *retp)
2N/A{
2N/A size_t len;
2N/A const char *rel;
2N/A struct stat sbuf;
2N/A char cur[PATH_MAX], *cp;
2N/A int i, rv = DI_WALK_CONTINUE;
2N/A finddevhdl_t handle;
2N/A char *d_name;
2N/A
2N/A
2N/A if ((rel = rel_path(hdp, dir)) == NULL)
2N/A return (DI_WALK_CONTINUE);
2N/A
2N/A /*
2N/A * Skip directories we are not interested in.
2N/A */
2N/A for (i = 0; i < N_SKIP_DIRS; i++) {
2N/A if (strcmp(rel, skip_dirs[i]) == 0) {
2N/A (void) dprintf(DBG_STEP, "do_recurse: skipping %s\n",
2N/A dir);
2N/A return (DI_WALK_CONTINUE);
2N/A }
2N/A }
2N/A
2N/A (void) dprintf(DBG_STEP, "do_recurse: dir = %s\n", dir);
2N/A
2N/A if (finddev_readdir(dir, &handle) != 0)
2N/A return (DI_WALK_CONTINUE);
2N/A
2N/A (void) snprintf(cur, sizeof (cur), "%s/", dir);
2N/A len = strlen(cur);
2N/A cp = cur + len;
2N/A len = sizeof (cur) - len;
2N/A
2N/A for (;;) {
2N/A if ((d_name = (char *)finddev_next(handle)) == NULL)
2N/A break;
2N/A
2N/A if (strlcpy(cp, d_name, len) >= len)
2N/A break;
2N/A
2N/A /*
2N/A * Skip files we are not interested in.
2N/A */
2N/A for (i = 0; i < N_SKIP_FILES; i++) {
2N/A
2N/A rel = rel_path(hdp, cur);
2N/A if (rel == NULL || strcmp(rel, skip_files[i]) == 0) {
2N/A (void) dprintf(DBG_STEP,
2N/A "do_recurse: skipping %s\n", cur);
2N/A goto next_entry;
2N/A }
2N/A }
2N/A
2N/A if (lstat(cur, &sbuf) == 0) {
2N/A if (S_ISDIR(sbuf.st_mode)) {
2N/A rv = do_recurse(cur, hdp, rp, retp);
2N/A } else if (S_ISLNK(sbuf.st_mode)) {
2N/A rv = rp->fcn(hdp, rp->data, cur);
2N/A } else {
2N/A (void) dprintf(DBG_STEP,
2N/A "do_recurse: Skipping entry: %s\n", cur);
2N/A }
2N/A } else {
2N/A (void) dprintf(DBG_ERR, "do_recurse: cur(%s): lstat"
2N/A " failed: %s\n", cur, strerror(errno));
2N/A }
2N/A
2N/Anext_entry:
2N/A *cp = '\0';
2N/A
2N/A if (rv != DI_WALK_CONTINUE)
2N/A break;
2N/A }
2N/A
2N/A finddev_close(handle);
2N/A
2N/A return (rv);
2N/A}
2N/A
2N/A
2N/Astatic int
2N/Acheck_attr(uint32_t attr)
2N/A{
2N/A switch (attr & A_LINK_TYPES) {
2N/A case A_PRIMARY:
2N/A case A_SECONDARY:
2N/A return (1);
2N/A default:
2N/A dprintf(DBG_ERR, "check_attr: incorrect attr(%u)\n",
2N/A attr);
2N/A return (0);
2N/A }
2N/A}
2N/A
2N/Astatic int
2N/Aattr2type(uint32_t attr)
2N/A{
2N/A switch (attr & A_LINK_TYPES) {
2N/A case A_PRIMARY:
2N/A return (DI_PRIMARY_LINK);
2N/A case A_SECONDARY:
2N/A return (DI_SECONDARY_LINK);
2N/A default:
2N/A dprintf(DBG_ERR, "attr2type: incorrect attr(%u)\n",
2N/A attr);
2N/A return (0);
2N/A }
2N/A}
2N/A
2N/A/* Allocate new node and link it in */
2N/Astatic cache_node_t *
2N/Anode_insert(
2N/A struct di_devlink_handle *hdp,
2N/A cache_node_t *pcnp,
2N/A const char *path,
2N/A int insert)
2N/A{
2N/A cache_node_t *cnp;
2N/A
2N/A if (path == NULL) {
2N/A errno = EINVAL;
2N/A SET_DB_ERR(hdp);
2N/A return (NULL);
2N/A }
2N/A
2N/A if ((cnp = calloc(1, sizeof (cache_node_t))) == NULL) {
2N/A SET_DB_ERR(hdp);
2N/A return (NULL);
2N/A }
2N/A
2N/A if ((cnp->path = strdup(path)) == NULL) {
2N/A SET_DB_ERR(hdp);
2N/A free(cnp);
2N/A return (NULL);
2N/A }
2N/A
2N/A cnp->parent = pcnp;
2N/A
2N/A if (pcnp == NULL) {
2N/A assert(strcmp(path, "/") == 0);
2N/A assert(CACHE(hdp)->root == NULL);
2N/A CACHE(hdp)->root = cnp;
2N/A } else if (insert == INSERT_HEAD) {
2N/A cnp->sib = pcnp->child;
2N/A pcnp->child = cnp;
2N/A } else if (CACHE_LAST(hdp) && CACHE_LAST(hdp)->node &&
2N/A CACHE_LAST(hdp)->node->parent == pcnp &&
2N/A CACHE_LAST(hdp)->node->sib == NULL) {
2N/A
2N/A CACHE_LAST(hdp)->node->sib = cnp;
2N/A
2N/A } else {
2N/A cache_node_t **pp;
2N/A
2N/A for (pp = &pcnp->child; *pp != NULL; pp = &(*pp)->sib)
2N/A ;
2N/A *pp = cnp;
2N/A }
2N/A
2N/A return (cnp);
2N/A}
2N/A
2N/A/*
2N/A * Allocate a new minor and link it in either at the tail or head
2N/A * of the minor list depending on the value of "prev".
2N/A */
2N/Astatic cache_minor_t *
2N/Aminor_insert(
2N/A struct di_devlink_handle *hdp,
2N/A cache_node_t *pcnp,
2N/A const char *name,
2N/A const char *nodetype,
2N/A cache_minor_t **prev)
2N/A{
2N/A cache_minor_t *cmnp;
2N/A
2N/A if (pcnp == NULL || name == NULL) {
2N/A errno = EINVAL;
2N/A SET_DB_ERR(hdp);
2N/A return (NULL);
2N/A }
2N/A
2N/A /*
2N/A * Some pseudo drivers don't specify nodetype. Assume pseudo if
2N/A * nodetype is not specified.
2N/A */
2N/A if (nodetype == NULL)
2N/A nodetype = DDI_PSEUDO;
2N/A
2N/A if ((cmnp = calloc(1, sizeof (cache_minor_t))) == NULL) {
2N/A SET_DB_ERR(hdp);
2N/A return (NULL);
2N/A }
2N/A
2N/A cmnp->name = strdup(name);
2N/A cmnp->nodetype = strdup(nodetype);
2N/A if (cmnp->name == NULL || cmnp->nodetype == NULL) {
2N/A SET_DB_ERR(hdp);
2N/A free(cmnp->name);
2N/A free(cmnp->nodetype);
2N/A free(cmnp);
2N/A return (NULL);
2N/A }
2N/A
2N/A cmnp->node = pcnp;
2N/A
2N/A /* Add to node's minor list */
2N/A if (prev == NULL) {
2N/A cmnp->sib = pcnp->minor;
2N/A pcnp->minor = cmnp;
2N/A } else {
2N/A assert(*prev == NULL);
2N/A *prev = cmnp;
2N/A }
2N/A
2N/A return (cmnp);
2N/A}
2N/A
2N/Astatic cache_link_t *
2N/Alink_insert(
2N/A struct di_devlink_handle *hdp,
2N/A cache_minor_t *cmnp,
2N/A const char *path,
2N/A const char *content,
2N/A uint32_t attr)
2N/A{
2N/A cache_link_t *clp;
2N/A
2N/A if (path == NULL || content == NULL || !check_attr(attr)) {
2N/A errno = EINVAL;
2N/A SET_DB_ERR(hdp);
2N/A return (NULL);
2N/A }
2N/A
2N/A if ((clp = calloc(1, sizeof (cache_link_t))) == NULL) {
2N/A SET_DB_ERR(hdp);
2N/A return (NULL);
2N/A }
2N/A
2N/A clp->path = strdup(path);
2N/A clp->content = strdup(content);
2N/A if (clp->path == NULL || clp->content == NULL) {
2N/A SET_DB_ERR(hdp);
2N/A link_free(&clp);
2N/A return (NULL);
2N/A }
2N/A
2N/A clp->attr = attr;
2N/A hash_insert(hdp, clp);
2N/A clp->minor = cmnp;
2N/A
2N/A /* Add to minor's link list */
2N/A if (cmnp != NULL) {
2N/A clp->sib = cmnp->link;
2N/A cmnp->link = clp;
2N/A } else {
2N/A clp->sib = CACHE(hdp)->dngl;
2N/A CACHE(hdp)->dngl = clp;
2N/A }
2N/A
2N/A return (clp);
2N/A}
2N/A
2N/Astatic void
2N/Ahash_insert(struct di_devlink_handle *hdp, cache_link_t *clp)
2N/A{
2N/A uint_t hval;
2N/A
2N/A hval = hashfn(hdp, clp->path);
2N/A clp->hash = CACHE_HASH(hdp, hval);
2N/A CACHE_HASH(hdp, hval) = clp;
2N/A}
2N/A
2N/A
2N/Astatic struct db_node *
2N/Aget_node(struct di_devlink_handle *hdp, uint32_t idx)
2N/A{
2N/A return (map_seg(hdp, idx, PROT_READ, DB_NODE));
2N/A}
2N/A
2N/Astatic struct db_node *
2N/Aset_node(struct di_devlink_handle *hdp, uint32_t idx)
2N/A{
2N/A return (map_seg(hdp, idx, PROT_READ | PROT_WRITE, DB_NODE));
2N/A}
2N/A
2N/Astatic struct db_minor *
2N/Aget_minor(struct di_devlink_handle *hdp, uint32_t idx)
2N/A{
2N/A return (map_seg(hdp, idx, PROT_READ, DB_MINOR));
2N/A}
2N/A
2N/Astatic struct db_minor *
2N/Aset_minor(struct di_devlink_handle *hdp, uint32_t idx)
2N/A{
2N/A return (map_seg(hdp, idx, PROT_READ | PROT_WRITE, DB_MINOR));
2N/A}
2N/A
2N/Astatic struct db_link *
2N/Aget_link(struct di_devlink_handle *hdp, uint32_t idx)
2N/A{
2N/A return (map_seg(hdp, idx, PROT_READ, DB_LINK));
2N/A}
2N/A
2N/Astatic struct db_link *
2N/Aset_link(struct di_devlink_handle *hdp, uint32_t idx)
2N/A{
2N/A return (map_seg(hdp, idx, PROT_READ | PROT_WRITE, DB_LINK));
2N/A}
2N/A
2N/Astatic char *
2N/Aget_string(struct di_devlink_handle *hdp, uint32_t idx)
2N/A{
2N/A return (map_seg(hdp, idx, PROT_READ, DB_STR));
2N/A}
2N/A
2N/Astatic char *
2N/Aset_string(struct di_devlink_handle *hdp, uint32_t idx)
2N/A{
2N/A return (map_seg(hdp, idx, PROT_READ | PROT_WRITE, DB_STR));
2N/A}
2N/A
2N/A
2N/A/*
2N/A * Returns the element corresponding to idx. If the portion of file involved
2N/A * is not yet mapped, does an mmap() as well. Existing mappings are not changed.
2N/A */
2N/Astatic void *
2N/Amap_seg(
2N/A struct di_devlink_handle *hdp,
2N/A uint32_t idx,
2N/A int prot,
2N/A db_seg_t seg)
2N/A{
2N/A int s;
2N/A off_t off;
2N/A size_t slen;
2N/A caddr_t addr;
2N/A
2N/A if (idx == DB_NIL) {
2N/A return (NULL);
2N/A }
2N/A
2N/A if (!VALID_INDEX(hdp, seg, idx)) {
2N/A (void) dprintf(DBG_ERR, "map_seg: seg(%d): invalid idx(%u)\n",
2N/A seg, idx);
2N/A return (NULL);
2N/A }
2N/A
2N/A /*
2N/A * If the seg is already mapped in, use it if the access type is
2N/A * valid.
2N/A */
2N/A if (DB_SEG(hdp, seg) != NULL) {
2N/A if (DB_SEG_PROT(hdp, seg) != prot) {
2N/A (void) dprintf(DBG_ERR, "map_seg: illegal access: "
2N/A "seg[%d]: idx=%u, seg_prot=%d, access=%d\n",
2N/A seg, idx, DB_SEG_PROT(hdp, seg), prot);
2N/A return (NULL);
2N/A }
2N/A return (DB_SEG(hdp, seg) + idx * elem_sizes[seg]);
2N/A }
2N/A
2N/A /*
2N/A * Segment is not mapped. Mmap() the segment.
2N/A */
2N/A off = seg_size(hdp, DB_HEADER);
2N/A for (s = 0; s < seg; s++) {
2N/A off += seg_size(hdp, s);
2N/A }
2N/A slen = seg_size(hdp, seg);
2N/A
2N/A addr = mmap(0, slen, prot, MAP_SHARED, DB(hdp)->db_fd, off);
2N/A if (addr == MAP_FAILED) {
2N/A (void) dprintf(DBG_ERR, "map_seg: seg[%d]: mmap failed: %s\n",
2N/A seg, strerror(errno));
2N/A (void) dprintf(DBG_ERR, "map_seg: args: len=%lu, prot=%d,"
2N/A " fd=%d, off=%ld\n", (ulong_t)slen, prot, DB(hdp)->db_fd,
2N/A off);
2N/A return (NULL);
2N/A }
2N/A
2N/A DB_SEG(hdp, seg) = addr;
2N/A DB_SEG_PROT(hdp, seg) = prot;
2N/A
2N/A (void) dprintf(DBG_STEP, "map_seg: seg[%d]: len=%lu, prot=%d, fd=%d, "
2N/A "off=%ld, seg_base=%p\n", seg, (ulong_t)slen, prot, DB(hdp)->db_fd,
2N/A off, (void *)addr);
2N/A
2N/A return (DB_SEG(hdp, seg) + idx * elem_sizes[seg]);
2N/A}
2N/A
2N/A/*
2N/A * Computes the size of a segment rounded up to the nearest page boundary.
2N/A */
2N/Astatic size_t
2N/Aseg_size(struct di_devlink_handle *hdp, int seg)
2N/A{
2N/A size_t sz;
2N/A
2N/A assert(DB_HDR(hdp)->page_sz);
2N/A
2N/A if (seg == DB_HEADER) {
2N/A sz = HDR_LEN;
2N/A } else {
2N/A assert(DB_NUM(hdp, seg) >= 1);
2N/A sz = DB_NUM(hdp, seg) * elem_sizes[seg];
2N/A }
2N/A
2N/A sz = (sz / DB_HDR(hdp)->page_sz) + 1;
2N/A
2N/A sz *= DB_HDR(hdp)->page_sz;
2N/A
2N/A return (sz);
2N/A}
2N/A
2N/Astatic size_t
2N/Asize_db(struct di_devlink_handle *hdp, long page_sz, uint32_t *count)
2N/A{
2N/A int i;
2N/A size_t sz;
2N/A cache_link_t *clp;
2N/A
2N/A assert(page_sz > 0);
2N/A
2N/A /* Take "NIL" element into account */
2N/A for (i = 0; i < DB_TYPES; i++) {
2N/A count[i] = 1;
2N/A }
2N/A
2N/A count_node(CACHE(hdp)->root, count);
2N/A
2N/A for (clp = CACHE(hdp)->dngl; clp != NULL; clp = clp->sib) {
2N/A count_link(clp, count);
2N/A }
2N/A
2N/A sz = ((HDR_LEN / page_sz) + 1) * page_sz;
2N/A for (i = 0; i < DB_TYPES; i++) {
2N/A assert(count[i] >= 1);
2N/A sz += (((count[i] * elem_sizes[i]) / page_sz) + 1) * page_sz;
2N/A (void) dprintf(DBG_INFO, "N[%u]=%u\n", i, count[i]);
2N/A }
2N/A (void) dprintf(DBG_INFO, "DB size=%lu\n", (ulong_t)sz);
2N/A
2N/A return (sz);
2N/A}
2N/A
2N/A
2N/Astatic void
2N/Acount_node(cache_node_t *cnp, uint32_t *count)
2N/A{
2N/A cache_minor_t *cmnp;
2N/A
2N/A if (cnp == NULL)
2N/A return;
2N/A
2N/A count[DB_NODE]++;
2N/A count_string(cnp->path, count);
2N/A
2N/A for (cmnp = cnp->minor; cmnp != NULL; cmnp = cmnp->sib) {
2N/A count_minor(cmnp, count);
2N/A }
2N/A
2N/A for (cnp = cnp->child; cnp != NULL; cnp = cnp->sib) {
2N/A count_node(cnp, count);
2N/A }
2N/A
2N/A}
2N/A
2N/Astatic void
2N/Acount_minor(cache_minor_t *cmnp, uint32_t *count)
2N/A{
2N/A cache_link_t *clp;
2N/A
2N/A if (cmnp == NULL)
2N/A return;
2N/A
2N/A count[DB_MINOR]++;
2N/A count_string(cmnp->name, count);
2N/A count_string(cmnp->nodetype, count);
2N/A
2N/A for (clp = cmnp->link; clp != NULL; clp = clp->sib) {
2N/A count_link(clp, count);
2N/A }
2N/A}
2N/A
2N/Astatic void
2N/Acount_link(cache_link_t *clp, uint32_t *count)
2N/A{
2N/A if (clp == NULL)
2N/A return;
2N/A
2N/A count[DB_LINK]++;
2N/A count_string(clp->path, count);
2N/A count_string(clp->content, count);
2N/A}
2N/A
2N/A
2N/Astatic void
2N/Acount_string(const char *str, uint32_t *count)
2N/A{
2N/A if (str == NULL) {
2N/A (void) dprintf(DBG_ERR, "count_string: NULL argument\n");
2N/A return;
2N/A }
2N/A
2N/A count[DB_STR] += strlen(str) + 1;
2N/A}
2N/A
2N/Astatic uint_t
2N/Ahashfn(struct di_devlink_handle *hdp, const char *str)
2N/A{
2N/A const char *cp;
2N/A ulong_t hval = 0;
2N/A
2N/A if (str == NULL) {
2N/A return (0);
2N/A }
2N/A
2N/A assert(CACHE(hdp)->hash_sz >= MIN_HASH_SIZE);
2N/A
2N/A for (cp = str; *cp != '\0'; cp++) {
2N/A hval += *cp;
2N/A }
2N/A
2N/A return (hval % CACHE(hdp)->hash_sz);
2N/A}
2N/A
2N/A/*
2N/A * enter_db_lock()
2N/A *
2N/A * If the handle is IS_RDWR then we lock as writer to "update" database,
2N/A * if IS_RDONLY then we lock as reader to "snapshot" database. The
2N/A * implementation uses advisory file locking.
2N/A *
2N/A * This function returns:
2N/A * == 1 success and grabbed the lock file, we can open the DB.
2N/A * == 0 success but did not lock the lock file, reader must walk
2N/A * the /dev directory.
2N/A * == -1 failure.
2N/A */
2N/Astatic int
2N/Aenter_db_lock(struct di_devlink_handle *hdp, const char *root_dir)
2N/A{
2N/A int fd;
2N/A struct flock lock;
2N/A char lockfile[PATH_MAX];
2N/A int rv;
2N/A int writer = HDL_RDWR(hdp);
2N/A static int did_sync = 0;
2N/A int eintrs;
2N/A
2N/A assert(hdp->lock_fd < 0);
2N/A
2N/A get_db_path(hdp, DB_LOCK, lockfile, sizeof (lockfile));
2N/A
2N/A dprintf(DBG_LCK, "enter_db_lock: %s BEGIN\n",
2N/A writer ? "update" : "snapshot");
2N/A
2N/A /* Record locks are per-process. Protect against multiple threads. */
2N/A (void) mutex_lock(&update_mutex);
2N/A
2N/Aagain: if ((fd = open(lockfile,
2N/A (writer ? (O_RDWR|O_CREAT) : O_RDONLY), DB_LOCK_PERMS)) < 0) {
2N/A /*
2N/A * Typically the lock file and the database go hand in hand.
2N/A * If we find that the lock file does not exist (for some
2N/A * unknown reason) and we are the reader then we return
2N/A * success (after triggering devfsadm to create the file and
2N/A * a retry) so that we can still provide service via slow
2N/A * /dev walk. If we get a failure as a writer we want the
2N/A * error to manifests itself.
2N/A */
2N/A if ((errno == ENOENT) && !writer) {
2N/A /* If reader, signal once to get files created */
2N/A if (did_sync == 0) {
2N/A did_sync = 1;
2N/A dprintf(DBG_LCK, "enter_db_lock: %s OSYNC\n",
2N/A writer ? "update" : "snapshot");
2N/A
2N/A /* signal to get files created */
2N/A (void) devlink_create(root_dir, NULL,
2N/A DCA_DEVLINK_SYNC);
2N/A goto again;
2N/A }
2N/A dprintf(DBG_LCK, "enter_db_lock: %s OPENFAILD %s: "
2N/A "WALK\n", writer ? "update" : "snapshot",
2N/A strerror(errno));
2N/A (void) mutex_unlock(&update_mutex);
2N/A return (0); /* success, but not locked */
2N/A } else {
2N/A dprintf(DBG_LCK, "enter_db_lock: %s OPENFAILD %s\n",
2N/A writer ? "update" : "snapshot", strerror(errno));
2N/A (void) mutex_unlock(&update_mutex);
2N/A return (-1); /* failed */
2N/A }
2N/A }
2N/A
2N/A lock.l_type = writer ? F_WRLCK : F_RDLCK;
2N/A lock.l_whence = SEEK_SET;
2N/A lock.l_start = 0;
2N/A lock.l_len = 0;
2N/A
2N/A /* Enter the lock. */
2N/A for (eintrs = 0; eintrs < MAX_LOCK_RETRY; eintrs++) {
2N/A rv = fcntl(fd, F_SETLKW, &lock);
2N/A if ((rv != -1) || (errno != EINTR))
2N/A break;
2N/A }
2N/A
2N/A if (rv != -1) {
2N/A hdp->lock_fd = fd;
2N/A dprintf(DBG_LCK, "enter_db_lock: %s LOCKED\n",
2N/A writer ? "update" : "snapshot");
2N/A return (1); /* success, locked */
2N/A }
2N/A
2N/A (void) close(fd);
2N/A dprintf(DBG_ERR, "enter_db_lock: %s FAILED: %s: WALK\n",
2N/A writer ? "update" : "snapshot", strerror(errno));
2N/A (void) mutex_unlock(&update_mutex);
2N/A return (-1);
2N/A}
2N/A
2N/A/*
2N/A * Close and re-open lock file every time so that it is recreated if deleted.
2N/A */
2N/Astatic void
2N/Aexit_db_lock(struct di_devlink_handle *hdp)
2N/A{
2N/A struct flock unlock;
2N/A int writer = HDL_RDWR(hdp);
2N/A
2N/A if (hdp->lock_fd < 0) {
2N/A return;
2N/A }
2N/A
2N/A unlock.l_type = F_UNLCK;
2N/A unlock.l_whence = SEEK_SET;
2N/A unlock.l_start = 0;
2N/A unlock.l_len = 0;
2N/A
2N/A dprintf(DBG_LCK, "exit_db_lock : %s UNLOCKED\n",
2N/A writer ? "update" : "snapshot");
2N/A if (fcntl(hdp->lock_fd, F_SETLK, &unlock) == -1) {
2N/A dprintf(DBG_ERR, "exit_db_lock : %s failed: %s\n",
2N/A writer ? "update" : "snapshot", strerror(errno));
2N/A }
2N/A
2N/A (void) close(hdp->lock_fd);
2N/A
2N/A hdp->lock_fd = -1;
2N/A
2N/A (void) mutex_unlock(&update_mutex);
2N/A}
2N/A
2N/A/*
2N/A * returns 1 if contents is a minor node in /devices.
2N/A * If mn_root is not NULL, mn_root is set to:
2N/A * if contents is a /dev node, mn_root = contents
2N/A * OR
2N/A * if contents is a /devices node, mn_root set to the '/'
2N/A * following /devices.
2N/A */
2N/Aint
2N/Ais_minor_node(const char *contents, const char **mn_root)
2N/A{
2N/A char *ptr, *prefix;
2N/A
2N/A prefix = "../devices/";
2N/A
2N/A if ((ptr = strstr(contents, prefix)) != NULL) {
2N/A
2N/A /* mn_root should point to the / following /devices */
2N/A if (mn_root != NULL) {
2N/A *mn_root = ptr += strlen(prefix) - 1;
2N/A }
2N/A return (1);
2N/A }
2N/A
2N/A prefix = "/devices/";
2N/A
2N/A if (strncmp(contents, prefix, strlen(prefix)) == 0) {
2N/A
2N/A /* mn_root should point to the / following /devices/ */
2N/A if (mn_root != NULL) {
2N/A *mn_root = contents + strlen(prefix) - 1;
2N/A }
2N/A return (1);
2N/A }
2N/A
2N/A if (mn_root != NULL) {
2N/A *mn_root = contents;
2N/A }
2N/A return (0);
2N/A}
2N/A
2N/Astatic int
2N/As_readlink(const char *link, char *buf, size_t blen)
2N/A{
2N/A int rv;
2N/A
2N/A if ((rv = readlink(link, buf, blen)) == -1)
2N/A goto bad;
2N/A
2N/A if (rv >= blen && buf[blen - 1] != '\0') {
2N/A errno = ENAMETOOLONG;
2N/A goto bad;
2N/A } else if (rv < blen) {
2N/A buf[rv] = '\0';
2N/A }
2N/A
2N/A return (0);
2N/Abad:
2N/A dprintf(DBG_ERR, "s_readlink: %s: failed: %s\n",
2N/A link, strerror(errno));
2N/A return (-1);
2N/A}
2N/A
2N/A/*
2N/A * Synchronous link creation interface routines
2N/A * The scope of the operation is determined by the "name" arg.
2N/A * "name" can be NULL, a driver name or a devfs pathname (without /devices)
2N/A *
2N/A * "name" creates
2N/A * ====== =======
2N/A *
2N/A * NULL => All devlinks in system
2N/A * <driver> => devlinks for named driver
2N/A * /pci@1 => devlinks for subtree rooted at pci@1
2N/A * /pseudo/foo@0:X => devlinks for minor X
2N/A *
2N/A * devlink_create() returns 0 on success or an errno value on failure
2N/A */
2N/A
2N/A/*
2N/A * Retry all but unrecoverable errors in case devfsadm
2N/A * has been or needs to be restarted. As EAGAIN indicates
2N/A * possible deadlock detection or resource exhaustion,
2N/A * retry that without timing out.
2N/A */
2N/A#define MAX_DAEMON_ATTEMPTS 60
2N/A#define DAEMON_BACKOFF_DELAY 1
2N/A
2N/Astatic int
2N/Adevlink_create(const char *root, const char *name, int dca_devlink_flag)
2N/A{
2N/A int retry;
2N/A struct dca_off dca;
2N/A ucred_t *uc = NULL;
2N/A const priv_set_t *eset;
2N/A int rv;
2N/A
2N/A assert(root);
2N/A
2N/A if ((uc = ucred_get(P_MYID)) == NULL)
2N/A return (EPERM);
2N/A eset = ucred_getprivset(uc, PRIV_EFFECTIVE);
2N/A
2N/A if (!priv_ismember(eset, PRIV_SYS_CONFIG)) {
2N/A ucred_free(uc);
2N/A return (EPERM);
2N/A }
2N/A
2N/A /* Convert name into arg for door_call */
2N/A if (dca_init(name, &dca, dca_devlink_flag) != 0) {
2N/A ucred_free(uc);
2N/A return (EINVAL);
2N/A }
2N/A
2N/A /* Attempt to call the daemon */
2N/A retry = 0;
2N/A for (;;) {
2N/A dca.dca_error = 0;
2N/A if (daemon_call(root, &dca) == 0) {
2N/A /*
2N/A * If the daemon isn't running and with full
2N/A * privileges, we are most likely in the install
2N/A * environment. We can run devfsadm directly.
2N/A */
2N/A if (priv_ismember(eset, PRIV_PROC_FORK) &&
2N/A priv_ismember(eset, PRIV_PROC_EXEC) &&
2N/A priv_ismember(eset, PRIV_SYS_DEVICES) &&
2N/A ucred_geteuid(uc) == 0 && ucred_getegid(uc) == 0) {
2N/A exec_devfsadm(root, &dca);
2N/A rv = 0;
2N/A goto out;
2N/A }
2N/A /*
2N/A * Not install so cannot do much but retry in
2N/A * the hope that the daemon restarts.
2N/A */
2N/A rv = ENXIO;
2N/A } else {
2N/A rv = dca.dca_error;
2N/A if (rv == 0 || rv == ENOTSUP || rv == EPERM)
2N/A goto out;
2N/A }
2N/A if (rv != EAGAIN) {
2N/A if (retry++ == MAX_DAEMON_ATTEMPTS)
2N/A goto out;
2N/A }
2N/A (void) sleep(DAEMON_BACKOFF_DELAY);
2N/A }
2N/A
2N/Aout:
2N/A if (uc != NULL)
2N/A ucred_free(uc);
2N/A return (rv);
2N/A}
2N/A
2N/A/*
2N/A * The "name" member of "struct dca" contains data in the following order
2N/A * root'\0'minor'\0'driver'\0'
2N/A * The root component is always present at offset 0 in the "name" field.
2N/A * The driver and minor are optional. If present they have a non-zero
2N/A * offset in the "name" member.
2N/A */
2N/Astatic int
2N/Adca_init(const char *name, struct dca_off *dcp, int dca_flags)
2N/A{
2N/A char *cp;
2N/A
2N/A dcp->dca_root = 0;
2N/A dcp->dca_minor = 0;
2N/A dcp->dca_driver = 0;
2N/A dcp->dca_error = 0;
2N/A dcp->dca_flags = dca_flags;
2N/A dcp->dca_name[0] = '\0';
2N/A
2N/A name = name ? name : "/";
2N/A
2N/A /* Check if name is a driver name */
2N/A if (*name != '/') {
2N/A (void) snprintf(dcp->dca_name, sizeof (dcp->dca_name),
2N/A "/ %s", name);
2N/A dcp->dca_root = 0;
2N/A *(dcp->dca_name + 1) = '\0';
2N/A dcp->dca_driver = 2;
2N/A return (0);
2N/A }
2N/A
2N/A (void) snprintf(dcp->dca_name, sizeof (dcp->dca_name), "%s", name);
2N/A
2N/A /* /devices prefix not allowed in devfs pathname */
2N/A if (is_minor_node(name, NULL))
2N/A return (-1);
2N/A
2N/A dcp->dca_root = 0;
2N/A if (cp = strrchr(dcp->dca_name, ':')) {
2N/A *cp++ = '\0';
2N/A dcp->dca_minor = cp - dcp->dca_name;
2N/A }
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Returns an indication whether the devfadm daemon is running,
2N/A * as determined by the existence of the sync door file.
2N/A * If the daemon is functioning, status of the door call
2N/A * is returned in dcp->dca_error.
2N/A */
2N/Astatic int
2N/Adaemon_call(const char *root, struct dca_off *dcp)
2N/A{
2N/A door_arg_t arg;
2N/A int fd;
2N/A sigset_t oset, nset;
2N/A char synch_door[PATH_MAX];
2N/A struct stat sb;
2N/A int rv;
2N/A
2N/A /*
2N/A * If the door used to communicate with the devfsadm daemon
2N/A * doesn't exist or isn't a door, the daemon may not be
2N/A * available yet.
2N/A */
2N/A (void) snprintf(synch_door, sizeof (synch_door),
2N/A "%s/etc/dev/%s", root, DEVFSADM_SYNCH_DOOR);
2N/A if ((stat(synch_door, &sb) == -1) || !S_ISDOOR(sb.st_mode)) {
2N/A return (0);
2N/A }
2N/A
2N/A if ((fd = open(synch_door, O_RDONLY)) == -1) {
2N/A dprintf(DBG_ERR, "open of %s failed: %s\n",
2N/A synch_door, strerror(errno));
2N/A return (0);
2N/A }
2N/A
2N/A arg.data_ptr = (char *)dcp;
2N/A arg.data_size = sizeof (*dcp);
2N/A arg.desc_ptr = NULL;
2N/A arg.desc_num = 0;
2N/A arg.rbuf = (char *)dcp;
2N/A arg.rsize = sizeof (*dcp);
2N/A
2N/A /* Block signals until door call completes. */
2N/A (void) sigfillset(&nset);
2N/A (void) sigemptyset(&oset);
2N/A (void) sigprocmask(SIG_SETMASK, &nset, &oset);
2N/A
2N/A if ((rv = door_call(fd, &arg)) != 0) {
2N/A dcp->dca_error = errno;
2N/A }
2N/A (void) sigprocmask(SIG_SETMASK, &oset, NULL);
2N/A (void) close(fd);
2N/A
2N/A if (rv != 0)
2N/A return (1);
2N/A
2N/A assert(arg.data_ptr);
2N/A
2N/A /*LINTED*/
2N/A dcp->dca_error = ((struct dca_off *)arg.data_ptr)->dca_error;
2N/A
2N/A /*
2N/A * The doors interface may return data in a different buffer
2N/A * If that happens, deallocate buffer via munmap()
2N/A */
2N/A if (arg.rbuf != (char *)dcp)
2N/A (void) munmap(arg.rbuf, arg.rsize);
2N/A return (1);
2N/A}
2N/A
2N/A#define DEVFSADM_PATH "/usr/sbin/devfsadm"
2N/A#define DEVFSADM "devfsadm"
2N/A
2N/Astatic void
2N/Aexec_devfsadm(const char *root, struct dca_off *dcp)
2N/A{
2N/A int i;
2N/A char *argv[8];
2N/A
2N/A i = 0;
2N/A argv[i++] = DEVFSADM;
2N/A
2N/A /* Load the specified driver if driver name provided */
2N/A if (dcp->dca_driver) {
2N/A argv[i++] = "-i";
2N/A argv[i++] = &dcp->dca_name[dcp->dca_driver];
2N/A } else {
2N/A argv[i++] = "-n";
2N/A }
2N/A
2N/A if (root != NULL && strcmp(root, "/") != 0) {
2N/A argv[i++] = "-r";
2N/A argv[i++] = (char *)root;
2N/A }
2N/A
2N/A argv[i] = NULL;
2N/A
2N/A if (do_exec(DEVFSADM_PATH, argv))
2N/A dcp->dca_error = errno;
2N/A}
2N/A
2N/Astatic int
2N/Ado_exec(const char *path, char *const argv[])
2N/A{
2N/A int i;
2N/A pid_t cpid;
2N/A
2N/A#ifdef DEBUG
2N/A dprintf(DBG_INFO, "Executing %s\n\tArgument list:", path);
2N/A for (i = 0; argv[i] != NULL; i++) {
2N/A dprintf(DBG_INFO, " %s", argv[i]);
2N/A }
2N/A dprintf(DBG_INFO, "\n");
2N/A#endif
2N/A
2N/A if ((cpid = fork1()) == -1) {
2N/A dprintf(DBG_ERR, "fork1 failed: %s\n", strerror(errno));
2N/A return (-1);
2N/A }
2N/A
2N/A if (cpid == 0) { /* child process */
2N/A int fd;
2N/A
2N/A if ((fd = open("/dev/null", O_RDWR)) >= 0) {
2N/A (void) dup2(fd, fileno(stdout));
2N/A (void) dup2(fd, fileno(stderr));
2N/A (void) close(fd);
2N/A
2N/A (void) execv(path, argv);
2N/A } else {
2N/A dprintf(DBG_ERR, "open of /dev/null failed: %s\n",
2N/A strerror(errno));
2N/A }
2N/A
2N/A _exit(-1);
2N/A }
2N/A
2N/A /* Parent process */
2N/A if (waitpid(cpid, &i, 0) == cpid) {
2N/A if (WIFEXITED(i)) {
2N/A if (WEXITSTATUS(i) == 0) {
2N/A dprintf(DBG_STEP,
2N/A "do_exec: child exited normally\n");
2N/A return (0);
2N/A } else
2N/A errno = EINVAL;
2N/A } else {
2N/A /*
2N/A * The child was interrupted by a signal
2N/A */
2N/A errno = EINTR;
2N/A }
2N/A dprintf(DBG_ERR, "child terminated abnormally: %s\n",
2N/A strerror(errno));
2N/A } else {
2N/A dprintf(DBG_ERR, "waitpid failed: %s\n", strerror(errno));
2N/A }
2N/A
2N/A return (-1);
2N/A}
2N/A
2N/Astatic int
2N/Awalk_cache_links(di_devlink_handle_t hdp, cache_link_t *clp, link_desc_t *linkp)
2N/A{
2N/A int i;
2N/A
2N/A assert(HDL_RDWR(hdp) || HDL_RDONLY(hdp));
2N/A
2N/A dprintf(DBG_INFO, "walk_cache_links: initial link: %s\n",
2N/A clp ? clp->path : "<NULL>");
2N/A
2N/A /*
2N/A * First search the links under the specified minor. On the
2N/A * 2nd pass, search the dangling list - secondary links may
2N/A * exist on this list since they are not resolved during the
2N/A * /dev walk.
2N/A */
2N/A for (i = 0; i < 2; i++) {
2N/A for (; clp != NULL; clp = clp->sib) {
2N/A struct di_devlink vlink = {NULL};
2N/A
2N/A assert(clp->path[0] != '/');
2N/A
2N/A vlink.rel_path = clp->path;
2N/A vlink.content = clp->content;
2N/A vlink.type = attr2type(clp->attr);
2N/A
2N/A if (visit_link(hdp, linkp, &vlink)
2N/A != DI_WALK_CONTINUE) {
2N/A dprintf(DBG_INFO, "walk_cache_links: "
2N/A "terminating at link: %s\n", clp->path);
2N/A goto out;
2N/A }
2N/A }
2N/A
2N/A clp = CACHE(hdp)->dngl;
2N/A }
2N/A
2N/Aout:
2N/A
2N/A /* If i < 2, we terminated the walk prematurely */
2N/A return (i < 2 ? DI_WALK_TERMINATE : DI_WALK_CONTINUE);
2N/A}
2N/A
2N/Astatic void
2N/Awalk_all_cache(di_devlink_handle_t hdp, link_desc_t *linkp)
2N/A{
2N/A int i;
2N/A cache_link_t *clp;
2N/A
2N/A dprintf(DBG_INFO, "walk_all_cache: entered\n");
2N/A
2N/A for (i = 0; i < CACHE(hdp)->hash_sz; i++) {
2N/A clp = CACHE_HASH(hdp, i);
2N/A for (; clp; clp = clp->hash) {
2N/A struct di_devlink vlink = {NULL};
2N/A
2N/A assert(clp->path[0] != '/');
2N/A
2N/A vlink.rel_path = clp->path;
2N/A vlink.content = clp->content;
2N/A vlink.type = attr2type(clp->attr);
2N/A if (visit_link(hdp, linkp, &vlink) !=
2N/A DI_WALK_CONTINUE) {
2N/A dprintf(DBG_INFO, "walk_all_cache: terminating "
2N/A "walk at link: %s\n", clp->path);
2N/A return;
2N/A }
2N/A }
2N/A }
2N/A}
2N/A
2N/Astatic void
2N/Awalk_cache_minor(di_devlink_handle_t hdp, const char *mpath, link_desc_t *linkp)
2N/A{
2N/A cache_minor_t *cmnp;
2N/A
2N/A assert(mpath);
2N/A
2N/A if ((cmnp = lookup_minor(hdp, mpath, NULL, TYPE_CACHE)) != NULL) {
2N/A (void) walk_cache_links(hdp, cmnp->link, linkp);
2N/A } else {
2N/A dprintf(DBG_ERR, "lookup minor failed: %s\n", mpath);
2N/A }
2N/A}
2N/A
2N/Astatic void
2N/Awalk_cache_node(di_devlink_handle_t hdp, const char *path, link_desc_t *linkp)
2N/A{
2N/A cache_minor_t *cmnp;
2N/A cache_node_t *cnp;
2N/A
2N/A assert(path);
2N/A
2N/A if ((cnp = lookup_node(hdp, (char *)path, TYPE_CACHE)) == NULL) {
2N/A dprintf(DBG_ERR, "lookup node failed: %s\n", path);
2N/A return;
2N/A }
2N/A
2N/A for (cmnp = cnp->minor; cmnp != NULL; cmnp = cmnp->sib) {
2N/A if (walk_cache_links(hdp, cmnp->link, linkp)
2N/A == DI_WALK_TERMINATE)
2N/A break;
2N/A }
2N/A}
2N/A
2N/A/*
2N/A * Private function
2N/A *
2N/A * Walk cached links corresponding to the given path.
2N/A *
2N/A * path path to a node or minor node.
2N/A *
2N/A * flags specifies the type of devlinks to be selected.
2N/A * If DI_PRIMARY_LINK is used, only primary links are selected.
2N/A * If DI_SECONDARY_LINK is specified, only secondary links
2N/A * are selected.
2N/A * If neither flag is specified, all devlinks are selected.
2N/A *
2N/A * re An extended regular expression in regex(5) format which
2N/A * selects the /dev links to be returned. The regular
2N/A * expression should use link pathnames relative to
2N/A * /dev. i.e. without the leading "/dev/" prefix.
2N/A * A NULL value matches all devlinks.
2N/A */
2N/Aint
2N/Adi_devlink_cache_walk(di_devlink_handle_t hdp,
2N/A const char *re,
2N/A const char *path,
2N/A uint_t flags,
2N/A void *arg,
2N/A int (*devlink_callback)(di_devlink_t, void *))
2N/A{
2N/A regex_t reg;
2N/A link_desc_t linkd = {NULL};
2N/A
2N/A if (hdp == NULL || path == NULL || !link_flag(flags) ||
2N/A !HDL_RDWR(hdp) || devlink_callback == NULL) {
2N/A errno = EINVAL;
2N/A return (-1);
2N/A }
2N/A
2N/A linkd.flags = flags;
2N/A linkd.arg = arg;
2N/A linkd.fcn = devlink_callback;
2N/A
2N/A if (re) {
2N/A if (regcomp(&reg, re, REG_EXTENDED) != 0)
2N/A return (-1);
2N/A linkd.regp = &reg;
2N/A }
2N/A
2N/A if (minor_colon(path) == NULL) {
2N/A walk_cache_node(hdp, path, &linkd);
2N/A } else {
2N/A walk_cache_minor(hdp, path, &linkd);
2N/A }
2N/A
2N/A if (re)
2N/A regfree(&reg);
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A#define DEBUG_ENV_VAR "_DEVLINK_DEBUG"
2N/Astatic int _devlink_debug = -1;
2N/A
2N/A/*
2N/A * debug level is initialized to -1.
2N/A * On first call into this routine, debug level is set.
2N/A * If debug level is zero, debugging msgs are disabled.
2N/A */
2N/Astatic void
2N/Adebug_print(debug_level_t msglevel, const char *fmt, va_list ap)
2N/A{
2N/A char *cp;
2N/A int save;
2N/A
2N/A /*
2N/A * We shouldn't be here if debug is disabled
2N/A */
2N/A assert(_devlink_debug != 0);
2N/A
2N/A /*
2N/A * Set debug level on first call into this routine
2N/A */
2N/A if (_devlink_debug < 0) {
2N/A if ((cp = getenv(DEBUG_ENV_VAR)) == NULL) {
2N/A _devlink_debug = 0;
2N/A return;
2N/A }
2N/A
2N/A save = errno;
2N/A errno = 0;
2N/A _devlink_debug = strtol(cp, NULL, 10);
2N/A if (errno != 0 || _devlink_debug < 0) {
2N/A _devlink_debug = 0;
2N/A errno = save;
2N/A return;
2N/A }
2N/A errno = save;
2N/A
2N/A if (!_devlink_debug)
2N/A return;
2N/A }
2N/A
2N/A /* debug msgs are enabled */
2N/A assert(_devlink_debug > 0);
2N/A
2N/A if (_devlink_debug < msglevel)
2N/A return;
2N/A if ((_devlink_debug == DBG_LCK) && (msglevel != _devlink_debug))
2N/A return;
2N/A
2N/A /* Print a distinctive label for error msgs */
2N/A if (msglevel == DBG_ERR) {
2N/A (void) fprintf(stderr, "[ERROR]: ");
2N/A }
2N/A
2N/A (void) vfprintf(stderr, fmt, ap);
2N/A (void) fflush(stderr);
2N/A}
2N/A
2N/A/* ARGSUSED */
2N/A/* PRINTFLIKE2 */
2N/Avoid
2N/Adprintf(debug_level_t msglevel, const char *fmt, ...)
2N/A{
2N/A va_list ap;
2N/A
2N/A assert(msglevel > 0);
2N/A if (!_devlink_debug)
2N/A return;
2N/A
2N/A va_start(ap, fmt);
2N/A debug_print(msglevel, fmt, ap);
2N/A va_end(ap);
2N/A}