blob: c3904792e35bc002993360dd9e9ff7058b5d04cf [file] [log] [blame]
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Devfsadm replaces drvconfig, audlinks, disks, tapes, ports, devlinks
* as a general purpose device administrative utility. It creates
* devices special files in /devices and logical links in /dev, and
* coordinates updates to /etc/path_to_instance with the kernel. It
* operates in both command line mode to handle user or script invoked
* reconfiguration updates, and operates in daemon mode to handle dynamic
* reconfiguration for hotplugging support.
*/
#include <string.h>
#include <tsol/label.h>
#include <bsm/devices.h>
#include <bsm/devalloc.h>
#include <utime.h>
#include "devfsadm_impl.h"
/* externs from devalloc.c */
extern void _reset_devalloc(int);
extern void _update_devalloc_db(devlist_t *, int, int, char *, char *);
extern int _da_check_for_usb(char *, char *);
/* create or remove nodes or links. unset with -n */
static int file_mods = TRUE;
/* cleanup mode. Set with -C */
static int cleanup = FALSE;
/* devlinks -d compatibility */
static int devlinks_debug = FALSE;
/* flag to check if system is labeled */
int system_labeled = FALSE;
/* flag to enable/disable device allocation with -e/-d */
static int devalloc_flag = 0;
/* flag to update device allocation database for this device type */
static int update_devdb = 0;
/*
* devices to be deallocated with -d :
* audio, floppy, cd, floppy, tape, rmdisk.
*/
static char *devalloc_list[10] = {DDI_NT_AUDIO, DDI_NT_CD, DDI_NT_CD_CHAN,
DDI_NT_FD, DDI_NT_TAPE, DDI_NT_BLOCK_CHAN,
DDI_NT_UGEN, DDI_NT_USB_ATTACHMENT_POINT,
DDI_NT_SCSI_NEXUS, NULL};
/* list of allocatable devices */
static devlist_t devlist;
/* load a single driver only. set with -i */
static int single_drv = FALSE;
static char *driver = NULL;
/* attempt to load drivers or defer attach nodes */
static int load_attach_drv = TRUE;
/* set if invoked via /usr/lib/devfsadm/devfsadmd */
static int daemon_mode = FALSE;
/* output directed to syslog during daemon mode if set */
static int logflag = FALSE;
/* build links in /dev. -x to turn off */
static int build_dev = TRUE;
/* build nodes in /devices. -y to turn off */
static int build_devices = TRUE;
/* -z to turn off */
static int flush_path_to_inst_enable = TRUE;
/* variables used for path_to_inst flushing */
static int inst_count = 0;
static mutex_t count_lock;
static cond_t cv;
/* variables for minor_fini calling system */
static int minor_fini_timeout = MINOR_FINI_TIMEOUT_DEFAULT;
static mutex_t minor_fini_mutex;
static int minor_fini_thread_created = FALSE;
static int minor_fini_delay_restart = FALSE;
/* single-threads /dev modification */
static sema_t dev_sema;
/* the program we were invoked as; ie argv[0] */
static char *prog;
/* pointers to create/remove link lists */
static create_list_t *create_head = NULL;
static remove_list_t *remove_head = NULL;
/* supports the class -c option */
static char **classes = NULL;
static int num_classes = 0;
/* used with verbose option -v or -V */
static int num_verbose = 0;
static char **verbose = NULL;
static struct mperm *minor_perms = NULL;
static driver_alias_t *driver_aliases = NULL;
/* set if -r alternate root given */
static char *root_dir = "";
/* /devices or <rootdir>/devices */
static char *devices_dir = DEVICES;
/* /dev or <rootdir>/dev */
static char *dev_dir = DEV;
/* /dev for the global zone */
static char *global_dev_dir = DEV;
/* /etc/path_to_inst unless -p used */
static char *inst_file = INSTANCE_FILE;
/* /usr/lib/devfsadm/linkmods unless -l used */
static char *module_dirs = MODULE_DIRS;
/* default uid/gid used if /etc/minor_perm entry not found */
static uid_t root_uid;
static gid_t sys_gid;
/* /etc/devlink.tab unless devlinks -t used */
static char *devlinktab_file = NULL;
/* set if /dev link is new. speeds up rm_stale_links */
static int linknew = TRUE;
/* variables for devlink.tab compat processing */
static devlinktab_list_t *devlinktab_list = NULL;
static unsigned int devlinktab_line = 0;
/* cache head for devfsadm_enumerate*() functions */
static numeral_set_t *head_numeral_set = NULL;
/* list list of devfsadm modules */
static module_t *module_head = NULL;
/* name_to_major list used in utility function */
static n2m_t *n2m_list = NULL;
/* cache of some links used for performance */
static linkhead_t *headlinkhead = NULL;
/* locking variables to prevent multiples writes to /dev */
static int hold_dev_lock = FALSE;
static int hold_daemon_lock = FALSE;
static int dev_lock_fd;
static int daemon_lock_fd;
static char dev_lockfile[PATH_MAX + 1];
static char daemon_lockfile[PATH_MAX + 1];
/* last devinfo node/minor processed. used for performance */
static di_node_t lnode;
static di_minor_t lminor;
static char lphy_path[PATH_MAX + 1] = {""};
/* Globals used by the link database */
static di_devlink_handle_t devlink_cache;
static int update_database = FALSE;
static int devlink_door_fd = -1; /* fd of devlink handler door */
/* Globals used to set logindev perms */
static struct login_dev *login_dev_cache = NULL;
static int login_dev_enable = FALSE;
/* Global to use devinfo snapshot cache */
static int use_snapshot_cache = FALSE;
/* Zone-related information */
static int zone_cmd_mode = 0;
static mutex_t zone_mutex; /* protects zone registration/unregistration ops */
static struct zone_devinfo *zone_head; /* linked list of zones */
/*
* Packaged directories - not removed even when empty.
* The dirs must be listed in canonical form
* i.e. without leading "/dev/"
*/
static char *packaged_dirs[] =
{"dsk", "rdsk", "term", NULL};
/* RCM related globals */
static void *librcm_hdl;
static rcm_handle_t *rcm_hdl = NULL;
static thread_t process_rcm_events_tid;
static struct rcm_eventq *volatile rcm_eventq_head = NULL;
static struct rcm_eventq *rcm_eventq_tail = NULL;
static mutex_t rcm_eventq_lock;
static cond_t rcm_eventq_cv;
static volatile int need_to_exit_rcm_event_thread = 0;
static void load_dev_acl(void);
static void update_drvconf(major_t);
int
main(int argc, char *argv[])
{
struct stat tx_stat;
struct passwd *pw;
struct group *gp;
pid_t pid;
(void) setlocale(LC_ALL, "");
(void) textdomain(TEXT_DOMAIN);
if ((prog = strrchr(argv[0], '/')) == NULL) {
prog = argv[0];
} else {
prog++;
}
if (getuid() != 0) {
err_print(MUST_BE_ROOT);
devfsadm_exit(1);
}
/*
* Close all files except stdin/stdout/stderr
*/
closefrom(3);
if ((pw = getpwnam(DEFAULT_DEV_USER)) != NULL) {
root_uid = pw->pw_uid;
} else {
err_print(CANT_FIND_USER, DEFAULT_DEV_USER);
root_uid = (uid_t)0; /* assume 0 is root */
}
/* the default group is sys */
if ((gp = getgrnam(DEFAULT_DEV_GROUP)) != NULL) {
sys_gid = gp->gr_gid;
} else {
err_print(CANT_FIND_GROUP, DEFAULT_DEV_GROUP);
sys_gid = (gid_t)3; /* assume 3 is sys */
}
(void) umask(0);
system_labeled = is_system_labeled();
if (system_labeled == FALSE) {
/*
* is_system_labeled() will return false in case we are
* starting before the first reboot after Trusted Extensions
* is installed. we check for a well known TX binary to
* to see if TX is installed.
*/
if (stat(DA_LABEL_CHECK, &tx_stat) == 0)
system_labeled = TRUE;
}
parse_args(argc, argv);
(void) sema_init(&dev_sema, 1, USYNC_THREAD, NULL);
/* Initialize device allocation list */
devlist.audio = devlist.cd = devlist.floppy = devlist.tape =
devlist.rmdisk = NULL;
if (daemon_mode == TRUE) {
/*
* Build /dev and /devices before daemonizing if
* reconfig booting and daemon invoked with alternate
* root. This is to support install.
*/
if (getenv(RECONFIG_BOOT) != NULL && root_dir[0] != '\0') {
vprint(INFO_MID, CONFIGURING);
load_dev_acl();
update_drvconf((major_t)-1);
process_devinfo_tree();
(void) modctl(MODSETMINIROOT);
}
/*
* fork before detaching from tty in order to print error
* message if unable to acquire file lock. locks not preserved
* across forks. Even under debug we want to fork so that
* when executed at boot we don't hang.
*/
if (fork() != 0) {
devfsadm_exit(0);
}
/* set directory to / so it coredumps there */
if (chdir("/") == -1) {
err_print(CHROOT_FAILED, strerror(errno));
}
/* only one daemon can run at a time */
if ((pid = enter_daemon_lock()) == getpid()) {
thread_t thread;
detachfromtty();
(void) cond_init(&cv, USYNC_THREAD, 0);
(void) mutex_init(&count_lock, USYNC_THREAD, 0);
if (thr_create(NULL, NULL,
(void *(*)(void *))instance_flush_thread,
NULL,
THR_DETACHED,
&thread) != 0) {
err_print(CANT_CREATE_THREAD, "daemon",
strerror(errno));
devfsadm_exit(1);
}
/*
* No need for rcm notifications when running
* with an alternate root. So initialize rcm only
* when devfsadm is running with root dir "/".
* Similarly, logindevperms need only be set
* in daemon mode and when root dir is "/".
*/
if (root_dir[0] == '\0') {
(void) rcm_init();
login_dev_enable = TRUE;
}
daemon_update();
} else {
err_print(DAEMON_RUNNING, pid);
devfsadm_exit(1);
}
exit_daemon_lock();
} else {
/* not a daemon, so just build /dev and /devices */
process_devinfo_tree();
if (devalloc_flag != 0)
/* Enable/disable device allocation */
_reset_devalloc(devalloc_flag);
}
return (0);
}
static void
update_drvconf(major_t major)
{
if (modctl(MODLOADDRVCONF, major) != 0)
err_print(gettext("update_drvconf failed for major %d\n"),
major);
}
static void
load_dev_acl()
{
if (load_devpolicy() != 0)
err_print(gettext("device policy load failed\n"));
load_minor_perm_file();
}
/*
* set_zone_params sets us up to run against a specific zone. It should
* only be called from parse_args.
*/
static int
set_zone_params(char *zone_name)
{
char zpath[MAXPATHLEN];
zone_dochandle_t hdl;
void *dlhdl;
assert(daemon_mode == FALSE);
if (strcmp(zone_name, GLOBAL_ZONENAME) == 0) {
err_print(INVALID_ZONE, zone_name);
return (DEVFSADM_FAILURE);
}
if ((dlhdl = dlopen(LIBZONECFG_PATH, RTLD_LAZY)) == NULL) {
err_print(ZONE_LIB_MISSING);
return (DEVFSADM_FAILURE);
}
if (zone_get_zonepath(zone_name, zpath, sizeof (zpath)) != Z_OK) {
err_print(ZONE_ROOTPATH_FAILED, zone_name, strerror(errno));
(void) dlclose(dlhdl);
return (DEVFSADM_FAILURE);
}
set_root_devices_dev_dir(zpath, 1);
if ((hdl = zonecfg_init_handle()) == NULL) {
err_print(ZONE_REP_FAILED, zone_name, strerror(errno));
(void) dlclose(dlhdl);
return (DEVFSADM_FAILURE);
}
if ((zonecfg_get_snapshot_handle(zone_name, hdl)) != Z_OK) {
err_print(ZONE_REP_FAILED, zone_name, strerror(errno));
zonecfg_fini_handle(hdl);
(void) dlclose(dlhdl);
return (DEVFSADM_FAILURE);
}
(void) dlclose(dlhdl);
zone_head = s_malloc(sizeof (struct zone_devinfo));
zone_head->zone_path = s_strdup(zpath);
zone_head->zone_name = s_strdup(zone_name);
zone_head->zone_dochdl = hdl;
zone_head->zone_next = NULL;
zone_cmd_mode = 1;
return (DEVFSADM_SUCCESS);
}
/*
* Parse arguments for all 6 programs handled from devfsadm.
*/
static void
parse_args(int argc, char *argv[])
{
char opt;
char get_linkcompat_opts = FALSE;
char *compat_class;
int num_aliases = 0;
int len;
int retval;
int add_bind = FALSE;
struct aliases *ap = NULL;
struct aliases *a_head = NULL;
struct aliases *a_tail = NULL;
struct modconfig mc;
if (strcmp(prog, DISKS) == 0) {
compat_class = "disk";
get_linkcompat_opts = TRUE;
} else if (strcmp(prog, TAPES) == 0) {
compat_class = "tape";
get_linkcompat_opts = TRUE;
} else if (strcmp(prog, PORTS) == 0) {
compat_class = "port";
get_linkcompat_opts = TRUE;
} else if (strcmp(prog, AUDLINKS) == 0) {
compat_class = "audio";
get_linkcompat_opts = TRUE;
} else if (strcmp(prog, DEVLINKS) == 0) {
devlinktab_file = DEVLINKTAB_FILE;
build_devices = FALSE;
load_attach_drv = FALSE;
while ((opt = getopt(argc, argv, "dnr:st:vV:")) != EOF) {
switch (opt) {
case 'd':
file_mods = FALSE;
flush_path_to_inst_enable = FALSE;
devlinks_debug = TRUE;
break;
case 'n':
/* prevent driver loading and deferred attach */
load_attach_drv = FALSE;
break;
case 'r':
set_root_devices_dev_dir(optarg, 0);
if (zone_pathcheck(root_dir) !=
DEVFSADM_SUCCESS)
devfsadm_exit(1);
break;
case 's':
/*
* suppress. don't create/remove links/nodes
* useful with -v or -V
*/
file_mods = FALSE;
flush_path_to_inst_enable = FALSE;
break;
case 't':
/* supply a non-default table file */
devlinktab_file = optarg;
break;
case 'v':
/* documented verbose flag */
add_verbose_id(VERBOSE_MID);
break;
case 'V':
/* undocumented for extra verbose levels */
add_verbose_id(optarg);
break;
default:
usage();
break;
}
}
if (optind < argc) {
usage();
}
} else if (strcmp(prog, DRVCONFIG) == 0) {
build_dev = FALSE;
while ((opt =
getopt(argc, argv, "a:bdc:i:m:np:R:r:svV:")) != EOF) {
switch (opt) {
case 'a':
ap = calloc(sizeof (struct aliases), 1);
ap->a_name = dequote(optarg);
len = strlen(ap->a_name) + 1;
if (len > MAXMODCONFNAME) {
err_print(ALIAS_TOO_LONG,
MAXMODCONFNAME, ap->a_name);
devfsadm_exit(1);
}
ap->a_len = len;
if (a_tail == NULL) {
a_head = ap;
} else {
a_tail->a_next = ap;
}
a_tail = ap;
num_aliases++;
add_bind = TRUE;
break;
case 'b':
add_bind = TRUE;
break;
case 'c':
(void) strcpy(mc.drvclass, optarg);
break;
case 'd':
/*
* need to keep for compatibility, but
* do nothing.
*/
break;
case 'i':
single_drv = TRUE;
(void) strcpy(mc.drvname, optarg);
driver = s_strdup(optarg);
break;
case 'm':
mc.major = atoi(optarg);
break;
case 'n':
/* prevent driver loading and deferred attach */
load_attach_drv = FALSE;
break;
case 'p':
/* specify alternate path_to_inst file */
inst_file = s_strdup(optarg);
break;
case 'R':
/*
* Private flag for suninstall to populate
* device information on the installed root.
*/
root_dir = s_strdup(optarg);
if (zone_pathcheck(root_dir) !=
DEVFSADM_SUCCESS)
devfsadm_exit(devfsadm_copy());
break;
case 'r':
devices_dir = s_strdup(optarg);
if (zone_pathcheck(devices_dir) !=
DEVFSADM_SUCCESS)
devfsadm_exit(1);
break;
case 's':
/*
* suppress. don't create nodes
* useful with -v or -V
*/
file_mods = FALSE;
flush_path_to_inst_enable = FALSE;
break;
case 'v':
/* documented verbose flag */
add_verbose_id(VERBOSE_MID);
break;
case 'V':
/* undocumented for extra verbose levels */
add_verbose_id(optarg);
break;
default:
usage();
}
}
if (optind < argc) {
usage();
}
if ((add_bind == TRUE) && (mc.major == -1 ||
mc.drvname[0] == NULL)) {
err_print(MAJOR_AND_B_FLAG);
devfsadm_exit(1);
}
if (add_bind == TRUE) {
mc.num_aliases = num_aliases;
mc.ap = a_head;
retval = modctl(MODADDMAJBIND, NULL, (caddr_t)&mc);
if (retval < 0) {
err_print(MODCTL_ADDMAJBIND);
}
devfsadm_exit(retval);
}
} else if ((strcmp(prog, DEVFSADM) == 0) ||
(strcmp(prog, DEVFSADMD) == 0)) {
char *zonename = NULL;
enum zreg_op zoneop;
int init_drvconf = 0;
int init_perm = 0;
int public_mode = 0;
if (strcmp(prog, DEVFSADMD) == 0) {
daemon_mode = TRUE;
}
devlinktab_file = DEVLINKTAB_FILE;
while ((opt = getopt(argc, argv,
"Cc:deIi:l:np:PR:r:st:vV:x:z:Z:")) != EOF) {
if (opt == 'I' || opt == 'P') {
if (public_mode)
usage();
} else {
if (init_perm || init_drvconf)
usage();
public_mode = 1;
}
switch (opt) {
case 'C':
cleanup = TRUE;
break;
case 'c':
num_classes++;
classes = s_realloc(classes, num_classes *
sizeof (char *));
classes[num_classes - 1] = optarg;
break;
case 'd':
if (daemon_mode == FALSE) {
/*
* Device allocation to be disabled.
*/
devalloc_flag = DA_OFF;
build_dev = FALSE;
}
break;
case 'e':
if (daemon_mode == FALSE) {
/*
* Device allocation to be enabled.
*/
devalloc_flag = DA_ON;
build_dev = FALSE;
}
break;
case 'I': /* update kernel driver.conf cache */
if (daemon_mode == TRUE)
usage();
init_drvconf = 1;
break;
case 'i':
single_drv = TRUE;
driver = s_strdup(optarg);
break;
case 'l':
/* specify an alternate module load path */
module_dirs = s_strdup(optarg);
break;
case 'n':
/* prevent driver loading and deferred attach */
load_attach_drv = FALSE;
break;
case 'p':
/* specify alternate path_to_inst file */
inst_file = s_strdup(optarg);
break;
case 'P':
if (daemon_mode == TRUE)
usage();
/* load minor_perm and device_policy */
init_perm = 1;
break;
case 'R':
/*
* Private flag for suninstall to populate
* device information on the installed root.
*/
root_dir = s_strdup(optarg);
devfsadm_exit(devfsadm_copy());
break;
case 'r':
set_root_devices_dev_dir(optarg, 0);
break;
case 's':
/*
* suppress. don't create/remove links/nodes
* useful with -v or -V
*/
file_mods = FALSE;
flush_path_to_inst_enable = FALSE;
break;
case 't':
devlinktab_file = optarg;
break;
case 'v':
/* documented verbose flag */
add_verbose_id(VERBOSE_MID);
break;
case 'V':
/* undocumented: specify verbose lvl */
add_verbose_id(optarg);
break;
case 'x':
/*
* x is the "private switch" option. The
* goal is to not suck up all the other
* option letters.
*/
if (strcmp(optarg, "update_devlinksdb") == 0) {
update_database = TRUE;
} else if (strcmp(optarg, "no_dev") == 0) {
/* don't build /dev */
build_dev = FALSE;
} else if (strcmp(optarg, "no_devices") == 0) {
/* don't build /devices */
build_devices = FALSE;
} else if (strcmp(optarg, "no_p2i") == 0) {
/* don't flush path_to_inst */
flush_path_to_inst_enable = FALSE;
} else if (strcmp(optarg, "use_dicache") == 0) {
use_snapshot_cache = TRUE;
} else {
usage();
}
break;
case 'z':
zonename = optarg;
zoneop = ZONE_REG;
break;
case 'Z':
zonename = optarg;
zoneop = ZONE_UNREG;
break;
default:
usage();
break;
}
}
if (optind < argc) {
usage();
}
/*
* We're not in zone mode; Check to see if the rootpath
* collides with any zonepaths.
*/
if (zonename == NULL) {
if (zone_pathcheck(root_dir) != DEVFSADM_SUCCESS)
devfsadm_exit(1);
}
if (zonename != NULL) {
/*
* -z and -Z cannot be used if we're the daemon. The
* daemon always manages all zones.
*/
if (daemon_mode == TRUE)
usage();
/*
* -z and -Z are private flags, but to be paranoid we
* check whether they have been combined with -r.
*/
if (*root_dir != '\0')
usage();
if (set_zone_params(optarg) != DEVFSADM_SUCCESS)
devfsadm_exit(1);
call_zone_register(zonename, zoneop);
if (zoneop == ZONE_UNREG)
devfsadm_exit(0);
/*
* If we are in ZONE_REG mode we plow on, laying out
* devices for this zone.
*/
}
if (init_drvconf || init_perm) {
/*
* Load minor perm before force-loading drivers
* so the correct permissions are picked up.
*/
if (init_perm)
load_dev_acl();
if (init_drvconf)
update_drvconf((major_t)-1);
devfsadm_exit(0);
/* NOTREACHED */
}
}
if (get_linkcompat_opts == TRUE) {
build_devices = FALSE;
load_attach_drv = FALSE;
num_classes++;
classes = s_realloc(classes, num_classes *
sizeof (char *));
classes[num_classes - 1] = compat_class;
while ((opt = getopt(argc, argv, "Cnr:svV:")) != EOF) {
switch (opt) {
case 'C':
cleanup = TRUE;
break;
case 'n':
/* prevent driver loading or deferred attach */
load_attach_drv = FALSE;
break;
case 'r':
set_root_devices_dev_dir(optarg, 0);
if (zone_pathcheck(root_dir) !=
DEVFSADM_SUCCESS)
devfsadm_exit(1);
break;
case 's':
/* suppress. don't create/remove links/nodes */
/* useful with -v or -V */
file_mods = FALSE;
flush_path_to_inst_enable = FALSE;
break;
case 'v':
/* documented verbose flag */
add_verbose_id(VERBOSE_MID);
break;
case 'V':
/* undocumented for extra verbose levels */
add_verbose_id(optarg);
break;
default:
usage();
}
}
if (optind < argc) {
usage();
}
}
}
void
usage(void)
{
if (strcmp(prog, DEVLINKS) == 0) {
err_print(DEVLINKS_USAGE);
} else if (strcmp(prog, DRVCONFIG) == 0) {
err_print(DRVCONFIG_USAGE);
} else if ((strcmp(prog, DEVFSADM) == 0) ||
(strcmp(prog, DEVFSADMD) == 0)) {
err_print(DEVFSADM_USAGE);
} else {
err_print(COMPAT_LINK_USAGE);
}
devfsadm_exit(1);
}
static void
devi_tree_walk(struct dca_impl *dcip, int flags, char *ev_subclass)
{
char *msg, *name;
struct mlist mlist = {0};
di_node_t node;
vprint(CHATTY_MID, "devi_tree_walk: root=%s, minor=%s, driver=%s,"
" error=%d, flags=%u\n", dcip->dci_root,
dcip->dci_minor ? dcip->dci_minor : "<NULL>",
dcip->dci_driver ? dcip->dci_driver : "<NULL>", dcip->dci_error,
dcip->dci_flags);
assert(dcip->dci_root);
if (dcip->dci_flags & DCA_LOAD_DRV) {
node = di_init_driver(dcip->dci_driver, flags);
msg = DRIVER_FAILURE;
name = dcip->dci_driver;
} else {
node = di_init(dcip->dci_root, flags);
msg = DI_INIT_FAILED;
name = dcip->dci_root;
}
if (node == DI_NODE_NIL) {
dcip->dci_error = errno;
/*
* Rapid hotplugging (commonly seen during USB testing),
* may remove a device before the create event for it
* has been processed. To prevent alarming users with
* a superfluous message, we suppress error messages
* for ENXIO and hotplug.
*/
if (!(errno == ENXIO && (dcip->dci_flags & DCA_HOT_PLUG)))
err_print(msg, name, strerror(dcip->dci_error));
return;
}
if (dcip->dci_flags & DCA_FLUSH_PATHINST)
flush_path_to_inst();
dcip->dci_arg = &mlist;
vprint(CHATTY_MID, "walking device tree\n");
(void) di_walk_minor(node, NULL, DI_CHECK_ALIAS, dcip,
check_minor_type);
process_deferred_links(dcip, DCA_CREATE_LINK);
dcip->dci_arg = NULL;
/*
* Finished creating devfs files and dev links.
* Log sysevent and notify RCM.
*/
if (ev_subclass)
build_and_log_event(EC_DEV_ADD, ev_subclass, dcip->dci_root,
node);
if ((dcip->dci_flags & DCA_NOTIFY_RCM) && rcm_hdl)
(void) notify_rcm(node, dcip->dci_minor);
/* Add new device to device allocation database */
if (system_labeled && update_devdb) {
_update_devalloc_db(&devlist, 0, DA_ADD, NULL, root_dir);
update_devdb = 0;
}
di_fini(node);
}
static void
process_deferred_links(struct dca_impl *dcip, int flags)
{
struct mlist *dep;
struct minor *mp, *smp;
vprint(CHATTY_MID, "processing deferred links\n");
dep = dcip->dci_arg;
/*
* The list head is not used during the deferred create phase
*/
dcip->dci_arg = NULL;
assert(dep);
assert((dep->head == NULL) ^ (dep->tail != NULL));
assert(flags == DCA_FREE_LIST || flags == DCA_CREATE_LINK);
for (smp = NULL, mp = dep->head; mp; mp = mp->next) {
if (flags == DCA_CREATE_LINK)
(void) check_minor_type(mp->node, mp->minor, dcip);
free(smp);
smp = mp;
}
free(smp);
}
/*
* Called in non-daemon mode to take a snap shot of the devinfo tree.
* Then it calls the appropriate functions to build /devices and /dev.
* It also flushes path_to_inst.
* DINFOCACHE snapshot needs to be updated when devfsadm is run.
* This will only happen if the flags that devfsadm uses matches the flags
* that DINFOCACHE uses and that is why flags is set to
* DI_CACHE_SNAPSHOT_FLAGS.
*/
void
process_devinfo_tree()
{
uint_t flags = DI_CACHE_SNAPSHOT_FLAGS;
struct dca_impl dci;
char name[MAXNAMELEN];
char *fcn = "process_devinfo_tree: ";
vprint(CHATTY_MID, "%senter\n", fcn);
dca_impl_init("/", NULL, &dci);
lock_dev();
/*
* Update kernel driver.conf cache when devfsadm/drvconfig
* is invoked to build /devices and /dev.
*/
if (load_attach_drv == TRUE)
update_drvconf((major_t)-1);
if (single_drv == TRUE) {
/*
* load a single driver, but walk the entire devinfo tree
*/
if (load_attach_drv == FALSE)
err_print(DRV_LOAD_REQD);
vprint(CHATTY_MID, "%sattaching driver (%s)\n", fcn, driver);
dci.dci_flags |= DCA_LOAD_DRV;
(void) snprintf(name, sizeof (name), "%s", driver);
dci.dci_driver = name;
} else if (load_attach_drv == TRUE) {
/*
* Load and attach all drivers, then walk the entire tree.
* If the cache flag is set, use DINFOCACHE to get cached
* data.
*/
if (use_snapshot_cache == TRUE) {
flags = DINFOCACHE;
vprint(CHATTY_MID, "%susing snapshot cache\n", fcn);
} else {
vprint(CHATTY_MID, "%sattaching all drivers\n", fcn);
flags |= DINFOFORCE;
if (cleanup) {
/*
* remove dangling entries from /etc/devices
* files.
*/
flags |= DINFOCLEANUP;
}
}
}
if (((load_attach_drv == TRUE) || (single_drv == TRUE)) &&
(build_devices == TRUE)) {
dci.dci_flags |= DCA_FLUSH_PATHINST;
}
/* handle pre-cleanup operations desired by the modules. */
pre_and_post_cleanup(RM_PRE);
devi_tree_walk(&dci, flags, NULL);
if (dci.dci_error) {
devfsadm_exit(1);
}
/* handle post-cleanup operations desired by the modules. */
pre_and_post_cleanup(RM_POST);
unlock_dev(SYNC_STATE);
}
/*ARGSUSED*/
static void
print_cache_signal(int signo)
{
if (signal(SIGUSR1, print_cache_signal) == SIG_ERR) {
err_print("signal SIGUSR1 failed: %s\n", strerror(errno));
devfsadm_exit(1);
}
}
/*
* Register with eventd for messages. Create doors for synchronous
* link creation.
*/
static void
daemon_update(void)
{
int fd;
char *fcn = "daemon_update: ";
char door_file[MAXPATHLEN];
const char *subclass_list;
sysevent_handle_t *sysevent_hp;
vprint(CHATTY_MID, "%senter\n", fcn);
if (signal(SIGUSR1, print_cache_signal) == SIG_ERR) {
err_print("signal SIGUSR1 failed: %s\n", strerror(errno));
devfsadm_exit(1);
}
if (snprintf(door_file, sizeof (door_file),
"%s%s", root_dir, DEVFSADM_SERVICE_DOOR) >= sizeof (door_file)) {
err_print("update_daemon failed to open sysevent service "
"door\n");
devfsadm_exit(1);
}
if ((sysevent_hp = sysevent_open_channel_alt(
door_file)) == NULL) {
err_print(CANT_CREATE_DOOR,
door_file, strerror(errno));
devfsadm_exit(1);
}
if (sysevent_bind_subscriber(sysevent_hp, event_handler) != 0) {
err_print(CANT_CREATE_DOOR,
door_file, strerror(errno));
(void) sysevent_close_channel(sysevent_hp);
devfsadm_exit(1);
}
subclass_list = EC_SUB_ALL;
if (sysevent_register_event(sysevent_hp, EC_ALL, &subclass_list, 1)
!= 0) {
err_print(CANT_CREATE_DOOR,
door_file, strerror(errno));
(void) sysevent_unbind_subscriber(sysevent_hp);
(void) sysevent_close_channel(sysevent_hp);
devfsadm_exit(1);
}
if (snprintf(door_file, sizeof (door_file),
"%s/%s", dev_dir, ZONE_REG_DOOR) >= sizeof (door_file)) {
err_print(CANT_CREATE_ZONE_DOOR, door_file,
strerror(ENAMETOOLONG));
devfsadm_exit(1);
}
(void) s_unlink(door_file);
if ((fd = open(door_file, O_RDWR | O_CREAT, ZONE_DOOR_PERMS)) == -1) {
err_print(CANT_CREATE_ZONE_DOOR, door_file, strerror(errno));
devfsadm_exit(1);
}
(void) close(fd);
if ((fd = door_create(zone_reg_handler, NULL,
DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) == -1) {
err_print(CANT_CREATE_ZONE_DOOR, door_file, strerror(errno));
(void) s_unlink(door_file);
devfsadm_exit(1);
}
if (fattach(fd, door_file) == -1) {
err_print(CANT_CREATE_ZONE_DOOR, door_file, strerror(errno));
(void) s_unlink(door_file);
devfsadm_exit(1);
}
(void) snprintf(door_file, sizeof (door_file), "%s/%s", dev_dir,
DEVFSADM_SYNCH_DOOR);
(void) s_unlink(door_file);
if ((fd = open(door_file, O_RDWR | O_CREAT, SYNCH_DOOR_PERMS)) == -1) {
err_print(CANT_CREATE_DOOR, door_file, strerror(errno));
devfsadm_exit(1);
}
(void) close(fd);
if ((fd = door_create(sync_handler, NULL,
DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) == -1) {
err_print(CANT_CREATE_DOOR, door_file, strerror(errno));
(void) s_unlink(door_file);
devfsadm_exit(1);
}
if (fattach(fd, door_file) == -1) {
err_print(CANT_CREATE_DOOR, door_file, strerror(errno));
(void) s_unlink(door_file);
devfsadm_exit(1);
}
devlink_door_fd = fd;
/*
* Make sure devfsadm is managing any and all configured system zones.
*/
if (register_all_zones() != DEVFSADM_SUCCESS) {
err_print(ZONE_LIST_FAILED, strerror(errno));
}
vprint(CHATTY_MID, "%spausing\n", fcn);
for (;;) {
(void) pause();
}
}
/*ARGSUSED*/
static void
sync_handler(void *cookie, char *ap, size_t asize,
door_desc_t *dp, uint_t ndesc)
{
door_cred_t dcred;
struct dca_off *dcp, rdca;
struct dca_impl dci;
/*
* Must be root to make this call
* If caller is not root, don't touch its data.
*/
if (door_cred(&dcred) != 0 || dcred.dc_euid != 0) {
dcp = &rdca;
dcp->dca_error = EPERM;
goto out;
}
assert(ap);
assert(asize == sizeof (*dcp));
dcp = (void *)ap;
/*
* Root is always present and is the first component of "name" member
*/
assert(dcp->dca_root == 0);
/*
* The structure passed in by the door_client uses offsets
* instead of pointers to work across address space boundaries.
* Now copy the data into a structure (dca_impl) which uses
* pointers.
*/
dci.dci_root = &dcp->dca_name[dcp->dca_root];
dci.dci_minor = dcp->dca_minor ? &dcp->dca_name[dcp->dca_minor] : NULL;
dci.dci_driver =
dcp->dca_driver ? &dcp->dca_name[dcp->dca_driver] : NULL;
dci.dci_error = 0;
dci.dci_flags = dcp->dca_flags | (dci.dci_driver ? DCA_LOAD_DRV : 0);
dci.dci_arg = NULL;
lock_dev();
devi_tree_walk(&dci, DINFOCPYALL, NULL);
unlock_dev(CACHE_STATE);
dcp->dca_error = dci.dci_error;
startup_cache_sync_thread();
out:
(void) door_return((char *)dcp, sizeof (*dcp), NULL, 0);
}
static void
lock_dev(void)
{
vprint(CHATTY_MID, "lock_dev(): entered\n");
if (build_dev == FALSE)
return;
/* lockout other threads from /dev */
while (sema_wait(&dev_sema) != 0);
/*
* Lock out other devfsadm processes from /dev.
* If this wasn't the last process to run,
* clear caches
*/
if (enter_dev_lock() != getpid()) {
invalidate_enumerate_cache();
rm_all_links_from_cache();
(void) di_devlink_close(&devlink_cache, DI_LINK_ERROR);
devlink_cache = NULL;
}
/*
* (re)load the reverse links database if not
* already cached.
*/
if (devlink_cache == NULL)
devlink_cache = di_devlink_open(root_dir, 0);
/*
* If modules were unloaded, reload them. Also use module status
* as an indication that we should check to see if other binding
* files need to be reloaded.
*/
if (module_head == NULL) {
load_modules();
read_minor_perm_file();
read_driver_aliases_file();
read_devlinktab_file();
read_logindevperm_file();
}
if (module_head != NULL)
return;
if (strcmp(prog, DEVLINKS) == 0) {
if (devlinktab_list == NULL) {
err_print(NO_LINKTAB, devlinktab_file);
err_print(NO_MODULES, module_dirs);
err_print(ABORTING);
devfsadm_exit(1);
}
} else {
err_print(NO_MODULES, module_dirs);
if (strcmp(prog, DEVFSADM) == 0) {
err_print(MODIFY_PATH);
}
}
}
static void
unlock_dev(int flag)
{
vprint(CHATTY_MID, "unlock_dev(): entered\n");
if (build_dev == FALSE)
return;
assert(devlink_cache);
assert(flag == SYNC_STATE || flag == CACHE_STATE);
if (flag == SYNC_STATE) {
unload_modules();
if (update_database)
(void) di_devlink_update(devlink_cache);
(void) di_devlink_close(&devlink_cache, 0);
devlink_cache = NULL;
}
exit_dev_lock();
(void) sema_post(&dev_sema);
}
/*
* Contact the daemon to register the identified zone. We do everything with
* zone names, for simplicity.
*/
static void
call_zone_register(char *zone_name, int regop)
{
int doorfd, ret, retries = 0;
door_arg_t arg;
struct zreg z;
char path[MAXPATHLEN];
assert(regop == ZONE_REG || regop == ZONE_UNREG);
if (strcmp(zone_name, GLOBAL_ZONENAME) == 0) {
err_print(INVALID_ZONE, zone_name);
return;
}
z.zreg_error = 0;
z.zreg_op = regop;
(void) strlcpy(z.zreg_zonename, zone_name, ZONENAME_MAX);
(void) snprintf(path, sizeof (path), "/dev/%s", ZONE_REG_DOOR);
if ((doorfd = open(path, O_RDWR)) == -1) {
return;
}
bzero(&arg, sizeof (arg));
arg.data_ptr = (char *)&z;
arg.data_size = sizeof (z);
arg.rbuf = (char *)&z;
arg.rsize = sizeof (z);
/*
* If the daemon is running, tell it about the zone. If not, it's
* ok. When it next gets run by the system (because there is
* device-related work to do), it will load the list of zones from
* the kernel.
*/
while (((ret = door_call(doorfd, &arg)) == -1) && retries++ < 3) {
(void) sleep(retries);
}
(void) close(doorfd);
if (ret != 0) {
return;
}
switch (z.zreg_error) {
case ZONE_SUCCESS:
break;
case ZONE_ERR_NOZONE:
err_print(ZONE_REG_FAILED, zone_name, strerror(z.zreg_errno));
break;
case ZONE_ERR_DOOR:
err_print(ZONE_DOOR_MKFAIL, zone_name, strerror(z.zreg_errno));
break;
case ZONE_ERR_REPOSITORY:
err_print(ZONE_REP_FAILED, zone_name, strerror(z.zreg_errno));
break;
case ZONE_ERR_NOLIB:
err_print(ZONE_LIB_MISSING);
break;
default:
err_print(ZONE_REG_FAILED, zone_name, strerror(z.zreg_errno));
break;
}
}
/*
* The following routines are the daemon-side code for managing the set of
* currently registered zones.
*
* TODO: improve brain-dead list performance--- use libuutil avl tree or hash?
*/
static void
zlist_insert(struct zone_devinfo *newzone)
{
struct zone_devinfo *z;
assert(MUTEX_HELD(&zone_mutex));
if (zone_head == NULL) {
zone_head = newzone;
return;
}
z = zlist_remove(newzone->zone_name);
if (z != NULL)
delete_zone(z);
newzone->zone_next = zone_head;
zone_head = newzone;
}
static void
delete_zone(struct zone_devinfo *z) {
char door_file[PATH_MAX];
/*
* Tidy up by withdrawing our door from the zone.
*/
(void) snprintf(door_file, sizeof (door_file), "%s/dev/%s",
z->zone_path, DEVFSADM_SYNCH_DOOR);
(void) s_unlink(door_file);
zonecfg_fini_handle(z->zone_dochdl);
free(z->zone_path);
free(z->zone_name);
free(z);
}
static struct zone_devinfo *
zlist_remove(char *zone_name)
{
struct zone_devinfo *z, *unlinked = NULL, **prevnextp;
assert(MUTEX_HELD(&zone_mutex));
prevnextp = &zone_head;
for (z = zone_head; z != NULL; z = z->zone_next) {
if (strcmp(zone_name, z->zone_name) == 0) {
unlinked = z;
*prevnextp = z->zone_next;
return (unlinked);
}
prevnextp = &(z->zone_next);
}
return (NULL);
}
/*
* Delete all zones. Note that this should *only* be called in the exit
* path of the daemon, as it does not take the zone_mutex-- this is because
* we could wind up calling devfsadm_exit() with that zone_mutex_held.
*/
static void
zlist_deleteall_unlocked(void)
{
struct zone_devinfo *tofree;
while (zone_head != NULL) {
tofree = zone_head;
zone_head = zone_head->zone_next;
delete_zone(tofree);
}
assert(zone_head == NULL);
}
static int
zone_register(char *zone_name)
{
char door_file[MAXPATHLEN], zpath[MAXPATHLEN];
int fd;
int need_unlink = 0, error = ZONE_SUCCESS, myerrno = 0;
zone_dochandle_t hdl = NULL;
void *dlhdl = NULL;
struct zone_devinfo *newzone = NULL;
assert(MUTEX_HELD(&zone_mutex));
if ((dlhdl = dlopen(LIBZONECFG_PATH, RTLD_LAZY)) == NULL) {
error = ZONE_ERR_NOLIB;
goto bad;
}
if (zone_get_zonepath(zone_name, zpath, sizeof (zpath)) != Z_OK) {
error = ZONE_ERR_NOZONE;
myerrno = errno;
goto bad;
}
if (snprintf(door_file, sizeof (door_file), "%s/dev/%s",
zpath, DEVFSADM_SYNCH_DOOR) >= sizeof (door_file)) {
myerrno = ENAMETOOLONG; /* synthesize a reasonable errno */
error = ZONE_ERR_DOOR;
goto bad;
}
(void) s_unlink(door_file);
if ((fd = open(door_file, O_RDWR | O_CREAT, ZONE_DOOR_PERMS)) == -1) {
myerrno = errno;
error = ZONE_ERR_DOOR;
goto bad;
}
need_unlink = 1;
(void) close(fd);
if (fattach(devlink_door_fd, door_file) == -1) {
error = ZONE_ERR_DOOR;
myerrno = errno;
goto bad;
}
if ((hdl = zonecfg_init_handle()) == NULL) {
error = ZONE_ERR_REPOSITORY;
myerrno = errno;
goto bad;
}
if ((zonecfg_get_snapshot_handle(zone_name, hdl)) != Z_OK) {
error = ZONE_ERR_REPOSITORY;
myerrno = errno;
goto bad;
}
newzone = s_malloc(sizeof (struct zone_devinfo));
newzone->zone_path = s_strdup(zpath);
newzone->zone_name = s_strdup(zone_name);
newzone->zone_next = NULL;
newzone->zone_dochdl = hdl;
zlist_insert(newzone);
(void) dlclose(dlhdl);
return (ZONE_SUCCESS);
bad:
(void) devfsadm_errprint("%s[%ld]: failed to register zone %s: %s",
prog, getpid(), zone_name, strerror(myerrno));
assert(newzone == NULL);
if (need_unlink)
(void) s_unlink(door_file);
if (hdl)
zonecfg_fini_handle(hdl);
if (dlhdl)
(void) dlclose(dlhdl);
errno = myerrno;
return (error);
}
static int
zone_unregister(char *zone_name)
{
struct zone_devinfo *z;
assert(MUTEX_HELD(&zone_mutex));
if ((z = zlist_remove(zone_name)) == NULL)
return (ZONE_ERR_NOZONE);
delete_zone(z);
return (ZONE_SUCCESS);
}
/*
* Called by the daemon when it receives a door call to the zone registration
* door.
*/
/*ARGSUSED*/
static void
zone_reg_handler(void *cookie, char *ap, size_t asize, door_desc_t *dp,
uint_t ndesc)
{
door_cred_t dcred;
struct zreg *zregp, rzreg;
/*
* We coarsely lock the whole registration process.
*/
(void) mutex_lock(&zone_mutex);
/*
* Must be root to make this call
* If caller is not root, don't touch its data.
*/
if (door_cred(&dcred) != 0 || dcred.dc_euid != 0) {
zregp = &rzreg;
zregp->zreg_error = ZONE_ERR_REPOSITORY;
zregp->zreg_errno = EPERM;
goto out;
}
assert(ap);
assert(asize == sizeof (*zregp));
zregp = (struct zreg *)(void *)ap;
/*
* Kernel must know about this zone; one way of discovering this
* is by looking up the zone id.
*/
if (getzoneidbyname(zregp->zreg_zonename) == -1) {
zregp->zreg_error = ZONE_ERR_REPOSITORY;
zregp->zreg_errno = errno;
goto out;
}
if (zregp->zreg_op == ZONE_REG) {
zregp->zreg_error = zone_register(zregp->zreg_zonename);
zregp->zreg_errno = errno;
} else {
zregp->zreg_error = zone_unregister(zregp->zreg_zonename);
zregp->zreg_errno = errno;
}
out:
(void) mutex_unlock(&zone_mutex);
(void) door_return((char *)zregp, sizeof (*zregp), NULL, 0);
}
static int
register_all_zones(void)
{
zoneid_t *zids = NULL;
uint_t nzents, nzents_saved;
int i;
(void) mutex_lock(&zone_mutex);
if (zone_list(NULL, &nzents) != 0)
return (DEVFSADM_FAILURE);
again:
assert(zids == NULL);
assert(MUTEX_HELD(&zone_mutex));
if (nzents == 0) {
(void) mutex_unlock(&zone_mutex);
return (DEVFSADM_SUCCESS);
}
zids = s_zalloc(nzents * sizeof (zoneid_t));
nzents_saved = nzents;
if (zone_list(zids, &nzents) != 0) {
(void) mutex_unlock(&zone_mutex);
free(zids);
return (DEVFSADM_FAILURE);
}
if (nzents != nzents_saved) {
/* list changed, try again */
free(zids);
zids = NULL;
goto again;
}
assert(zids != NULL);
for (i = 0; i < nzents; i++) {
char name[ZONENAME_MAX];
if (zids[i] == GLOBAL_ZONEID)
continue;
if (getzonenamebyid(zids[i], name, sizeof (name)) >= 0)
(void) zone_register(name);
}
(void) mutex_unlock(&zone_mutex);
free(zids);
return (DEVFSADM_SUCCESS);
}
/*
* Check that if -r is set, it is not any part of a zone--- that is, that
* the zonepath is not a substring of the root path.
*/
static int
zone_pathcheck(char *checkpath)
{
void *dlhdl = NULL;
char *name;
char root[MAXPATHLEN]; /* resolved devfsadm root path */
char zroot[MAXPATHLEN]; /* zone root path */
char rzroot[MAXPATHLEN]; /* resolved zone root path */
char tmp[MAXPATHLEN];
FILE *cookie;
int err = DEVFSADM_SUCCESS;
if (checkpath[0] == '\0')
return (DEVFSADM_SUCCESS);
/*
* Check if zones is available on this system.
*/
if ((dlhdl = dlopen(LIBZONECFG_PATH, RTLD_LAZY)) == NULL) {
return (DEVFSADM_SUCCESS);
}
bzero(root, sizeof (root));
if (resolvepath(checkpath, root, sizeof (root) - 1) == -1) {
/*
* In this case the user has done 'devfsadm -r' on some path
* which does not yet exist, or we got some other misc. error.
* We punt and don't resolve the path in this case.
*/
(void) strlcpy(root, checkpath, sizeof (root));
}
if (strlen(root) > 0 && (root[strlen(root) - 1] != '/')) {
(void) snprintf(tmp, sizeof (tmp), "%s/", root);
(void) strlcpy(root, tmp, sizeof (root));
}
cookie = setzoneent();
while ((name = getzoneent(cookie)) != NULL) {
/* Skip the global zone */
if (strcmp(name, GLOBAL_ZONENAME) == 0) {
free(name);
continue;
}
if (zone_get_zonepath(name, zroot, sizeof (zroot)) != Z_OK) {
free(name);
continue;
}
bzero(rzroot, sizeof (rzroot));
if (resolvepath(zroot, rzroot, sizeof (rzroot) - 1) == -1) {
/*
* Zone path doesn't exist, or other misc error,
* so we try using the non-resolved pathname.
*/
(void) strlcpy(rzroot, zroot, sizeof (rzroot));
}
if (strlen(rzroot) > 0 && (rzroot[strlen(rzroot) - 1] != '/')) {
(void) snprintf(tmp, sizeof (tmp), "%s/", rzroot);
(void) strlcpy(rzroot, tmp, sizeof (rzroot));
}
/*
* Finally, the comparison. If the zone root path is a
* leading substring of the root path, fail.
*/
if (strncmp(rzroot, root, strlen(rzroot)) == 0) {
err_print(ZONE_PATHCHECK, root, name);
err = DEVFSADM_FAILURE;
free(name);
break;
}
free(name);
}
endzoneent(cookie);
(void) dlclose(dlhdl);
return (err);
}
/*
* Called by the daemon when it receives an event from the devfsadm SLM
* to syseventd.
*
* The devfsadm SLM uses a private event channel for communication to
* devfsadmd set-up via private libsysevent interfaces. This handler is
* used to bind to the devfsadmd channel for event delivery.
* The devfsadmd SLM insures single calls to this routine as well as
* synchronized event delivery.
*
*/
static void
event_handler(sysevent_t *ev)
{
char *path;
char *minor;
char *subclass;
char *dev_ev_subclass;
char *driver_name;
nvlist_t *attr_list = NULL;
int err = 0;
int instance;
int branch_event = 0;
subclass = sysevent_get_subclass_name(ev);
vprint(EVENT_MID, "event_handler: %s id:0X%llx\n",
subclass, sysevent_get_seq(ev));
/* Check if event is an instance modification */
if (strcmp(subclass, ESC_DEVFS_INSTANCE_MOD) == 0) {
devfs_instance_mod();
return;
}
if (sysevent_get_attr_list(ev, &attr_list) != 0) {
vprint(EVENT_MID, "event_handler: can not get attr list\n");
return;
}
if (strcmp(subclass, ESC_DEVFS_DEVI_ADD) == 0 ||
strcmp(subclass, ESC_DEVFS_DEVI_REMOVE) == 0 ||
strcmp(subclass, ESC_DEVFS_MINOR_CREATE) == 0 ||
strcmp(subclass, ESC_DEVFS_MINOR_REMOVE) == 0) {
if ((err = nvlist_lookup_string(attr_list, DEVFS_PATHNAME,
&path)) != 0)
goto out;
if (strcmp(subclass, ESC_DEVFS_DEVI_ADD) == 0 ||
strcmp(subclass, ESC_DEVFS_DEVI_REMOVE) == 0) {
if (nvlist_lookup_string(attr_list, DEVFS_DEVI_CLASS,
&dev_ev_subclass) != 0)
dev_ev_subclass = NULL;
if (nvlist_lookup_string(attr_list, DEVFS_DRIVER_NAME,
&driver_name) != 0)
driver_name = NULL;
if (nvlist_lookup_int32(attr_list, DEVFS_INSTANCE,
&instance) != 0)
instance = -1;
if (nvlist_lookup_int32(attr_list, DEVFS_BRANCH_EVENT,
&branch_event) != 0)
branch_event = 0;
} else {
if (nvlist_lookup_string(attr_list, DEVFS_MINOR_NAME,
&minor) != 0)
minor = NULL;
}
lock_dev();
if (strcmp(ESC_DEVFS_DEVI_ADD, subclass) == 0) {
add_minor_pathname(path, NULL, dev_ev_subclass);
if (branch_event) {
build_and_log_event(EC_DEV_BRANCH,
ESC_DEV_BRANCH_ADD, path, DI_NODE_NIL);
}
} else if (strcmp(ESC_DEVFS_MINOR_CREATE, subclass) == 0) {
add_minor_pathname(path, minor, NULL);
} else if (strcmp(ESC_DEVFS_MINOR_REMOVE, subclass) == 0) {
hot_cleanup(path, minor, NULL, NULL, -1);
} else { /* ESC_DEVFS_DEVI_REMOVE */
hot_cleanup(path, NULL, dev_ev_subclass,
driver_name, instance);
if (branch_event) {
build_and_log_event(EC_DEV_BRANCH,
ESC_DEV_BRANCH_REMOVE, path, DI_NODE_NIL);
}
}
unlock_dev(CACHE_STATE);
startup_cache_sync_thread();
} else if (strcmp(subclass, ESC_DEVFS_BRANCH_ADD) == 0 ||
strcmp(subclass, ESC_DEVFS_BRANCH_REMOVE) == 0) {
if ((err = nvlist_lookup_string(attr_list,
DEVFS_PATHNAME, &path)) != 0)
goto out;
/* just log ESC_DEV_BRANCH... event */
if (strcmp(subclass, ESC_DEVFS_BRANCH_ADD) == 0)
dev_ev_subclass = ESC_DEV_BRANCH_ADD;
else
dev_ev_subclass = ESC_DEV_BRANCH_REMOVE;
lock_dev();
build_and_log_event(EC_DEV_BRANCH, dev_ev_subclass, path,
DI_NODE_NIL);
unlock_dev(CACHE_STATE);
startup_cache_sync_thread();
} else
err_print(UNKNOWN_EVENT, subclass);
out:
if (err)
err_print(EVENT_ATTR_LOOKUP_FAILED, strerror(err));
nvlist_free(attr_list);
}
static void
dca_impl_init(char *root, char *minor, struct dca_impl *dcip)
{
assert(root);
dcip->dci_root = root;
dcip->dci_minor = minor;
dcip->dci_driver = NULL;
dcip->dci_error = 0;
dcip->dci_flags = 0;
dcip->dci_arg = NULL;
}
/*
* Kernel logs a message when a devinfo node is attached. Try to create
* /dev and /devices for each minor node. minorname can be NULL.
*/
void
add_minor_pathname(char *node, char *minor, char *ev_subclass)
{
struct dca_impl dci;
vprint(CHATTY_MID, "add_minor_pathname: node_path=%s minor=%s\n",
node, minor ? minor : "NULL");
dca_impl_init(node, minor, &dci);
/*
* Restrict hotplug link creation if daemon
* started with -i option.
*/
if (single_drv == TRUE) {
dci.dci_driver = driver;
}
/*
* We are being invoked in response to a hotplug
* event. Also, notify RCM if nodetype indicates
* a network device has been hotplugged.
*/
dci.dci_flags = DCA_HOT_PLUG | DCA_CHECK_TYPE;
devi_tree_walk(&dci, DINFOPROP|DINFOMINOR, ev_subclass);
}
static di_node_t
find_clone_node()
{
static di_node_t clone_node = DI_NODE_NIL;
if (clone_node == DI_NODE_NIL)
clone_node = di_init("/pseudo/clone@0", DINFOPROP);
return (clone_node);
}
static int
is_descendent_of(di_node_t node, char *driver)
{
while (node != DI_NODE_NIL) {
char *drv = di_driver_name(node);
if (strcmp(drv, driver) == 0)
return (1);
node = di_parent_node(node);
}
return (0);
}
/*
* Checks the minor type. If it is an alias node, then lookup
* the real node/minor first, then call minor_process() to
* do the real work.
*/
static int
check_minor_type(di_node_t node, di_minor_t minor, void *arg)
{
ddi_minor_type minor_type;
di_node_t clone_node;
char *mn;
char *nt;
struct mlist *dep;
struct dca_impl *dcip = arg;
assert(dcip);
dep = dcip->dci_arg;
mn = di_minor_name(minor);
/*
* We match driver here instead of in minor_process
* as we want the actual driver name. This check is
* unnecessary during deferred processing.
*/
if (dep &&
((dcip->dci_driver && !is_descendent_of(node, dcip->dci_driver)) ||
(dcip->dci_minor && strcmp(mn, dcip->dci_minor)))) {
return (DI_WALK_CONTINUE);
}
if ((dcip->dci_flags & DCA_CHECK_TYPE) &&
(nt = di_minor_nodetype(minor)) &&
(strcmp(nt, DDI_NT_NET) == 0)) {
dcip->dci_flags |= DCA_NOTIFY_RCM;
dcip->dci_flags &= ~DCA_CHECK_TYPE;
}
minor_type = di_minor_type(minor);
if (minor_type == DDM_MINOR) {
minor_process(node, minor, dep);
} else if (minor_type == DDM_ALIAS) {
struct mlist *cdep, clone_del = {0};
clone_node = find_clone_node();
if (clone_node == DI_NODE_NIL) {
err_print(DI_INIT_FAILED, "clone", strerror(errno));
return (DI_WALK_CONTINUE);
}
cdep = dep ? &clone_del : NULL;
minor_process(clone_node, minor, cdep);
/*
* cache "alias" minor node and free "clone" minor
*/
if (cdep != NULL && cdep->head != NULL) {
assert(cdep->tail != NULL);
cache_deferred_minor(dep, node, minor);
dcip->dci_arg = cdep;
process_deferred_links(dcip, DCA_FREE_LIST);
dcip->dci_arg = dep;
}
}
return (DI_WALK_CONTINUE);
}
/*
* This is the entry point for each minor node, whether walking
* the entire tree via di_walk_minor() or processing a hotplug event
* for a single devinfo node (via hotplug ndi_devi_online()).
*/
/*ARGSUSED*/
static void
minor_process(di_node_t node, di_minor_t minor, struct mlist *dep)
{
create_list_t *create;
int defer;
vprint(CHATTY_MID, "minor_process: node=%s, minor=%s\n",
di_node_name(node), di_minor_name(minor));
if (dep != NULL) {
/*
* Reset /devices node to minor_perm perm/ownership
* if we are here to deactivate device allocation
*/
if (build_devices == TRUE) {
reset_node_permissions(node, minor);
}
if (build_dev == FALSE) {
return;
}
/*
* This function will create any nodes for /etc/devlink.tab.
* If devlink.tab handles link creation, we don't call any
* devfsadm modules since that could cause duplicate caching
* in the enumerate functions if different re strings are
* passed that are logically identical. I'm still not
* convinced this would cause any harm, but better to be safe.
*
* Deferred processing is available only for devlinks
* created through devfsadm modules.
*/
if (process_devlink_compat(minor, node) == TRUE) {
return;
}
} else {
vprint(CHATTY_MID, "minor_process: deferred processing\n");
}
/*
* look for relevant link create rules in the modules, and
* invoke the link create callback function to build a link
* if there is a match.
*/
defer = 0;
for (create = create_head; create != NULL; create = create->next) {
if ((minor_matches_rule(node, minor, create) == TRUE) &&
class_ok(create->create->device_class) ==
DEVFSADM_SUCCESS) {
if (call_minor_init(create->modptr) ==
DEVFSADM_FAILURE) {
continue;
}
/*
* If NOT doing the deferred creates (i.e. 1st pass) and
* rule requests deferred processing cache the minor
* data.
*
* If deferred processing (2nd pass), create links
* ONLY if rule requests deferred processing.
*/
if (dep && ((create->create->flags & CREATE_MASK) ==
CREATE_DEFER)) {
defer = 1;
continue;
} else if (dep == NULL &&
((create->create->flags & CREATE_MASK) !=
CREATE_DEFER)) {
continue;
}
if ((*(create->create->callback_fcn))
(minor, node) == DEVFSADM_TERMINATE) {
break;
}
}
}
if (defer)
cache_deferred_minor(dep, node, minor);
}
/*
* Cache node and minor in defer list.
*/
static void
cache_deferred_minor(
struct mlist *dep,
di_node_t node,
di_minor_t minor)
{
struct minor *mp;
const char *fcn = "cache_deferred_minor";
vprint(CHATTY_MID, "%s node=%s, minor=%s\n", fcn,
di_node_name(node), di_minor_name(minor));
if (dep == NULL) {
vprint(CHATTY_MID, "%s: cannot cache during "
"deferred processing. Ignoring minor\n", fcn);
return;
}
mp = (struct minor *)s_zalloc(sizeof (struct minor));
mp->node = node;
mp->minor = minor;
mp->next = NULL;
assert(dep->head == NULL || dep->tail != NULL);
if (dep->head == NULL) {
dep->head = mp;
} else {
dep->tail->next = mp;
}
dep->tail = mp;
}
/*
* Check to see if "create" link creation rule matches this node/minor.
* If it does, return TRUE.
*/
static int
minor_matches_rule(di_node_t node, di_minor_t minor, create_list_t *create)
{
char *m_nodetype, *m_drvname;
if (create->create->node_type != NULL) {
m_nodetype = di_minor_nodetype(minor);
assert(m_nodetype != NULL);
switch (create->create->flags & TYPE_MASK) {
case TYPE_EXACT:
if (strcmp(create->create->node_type, m_nodetype) !=
0) {
return (FALSE);
}
break;
case TYPE_PARTIAL:
if (strncmp(create->create->node_type, m_nodetype,
strlen(create->create->node_type)) != 0) {
return (FALSE);
}
break;
case TYPE_RE:
if (regexec(&(create->node_type_comp), m_nodetype,
0, NULL, 0) != 0) {
return (FALSE);
}
break;
}
}
if (create->create->drv_name != NULL) {
m_drvname = di_driver_name(node);
switch (create->create->flags & DRV_MASK) {
case DRV_EXACT:
if (strcmp(create->create->drv_name, m_drvname) != 0) {
return (FALSE);
}
break;
case DRV_RE:
if (regexec(&(create->drv_name_comp), m_drvname,
0, NULL, 0) != 0) {
return (FALSE);
}
break;
}
}
return (TRUE);
}
/*
* If no classes were given on the command line, then return DEVFSADM_SUCCESS.
* Otherwise, return DEVFSADM_SUCCESS if the device "class" from the module
* matches one of the device classes given on the command line,
* otherwise, return DEVFSADM_FAILURE.
*/
static int
class_ok(char *class)
{
int i;
if (num_classes == 0) {
return (DEVFSADM_SUCCESS);
}
for (i = 0; i < num_classes; i++) {
if (strcmp(class, classes[i]) == 0) {
return (DEVFSADM_SUCCESS);
}
}
return (DEVFSADM_FAILURE);
}
/*
* call minor_fini on active modules, then unload ALL modules
*/
static void
unload_modules(void)
{
module_t *module_free;
create_list_t *create_free;
remove_list_t *remove_free;
while (create_head != NULL) {
create_free = create_head;
create_head = create_head->next;
if ((create_free->create->flags & TYPE_RE) == TYPE_RE) {
regfree(&(create_free->node_type_comp));
}
if ((create_free->create->flags & DRV_RE) == DRV_RE) {
regfree(&(create_free->drv_name_comp));
}
free(create_free);
}
while (remove_head != NULL) {
remove_free = remove_head;
remove_head = remove_head->next;
free(remove_free);
}
while (module_head != NULL) {
if ((module_head->minor_fini != NULL) &&
((module_head->flags & MODULE_ACTIVE) == MODULE_ACTIVE)) {
(void) (*(module_head->minor_fini))();
}
vprint(MODLOAD_MID, "unloading module %s\n", module_head->name);
free(module_head->name);
(void) dlclose(module_head->dlhandle);
module_free = module_head;
module_head = module_head->next;
free(module_free);
}
}
/*
* Load devfsadm logical link processing modules.
*/
static void
load_modules(void)
{
DIR *mod_dir;
struct dirent *entp;
char cdir[PATH_MAX + 1];
char *last;
char *mdir = module_dirs;
char *fcn = "load_modules: ";
while (*mdir != '\0') {
while (*mdir == ':') {
mdir++;
}
if (*mdir == '\0') {
continue;
}
last = strchr(mdir, ':');
if (last == NULL) {
last = mdir + strlen(mdir);
}
(void) strncpy(cdir, mdir, last - mdir);
cdir[last - mdir] = '\0';
mdir += strlen(cdir);
if ((mod_dir = opendir(cdir)) == NULL) {
vprint(MODLOAD_MID, "%sopendir(%s): %s\n",
fcn, cdir, strerror(errno));
continue;
}
while ((entp = readdir(mod_dir)) != NULL) {
if ((strcmp(entp->d_name, ".") == 0) ||
(strcmp(entp->d_name, "..") == 0)) {
continue;
}
load_module(entp->d_name, cdir);
}
s_closedir(mod_dir);
}
}
static void
load_module(char *mname, char *cdir)
{
_devfsadm_create_reg_t *create_reg;
_devfsadm_remove_reg_t *remove_reg;
create_list_t *create_list_element;
create_list_t **create_list_next;
remove_list_t *remove_list_element;
remove_list_t **remove_list_next;
char epath[PATH_MAX + 1], *end;
char *fcn = "load_module: ";
char *dlerrstr;
void *dlhandle;
module_t *module;
int n;
int i;
/* ignore any file which does not end in '.so' */
if ((end = strstr(mname, MODULE_SUFFIX)) != NULL) {
if (end[strlen(MODULE_SUFFIX)] != '\0') {
return;
}
} else {
return;
}
(void) snprintf(epath, sizeof (epath), "%s/%s", cdir, mname);
if ((dlhandle = dlopen(epath, RTLD_LAZY)) == NULL) {
dlerrstr = dlerror();
err_print(DLOPEN_FAILED, epath,
dlerrstr ? dlerrstr : "unknown error");
return;
}
/* dlsym the _devfsadm_create_reg structure */
if (NULL == (create_reg = (_devfsadm_create_reg_t *)
dlsym(dlhandle, _DEVFSADM_CREATE_REG))) {
vprint(MODLOAD_MID, "dlsym(%s, %s): symbol not found\n", epath,
_DEVFSADM_CREATE_REG);
} else {
vprint(MODLOAD_MID, "%sdlsym(%s, %s) succeeded\n",
fcn, epath, _DEVFSADM_CREATE_REG);
}
/* dlsym the _devfsadm_remove_reg structure */
if (NULL == (remove_reg = (_devfsadm_remove_reg_t *)
dlsym(dlhandle, _DEVFSADM_REMOVE_REG))) {
vprint(MODLOAD_MID, "dlsym(%s,\n\t%s): symbol not found\n",
epath, _DEVFSADM_REMOVE_REG);
} else {
vprint(MODLOAD_MID, "dlsym(%s, %s): succeeded\n",
epath, _DEVFSADM_REMOVE_REG);
}
vprint(MODLOAD_MID, "module %s loaded\n", epath);
module = (module_t *)s_malloc(sizeof (module_t));
module->name = s_strdup(epath);
module->dlhandle = dlhandle;
/* dlsym other module functions, to be called later */
module->minor_fini = (int (*)())dlsym(dlhandle, MINOR_FINI);
module->minor_init = (int (*)())dlsym(dlhandle, MINOR_INIT);
module->flags = 0;
/*
* put a ptr to each struct devfsadm_create on "create_head"
* list sorted in interpose_lvl.
*/
if (create_reg != NULL) {
for (i = 0; i < create_reg->count; i++) {
int flags = create_reg->tblp[i].flags;
create_list_element = (create_list_t *)
s_malloc(sizeof (create_list_t));
create_list_element->create = &(create_reg->tblp[i]);
create_list_element->modptr = module;
if (((flags & CREATE_MASK) != 0) &&
((flags & CREATE_MASK) != CREATE_DEFER)) {
free(create_list_element);
err_print("illegal flag combination in "
"module create\n");
err_print(IGNORING_ENTRY, i, epath);
continue;
}
if (((flags & TYPE_MASK) == 0) ^
(create_reg->tblp[i].node_type == NULL)) {
free(create_list_element);
err_print("flags value incompatible with "
"node_type value in module create\n");
err_print(IGNORING_ENTRY, i, epath);
continue;
}
if (((flags & TYPE_MASK) != 0) &&
((flags & TYPE_MASK) != TYPE_EXACT) &&
((flags & TYPE_MASK) != TYPE_RE) &&
((flags & TYPE_MASK) != TYPE_PARTIAL)) {
free(create_list_element);
err_print("illegal TYPE_* flag combination in "
"module create\n");
err_print(IGNORING_ENTRY, i, epath);
continue;
}
/* precompile regular expression for efficiency */
if ((flags & TYPE_RE) == TYPE_RE) {
if ((n = regcomp(&(create_list_element->
node_type_comp),
create_reg->tblp[i].node_type,
REG_EXTENDED)) != 0) {
free(create_list_element);
err_print(REGCOMP_FAILED,
create_reg->tblp[i].node_type,
n);
err_print(IGNORING_ENTRY, i, epath);
continue;
}
}
if (((flags & DRV_MASK) == 0) ^
(create_reg->tblp[i].drv_name == NULL)) {
if ((flags & TYPE_RE) == TYPE_RE) {
regfree(&(create_list_element->
node_type_comp));
}
free(create_list_element);
err_print("flags value incompatible with "
"drv_name value in module create\n");
err_print(IGNORING_ENTRY, i, epath);
continue;
}
if (((flags & DRV_MASK) != 0) &&
((flags & DRV_MASK) != DRV_EXACT) &&
((flags & DRV_MASK) != DRV_RE)) {
if ((flags & TYPE_RE) == TYPE_RE) {
regfree(&(create_list_element->
node_type_comp));
}
free(create_list_element);
err_print("illegal DRV_* flag combination in "
"module create\n");
err_print(IGNORING_ENTRY, i, epath);
continue;
}
/* precompile regular expression for efficiency */
if ((create_reg->tblp[i].flags & DRV_RE) == DRV_RE) {
if ((n = regcomp(&(create_list_element->
drv_name_comp),
create_reg->tblp[i].drv_name,
REG_EXTENDED)) != 0) {
if ((flags & TYPE_RE) == TYPE_RE) {
regfree(&(create_list_element->
node_type_comp));
}
free(create_list_element);
err_print(REGCOMP_FAILED,
create_reg->tblp[i].drv_name,
n);
err_print(IGNORING_ENTRY, i, epath);
continue;
}
}
/* add to list sorted by interpose level */
for (create_list_next = &(create_head);
(*create_list_next != NULL) &&
(*create_list_next)->create->interpose_lvl >=
create_list_element->create->interpose_lvl;
create_list_next =
&((*create_list_next)->next));
create_list_element->next = *create_list_next;
*create_list_next = create_list_element;
}
}
/*
* put a ptr to each struct devfsadm_remove on "remove_head"
* list sorted by interpose_lvl.
*/
if (remove_reg != NULL) {
for (i = 0; i < remove_reg->count; i++) {
remove_list_element = (remove_list_t *)
s_malloc(sizeof (remove_list_t));
remove_list_element->remove = &(remove_reg->tblp[i]);
remove_list_element->modptr = module;
for (remove_list_next = &(remove_head);
(*remove_list_next != NULL) &&
(*remove_list_next)->remove->interpose_lvl >=
remove_list_element->remove->interpose_lvl;
remove_list_next =
&((*remove_list_next)->next));
remove_list_element->next = *remove_list_next;
*remove_list_next = remove_list_element;
}
}
module->next = module_head;
module_head = module;
}
/*
* Create a thread to call minor_fini after some delay
*/
static void
startup_cache_sync_thread()
{
vprint(INITFINI_MID, "startup_cache_sync_thread\n");
(void) mutex_lock(&minor_fini_mutex);
minor_fini_delay_restart = TRUE;
if (minor_fini_thread_created == FALSE) {
if (thr_create(NULL, NULL,
(void *(*)(void *))call_minor_fini_thread, NULL,
THR_DETACHED, NULL)) {
err_print(CANT_CREATE_THREAD, "minor_fini",
strerror(errno));
(void) mutex_unlock(&minor_fini_mutex);
/*
* just sync state here instead of
* giving up
*/
lock_dev();
unlock_dev(SYNC_STATE);
return;
}
minor_fini_thread_created = TRUE;
} else {
vprint(INITFINI_MID, "restarting delay\n");
}
(void) mutex_unlock(&minor_fini_mutex);
}
/*
* after not receiving an event for minor_fini_timeout secs, we need
* to call the minor_fini routines
*/
/*ARGSUSED*/
static void
call_minor_fini_thread(void *arg)
{
int count = 0;
(void) mutex_lock(&minor_fini_mutex);
vprint(INITFINI_MID, "call_minor_fini_thread starting\n");
do {
minor_fini_delay_restart = FALSE;
(void) mutex_unlock(&minor_fini_mutex);
(void) sleep(minor_fini_timeout);
(void) mutex_lock(&minor_fini_mutex);
/*
* if minor_fini_delay_restart is still false then
* we can call minor fini routines.
* ensure that at least periodically minor_fini gets
* called satisfying link generators depending on fini
* being eventually called
*/
if ((count++ >= FORCE_CALL_MINOR_FINI) ||
(minor_fini_delay_restart == FALSE)) {
vprint(INITFINI_MID,
"call_minor_fini starting (%d)\n", count);
(void) mutex_unlock(&minor_fini_mutex);
lock_dev();
unlock_dev(SYNC_STATE);
vprint(INITFINI_MID, "call_minor_fini done\n");
/*
* hang around before exiting just in case
* minor_fini_delay_restart is set again
*/
(void) sleep(1);
count = 0;
(void) mutex_lock(&minor_fini_mutex);
}
} while (minor_fini_delay_restart);
minor_fini_thread_created = FALSE;
(void) mutex_unlock(&minor_fini_mutex);
vprint(INITFINI_MID, "call_minor_fini_thread exiting\n");
}
/*
* Attempt to initialize module, if a minor_init routine exists. Set
* the active flag if the routine exists and succeeds. If it doesn't
* exist, just set the active flag.
*/
static int
call_minor_init(module_t *module)
{
char *fcn = "call_minor_init: ";
if ((module->flags & MODULE_ACTIVE) == MODULE_ACTIVE) {
return (DEVFSADM_SUCCESS);
}
vprint(INITFINI_MID, "%smodule %s. current state: inactive\n",
fcn, module->name);
if (module->minor_init == NULL) {
module->flags |= MODULE_ACTIVE;
vprint(INITFINI_MID, "minor_init not defined\n");
return (DEVFSADM_SUCCESS);
}
if ((*(module->minor_init))() == DEVFSADM_FAILURE) {
err_print(FAILED_FOR_MODULE, MINOR_INIT, module->name);
return (DEVFSADM_FAILURE);
}
vprint(INITFINI_MID, "minor_init() returns DEVFSADM_SUCCESS. "
"new state: active\n");
module->flags |= MODULE_ACTIVE;
return (DEVFSADM_SUCCESS);
}
static int
i_mknod(char *path, int stype, int mode, dev_t dev, uid_t uid, gid_t gid)
{
struct stat sbuf;
assert((stype & (S_IFCHR|S_IFBLK)) != 0);
assert((mode & S_IFMT) == 0);
if (stat(path, &sbuf) == 0) {
/*
* the node already exists, check if it's device
* information is correct
*/
if (((sbuf.st_mode & S_IFMT) == stype) &&
(sbuf.st_rdev == dev)) {
/* the device node is correct, continue */
return (DEVFSADM_SUCCESS);
}
/*
* the device node already exists but has the wrong
* mode/dev_t value. we need to delete the current
* node and re-create it with the correct mode/dev_t
* value, but we also want to preserve the current
* owner and permission information.
*/
uid = sbuf.st_uid;
gid = sbuf.st_gid;
mode = sbuf.st_mode & ~S_IFMT;
s_unlink(path);
}
top:
if (mknod(path, stype | mode, dev) == -1) {
if (errno == ENOENT) {
/* dirpath to node doesn't exist, create it */
char *hide = strrchr(path, '/');
*hide = '\0';
s_mkdirp(path, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
*hide = '/';
goto top;
}
err_print(MKNOD_FAILED, path, strerror(errno));
return (DEVFSADM_FAILURE);
} else {
/*
* If we successfully made the node, then set its owner
* and group. Existing nodes will be unaffected.
*/
(void) chown(path, uid, gid);
}
return (DEVFSADM_SUCCESS);
}
/*ARGSUSED*/
int
devfsadm_mklink_zone(struct zone_devinfo *z, char *link, di_node_t node,
di_minor_t minor, int flags)
{
char path[PATH_MAX];
char phy_path[PATH_MAX];
char *dev_pathp;
char *acontents, *aminor = NULL;
mode_t mode;
uid_t uid;
gid_t gid;
dev_t dev;
struct zone_devtab out_match;
if (zonecfg_match_dev(z->zone_dochdl, link, &out_match) != Z_OK) {
return (DEVFSADM_FAILURE);
}
vprint(ZONE_MID, "zone device match: <device match=\"%s\"> "
"matches /dev/%s\n", out_match.zone_dev_match, link);
/*
* In daemon mode, zone_path will be non-empty. In non-daemon mode
* it will be empty since we've already stuck the zone into dev_dir,
* etc.
*/
(void) snprintf(path, sizeof (path), "%s/dev/%s", z->zone_path, link);
dev = di_minor_devt(minor);
/*
* If this is an alias node (i.e. a clone node), we have to figure
* out the minor name.
*/
if (di_minor_type(minor) == DDM_ALIAS) {
/* use /pseudo/clone@0:<driver> as the phys path */
(void) snprintf(phy_path, sizeof (phy_path),
"/pseudo/clone@0:%s",
di_driver_name(di_minor_devinfo(minor)));
aminor = di_minor_name(minor);
acontents = phy_path;
} else {
if ((dev_pathp = di_devfs_path(node)) == NULL) {
err_print(DI_DEVFS_PATH_FAILED, strerror(errno));
devfsadm_exit(1);
}
(void) snprintf(phy_path, sizeof (phy_path), "%s:%s",
dev_pathp, di_minor_name(minor));
di_devfs_path_free(dev_pathp);
acontents = phy_path;
}
getattr(acontents, aminor, di_minor_spectype(minor), dev,
&mode, &uid, &gid);
vprint(ZONE_MID, "zone getattr(%s, %s, %d, %lu, 0%lo, %lu, %lu)\n",
acontents, aminor ? aminor : "<NULL>", di_minor_spectype(minor),
dev, mode, uid, gid);
/* Create the node */
return (i_mknod(path, di_minor_spectype(minor), mode, dev, uid, gid));
}
/*
* Creates a symlink 'link' to the physical path of node:minor.
* Construct link contents, then call create_link_common().
*/
/*ARGSUSED*/
int
devfsadm_mklink_default(char *link, di_node_t node, di_minor_t minor, int flags)
{
char rcontents[PATH_MAX];
char devlink[PATH_MAX];
char phy_path[PATH_MAX];
char *acontents;
char *dev_path;
int numslashes;
int rv;
int i, link_exists;
int last_was_slash = FALSE;
/*
* try to use devices path
*/
if ((node == lnode) && (minor ==