[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

makedev stuff



I've been real busy w/ other stuff, so I'll release this in the state
it's in, and continue work if people like what I've done.  My MAKEDEV
scans all files in /etc/makedev.d, reading in only files w/ no
extension, or an extension that matches the OS.  The format of the files
in /etc/makedev.d is "<name> <command>".  MAKEDEV will take one of more
names on the command line (`./MAKEDEV std ide`), searching all appropriate
files for lines starting w/ "std " or "ide ".  Any lines that it finds,
it will pass off to /bin/sh -c.

Here are a few example config file entries:
std mknod --mode=640 mem c 1 1 && chown root:kmem mem
std mknod --mode=640 kmem c 1 2 && chown root:kmem kmem
lvm2 mkdir device-mapper; minor=$(grep "[0-9] device-mapper$" /proc/misc | sed 's/[ ]\+device-mapper//'); mknod --mod=600 device-mapper/control c 10 $minor && chown root:root device-mapper/control

I decided to go w/ running commands, instead of going w/ Redhat and
Ian Zimmerman's methods of calling mknod()/link() directly so that
packages could extend makedev without having to touch the source code
itself.  For example, current debian makedev needs to know about
/proc/devices; my lvm2 packages will need makedev to know about
/proc/misc, as the kernel driver registers a misc device w/ a
dynamically allocated minor number.  Other architectures we support make
need to create devices using input from another source; as long as that
source can be expressed in some sort of command, it can be done w/out
having to hack makedev.

The drawbacks, of course, is the fact that shell is ugly (to some ;),
and there will be a lot of fork'ing/exec'ing involved.  Of course, it
will still be less than the old makedev, and it shouldn't be run all
that often (or w/ a massive number of devices).

I kept it simple; it could use features such as being able to put
commands on multiple lines, some sort of macro support.  If folks like
the direction this is headed, I'll do those things; otherwise I'll wait
for/help w/ an alternate implementation.


-- 
Broad surveillance is a mark of bad security.
	-- Bruce Schneier
/*
 * Copyright (C) 2002  Andres Salomon <dilinger@mp3revolution.net>
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#include <dirent.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#ifndef CONFDIR
#define CONFDIR "/etc/makedev.d"
#endif

#ifndef DEVDIR
#define DEVDIR "/dev"
#endif

#ifndef DEFAULT_OS
#define DEFAULT_OS "linux"
#endif

#ifndef PACKAGE
#define PACKAGE "MAKEDEV"
#endif

#ifndef VERSION
#define VERSION "4.0"
#endif

static const char *makedev_cfgdir = CONFDIR;
static const char *makedev_os = DEFAULT_OS;

/*
 * Same as snprintf(), except ensure NUL termination.
 */
static int t_snprintf (char *str, size_t size, const char *fmt, ...)
{
	va_list ap;
	int ret;

	va_start (ap, fmt);
	ret = vsnprintf (str, size, fmt, ap);
	va_end (ap);
	str[size - 1] = '\0';

	return ret;
}

/*
 * Remove all whitespace from the beginning of a string.
 */
static char *str_chug (char *s)
{
	while (isspace (*s))
		s++;

	return s;
}

/*
 * Remove all whitespace from the end of a string.
 */
static char *str_chomp (char *s)
{
	char *ret = s;

	s = &s[strlen (s) - 1];
	while (isspace (*s)) {
		*s = '\0';
		if (s == ret)
			break;
		s--;
	}

	return ret;
}

/*
 * Return the next word from a string, updating and removing the word from
 * the passed string.
 */
static char *str_get_next_word (char **s)
{
	char *ret;

	ret = *s = str_chug (*s);
	
	while (!isspace (**s) && **s)
		(*s)++;

	if (**s) {
		**s = '\0';
		(*s)++;
	}

	return ret;
}

/*
 * Check a file for information about creating devname.  Syntax is:
 * <name> <command> ...
 * If <name> matches 'devname', then run the command.  <name> may also be
 * processed if it's a special value; 
 */
static void scan_file (const char *f, const char *devname)
{
	FILE *fp;
	char buf[512], *s, *next;
	size_t line_nr = 0;

	fp = fopen (f, "r");
	if (fp == NULL) {
		fprintf (stderr, "Warning: cannot read %s: %s\n", f,
				strerror (errno));
		return;
	}

	while ((s = fgets (buf, sizeof (buf), fp)) != NULL) {
		line_nr++;
		next = str_get_next_word (&s);
		
		switch (*next) {
			case '#':
			case '\0':
				/* skip comments/blank lines */
				break;

			default:
				/* name; only process if creating */
				if (strcmp (devname, next) == 0) {
					switch (fork ()) {
						case -1:
							fprintf (stderr, "Error: unable to fork: %s\n", strerror (errno));
							break;
						case 0:
							execl ("/bin/sh", "/bin/sh", "-c", s, NULL);
							fprintf (stderr, "Error: exec failed: %s\n", strerror (errno));
							break;
						default:
							waitpid (0, NULL, 0);
					}
				}
				break;
		}
	}

	fclose (fp);
}

/*
 * Given a device name, search for it; this scans the cfgdir for files
 * containing relevant device info, searching for instructions on how to
 * create.  Only files w/out extensions, or files w/ extensions of the
 * same OS are scanned.
 */
static void create_device (const char *devname)
{
	struct dirent *file;
	char s[256], *ext;
	struct stat buf;
	DIR *d;

	d = opendir (makedev_cfgdir);
	if (d == NULL) {
		fprintf (stderr, "Error: cannot read %s: %s\n", makedev_cfgdir,
				strerror (errno));
		exit (1);
	}
	
	while ((file = readdir (d))) {
		if (strcmp (file->d_name, ".") == 0 ||
				strcmp (file->d_name, "..") == 0)
			continue;

		ext = strrchr (file->d_name, '.');
		if (ext != NULL && strcmp (&ext[1], makedev_os) == 0) {
			/* extension matching OS */
			t_snprintf (s, sizeof (s), "%s/%s", makedev_cfgdir,
					file->d_name);
			scan_file (s, devname);
		}
		else if (ext == NULL) {
			/* no extension */
			t_snprintf (s, sizeof (s), "%s/%s.%s", makedev_cfgdir,
					file->d_name, makedev_os);
			if (stat (s, &buf) == -1) {
				t_snprintf (s, sizeof (s), "%s/%s", 
						makedev_cfgdir, file->d_name);
				scan_file (s, devname);
			}
		}
	}

	closedir (d);
}

static void usage (const char *argv0, int to_stderr)
{
	FILE *out = to_stderr ? stderr : stdout;

	fprintf (out, "Usage: %s [options] [device list ...]\n\n"
			"Options:\n"
			" -c <configdir>  config file directory [" CONFDIR "]\n"
			" -d <devicedir>  device directory [" DEVDIR "]\n"
			" -h              display this screen and exit\n"
			" -o <os name>    target OS [" DEFAULT_OS "]\n"
			" -v              display version info and exit\n",
			argv0);

	exit (to_stderr);
}

int main (int argc, char **argv)
{
	char cwd[PATH_MAX+1];
	char *devdir = NULL;
	int c;

	while ((c = getopt (argc, argv, "c:d:ho:v")) != -1) {
		switch (c) {
			case 'c':
				makedev_cfgdir = strdup (optarg);
				break;
			case 'd':
				if (devdir)
					free (devdir);
				devdir = strdup (optarg);
				break;
			case 'h':
				usage (argv[0], 0);
				break;
			case 'o':
				makedev_os = strdup (optarg);
				break;
			case 'v':
				printf (PACKAGE " " VERSION "\n");
				return 0;
			default:
				usage (argv[0], 1);
				break;
		}
	}

	if (optind >= argc)
		usage (argv[0], 1);

	/* TODO: check for $devdir/.devfsd */

	getcwd (cwd, sizeof (cwd));
	if (chdir (devdir ? devdir : DEVDIR) == -1) {
		fprintf (stderr, "Error: cannot change directory to %s: %s\n",
				devdir ? devdir : DEVDIR, strerror (errno));
		return 1;
	}
	
	while (optind < argc) {
		create_device (argv[optind]);
		optind++;
	}
	chdir (cwd);

	return 0;
}

Reply to: