add_drv.c revision 7e48531733326c7034772ff72376656c5b1183ca
/*
* 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
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <stdio.h>
#include <stdlib.h>
#include <libelf.h>
#include <wait.h>
#include <unistd.h>
#include <libintl.h>
#include <sys/systeminfo.h>
#include <string.h>
#include <limits.h>
#include <locale.h>
#include <ftw.h>
#include <libdevinfo.h>
#include <sys/sysmacros.h>
#include <fcntl.h>
#include "addrem.h"
#include "errmsg.h"
#include "plcysubr.h"
/*
* globals needed for libdevinfo - there is no way to pass
* private data to the find routine.
*/
struct dev_list {
int clone;
char *dev_name;
char *driver_name;
};
static int kelf_type = ELFCLASSNONE;
static char *new_drv;
static int module_not_found(char *, char *, int, char **, int *);
static void usage();
static int update_minor_perm(char *, char *);
static int devfs_update_minor_perm(char *, char *, char *);
static int update_driver_classes(char *, char *);
static int drv_name_conflict(di_node_t);
static int drv_name_match(char *, int, char *, char *);
static void print_drv_conflict_info(int);
static void check_dev_dir(int);
static void free_conflict_list(struct dev_list *);
static int elf_type(char *, char **, int *);
static int correct_location(char *, char **, int *);
static int isaspec_drvmod_discovery();
static void remove_slashes(char *);
static int update_extra_privs(char *, char *privlist);
static int ignore_root_basedir();
int
{
int opt;
int driver_name_size = sizeof (driver_name);
char path_driver_name[MAXPATHLEN];
int path_driver_name_size = sizeof (path_driver_name);
int noload_flag = 0;
int verbose_flag = 0;
int force_flag = 0;
int i_flag = 0;
int c_flag = 0;
int m_flag = 0;
int cleanup_flag = 0;
int server = 0;
int is_unique;
char *slash;
int conflict;
char *drvelf_desc = NULL;
int drvelf_type = ELFCLASSNONE;
#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
#endif
(void) textdomain(TEXT_DOMAIN);
/* must be run by root */
if (geteuid() != 0) {
exit(1);
}
switch (opt) {
case 'm' :
m_flag = 1;
break;
case 'f':
force_flag++;
break;
case 'v':
verbose_flag++;
break;
case 'n':
noload_flag++;
break;
case 'i' :
i_flag = 1;
aliases);
exit(1);
}
break;
case 'b' :
server = 1;
ignore_root_basedir()) {
server = 0;
}
break;
case 'c':
c_flag = 1;
break;
case 'p':
break;
case 'P':
break;
case '?' :
default:
usage();
exit(1);
}
}
exit(1);
}
/*
* check for extra args
*/
usage();
exit(1);
}
} else {
usage();
exit(1);
}
/*
* Fail if add_drv was invoked with a pathname prepended to the
* driver_name argument.
*
* Check driver_name for any '/'s. If found, we assume that caller
* is trying to specify a pathname.
*/
if (slash) {
/* extract module name out of path */
++slash);
exit(1);
}
}
/* set up add_drv filenames */
exit(1);
}
enter_lock();
err_exit();
err_exit();
/*
* check validity of options
*/
if (m_flag) {
usage();
err_exit();
}
}
if (i_flag) {
err_exit();
}
/* update kernel unless -b or -n */
if (noload_flag == 0 && server == 0 &&
err_exit();
err_exit();
}
err_exit();
if (is_unique == NOT_UNIQUE) {
err_exit();
}
if (noload_flag == 0 && server == 0) {
err_exit();
}
ERROR) {
err_exit();
}
/*
* If the driver location is incorrect but the kernel and driver
* are of the same ISA, suggest a fix. If the driver location
* is incorrect and the ISA's mismatch, notify the user that
* this driver can not be loaded on this kernel. In both cases,
* do not attempt to load the driver module.
*/
(&drvelf_type)) == ERROR) {
noload_flag = 1;
if (kelf_type == drvelf_type) {
} else {
}
/*
* The driver location is correct. Verify that the kernel ISA
* and driver ISA match. If they do not match, produce an error
* message and do not attempt to load the module.
*/
} else if (kelf_type != drvelf_type) {
noload_flag = 1;
}
/*
* Check for a more specific driver conflict - see
* PSARC/1995/239
* Note that drv_name_conflict() can return -1 for error
* or 1 for a conflict. Since the default is to fail unless
* the -f flag is specified, we don't bother to differentiate.
*/
== DI_NODE_NIL) {
conflict = -1;
} else {
}
if (conflict) {
/*
* if the force flag is not set, we fail here
*/
if (!force_flag) {
"another driver.\n");
if (verbose_flag)
err_exit();
}
/*
* The force flag was specified so we print warnings
* and install the driver anyways
*/
if (verbose_flag)
}
}
err_exit();
}
if (m_flag) {
err_exit();
}
}
if (i_flag) {
err_exit();
}
}
if (c_flag) {
err_exit();
}
}
err_exit();
}
}
== ERROR) {
err_exit();
}
}
if (noload_flag || server) {
} else {
/*
* paranoia - if we crash whilst configuring the driver
* this might avert possible file corruption.
*/
sync();
err_exit();
}
if (m_flag) {
err_exit();
}
}
if (!noload_flag)
else
}
exit_unlock();
if (verbose_flag) {
}
return (NOERR);
}
/*
* Searches for the driver module along the module path (returned
* from modctl) and returns a string (in drv_path) representing the path
* where drv_name was found. ERROR is returned if function is unable
* to locate drv_name.
*/
int
char **drvelf_desc, int *drvelf_type_ptr)
{
char data [MAXMODPATHS];
char pathsave [MAXMODPATHS];
char foundpath[MAXPATHLEN];
return (ERROR);
}
if (isaspec_drvmod_discovery() == ERROR)
err_exit();
drvelf_type_ptr) == ERROR) {
drv_name);
err_exit();
}
>= drv_path_size) {
return (ERROR);
}
return (NOERR);
}
}
}
return (ERROR);
}
static void
usage()
{
}
static int
char *driver_name,
char *classes)
{
/* make call to update the classes file */
' ', "\t", 0));
}
static int
char *driver_name,
char *perm_list)
{
',', ":", 0));
}
/*
* Complete the minor perm update by communicating the minor perm
* data to the kernel. This information is used by devfs to ensure
* that devices always have the correct permissions when attached.
* The minor perm file must be updated and the driver configured
* in the system for this step to complete correctly.
*/
static int
char *basedir,
char *driver_name,
char *perm_list)
{
int rval = 0;
log_minorperm_error) != 0) {
}
}
return (rval);
}
static int
char *driver_name,
char *privlist)
{
',', ":", 0));
}
/*
* Check to see if the driver we are adding is a more specific
* driver for a device already attached to a less specific driver.
* In other words, see if this driver comes earlier on the compatible
* list of a device already attached to another driver.
* If so, the new node will not be created (since the device is
* already attached) but when the system reboots, it will attach to
* the new driver but not have a node - we need to warn the user
* if this is the case.
*/
static int
{
/*
* walk the device tree checking each node
*/
return (-1);
}
if (conflict_lst == NULL)
/* no conflicts found */
return (0);
else
/* conflicts! */
return (1);
}
/*
* called via di_walk_node().
* called for each node in the device tree. We skip nodes that:
* 1. are not hw nodes (since they cannot have generic names)
* 2. that do not have a compatible property
* 3. whose node name = binding name.
* 4. nexus nodes - the name of a generic nexus node would
* not be affected by a driver change.
* Otherwise, we parse the compatible property, if we find a
* match with the new driver before we find a match with the
* current driver, then we have a conflict and we save the
* node away.
*/
/*ARGSUSED*/
static int
{
char strbuf[MAXPATHLEN];
int n_names;
/*
* if there is no compatible property, we don't
* have to worry about any conflicts.
*/
return (DI_WALK_CONTINUE);
/*
* if the binding name and the node name match, then
* either no driver existed that could be bound to this node,
* or the driver name is the same as the node name.
*/
return (DI_WALK_CONTINUE);
/*
* we can skip nexus drivers since they do not
* /devices name and therefore won't change.
*/
return (DI_WALK_CONTINUE);
/*
* check for conflicts
* If we do find that the new driver is a more specific driver
* than the driver already attached to the device, we'll save
* away the node name for processing later.
*/
sizeof (struct dev_list));
err_exit();
}
/* save the /devices name */
err_exit();
}
/* save the driver name */
== NULL) {
err_exit();
}
/* check to see if this is a clone device */
/* add it to the list */
}
return (DI_WALK_CONTINUE);
}
static int
{
return (1);
}
return (0);
}
/*
* check to see if the new_name shows up on the compat list before
* the cur_name (driver currently attached to the device).
*/
static int
{
int i, ret = 0;
return (0);
/* parse the coompatible list */
for (i = 0; i < n_names; i++) {
ret = 1;
break;
}
break;
}
}
return (ret);
}
/*
* A more specific driver is being added for a device already attached
* to a less specific driver. Print out a general warning and if
* the force flag was passed in, give the user a hint as to what
* nodes may be affected in /devices and /dev
*/
static void
{
if (conflict_lst == NULL)
return;
if (force) {
"\nA reconfiguration boot must be performed to "
"complete the\n");
}
if (force) {
"\nThe following entries in /devices will be "
"affected:\n\n");
} else {
"\nDriver installation failed because the following\n");
"entries in /devices would be affected:\n\n");
}
ptr = conflict_lst;
else
}
}
/*
* use nftw to walk through /dev looking for links that match
* an entry in the conflict list.
*/
static void
check_dev_dir(int force)
{
int ft_depth = 15;
if (force) {
"be affected:\n\n");
} else {
"be affected:\n\n");
}
}
/*
* checks a /dev link to see if it matches any of the conlficting
* /devices nodes in conflict_lst.
*/
/*ARGSUSED1*/
static int
{
char linkbuf[MAXPATHLEN];
return (0);
ptr = conflict_lst;
}
return (0);
}
static void
{
/* free up any dev_list structs we allocated. */
}
}
int
{
int fd;
char *ident;
return (ERROR);
}
elf_errmsg(-1));
return (ERROR);
}
return (ERROR);
}
ident = elf_getident(elf, 0);
if (ident[EI_CLASS] == ELFCLASS32) {
*elfdesc = "32";
} else if (ident[EI_CLASS] == ELFCLASS64) {
*elfdesc = "64";
} else {
*elfdesc = "none";
}
return (NOERR);
}
int
{
char copy_drv_path[MAXPATHLEN];
char *token = copy_drv_path;
err_exit();
}
if (*drvelf_type_ptr == ELFCLASS64)
return (NOERR);
*drvelf_desc, drv_path);
return (ERROR);
} else {
if (*drvelf_type_ptr == ELFCLASS32)
return (NOERR);
*drvelf_desc, drv_path);
return (ERROR);
}
} else {
}
}
return (ERROR);
}
/*
* Creates a two-element linked list of isa-specific subdirectories to
* search for each driver, which is is used by the function
* module_not_found() to convert the isa-independent modpath into an
* isa-specific path . The list is ordered depending on the machine
* architecture and instruction set architecture, corresponding to the
* order in which module_not_found() will search for the driver. This
* routine relies on an architecture not having more than two
*/
int
{
return (ERROR);
}
return (ERROR);
}
return (ERROR);
}
if (kelf_type == ELFCLASS64) {
} else {
}
return (NOERR);
} else {
return (ERROR);
}
}
void
remove_slashes(char *path)
{
char *remain_str;
int pathlen;
remain_str = ++slash;
while (*remain_str == '/')
++remain_str;
if (slash != remain_str)
}
}
/*
* This is for ITU floppies to add packages to the miniroot
*/
static int
ignore_root_basedir(void)
{
}