/*======================================================================

    PCMCIA Card Manager

    Written by David Hinds, dhinds@allegro.stanford.edu
    
======================================================================*/

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <syslog.h>
#include <getopt.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <sys/kd.h>
#include <sys/file.h>

#include "version.h"
#include "cs_types.h"
#include "cs.h"
#include "cistpl.h"
#include "ds.h"

#include "cardmgr.h"

static const char *version =
    "cardmgr.c 1.55 1995/05/15 01:48:24 (David Hinds)";

/*====================================================================*/

typedef struct socket_info_t {
    int fd;
    int state;
    card_info_t *card;
    bind_info_t bind[MAX_FUNCTIONS];
    char *mtd[2*CISTPL_MAX_DEVICES];
} socket_info_t;

#define SOCKET_PRESENT	0x01
#define SOCKET_READY	0x02
#define SOCKET_BOUND	0x04

/* Linked list of resource adjustments */
struct adjust_list_t *root_adjust = NULL;

/* Linked list of device definitions */
struct device_info_t *root_device = NULL;

/* Special pointer to "anonymous" card definition */
struct card_info_t *blank_card = NULL;

/* Linked list of card definitions */
struct card_info_t *root_card = NULL;

/* Linked list of function definitions */
struct card_info_t *root_func = NULL;

/* Linked list of MTD definitions */
struct mtd_ident_t *root_mtd = NULL;

/* Default MTD */
struct mtd_ident_t *default_mtd = NULL;

static int sockets;
static struct socket_info_t socket[MAX_SOCKS];

/* Default path for config file */
#ifdef ETC
static char *config = ETC "/config";
#else
static char *config = "/etc/pcmcia/config";
#endif

/* Default path for pid file */
static char *pidfile = "/var/run/cardmgr.pid";

/* Default path for finding modules */
static char *modpath = NULL;

/* Default path for socket info table */
static char *stabfile = "/etc/stab";

/* If set, don't generate beeps when cards are inserted */
static int be_quiet = 0;

/* most recent signal */
static int caught_signal = 0;

/*====================================================================*/

int lookup_dev(char *name)
{
    FILE *f;
    int n;
    char s[32], t[32];
    
    f = fopen("/proc/devices", "r");
    if (f == NULL)
	return -1;
    while (fgets(s, 32, f) != NULL) {
	if (sscanf(s, "%d %s", &n, t) == 2)
	    if (strcmp(name, t) == 0)
		break;
    }
    fclose(f);
    if (strcmp(name, t) == 0)
	return n;
    else
	return -1;
} /* lookup_dev */

/*====================================================================*/

int open_dev(dev_t dev)
{
    static char *fn;
    int fd;

    if ((fn = tmpnam(NULL)) == NULL)
	return -1;
    if (mknod(fn, (S_IFCHR|S_IREAD|S_IWRITE), dev) != 0)
	return -1;
    fd = open(fn, O_RDWR);
    unlink(fn);
    return fd;
} /* open_dev */

/*====================================================================*/

#define BEEP_TIME 150
#define BEEP_OK   1000
#define BEEP_WARN 2000
#define BEEP_ERR  4000

void beep(unsigned int ms, unsigned int freq)
{
    int fd, arg;

    if (be_quiet)
	return;
    fd = open("/dev/console", O_RDWR);
    if (fd < 0)
	return;
    arg = (ms << 16) | freq;
    ioctl(fd, KDMKTONE, arg);
    close(fd);
    usleep(ms*1000);
}

/*====================================================================*/

void write_stab(void)
{
    int i, j;
    FILE *f;
    socket_info_t *s;

    f = fopen(stabfile, "w");
    if (f == NULL) {
	syslog(LOG_INFO, "fopen(stabfile) failed: %m");
	return;
    }
    if (flock(fileno(f), LOCK_EX) != 0) {
	syslog(LOG_INFO, "flock(stabfile) failed: %m");
	return;
    }
    for (i = 0; i < sockets; i++) {
	s = &socket[i];
	if (s->state & SOCKET_PRESENT) {
	    if (s->card) {
		fprintf(f, "Socket %d: %s\n", i, s->card->name);
		for (j = 0; j < s->card->functions; j++)
		    fprintf(f, "%2d\t%s\t%s\t%d\t%d\n",
			    i, s->bind[j].dev_info, s->bind[j].name,
			    s->bind[j].major, s->bind[j].minor);
	    }
	    else
		fprintf(f, "Socket %d: unsupported card\n", i);
	}
	else
	    fprintf(f, "Socket %d: empty\n", i);
    }
    fflush(f);
    flock(fileno(f), LOCK_UN);
    fclose(f);
} /* write_stab */

/*====================================================================*/

static int get_tuple(int ns, cisdata_t code, ds_ioctl_arg_t *arg)
{
    socket_info_t *s = &socket[ns];
    
    arg->tuple.DesiredTuple = code;
    arg->tuple.Attributes = 0;
    if (ioctl(s->fd, DS_GET_FIRST_TUPLE, arg) != 0)
	return -1;
    arg->tuple.TupleOffset = 0;
    if (ioctl(s->fd, DS_GET_TUPLE_DATA, arg) != 0) {
	syslog(LOG_INFO, "error reading CIS data on socket %d: %m", ns);
	return -1;
    }
    if (ioctl(s->fd, DS_PARSE_TUPLE, arg) != 0) {
	syslog(LOG_INFO, "error parsing CIS on socket %d: %m", ns);
	return -1;
    }
    return 0;
}

static card_info_t *lookup_card(int ns)
{
    socket_info_t *s = &socket[ns];
    card_info_t *card;
    ds_ioctl_arg_t arg;
    cistpl_vers_1_t *vers = NULL;
    int i, ret, match;
    int has_cis = 0;

    /* Do we have a CIS structure? */
    ret = ioctl(s->fd, DS_VALIDATE_CIS, &arg);
    has_cis = ((ret == 0) && (arg.cisinfo.Chains > 0));
    
    /* Try to read VERS_1 tuple */
    if (has_cis) {
	if (get_tuple(ns, CISTPL_VERS_1, &arg) == 0)
	    vers = &arg.tuple_parse.parse.version_1;

	match = 0;
	for (card = root_card; card; card = card->next) {
	    switch (card->ident_type) {
		
	    case VERS_1_IDENT:
		if (vers == NULL)
		    break;
		for (i = 0; i < card->vers.ns; i++) {
		    if (strcmp(card->vers.pi[i], "*") == 0)
			continue;
		    if (i >= vers->ns)
			break;
		    if (strcmp(card->vers.pi[i],
			       vers->str+vers->ofs[i]) != 0)
			break;
		}
		if (i < card->vers.ns)
		    break;
		match = 1;
		break;
		
	    case TUPLE_IDENT:
		arg.tuple.DesiredTuple = card->tuple.code;
		arg.tuple.Attributes = 0;
		ret = ioctl(s->fd, DS_GET_FIRST_TUPLE, &arg);
		if (ret != 0) break;
		arg.tuple.TupleOffset = card->tuple.ofs;
		ret = ioctl(s->fd, DS_GET_TUPLE_DATA, &arg);
		if (ret != 0) break;
		if (strncmp(arg.tuple_parse.data, card->tuple.info,
			    strlen(card->tuple.info)) != 0)
		    break;
		match = 1;
		break;

	    default:
		/* Skip */
		break;
	    }
	    if (match) break;
	}
	if (match) {
	    syslog(LOG_INFO, "socket %d: %s", ns, card->name);
	    beep(BEEP_TIME, BEEP_OK);
	    return card;
	}
    }

    /* Try for a FUNCID match */
    if (get_tuple(ns, CISTPL_FUNCID, &arg) == 0) {
	for (card = root_func; card; card = card->next)
	    if (card->func.funcid == arg.tuple_parse.parse.funcid.func)
		break;
	if (card) {
	    syslog(LOG_INFO, "socket %d: %s", ns, card->name);
	    beep(BEEP_TIME, BEEP_OK);
	    return card;
	}
    }
    
    if (vers || (!blank_card)) {
	syslog(LOG_INFO, "unsupported card in socket %d", ns);
	beep(BEEP_TIME, BEEP_ERR);
	if (vers) {
	    char v[256] = "";
	    for (i = 0; i < vers->ns; i++)
		sprintf(v+strlen(v), "%s\"%s\"",
			(i>0) ? ", " : "", vers->str+vers->ofs[i]);
	    syslog(LOG_INFO, "version info: %s", v);
	}
	else
	    syslog(LOG_INFO, "no version info");
	return NULL;
    }
    else {
	card = blank_card;
	syslog(LOG_INFO, "socket %d: %s", ns, card->name);
	beep(BEEP_TIME, BEEP_WARN);
	return card;
    }
} /* lookup_card */

/*====================================================================*/

static void load_config(void)
{
    FILE *f;
    f = fopen(config, "r");
    if (f == NULL) {
	syslog(LOG_INFO, "could not open %s: %m", config);
	exit(EXIT_FAILURE);
    }
    parse_configfile(f);
    fclose(f);
} /* load_config */

/*====================================================================*/

static void free_card(card_info_t *card)
{
    int i;
    free(card->name);
    switch(card->ident_type) {
    case VERS_1_IDENT:
	for (i = 0; i < card->vers.ns; i++)
	    free(card->vers.pi[i]);
	break;
    case TUPLE_IDENT:
	free(card->tuple.info);
	break;
    default:
	break;
    }
    free(card);
}

static void free_config(void)
{
    while (root_adjust != NULL) {
	adjust_list_t *adj = root_adjust;
	root_adjust = root_adjust->next;
	free(adj);
    }
    
    while (root_device != NULL) {
	device_info_t *dev = root_device;
	int i;
	root_device = root_device->next;
	for (i = 0; i < dev->modules; i++) {
	    free(dev->module[i]);
	    if (dev->opts[i]) free(dev->opts[i]);
	}
	if (dev->start_cmd) free(dev->start_cmd);
	if (dev->stop_cmd) free(dev->stop_cmd);
	free(dev);
    }

    while (root_card != NULL) {
	card_info_t *card = root_card;
	root_card = root_card->next;
	free_card(card);
    }
    
    while (root_func != NULL) {
	card_info_t *card = root_func;
	root_func = root_func->next;
	free_card(card);
    }
    blank_card = NULL;
    
    while (root_mtd != NULL) {
	mtd_ident_t *mtd = root_mtd;
	root_mtd = root_mtd->next;
	free(mtd->name);
	free(mtd->module);
	free(mtd);
    }
    default_mtd = NULL;
} /* free_config */

/*====================================================================*/

static int execute(char *msg, char *cmd)
{
    int ret;
    syslog(LOG_INFO, "executing: '%s'", cmd);
    ret = system(cmd);
    if (WIFEXITED(ret)) {
	if (WEXITSTATUS(ret))
	    syslog(LOG_INFO, "%s exited with status %d",
		   msg, WEXITSTATUS(ret));
	return WEXITSTATUS(ret);
    }
    else
	syslog(LOG_INFO, "%s exited on signal %d",
	       msg, WTERMSIG(ret));
    return -1;
} /* execute */

/*====================================================================*/

static int execute_on_dev(char *msg, char *fmt,
			  int sn, bind_info_t *bind)
{
    char cmd[1024], *s;

    s = cmd;
    while (*fmt != '\0') {
	if ((fmt[0] == '%') && (fmt[1] != '\0') && (fmt[2] == '%')) {
	    fmt++;
	    switch (*fmt) {
	    case 'c':
		strcpy(s, socket[sn].card->name);
		break;
	    case 'd':
		strcpy(s, bind->name);
		break;
	    case 'm':
		sprintf(s, "%d", bind->major);
		break;
	    case 'n':
		sprintf(s, "%d", bind->minor);
		break;
	    case 's':
		sprintf(s, "%d", sn);
		break;
	    default:
		*s = '\0';
	    }
	    s += strlen(s);
	    fmt += 2;
	}
	else
	    *s++ = *fmt++;
    }
    *s = '\0';

    return execute(msg, cmd);

} /* execute_on_dev */

/*====================================================================*/

typedef struct module_list_t {
    char *mod;
    int usage;
    struct module_list_t *next;
} module_list_t;

static module_list_t *module_list = NULL;

static int install_module(char *mod, char *opts)
{
    char cmd[128];
    module_list_t *ml;
    
    for (ml = module_list; ml != NULL; ml = ml->next)
	if (strcmp(mod, ml->mod) == 0) break;
    if (ml == NULL) {
	ml = (module_list_t *)malloc(sizeof(struct module_list_t));
	ml->mod = mod;
	ml->usage = 0;
	ml->next = module_list;
	module_list = ml;
    }
    ml->usage++;
    if (ml->usage == 1) {
	if (strchr(mod, '/') != NULL)
	    sprintf(cmd, "/sbin/insmod %s/%s.o", modpath, mod);
	else
	    sprintf(cmd, "/sbin/insmod %s/pcmcia/%s.o", modpath, mod);
	if (opts) {
	    strcat(cmd, " ");
	    strcat(cmd, opts);
	}
	return execute("insmod", cmd);
    }
    else
	return 0;
}

static void remove_module(char *mod)
{
    char *s, cmd[128];
    module_list_t *ml;

    for (ml = module_list; ml != NULL; ml = ml->next)
	if (strcmp(mod, ml->mod) == 0) break;
    if (ml != NULL) {
	ml->usage--;
	if (ml->usage == 0) {
	    /* Strip off leading path names */
	    s = strrchr(mod, '/');
	    if (s == NULL)
		s = mod;
	    else
		s++;
	    sprintf(cmd, "/sbin/rmmod %s", s);
	    execute("rmmod", cmd);
	}
    }
}

/*====================================================================*/

static mtd_ident_t *lookup_mtd(region_info_t *region)
{
    mtd_ident_t *mtd;
    int match = 0;
    
    for (mtd = root_mtd; mtd; mtd = mtd->next) {
	switch (mtd->mtd_type) {
	case JEDEC_MTD:
	    if ((mtd->jedec_mfr == region->JedecMfr) &&
		(mtd->jedec_info == region->JedecInfo)) {
		match = 1;
		break;
	    }
	case DTYPE_MTD:
	    break;
	default:
	    break;
	}
	if (match) break;
    }
    if (mtd)
	return mtd;
    else
	return default_mtd;
} /* lookup_mtd */

/*====================================================================*/

static void bind_mtd(int sn)
{
    socket_info_t *s = &socket[sn];
    region_info_t region;
    bind_info_t bind;
    mtd_info_t mtd_info;
    mtd_ident_t *mtd;
    int i, attr, ret, nr;

    nr = 0;
    for (attr = 0; attr < 2; attr++) {
	region.Attributes = attr;
	ret = ioctl(s->fd, DS_GET_FIRST_REGION, &region);
	while (ret == 0) {
	    mtd = lookup_mtd(&region);
	    if (mtd) {
		/* Have we seen this MTD before? */
		for (i = 0; i < nr; i++)
		    if (strcmp(s->mtd[i], mtd->module) == 0) break;
		if (i == nr) {
		    install_module(mtd->module, NULL);
		    s->mtd[nr] = mtd->module;
		    nr++;
		}
		syslog(LOG_INFO, "  %s memory region at 0x%lx: %s",
		       attr ? "Attribute" : "Common", region.CardOffset,
		       mtd->name);
		/* Bind MTD to this region */
		strcpy(mtd_info.dev_info, s->mtd[i]);
		mtd_info.Attributes = region.Attributes;
		mtd_info.CardOffset = region.CardOffset;
		if (ioctl(s->fd, DS_BIND_MTD, &mtd_info) != 0) {
		    syslog(LOG_INFO,
			   "bind MTD '%s' to region at 0x%lx failed: %m",
			   (char *)mtd_info.dev_info, region.CardOffset);
		}
	    }
	    ret = ioctl(s->fd, DS_GET_NEXT_REGION, &region);
	}
    }
    s->mtd[nr] = NULL;
    
    /* Now bind each unique MTD as a normal client of this socket */
    for (i = 0; i < nr; i++) {
	strcpy(bind.dev_info, s->mtd[i]);
	bind.instance = NULL;
	if (ioctl(s->fd, DS_BIND_REQUEST, &bind) != 0)
	    syslog(LOG_INFO, "bind MTD '%s' to socket %d failed: %m",
		   (char *)bind.dev_info, sn);
    }
} /* bind_mtd */

/*====================================================================*/

static void do_insert(int sn)
{
    socket_info_t *s = &socket[sn];
    card_info_t *card;
    device_info_t **dev;
    bind_info_t *bind;
    int i, j, ret;

    /* Already identified? */
    if ((s->card != NULL) && (s->card != blank_card))
	return;
    
    syslog(LOG_INFO, "initializing socket %d", sn);
    card = lookup_card(sn);
    /* Make sure we've learned something new before continuing */
    if (card == s->card)
	return;
    s->card = card;

    /* Initialize socket bindings */
    dev = card->device; bind = s->bind;
    for (i = 0; i < card->functions; i++) {
	strcpy((char *)bind[i].dev_info, (char *)dev[i]->dev_info);
	bind[i].instance = NULL;
	bind[i].name[0] = '\0';
    }
    write_stab();

    /* Set up MTD's */
    bind_mtd(sn);
    
    /* Install kernel modules */
    for (i = 0; i < card->functions; i++) {
	for (j = 0; j < dev[i]->modules; j++)
	    install_module(dev[i]->module[j], dev[i]->opts[j]);
    }

    /* Bind drivers by their dev_info identifiers */
    for (i = 0; i < card->functions; i++) {
	if (ioctl(s->fd, DS_BIND_REQUEST, &bind[i]) != 0) {
	    syslog(LOG_INFO, "bind '%s' to socket %d failed: %m",
		   (char *)bind[i].dev_info, sn);
	    if (errno != EBUSY) {
		beep(BEEP_TIME, BEEP_ERR);
		return;
	    }
	}
	for (j = 0; j < 10; j++) {
	    ret = ioctl(s->fd, DS_GET_DEVICE_INFO, &bind[i]);
	    if ((ret == 0) || (errno != EAGAIN))
		break;
	    usleep(100000);
	}
	if (ret != 0) {
	    syslog(LOG_INFO, "get dev info on socket %d failed: %m",
		   sn);
	    beep(BEEP_TIME, BEEP_ERR);
	    return;
	}
	if (strlen(bind[i].name) == 0) {
	    syslog(LOG_INFO, "no dev info for socket %d", sn);
	    beep(BEEP_TIME, BEEP_ERR);
	    return;
	}
	write_stab();
    }

    /* Run "start" commands */
    ret = 0;
    for (i = 0; i < card->functions; i++) {
	if (dev[i]->start_cmd)
	    ret |= execute_on_dev("start cmd", dev[i]->start_cmd,
				  sn, &bind[i]);
    }
    if (ret)
	beep(BEEP_TIME, BEEP_ERR);
    else
	beep(BEEP_TIME, BEEP_OK);
    
} /* do_insert */

/*====================================================================*/

static int do_remove(int sn, int request)
{
    socket_info_t *s = &socket[sn];
    card_info_t *card;
    device_info_t **dev;
    bind_info_t *bind;
    int i, j, ret;

    card = s->card;
    if (card == NULL)
	goto done;
    
    syslog(LOG_INFO, "shutting down socket %d", sn);
    
    /* Run "stop" commands */
    dev = card->device; bind = s->bind;
    for (i = 0; i < card->functions; i++) {
	if (dev[i]->stop_cmd && (bind[i].name[0] != '\0')) {
	    ret = execute_on_dev("stop cmd", dev[i]->stop_cmd,
				 sn, &bind[i]);
	    if (request && (ret != 0))
		goto fail;
	}
    }

    /* unbind driver instances */
    for (i = 0; i < card->functions; i++) {
	if (bind[i].instance != NULL) {
	    if (ioctl(s->fd, DS_UNBIND_REQUEST, &s->bind[i]) != 0)
		syslog(LOG_INFO, "unbind '%s' from socket %d failed: %m",
		       (char *)bind[i].dev_info, sn);
	}
    }

    /* remove kernel modules in inverse order */
    for (i = 0; i < card->functions; i++) {
	for (j = dev[i]->modules-1; j >= 0; j--)
	    remove_module(dev[i]->module[j]);
    }
    /* Remove any MTD's bound to this socket */
    for (i = 0; (s->mtd[i] != NULL); i++)
	remove_module(s->mtd[i]);

done:
    beep(BEEP_TIME, BEEP_OK);
    s->card = NULL;
    write_stab();
    return 0;
fail:
    beep(BEEP_TIME, BEEP_ERR);
    write_stab();
    return -EBUSY;
}

/*====================================================================*/

void free_resources(void)
{
    adjust_list_t *al;
    int fd = socket[0].fd;

    for (al = root_adjust; al; al = al->next) {
	if (al->adj.Action == ADD_MANAGED_RESOURCE) {
	    al->adj.Action = REMOVE_MANAGED_RESOURCE;
	    ioctl(fd, DS_ADJUST_RESOURCE_INFO, &al->adj);
	}
	else if ((al->adj.Action == REMOVE_MANAGED_RESOURCE) &&
		 (al->adj.Resource == RES_IRQ)) {
	    al->adj.Action = ADD_MANAGED_RESOURCE;
	    ioctl(fd, DS_ADJUST_RESOURCE_INFO, &al->adj);
	}
    }
    
} /* free_resources */

/*====================================================================*/

void adjust_resources(void)
{
    adjust_list_t *al;
    int ret;
    char tmp[64];
    int fd = socket[0].fd;
    
    for (al = root_adjust; al; al = al->next) {
	ret = ioctl(fd, DS_ADJUST_RESOURCE_INFO, &al->adj);
	if (ret != 0) {
	    switch (al->adj.Resource) {
	    case RES_MEMORY_RANGE:
		sprintf(tmp, "memory %p-%p",
			al->adj.resource.memory.Base,
			al->adj.resource.memory.Base +
			al->adj.resource.memory.Size - 1);
		break;
	    case RES_IO_RANGE:
		sprintf(tmp, "IO ports %#x-%#x",
			al->adj.resource.io.BasePort,
			al->adj.resource.io.BasePort +
			al->adj.resource.io.NumPorts - 1);
		break;
	    case RES_IRQ:
		sprintf(tmp, "irq %lu", al->adj.resource.irq.IRQ);
		break;
	    }
	    syslog(LOG_INFO, "could not adjust resource: %s: %m", tmp);
	}
    }
} /* adjust_resources */
    
/*====================================================================*/

void catch_signal(int sig)
{
    caught_signal = sig;
    if (signal(sig, catch_signal) == SIG_ERR)
	syslog(LOG_INFO, "signal(%d): %m", sig);
}

void handle_signal(void)
{
    int i;
    switch (caught_signal) {
    case SIGTERM:
	for (i = 0; i < sockets; i++)
	    if (socket[i].state & SOCKET_PRESENT)
		do_remove(i, 0);
	free_resources();
	exit(0);
	break;
    case SIGHUP:
	for (i = 0; i < sockets; i++)
	    if (socket[i].state & SOCKET_PRESENT) {
		do_remove(i, 0);
		socket[i].state |= SOCKET_PRESENT;
	    }
	free_resources();
	free_config();
	syslog(LOG_INFO, "re-loading config file");
	load_config();
	adjust_resources();
	for (i = 0; i < sockets; i++)
	    if (socket[i].state & SOCKET_PRESENT) {
		socket[i].state &= ~SOCKET_PRESENT;
		do_insert(i);
	    }
	break;
    case SIGPWR:
	break;
    }
}

void done(void)
{
    syslog(LOG_INFO, "exiting");
    unlink(pidfile);
    unlink(stabfile);
}

/*====================================================================*/

int init_sockets(void)
{
    int major, fd, i;
    servinfo_t serv;
    
    major = lookup_dev("pcmcia");
    if (major < 0) {
	syslog(LOG_INFO, "no pcmcia driver in /proc/devices");
	exit(EXIT_FAILURE);
    }
    for (i = 0; i < MAX_SOCKS; i++) {
	fd = open_dev((major<<8)+i);
	if (fd < 0) break;
	socket[i].fd = fd;
	socket[i].state = 0;
    }
    if ((fd < 0) && (errno != ENODEV)) {
	syslog(LOG_INFO, "open_dev(socket %d) failed: %m", i);
	return -1;
    }
    sockets = i;
    if (sockets == 0) {
	syslog(LOG_INFO, "no sockets found!");
	return -1;
    }
    else
	syslog(LOG_INFO, "watching %d sockets", sockets);

    if (ioctl(socket[0].fd, DS_GET_CARD_SERVICES_INFO, &serv) == 0) {
	if (serv.Revision != CS_RELEASE_CODE) {
	    syslog(LOG_INFO, "Card Services release does not match!");
	    return -1;
	}
    }
    else {
	syslog(LOG_INFO, "could not get CS revision info!");
	return -1;
    }
    adjust_resources();
    return 0;
}

/*====================================================================*/

void main(int argc, char *argv[])
{
    int optch, errflg;
    int i, ret, event;
    int verbose = 0;
    extern char *optarg;
    fd_set fds;
    FILE *f;

    errflg = 0;
    while ((optch = getopt(argc, argv, "qvc:m:p:s:")) != -1) {
	switch (optch) {
	case 'q':
	    be_quiet = 1; break;
	case 'v':
	    verbose = 1; break;
	case 'c':
	    config = strdup(optarg); break;
	case 'm':
	    modpath = strdup(optarg); break;
	case 'p':
	    pidfile = strdup(optarg); break;
	case 's':
	    stabfile = strdup(optarg); break;
	default:
	    errflg = 1; break;
	}
    }
    if (errflg || (optind < argc)) {
	fprintf(stderr, "usage: %s [-q] [-v] [-c config] [-m modpath] "
		"[-p pidfile] [-s stabfile]\n", argv[0]);
	exit(EXIT_FAILURE);
    }

#ifdef DEBUG
    openlog("cardmgr", LOG_PID|LOG_PERROR, LOG_DAEMON);
#else
    if (verbose)
	openlog("cardmgr", LOG_PID|LOG_PERROR, LOG_DAEMON);
    else
	openlog("cardmgr", LOG_PID|LOG_CONS, LOG_DAEMON);
#endif

#ifndef DEBUG
    if ((ret = fork()) > 0) exit(0);
    if (ret == -1)
	syslog(LOG_INFO, "forking: %m");
    if (setsid() < 0)
	syslog(LOG_INFO, "detaching from tty: %m");
#endif
    
    syslog(LOG_INFO, "starting");
    syslog(LOG_INFO, "%s", version);
    atexit(&done);

    if (modpath == NULL) {
	struct utsname utsname;
	if (uname(&utsname) != 0) {
	    syslog(LOG_INFO, "uname(): %m");
	    exit(EXIT_FAILURE);
	}
	modpath = (char *)malloc(32);
	sprintf(modpath, "/lib/modules/%s", utsname.release);
    }
    if (access(modpath, X_OK) != 0) {
	syslog(LOG_INFO, "cannot access %s: %m", modpath);
	exit(EXIT_FAILURE);
    }
    
    load_config();
    
    f = fopen(pidfile, "w");
    if (f == NULL)
	syslog(LOG_INFO, "could not open %s: %m", pidfile);
    else {
	fprintf(f, "%d\n", getpid());
	fclose(f);
    }

    if (init_sockets() != 0)
	exit(EXIT_FAILURE);

    if (signal(SIGHUP, catch_signal) == SIG_ERR)
	syslog(LOG_INFO, "signal(SIGHUP): %m");
    if (signal(SIGTERM, catch_signal) == SIG_ERR)
	syslog(LOG_INFO, "signal(SIGTERM): %m");
    if (signal(SIGPWR, catch_signal) == SIG_ERR)
	syslog(LOG_INFO, "signal(SIGPWR): %m");

    for (;;) {
	FD_ZERO(&fds);
	for (i = 0; i < sockets; i++)
	    FD_SET(socket[i].fd, &fds);

	while ((ret = select(MAX_SOCKS+4, &fds, NULL, NULL, NULL)) < 0) {
	    if (errno == EINTR)
		handle_signal();
	    else {
		syslog(LOG_INFO, "select(): %m");
		exit(EXIT_FAILURE);
	    }
	}

	for (i = 0; i < sockets; i++) {
	    if (!FD_ISSET(socket[i].fd, &fds))
		continue;
	    ret = read(socket[i].fd, &event, 4);
	    if ((ret == -1) && (errno != EAGAIN))
		syslog(LOG_INFO, "read(): %m\n");
	    if (ret != 4)
		continue;
	    
	    switch (event) {
	    case CS_EVENT_CARD_REMOVAL:
		socket[i].state &= ~(SOCKET_PRESENT | SOCKET_READY);
		do_remove(i, 0);
		break;
	    case CS_EVENT_EJECTION_REQUEST:
		socket[i].state &= ~(SOCKET_PRESENT | SOCKET_READY);
		ret = do_remove(i, 1);
		write(socket[i].fd, &ret, 4);
		break;
	    case CS_EVENT_CARD_INSERTION:
	    case CS_EVENT_INSERTION_REQUEST:
		socket[i].state |= SOCKET_PRESENT;
	    case CS_EVENT_CARD_RESET:
		socket[i].state |= SOCKET_READY;
		do_insert(i);
		break;
	    case CS_EVENT_RESET_PHYSICAL:
		socket[i].state &= ~SOCKET_READY;
		break;
	    }
	    
	}
    } /* repeat */
}
