#include "libdevinfo.h"
#include "devinfo_devlink.h"
#include "device_info.h"
#undef DEBUG
#ifndef DEBUG
#define NDEBUG 1
#else
#undef NDEBUG
#endif
#include <assert.h>
static mutex_t update_mutex = DEFAULTMUTEX;
static mutex_t temp_file_mutex = DEFAULTMUTEX;
static const size_t elem_sizes[DB_TYPES] = {
sizeof (struct db_node),
sizeof (struct db_minor),
sizeof (struct db_link),
sizeof (char)
};
static const char *skip_dirs[] = {"fd"};
static const char *skip_files[] = {
"stdout",
"stdin",
"stderr"
};
#define N_SKIP_DIRS (sizeof (skip_dirs) / sizeof (skip_dirs[0]))
#define N_SKIP_FILES (sizeof (skip_files) / sizeof (skip_files[0]))
#define DI_TEST_DB ETCDEV "di_test_db"
di_devlink_handle_t
di_devlink_open(const char *root_dir, uint_t flags)
{
int err;
char path[PATH_MAX];
struct di_devlink_handle *hdp;
int retried = 0;
retry:
if ((hdp = handle_alloc(root_dir, OPEN_RDWR)) == NULL) {
return (NULL);
}
err = open_db(hdp, OPEN_RDONLY);
get_db_path(hdp, DB_FILE, path, sizeof (path));
if (flags != 0) {
handle_free(&hdp);
errno = EINVAL;
return (NULL);
}
if (cache_alloc(hdp) != 0) {
handle_free(&hdp);
return (NULL);
}
if (err) {
if (retried == 0) {
(void) di_devlink_close(&hdp, 0);
retried = 1;
goto retry;
}
return (hdp);
}
CACHE(hdp)->update_count = DB_HDR(hdp)->update_count;
(void) read_nodes(hdp, NULL, DB_HDR(hdp)->root_idx);
(void) read_links(hdp, NULL, DB_HDR(hdp)->dngl_idx);
(void) close_db(hdp);
return (hdp);
}
static void
get_db_path(
struct di_devlink_handle *hdp,
const char *fname,
char *buf,
size_t blen)
{
char *dir = NULL;
#ifdef DEBUG
if (dir = getenv(ALT_DB_DIR)) {
(void) devlink_dprintf(DBG_INFO,
"get_db_path: alternate db dir: %s\n", dir);
}
#endif
if (dir == NULL) {
dir = hdp->db_dir;
}
(void) snprintf(buf, blen, "%s/%s", dir, fname);
}
static int
open_db(struct di_devlink_handle *hdp, int flags)
{
size_t sz;
long page_sz;
int fd, rv, flg;
struct stat sbuf;
uint32_t count[DB_TYPES] = {0};
char path[PATH_MAX];
void *cp;
assert(!DB_OPEN(hdp));
#ifdef DEBUG
if (getenv(SKIP_DB)) {
(void) devlink_dprintf(DBG_INFO,
"open_db: skipping database\n");
return (-1);
}
#endif
if ((page_sz = sysconf(_SC_PAGE_SIZE)) == -1) {
return (-1);
}
if (IS_RDONLY(flags)) {
flg = O_RDONLY;
get_db_path(hdp, DB_FILE, path, sizeof (path));
} else {
flg = O_RDWR|O_CREAT|O_TRUNC;
get_db_path(hdp, DB_TMP, path, sizeof (path));
}
if (IS_RDONLY(flags) &&
(strncmp(path, "/dev/", 5) == 0) && !device_exists(path)) {
return (-1);
}
if ((fd = open(path, flg, DB_PERMS)) == -1) {
return (-1);
}
if (IS_RDONLY(flags)) {
flg = PROT_READ;
rv = fstat(fd, &sbuf);
sz = sbuf.st_size;
} else {
flg = PROT_READ | PROT_WRITE;
sz = size_db(hdp, page_sz, count);
rv = ftruncate(fd, sz);
}
if (rv == -1 || sz < HDR_LEN) {
if (rv != -1)
errno = EINVAL;
(void) close(fd);
return (-1);
}
cp = mmap(0, HDR_LEN, flg, MAP_SHARED, fd, 0);
if (cp == MAP_FAILED) {
(void) close(fd);
return (-1);
}
DB(hdp)->hdr = (struct db_hdr *)cp;
DB(hdp)->db_fd = fd;
DB(hdp)->flags = flags;
if (IS_RDONLY(flags)) {
rv = invalid_db(hdp, sz, page_sz);
} else {
rv = init_hdr(hdp, page_sz, count);
}
if (rv) {
(void) devlink_dprintf(DBG_ERR, "open_db: invalid DB(%s)\n",
path);
(void) close_db(hdp);
return (-1);
} else {
(void) devlink_dprintf(DBG_STEP, "open_db: DB(%s): opened\n",
path);
return (0);
}
}
static struct di_devlink_handle *
handle_alloc(const char *root_dir, uint_t flags)
{
char dev_dir[PATH_MAX], path[PATH_MAX], db_dir[PATH_MAX];
struct di_devlink_handle *hdp, proto = {0};
int install = 0;
int isroot = 0;
struct stat sb;
char can_path[PATH_MAX];
assert(flags == OPEN_RDWR || flags == OPEN_RDONLY);
dev_dir[0] = '\0';
db_dir[0] = '\0';
if (root_dir && root_dir[0] != '\0') {
if (root_dir[0] != '/') {
errno = EINVAL;
return (NULL);
}
#ifdef DEBUG
assert(sizeof (dev_dir) >= PATH_MAX);
#endif
if ((realpath(root_dir, dev_dir) == NULL) ||
(realpath(root_dir, db_dir) == NULL)) {
return (NULL);
}
} else {
isroot = 1;
}
if (strcmp(dev_dir, "/") == 0) {
dev_dir[0] = 0;
db_dir[0] = 0;
} else {
(void) strlcpy(db_dir, dev_dir, sizeof (db_dir));
}
(void) strlcat(dev_dir, DEV, sizeof (dev_dir));
(void) strlcat(db_dir, ETCDEV, sizeof (db_dir));
install = 0;
(void) snprintf(can_path, sizeof (can_path), "%s/%s", ETCDEV, DB_FILE);
if (flags == OPEN_RDWR && isroot) {
char di_test_db[PATH_MAX];
int fd;
(void) mutex_lock(&temp_file_mutex);
(void) snprintf(di_test_db, sizeof (di_test_db), "%s.%d",
DI_TEST_DB, getpid());
fd = open(di_test_db, O_CREAT|O_RDWR|O_EXCL, 0644);
if (fd == -1 && errno == EROFS && stat(can_path, &sb) == -1)
install = 1;
if (fd != -1) {
(void) close(fd);
(void) unlink(di_test_db);
}
(void) mutex_unlock(&temp_file_mutex);
} else if (isroot) {
char install_path[PATH_MAX];
(void) snprintf(install_path, sizeof (install_path),
"/tmp%s/%s", ETCDEV, DB_FILE);
if (stat(can_path, &sb) == -1 && stat(install_path, &sb)
!= -1 && sb.st_uid == 0) {
install = 1;
}
}
if (install)
(void) snprintf(db_dir, sizeof (db_dir), "/tmp%s", ETCDEV);
proto.dev_dir = dev_dir;
proto.db_dir = db_dir;
proto.flags = flags;
proto.lock_fd = -1;
if (HDL_RDWR(&proto)) {
if (enter_db_lock(&proto, root_dir) != 1) {
return (NULL);
}
}
DB(&proto)->db_fd = -1;
hdp = calloc(1, sizeof (struct di_devlink_handle));
if (hdp == NULL) {
goto error;
}
*hdp = proto;
if ((hdp->dev_dir = strdup(proto.dev_dir)) == NULL) {
free(hdp);
goto error;
}
if ((hdp->db_dir = strdup(proto.db_dir)) == NULL) {
free(hdp->dev_dir);
free(hdp);
goto error;
}
return (hdp);
error:
if (HDL_RDWR(&proto)) {
get_db_path(&proto, DB_FILE, path, sizeof (path));
(void) unlink(path);
exit_db_lock(&proto);
}
return (NULL);
}
static int
cache_alloc(struct di_devlink_handle *hdp)
{
size_t hash_sz = 0;
assert(HDL_RDWR(hdp));
if (DB_OPEN(hdp)) {
hash_sz = DB_NUM(hdp, DB_LINK) / AVG_CHAIN_SIZE;
}
hash_sz = (hash_sz >= MIN_HASH_SIZE) ? hash_sz : MIN_HASH_SIZE;
CACHE(hdp)->hash = calloc(hash_sz, sizeof (cache_link_t *));
if (CACHE(hdp)->hash == NULL) {
return (-1);
}
CACHE(hdp)->hash_sz = hash_sz;
return (0);
}
static int
invalid_db(struct di_devlink_handle *hdp, size_t fsize, long page_sz)
{
int i;
char *cp;
size_t sz;
if (DB_HDR(hdp)->magic != DB_MAGIC || DB_HDR(hdp)->vers != DB_VERSION) {
return (1);
}
if (DB_HDR(hdp)->page_sz == 0 || DB_HDR(hdp)->page_sz != page_sz) {
return (1);
}
sz = seg_size(hdp, DB_HEADER);
for (i = 0; i < DB_TYPES; i++) {
(void) devlink_dprintf(DBG_INFO, "N[%u] = %u\n", i,
DB_NUM(hdp, i));
if (DB_NUM(hdp, i) < 1) {
return (1);
}
sz += seg_size(hdp, i);
assert(sz % page_sz == 0);
}
if (sz != fsize) {
return (1);
}
if (!VALID_INDEX(hdp, DB_NODE, DB_HDR(hdp)->root_idx)) {
return (1);
}
if (!VALID_INDEX(hdp, DB_LINK, DB_HDR(hdp)->dngl_idx)) {
return (1);
}
if (DB_EMPTY(hdp)) {
return (1);
}
cp = get_string(hdp, DB_NUM(hdp, DB_STR) - 1);
if (cp == NULL || *cp != '\0') {
return (1);
}
return (0);
}
static int
read_nodes(struct di_devlink_handle *hdp, cache_node_t *pcnp, uint32_t nidx)
{
char *path;
cache_node_t *cnp;
struct db_node *dnp;
const char *fcn = "read_nodes";
assert(HDL_RDWR(hdp));
if ((pcnp == NULL) ^ (nidx == DB_HDR(hdp)->root_idx)) {
(void) devlink_dprintf(DBG_ERR,
"%s: invalid parent or index(%u)\n", fcn, nidx);
SET_DB_ERR(hdp);
return (-1);
}
for (; dnp = get_node(hdp, nidx); nidx = dnp->sib) {
path = get_string(hdp, dnp->path);
cnp = node_insert(hdp, pcnp, path, INSERT_HEAD);
if (cnp == NULL) {
SET_DB_ERR(hdp);
break;
}
assert(strcmp(path, "/") ^ (nidx == DB_HDR(hdp)->root_idx));
assert(strcmp(path, "/") != 0 || dnp->sib == DB_NIL);
if (read_minors(hdp, cnp, dnp->minor) != 0 ||
read_nodes(hdp, cnp, dnp->child) != 0) {
break;
}
(void) devlink_dprintf(DBG_STEP, "%s: node[%u]: %s\n",
fcn, nidx, cnp->path);
}
return (dnp ? -1 : 0);
}
static int
read_minors(struct di_devlink_handle *hdp, cache_node_t *pcnp, uint32_t nidx)
{
cache_minor_t *cmnp;
struct db_minor *dmp;
char *name, *nodetype;
const char *fcn = "read_minors";
assert(HDL_RDWR(hdp));
if (pcnp == NULL) {
(void) devlink_dprintf(DBG_ERR, "%s: minor[%u]: orphan minor\n",
fcn, nidx);
SET_DB_ERR(hdp);
return (-1);
}
for (; dmp = get_minor(hdp, nidx); nidx = dmp->sib) {
name = get_string(hdp, dmp->name);
nodetype = get_string(hdp, dmp->nodetype);
cmnp = minor_insert(hdp, pcnp, name, nodetype, NULL);
if (cmnp == NULL) {
SET_DB_ERR(hdp);
break;
}
(void) devlink_dprintf(DBG_STEP, "%s: minor[%u]: %s\n",
fcn, nidx, cmnp->name);
if (read_links(hdp, cmnp, dmp->link) != 0) {
break;
}
}
return (dmp ? -1 : 0);
}
static int
read_links(struct di_devlink_handle *hdp, cache_minor_t *pcmp, uint32_t nidx)
{
cache_link_t *clp;
struct db_link *dlp;
char *path, *content;
assert(HDL_RDWR(hdp));
if (nidx != DB_NIL &&
((pcmp == NULL) ^ (nidx == DB_HDR(hdp)->dngl_idx))) {
(void) devlink_dprintf(DBG_ERR, "read_links: invalid minor or"
" index(%u)\n", nidx);
SET_DB_ERR(hdp);
return (-1);
}
for (; dlp = get_link(hdp, nidx); nidx = dlp->sib) {
path = get_string(hdp, dlp->path);
content = get_string(hdp, dlp->content);
clp = link_insert(hdp, pcmp, path, content, dlp->attr);
if (clp == NULL) {
SET_DB_ERR(hdp);
break;
}
(void) devlink_dprintf(DBG_STEP, "read_links: link[%u]: %s%s\n",
nidx, clp->path, pcmp == NULL ? "(DANGLING)" : "");
}
return (dlp ? -1 : 0);
}
int
di_devlink_close(di_devlink_handle_t *pp, int flag)
{
int i, rv;
char tmp[PATH_MAX];
char file[PATH_MAX];
uint32_t next[DB_TYPES] = {0};
struct di_devlink_handle *hdp;
if (pp == NULL || *pp == NULL || !HDL_RDWR(*pp)) {
errno = EINVAL;
return (-1);
}
hdp = *pp;
*pp = NULL;
if (flag == DI_LINK_ERROR) {
handle_free(&hdp);
return (0);
}
if (DB_ERR(hdp)) {
handle_free(&hdp);
errno = EINVAL;
return (-1);
}
get_db_path(hdp, DB_FILE, file, sizeof (file));
get_db_path(hdp, DB_TMP, tmp, sizeof (tmp));
(void) devlink_dprintf(DBG_INFO,
"di_devlink_close: update_count = %u\n", CACHE(hdp)->update_count);
if (CACHE(hdp)->update_count == 0) {
CACHE(hdp)->update_count = 1;
(void) devlink_dprintf(DBG_INFO,
"di_devlink_close: synchronizing DB\n");
(void) synchronize_db(hdp);
}
resolve_dangling_links(hdp);
if (CACHE_EMPTY(hdp)) {
(void) devlink_dprintf(DBG_INFO,
"di_devlink_close: skipping write\n");
(void) unlink(file);
handle_free(&hdp);
return (0);
}
if (open_db(hdp, OPEN_RDWR) != 0) {
handle_free(&hdp);
return (-1);
}
for (i = 0; i < DB_TYPES; i++) {
next[i] = 1;
}
(void) write_nodes(hdp, NULL, CACHE_ROOT(hdp), next);
(void) write_links(hdp, NULL, CACHE(hdp)->dngl, next);
DB_HDR(hdp)->update_count = CACHE(hdp)->update_count;
rv = close_db(hdp);
if (rv != 0 || DB_ERR(hdp) || rename(tmp, file) != 0) {
(void) devlink_dprintf(DBG_ERR,
"di_devlink_close: %s error: %s\n",
rv ? "close_db" : "DB or rename", strerror(errno));
(void) unlink(tmp);
(void) unlink(file);
handle_free(&hdp);
return (-1);
}
handle_free(&hdp);
(void) devlink_dprintf(DBG_INFO, "di_devlink_close: wrote DB(%s)\n",
file);
return (0);
}
static int
init_hdr(struct di_devlink_handle *hdp, long page_sz, uint32_t *count)
{
int i;
DB_HDR(hdp)->magic = DB_MAGIC;
DB_HDR(hdp)->vers = DB_VERSION;
DB_HDR(hdp)->root_idx = DB_NIL;
DB_HDR(hdp)->dngl_idx = DB_NIL;
DB_HDR(hdp)->page_sz = (uint32_t)page_sz;
for (i = 0; i < DB_TYPES; i++) {
assert(count[i] >= 1);
DB_NUM(hdp, i) = count[i];
}
return (0);
}
static int
write_nodes(
struct di_devlink_handle *hdp,
struct db_node *pdnp,
cache_node_t *cnp,
uint32_t *next)
{
uint32_t idx;
struct db_node *dnp;
const char *fcn = "write_nodes";
assert(HDL_RDWR(hdp));
for (; cnp != NULL; cnp = cnp->sib) {
assert(cnp->path != NULL);
if ((pdnp == NULL) ^ (cnp == CACHE_ROOT(hdp))) {
(void) devlink_dprintf(DBG_ERR,
"%s: invalid parent for: %s\n", fcn, cnp->path);
SET_DB_ERR(hdp);
break;
}
assert((strcmp(cnp->path, "/") != 0) ^
(cnp == CACHE_ROOT(hdp)));
idx = next[DB_NODE];
if ((dnp = set_node(hdp, idx)) == NULL) {
SET_DB_ERR(hdp);
break;
}
dnp->path = write_string(hdp, cnp->path, next);
if (dnp->path == DB_NIL) {
SET_DB_ERR(hdp);
break;
}
next[DB_NODE]++;
if (pdnp == NULL) {
assert(DB_HDR(hdp)->root_idx == DB_NIL);
DB_HDR(hdp)->root_idx = idx;
} else {
dnp->sib = pdnp->child;
pdnp->child = idx;
}
(void) devlink_dprintf(DBG_STEP, "%s: node[%u]: %s\n", fcn, idx,
cnp->path);
if (write_minors(hdp, dnp, cnp->minor, next) != 0 ||
write_nodes(hdp, dnp, cnp->child, next) != 0) {
break;
}
}
return (cnp ? -1 : 0);
}
static int
write_minors(
struct di_devlink_handle *hdp,
struct db_node *pdnp,
cache_minor_t *cmnp,
uint32_t *next)
{
uint32_t idx;
struct db_minor *dmp;
const char *fcn = "write_minors";
assert(HDL_RDWR(hdp));
if (pdnp == NULL) {
(void) devlink_dprintf(DBG_ERR, "%s: no node for minor: %s\n",
fcn, cmnp ? cmnp->name : "<NULL>");
SET_DB_ERR(hdp);
return (-1);
}
for (; cmnp != NULL; cmnp = cmnp->sib) {
assert(cmnp->name != NULL);
idx = next[DB_MINOR];
if ((dmp = set_minor(hdp, idx)) == NULL) {
SET_DB_ERR(hdp);
break;
}
dmp->name = write_string(hdp, cmnp->name, next);
dmp->nodetype = write_string(hdp, cmnp->nodetype, next);
if (dmp->name == DB_NIL || dmp->nodetype == DB_NIL) {
dmp->name = dmp->nodetype = DB_NIL;
SET_DB_ERR(hdp);
break;
}
next[DB_MINOR]++;
dmp->sib = pdnp->minor;
pdnp->minor = idx;
(void) devlink_dprintf(DBG_STEP, "%s: minor[%u]: %s\n",
fcn, idx, cmnp->name);
if (write_links(hdp, dmp, cmnp->link, next) != 0) {
break;
}
}
return (cmnp ? -1 : 0);
}
static int
write_links(
struct di_devlink_handle *hdp,
struct db_minor *pdmp,
cache_link_t *clp,
uint32_t *next)
{
uint32_t idx;
struct db_link *dlp;
const char *fcn = "write_links";
assert(HDL_RDWR(hdp));
if (clp != NULL && ((pdmp == NULL) ^ (clp == CACHE(hdp)->dngl))) {
(void) devlink_dprintf(DBG_ERR, "%s: invalid minor for link\n",
fcn);
SET_DB_ERR(hdp);
return (-1);
}
for (; clp != NULL; clp = clp->sib) {
assert(clp->path != NULL);
if ((pdmp == NULL) ^ (clp->minor == NULL)) {
(void) devlink_dprintf(DBG_ERR,
"%s: invalid minor for link(%s)\n", fcn, clp->path);
SET_DB_ERR(hdp);
break;
}
idx = next[DB_LINK];
if ((dlp = set_link(hdp, idx)) == NULL) {
SET_DB_ERR(hdp);
break;
}
dlp->path = write_string(hdp, clp->path, next);
dlp->content = write_string(hdp, clp->content, next);
if (dlp->path == DB_NIL || dlp->content == DB_NIL) {
dlp->path = dlp->content = DB_NIL;
SET_DB_ERR(hdp);
break;
}
dlp->attr = clp->attr;
next[DB_LINK]++;
if (pdmp != NULL) {
dlp->sib = pdmp->link;
pdmp->link = idx;
} else {
dlp->sib = DB_HDR(hdp)->dngl_idx;
DB_HDR(hdp)->dngl_idx = idx;
}
(void) devlink_dprintf(DBG_STEP, "%s: link[%u]: %s%s\n",
fcn, idx, clp->path, pdmp == NULL ? "(DANGLING)" : "");
}
return (clp ? -1 : 0);
}
static uint32_t
write_string(struct di_devlink_handle *hdp, const char *str, uint32_t *next)
{
char *dstr;
uint32_t idx;
assert(HDL_RDWR(hdp));
if (str == NULL) {
(void) devlink_dprintf(DBG_ERR,
"write_string: NULL argument\n");
return (DB_NIL);
}
idx = next[DB_STR];
if (!VALID_STR(hdp, idx, str)) {
(void) devlink_dprintf(DBG_ERR,
"write_string: invalid index[%u], string(%s)\n", idx, str);
return (DB_NIL);
}
if ((dstr = set_string(hdp, idx)) == NULL) {
return (DB_NIL);
}
(void) strcpy(dstr, str);
next[DB_STR] += strlen(dstr) + 1;
return (idx);
}
static int
close_db(struct di_devlink_handle *hdp)
{
int i, rv = 0;
size_t sz;
if (!DB_OPEN(hdp)) {
#ifdef DEBUG
assert(DB(hdp)->db_fd == -1);
assert(DB(hdp)->flags == 0);
for (i = 0; i < DB_TYPES; i++) {
assert(DB_SEG(hdp, i) == NULL);
assert(DB_SEG_PROT(hdp, i) == 0);
}
#endif
return (0);
}
for (i = 0; i < DB_TYPES; i++) {
if (DB_SEG(hdp, i)) {
sz = seg_size(hdp, i);
if (DB_RDWR(hdp))
rv += msync(DB_SEG(hdp, i), sz, MS_SYNC);
(void) munmap(DB_SEG(hdp, i), sz);
DB_SEG(hdp, i) = NULL;
DB_SEG_PROT(hdp, i) = 0;
}
}
if (DB_RDWR(hdp))
rv += msync((caddr_t)DB_HDR(hdp), HDR_LEN, MS_SYNC);
(void) munmap((caddr_t)DB_HDR(hdp), HDR_LEN);
DB(hdp)->hdr = NULL;
(void) close(DB(hdp)->db_fd);
DB(hdp)->db_fd = -1;
DB(hdp)->flags = 0;
return (rv ? -1 : 0);
}
static void
cache_free(struct di_devlink_handle *hdp)
{
cache_link_t *clp;
subtree_free(hdp, &(CACHE_ROOT(hdp)));
assert(CACHE_LAST(hdp) == NULL);
while (CACHE(hdp)->dngl != NULL) {
clp = CACHE(hdp)->dngl;
CACHE(hdp)->dngl = clp->sib;
assert(clp->minor == NULL);
link_free(&clp);
}
assert((CACHE(hdp)->hash == NULL) ^ (CACHE(hdp)->hash_sz != 0));
free(CACHE(hdp)->hash);
CACHE(hdp)->hash = NULL;
CACHE(hdp)->hash_sz = 0;
}
static void
handle_free(struct di_devlink_handle **pp)
{
struct di_devlink_handle *hdp = *pp;
*pp = NULL;
if (hdp == NULL)
return;
(void) close_db(hdp);
cache_free(hdp);
if (HDL_RDWR(hdp))
exit_db_lock(hdp);
assert(hdp->lock_fd == -1);
free(hdp->dev_dir);
free(hdp->db_dir);
free(hdp);
}
static void
subtree_free(struct di_devlink_handle *hdp, cache_node_t **pp)
{
cache_node_t *np;
cache_link_t *clp;
cache_minor_t *cmnp;
if (pp == NULL || *pp == NULL)
return;
while ((*pp)->child != NULL) {
np = (*pp)->child;
(*pp)->child = np->sib;
subtree_free(hdp, &np);
}
while ((*pp)->minor != NULL) {
cmnp = (*pp)->minor;
(*pp)->minor = cmnp->sib;
while (cmnp->link != NULL) {
clp = cmnp->link;
cmnp->link = clp->sib;
rm_link_from_hash(hdp, clp);
link_free(&clp);
}
minor_free(hdp, &cmnp);
}
node_free(pp);
}
static void
rm_link_from_hash(struct di_devlink_handle *hdp, cache_link_t *clp)
{
int hval;
cache_link_t **pp;
if (clp == NULL)
return;
if (clp->path == NULL)
return;
hval = hashfn(hdp, clp->path);
pp = &(CACHE_HASH(hdp, hval));
for (; *pp != NULL; pp = &(*pp)->hash) {
if (*pp == clp) {
*pp = clp->hash;
clp->hash = NULL;
return;
}
}
devlink_dprintf(DBG_ERR, "rm_link_from_hash: link(%s) not found\n",
clp->path);
}
static cache_link_t *
link_hash(di_devlink_handle_t hdp, const char *link, uint_t flags)
{
int hval;
cache_link_t **pp, *clp;
if (link == NULL)
return (NULL);
hval = hashfn(hdp, link);
pp = &(CACHE_HASH(hdp, hval));
for (; (clp = *pp) != NULL; pp = &clp->hash) {
if (strcmp(clp->path, link) == 0) {
break;
}
}
if (clp == NULL)
return (NULL);
if ((flags & UNLINK_FROM_HASH) == UNLINK_FROM_HASH) {
*pp = clp->hash;
clp->hash = NULL;
}
return (clp);
}
static cache_minor_t *
link2minor(struct di_devlink_handle *hdp, cache_link_t *clp)
{
cache_link_t *plp;
const char *minor_path;
char *cp, buf[PATH_MAX], link[PATH_MAX];
char abspath[PATH_MAX];
struct stat st;
if (TYPE_PRI(attr2type(clp->attr))) {
if (!is_minor_node(clp->content, &minor_path)) {
return (NULL);
}
return (lookup_minor(hdp, minor_path, NULL,
TYPE_CACHE|CREATE_FLAG));
}
buf[0] = '\0';
if (strncmp(clp->content, DEV"/", strlen(DEV"/")) == 0) {
cp = &clp->content[strlen(DEV"/")];
} else if (clp->content[0] != '/') {
if ((cp = strrchr(clp->path, '/')) != NULL) {
char savechar = *(cp + 1);
*(cp + 1) = '\0';
(void) snprintf(buf, sizeof (buf), "%s", clp->path);
*(cp + 1) = savechar;
}
(void) strlcat(buf, clp->content, sizeof (buf));
cp = buf;
} else {
goto follow_link;
}
if ((plp = link_hash(hdp, cp, 0)) != NULL && plp->minor != NULL) {
return (plp->minor);
}
follow_link:
(void) snprintf(link, sizeof (link), "%s/%s", hdp->dev_dir, clp->path);
#ifdef DEBUG
assert(sizeof (buf) >= PATH_MAX);
#endif
if (lstat(link, &st) == -1)
return (NULL);
if (S_ISLNK(st.st_mode)) {
if (s_readlink(link, buf, sizeof (buf)) < 0)
return (NULL);
if (buf[0] != '/') {
char *p;
size_t n = sizeof (abspath);
if (strlcpy(abspath, link, n) >= n)
return (NULL);
p = strrchr(abspath, '/') + 1;
*p = 0;
n = sizeof (abspath) - strlen(p);
if (strlcpy(p, buf, n) >= n)
return (NULL);
} else {
if (strlcpy(abspath, buf, sizeof (abspath)) >=
sizeof (abspath))
return (NULL);
}
if (!device_exists(abspath))
return (NULL);
}
if (s_realpath(link, buf) == NULL || !is_minor_node(buf, &minor_path)) {
return (NULL);
}
return (lookup_minor(hdp, minor_path, NULL, TYPE_CACHE|CREATE_FLAG));
}
static void
resolve_dangling_links(struct di_devlink_handle *hdp)
{
cache_minor_t *cmnp;
cache_link_t *clp, **pp;
for (pp = &(CACHE(hdp)->dngl); *pp != NULL; ) {
clp = *pp;
if ((cmnp = link2minor(hdp, clp)) != NULL) {
*pp = clp->sib;
clp->sib = cmnp->link;
cmnp->link = clp;
assert(clp->minor == NULL);
clp->minor = cmnp;
} else {
devlink_dprintf(DBG_INFO,
"resolve_dangling_links: link(%s): unresolved\n",
clp->path);
pp = &clp->sib;
}
}
}
static void
node_free(cache_node_t **pp)
{
cache_node_t *cnp = *pp;
*pp = NULL;
if (cnp == NULL)
return;
free(cnp->path);
free(cnp);
}
static void
minor_free(struct di_devlink_handle *hdp, cache_minor_t **pp)
{
cache_minor_t *cmnp = *pp;
*pp = NULL;
if (cmnp == NULL)
return;
if (CACHE_LAST(hdp) == cmnp) {
devlink_dprintf(DBG_STEP, "minor_free: last_minor(%s)\n",
cmnp->name);
CACHE_LAST(hdp) = NULL;
}
free(cmnp->name);
free(cmnp->nodetype);
free(cmnp);
}
static void
link_free(cache_link_t **pp)
{
cache_link_t *clp = *pp;
*pp = NULL;
if (clp == NULL)
return;
free(clp->path);
free(clp->content);
free(clp);
}
static char *
minor_colon(const char *path)
{
char *cp;
if ((cp = strrchr(path, '/')) == NULL) {
return (NULL);
}
return (strchr(cp, ':'));
}
static void *
lookup_minor(
struct di_devlink_handle *hdp,
const char *minor_path,
const char *nodetype,
const int flags)
{
void *vp;
char *colon;
char pdup[PATH_MAX];
const char *fcn = "lookup_minor";
if (minor_path == NULL) {
errno = EINVAL;
return (NULL);
}
(void) snprintf(pdup, sizeof (pdup), "%s", minor_path);
if ((colon = minor_colon(pdup)) == NULL) {
(void) devlink_dprintf(DBG_ERR, "%s: invalid minor path(%s)\n",
fcn, minor_path);
errno = EINVAL;
return (NULL);
}
*colon = '\0';
if ((vp = get_last_minor(hdp, pdup, colon + 1, flags)) != NULL) {
return (vp);
}
if ((vp = lookup_node(hdp, pdup, flags)) == NULL) {
(void) devlink_dprintf(DBG_ERR, "%s: node(%s) not found\n",
fcn, pdup);
return (NULL);
}
*colon = ':';
if (LOOKUP_CACHE(flags)) {
cache_minor_t **pp;
pp = &((cache_node_t *)vp)->minor;
for (; *pp != NULL; pp = &(*pp)->sib) {
if (strcmp((*pp)->name, colon + 1) == 0)
break;
}
if (*pp == NULL && CREATE_ELEM(flags)) {
*pp = minor_insert(hdp, vp, colon + 1, nodetype, pp);
}
set_last_minor(hdp, *pp, flags);
return (*pp);
} else {
char *cp;
uint32_t nidx;
struct db_minor *dmp;
nidx = (((struct db_node *)vp)->minor);
for (; dmp = get_minor(hdp, nidx); nidx = dmp->sib) {
cp = get_string(hdp, dmp->name);
if (cp && strcmp(cp, colon + 1) == 0)
break;
}
return (dmp);
}
}
static void *
lookup_node(struct di_devlink_handle *hdp, char *path, const int flags)
{
struct tnode tnd = {NULL};
if (tnd.node = get_last_node(hdp, path, flags))
return (tnd.node);
tnd.handle = hdp;
tnd.flags = flags;
if (walk_tree(path, &tnd, visit_node) != 0)
return (NULL);
return (tnd.node);
}
static void *
get_last_node(struct di_devlink_handle *hdp, const char *path, int flags)
{
cache_node_t *cnp;
#ifdef DEBUG
if (getenv(SKIP_LAST_CACHE)) {
(void) devlink_dprintf(DBG_INFO,
"get_last_node: SKIPPING \"last\" node cache\n");
return (NULL);
}
#endif
if (!LOOKUP_CACHE(flags) || CACHE_LAST(hdp) == NULL ||
CACHE_LAST(hdp)->node == NULL) {
return (NULL);
}
cnp = CACHE_LAST(hdp)->node;
if (strcmp(cnp->path, path) == 0) {
return (cnp);
}
cnp = cnp->sib;
if (cnp && strcmp(cnp->path, path) == 0) {
return (cnp);
}
return (NULL);
}
static void *
get_last_minor(
struct di_devlink_handle *hdp,
const char *devfs_path,
const char *minor_name,
int flags)
{
cache_minor_t *cmnp;
#ifdef DEBUG
if (getenv(SKIP_LAST_CACHE)) {
(void) devlink_dprintf(DBG_INFO,
"get_last_minor: SKIPPING \"last\" minor cache\n");
return (NULL);
}
#endif
if (!LOOKUP_CACHE(flags) || CACHE_LAST(hdp) == NULL) {
return (NULL);
}
cmnp = CACHE_LAST(hdp);
if (strcmp(cmnp->name, minor_name) == 0 && cmnp->node &&
strcmp(cmnp->node->path, devfs_path) == 0) {
return (cmnp);
}
cmnp = cmnp->sib;
if (cmnp && strcmp(cmnp->name, minor_name) == 0 && cmnp->node &&
strcmp(cmnp->node->path, devfs_path) == 0) {
set_last_minor(hdp, cmnp, TYPE_CACHE);
return (cmnp);
}
return (NULL);
}
static void
set_last_minor(struct di_devlink_handle *hdp, cache_minor_t *cmnp, int flags)
{
#ifdef DEBUG
if (getenv(SKIP_LAST_CACHE)) {
(void) devlink_dprintf(DBG_INFO,
"set_last_minor: SKIPPING \"last\" minor cache\n");
return;
}
#endif
if (LOOKUP_CACHE(flags) && cmnp) {
CACHE_LAST(hdp) = cmnp;
}
}
static int
walk_tree(
char *cur,
void *arg,
int (*node_callback)(const char *path, void *arg))
{
char *slash, buf[PATH_MAX];
if (cur == NULL || cur[0] != '/' || strlen(cur) > sizeof (buf) - 1) {
errno = EINVAL;
return (-1);
}
(void) strcpy(buf, "/");
for (;;) {
if (node_callback(buf, arg) != DI_WALK_CONTINUE)
break;
while (*cur == '/')
cur++;
if (*cur == '\0')
break;
if (buf[1] != '\0') {
(void) strlcat(buf, "/", sizeof (buf));
}
if (slash = strchr(cur, '/')) {
*slash = '\0';
(void) strlcat(buf, cur, sizeof (buf));
*slash = '/';
cur = slash;
} else {
(void) strlcat(buf, cur, sizeof (buf));
cur += strlen(cur);
}
}
return (0);
}
static int
visit_node(const char *path, void *arg)
{
struct tnode *tnp = arg;
if (LOOKUP_CACHE(tnp->flags)) {
cache_node_t *cnp = tnp->node;
cnp = (cnp) ? cnp->child : CACHE_ROOT(tnp->handle);
for (; cnp != NULL; cnp = cnp->sib) {
if (strcmp(cnp->path, path) == 0)
break;
}
if (cnp == NULL && CREATE_ELEM(tnp->flags)) {
cnp = node_insert(tnp->handle, tnp->node, path,
INSERT_TAIL);
}
tnp->node = cnp;
} else {
char *cp;
struct db_node *dnp = tnp->node;
dnp = (dnp) ? get_node(tnp->handle, dnp->child)
: get_node(tnp->handle, DB_HDR(tnp->handle)->root_idx);
for (; dnp != NULL; dnp = get_node(tnp->handle, dnp->sib)) {
cp = get_string(tnp->handle, dnp->path);
if (cp && strcmp(cp, path) == 0) {
break;
}
}
tnp->node = dnp;
}
return (tnp->node ? DI_WALK_CONTINUE : DI_WALK_TERMINATE);
}
static void
minor_delete(di_devlink_handle_t hdp, cache_minor_t *cmnp)
{
cache_link_t **lpp;
cache_minor_t **mpp;
const char *fcn = "minor_delete";
(void) devlink_dprintf(DBG_STEP, "%s: removing minor: %s\n",
fcn, cmnp->name);
if (cmnp->node != NULL) {
mpp = &cmnp->node->minor;
for (; *mpp != NULL; mpp = &(*mpp)->sib) {
if (*mpp == cmnp)
break;
}
if (*mpp == NULL) {
(void) devlink_dprintf(DBG_ERR,
"%s: dangling minor: %s\n", fcn, cmnp->name);
} else {
*mpp = cmnp->sib;
}
} else {
(void) devlink_dprintf(DBG_ERR, "%s: orphan minor(%s)\n", fcn,
cmnp->name);
}
delete_unused_nodes(hdp, cmnp->node);
cmnp->node = NULL;
cmnp->sib = NULL;
for (lpp = &cmnp->link; *lpp != NULL; lpp = &(*lpp)->sib) {
(*lpp)->minor = NULL;
}
*lpp = CACHE(hdp)->dngl;
CACHE(hdp)->dngl = cmnp->link;
cmnp->link = NULL;
minor_free(hdp, &cmnp);
}
static void
delete_unused_nodes(di_devlink_handle_t hdp, cache_node_t *cnp)
{
cache_node_t **npp;
const char *fcn = "delete_unused_nodes";
if (cnp == NULL)
return;
if (cnp->minor != NULL || cnp->child != NULL)
return;
(void) devlink_dprintf(DBG_INFO, "%s: removing unused node: %s\n", fcn,
cnp->path);
if (cnp->parent != NULL) {
npp = &cnp->parent->child;
for (; *npp != NULL; npp = &(*npp)->sib) {
if (*npp == cnp)
break;
}
if (*npp == NULL) {
(void) devlink_dprintf(DBG_ERR,
"%s: dangling node: %s\n", fcn, cnp->path);
} else {
*npp = cnp->sib;
}
} else if (cnp == CACHE_ROOT(hdp)) {
CACHE_ROOT(hdp) = NULL;
} else {
(void) devlink_dprintf(DBG_ERR, "%s: orphan node (%s)\n", fcn,
cnp->path);
}
delete_unused_nodes(hdp, cnp->parent);
cnp->parent = cnp->sib = NULL;
node_free(&cnp);
}
static int
rm_link(di_devlink_handle_t hdp, const char *link)
{
cache_link_t *clp;
const char *fcn = "rm_link";
if (hdp == NULL || DB_ERR(hdp) || link == NULL || link[0] == '/' ||
(!HDL_RDWR(hdp) && !HDL_RDONLY(hdp))) {
devlink_dprintf(DBG_ERR, "%s: %s: invalid args\n",
fcn, link ? link : "<NULL>");
errno = EINVAL;
return (-1);
}
devlink_dprintf(DBG_STEP, "%s: link(%s)\n", fcn, link);
if ((clp = link_hash(hdp, link, UNLINK_FROM_HASH)) == NULL) {
return (0);
}
link_delete(hdp, clp);
return (0);
}
int
di_devlink_rm_link(di_devlink_handle_t hdp, const char *link)
{
if (hdp == NULL || !HDL_RDWR(hdp)) {
errno = EINVAL;
return (-1);
}
return (rm_link(hdp, link));
}
static void
link_delete(di_devlink_handle_t hdp, cache_link_t *clp)
{
cache_link_t **pp;
const char *fcn = "link_delete";
(void) devlink_dprintf(DBG_STEP, "%s: removing link: %s\n",
fcn, clp->path);
if (clp->minor == NULL)
pp = &(CACHE(hdp)->dngl);
else
pp = &clp->minor->link;
for (; *pp != NULL; pp = &(*pp)->sib) {
if (*pp == clp)
break;
}
if (*pp == NULL) {
(void) devlink_dprintf(DBG_ERR, "%s: link(%s) not on list\n",
fcn, clp->path);
} else {
*pp = clp->sib;
}
delete_unused_minor(hdp, clp->minor);
clp->minor = NULL;
link_free(&clp);
}
static void
delete_unused_minor(di_devlink_handle_t hdp, cache_minor_t *cmnp)
{
if (cmnp == NULL)
return;
if (cmnp->link != NULL)
return;
devlink_dprintf(DBG_STEP, "delete_unused_minor: removing minor(%s)\n",
cmnp->name);
minor_delete(hdp, cmnp);
}
int
di_devlink_add_link(
di_devlink_handle_t hdp,
const char *link,
const char *content,
int flags)
{
return (add_link(hdp, link, content, flags) != NULL ? 0 : -1);
}
static cache_link_t *
add_link(
struct di_devlink_handle *hdp,
const char *link,
const char *content,
int flags)
{
uint32_t attr;
cache_link_t *clp;
cache_minor_t *cmnp;
const char *fcn = "add_link";
if (hdp == NULL || DB_ERR(hdp) || link == NULL ||
link[0] == '/' || content == NULL || !link_flag(flags) ||
(!HDL_RDWR(hdp) && !HDL_RDONLY(hdp))) {
devlink_dprintf(DBG_ERR, "%s: %s: invalid args\n",
fcn, link ? link : "<NULL>");
errno = EINVAL;
return (NULL);
}
if ((clp = link_hash(hdp, link, 0)) != NULL) {
if (link_cmp(clp, content, LINK_TYPE(flags)) != 0) {
(void) rm_link(hdp, link);
} else {
return (clp);
}
}
if (TYPE_PRI(flags)) {
const char *minor_path = NULL;
if (!is_minor_node(content, &minor_path)) {
(void) devlink_dprintf(DBG_ERR,
"%s: invalid content(%s) for primary link\n",
fcn, content);
errno = EINVAL;
return (NULL);
}
if ((cmnp = lookup_minor(hdp, minor_path, NULL,
TYPE_CACHE|CREATE_FLAG)) == NULL) {
return (NULL);
}
attr = A_PRIMARY;
} else {
cmnp = NULL;
attr = A_SECONDARY;
}
return (link_insert(hdp, cmnp, link, content, attr));
}
static int
link_cmp(cache_link_t *clp, const char *content, int type)
{
if (strcmp(clp->content, content) != 0)
return (1);
if (attr2type(clp->attr) != type)
return (1);
return (0);
}
int
di_devlink_update(di_devlink_handle_t hdp)
{
if (hdp == NULL || !HDL_RDWR(hdp) || DB_ERR(hdp)) {
errno = EINVAL;
return (-1);
}
CACHE(hdp)->update_count = 0;
return (0);
}
static int
synchronize_db(di_devlink_handle_t hdp)
{
int hval;
cache_link_t *clp;
char pdup[PATH_MAX];
recurse_t rec = {NULL};
const char *fcn = "synchronize_db";
rec.data = NULL;
rec.fcn = cache_dev_link;
if (recurse_dev(hdp, &rec) != 0) {
return (-1);
}
for (hval = 0; hval < CACHE(hdp)->hash_sz; hval++) {
for (clp = CACHE_HASH(hdp, hval); clp != NULL; ) {
if (GET_VALID_ATTR(clp->attr)) {
CLR_VALID_ATTR(clp->attr);
clp = clp->hash;
continue;
}
(void) snprintf(pdup, sizeof (pdup), "%s", clp->path);
clp = clp->hash;
(void) devlink_dprintf(DBG_STEP,
"%s: removing invalid link: %s\n", fcn, pdup);
(void) di_devlink_rm_link(hdp, pdup);
}
}
(void) devlink_dprintf(DBG_STEP, "%s: update completed\n", fcn);
return (0);
}
static di_devlink_handle_t
di_devlink_init_impl(const char *root, const char *name, uint_t flags)
{
int err = 0;
if ((flags != 0 && flags != DI_MAKE_LINK) ||
(flags == 0 && name != NULL)) {
errno = EINVAL;
return (NULL);
}
if ((flags == DI_MAKE_LINK) &&
(err = devlink_create(root, name, DCA_DEVLINK_CACHE))) {
errno = err;
return (NULL);
}
(void) devlink_dprintf(DBG_INFO, "devlink_init_impl: success\n");
return (devlink_snapshot(root));
}
di_devlink_handle_t
di_devlink_init(const char *name, uint_t flags)
{
return (di_devlink_init_impl("/", name, flags));
}
di_devlink_handle_t
di_devlink_init_root(const char *root, const char *name, uint_t flags)
{
return (di_devlink_init_impl(root, name, flags));
}
static di_devlink_handle_t
devlink_snapshot(const char *root_dir)
{
struct di_devlink_handle *hdp;
int err;
static int retried = 0;
if ((hdp = handle_alloc(root_dir, OPEN_RDONLY)) == NULL) {
return (NULL);
}
again: err = open_db(hdp, OPEN_RDONLY);
if (err && (retried == 0)) {
retried++;
(void) devlink_create(root_dir, NULL, DCA_DEVLINK_SYNC);
goto again;
}
return (hdp);
}
int
di_devlink_fini(di_devlink_handle_t *pp)
{
if (pp == NULL || *pp == NULL || !HDL_RDONLY(*pp)) {
errno = EINVAL;
return (-1);
}
handle_free(pp);
return (0);
}
int
di_devlink_walk(
di_devlink_handle_t hdp,
const char *re,
const char *minor_path,
uint_t flags,
void *arg,
int (*devlink_callback)(di_devlink_t, void *))
{
int rv;
regex_t reg;
link_desc_t linkd = {NULL};
if (hdp == NULL || !HDL_RDONLY(hdp)) {
errno = EINVAL;
return (-1);
}
linkd.minor_path = minor_path;
linkd.flags = flags;
linkd.arg = arg;
linkd.fcn = devlink_callback;
if (re) {
if (regcomp(®, re, REG_EXTENDED) != 0)
return (-1);
linkd.regp = ®
}
if (check_args(&linkd)) {
errno = EINVAL;
rv = -1;
goto out;
}
if (DB_OPEN(hdp)) {
rv = walk_db(hdp, &linkd);
} else {
rv = walk_dev(hdp, &linkd);
}
out:
if (re) {
regfree(®);
}
return (rv ? -1 : 0);
}
static int
link_flag(uint_t flags)
{
if (flags != 0 && flags != DI_PRIMARY_LINK &&
flags != DI_SECONDARY_LINK) {
return (0);
}
return (1);
}
static int
check_args(link_desc_t *linkp)
{
if (linkp->fcn == NULL)
return (-1);
if (!link_flag(linkp->flags)) {
return (-1);
}
if (linkp->minor_path) {
if (linkp->minor_path[0] != '/' ||
minor_colon(linkp->minor_path) == NULL) {
return (-1);
}
}
return (0);
}
static int
walk_db(struct di_devlink_handle *hdp, link_desc_t *linkp)
{
assert(DB_OPEN(hdp));
if (linkp->minor_path == NULL) {
return (walk_all_links(hdp, linkp));
} else {
return (walk_matching_links(hdp, linkp));
}
}
static int
cache_dev(struct di_devlink_handle *hdp)
{
size_t sz;
recurse_t rec = {NULL};
assert(hdp);
assert(HDL_RDONLY(hdp));
if (hdp == NULL || !HDL_RDONLY(hdp)) {
devlink_dprintf(DBG_ERR, "cache_dev: invalid arg\n");
return (-1);
}
sz = MIN_HASH_SIZE;
CACHE(hdp)->hash = calloc(sz, sizeof (cache_link_t *));
if (CACHE(hdp)->hash == NULL) {
return (-1);
}
CACHE(hdp)->hash_sz = sz;
rec.data = NULL;
rec.fcn = cache_dev_link;
return (recurse_dev(hdp, &rec));
}
static int
walk_dev(struct di_devlink_handle *hdp, link_desc_t *linkp)
{
assert(hdp && linkp);
assert(!DB_OPEN(hdp));
assert(HDL_RDONLY(hdp));
if (hdp == NULL || !HDL_RDONLY(hdp) || DB_OPEN(hdp)) {
devlink_dprintf(DBG_ERR, "walk_dev: invalid args\n");
return (-1);
}
if (CACHE_EMPTY(hdp) && cache_dev(hdp) != 0) {
devlink_dprintf(DBG_ERR, "walk_dev: /dev caching failed\n");
return (-1);
}
if (linkp->minor_path)
walk_cache_minor(hdp, linkp->minor_path, linkp);
else
walk_all_cache(hdp, linkp);
return (linkp->retval);
}
static int
cache_dev_link(struct di_devlink_handle *hdp, void *data, const char *link)
{
int flags;
cache_link_t *clp;
char content[PATH_MAX];
assert(HDL_RDWR(hdp) || HDL_RDONLY(hdp));
if (s_readlink(link, content, sizeof (content)) < 0) {
return (DI_WALK_CONTINUE);
}
if (is_minor_node(content, NULL)) {
flags = DI_PRIMARY_LINK;
} else {
flags = DI_SECONDARY_LINK;
}
assert(strncmp(link, hdp->dev_dir, strlen(hdp->dev_dir)) == 0);
link += strlen(hdp->dev_dir) + 1;
if ((clp = add_link(hdp, link, content, flags)) != NULL) {
SET_VALID_ATTR(clp->attr);
}
return (DI_WALK_CONTINUE);
}
static int
walk_all_links(struct di_devlink_handle *hdp, link_desc_t *linkp)
{
struct db_link *dlp;
uint32_t nidx, eidx;
assert(DB_NUM(hdp, DB_LINK) >= 1);
eidx = DB_NUM(hdp, DB_LINK);
for (nidx = 1; nidx < eidx; nidx++) {
struct di_devlink vlink = {NULL};
if ((dlp = get_link(hdp, nidx)) == NULL)
continue;
vlink.rel_path = get_string(hdp, dlp->path);
vlink.content = get_string(hdp, dlp->content);
vlink.type = attr2type(dlp->attr);
if (visit_link(hdp, linkp, &vlink) != DI_WALK_CONTINUE) {
break;
}
}
return (linkp->retval);
}
static int
walk_matching_links(struct di_devlink_handle *hdp, link_desc_t *linkp)
{
uint32_t nidx;
struct db_link *dlp;
struct db_minor *dmp;
assert(linkp->minor_path != NULL);
dmp = lookup_minor(hdp, linkp->minor_path, NULL, TYPE_DB);
for (;;) {
nidx = dmp ? dmp->link : DB_HDR(hdp)->dngl_idx;
for (; dlp = get_link(hdp, nidx); nidx = dlp->sib) {
struct di_devlink vlink = {NULL};
vlink.rel_path = get_string(hdp, dlp->path);
vlink.content = get_string(hdp, dlp->content);
vlink.type = attr2type(dlp->attr);
if (visit_link(hdp, linkp, &vlink) != DI_WALK_CONTINUE)
goto out;
}
if (dmp == NULL) {
break;
} else {
dmp = NULL;
}
}
out:
return (linkp->retval);
}
static int
visit_link(
struct di_devlink_handle *hdp,
link_desc_t *linkp,
struct di_devlink *vlp)
{
struct stat sbuf;
const char *minor_path = NULL;
char abs_path[PATH_MAX], cont[PATH_MAX];
if (vlp->rel_path == NULL && vlp->abs_path == NULL) {
(void) devlink_dprintf(DBG_ERR,
"visit_link: invalid arguments\n");
return (DI_WALK_CONTINUE);
}
if (vlp->rel_path == NULL) {
vlp->rel_path = (char *)rel_path(hdp, vlp->abs_path);
if (vlp->rel_path == NULL || vlp->rel_path[0] == '\0')
return (DI_WALK_CONTINUE);
}
if (linkp->regp) {
if (regexec(linkp->regp, vlp->rel_path, 0, NULL, 0) != 0)
return (DI_WALK_CONTINUE);
}
if (vlp->abs_path == NULL) {
assert(vlp->rel_path[0] != '/');
(void) snprintf(abs_path, sizeof (abs_path), "%s/%s",
hdp->dev_dir, vlp->rel_path);
vlp->abs_path = abs_path;
}
if (vlp->content == NULL) {
if (s_readlink(vlp->abs_path, cont, sizeof (cont)) < 0) {
return (DI_WALK_CONTINUE);
}
vlp->content = cont;
}
if (vlp->type == 0) {
if (is_minor_node(vlp->content, &minor_path)) {
vlp->type = DI_PRIMARY_LINK;
} else {
vlp->type = DI_SECONDARY_LINK;
}
}
if (linkp->minor_path) {
char tmp[PATH_MAX];
if (vlp->type == DI_SECONDARY_LINK) {
#ifdef DEBUG
assert(sizeof (tmp) >= PATH_MAX);
#endif
if (s_realpath(vlp->abs_path, tmp) == NULL)
return (DI_WALK_CONTINUE);
if (!is_minor_node(tmp, &minor_path))
return (DI_WALK_CONTINUE);
} else if (minor_path == NULL) {
if (!is_minor_node(vlp->content, &minor_path))
return (DI_WALK_CONTINUE);
}
assert(minor_path != NULL);
if (strcmp(linkp->minor_path, minor_path) != 0)
return (DI_WALK_CONTINUE);
}
if (!TYPE_NONE(linkp->flags) && LINK_TYPE(linkp->flags) != vlp->type) {
return (DI_WALK_CONTINUE);
}
if (lstat(vlp->abs_path, &sbuf) < 0) {
devlink_dprintf(DBG_ERR, "visit_link: %s: lstat failed: %s\n",
vlp->abs_path, strerror(errno));
return (DI_WALK_CONTINUE);
}
return (linkp->fcn(vlp, linkp->arg));
}
static int
devlink_valid(di_devlink_t devlink)
{
if (devlink == NULL || devlink->rel_path == NULL ||
devlink->abs_path == NULL || devlink->content == NULL ||
TYPE_NONE(devlink->type)) {
return (0);
}
return (1);
}
const char *
di_devlink_path(di_devlink_t devlink)
{
if (!devlink_valid(devlink)) {
errno = EINVAL;
return (NULL);
}
return (devlink->abs_path);
}
const char *
di_devlink_content(di_devlink_t devlink)
{
if (!devlink_valid(devlink)) {
errno = EINVAL;
return (NULL);
}
return (devlink->content);
}
int
di_devlink_type(di_devlink_t devlink)
{
if (!devlink_valid(devlink)) {
errno = EINVAL;
return (-1);
}
return (devlink->type);
}
di_devlink_t
di_devlink_dup(di_devlink_t devlink)
{
struct di_devlink *duplink;
if (!devlink_valid(devlink)) {
errno = EINVAL;
return (NULL);
}
if ((duplink = calloc(1, sizeof (struct di_devlink))) == NULL) {
return (NULL);
}
duplink->rel_path = strdup(devlink->rel_path);
duplink->abs_path = strdup(devlink->abs_path);
duplink->content = strdup(devlink->content);
duplink->type = devlink->type;
if (!devlink_valid(duplink)) {
(void) di_devlink_free(duplink);
errno = ENOMEM;
return (NULL);
}
return (duplink);
}
int
di_devlink_free(di_devlink_t devlink)
{
if (devlink == NULL) {
errno = EINVAL;
return (-1);
}
free(devlink->rel_path);
free(devlink->abs_path);
free(devlink->content);
free(devlink);
return (0);
}
static const char *
rel_path(struct di_devlink_handle *hdp, const char *path)
{
const size_t len = strlen(hdp->dev_dir);
if (strncmp(path, hdp->dev_dir, len) != 0)
return (NULL);
if (path[len] == '\0')
return (&path[len]);
if (path[len] != '/')
return (NULL);
return (&path[len+1]);
}
static int
recurse_dev(struct di_devlink_handle *hdp, recurse_t *rp)
{
int ret = 0;
(void) do_recurse(hdp->dev_dir, hdp, rp, &ret);
return (ret);
}
static int
do_recurse(
const char *dir,
struct di_devlink_handle *hdp,
recurse_t *rp,
int *retp)
{
size_t len;
const char *rel;
struct stat sbuf;
char cur[PATH_MAX], *cp;
int i, rv = DI_WALK_CONTINUE;
finddevhdl_t handle;
char *d_name;
if ((rel = rel_path(hdp, dir)) == NULL)
return (DI_WALK_CONTINUE);
for (i = 0; i < N_SKIP_DIRS; i++) {
if (strcmp(rel, skip_dirs[i]) == 0) {
(void) devlink_dprintf(DBG_STEP,
"do_recurse: skipping %s\n", dir);
return (DI_WALK_CONTINUE);
}
}
(void) devlink_dprintf(DBG_STEP, "do_recurse: dir = %s\n", dir);
if (finddev_readdir(dir, &handle) != 0)
return (DI_WALK_CONTINUE);
(void) snprintf(cur, sizeof (cur), "%s/", dir);
len = strlen(cur);
cp = cur + len;
len = sizeof (cur) - len;
for (;;) {
if ((d_name = (char *)finddev_next(handle)) == NULL)
break;
if (strlcpy(cp, d_name, len) >= len)
break;
for (i = 0; i < N_SKIP_FILES; i++) {
rel = rel_path(hdp, cur);
if (rel == NULL || strcmp(rel, skip_files[i]) == 0) {
(void) devlink_dprintf(DBG_STEP,
"do_recurse: skipping %s\n", cur);
goto next_entry;
}
}
if (lstat(cur, &sbuf) == 0) {
if (S_ISDIR(sbuf.st_mode)) {
rv = do_recurse(cur, hdp, rp, retp);
} else if (S_ISLNK(sbuf.st_mode)) {
rv = rp->fcn(hdp, rp->data, cur);
} else {
(void) devlink_dprintf(DBG_STEP,
"do_recurse: Skipping entry: %s\n", cur);
}
} else {
(void) devlink_dprintf(DBG_ERR,
"do_recurse: cur(%s): lstat failed: %s\n",
cur, strerror(errno));
}
next_entry:
*cp = '\0';
if (rv != DI_WALK_CONTINUE)
break;
}
finddev_close(handle);
return (rv);
}
static int
check_attr(uint32_t attr)
{
switch (attr & A_LINK_TYPES) {
case A_PRIMARY:
case A_SECONDARY:
return (1);
default:
devlink_dprintf(DBG_ERR,
"check_attr: incorrect attr(%u)\n", attr);
return (0);
}
}
static int
attr2type(uint32_t attr)
{
switch (attr & A_LINK_TYPES) {
case A_PRIMARY:
return (DI_PRIMARY_LINK);
case A_SECONDARY:
return (DI_SECONDARY_LINK);
default:
devlink_dprintf(DBG_ERR,
"attr2type: incorrect attr(%u)\n", attr);
return (0);
}
}
static cache_node_t *
node_insert(
struct di_devlink_handle *hdp,
cache_node_t *pcnp,
const char *path,
int insert)
{
cache_node_t *cnp;
if (path == NULL) {
errno = EINVAL;
SET_DB_ERR(hdp);
return (NULL);
}
if ((cnp = calloc(1, sizeof (cache_node_t))) == NULL) {
SET_DB_ERR(hdp);
return (NULL);
}
if ((cnp->path = strdup(path)) == NULL) {
SET_DB_ERR(hdp);
free(cnp);
return (NULL);
}
cnp->parent = pcnp;
if (pcnp == NULL) {
assert(strcmp(path, "/") == 0);
assert(CACHE(hdp)->root == NULL);
CACHE(hdp)->root = cnp;
} else if (insert == INSERT_HEAD) {
cnp->sib = pcnp->child;
pcnp->child = cnp;
} else if (CACHE_LAST(hdp) && CACHE_LAST(hdp)->node &&
CACHE_LAST(hdp)->node->parent == pcnp &&
CACHE_LAST(hdp)->node->sib == NULL) {
CACHE_LAST(hdp)->node->sib = cnp;
} else {
cache_node_t **pp;
for (pp = &pcnp->child; *pp != NULL; pp = &(*pp)->sib)
;
*pp = cnp;
}
return (cnp);
}
static cache_minor_t *
minor_insert(
struct di_devlink_handle *hdp,
cache_node_t *pcnp,
const char *name,
const char *nodetype,
cache_minor_t **prev)
{
cache_minor_t *cmnp;
if (pcnp == NULL || name == NULL) {
errno = EINVAL;
SET_DB_ERR(hdp);
return (NULL);
}
if (nodetype == NULL)
nodetype = DDI_PSEUDO;
if ((cmnp = calloc(1, sizeof (cache_minor_t))) == NULL) {
SET_DB_ERR(hdp);
return (NULL);
}
cmnp->name = strdup(name);
cmnp->nodetype = strdup(nodetype);
if (cmnp->name == NULL || cmnp->nodetype == NULL) {
SET_DB_ERR(hdp);
free(cmnp->name);
free(cmnp->nodetype);
free(cmnp);
return (NULL);
}
cmnp->node = pcnp;
if (prev == NULL) {
cmnp->sib = pcnp->minor;
pcnp->minor = cmnp;
} else {
assert(*prev == NULL);
*prev = cmnp;
}
return (cmnp);
}
static cache_link_t *
link_insert(
struct di_devlink_handle *hdp,
cache_minor_t *cmnp,
const char *path,
const char *content,
uint32_t attr)
{
cache_link_t *clp;
if (path == NULL || content == NULL || !check_attr(attr)) {
errno = EINVAL;
SET_DB_ERR(hdp);
return (NULL);
}
if ((clp = calloc(1, sizeof (cache_link_t))) == NULL) {
SET_DB_ERR(hdp);
return (NULL);
}
clp->path = strdup(path);
clp->content = strdup(content);
if (clp->path == NULL || clp->content == NULL) {
SET_DB_ERR(hdp);
link_free(&clp);
return (NULL);
}
clp->attr = attr;
hash_insert(hdp, clp);
clp->minor = cmnp;
if (cmnp != NULL) {
clp->sib = cmnp->link;
cmnp->link = clp;
} else {
clp->sib = CACHE(hdp)->dngl;
CACHE(hdp)->dngl = clp;
}
return (clp);
}
static void
hash_insert(struct di_devlink_handle *hdp, cache_link_t *clp)
{
uint_t hval;
hval = hashfn(hdp, clp->path);
clp->hash = CACHE_HASH(hdp, hval);
CACHE_HASH(hdp, hval) = clp;
}
static struct db_node *
get_node(struct di_devlink_handle *hdp, uint32_t idx)
{
return (map_seg(hdp, idx, PROT_READ, DB_NODE));
}
static struct db_node *
set_node(struct di_devlink_handle *hdp, uint32_t idx)
{
return (map_seg(hdp, idx, PROT_READ | PROT_WRITE, DB_NODE));
}
static struct db_minor *
get_minor(struct di_devlink_handle *hdp, uint32_t idx)
{
return (map_seg(hdp, idx, PROT_READ, DB_MINOR));
}
static struct db_minor *
set_minor(struct di_devlink_handle *hdp, uint32_t idx)
{
return (map_seg(hdp, idx, PROT_READ | PROT_WRITE, DB_MINOR));
}
static struct db_link *
get_link(struct di_devlink_handle *hdp, uint32_t idx)
{
return (map_seg(hdp, idx, PROT_READ, DB_LINK));
}
static struct db_link *
set_link(struct di_devlink_handle *hdp, uint32_t idx)
{
return (map_seg(hdp, idx, PROT_READ | PROT_WRITE, DB_LINK));
}
static char *
get_string(struct di_devlink_handle *hdp, uint32_t idx)
{
return (map_seg(hdp, idx, PROT_READ, DB_STR));
}
static char *
set_string(struct di_devlink_handle *hdp, uint32_t idx)
{
return (map_seg(hdp, idx, PROT_READ | PROT_WRITE, DB_STR));
}
static void *
map_seg(
struct di_devlink_handle *hdp,
uint32_t idx,
int prot,
db_seg_t seg)
{
int s;
off_t off;
size_t slen;
caddr_t addr;
if (idx == DB_NIL) {
return (NULL);
}
if (!VALID_INDEX(hdp, seg, idx)) {
(void) devlink_dprintf(DBG_ERR,
"map_seg: seg(%d): invalid idx(%u)\n", seg, idx);
return (NULL);
}
if (DB_SEG(hdp, seg) != NULL) {
if (DB_SEG_PROT(hdp, seg) != prot) {
(void) devlink_dprintf(DBG_ERR,
"map_seg: illegal access: "
"seg[%d]: idx=%u, seg_prot=%d, access=%d\n",
seg, idx, DB_SEG_PROT(hdp, seg), prot);
return (NULL);
}
return (DB_SEG(hdp, seg) + idx * elem_sizes[seg]);
}
off = seg_size(hdp, DB_HEADER);
for (s = 0; s < seg; s++) {
off += seg_size(hdp, s);
}
slen = seg_size(hdp, seg);
addr = mmap(0, slen, prot, MAP_SHARED, DB(hdp)->db_fd, off);
if (addr == MAP_FAILED) {
(void) devlink_dprintf(DBG_ERR,
"map_seg: seg[%d]: mmap failed: %s\n", seg,
strerror(errno));
(void) devlink_dprintf(DBG_ERR,
"map_seg: args: len=%lu, prot=%d, fd=%d, off=%ld\n",
(ulong_t)slen, prot, DB(hdp)->db_fd, off);
return (NULL);
}
DB_SEG(hdp, seg) = addr;
DB_SEG_PROT(hdp, seg) = prot;
(void) devlink_dprintf(DBG_STEP, "map_seg: seg[%d]: len=%lu, prot=%d, "
"fd=%d, off=%ld, seg_base=%p\n", seg, (ulong_t)slen, prot,
DB(hdp)->db_fd, off, (void *)addr);
return (DB_SEG(hdp, seg) + idx * elem_sizes[seg]);
}
static size_t
seg_size(struct di_devlink_handle *hdp, int seg)
{
size_t sz;
assert(DB_HDR(hdp)->page_sz);
if (seg == DB_HEADER) {
sz = HDR_LEN;
} else {
assert(DB_NUM(hdp, seg) >= 1);
sz = DB_NUM(hdp, seg) * elem_sizes[seg];
}
sz = (sz / DB_HDR(hdp)->page_sz) + 1;
sz *= DB_HDR(hdp)->page_sz;
return (sz);
}
static size_t
size_db(struct di_devlink_handle *hdp, long page_sz, uint32_t *count)
{
int i;
size_t sz;
cache_link_t *clp;
assert(page_sz > 0);
for (i = 0; i < DB_TYPES; i++) {
count[i] = 1;
}
count_node(CACHE(hdp)->root, count);
for (clp = CACHE(hdp)->dngl; clp != NULL; clp = clp->sib) {
count_link(clp, count);
}
sz = ((HDR_LEN / page_sz) + 1) * page_sz;
for (i = 0; i < DB_TYPES; i++) {
assert(count[i] >= 1);
sz += (((count[i] * elem_sizes[i]) / page_sz) + 1) * page_sz;
(void) devlink_dprintf(DBG_INFO, "N[%u]=%u\n", i, count[i]);
}
(void) devlink_dprintf(DBG_INFO, "DB size=%lu\n", (ulong_t)sz);
return (sz);
}
static void
count_node(cache_node_t *cnp, uint32_t *count)
{
cache_minor_t *cmnp;
if (cnp == NULL)
return;
count[DB_NODE]++;
count_string(cnp->path, count);
for (cmnp = cnp->minor; cmnp != NULL; cmnp = cmnp->sib) {
count_minor(cmnp, count);
}
for (cnp = cnp->child; cnp != NULL; cnp = cnp->sib) {
count_node(cnp, count);
}
}
static void
count_minor(cache_minor_t *cmnp, uint32_t *count)
{
cache_link_t *clp;
if (cmnp == NULL)
return;
count[DB_MINOR]++;
count_string(cmnp->name, count);
count_string(cmnp->nodetype, count);
for (clp = cmnp->link; clp != NULL; clp = clp->sib) {
count_link(clp, count);
}
}
static void
count_link(cache_link_t *clp, uint32_t *count)
{
if (clp == NULL)
return;
count[DB_LINK]++;
count_string(clp->path, count);
count_string(clp->content, count);
}
static void
count_string(const char *str, uint32_t *count)
{
if (str == NULL) {
(void) devlink_dprintf(DBG_ERR,
"count_string: NULL argument\n");
return;
}
count[DB_STR] += strlen(str) + 1;
}
static uint_t
hashfn(struct di_devlink_handle *hdp, const char *str)
{
const char *cp;
ulong_t hval = 0;
if (str == NULL) {
return (0);
}
assert(CACHE(hdp)->hash_sz >= MIN_HASH_SIZE);
for (cp = str; *cp != '\0'; cp++) {
hval += *cp;
}
return (hval % CACHE(hdp)->hash_sz);
}
static int
enter_db_lock(struct di_devlink_handle *hdp, const char *root_dir)
{
int fd;
struct flock lock;
char lockfile[PATH_MAX];
int rv;
int writer = HDL_RDWR(hdp);
static int did_sync = 0;
int eintrs;
assert(hdp->lock_fd < 0);
get_db_path(hdp, DB_LOCK, lockfile, sizeof (lockfile));
devlink_dprintf(DBG_LCK, "enter_db_lock: %s BEGIN\n",
writer ? "update" : "snapshot");
(void) mutex_lock(&update_mutex);
again: if ((fd = open(lockfile,
(writer ? (O_RDWR|O_CREAT) : O_RDONLY), DB_LOCK_PERMS)) < 0) {
if ((errno == ENOENT) && !writer) {
if (did_sync == 0) {
did_sync = 1;
devlink_dprintf(DBG_LCK,
"enter_db_lock: %s OSYNC\n",
writer ? "update" : "snapshot");
(void) devlink_create(root_dir, NULL,
DCA_DEVLINK_SYNC);
goto again;
}
devlink_dprintf(DBG_LCK,
"enter_db_lock: %s OPENFAILD %s: WALK\n",
writer ? "update" : "snapshot", strerror(errno));
(void) mutex_unlock(&update_mutex);
return (0);
} else {
devlink_dprintf(DBG_LCK,
"enter_db_lock: %s OPENFAILD %s\n",
writer ? "update" : "snapshot", strerror(errno));
(void) mutex_unlock(&update_mutex);
return (-1);
}
}
lock.l_type = writer ? F_WRLCK : F_RDLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
for (eintrs = 0; eintrs < MAX_LOCK_RETRY; eintrs++) {
rv = fcntl(fd, F_SETLKW, &lock);
if ((rv != -1) || (errno != EINTR))
break;
}
if (rv != -1) {
hdp->lock_fd = fd;
devlink_dprintf(DBG_LCK, "enter_db_lock: %s LOCKED\n",
writer ? "update" : "snapshot");
return (1);
}
(void) close(fd);
devlink_dprintf(DBG_ERR, "enter_db_lock: %s FAILED: %s: WALK\n",
writer ? "update" : "snapshot", strerror(errno));
(void) mutex_unlock(&update_mutex);
return (-1);
}
static void
exit_db_lock(struct di_devlink_handle *hdp)
{
struct flock unlock;
int writer = HDL_RDWR(hdp);
if (hdp->lock_fd < 0) {
return;
}
unlock.l_type = F_UNLCK;
unlock.l_whence = SEEK_SET;
unlock.l_start = 0;
unlock.l_len = 0;
devlink_dprintf(DBG_LCK, "exit_db_lock : %s UNLOCKED\n",
writer ? "update" : "snapshot");
if (fcntl(hdp->lock_fd, F_SETLK, &unlock) == -1) {
devlink_dprintf(DBG_ERR, "exit_db_lock : %s failed: %s\n",
writer ? "update" : "snapshot", strerror(errno));
}
(void) close(hdp->lock_fd);
hdp->lock_fd = -1;
(void) mutex_unlock(&update_mutex);
}
int
is_minor_node(const char *contents, const char **mn_root)
{
char *ptr, *prefix;
prefix = "../devices/";
if ((ptr = strstr(contents, prefix)) != NULL) {
if (mn_root != NULL) {
*mn_root = ptr += strlen(prefix) - 1;
}
return (1);
}
prefix = "/devices/";
if (strncmp(contents, prefix, strlen(prefix)) == 0) {
if (mn_root != NULL) {
*mn_root = contents + strlen(prefix) - 1;
}
return (1);
}
if (mn_root != NULL) {
*mn_root = contents;
}
return (0);
}
static int
s_readlink(const char *link, char *buf, size_t blen)
{
int rv;
if ((rv = readlink(link, buf, blen)) == -1)
goto bad;
if (rv >= blen && buf[blen - 1] != '\0') {
errno = ENAMETOOLONG;
goto bad;
} else if (rv < blen) {
buf[rv] = '\0';
}
return (0);
bad:
devlink_dprintf(DBG_ERR, "s_readlink: %s: failed: %s\n",
link, strerror(errno));
return (-1);
}
#define MAX_DAEMON_ATTEMPTS 2
static int
devlink_create(const char *root, const char *name, int dca_devlink_flag)
{
int i;
int install;
struct dca_off dca;
assert(root);
if (dca_init(name, &dca, dca_devlink_flag) != 0)
return (EINVAL);
i = 0;
do {
install = daemon_call(root, &dca);
devlink_dprintf(DBG_INFO, "daemon_call() retval=%d\n",
dca.dca_error);
if (dca.dca_error != ENOENT && dca.dca_error != EBADF) {
return (dca.dca_error);
}
dca.dca_error = 0;
if (geteuid() != 0)
return (EPERM);
} while ((++i < MAX_DAEMON_ATTEMPTS) &&
start_daemon(root, install) == 0);
devlink_dprintf(DBG_INFO, "devlink_create: can't start daemon\n");
assert(dca.dca_error == 0);
exec_cmd(root, &dca);
return (dca.dca_error);
}
static int
dca_init(const char *name, struct dca_off *dcp, int dca_flags)
{
char *cp;
dcp->dca_root = 0;
dcp->dca_minor = 0;
dcp->dca_driver = 0;
dcp->dca_error = 0;
dcp->dca_flags = dca_flags;
dcp->dca_name[0] = '\0';
name = name ? name : "/";
if (*name != '/') {
(void) snprintf(dcp->dca_name, sizeof (dcp->dca_name),
"/ %s", name);
dcp->dca_root = 0;
*(dcp->dca_name + 1) = '\0';
dcp->dca_driver = 2;
return (0);
}
(void) snprintf(dcp->dca_name, sizeof (dcp->dca_name), "%s", name);
if (is_minor_node(name, NULL))
return (-1);
dcp->dca_root = 0;
if (cp = strrchr(dcp->dca_name, ':')) {
*cp++ = '\0';
dcp->dca_minor = cp - dcp->dca_name;
}
return (0);
}
#define DAEMON_STARTUP_TIME 1
#define DEVNAME_CHECK_FILE "/etc/devname_check_RDONLY"
static int
daemon_call(const char *root, struct dca_off *dcp)
{
door_arg_t arg;
int fd, door_error;
sigset_t oset, nset;
char synch_door[PATH_MAX];
struct stat sb;
char *prefix;
int rofd;
int rdonly;
int install = 0;
rofd = -1;
rdonly = 0;
if ((rofd = open(DEVNAME_CHECK_FILE, O_WRONLY|O_CREAT|O_TRUNC, 0644))
== -1 && errno == EROFS) {
rdonly = 1;
prefix = "/tmp";
} else {
if (rofd != -1) {
(void) close(rofd);
(void) unlink(DEVNAME_CHECK_FILE);
}
prefix = (char *)root;
}
if (rdonly && stat(DEVNAME_CHECK_FILE, &sb) != -1)
install = 1;
(void) snprintf(synch_door, sizeof (synch_door),
"%s/etc/dev/%s", prefix, DEVFSADM_SYNCH_DOOR);
if (stat(synch_door, &sb) == -1 || sb.st_uid != 0) {
if (rdonly)
dcp->dca_error = ENOTSUP;
else
dcp->dca_error = ENOENT;
devlink_dprintf(DBG_ERR,
"stat failed: %s: no file or not root owned\n", synch_door);
return (install);
}
if ((fd = open(synch_door, O_RDONLY)) == -1) {
dcp->dca_error = errno;
devlink_dprintf(DBG_ERR, "open of %s failed: %s\n",
synch_door, strerror(errno));
return (install);
}
arg.data_ptr = (char *)dcp;
arg.data_size = sizeof (*dcp);
arg.desc_ptr = NULL;
arg.desc_num = 0;
arg.rbuf = (char *)dcp;
arg.rsize = sizeof (*dcp);
(void) sigfillset(&nset);
(void) sigemptyset(&oset);
(void) sigprocmask(SIG_SETMASK, &nset, &oset);
if (door_call(fd, &arg)) {
door_error = 1;
dcp->dca_error = errno;
}
(void) sigprocmask(SIG_SETMASK, &oset, NULL);
(void) close(fd);
if (door_error)
return (install);
assert(arg.data_ptr);
dcp->dca_error = ((struct dca_off *)arg.data_ptr)->dca_error;
if (arg.rbuf != (char *)dcp)
(void) munmap(arg.rbuf, arg.rsize);
return (install);
}
#define DEVFSADM_PATH "/usr/sbin/devfsadm"
#define DEVFSADM "devfsadm"
#define DEVFSADMD_PATH "/usr/lib/devfsadm/devfsadmd"
#define DEVFSADM_DAEMON "devfsadmd"
static int
start_daemon(const char *root, int install)
{
int rv, i = 0;
char *argv[20];
argv[i++] = DEVFSADM_DAEMON;
if (install) {
argv[i++] = "-a";
argv[i++] = "/tmp";
argv[i++] = "-p";
argv[i++] = "/tmp/root/etc/path_to_inst";
} else if (strcmp(root, "/")) {
argv[i++] = "-r";
argv[i++] = (char *)root;
}
argv[i++] = NULL;
rv = do_exec(DEVFSADMD_PATH, argv);
(void) sleep(DAEMON_STARTUP_TIME);
return (rv);
}
static void
exec_cmd(const char *root, struct dca_off *dcp)
{
int i;
char *argv[20];
i = 0;
argv[i++] = DEVFSADM;
if (dcp->dca_driver) {
argv[i++] = "-i";
argv[i++] = &dcp->dca_name[dcp->dca_driver];
} else {
argv[i++] = "-n";
}
if (root != NULL && strcmp(root, "/") != 0) {
argv[i++] = "-r";
argv[i++] = (char *)root;
}
argv[i] = NULL;
if (do_exec(DEVFSADM_PATH, argv))
dcp->dca_error = errno;
}
static int
do_exec(const char *path, char *const argv[])
{
int i;
pid_t cpid;
#ifdef DEBUG
devlink_dprintf(DBG_INFO, "Executing %s\n\tArgument list:", path);
for (i = 0; argv[i] != NULL; i++) {
devlink_dprintf(DBG_INFO, " %s", argv[i]);
}
devlink_dprintf(DBG_INFO, "\n");
#endif
if ((cpid = fork1()) == -1) {
devlink_dprintf(DBG_ERR, "fork1 failed: %s\n", strerror(errno));
return (-1);
}
if (cpid == 0) {
int fd;
if ((fd = open("/dev/null", O_RDWR)) >= 0) {
(void) dup2(fd, fileno(stdout));
(void) dup2(fd, fileno(stderr));
(void) close(fd);
(void) execv(path, argv);
} else {
devlink_dprintf(DBG_ERR,
"open of /dev/null failed: %s\n", strerror(errno));
}
_exit(-1);
}
if (waitpid(cpid, &i, 0) == cpid) {
if (WIFEXITED(i)) {
if (WEXITSTATUS(i) == 0) {
devlink_dprintf(DBG_STEP,
"do_exec: child exited normally\n");
return (0);
} else
errno = EINVAL;
} else {
errno = EINTR;
}
devlink_dprintf(DBG_ERR, "child terminated abnormally: %s\n",
strerror(errno));
} else {
devlink_dprintf(DBG_ERR, "waitpid failed: %s\n",
strerror(errno));
}
return (-1);
}
static int
walk_cache_links(di_devlink_handle_t hdp, cache_link_t *clp, link_desc_t *linkp)
{
int i;
assert(HDL_RDWR(hdp) || HDL_RDONLY(hdp));
devlink_dprintf(DBG_INFO, "walk_cache_links: initial link: %s\n",
clp ? clp->path : "<NULL>");
for (i = 0; i < 2; i++) {
for (; clp != NULL; clp = clp->sib) {
struct di_devlink vlink = {NULL};
assert(clp->path[0] != '/');
vlink.rel_path = clp->path;
vlink.content = clp->content;
vlink.type = attr2type(clp->attr);
if (visit_link(hdp, linkp, &vlink)
!= DI_WALK_CONTINUE) {
devlink_dprintf(DBG_INFO, "walk_cache_links: "
"terminating at link: %s\n", clp->path);
goto out;
}
}
clp = CACHE(hdp)->dngl;
}
out:
return (i < 2 ? DI_WALK_TERMINATE : DI_WALK_CONTINUE);
}
static void
walk_all_cache(di_devlink_handle_t hdp, link_desc_t *linkp)
{
int i;
cache_link_t *clp;
devlink_dprintf(DBG_INFO, "walk_all_cache: entered\n");
for (i = 0; i < CACHE(hdp)->hash_sz; i++) {
clp = CACHE_HASH(hdp, i);
for (; clp; clp = clp->hash) {
struct di_devlink vlink = {NULL};
assert(clp->path[0] != '/');
vlink.rel_path = clp->path;
vlink.content = clp->content;
vlink.type = attr2type(clp->attr);
if (visit_link(hdp, linkp, &vlink) !=
DI_WALK_CONTINUE) {
devlink_dprintf(DBG_INFO, "walk_all_cache: "
"terminating walk at link: %s\n",
clp->path);
return;
}
}
}
}
static void
walk_cache_minor(di_devlink_handle_t hdp, const char *mpath, link_desc_t *linkp)
{
cache_minor_t *cmnp;
assert(mpath);
if ((cmnp = lookup_minor(hdp, mpath, NULL, TYPE_CACHE)) != NULL) {
(void) walk_cache_links(hdp, cmnp->link, linkp);
} else {
devlink_dprintf(DBG_ERR, "lookup minor failed: %s\n", mpath);
}
}
static void
walk_cache_node(di_devlink_handle_t hdp, const char *path, link_desc_t *linkp)
{
cache_minor_t *cmnp;
cache_node_t *cnp;
assert(path);
if ((cnp = lookup_node(hdp, (char *)path, TYPE_CACHE)) == NULL) {
devlink_dprintf(DBG_ERR, "lookup node failed: %s\n", path);
return;
}
for (cmnp = cnp->minor; cmnp != NULL; cmnp = cmnp->sib) {
if (walk_cache_links(hdp, cmnp->link, linkp)
== DI_WALK_TERMINATE)
break;
}
}
int
di_devlink_cache_walk(di_devlink_handle_t hdp,
const char *re,
const char *path,
uint_t flags,
void *arg,
int (*devlink_callback)(di_devlink_t, void *))
{
regex_t reg;
link_desc_t linkd = {NULL};
if (hdp == NULL || path == NULL || !link_flag(flags) ||
!HDL_RDWR(hdp) || devlink_callback == NULL) {
errno = EINVAL;
return (-1);
}
linkd.flags = flags;
linkd.arg = arg;
linkd.fcn = devlink_callback;
if (re) {
if (regcomp(®, re, REG_EXTENDED) != 0)
return (-1);
linkd.regp = ®
}
if (minor_colon(path) == NULL) {
walk_cache_node(hdp, path, &linkd);
} else {
walk_cache_minor(hdp, path, &linkd);
}
if (re)
regfree(®);
return (0);
}
#define DEBUG_ENV_VAR "_DEVLINK_DEBUG"
static int _devlink_debug = -1;
static void
debug_print(debug_level_t msglevel, const char *fmt, va_list ap)
{
char *cp;
int save;
assert(_devlink_debug != 0);
if (_devlink_debug < 0) {
if ((cp = getenv(DEBUG_ENV_VAR)) == NULL) {
_devlink_debug = 0;
return;
}
save = errno;
errno = 0;
_devlink_debug = strtol(cp, NULL, 10);
if (errno != 0 || _devlink_debug < 0) {
_devlink_debug = 0;
errno = save;
return;
}
errno = save;
if (!_devlink_debug)
return;
}
assert(_devlink_debug > 0);
if (_devlink_debug < msglevel)
return;
if ((_devlink_debug == DBG_LCK) && (msglevel != _devlink_debug))
return;
if (msglevel == DBG_ERR) {
(void) fprintf(stderr, "[ERROR]: ");
}
(void) vfprintf(stderr, fmt, ap);
(void) fflush(stderr);
}
void
devlink_dprintf(debug_level_t msglevel, const char *fmt, ...)
{
va_list ap;
assert(msglevel > 0);
if (!_devlink_debug)
return;
va_start(ap, fmt);
debug_print(msglevel, fmt, ap);
va_end(ap);
}