/*  devfsd.c

    Main file for  devfsd  (devfs daemon for Linux).

    Copyright (C) 1998-1999  Richard Gooch

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    Richard Gooch may be reached by email at  rgooch@atnf.csiro.au
    The postal address is:
      Richard Gooch, c/o ATNF, P. O. Box 76, Epping, N.S.W., 2121, Australia.
*/

/*
    This programme will notify manage the devfs filesystem.


    Written by      Richard Gooch   9-AUG-1998

    Updated by      Richard Gooch   11-AUG-1998

    Updated by      Richard Gooch   10-SEP-1998: Added support for asynchronous
  open and close events.

    Updated by      Richard Gooch   11-SEP-1998: Fixed bug in <read_config>
  where <<line>> pointer was dereferenced before being initialised.

    Updated by      Richard Gooch   9-JUN-1999: Added variable expansion
  support.

    Updated by      Richard Gooch   10-JUN-1999: Added "devname" variable and
  renamed "device" variable name to "devpath". Fixed bug in argument handling.
  Added "-d" switch.

    Updated by      Richard Gooch   13-JUN-1999: Added compile-time check of
  protocol revision.

    Updated by      Richard Gooch   14-JUN-1999: Stat inode instead of using
  device entry information (which may be out of date).

    Updated by      Richard Gooch   15-JUN-1999: Added tracing output. Thanks
  to Piete Brooks <Piete.Brooks@cl.cam.ac.uk>.

    Updated by      Richard Gooch   16-JUN-1999: Added action structure.

    Updated by      Richard Gooch   17-JUN-1999: Added "hostname" and "mntpnt"
  variables. Added nesting of include files. Added "CLEAR_CONFIG" key.

    Updated by      Richard Gooch   18-JUN-1999: Added "OPTIONAL_INCLUDE" key.
  Added ignore actions. Added event mask.

    Updated by      Richard Gooch   24-JUN-1999: Switched to separated event
  and action config file specifiers.

    Updated by      Richard Gooch   20-OCT-1999: Fixed typo in debugging output

    Last updated by Richard Gooch   24-OCT-1999: Added "-fg" switch and
  redefined tracing levels. Delay opening syslog until /dev/log appears. Scan
  mounted FS and generate synthetic REGISTER events.


*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>
#include <linux/devfs_fs.h>
#include <syslog.h>
#include <signal.h>
#include <regex.h>
#include <errno.h>
#include <karma.h>


#define VERSION "1.2.4"
#define CONFIG_FILE "/etc/devfsd.conf"
#define MAX_ARGS (6 + 1)

/*  Update only after changing code to reflect new protocol  */
#define DEVFSD_PROTOCOL_REVISION_DAEMON  4

/*  Compile-time check  */
#if DEVFSD_PROTOCOL_REVISION_KERNEL != DEVFSD_PROTOCOL_REVISION_DAEMON
#error protocol version mismatch. Update your kernel headers
#endif


#define AC_PERMISSIONS              0
#define AC_EXECUTE                  1
#define AC_IGNORE                   2

#define MKACTION(what,when) (struct action_type) {what, when}

#define SYSLOG(level, format, args...) \
    {if (syslog_is_open) syslog (level, format, ##args); \
     else fprintf (stderr, format, ## args); \
    }


struct permissions_type
{
    mode_t mode;
    uid_t uid;
    gid_t gid;
};

struct execute_type
{
    char *argv[MAX_ARGS + 1];
};

struct action_type
{
    unsigned int what;
    unsigned int when;
};

struct config_entry
{
    struct action_type action;
    regex_t preg;
    union
    {
	struct permissions_type permissions;
	struct execute_type execute;
    }
    u;
    struct config_entry *next;
};

struct get_variable_info
{
    const struct devfsd_notify_struct *info;
    CONST char *devname;
    char devpath[STRING_LENGTH];
};


/*  External functions  */
EXTERN_FUNCTION (flag st_expr_expand,
		 (char *output, unsigned int length, CONST char *input,
		  CONST char *(*get_variable) (CONST char *variable,
					       void *info),
		  void *info, FILE *errfp) );


/*  Private functions  */
static void setup_initial_entries (CONST char *mount_point);
static void read_config (CONST char *filename, flag optional,
			 unsigned long *event_mask);
static void do_servicing (int fd, unsigned long event_mask);
static void service_name (const struct devfsd_notify_struct *info);
static void update_permissions (const struct devfsd_notify_struct *info,
				const struct config_entry *entry);
static void execute (const struct devfsd_notify_struct *info,
		     const struct config_entry *entry);
static void free_config ();
static void do_debug (int fd);
static uid_t get_uid (const char *string);
static gid_t get_gid (const char *string);
static mode_t get_mode (const char *string);
static void sig_handler (int sig);
static CONST char *get_variable (CONST char *variable, void *info);
static void do_open_syslog ();
static void do_scan_and_service (DIR *dp, CONST char *dirname);


/*  Private data  */
static struct config_entry *first_config = NULL;
static struct config_entry *last_config = NULL;
static const char *mount_point = NULL;
static flag no_syslog = FALSE;
static flag syslog_is_open = FALSE;
static char trace_level = 0;
static flag debug_protocol = FALSE;
static struct initial_symlink_struct
{
    char *dest;
    char *name;
} initial_symlinks[] =
{
    {"/proc/self/fd", "fd"},
    {"fd/0", "stdin"},
    {"fd/1", "stdout"},
    {"fd/2", "stderr"},
    {NULL, NULL},
};
    

/*  Public functions follow  */

int main (int argc, char **argv)
{
    flag print_version = FALSE;
    flag debug_devfs = FALSE;
    flag do_daemon = TRUE;
    int fd, proto_rev, count;
    unsigned int devfs_debug_mask;
    unsigned long event_mask = 0;
    struct sigaction new_action;
    struct stat statbuf;
    char filename[STRING_LENGTH];
    static char usage[] = "devfsd mntpnt [-v] [-d] [-t num] [-D mask] [-fg]";

    if (argc < 2)
    {
	fprintf (stderr, "Usage:\t%s\n", usage);
	exit (1);
    }
    for (count = 2; count < argc; ++count)
    {
	if (strcmp (argv[count], "-v") == 0) print_version = TRUE;
	else if (strcmp (argv[count], "-d") == 0)
	{
	    debug_protocol = TRUE;
	    no_syslog = TRUE;
	}
	else if ( (strcmp (argv[count], "-t") == 0) && (count + 1 < argc) )
	    trace_level = atoi (argv[++count]);
	else if ( (strcmp (argv[count], "-D") == 0) && (++count < argc) )
	{
	    devfs_debug_mask = strtol (argv[count], NULL, 0);
	    debug_devfs = TRUE;
	}
	else if (strcmp (argv[count], "-fg") == 0) do_daemon = FALSE;
	else
	{
	    fprintf (stderr, "Usage:\t%s\n", usage);
	    exit (1);
	}
    }
    if (trace_level > 0) no_syslog = TRUE;
    mount_point = argv[1];
    strcpy (filename, argv[1]);
    strcat (filename, "/.devfsd");
    if ( ( fd = open (filename, O_RDONLY, 0) ) < 0 )
    {
	fprintf (stderr, "Error opening file: \"%s\"\t%s\n",
		 filename, ERRSTRING);
	exit (1);
    }
    if (ioctl (fd, DEVFSDIOC_GET_PROTO_REV, &proto_rev) != 0)
    {
	fprintf (stderr, "Error getting protocol revision\t%s\n", ERRSTRING);
	exit (1);
    }
    setup_initial_entries (mount_point);
    if (!print_version && !debug_protocol && !debug_devfs)
    {
	if (stat (CONFIG_FILE, &statbuf) != 0)
	{
	    fprintf (stderr, "device management daemon exiting as no %s\n",
		     CONFIG_FILE);
	    exit (0);
	}
	if (statbuf.st_size == 0)
	{
	    fprintf (stderr,
		     "device management daemon exiting as %s is empty\n",
		     CONFIG_FILE);
	    exit (0);
	}
    }
    if ( print_version || (trace_level > 1) || debug_protocol ||
	 (DEVFSD_PROTOCOL_REVISION_DAEMON != proto_rev) )
    {
	fprintf (stderr, "devfsd: Linux device filesystem daemon v%s\n",
		 VERSION);
	fprintf (stderr,
		 "(C) 1998-1999  Richard Gooch <rgooch@atnf.csiro.au>\n\n");
	fprintf (stderr, "Daemon protocol revision:\t%d\n",
		 DEVFSD_PROTOCOL_REVISION_DAEMON);
	fprintf (stderr, "Kernel-side protocol revision:\t%d\n", proto_rev);
	if (DEVFSD_PROTOCOL_REVISION_DAEMON != proto_rev)
	{
	    fprintf (stderr, "Protocol mismatch!\n");
	    exit (1);
	}
    }
    if (print_version) exit (0);
    if (debug_devfs)
    {
	if (ioctl (fd, DEVFSDIOC_SET_DEBUG_MASK, &devfs_debug_mask) != 0)
	{
	    fprintf (stderr, "Error setting debug mask\t%s\n", ERRSTRING);
	    exit (1);
	}
	exit (0);
    }
    if (debug_protocol) do_debug (fd);
    sigemptyset (&new_action.sa_mask);
    new_action.sa_flags = 0;
    /*  Set up SIGHUP handler  */
    new_action.sa_handler = sig_handler;
    if (sigaction (SIGHUP, &new_action, NULL) != 0)
    {
	fprintf (stderr, "Error setting SIGHUP handler\t%s\n", ERRSTRING);
	exit (1);
    }
    if (trace_level < 1)
    	fprintf (stderr, "Started device management daemon for %s\n",
		 mount_point);
    else
    	fprintf (stderr,
		 "Started device management daemon for %s at trace level %d\n",
		 mount_point, trace_level);
    read_config (CONFIG_FILE, FALSE, &event_mask);
    /*  Do the scan before forking, so that boot scripts see the finished
	product  */
    do_scan_and_service (opendir (mount_point), mount_point);
    if (do_daemon)
    {
	switch ( fork () )
	{
	  case 0:
	    /*  Child  */
	    break;
	  case -1:
	    /*  Error  */
	    perror ("devfsd");
	    exit (2);
	    /*break;*/
	  default:
	    /*  Parent  */
	    exit (0);
	    /*break;*/
	}
    }
    do_open_syslog ();
    while (TRUE)
    {
	do_servicing (fd, event_mask);
	free_config ();
	read_config (CONFIG_FILE, FALSE, &event_mask);
    }
}   /*  End Function main  */


/*  Private functions follow  */

static void setup_initial_entries (CONST char *mount_point)
{
    struct initial_symlink_struct *curr;
    char name[STRING_LENGTH];

    for (curr = initial_symlinks; curr->dest != NULL; ++curr)
    {
	strcpy (name, mount_point);
	strcat (name, "/");
	strcat (name, curr->name);
	symlink (curr->dest, name);
    }
}   /*  End Function setup_initial_entries  */

static void read_config (CONST char *filename, flag optional,
			 unsigned long *event_mask)
/*  [SUMMARY] Read the configuration file.
    <filename> The filename to read from.
    <optional> If TRUE, the routine will silently ignore a missing config file.
    <event_mask> The event mask is written here. This is not initialised.
    [RETURNS] Nothing.
*/
{
    char *ptr, *line;
    FILE *fp;
    char buf[STRING_LENGTH];

    if ( ( fp = fopen (filename, "r") ) == NULL )
    {
	if ( optional && (errno == ENOENT) ) return;
	SYSLOG (LOG_ERR, "error opening file: \"%s\"\t%s\n",
		CONFIG_FILE, ERRSTRING);
	exit (1);
    }
    if (trace_level > 2) fprintf (stderr, "Reading file: \"%s\"\n",
				  CONFIG_FILE);
    while (fgets (buf, STRING_LENGTH, fp) != NULL)
    {
	int err, num_args, count;
	struct config_entry *new;
	char p[MAX_ARGS][STRING_LENGTH];
	char when[STRING_LENGTH], what[STRING_LENGTH];
	char name[STRING_LENGTH], tmp[STRING_LENGTH];

	buf[strlen (buf) - 1] = '\0';
	/*  Skip whitespace  */
	for (line = buf; isspace (*line); ++line);
	if (line[0] == '\0') continue;
	if (line[0] == '#') continue;
	for (count = 0; count < MAX_ARGS; ++count) p[count][0] = '\0';
	num_args = sscanf (line, "%s %s %s %s %s %s %s %s %s %s",
			   when, what, name, p[0], p[1], p[2], p[3],
			   p[4], p[5], p[6]);
	if (strcasecmp (when, "CLEAR_CONFIG") == 0)
	{
	    free_config ();
	    *event_mask = 0;
	    continue;
	}
	if (num_args < 2)
	{
	    SYSLOG (LOG_ERR, "bad config line: \"%s\"\n", line);
	    SYSLOG (LOG_ERR, "exiting\n");
	    exit (1);
	}
	if ( (strcasecmp (when, "INCLUDE") == 0) ||
	     (strcasecmp (when, "OPTIONAL_INCLUDE") == 0) )
	{
	    st_expr_expand (name, STRING_LENGTH, what, get_variable, NULL,
			    NULL);
	    read_config (what, (toupper (when[0]) == 'I') ? FALSE : TRUE,
			 event_mask);
	    continue;
	}
	if (num_args < 3)
	{
	    SYSLOG (LOG_ERR, "bad config line: \"%s\"\n", line);
	    SYSLOG (LOG_ERR, "exiting\n");
	    exit (1);
	}
	if ( ( new = malloc (sizeof *new) ) == NULL )
	{
	    SYSLOG (LOG_ERR, "error allocating\n");
	    SYSLOG (LOG_ERR, "exiting\n");
	    exit (1);
	}
	memset (new, 0, sizeof *new);
	if (strcasecmp (when, "REGISTER") == 0)
	    new->action.when = DEVFSD_NOTIFY_REGISTERED;
	else if (strcasecmp (when, "UNREGISTER") == 0)
	    new->action.when = DEVFSD_NOTIFY_UNREGISTERED;
	else if (strcasecmp (when, "ASYNC_OPEN") == 0)
	    new->action.when = DEVFSD_NOTIFY_ASYNC_OPEN;
	else if (strcasecmp (when, "CLOSE") == 0)
	    new->action.when = DEVFSD_NOTIFY_CLOSE;
	else if (strcasecmp (when, "LOOKUP") == 0)
	    new->action.when = DEVFSD_NOTIFY_LOOKUP;
	else if (strcasecmp (when, "CHANGE") == 0)
	    new->action.when = DEVFSD_NOTIFY_CHANGE;
	else if (strcasecmp (when, "CREATE") == 0)
	    new->action.when = DEVFSD_NOTIFY_CREATE;
	else
	{
	    SYSLOG (LOG_ERR, "bad WHEN in config line: \"%s\"\n", line);
	    SYSLOG (LOG_ERR, "exiting\n");
	    exit (1);
	}
	if (strcasecmp (what, "PERMISSIONS") == 0)
	{
	    new->action.what = AC_PERMISSIONS;
	    /*  Get user and group  */
	    if ( ( ptr = strchr (p[0], '.') ) == NULL )
	    {
		SYSLOG (LOG_ERR, "missing '.' character in: \"%s\"\n", p[0]);
		SYSLOG (LOG_ERR, "exiting\n");
		exit (1);
	    }
	    *ptr++ = '\0';
	    new->u.permissions.uid = get_uid (p[0]);
	    new->u.permissions.gid = get_gid (ptr);
	    /*  Get mode  */
	    new->u.permissions.mode = get_mode (p[1]);
	}
	else if (strcasecmp (what, "EXECUTE") == 0)
	{
	    new->action.what = AC_EXECUTE;
	    num_args -= 3;
	    for (count = 0; count < num_args; ++count)
	    {
		err = strlen (p[count]) + 1;
		if ( ( new->u.execute.argv[count] = malloc (err) ) == NULL )
		{
		    SYSLOG (LOG_ERR, "error allocating\n");
		    SYSLOG (LOG_ERR, "exiting\n");
		    exit (1);
		}
		strcpy (new->u.execute.argv[count], p[count]);
	    }
	    new->u.execute.argv[num_args] = NULL;
	}
	else if (strcasecmp (what, "IGNORE") == 0)
	    new->action.what = AC_IGNORE;
	else
	{
	    SYSLOG (LOG_ERR, "bad WHAT in config line: \"%s\"\n", line);
	    SYSLOG (LOG_ERR, "exiting\n");
	    exit (1);
	}
	if ( ( err = regcomp (&new->preg, name, 0) ) != 0 )
	{
	    regerror (err, &new->preg, tmp, STRING_LENGTH);
	    SYSLOG (LOG_ERR, "error compiling regexp: \"%s\"\t%s\n", name,tmp);
	    SYSLOG (LOG_ERR, "exiting\n");
	    exit (1);
	}
	*event_mask |= 1 << new->action.when;
	new->next = NULL;
	if (first_config == NULL) first_config = new;
	else last_config->next = new;
	last_config = new;
    }
    fclose (fp);
    if (syslog_is_open)
	SYSLOG (LOG_INFO, "read config file: \"%s\"\n", CONFIG_FILE);
}   /*  End Function read_config   */

static void do_servicing (int fd, unsigned long event_mask)
/*  [SUMMARY] Service devfs changes until a signal is received.
    <fd> The open control file.
    <event_mask> The event mask.
    [RETURNS] Nothing.
*/
{
    ssize_t bytes;
    struct devfsd_notify_struct info;
    unsigned long tmp_event_mask;

    /*  Tell devfs what events we care about  */
    tmp_event_mask = event_mask;
    /*  May need to trap inode creates to watch for syslogd(8) starting  */
    if (!syslog_is_open && !no_syslog)
    {
	tmp_event_mask |= 1 << DEVFSD_NOTIFY_CREATE;
    }
    if (ioctl (fd, DEVFSDIOC_SET_EVENT_MASK, tmp_event_mask) != 0)
    {
	SYSLOG (LOG_ERR, "error setting event mask\t%s\n", ERRSTRING);
	exit (1);
    }
    while ( ( bytes = read (fd, (char *) &info, sizeof info) ) >= 0 )
    {
	if (bytes < 1)
	{
	    SYSLOG (LOG_ERR, "devfs closed file!\n");
	    exit (1);
	}
	/*  Special trap for "/dev/log" creation  */
	if (!syslog_is_open && !no_syslog &&
	    (info.type == DEVFSD_NOTIFY_CREATE) &&
	    (strcmp (info.devname, "log") == 0) )
	{
	    /*  Open syslog, now that "/dev/log" exists  */
	    do_open_syslog ();
	    if (ioctl (fd, DEVFSDIOC_SET_EVENT_MASK, event_mask) != 0)
	    {
		SYSLOG (LOG_ERR, "error setting event mask\t%s\n", ERRSTRING);
		exit (1);
	    }
	}
	service_name (&info);
    }
    if (errno == EINTR) return;
    SYSLOG (LOG_ERR, "Error reading control file\t%s\n", ERRSTRING);
    exit (1);
}   /*  End Function do_servicing  */

static void service_name (const struct devfsd_notify_struct *info)
/*  [SUMMARY] Service a single devfs change.
    <info> The devfs change.
    [RETURNS] Nothing.
*/
{
    regmatch_t mbuf;
    struct config_entry *entry;

    if (info->overrun_count > 0)
	SYSLOG (LOG_ERR, "%u events have been lost!\n", info->overrun_count);
    if (trace_level > 2) fprintf (stderr, "Looking for \"%s\" (%d)\n",
				  info->devname, info->type);
    for (entry = first_config; entry != NULL; entry = entry->next)
    {
	if (trace_level > 3) fprintf (stderr, "\t\tProcess \"%s\" (%d)\n",
				      info->devname, entry->action.when);
	/*  First check if action matches the type, then check if name matches
	 */
	if (info->type != entry->action.when) continue;
	if (regexec (&entry->preg, info->devname, 1, &mbuf, 0) != 0) continue;
	switch (entry->action.what)
	{
	  case AC_PERMISSIONS:
	    update_permissions (info, entry);
	    break;
	  case AC_EXECUTE:
	    execute (info, entry);
	    break;
	  case AC_IGNORE:
	    return;
	    /*break;*/
	  default:
	    SYSLOG (LOG_ERR, "Unknown action type: %u\n", entry->action.what);
	    exit (1);
	    /*break;*/
	}
    }
}   /*  End Function service_name  */

static void update_permissions (const struct devfsd_notify_struct *info,
				const struct config_entry *entry)
/*  [SUMMARY] Update permissions for a device entry.
    <info> The devfs change.
    <entry> The config file entry.
    [RETURNS] Nothing.
*/
{
    mode_t new_mode;
    struct stat statbuf;
    char path[STRING_LENGTH];

    strcpy (path, mount_point);
    strcat (path, "/");
    strcat (path, info->devname);
    if (lstat (path, &statbuf) != 0)
    {
	SYSLOG (LOG_ERR, "error stat(2)ing: \"%s\"\t%s\n", path, ERRSTRING);
	SYSLOG (LOG_ERR, "exiting\n");
	exit (1);
    }
    new_mode = (statbuf.st_mode & S_IFMT) |
	(entry->u.permissions.mode & ~S_IFMT);
    if (trace_level > 2)
	fprintf (stderr,
		 "\tupdate permissions for \"%s\" from %05o to %05o, user.group from %d.%d to %d.%d\n",
		 path, info->mode, new_mode, info->uid, info->gid,
		 entry->u.permissions.uid, entry->u.permissions.gid);
    if (new_mode != statbuf.st_mode)
    {
	if (chmod (path, new_mode) != 0)
	{
	    SYSLOG (LOG_ERR, "error changing mode for: \"%s\"\t%s\n",
		    path, ERRSTRING);
	    SYSLOG (LOG_ERR, "exiting\n");
	    exit (1);
	}
	if (trace_level > 2) fprintf (stderr,"Set permission %05o on \"%s\"\n",
				      new_mode, path);
    }
    if ( (entry->u.permissions.uid != statbuf.st_uid) ||
	 (entry->u.permissions.gid != statbuf.st_gid) )
    {
	if (chown (path, entry->u.permissions.uid, entry->u.permissions.gid)
	    != 0)
	{
	    SYSLOG (LOG_ERR, "error changing mode for: \"%s\"\t%s\n",
		    path, ERRSTRING);
	    SYSLOG (LOG_ERR, "exiting\n");
	    exit (1);
	}
	if (trace_level > 2)
	    fprintf (stderr, "Set user.group %d.%d on \"%s\"\n",
		     entry->u.permissions.uid, entry->u.permissions.gid, path);
    }
}   /*  End Function update_permissions  */

static void execute (const struct devfsd_notify_struct *info,
		     const struct config_entry *entry)
/*  [SUMMARY] Execute a programme.
    <info> The devfs change.
    <entry> The config file entry.
    [RETURNS] Nothing.
*/
{
    unsigned int count;
    struct get_variable_info gv_info;
    char *argv[MAX_ARGS + 1];
    char largv[MAX_ARGS + 1][STRING_LENGTH];

    gv_info.info = info;
    gv_info.devname = info->devname;
    strcpy (gv_info.devpath, mount_point);
    strcat (gv_info.devpath, "/");
    strcat (gv_info.devpath, info->devname);
    if (trace_level > 1) fprintf (stderr, "Calling %s for \"%s\" | \"%s\":",
				  entry->u.execute.argv[0], info->devname,
				  gv_info.devpath);
    for (count = 0; entry->u.execute.argv[count] != NULL; ++count)
    {
	st_expr_expand (largv[count], STRING_LENGTH,
			entry->u.execute.argv[count], get_variable, &gv_info,
			NULL);
	argv[count] = largv[count];
	if (trace_level > 1) fprintf (stderr, " '%s'", argv[count]);
    }
    if (trace_level > 1) fprintf (stderr, ". i.e. %d arg%s\n",
				  count - 1, (count == 2) ? "" : "s");
    argv[count] = NULL;
    switch ( fork () )
    {
      case 0:
	/*  Child  */
	break;
      case -1:
	/*  Error  */
	SYSLOG (LOG_ERR, "error forking\t%s\n", ERRSTRING);
	exit (2);
	/*break;*/
      default:
	/*  Parent  */
	wait (NULL);
	return;
	/*break;*/
    }
    execvp (argv[0], argv);
    SYSLOG (LOG_ERR, "error execing: \"%s\"\t%s\n", argv[0], ERRSTRING);
}   /*  End Function execute  */

static void free_config ()
/*  [SUMMARY] Free the configuration information.
    [RETURNS] Nothing.
*/
{
    struct config_entry *entry, *next;

    for (entry = first_config; entry != NULL; entry = next)
    {
	unsigned int count;

	next = entry->next;
	regfree (&entry->preg);
	if (entry->action.what == AC_EXECUTE)
	{
	    for (count = 0; count < MAX_ARGS; ++count)
	    {
		if (entry->u.execute.argv[count] == NULL) break;
		free (entry->u.execute.argv[count]);
	    }
	}
	free (entry);
    }
    first_config = NULL;
    last_config = NULL;
}   /*  End Function free_config  */

static void do_debug (int fd)
/*  [SUMMARY] Debug the devfs protocol.
    <fd> The open control file.
    [RETURNS] Nothing.
*/
{
    ssize_t bytes;
    struct devfsd_notify_struct info;
    char *type;

    fprintf (stderr, "Running in debug mode: no actions will be taken...\n");
    /*  Tell devfs what events we care about  */
    if (ioctl (fd, DEVFSDIOC_SET_EVENT_MASK, ~0) != 0)
    {
	SYSLOG (LOG_ERR, "error setting event mask\t%s\n", ERRSTRING);
	exit (1);
    }
    while ( ( bytes = read (fd, (char *) &info, sizeof info) ) >= 0 )
    {
	if (bytes < 1)
	{
	    fprintf (stderr, "devfs closed file!\n");
	    exit (1);
	}
	if (info.overrun_count > 0)
	    SYSLOG (LOG_ERR, "%u events have been lost!\n",info.overrun_count);
	switch (info.type)
	{
	  case DEVFSD_NOTIFY_REGISTERED:
	    type = "registered";
	    break;
	  case DEVFSD_NOTIFY_UNREGISTERED:
	    type = "unregistered";
	    break;
	  case DEVFSD_NOTIFY_ASYNC_OPEN:
	    type = "asynchonously opened";
	    break;
	  case DEVFSD_NOTIFY_CLOSE:
	    type = "closed";
	    break;
	  case DEVFSD_NOTIFY_LOOKUP:
	    type = "lookup";
	    break;
	  case DEVFSD_NOTIFY_CHANGE:
	    type = "change";
	    break;
	  case DEVFSD_NOTIFY_CREATE:
	    type = "create";
	    break;
	  default:
	    fprintf (stderr, "Unknown type: %u\n", info.type);
	    exit (1);
	    /*break;*/
	}
	fprintf (stderr, "Entry: \"%s\"(len=%d) %s mode: %o uid: %d gid: %d\n",
		 info.devname, strlen (info.devname), type,
		 (int) info.mode, (int) info.uid, (int) info.gid);
    }
    fprintf (stderr, "Error reading control file\t%s\n", ERRSTRING);
    exit (1);
}   /*  End Function do_debug  */

static uid_t get_uid (const char *string)
/*  [SUMMARY] Convert a string to a UID value.
    <string> The string.
    [RETURNS] The UID value.
*/
{
    struct passwd *pw_ent;

    if ( isdigit (string[0]) ) return atoi (string);
    if ( ( pw_ent = getpwnam (string) ) == NULL )
    {
	SYSLOG (LOG_ERR, "unknown user: \"%s\"\n", string);
	SYSLOG (LOG_ERR, "exiting\n");
	exit (1);
    }
    return (pw_ent->pw_uid);
}   /*  End Function get_uid  */

static gid_t get_gid (const char *string)
/*  [SUMMARY] Convert a string to a GID value.
    <string> The string.
    [RETURNS] The GID value.
*/
{
    struct group *grp_ent;

    if ( isdigit (string[0]) ) return atoi (string);
    if ( ( grp_ent = getgrnam (string) ) == NULL )
    {
	SYSLOG (LOG_ERR, "unknown group: \"%s\"\n", string);
	SYSLOG (LOG_ERR, "exiting\n");
	exit (1);
    }
    return (grp_ent->gr_gid);
}   /*  End Function get_gid  */

static mode_t get_mode (const char *string)
/*  [SUMMARY] Convert a string to a mode value.
    <string> The string.
    [RETURNS] The mode value.
*/
{
    mode_t mode;

    if ( isdigit (string[0]) ) return strtoul (string, NULL, 8);
    if (strlen (string) != 9)
    {
	SYSLOG (LOG_ERR, "bad symbolic mode: \"%s\"\n", string);
	SYSLOG (LOG_ERR, "exiting\n");
	exit (1);
    }
    mode = 0;
    if (string[0] == 'r') mode |= S_IRUSR;
    if (string[1] == 'w') mode |= S_IWUSR;
    if (string[2] == 'x') mode |= S_IXUSR;
    if (string[3] == 'r') mode |= S_IRGRP;
    if (string[4] == 'w') mode |= S_IWGRP;
    if (string[5] == 'x') mode |= S_IXGRP;
    if (string[6] == 'r') mode |= S_IROTH;
    if (string[7] == 'w') mode |= S_IWOTH;
    if (string[8] == 'x') mode |= S_IXOTH;
    if (trace_level > 3) fprintf (stderr, "\tMode \"%s\" gives %05o\n",
				  string, mode);
    return (mode);
}   /*  End Function get_mode  */

static void sig_handler (int sig)
{
    SYSLOG (LOG_INFO, "Caught SIGHUP\n");
}   /*  End Function sig_handler  */

static CONST char *get_variable (CONST char *variable, void *info)
{
    struct get_variable_info *gv_info = info;
    static flag first_time = TRUE;
    static char hostname[STRING_LENGTH], sbuf[STRING_LENGTH];

    if (first_time)
    {
	first_time = FALSE;
	if (gethostname (hostname, STRING_LENGTH - 1) != 0)
	{
	    SYSLOG (LOG_ERR, "Error getting hostname\t%s\n", ERRSTRING);
	    exit (RV_SYS_ERROR);
	}
	hostname[STRING_LENGTH - 1] = '\0';
    }
    if (strcmp (variable, "hostname") == 0) return (hostname);
    if (strcmp (variable, "mntpnt") == 0) return (mount_point);
    if (gv_info == NULL) return (NULL);
    if (strcmp (variable, "devpath") == 0) return (gv_info->devpath);
    if (strcmp (variable, "devname") == 0) return (gv_info->devname);
    if (strcmp (variable, "mode") == 0)
    {
	sprintf (sbuf, "%o", gv_info->info->mode);
	return (sbuf);
    }
    if (strcmp (variable, "uid") == 0)
    {
	sprintf (sbuf, "%u", gv_info->info->uid);
	return (sbuf);
    }
    if (strcmp (variable, "gid") == 0)
    {
	sprintf (sbuf, "%u", gv_info->info->gid);
	return (sbuf);
    }
    return (NULL);
}   /*  End Function get_variable  */

static void do_open_syslog ()
/*  [SUMMARY] Open syslog(3) connection if appropriate.
    [RETURNS] Nothing.
*/
{
    if (syslog_is_open || no_syslog) return;
    if (access ("/dev/log", F_OK) != 0) return;
    openlog ("devfsd", LOG_PID, LOG_DAEMON);
    close (0);
    close (1);
    close (2);
    syslog_is_open = TRUE;
}   /*  End Function do_open_syslog  */

static void do_scan_and_service (DIR *dp, CONST char *dirname)
/*  [SUMMARY] Scan a directory tree and generate register events on leaf nodes.
    <dp> The directory pointer.
    <dirname> The name of the directory.
    [RETURNS] Nothing.
*/
{
    struct stat statbuf;
    struct dirent *de;
    char path[STRING_LENGTH];

    while ( (de = readdir (dp) ) != NULL )
    {
	struct devfsd_notify_struct info;

	if ( (strcmp (de->d_name, ".") == 0) ||
	     (strcmp (de->d_name, "..") == 0) ) continue;
	sprintf (path, "%s/%s", dirname, de->d_name);
	if (lstat (path, &statbuf) != 0)
	{
	    SYSLOG (LOG_ERR, "Error stating file \"%s\"\t%s\n",
		    path, ERRSTRING);
	    exit (RV_SYS_ERROR);
	}
	if ( S_ISDIR (statbuf.st_mode) )
	{
	    do_scan_and_service (opendir (path), path);
	    continue;
	}
	memset (&info, 0, sizeof info);
	info.type = DEVFSD_NOTIFY_REGISTERED;
	info.mode = statbuf.st_mode;
	info.uid = statbuf.st_uid;
	info.gid = statbuf.st_gid;
	strcpy (info.devname, path + strlen (mount_point) + 1);
	info.namelen = strlen (info.devname);
	service_name (&info);
    }
}   /*  End Function do_scan_and_service  */
