Upgrade mdev to allow commands to be run on create/delete.

Both Jason Schoon and Giuseppe Ciotta deserve credit for this, I used elements
of both.  It's been upgraded so that you can specify that a given command
should run at create, at delete, or at both using different special characters
(@, $, and * respectively).  It uses the system() method of running command
lines which means you can use environment variables on the command line (it
sets $MDEV to the name of the current device being created/deleted, which is
useful if you matched it via regex), and the documentation warns that you need
a /bin/sh to make that work, so you probably want to pick a default shell.
This commit is contained in:
Rob Landley 2006-06-26 14:11:33 +00:00
parent cf7577d417
commit ef10d52745
3 changed files with 130 additions and 105 deletions

View File

@ -1799,19 +1799,25 @@ USE_FEATURE_DATE_ISOFMT( \
"\t-s\tScan /sys and populate /dev during system boot\n\n" \ "\t-s\tScan /sys and populate /dev during system boot\n\n" \
"Called with no options (via hotplug) it uses environment variables\n" \ "Called with no options (via hotplug) it uses environment variables\n" \
"to determine which device to add/remove." "to determine which device to add/remove."
#ifdef CONFIG_FEATURE_MDEV_CONFIG #define mdev_notes_usage "" \
#define mdev_notes_usage \ USE_FEATURE_MDEV_CONFIG( \
"The mdev config file contains lines that look like:\n" \ "The mdev config file contains lines that look like:\n" \
" hd[a-z][0-9]* 0:3 660\n\n" \ " hd[a-z][0-9]* 0:3 660\n\n" \
"That's device name (with regex match), uid:gid, and permissions.\n\n" \ "That's device name (with regex match), uid:gid, and permissions.\n\n" \
"Optionally, that can be followed (on the same line) by an asterisk\n" \ USE_FEATURE_MDEV_EXEC( \
"and a command line to run after creating the corresponding device(s),\n"\ "Optionally, that can be followed (on the same line) by a special character\n" \
"ala:\n\n" \ "and a command line to run after creating/before deleting the corresponding\n" \
" hdc root:cdrom 660 *ln -s hdc cdrom\n\n" \ "device(s). The environment variable $MDEV indicates the active device node\n" \
"(which is useful if it's a regex match). For example:\n\n" \
" hdc root:cdrom 660 *ln -s $MDEV cdrom\n\n" \
"The special characters are @ (run after creating), $ (run before deleting),\n" \
"and * (run both after creating and before deleting). The commands run in\n" \
"the /dev directory, and use system() which calls /bin/sh.\n\n" \
) \
"Config file parsing stops on the first matching line. If no config\n"\ "Config file parsing stops on the first matching line. If no config\n"\
"entry is matched, devices are created with default 0:0 660. (Make\n"\ "entry is matched, devices are created with default 0:0 660. (Make\n"\
"the last line match .* to override this.)\n\n" "the last line match .* to override this.)\n\n" \
#endif )
#define mesg_trivial_usage \ #define mesg_trivial_usage \
"[y|n]" "[y|n]"

View File

@ -265,16 +265,31 @@ config CONFIG_FEATURE_MDEV_CONF
That's device name (with regex match), uid:gid, and permissions. That's device name (with regex match), uid:gid, and permissions.
Optionally, that can be followed (on the same line) by an asterisk
and a command line to run after creating the corresponding device(s),
ala:
hdc root:cdrom 660 *ln -s hdc cdrom
Config file parsing stops on the first matching line. If no config Config file parsing stops on the first matching line. If no config
entry is matched, devices are created with default 0:0 660. (Make entry is matched, devices are created with default 0:0 660. (Make
the last line match .* to override this.) the last line match .* to override this.)
config CONFIG_FEATURE_MDEV_EXEC
bool "Support command execution at device addition/removal"
default n
depends on CONFIG_FEATURE_MDEV_CONF
help
This adds support for an optional field to /etc/mdev.conf, consisting
of a special character and a command line to run after creating the
corresponding device(s) and before removing, ala:
hdc root:cdrom 660 *ln -s $MDEV cdrom
The $MDEV environment variable is set to the name of the device.
The special characters and their meanings are:
@ Run after creating the device.
$ Run before removing the device.
* Run both after creating and before removing the device.
Commands are executed via system() so you need /bin/sh, meaning you
probably want to select a default shell in the Shells menu.
config CONFIG_MKSWAP config CONFIG_MKSWAP
bool "mkswap" bool "mkswap"
default n default n

View File

@ -8,25 +8,14 @@
* Licensed under GPLv2 or later, see file LICENSE in this tarball for details. * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
*/ */
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include "busybox.h" #include "busybox.h"
#include <ctype.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/sysmacros.h>
#include "xregex.h" #include "xregex.h"
#define DEV_PATH "/dev" #define DEV_PATH "/dev"
#define MDEV_CONF "/etc/mdev.conf"
#include <busybox.h>
struct mdev_globals struct mdev_globals
{ {
@ -36,14 +25,15 @@ struct mdev_globals
#define bbg mdev_globals #define bbg mdev_globals
/* mknod in /dev based on a path like "/sys/block/hda/hda1" */ /* mknod in /dev based on a path like "/sys/block/hda/hda1" */
static void make_device(char *path) static void make_device(char *path, int delete)
{ {
char *device_name, *s; char *device_name;
int major, minor, type, len, fd; int major, minor, type, len, fd;
int mode = 0660; int mode = 0660;
uid_t uid = 0; uid_t uid = 0;
gid_t gid = 0; gid_t gid = 0;
char *temp = path + strlen(path); char *temp = path + strlen(path);
char *command = NULL;
/* Try to read major/minor string. Note that the kernel puts \n after /* Try to read major/minor string. Note that the kernel puts \n after
* the data, so we don't need to worry about null terminating the string * the data, so we don't need to worry about null terminating the string
@ -69,7 +59,7 @@ static void make_device(char *path)
char *conf, *pos, *end; char *conf, *pos, *end;
/* mmap the config file */ /* mmap the config file */
if (-1 != (fd=open(MDEV_CONF,O_RDONLY))) { if (-1 != (fd=open("/etc/mdev.conf",O_RDONLY))) {
len = lseek(fd, 0, SEEK_END); len = lseek(fd, 0, SEEK_END);
conf = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0); conf = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
if (conf) { if (conf) {
@ -86,88 +76,89 @@ static void make_device(char *path)
; ;
/* Three fields: regex, uid:gid, mode */ /* Three fields: regex, uid:gid, mode */
for (field=3; field; field--) { for (field=0; field < (3 + ENABLE_FEATURE_MDEV_EXEC);
field++)
{
/* Skip whitespace */ /* Skip whitespace */
while (pos<end && isspace(*pos)) while (pos<end && isspace(*pos)) pos++;
pos++; if (pos==end || *pos=='#') break;
if (pos==end || *pos=='#') for (end2=pos;
break; end2<end && !isspace(*end2) && *end2!='#'; end2++)
for (end2=pos; end2<end && !isspace(*end2) && *end2!='#'; end2++)
; ;
switch (field) { if (!field) {
/* Regex to match this device */ /* Regex to match this device */
case 3:
{ char *regex = strndupa(pos, end2-pos);
char *regex = strndupa(pos,end2-pos);
regex_t match; regex_t match;
regmatch_t off; regmatch_t off;
int result; int result;
/* Is this it? */ /* Is this it? */
xregcomp(&match,regex,REG_EXTENDED); xregcomp(&match,regex, REG_EXTENDED);
result = regexec(&match,device_name,1,&off,0); result = regexec(&match, device_name, 1, &off, 0);
regfree(&match); regfree(&match);
/* If not this device, skip rest of line */ /* If not this device, skip rest of line */
if (result || off.rm_so || off.rm_eo!=strlen(device_name)) if (result || off.rm_so
goto end_line; || off.rm_eo != strlen(device_name))
break; break;
}
} else if (field == 1) {
/* uid:gid */ /* uid:gid */
case 2:
{ char *s, *s2;
char *s2;
/* Find : */ /* Find : */
for(s=pos; s<end2 && *s!=':'; s++) for(s=pos; s<end2 && *s!=':'; s++)
; ;
if (s == end2) if (s == end2) break;
goto end_line;
/* Parse UID */ /* Parse UID */
uid = strtoul(pos,&s2,10); uid = strtoul(pos,&s2,10);
if (s != s2) { if (s != s2) {
struct passwd *pass; struct passwd *pass;
pass = getpwnam(strndupa(pos,s-pos)); pass = getpwnam(strndupa(pos, s-pos));
if (!pass) if (!pass) break;
goto end_line;
uid = pass->pw_uid; uid = pass->pw_uid;
} }
s++; s++;
/* parse GID */ /* parse GID */
gid = strtoul(s,&s2,10); gid = strtoul(s, &s2, 10);
if (end2 != s2) { if (end2 != s2) {
struct group *grp; struct group *grp;
grp = getgrnam(strndupa(s,end2-s)); grp = getgrnam(strndupa(s, end2-s));
if (!grp) if (!grp) break;
goto end_line;
gid = grp->gr_gid; gid = grp->gr_gid;
} }
} else if (field == 2) {
/* mode */
mode = strtoul(pos, &pos, 8);
if (pos != end2) break;
} else if (ENABLE_FEATURE_MDEV_EXEC && field == 3) {
// Command to run
char *s = "@$*", *s2;
if (!(s2 = strchr(s, *pos++))) {
// Force error
field = 1;
break; break;
} }
/* mode */ if ((s2-s+1) & (1<<delete))
case 1: command = bb_xstrndup(pos, end-pos);
{
mode = strtoul(pos,&pos,8);
if (pos != end2)
goto end_line;
else
goto found_device;
}
} }
pos = end2; pos = end2;
} }
end_line:
/* Did everything parse happily? */ /* Did everything parse happily? */
if (field && field!=3)
bb_error_msg_and_die("Bad line %d",line); if (field > 2) break;
if (field) bb_error_msg_and_die("Bad line %d",line);
/* Next line */ /* Next line */
pos = ++end; pos = ++end;
} }
found_device:
munmap(conf, len); munmap(conf, len);
} }
close(fd); close(fd);
@ -175,13 +166,29 @@ found_device:
} }
umask(0); umask(0);
if (!delete) {
if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST) if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST)
bb_perror_msg_and_die("mknod %s failed", device_name); bb_perror_msg_and_die("mknod %s failed", device_name);
if (major==bbg.root_major && minor==bbg.root_minor) if (major == bbg.root_major && minor == bbg.root_minor)
symlink(device_name, "root"); symlink(device_name, "root");
if (ENABLE_FEATURE_MDEV_CONF) chown(device_name, uid, gid); if (ENABLE_FEATURE_MDEV_CONF) chown(device_name, uid, gid);
}
if (command) {
int rc;
char *s;
s=bb_xasprintf("MDEV=%s",device_name);
putenv(s);
rc = system(command);
s[4]=0;
putenv(s);
free(s);
free(command);
if (rc == -1) bb_perror_msg_and_die("Couldn't run %s", command);
}
if (delete) unlink(device_name);
} }
/* Recursive search of /sys/block or /sys/class. path must be a writeable /* Recursive search of /sys/block or /sys/class. path must be a writeable
@ -212,7 +219,7 @@ static void find_dev(char *path)
/* If there's a dev entry, mknod it */ /* If there's a dev entry, mknod it */
if (!strcmp(entry->d_name, "dev")) make_device(path); if (!strcmp(entry->d_name, "dev")) make_device(path, 0);
} }
closedir(dir); closedir(dir);
@ -247,12 +254,9 @@ int mdev_main(int argc, char *argv[])
if (!action || !env_path) if (!action || !env_path)
bb_show_usage(); bb_show_usage();
if (!strcmp(action, "add")) {
sprintf(temp, "/sys%s", env_path); sprintf(temp, "/sys%s", env_path);
make_device(temp); if (!strcmp(action, "add")) make_device(temp,0);
} else if (!strcmp(action, "remove")) { else if (!strcmp(action, "remove")) make_device(temp,1);
unlink(strrchr(env_path, '/') + 1);
}
} }
if (ENABLE_FEATURE_CLEAN_UP) RELEASE_CONFIG_BUFFER(temp); if (ENABLE_FEATURE_CLEAN_UP) RELEASE_CONFIG_BUFFER(temp);