/*  devfsd.c

    Main file for  devfsd  (devfs daemon for Linux).

    Copyright (C) 1998-2001  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 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

    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.

    Updated by      Richard Gooch   25-OCT-1999: Added compatibility actions.
  Added compatibility entries for sound, printer, video4linux, parallel port
  and frame buffer drivers.

    Updated by      Richard Gooch   26-OCT-1999: Incremented protocol revision
  to 5. Extract major and minor number in <do_scan_and_service>. Added
  compatibility entries for SoundBlaster CD-ROM, netlink, SCSI generic and
  SCSI tape drivers.

    Updated by      Richard Gooch   30-OCT-1999: Added entries for /dev/st,
  /dev/sg and /dev/sr hierarchies. Added compatibility entries for loop, IDE
  tape and SCSI CD-ROM drivers.

    Updated by      Richard Gooch   31-OCT-1999: Added compatibility entries
  for floppy, RAMDISC, meta-device, SCSI disc, IDE disc and IDE CD-ROM drivers.
  Added entries for /dev/sd, /dev/ide/hd and /dev/ide/cd hierarchies.

    Updated by      Richard Gooch   3-NOV-1999: Created <make_symlink> which
  will create intermediate directories as required. Updated compatibility code
  for IDE tapes. Added entries for /dev/ide/mt hierarchy.

    Updated by      Richard Gooch   4-NOV-1999: Added compatibility entries for
  virtual console capture, serial and console devices.

    Updated by      Richard Gooch   5-NOV-1999: Added compatibility entries for
  BSD pty devices.

    Updated by      Richard Gooch   11-NOV-1999: Only show messages about
  compatibility entries being created or destroyed when in trace mode.

    Updated by      Richard Gooch   21-NOV-1999: Added compatibility entries
  for joystick devices.

    Updated by      Richard Gooch   6-DEC-1999: Change directory to devfs FS.

    Updated by      Richard Gooch   9-DEC-1999: Support compiling with 2.2.x
  kernels.

    Updated by      Richard Gooch   15-DEC-1999: Added compatibility entries
  for miscellaneous character devices.

    Updated by      Richard Gooch   17-DEC-1999: Do not grab event queue when
  just setting devfs debug mask.

    Updated by      Richard Gooch   20-DEC-1999: Swap pathname and action
  fields in config file.

    Updated by      Richard Gooch   25-DEC-1999: Discard lookups on "/dev/log"
  and "/dev/initctl". Added "MODLOAD" action.

    Updated by      Richard Gooch   3-FEB-2000: Bitch and moan if modprobe(8)
  is used with "EXECUTE" action.

    Updated by      Richard Gooch   3-MAR-2000: Bug fix with inclusion
  directives.

    Updated by      Richard Gooch   6-MAR-2000: Support negative UID and GID in
  config file. Thanks to Chris Richards <crichard@wso.williams.edu>.

    Updated by      Richard Gooch   12-APR-2000: More efficient discarding of
  lookups on "/dev/log" and "/dev/initctl".

    Updated by      Richard Gooch   16-APR-2000: Added "MFUNCTION" and
  "CFUNCTION" actions. Added compatibility entries for Computone Multiport
  serial devices.

    Updated by      Richard Gooch   17-APR-2000: Added "COPY" action.

    Updated by      Richard Gooch   18-APR-2000: Moved some things to header
  "devfsd.h". Removed special argument "ENTRY" and renamed "INFO" to "EVENT"
  for "CFUNCTION" action. Bug fix in debug mode with shared objects.

    Updated by      Richard Gooch   20-APR-2000: Changed version macro.

    Updated by      Richard Gooch   24-APR-2000: Created a table of rules for
  simple compatibility translations. Split part of <read_config> into
  <process_config_line>, in preparation for NIS support.

    Updated by      Richard Gooch   26-APR-2000: Added NIS configuration
  support.

    Updated by      Richard Gooch   27-APR-2000: Fixed DIR leak. Generate
  synthetic REGISTER events on SIGHUP. Silently ignore NIS attempts when no
  NIS domain set.

    Updated by      Richard Gooch   30-APR-2000: Moved parts of <action_compat>
  to <get_old_name> in compat_name.c. Simplified <action_compat> for SCSI and
  IDE new compatibilty entries.

    Updated by      Richard Gooch   10-JUN-2000: Moved version string to
  version.h

    Updated by      Richard Gooch   3-JUL-2000: Added "-C /etc/modules.devfs"
  when calling modprobe(8). Fail if a configuration line has EXECUTE modprobe.

    Updated by      Richard Gooch   4-JUL-2000: Added #define __USE_GNU. Thanks
  to Adam J. Richter <adam@yggdrasil.com>.

    Updated by      Chris Rankin    21-JAN-2001: Allow the daemon to substitute
  regular expressions, similarly to sed. E.g. the expression ^foo\([0-9]\)$ has
  a subexpression within the \(, \) markers. The string foo4 matches this
  expression: the tag \0 would be replaced by "foo4", its first subexpression
  \1 by "4", etc.

    Updated by      Richard Gooch   5-FEB-2001: Switched from __USE_GNU to
  _GNU_SOURCE, according to recommendation from Ulrich. Do dummy opens of
  /dev/null so that fds [0:2] are open (and thus may be safely closed later).

    Updated by      Richard Gooch   21-FEB-2001: Bug fix in function
  <action_call_function>: a failed MFUNCTION would result in a bogus CFUNCTION
  being called.

    Updated by      Richard Gooch   27-JUL-2001: Bug fixes when execvp(3) fails
  (child did not exit).

    Updated by      Richard Gooch   30-JUL-2001: Applied and cleaned up patch
  by Chris Rankin (regular subexpression support). Fixed potential buffer
  overrun problems by using <snprintf>. Thanks to Sebastian Krahmer
  <krahmer@suse.de>. Added "-np" switch. Thanks to John Fremlin.

    Updated by      Richard Gooch   31-JUL-2001: Added support for DELETE
  event. Added debug trace to <action_modload>.

    Updated by      Richard Gooch   8-AUG-2001: Added support for recursively
  reading config directories.

    Last updated by Richard Gooch   10-AUG-2001: Fixed support for recursively
  reading config directories.


*/
#define _GNU_SOURCE
#include <unistd.h>
#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 <sys/socket.h>
#include <sys/un.h>
#include <dirent.h>
#include <fcntl.h>
#include <linux/devfs_fs.h>
#include <linux/kdev_t.h>
#include <syslog.h>
#include <signal.h>
#include <regex.h>
#include <errno.h>
#include <dlfcn.h>
#include <rpcsvc/ypclnt.h>
#include <rpcsvc/yp_prot.h>
#include <karma.h>
#include "devfsd.h"
#include "version.h"

#ifndef RTLD_DEFAULT  /*  Glibc 2.2 broke old code  */
#  define RTLD_DEFAULT RTLD_NEXT
#endif

#define CONFIG_FILE "/etc/devfsd.conf"
#define MAX_ARGS (6 + 1)
#define MAX_SUBEXPR  10

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

#ifndef DEVFSD_NOTIFY_DELETE  /*  Temporary hack  */
#  define DEVFSD_NOTIFY_DELETE        7
#endif

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

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

#define AC_PERMISSIONS              0
#define AC_MODLOAD                  1
#define AC_EXECUTE                  2
#define AC_MFUNCTION                3
#define AC_CFUNCTION                4
#define AC_COPY                     5
#define AC_IGNORE                   6
#define AC_MKOLDCOMPAT              7
#define AC_MKNEWCOMPAT              8
#define AC_RMOLDCOMPAT              9
#define AC_RMNEWCOMPAT              10


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

struct execute_type
{
    char *argv[MAX_ARGS + 1];  /*  argv[0] must always be the programme  */
};

struct call_function_type
{
    struct shared_object *so;
    union
    {
	int (*m) (int argc, char **argv);
	int (*c) (void *arg1, void *arg2, void *arg3, void *arg4, void *arg5);
    }
    func;
    char *argv[MAX_ARGS + 1];  /*  argv[0] is the function name          */
};

struct copy_type
{
    const char *source;
    const char *destination;
};

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

struct config_entry_struct
{
    struct action_type action;
    regex_t preg;
    union
    {
	struct permissions_type permissions;
	struct execute_type execute;
	struct call_function_type function;
	struct copy_type copy;
    }
    u;
    struct config_entry_struct *next;
};

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

struct shared_object
{
    CONST char *name;
    void *handle;
    struct shared_object *next;
};


/*  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) );
EXTERN_FUNCTION (const char *get_old_name,
		 (const char *devname, unsigned int namelen,
		  char *buffer, unsigned int major, unsigned int minor) );


/*  Public data  */
flag syslog_is_open = FALSE;


/*  Private functions  */
static void setup_initial_entries ();
static void read_config (CONST char *location, flag optional,
			 unsigned long *event_mask);
static void read_config_file (CONST char *path, flag optional,
			      unsigned long *event_mask);
static void process_config_line (CONST char *line, unsigned long *event_mask);
static int process_yp_line (int instatus, char *inkey, int inkeylen,
			    char *inval, int invallen, char *indata);
static void do_servicing (int fd, unsigned long event_mask);
static void service_name (const struct devfsd_notify_struct *info);
static void action_permissions (const struct devfsd_notify_struct *info,
				const struct config_entry_struct *entry);
static void action_modload (const struct devfsd_notify_struct *info,
			    const struct config_entry_struct *entry);
static void action_execute (const struct devfsd_notify_struct *info,
			    const struct config_entry_struct *entry,
			    const regmatch_t *regexpr, unsigned int numexpr);
static void action_call_function (const struct devfsd_notify_struct *info,
				  const struct config_entry_struct *entry,
				  const regmatch_t *regexpr,
				  unsigned int numexpr);
static void action_copy (const struct devfsd_notify_struct *info,
			 const struct config_entry_struct *entry,
			 const regmatch_t *regexpr, unsigned int number);
static void action_compat (const struct devfsd_notify_struct *info,
			   unsigned int action);
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);
static flag make_symlink (const char *oldpath, const char *newpath);
static flag make_dir_tree (const char *path);
static struct shared_object *get_shared_object (CONST char *name);
static flag expand_expression(char *output, unsigned int outsize,
			      const char *input,
			      const char *(*get_variable)(const char *variable,
                                                          void *info),
                              void *info,
                              const char *devname,
                              const regmatch_t *ex, unsigned int numexp,
                              FILE *errfp);
static void expand_regexp (char *output, size_t outsize,
			   const char *input, const char *devname,
			   const regmatch_t *ex, unsigned int numex,
			   FILE *errfp);


/*  Private data  */
static struct config_entry_struct *first_config = NULL;
static struct config_entry_struct *last_config = NULL;
static struct shared_object *first_so = NULL;
static const char *mount_point = NULL;
static flag no_syslog = 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;
    flag no_polling = FALSE;
    int fd, proto_rev, count;
    unsigned int devfs_debug_mask;
    unsigned long event_mask = 0;
    struct sigaction new_action;
    struct stat statbuf;
    static char usage[] = "devfsd mntpnt [-v] [-d] [-t num] [-D mask] [-fg] [-np]";

    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 if (strcmp (argv[count], "-np") == 0) no_polling = TRUE;
	else
	{
	    fprintf (stderr, "Usage:\t%s\n", usage);
	    exit (1);
	}
    }
    if (trace_level > 0) no_syslog = TRUE;
    mount_point = argv[1];
    if (chdir (mount_point) != 0)
    {
	fprintf (stderr, "Error changing directory to: \"%s\"\t%s\n",
		 mount_point, ERRSTRING);
	exit (1);
    }
    /*  Open dummy stdin, stdout and stderr if required, so that closing in
	do_open_syslog() doesn't close other files  */
    while ( ( fd = open ("/dev/null", O_RDWR, 0) ) < 2 );
    close (fd);
    if ( ( fd = open (".devfsd", O_RDONLY, 0) ) < 0 )
    {
	fprintf (stderr, "Error opening file: \".devfsd\"\t%s\n", 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);
    }
    if (!debug_devfs) setup_initial_entries ();
    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",
		 DEVFSD_VERSION);
	fprintf (stderr,
		 "(C) 1998-2000  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);
    }
    /*  Tell kernel we are special (i.e. we get to see hidden entries)  */
    if (ioctl (fd, DEVFSDIOC_SET_EVENT_MASK, 0) != 0)
    {
	fprintf (stderr, "Error setting event mask\t%s\n", ERRSTRING);
	exit (1);
    }
    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 (no_polling) exit (0);
    if (do_daemon)
    {
	/*  Release so that the child can grab it  */
	if (ioctl (fd, DEVFSDIOC_RELEASE_EVENT_QUEUE, 0) != 0)
	{
	    fprintf (stderr, "Error releasing event queue\t%s\n", ERRSTRING);
	    exit (1);
	}
	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);
	do_scan_and_service (opendir (mount_point), mount_point);
    }
}   /*  End Function main  */


/*  Private functions follow  */

static void setup_initial_entries ()
{
    struct initial_symlink_struct *curr;

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

static void read_config (CONST char *location, flag optional,
			 unsigned long *event_mask)
/*  [SUMMARY] Read a configuration database.
    <location> The location to read the database from. Either a filename or a
    YP(NIS) map name prefixed with '+'.
    <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.
*/
{
    if (location[0] == '+')
    {
	/*  It's a YP map  */
	struct ypall_callback ypall_cbk;
	char *domainname = NULL;

	/*  If no domain: fail silently (probably too early)  */
	if (yp_get_default_domain (&domainname) != 0) return;
	if (domainname == NULL) return;
	ypall_cbk.foreach = ( int (*) () ) process_yp_line;
	ypall_cbk.data = (char *) event_mask;
	if (yp_all (domainname, (char *) location + 1, &ypall_cbk) == 0)
	{
	    if (syslog_is_open)
		SYSLOG (LOG_INFO, "read map: \"%s\"\n", location + 1);
	    return;
	}
	if (optional) return;
	SYSLOG (LOG_ERR, "error reading map: \"%s\"\n", location + 1);
	exit (1);
    }
    read_config_file (location, optional, event_mask);
}   /*  End Function read_config   */

static void read_config_file (CONST char *path, flag optional,
			      unsigned long *event_mask)
/*  [SUMMARY] Read a configuration database.
    <path> The path to read the database from. If this is a directory, all
    entries in that directory will be read (except hidden entries).
    <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.
*/
{
    struct stat statbuf;
    FILE *fp;
    char buf[STRING_LENGTH];

    if (stat (path, &statbuf) != 0)
    {
	if ( optional && (errno == ENOENT) ) return;
	SYSLOG (LOG_ERR, "error opening file: \"%s\"\t%s\n",
		path, ERRSTRING);
	exit (1);
    }
    if ( S_ISDIR (statbuf.st_mode) )
    {
	DIR *dp;
	struct dirent *de;

	if ( ( dp = opendir (path) ) == NULL )
	{
	    SYSLOG (LOG_ERR, "error opening directory: \"%s\"\t%s\n",
		    path, ERRSTRING);
	    exit (1);
	}
	while ( ( de = readdir (dp) ) != NULL )
	{
	    char fname[STRING_LENGTH];

	    if (de->d_name[0] == '.') continue;
	    snprintf (fname, STRING_LENGTH, "%s/%s", path, de->d_name);
	    read_config_file (fname, optional, event_mask);
	}
	closedir (dp);
	return;
    }
    if ( ( fp = fopen (path, "r") ) == NULL )
    {
	if ( optional && (errno == ENOENT) ) return;
	SYSLOG (LOG_ERR, "error opening file: \"%s\"\t%s\n", path, ERRSTRING);
	exit (1);
    }
    if (trace_level > 2) fprintf (stderr, "Reading file: \"%s\"\n", path);
    while (fgets (buf, STRING_LENGTH, fp) != NULL)
    {
	char *line;

	buf[strlen (buf) - 1] = '\0';
	/*  Skip whitespace  */
	for (line = buf; isspace (*line); ++line);
	if (line[0] == '\0') continue;
	if (line[0] == '#') continue;
	process_config_line (line, event_mask);
    }
    fclose (fp);
    if (syslog_is_open)
	SYSLOG (LOG_INFO, "read config file: \"%s\"\n", path);
}   /*  End Function read_config_file   */

static void process_config_line (CONST char *line, unsigned long *event_mask)
/*  [SUMMARY] Process a line from a configuration file.
    <line> The configuration line.
    <event_mask> The event mask is written here. This is not initialised.
    [RETURNS] Nothing.
*/
{
    int err, num_args, count;
    struct config_entry_struct *new;
    char p[MAX_ARGS][STRING_LENGTH];
    char when[STRING_LENGTH], what[STRING_LENGTH];
    char name[STRING_LENGTH], tmp[STRING_LENGTH];

    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, name, what,
		       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;
	return;
    }
    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, name, get_variable, NULL,
			NULL);
	read_config (name, (toupper (when[0]) == 'I') ? FALSE : TRUE,
		     event_mask);
	return;
    }
    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 if (strcasecmp (when, "DELETE") == 0)
	new->action.when = DEVFSD_NOTIFY_DELETE;
    else
    {
	SYSLOG (LOG_ERR, "bad WHEN in config line: \"%s\"\n", line);
	SYSLOG (LOG_ERR, "exiting\n");
	exit (1);
    }
    if (strcasecmp (what, "PERMISSIONS") == 0)
    {
	char *ptr;

	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, "MODLOAD") == 0) new->action.what = AC_MODLOAD;
    else if (strcasecmp (what, "EXECUTE") == 0)
    {
	new->action.what = AC_EXECUTE;
	num_args -= 3;
	if (strstr (p[0], "modprobe") != NULL)
	{   /*  Bitch and moan  */
	    SYSLOG (LOG_NOTICE,
		    "Use MODLOAD action instead of EXECUTE modprobe!\n");
	    exit (1);
	}
	for (count = 0; count < num_args; ++count)
	{
	    if ( ( new->u.execute.argv[count] = strdup (p[count]) ) == NULL )
	    {
		SYSLOG (LOG_ERR, "error allocating\n");
		SYSLOG (LOG_ERR, "exiting\n");
		exit (1);
	    }
	}
	new->u.execute.argv[num_args] = NULL;
    }
    else if ( (strcasecmp (what, "MFUNCTION") == 0) ||
	      (strcasecmp (what, "CFUNCTION") == 0) )
    {
	new->action.what = (strcasecmp (what, "MFUNCTION") == 0) ?
	    AC_MFUNCTION : AC_CFUNCTION;
	num_args -= 3;
	if (num_args < 2)
	{
	    SYSLOG (LOG_ERR, "missing path and function in \"%s\"\n",line);
	    exit (1);
	}
	if ( ( new->u.function.so = get_shared_object (p[0]) ) == NULL )
	{
	    SYSLOG (LOG_ERR, "error loading: \"%s\"\t%s\n", p[0], dlerror () );
	    exit (1);
	}
	if ( ( new->u.function.func.m =
	       dlsym (new->u.function.so->handle, p[1]) ) == NULL )
	{
	    SYSLOG (LOG_ERR, "symbol: \"%s\" not found in %s\n", p[1], p[0]);
	    exit (1);
	}
	--num_args;
	for (count = 0; count < num_args; ++count)
	{
	    if ( ( new->u.function.argv[count] = strdup (p[count + 1]) )
		 == NULL )
	    {
		SYSLOG (LOG_ERR, "error allocating\n");
		SYSLOG (LOG_ERR, "exiting\n");
		exit (1);
	    }
	}
	new->u.function.argv[num_args] = NULL;
    }
    else if (strcasecmp (what, "COPY") == 0)
    {
	new->action.what = AC_COPY;
	num_args -= 3;
	if (num_args != 2)
	{
	    SYSLOG (LOG_ERR, "missing path and function in \"%s\"\n", line);
	    exit (1);
	}
	if ( ( new->u.copy.source = strdup (p[0]) ) == NULL )
	{
	    SYSLOG (LOG_ERR, "error allocating source string\n");
	    SYSLOG (LOG_ERR, "exiting\n");
	    exit (1);
	}
	if ( ( new->u.copy.destination = strdup (p[1]) ) == NULL )
	{
	    SYSLOG (LOG_ERR, "error allocating destination string\n");
	    SYSLOG (LOG_ERR, "exiting\n");
	    exit (1);
	}
    }
    else if (strcasecmp (what, "IGNORE") == 0) new->action.what = AC_IGNORE;
    else if (strcasecmp (what, "MKOLDCOMPAT") == 0)
	new->action.what = AC_MKOLDCOMPAT;
    else if (strcasecmp (what, "MKNEWCOMPAT") == 0)
	new->action.what = AC_MKNEWCOMPAT;
    else if (strcasecmp (what, "RMOLDCOMPAT") == 0)
	new->action.what = AC_RMOLDCOMPAT;
    else if (strcasecmp (what, "RMNEWCOMPAT") == 0)
	new->action.what = AC_RMNEWCOMPAT;
    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;
}   /*  End Function process_config_line   */

static int process_yp_line (int instatus, char *inkey, int inkeylen,
			    char *inval, int invallen, char *indata)
{
    if (instatus != YP_TRUE) return (instatus);
    if (invallen > 0) process_config_line (inval, (unsigned long *) indata);
    return (0);
}   /*  End Function process_yp_line  */

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.
*/
{
    unsigned int n;
    regmatch_t mbuf[MAX_SUBEXPR];
    struct config_entry_struct *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);
    /*  Discard lookups on "/dev/log" and "/dev/initctl"  */
    if (info->type == DEVFSD_NOTIFY_LOOKUP)
    {
	if ( (strcmp (info->devname, "log") == 0) ||
	     (strcmp (info->devname, "initctl") == 0) ) return;
    }
    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, MAX_SUBEXPR, mbuf, 0) != 0)
	    continue;
	for (n = 0; (n < MAX_SUBEXPR) && (mbuf[n].rm_so != -1); ++n);
	switch (entry->action.what)
	{
	  case AC_PERMISSIONS:
	    action_permissions (info, entry);
	    break;
	  case AC_MODLOAD:
	    action_modload (info, entry);
	    break;
	  case AC_EXECUTE:
	    action_execute (info, entry, mbuf, n);
	    break;
	  case AC_MFUNCTION:
	  case AC_CFUNCTION:
	    action_call_function (info, entry, mbuf, n);
	    break;
	  case AC_COPY:
	    action_copy (info, entry, mbuf, n);
	    break;
	  case AC_IGNORE:
	    return;
	    /*break;*/
	  case AC_MKOLDCOMPAT:
	  case AC_MKNEWCOMPAT:
	  case AC_RMOLDCOMPAT:
	  case AC_RMNEWCOMPAT:
	    action_compat (info, entry->action.what);
	    break;
	  default:
	    SYSLOG (LOG_ERR, "Unknown action type: %u\n", entry->action.what);
	    exit (1);
	    /*break;*/
	}
    }
}   /*  End Function service_name  */

static void action_permissions (const struct devfsd_notify_struct *info,
				const struct config_entry_struct *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;

    if (lstat (info->devname, &statbuf) != 0)
    {
	SYSLOG (LOG_ERR, "error lstat(2)ing: \"%s\"\t%s\n",
		info->devname, 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",
		 info->devname, info->mode, new_mode, info->uid, info->gid,
		 entry->u.permissions.uid, entry->u.permissions.gid);
    if (new_mode != statbuf.st_mode)
    {
	if (chmod (info->devname, new_mode) != 0)
	{
	    SYSLOG (LOG_ERR, "error changing mode for: \"%s\"\t%s\n",
		    info->devname, ERRSTRING);
	    SYSLOG (LOG_ERR, "exiting\n");
	    exit (1);
	}
	if (trace_level > 2) fprintf (stderr,"Set permission %05o on \"%s\"\n",
				      new_mode, info->devname);
    }
    if ( (entry->u.permissions.uid != statbuf.st_uid) ||
	 (entry->u.permissions.gid != statbuf.st_gid) )
    {
	if (chown (info->devname, entry->u.permissions.uid,
		   entry->u.permissions.gid) != 0)
	{
	    SYSLOG (LOG_ERR, "error changing mode for: \"%s\"\t%s\n",
		    info->devname, 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,
		     info->devname);
    }
}   /*  End Function action_permissions  */

static void action_modload (const struct devfsd_notify_struct *info,
			    const struct config_entry_struct *entry)
/*  [SUMMARY] Load a module.
    <info> The devfs change.
    <entry> The config file entry.
    [RETURNS] Nothing.
*/
{
    char *argv[6];
    char device[STRING_LENGTH];
    static int (*modprobe) (int argc, char **argv) = NULL;
    static flag first_time = TRUE;

    if (first_time)
    {
	void *lib;

	first_time = FALSE;
	if ( ( lib = dlopen ("/lib/modutils.so", RTLD_NOW) ) != NULL )
	    modprobe = dlsym (lib, "modprobe");
    }
    argv[0] = "/sbin/modprobe";
    argv[1] = "-k";
    argv[2] = "-C";
    argv[3] = "/etc/modules.devfs";
    argv[4] = device;
    argv[5] = NULL;
    snprintf (device, sizeof (device), "/dev/%s", info->devname);
    if (trace_level > 1)
	fprintf (stderr, "Calling modprobe with name: \"%s\"\n", device);
    if (modprobe != NULL)
    {
	(*modprobe) (5, argv);
	return;
    }
    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);
    _exit (1);
}   /*  End Function action_modload  */

static void action_execute (const struct devfsd_notify_struct *info,
			    const struct config_entry_struct *entry,
			    const regmatch_t *regexpr, unsigned int numexpr)
/*  [SUMMARY] Execute a programme.
    <info> The devfs change.
    <entry> The config file entry.
    <regexpr> The number of subexpression (start, end) offsets within the
    device name.
    <numexpr> The number of elements within <<regexpr>>.
    [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;
    snprintf (gv_info.devpath, sizeof (gv_info.devpath), "%s/%s",
	      mount_point, 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)
    {
	expand_expression (largv[count], STRING_LENGTH,
			   entry->u.execute.argv[count],
			   get_variable, &gv_info,
			   gv_info.devname, regexpr, numexpr, 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);
    _exit (1);
}   /*  End Function action_execute  */

static void action_call_function (const struct devfsd_notify_struct *info,
				  const struct config_entry_struct *entry,
				  const regmatch_t *regexpr,
				  unsigned int numexpr)
/*  [SUMMARY] Call a function.
    <info> The devfs change.
    <entry> The config file entry.
    <regexpr> The number of subexpression (start, end) offsets within the
    device name.
    <numexpr> The number of elements within <<regexpr>>.
    [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;
    snprintf (gv_info.devpath, sizeof (gv_info.devpath), "%s/%s",
	      mount_point, info->devname);
    if (trace_level > 1)
	fprintf (stderr, "Calling %s in %s for \"%s\" | \"%s\":",
		 entry->u.function.argv[0], entry->u.function.so->name,
		 info->devname, gv_info.devpath);
    argv[0] = (char *) entry->u.function.so->name;
    for (count = 1; entry->u.function.argv[count] != NULL; ++count)
    {
	if (strcmp (entry->u.function.argv[count], "EVENT") == 0)
	    argv[count] = (char *) info;
	else
	{
	    expand_expression (largv[count], STRING_LENGTH,
			       entry->u.function.argv[count], get_variable,
			       &gv_info, gv_info.devname, regexpr, numexpr,
			       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;
    if (entry->action.what == AC_MFUNCTION)
    {
	if ( (*entry->u.function.func.m) (count, argv) == 0 )
	    return;
    }
    else
    {
	if ( (*entry->u.function.func.c) (argv[1], argv[2], argv[3], argv[4],
					  argv[5]) == 0 ) return;
    }
    SYSLOG (LOG_ERR, "error calling: \"%s\" in \"%s\"\n",
	    entry->u.function.argv[0], argv[0]);
}   /*  End Function action_call_function  */

static void action_copy (const struct devfsd_notify_struct *info,
			 const struct config_entry_struct *entry,
                         const regmatch_t *regexpr, unsigned int numexpr)
/*  [SUMMARY] Copy permissions.
    <info> The devfs change.
    <entry> The config file entry.
    <regexpr> This list of subexpression (start, end) offsets within the
    device name.
    <numexpr> The number of elements in <<regexpr>>.  
    [RETURNS] Nothing.
*/
{
    struct get_variable_info gv_info;
    struct stat source_stat, dest_stat;
    char source[STRING_LENGTH], destination[STRING_LENGTH];

    gv_info.info = info;
    gv_info.devname = info->devname;
    snprintf (gv_info.devpath, sizeof (gv_info.devpath), "%s/%s",
	      mount_point, info->devname);
    expand_expression (source, STRING_LENGTH, entry->u.copy.source,
		       get_variable, &gv_info, gv_info.devname,
		       regexpr, numexpr, NULL);
    expand_expression (destination, STRING_LENGTH, entry->u.copy.destination,
		       get_variable, &gv_info, gv_info.devname,
		       regexpr, numexpr, NULL);
    if (trace_level > 1)
	fprintf (stderr, "Copying %s to %s for \"%s\" | \"%s\"\n",
		 source, destination, info->devname, gv_info.devpath);
    if ( !make_dir_tree (destination) )
    {
	SYSLOG (LOG_ERR, "error making tree for: \"%s\"\n", destination);
	return;
    }
    if (lstat (source, &source_stat) != 0) return;
    if (lstat (destination, &dest_stat) != 0) dest_stat.st_mode = 0;
    if ( (source_stat.st_mode & S_IFMT) == (dest_stat.st_mode & S_IFMT) )
    {
	/*  Same type  */
	if ( S_ISLNK (source_stat.st_mode) )
	{
	    int source_len, dest_len;
	    char source_link[STRING_LENGTH], dest_link[STRING_LENGTH];

	    source_len = readlink (source, source_link, STRING_LENGTH - 1);
	    source_link[source_len] = '\0';
	    dest_len = readlink (destination, dest_link, STRING_LENGTH - 1);
	    dest_link[dest_len] = '\0';
	    if ( (source_len != dest_len) ||
		 (strcmp (source_link, dest_link) != 0) )
	    {
		unlink (destination);
		symlink (source_link, destination);
	    }
	    return;
	}
	if (source_stat.st_mode != dest_stat.st_mode)
	    chmod (destination, source_stat.st_mode & ~S_IFMT);
	if ( (source_stat.st_uid != dest_stat.st_uid) ||
	     (source_stat.st_gid != dest_stat.st_gid) )
	    chown (destination, source_stat.st_uid, source_stat.st_gid);
	return;
    }
    /*  Different types: unlink and create  */
    unlink (destination);
    switch (source_stat.st_mode & S_IFMT)
    {
	int fd, val;
	struct sockaddr_un un_addr;
	char symlink_val[STRING_LENGTH];

      case S_IFSOCK:
	if ( ( fd = socket (AF_UNIX, SOCK_STREAM, 0) ) < 0 ) break;
	un_addr.sun_family = AF_UNIX;
	snprintf (un_addr.sun_path, sizeof (un_addr.sun_path), "%s",
		  destination);
	if (bind (fd, (struct sockaddr *) &un_addr, (int) sizeof un_addr) != 0)
	    break;
	if (chmod (destination, source_stat.st_mode & ~S_IFMT) != 0) break;
	if (chown (destination, source_stat.st_uid, source_stat.st_gid) == 0)
	    return;
	break;
      case S_IFLNK:
	if ( ( val = readlink (source, symlink_val, STRING_LENGTH - 1) ) < 0 )
	    break;
	symlink_val[val] = '\0';
	if (symlink (symlink_val, destination) == 0) return;
	break;
      case S_IFREG:
	if (open (destination, O_RDONLY | O_CREAT,
		  source_stat.st_mode & ~S_IFMT) < 0) break;
	if (chmod (destination, source_stat.st_mode & ~S_IFMT) != 0) break;
	if (chown (destination, source_stat.st_uid, source_stat.st_gid) == 0)
	    return;
	break;
      case S_IFBLK:
      case S_IFCHR:
      case S_IFIFO:
	if (mknod (destination, source_stat.st_mode, source_stat.st_rdev) != 0)
	    break;
	if (chown (destination, source_stat.st_uid, source_stat.st_gid) == 0)
	    return;
	break;
      case S_IFDIR:
	if (mkdir (destination, source_stat.st_mode & ~S_IFMT) != 0) break;
	if (chown (destination, source_stat.st_uid, source_stat.st_gid) == 0)
	    return;
	return;
	/*break;*/
    }
    SYSLOG (LOG_ERR, "error copying: \"%s\" to \"%s\"\n", source, destination);
}   /*  End Function action_copy  */

static void action_compat (const struct devfsd_notify_struct *info,
			   unsigned int action)
/*  [SUMMARY] Process a compatibility request.
    <info> The devfs change.
    <action> The action to take.
    [RETURNS] Nothing.
*/
{
    const char *compat_name = NULL;
    const char *dest_name = info->devname;
    char *ptr;
    char compat_buf[STRING_LENGTH], dest_buf[STRING_LENGTH];
    static char function_name[] = "action_compat";

    /*  First construct compatibility name  */
    switch (action)
    {
      case AC_MKOLDCOMPAT:
      case AC_RMOLDCOMPAT:
	compat_name = get_old_name (info->devname, info->namelen, compat_buf,
				    info->major, info->minor);
	break;
      case AC_MKNEWCOMPAT:
      case AC_RMNEWCOMPAT:
	if (strncmp (info->devname, "scsi/", 5) == 0)
	{
	    int mode, host, bus, target, lun;

	    sscanf (info->devname + 5, "host%d/bus%d/target%d/lun%d/",
		    &host, &bus, &target, &lun);
	    compat_name = compat_buf;
	    snprintf (dest_buf, sizeof (dest_buf), "../%s", info->devname);
	    dest_name = dest_buf;
	    if (strncmp (ptr = (strrchr (info->devname, '/') + 1), "mt", 2)
		== 0)
	    {
		char rewind = info->devname[info->namelen - 1];

		if (rewind != 'n') rewind = '\0';
		switch (ptr[2])
		{
		  default:
		    mode = 0;
		    break;
		  case 'l':
		    mode = 1;
		    break;
		  case 'm':
		    mode = 2;
		    break;
		  case 'a':
		    mode = 3;
		    break;
		}
		sprintf (compat_buf, "st/c%db%dt%du%dm%d%c",
			 host, bus, target, lun, mode, rewind);
	    }
	    else if (strcmp (info->devname + info->namelen - 7,"generic") == 0)
		sprintf (compat_buf, "sg/c%db%dt%du%d",
			 host, bus, target, lun);
	    else if (strcmp (info->devname + info->namelen - 2, "cd") == 0)
		sprintf (compat_buf, "sr/c%db%dt%du%d",
			 host, bus, target, lun);
	    else if (strcmp (info->devname + info->namelen - 4, "disc") == 0)
		sprintf (compat_buf, "sd/c%db%dt%du%d",
			 host, bus, target, lun);
	    else if (strncmp (ptr = (strrchr (info->devname, '/') + 1), "part",
			      4) == 0)
		sprintf ( compat_buf, "sd/c%db%dt%du%dp%d",
			  host, bus, target, lun, atoi (ptr + 4) );
	    else compat_name = NULL;
	}
	else if (strncmp (info->devname, "ide/", 4) == 0)
	{
	    int host, bus, target, lun;

	    sscanf (info->devname + 4, "host%d/bus%d/target%d/lun%d/",
		    &host, &bus, &target, &lun);
	    compat_name = compat_buf;
	    snprintf (dest_buf, sizeof (dest_buf), "../%s", info->devname + 4);
	    dest_name = dest_buf;
	    if (strcmp (info->devname + info->namelen - 4, "disc") == 0)
		sprintf (compat_buf, "ide/hd/c%db%dt%du%d",
			 host, bus, target, lun);
	    else if (strncmp (ptr = (strrchr (info->devname, '/') + 1), "part",
			      4) == 0)
		sprintf ( compat_buf, "ide/hd/c%db%dt%du%dp%d",
			  host, bus, target, lun, atoi (ptr + 4) );
	    else if (strcmp (info->devname + info->namelen - 2, "cd") == 0)
		sprintf (compat_buf, "ide/cd/c%db%dt%du%d",
			 host, bus, target,lun);
	    else if (strncmp (ptr = (strrchr (info->devname, '/') + 1), "mt",
			      2) == 0)
		snprintf (compat_buf, sizeof (compat_buf),
			  "ide/mt/c%db%dt%du%d%s",
			  host, bus, target, lun, ptr + 2);
	    else compat_name = NULL;
	}
	break;
    }
    if (compat_name == NULL) return;
    /*  Now decide what to do with it  */
    switch (action)
    {
      case AC_MKOLDCOMPAT:
      case AC_MKNEWCOMPAT:
	if ( make_symlink (dest_name, compat_name) )
	    if (trace_level > 1)
		fprintf (stderr, "made symlink: \"%s\" for dev: %u,%u\n",
			compat_name, info->major, info->minor);
	break;
      case AC_RMOLDCOMPAT:
      case AC_RMNEWCOMPAT:
	if (unlink (compat_name) != 0)
	{
	    SYSLOG (LOG_ERR, "%s: error unlinking: \"%s\"\t%s\n",
		    function_name, compat_name, ERRSTRING);
	}
	else if (trace_level > 1)
	    fprintf (stderr, "unlinked: \"%s\"\n", compat_name);
	break;
    }
}   /*  End Function action_compat  */

static void free_config ()
/*  [SUMMARY] Free the configuration information.
    [RETURNS] Nothing.
*/
{
    struct config_entry_struct *c_entry;
    struct shared_object *so_entry;
    void *next;

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

	next = c_entry->next;
	regfree (&c_entry->preg);
	if (c_entry->action.what == AC_EXECUTE)
	{
	    for (count = 0; count < MAX_ARGS; ++count)
	    {
		if (c_entry->u.execute.argv[count] == NULL) break;
		free (c_entry->u.execute.argv[count]);
	    }
	}
	else if ( (c_entry->action.what == AC_MFUNCTION) ||
		  (c_entry->action.what == AC_CFUNCTION) )
	{
	    for (count = 0; count < MAX_ARGS; ++count)
	    {
		if (c_entry->u.function.argv[count] == NULL) break;
		free (c_entry->u.function.argv[count]);
	    }
	}
	free (c_entry);
    }
    first_config = NULL;
    last_config = NULL;
    for (so_entry = first_so; so_entry != NULL; so_entry = next)
    {
	next = so_entry->next;
	free ( (char *) so_entry->name );
	if (so_entry->handle != RTLD_DEFAULT) dlclose (so_entry->handle);
	free (so_entry);
    }
    first_so = 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 = "asynchronously 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;
	  case DEVFSD_NOTIFY_DELETE:
	    type = "delete";
	    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, (int) 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]) || ( (string[0] == '-') && isdigit (string[1]) ) )
	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]) || ( (string[0] == '-') && isdigit (string[1]) ) )
	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. This is closed upon completion.
    <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;
	snprintf (path, sizeof (path), "%s/%s", dirname, de->d_name);
	if (lstat (path, &statbuf) != 0)
	{
	    SYSLOG (LOG_ERR, "Error lstat(2)ing 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.major = MAJOR (statbuf.st_rdev);
	info.minor = MINOR (statbuf.st_rdev);
	info.uid = statbuf.st_uid;
	info.gid = statbuf.st_gid;
	snprintf (info.devname, sizeof (info.devname), "%s",
		  path + strlen (mount_point) + 1);
	info.namelen = strlen (info.devname);
	service_name (&info);
    }
    closedir (dp);
}   /*  End Function do_scan_and_service  */

static flag make_symlink (const char *oldpath, const char *newpath)
/*  [SUMMARY] Create a symlink, creating intervening directories as required.
    <oldpath> The string contained in the symlink.
    <newpath> The name of the new symlink.
    [RETURNS] TRUE on success, else FALSE.
*/
{
    static char function_name[] = "make_symlink";

    if ( !make_dir_tree (newpath) ) return (FALSE);
    if (symlink (oldpath, newpath) != 0)
    {
	if (errno == EEXIST)
	{
	    if (trace_level > 1)
		fprintf (stderr, "symlink: \"%s\" already exists\n", newpath);
	}
	else
	    SYSLOG (LOG_ERR, "%s: error creating symlink: \"%s\"\t%s\n",
		    function_name, newpath, ERRSTRING);
	return (FALSE);
    }
    return (TRUE);
}   /*  End Function make_symlink  */

static flag make_dir_tree (const char *path)
/*  [SUMMARY] Creating intervening directories for a path as required.
    <path> The full pathname (including he leaf node).
    [RETURNS] TRUE on success, else FALSE.
*/
{
    char ch;
    char *ptr1, *ptr2;
    char path_copy[STRING_LENGTH];
    /*static char function_name[] = "make_dir_tree";*/

    snprintf (path_copy, sizeof (path_copy), "%s", path);
    ptr1 = path_copy;
    while (TRUE)
    {
	if ( ( ptr2 = strchr (ptr1 + 1, '/') ) == NULL ) break;
	ch = *ptr2;
	*ptr2 = '\0';
	if (access (path_copy, F_OK) == 0)
	{
	    *ptr2 = ch;
	    ptr1 = ptr2;
	    continue;
	}
	if (mkdir (path_copy,
		   S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) != 0)
	{
	    SYSLOG (LOG_ERR, "Error making directory \"%s\"\t%s\n",
		    path_copy, ERRSTRING);
	    *ptr2 = ch;
	    return (FALSE);
	}
	*ptr2 = ch;
	ptr1 = ptr2;
    }
    return (TRUE);
}   /*  End Function make_dir_tree  */

static struct shared_object *get_shared_object (CONST char *name)
/*  [SUMMARY] Load a shared object if possible.
    <name> The shared object.
    [RETURNS] A pointer to the object on success, else NULL.
*/
{
    struct shared_object *so;

    for (so = first_so; so != NULL; so = so->next)
	if (strcmp (so->name, name) == 0) return (so);
    if ( ( so = malloc (sizeof *so) ) == NULL ) return (NULL);
    if ( ( so->name = strdup (name) ) == NULL )
    {
	free (so);
	return (NULL);
    }
    if (strcmp (name, "GLOBAL") == 0) so->handle = RTLD_DEFAULT;
    else if ( ( so->handle = dlopen (name, RTLD_NOW) ) == NULL )
    {
	free ( (char *) so->name );
	free (so);
	return (NULL);
    }
    so->next = first_so;
    first_so = so;
    return (so);
}   /*  End Function get_shared_object  */

static flag expand_expression(char *output, unsigned int outsize,
			      const char *input,
			      const char *(*get_variable)(const char *variable,
                                                          void *info),
                              void *info,
                              const char *devname,
                              const regmatch_t *ex, unsigned int numexp,
                              FILE *errfp)
/*  [SUMMARY] Expand enviroment variables and regular subexpressions in string.
    <output> The output expanded expression is written here.
    <length> The size of the output buffer.
    <input> The input expression. This may equal <<output>>.
    <get_variable> A function which will be used to get variable values. If
    this returns NULL, the environment is searched instead. If this is NULL,
    only the environment is searched.
    <info> An arbitrary pointer passed to <<get_variable>>.
    <devname> Device name; specifically, this is the string that contains all
    of the regular subexpressions.
    <ex> Array of start / end offsets into info->devname for each subexpression
    <numexp> Number of regular subexpressions found in <<devname>>.
    <errfp> Diagnostic messages are written here. If this is NULL the global
    stderr is used instead.
    [RETURNS] TRUE on success, else FALSE.
*/
{
    char temp[STRING_LENGTH];

    if ( !st_expr_expand (temp, STRING_LENGTH, input,
			  get_variable, info, errfp) ) return (FALSE);
    expand_regexp (output, outsize, temp, devname, ex, numexp, errfp);
    return (TRUE);
}   /*  End Function expand_expression  */

static void expand_regexp (char *output, size_t outsize, const char *input,
			   const char *devname,
			   const regmatch_t *ex, unsigned int numex,
			   FILE *errfp)
/*  [SUMMARY] Expand all occurrences of the regular subexpressions \0 to \9.
    <output> The output expanded expression is written here.
    <outsize> The size of the output buffer.
    <input> The input expression. This may NOT equal <<output>>, because
    supporting that would require yet another string-copy. However, it's not
    hard to write a simple wrapper function to add this functionality for those
    few cases that need it.
    <devname> Device name; specifically, this is the string that contains all
    of the regular subexpressions.
    <ex> An array of start and end offsets into <<devname>>, one for each
    subexpression
    <numex> Number of subexpressions in the offset-array <<ex>>.
    <errfp> Diagnostic messages are written here. If this is NULL the global
    stderr is used instead.
*/
{
    const char last_exp = '0' - 1 + numex;
    int c = -1;

    if (!errfp) errfp = stderr;
    /*  Guarantee NULL termination by writing an explicit '\0' character into
	the very last byte  */
    if (outsize) output[--outsize] = '\0';
    /*  Copy the input string into the output buffer, replacing '\\' with '\'
	and '\0' .. '\9' with subexpressions 0 .. 9, if they exist. Other \x
	codes are deleted  */
    while ( (c != '\0') && (outsize != 0) )
    {
        c = *input;
        ++input;
        if (c == '\\')
        {
            c = *input;
            ++input;
            if (c != '\\')
            {
                if ((c >= '0') && (c <= last_exp))
                {
                    const regmatch_t *subexp = ex + (c - '0');
                    unsigned int sublen = subexp->rm_eo - subexp->rm_so;

                    /*  Range checking  */
                    if (sublen > outsize) sublen = outsize;
                    strncpy (output, devname + subexp->rm_so, sublen);
                    output += sublen;
                    outsize -= sublen;
                }
                continue;
            }
        }
        *output = c;
        ++output;
        --outsize;
    } /* while */
}   /*  End Function expand_regexp  */
