| /* |
| * 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 = ⤷ |
| 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 == |