minix/commands/devmand/main.c
David van Moolenbroek fa901649a3 devmand: don't die so easily
Change-Id: I048a975fb35dadf07ebb5d067d76fb1d1892bda8
2014-07-28 17:05:13 +02:00

925 lines
23 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <errno.h>
#include <string.h>
#include <lib.h>
#include <sys/stat.h>
#include <dirent.h>
#include <assert.h>
#include <signal.h>
#include "usb_driver.h"
#include "proto.h"
#define SERVICE_BINARY "/bin/service"
#define DEVMAN_TYPE_NAME "dev_type"
#define PATH_LEN 256
#define INVAL_MAJOR -1
#define MAX_CONFIG_DIRS 4
static void main_loop();
static void handle_event();
static void cleanup();
static void parse_config();
static void display_usage();
static enum dev_type determine_type(char *path);
static int get_major();
static void create_pid_file();
static void put_major(int major);
static struct devmand_usb_driver* match_usb_driver(struct usb_device_id *id);
static struct devmand_driver_instance *find_instance(int dev_id);
#define dbg(fmt, ... ) \
if (args.verbose) \
printf("%8s:%4d: %13s()| "fmt"\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__ )
static LIST_HEAD(usb_driver_head, devmand_usb_driver) drivers =
LIST_HEAD_INITIALIZER(drivers);
static LIST_HEAD(usb_driver_inst_head, devmand_driver_instance) instances =
LIST_HEAD_INITIALIZER(instances);
static int _run = 1;
struct global_args {
char *path;
char *config_dirs[MAX_CONFIG_DIRS];
int config_dir_count ;
int major_offset;
int verbose;
int check_config;
};
enum dev_type {
DEV_TYPE_USB_DEVICE,
DEV_TYPE_USB_INTF,
DEV_TYPE_UNKOWN
};
extern FILE *yyin;
static struct global_args args = {
.path = NULL,
.config_dirs = {NULL,NULL,NULL,NULL},
.config_dir_count = 0,
.major_offset = 25,
.verbose = 0,
.check_config = 0};
static struct option options[] =
{
{"dir" , required_argument, NULL, 'd'},
{"path", required_argument, NULL, 'p'},
{"verbose", required_argument, NULL, 'v'},
{"check-config", no_argument, NULL, 'x'},
{0,0,0,0} /* terminating entry */
};
static char major_bitmap[16]; /* can store up to 128 major number states */
/*===========================================================================*
* run_upscript *
*===========================================================================*/
int run_upscript(struct devmand_driver_instance *inst)
{
char cmdl[1024];
cmdl[0] = 0;
int ret;
snprintf(cmdl, 1024, "%s up %s %d %d",
inst->drv->upscript, inst->label, inst->major, inst->dev_id);
dbg("Running Upscript: \"%s\"", cmdl);
ret = system(cmdl);
if (ret != 0) {
return EINVAL;
}
return 0;
}
/*===========================================================================*
* run_cleanscript *
*===========================================================================*/
int run_cleanscript(struct devmand_usb_driver *drv)
{
char cmdl[1024];
cmdl[0] = 0;
int ret;
snprintf(cmdl, 1024, "%s clean %s ",
drv->upscript, drv->devprefix);
dbg("Running Upscript: \"%s\"", cmdl);
ret = system(cmdl);
if (ret != 0) {
return EINVAL;
}
return 0;
}
/*===========================================================================*
* run_downscript *
*===========================================================================*/
int run_downscript(struct devmand_driver_instance *inst)
{
char cmdl[1024];
cmdl[0] = 0;
int ret;
snprintf(cmdl, 1024, "%s down %s %d",
inst->drv->downscript, inst->label, inst->major);
dbg("Running Upscript: \"%s\"", cmdl);
ret = system(cmdl);
if (ret != 0) {
return EINVAL;
}
return 0;
}
/*===========================================================================*
* stop_driver *
*===========================================================================*/
int stop_driver(struct devmand_driver_instance *inst)
{
char cmdl[1024];
cmdl[0] = 0;
int ret;
snprintf(cmdl, 1024, "%s down %s %d",
SERVICE_BINARY, inst->label, inst->dev_id);
dbg("executing service: \"%s\"", cmdl);
ret = system(cmdl);
if (ret != 0)
{
return EINVAL;
}
printf("Stopped driver %s with label %s for device %d.\n",
inst->drv->binary, inst->label, inst->dev_id);
return 0;
}
/*===========================================================================*
* start_driver *
*===========================================================================*/
int start_driver(struct devmand_driver_instance *inst)
{
char cmdl[1024];
cmdl[0] = 0;
int ret;
/* generate label */
ret = snprintf(inst->label, 32, "%s%d", inst->drv->devprefix,
inst->dev_id);
if (ret < 0 || ret > DEVMAND_DRIVER_LABEL_LEN) {
dbg("label too long");
return ENOMEM;
}
snprintf(cmdl, 1024, "%s up %s -major %d -devid %d -label %s",
SERVICE_BINARY, inst->drv->binary, inst->major, inst->dev_id,
inst->label);
dbg("executing service: \"%s\"", cmdl);
ret = system(cmdl);
if (ret != 0) {
return EINVAL;
}
printf("Started driver %s with label %s for device %d.\n",
inst->drv->binary, inst->label, inst->dev_id);
return 0;
}
/*===========================================================================*
* find_instance *
*===========================================================================*/
static struct devmand_driver_instance *
find_instance(int dev_id)
{
struct devmand_driver_instance *inst;
LIST_FOREACH(inst, &instances, list) {
if (inst->dev_id == dev_id) {
return inst;
}
}
return NULL;
}
/*===========================================================================*
* match_usb_driver *
*===========================================================================*/
static int
match_usb_id(struct devmand_usb_match_id *mid, struct usb_device_id *id)
{
int res = 1;
unsigned long match = mid->match_flags;
struct usb_device_id *_id = &mid->match_id;
if (match & USB_MATCH_ID_VENDOR)
if (id->idVendor != _id->idVendor) res = 0;
if (match & USB_MATCH_ID_PRODUCT)
if (id->idProduct != _id->idProduct) res = 0;
if (match & USB_MATCH_BCD_DEVICE)
if (id->bcdDevice != _id->bcdDevice) res = 0;
if (match & USB_MATCH_DEVICE_PROTOCOL)
if (id->bDeviceProtocol != _id->bDeviceProtocol) res = 0;
if (match & USB_MATCH_DEVICE_SUBCLASS)
if (id->bDeviceSubClass != _id->bDeviceSubClass) res = 0;
if (match & USB_MATCH_DEVICE_PROTOCOL)
if (id->bDeviceProtocol != _id->bDeviceProtocol) res = 0;
if (match & USB_MATCH_INTERFACE_CLASS)
if (id->bInterfaceClass != _id->bInterfaceClass) res = 0;
if (match & USB_MATCH_INTERFACE_SUBCLASS)
if (id->bInterfaceSubClass != _id->bInterfaceSubClass) res = 0;
if (match & USB_MATCH_INTERFACE_PROTOCOL)
if (id->bInterfaceProtocol != _id->bInterfaceProtocol) res = 0;
if (match == 0UL) {
res = 0;
}
return res;
}
/*===========================================================================*
* match_usb_driver *
*===========================================================================*/
static struct devmand_usb_driver*
match_usb_driver(struct usb_device_id *id)
{
struct devmand_usb_driver *driver;
struct devmand_usb_match_id *mid;
LIST_FOREACH(driver, &drivers, list) {
LIST_FOREACH(mid, &driver->ids, list) {
if (match_usb_id(mid, id)) {
return driver;
}
}
}
return NULL;
}
/*===========================================================================*
* add_usb_match_id *
*===========================================================================*/
struct devmand_usb_driver * add_usb_driver(char *name)
{
struct devmand_usb_driver *udrv = (struct devmand_usb_driver*)
malloc(sizeof(struct devmand_usb_driver));
LIST_INSERT_HEAD(&drivers, udrv, list);
LIST_INIT(&udrv->ids);
udrv->name = name;
return udrv;
}
/*===========================================================================*
* add_usb_match_id *
*===========================================================================*/
struct devmand_usb_match_id *
add_usb_match_id
(struct devmand_usb_driver *drv)
{
struct devmand_usb_match_id *id = (struct devmand_usb_match_id*)
malloc(sizeof(struct devmand_usb_match_id));
memset(id, 0, sizeof(struct devmand_usb_match_id));
LIST_INSERT_HEAD(&drv->ids, id, list);
return id;
}
/*===========================================================================*
* parse_config *
*===========================================================================*/
static void parse_config()
{
int i, status, error;
struct stat stats;
char * dirname;
DIR * dir;
struct dirent entry;
struct dirent *result;
char config_file[PATH_MAX];
dbg("Parsing configuration directories... ");
/* Next parse the configuration directories */
for(i=0; i < args.config_dir_count; i++){
dirname = args.config_dirs[i];
dbg("Parsing config dir %s ", dirname);
status = stat(dirname,&stats);
if (status == -1){
error = errno;
dbg("Failed to read directory '%s':%s (skipping) \n",
dirname,strerror(error));
continue;
}
if (!S_ISDIR(stats.st_mode)){
dbg("Parse configuration skipping %s "
"(not a directory) \n",dirname);
continue;
}
dir = opendir(dirname);
if (dir == NULL){
error = errno;
dbg("Parse configuration failed to read dir '%s'"
"(skipping) :%s\n",dirname, strerror(error));
continue;
}
while( (status = readdir_r(dir,&entry,&result)) == 0 ){
if (result == NULL){ /* last entry */
closedir(dir);
break;
}
/* concatenate dir and file name to open it */
snprintf(config_file,PATH_MAX, "%s/%s",
dirname,entry.d_name);
status = stat(config_file, &stats);
if (status == -1){
error = errno;
dbg("Parse configuration Failed to stat file "
"'%s': %s (skipping)\n", config_file,
strerror(error));
}
if (S_ISREG(stats.st_mode)){
dbg("Parsing file %s",config_file);
yyin = fopen(config_file, "r");
if (yyin < 0) {
dbg("Can not open config file:"
" %d.\n", errno);
}
yyparse();
dbg("Done.");
fclose(yyin);
}
}
}
dbg("Parsing configuration directories done... ");
}
/*===========================================================================*
* cleanup *
*===========================================================================*/
static void cleanup() {
struct devmand_driver_instance *inst;
/* destroy fifo */
dbg("cleaning up... ");
/* quit all running drivers */
LIST_FOREACH(inst, &instances, list) {
dbg("stopping driver %s", inst->label);
run_downscript (inst);
stop_driver(inst);
}
unlink("/var/run/devmand.pid");
}
static void sig_int(int sig) {
dbg("devman: Received SIGINT... cleaning up.");
_run = 0;
}
/*===========================================================================*
* create_pid_file *
*===========================================================================*/
void create_pid_file()
{
FILE *fd;
fd = fopen("/var/run/devmand.pid", "r");
if(fd) {
fprintf(stderr, "devmand: /var/run/devmand.pid exists... "
"another devmand running?\n");
fclose(fd);
exit(1);
} else {
fd = fopen("/var/run/devmand.pid","w");
fprintf(fd, "%d", getpid());
fclose(fd);
}
}
/*===========================================================================*
* main *
*===========================================================================*/
int main(int argc, char *argv[])
{
int opt, optindex;
struct devmand_usb_driver *driver;
/* get command line arguments */
while ((opt = getopt_long(argc, argv, "d:p:vxh?", options, &optindex))
!= -1) {
switch (opt) {
case 'd':/* config directory */
if (args.config_dir_count >= MAX_CONFIG_DIRS){
fprintf(stderr,"Parse arguments: Maximum"
" of %i configuration directories"
" reached skipping directory '%s'\n"
, MAX_CONFIG_DIRS, optarg);
break;
}
args.config_dirs[args.config_dir_count] = optarg;
args.config_dir_count++;
break;
case 'p': /* sysfs path */
args.path = optarg;
break;
case 'v': /* verbose */
args.verbose = 1;
break;
case 'x': /* check config */
args.check_config = 1;
break;
case 'h': /* help */
case '?': /* help */
default:
display_usage(argv[0]);
return 0;
}
}
/* is path set? */
if (args.path == NULL) {
args.path = "/sys/";
}
/* is the configuration directory set? */
if (args.config_dir_count == 0) {
dbg("Using default configuration directory");
args.config_dirs[0] = "/etc/devmand";
args.config_dir_count = 1;
}
/* If we only check the configuration run and exit imediately */
if (args.check_config == 1){
fprintf(stdout, "Only parsing configuration\n");
parse_config();
exit(0);
}
create_pid_file();
parse_config();
LIST_FOREACH(driver, &drivers, list) {
run_cleanscript(driver);
}
signal(SIGINT, sig_int);
main_loop();
cleanup();
return 0;
}
/*===========================================================================*
* determine_type *
*===========================================================================*/
static enum dev_type determine_type (char *path)
{
FILE * fd;
char *mypath;
char buf[256];
int res;
mypath = (char *) calloc(1, strlen(path)+strlen(DEVMAN_TYPE_NAME)+1);
if (mypath == NULL) {
fprintf(stderr, "ERROR: out of mem\n");
cleanup();
exit(1);
}
strcat(mypath, path);
strcat(mypath, DEVMAN_TYPE_NAME);
fd = fopen(mypath, "r");
free(mypath);
if (fd == NULL) {
fprintf(stderr, "WARN: could not open %s\n", mypath);
return DEV_TYPE_UNKOWN;
}
res = fscanf(fd , "%s\n", buf);
fclose(fd);
if (res != 1) {
fprintf(stderr, "WARN: could not parse %s\n", mypath);
return DEV_TYPE_UNKOWN;
}
if (strcmp(buf, "USB_DEV") == 0) {
return DEV_TYPE_USB_DEVICE;
} else if (strcmp(buf, "USB_INTF") == 0) {
return DEV_TYPE_USB_INTF;
}
return DEV_TYPE_UNKOWN;
}
/*===========================================================================*
* read_hex_uint *
*===========================================================================*/
static int read_hex_uint(char *base_path, char *name, unsigned int* val )
{
char my_path[PATH_LEN];
FILE *fd;
memset(my_path,0,PATH_LEN);
int ret = 0;
strcat(my_path, base_path);
strcat(my_path, name);
fd = fopen(my_path, "r");
if (fd == NULL) {
fprintf(stderr, "WARN: could not open %s\n", my_path);
return EEXIST;
} else if (fscanf(fd, "0x%x\n", val ) != 1) {
fprintf(stderr, "WARN: could not parse %s\n", my_path);
ret = EINVAL;
}
fclose(fd);
return ret;
}
/*===========================================================================*
* get_major *
*===========================================================================*/
static int get_major() {
int i, ret = args.major_offset;
for (i=0; i < 16; i++) {
int j;
for (j = 0; j < 8; j++ ) {
if ((major_bitmap[i] & (1 << j))) {
major_bitmap[i] &= !(1 << j);
return ret;
}
ret++;
}
}
return INVAL_MAJOR;
}
/*===========================================================================*
* put_major *
*===========================================================================*/
static void put_major(int major) {
int i;
major -= args.major_offset;
assert(major >= 0);
for (i=0; i < 16; i++) {
int j;
for (j = 0; j < 8; j++ ) {
if (major==0) {
assert(!(major_bitmap[i] & (1 <<j)));
major_bitmap[i] |= (1 << j);
return;
}
major--;
}
}
}
/*===========================================================================*
* generate_usb_device_id *
*===========================================================================*/
static struct usb_device_id *
generate_usb_device_id(char * path, int is_interface)
{
struct usb_device_id *ret;
int res;
unsigned int val;
ret = (struct usb_device_id *)
calloc(1,sizeof (struct usb_device_id));
if (is_interface) {
res = read_hex_uint(path, "../idVendor", &val);
if (res) goto err;
ret->idVendor = val;
res = read_hex_uint(path, "../idProduct", &val);
if (res) goto err;
ret->idProduct = val;
#if 0
res = read_hex_uint(path, "../bcdDevice", &val);
if (res) goto err;
ret->bcdDevice = val;
#endif
res = read_hex_uint(path, "../bDeviceClass", &val);
if (res) goto err;
ret->bDeviceClass = val;
res = read_hex_uint(path, "../bDeviceSubClass", &val);
if (res) goto err;
ret->bDeviceSubClass = val;
res = read_hex_uint(path, "../bDeviceProtocol", &val);
if (res) goto err;
ret->bDeviceProtocol = val;
res = read_hex_uint(path, "/bInterfaceClass", &val);
if (res) goto err;
ret->bInterfaceClass = val;
res = read_hex_uint(path, "/bInterfaceSubClass", &val);
if (res) goto err;
ret->bInterfaceSubClass = val;
res = read_hex_uint(path, "/bInterfaceProtocol", &val);
if (res) goto err;
ret->bInterfaceProtocol = val;
}
return ret;
err:
free(ret);
return NULL;
}
/*===========================================================================*
* usb_intf_add_even *
*===========================================================================*/
static void usb_intf_add_event(char *path, int dev_id)
{
struct usb_device_id *id;
struct devmand_usb_driver *drv;
struct devmand_driver_instance *drv_inst;
int major, ret;
/* generate usb_match_id */
id = generate_usb_device_id(path,TRUE);
if (id == NULL) {
fprintf(stderr, "WARN: could not create usb_device id...\n"
" ommiting event\n");
free(id);
return;
}
/* find suitable driver */
drv = match_usb_driver(id);
free (id);
if (drv == NULL) {
dbg("INFO: could not find a suitable driver for %s", path);
return;
}
/* create instance */
drv_inst = (struct devmand_driver_instance *)
calloc(1,sizeof(struct devmand_driver_instance));
if (drv_inst == NULL) {
fprintf(stderr, "ERROR: out of memory");
return; /* maybe better quit here. */
}
/* allocate inode number, if device files needed */
major = get_major();
if (major == INVAL_MAJOR) {
fprintf(stderr, "WARN: ran out of major numbers\n"
" cannot start driver %s for %s\n",
drv->name, path);
return;
}
drv_inst->major = major;
drv_inst->drv = drv;
drv_inst->dev_id = dev_id;
/* start driver (invoke service) */
start_driver(drv_inst);
/*
* run the up action
*
* An up action can be any executable. Before running it devmand
* will set certain environment variables so the script can configure
* the device (or generate device files, etc). See up_action() for that.
*/
if (drv->upscript) {
ret = run_upscript(drv_inst);
if (ret) {
stop_driver(drv_inst);
fprintf(stderr, "devmand: warning, could not run up_action\n");
free(drv_inst);
return;
}
}
LIST_INSERT_HEAD(&instances,drv_inst,list);
}
/*===========================================================================*
* usb_intf_remove_event *
*===========================================================================*/
static void usb_intf_remove_event(char *path, int dev_id)
{
struct devmand_driver_instance *inst;
struct devmand_usb_driver *drv;
int ret;
/* find the driver instance */
inst = find_instance(dev_id);
if (inst == NULL) {
dbg("No driver running for id: %d", dev_id);
return;
}
drv = inst->drv;
/* run the down script */
if (drv->downscript) {
ret = run_downscript(inst);
if (ret) {
fprintf(stderr, "WARN: error running up_action");
}
}
/* stop the driver */
stop_driver(inst);
/* free major */
put_major(inst->major);
/* free instance */
LIST_REMOVE(inst,list);
free(inst);
}
/*===========================================================================*
* handle_event *
*===========================================================================*/
static void handle_event(char *event)
{
enum dev_type type;
char path[PATH_LEN];
char tmp_path[PATH_LEN];
int dev_id, res;
path[0]=0;
if (strncmp("ADD ", event, 4) == 0) {
/* read data from event */
res = sscanf(event, "ADD %s 0x%x", tmp_path, &dev_id);
if (res != 2) {
fprintf(stderr, "WARN: could not parse event: %s", event);
fprintf(stderr, "WARN: omitting event: %s", event);
}
strcpy(path, args.path);
strcat(path, tmp_path);
/* what kind of device is added? */
type = determine_type(path);
switch (type) {
case DEV_TYPE_USB_DEVICE:
dbg("USB device added: ommited....");
/* ommit usb devices for now */
break;
case DEV_TYPE_USB_INTF:
dbg("USB interface added: (%s, devid: = %d)",path, dev_id);
usb_intf_add_event(path, dev_id);
return;
default:
dbg("default");
fprintf(stderr, "WARN: ommiting event\n");
}
} else if (strncmp("REMOVE ", event, 7) == 0) {
/* read data from event */
res = sscanf(event,"REMOVE %s 0x%x", tmp_path, &dev_id);
if (res != 2) {
fprintf(stderr, "WARN: could not parse event: %s", event);
fprintf(stderr, "WARN: omitting event: %s", event);
}
usb_intf_remove_event(path, dev_id);
#if 0
strcpy(path, args.path);
strcat(path, tmp_path);
/* what kind of device is added? */
type = determine_type(path);
switch (type) {
case DEV_TYPE_USB_DEVICE:
/* ommit usb devices for now */
break;
case DEV_TYPE_USB_INTF:
usb_intf_remove_event(path, dev_id);
return;
default:
fprintf(stderr, "WARN: ommiting event\n");
}
#endif
}
}
/*===========================================================================*
* main_loop *
*===========================================================================*/
static void main_loop()
{
char ev_path[128];
char buf[256];
int len;
FILE* fd;
len = strlen(args.path);
/* init major numbers */
memset(&major_bitmap, 0xff, 16);
if (len > 128 - 7 /*len of "events" */) {
fprintf(stderr, "pathname to long\n");
cleanup();
exit(1);
}
strcpy(ev_path, args.path);
strcat(ev_path, "events");
while (_run) {
char *res;
fd = fopen(ev_path, "r");
if (fd == NULL) {
/*
* ENFILE is a temporary failure, often caused by
* running the test set. Don't die from that..
*/
if (errno == ENFILE) {
usleep(50000);
continue;
}
fprintf(stderr,"devmand error: could not open event "
"file %s bailing out\n", ev_path);
cleanup();
exit(1);
}
res = fgets(buf, 256, fd);
fclose(fd);
if (res == NULL) {
usleep(50000);
continue;
}
dbg("handle_event: %s", buf);
handle_event(buf);
}
}
/*===========================================================================*
* display_usage *
*===========================================================================*/
static void display_usage(const char *name)
{
printf("Usage: %s [{-p|--pathname} PATH_TO_SYS}"
" [{-d|--config-dir} CONFIG_DIR] [-v|--verbose]"
" [[x||--check-config]\n", name);
}