replace in-memory rules array with match/action token list

The in-memory rule array of a common desktop distro install took:
  1151088 bytes
with the token list:
  109232 bytes tokens (6827 * 16 bytes), 71302 bytes buffer
This commit is contained in:
Kay Sievers 2008-10-23 00:13:59 +02:00
parent a391f49d7f
commit 6880b25d40
7 changed files with 1354 additions and 1213 deletions

2
TODO
View File

@ -1,5 +1,5 @@
o use node for tmpnode, if it already exists
o add DVB variables to kernel, and drop shell script rule
o rework rules to a match-action list, instead of a rules array
o DEVTYPE for disks is set by the kernel, they will be removed from
the default rules
o "udevadm control" commands will only accept the --<command> syntax

View File

@ -470,8 +470,8 @@ EOF
devpath => "/devices/virtual/tty/console",
exp_name => "foo" ,
rules => <<EOF
ATTRS{dev}=="5:1", NAME="foo"
KERNEL=="console", NAME="TTY"
ATTRS{dev}=="5:1", NAME="foo"
EOF
},
{
@ -546,8 +546,8 @@ EOF
SUBSYSTEMS=="scsi", KERNELS=="*:1", NAME="no-match"
SUBSYSTEMS=="scsi", KERNELS=="*:0:1", NAME="no-match"
SUBSYSTEMS=="scsi", KERNELS=="*:0:0:1", NAME="no-match"
SUBSYSTEMS=="scsi", KERNEL=="0:0:0:0", NAME="before"
SUBSYSTEMS=="scsi", KERNELS=="*", NAME="scsi-0:0:0:0"
SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", NAME="bad"
EOF
},
{
@ -556,8 +556,8 @@ EOF
devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
exp_name => "scsi-0:0:0:0",
rules => <<EOF
SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", NAME="before"
SUBSYSTEMS=="scsi", KERNELS=="*:0", NAME="scsi-0:0:0:0"
SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", NAME="bad"
EOF
},
{
@ -566,8 +566,8 @@ EOF
devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
exp_name => "scsi-0:0:0:0",
rules => <<EOF
SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", NAME="before"
SUBSYSTEMS=="scsi", KERNELS=="*:0:0:0", NAME="scsi-0:0:0:0"
SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", NAME="bad"
EOF
},
{
@ -1154,8 +1154,8 @@ EOF
exp_name => "match",
rules => <<EOF
SUBSYSTEMS=="scsi", KERNEL!="sda1", NAME="matches-but-is-negated"
SUBSYSTEMS=="scsi", KERNEL=="sda1", NAME="before"
SUBSYSTEMS=="scsi", KERNEL!="xsda1", NAME="match"
SUBSYSTEMS=="scsi", KERNEL=="sda1", NAME="wrong"
EOF
},
{
@ -1165,8 +1165,8 @@ EOF
exp_name => "not-anything",
rules => <<EOF
SUBSYSTEMS=="scsi", SUBSYSTEM=="block", KERNEL!="sda1", NAME="matches-but-is-negated"
SUBSYSTEMS=="scsi", KERNEL=="sda1", NAME="before"
SUBSYSTEMS=="scsi", SUBSYSTEM!="anything", NAME="not-anything"
SUBSYSTEMS=="scsi", KERNEL=="sda1", NAME="wrong"
EOF
},
{
@ -1175,8 +1175,8 @@ EOF
devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
exp_name => "nonzero-program",
rules => <<EOF
SUBSYSTEMS=="scsi", KERNEL=="sda1", NAME="before"
KERNEL=="sda1", PROGRAM!="/bin/false", NAME="nonzero-program"
SUBSYSTEMS=="scsi", KERNEL=="sda1", NAME="wrong"
EOF
},
{
@ -1185,8 +1185,8 @@ EOF
devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
exp_name => "true",
rules => <<EOF
SUBSYSTEMS=="scsi", KERNEL=="sda1", NAME="before"
KERNEL == "sda1" , NAME = "true"
SUBSYSTEMS=="scsi", KERNEL=="sda1", NAME="wrong"
EOF
},
{
@ -1209,8 +1209,8 @@ EOF
rules => <<EOF
ENV{ENV_KEY_TEST}="test"
SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="go", NAME="wrong"
SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="yes", ENV{ACTION}=="add", ENV{DEVPATH}=="/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sdax1", NAME="no"
SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="test", ENV{ACTION}=="add", ENV{DEVPATH}=="/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", NAME="true"
SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="yes", ENV{ACTION}=="add", ENV{DEVPATH}=="*/block/sda/sdax1", NAME="no"
SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="test", ENV{ACTION}=="add", ENV{DEVPATH}=="*/block/sda/sda1", NAME="true"
SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="bad", NAME="bad"
EOF
},
@ -1222,8 +1222,8 @@ EOF
rules => <<EOF
SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true"
SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="yes", NAME="no"
SUBSYSTEMS=="scsi", KERNEL=="sda1", NAME="before"
SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="true", NAME="true"
SUBSYSTEMS=="scsi", KERNEL=="sda1", NAME="bad"
EOF
},
{
@ -1234,9 +1234,9 @@ EOF
rules => <<EOF
SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true"
SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="absolutely-\$env{ASSIGN}"
SUBSYSTEMS=="scsi", KERNEL=="sda1", NAME="before"
SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="yes", NAME="no"
SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="absolutely-true", NAME="true"
SUBSYSTEMS=="scsi", KERNEL=="sda1", NAME="bad"
EOF
},
{
@ -1248,8 +1248,8 @@ EOF
SUBSYSTEM=="block", KERNEL=="*[0-9]", ENV{PARTITION}="true", ENV{MAINDEVICE}="false"
SUBSYSTEM=="block", KERNEL=="*[!0-9]", ENV{PARTITION}="false", ENV{MAINDEVICE}="true"
ENV{MAINDEVICE}=="true", NAME="disk"
SUBSYSTEM=="block", NAME="before"
ENV{PARTITION}=="true", NAME="part"
SUBSYSTEM=="block", NAME="bad"
EOF
},
{
@ -1280,12 +1280,12 @@ SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \\xef\\xe8garbage", R
EOF
},
{
desc => "read sysfs value from device down in the chain",
desc => "read sysfs value from parent device",
subsys => "block",
devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
exp_name => "serial-354172020305000",
rules => <<EOF
KERNEL=="ttyACM*", NAME="serial-%s{serial}"
KERNEL=="ttyACM*", ATTRS{serial}=="?*", NAME="serial-%s{serial}"
EOF
},
{
@ -1311,13 +1311,13 @@ ACTION=="add", KERNEL=="sda", NAME="ok"
EOF
},
{
desc => "apply NAME only once",
desc => "apply NAME final",
subsys => "block",
devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
exp_name => "link",
exp_target => "ok",
rules => <<EOF
KERNEL=="sda", NAME="ok"
KERNEL=="sda", NAME:="ok"
KERNEL=="sda", NAME="not-ok"
KERNEL=="sda", SYMLINK+="link"
EOF
@ -1344,8 +1344,8 @@ EOF
exp_rem_error => "yes",
option => "clean",
rules => <<EOF
KERNEL=="sda", NAME="ok", RUN+="/bin/sh -c 'ln -s `basename \$\$DEVNAME` %r/testsymlink'"
KERNEL=="sda", NAME="not-ok"
KERNEL=="sda", NAME="ok", RUN+="/bin/sh -c 'ln -s `basename \$\$DEVNAME` %r/testsymlink'"
EOF
},
{
@ -1410,12 +1410,12 @@ EOF
desc => "test empty NAME",
subsys => "tty",
devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
exp_name => "node",
exp_name => "<none>",
not_exp_name => "wrong",
exp_add_error => "yes",
rules => <<EOF
KERNEL=="ttyACM[0-9]*", NAME=""
KERNEL=="ttyACM[0-9]*", NAME="wrong"
KERNEL=="ttyACM[0-9]*", NAME=""
EOF
},
{
@ -1424,9 +1424,9 @@ EOF
devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
exp_name => "right",
rules => <<EOF
KERNEL=="ttyACM[0-9]*", NAME="right"
KERNEL=="ttyACM[0-9]*", NAME=""
KERNEL=="ttyACM[0-9]*", NAME="wrong"
KERNEL=="ttyACM[0-9]*", NAME="right"
EOF
},
{
@ -1435,8 +1435,8 @@ EOF
devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
exp_name => "right",
rules => <<EOF
KERNEL=="ttyACM*", NAME="before"
KERNEL=="ttyACM*|nothing", NAME="right"
KERNEL=="ttyACM*", NAME="wrong"
EOF
},
{
@ -1446,8 +1446,8 @@ EOF
exp_name => "right",
rules => <<EOF
KERNEL=="dontknow*|*nothing", NAME="nomatch"
KERNEL=="ttyACM*", NAME="before"
KERNEL=="dontknow*|ttyACM*|nothing*", NAME="right"
KERNEL=="ttyACM*", NAME="wrong"
EOF
},
{
@ -1479,9 +1479,10 @@ EOF
rules => <<EOF
KERNEL=="sda1", GOTO="TEST"
KERNEL=="sda1", NAME="wrong"
KERNEL=="sda1", GOTO="BAD"
KERNEL=="sda1", NAME="", LABEL="NO"
KERNEL=="sda1", NAME="right", LABEL="TEST"
KERNEL=="sda1", NAME="wrong2"
KERNEL=="sda1", LABEL="BAD"
EOF
},
{
@ -1615,6 +1616,7 @@ EOF
exp_name => "sda",
exp_perms => "0:0:0400",
rules => <<EOF
KERNEL=="sda", MODE="666"
KERNEL=="sda", PROGRAM=="/bin/echo 0 0 0400", OWNER="%c{1}", GROUP="%c{2}", MODE="%c{3}"
EOF
},
@ -1623,8 +1625,9 @@ EOF
subsys => "block",
devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
exp_name => "sda",
exp_perms => "0:0:0400",
exp_perms => "0:0:0660",
rules => <<EOF
KERNEL=="sda", MODE="440"
KERNEL=="sda", PROGRAM=="/bin/echo 0 0 0400letsdoabuffferoverflow0123456789012345789012345678901234567890", OWNER="%c{1}", GROUP="%c{2}", MODE="%c{3}"
EOF
},

View File

@ -37,14 +37,10 @@ struct udev_event *udev_event_new(struct udev_device *dev)
event = calloc(1, sizeof(struct udev_event));
if (event == NULL)
return NULL;
event->dev = dev;
event->udev = udev_device_get_udev(dev);
udev_list_init(&event->run_list);
event->mode = 0660;
util_strlcpy(event->owner, "0", sizeof(event->owner));
util_strlcpy(event->group, "0", sizeof(event->group));
dbg(event->udev, "allocated event %p\n", event);
return event;
}
@ -95,69 +91,6 @@ static int get_format_len(struct udev *udev, char **str)
return -1;
}
/* handle "[<SUBSYSTEM>/<KERNEL>]<attribute>" format */
int udev_event_apply_subsys_kernel(struct udev_event *udev_event, const char *string,
char *result, size_t maxsize, int read_value)
{
char temp[UTIL_PATH_SIZE];
char *subsys;
char *sysname;
char *attr;
struct udev_device *dev;
if (string == NULL)
string = result;
if (string[0] != '[')
return -1;
util_strlcpy(temp, string, sizeof(temp));
subsys = &temp[1];
sysname = strchr(subsys, '/');
if (sysname == NULL)
return -1;
sysname[0] = '\0';
sysname = &sysname[1];
attr = strchr(sysname, ']');
if (attr == NULL)
return -1;
attr[0] = '\0';
attr = &attr[1];
if (attr[0] == '/')
attr = &attr[1];
if (attr[0] == '\0')
attr = NULL;
if (read_value && attr == NULL)
return -1;
dev = udev_device_new_from_subsystem_sysname(udev_event->udev, subsys, sysname);
if (dev == NULL)
return -1;
if (read_value) {
const char *val;
val = udev_device_get_sysattr_value(dev, attr);
if (val != NULL)
util_strlcpy(result, val, maxsize);
else
result[0] = '\0';
info(udev_event->udev, "value '[%s/%s]%s' is '%s'\n", subsys, sysname, attr, result);
} else {
util_strlcpy(result, udev_device_get_syspath(dev), maxsize);
if (attr != NULL) {
util_strlcat(result, "/", maxsize);
util_strlcat(result, attr, maxsize);
}
info(udev_event->udev, "path '[%s/%s]%s' is '%s'\n", subsys, sysname, attr, result);
}
udev_device_unref(dev);
return 0;
}
void udev_event_apply_format(struct udev_event *event, char *string, size_t maxsize)
{
struct udev_device *dev = event->dev;
@ -349,36 +282,23 @@ found:
if (attr == NULL)
err(event->udev, "missing file parameter for attr\n");
else {
const char *val;
char value[UTIL_NAME_SIZE] = "";
size_t size;
udev_event_apply_subsys_kernel(event, attr, value, sizeof(value), 1);
util_resolve_subsys_kernel(event->udev, attr, value, sizeof(value), 1);
val = udev_device_get_sysattr_value(event->dev, attr);
if (val != NULL)
util_strlcpy(value, val, sizeof(value));
/* try the current device, other matches may have selected */
if (value[0] == '\0' && event->dev_parent != NULL && event->dev_parent != event->dev) {
const char *val;
val = udev_device_get_sysattr_value(event->dev_parent, attr);
if (val != NULL)
util_strlcpy(value, val, sizeof(value));
}
/* look at all devices along the chain of parents */
if (value[0] == '\0') {
struct udev_device *dev_parent = dev;
const char *val;
do {
dbg(event->udev, "looking at '%s'\n", udev_device_get_syspath(dev_parent));
val = udev_device_get_sysattr_value(dev_parent, attr);
if (val != NULL) {
util_strlcpy(value, val, sizeof(value));
break;
}
dev_parent = udev_device_get_parent(dev_parent);
} while (dev_parent != NULL);
}
if (value[0]=='\0')
break;
@ -590,17 +510,29 @@ int udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules)
dbg(event->udev, "device node add '%s'\n", udev_device_get_devpath(dev));
udev_rules_get_name(rules, event);
udev_rules_apply_to_event(rules, event);
if (event->tmp_node[0] != '\0') {
dbg(event->udev, "removing temporary device node\n");
util_unlink_secure(event->udev, event->tmp_node);
event->tmp_node[0] = '\0';
}
if (event->ignore_device) {
info(event->udev, "device event will be ignored\n");
goto exit;
}
if (event->name[0] == '\0') {
if (event->name_ignore) {
info(event->udev, "device node creation supressed\n");
goto exit;
}
if (event->name[0] == '\0') {
info(event->udev, "no node name set, will use kernel name '%s'\n",
udev_device_get_sysname(event->dev));
util_strlcpy(event->name, udev_device_get_sysname(event->dev), sizeof(event->name));
}
/* set device node name */
util_strlcpy(filename, udev_get_dev_path(event->udev), sizeof(filename));
util_strlcat(filename, "/", sizeof(filename));
@ -610,15 +542,17 @@ int udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules)
/* read current database entry; cleanup, if it is known device */
dev_old = udev_device_new_from_syspath(event->udev, udev_device_get_syspath(dev));
if (dev_old != NULL) {
info(event->udev, "device '%s' already in database, updating\n",
udev_device_get_devpath(dev));
udev_node_update_old_links(dev, dev_old, event->test);
if (udev_device_get_devnode(dev_old) != NULL) {
info(event->udev, "device node '%s' already in database, updating\n",
udev_device_get_devnode(dev_old));
udev_node_update_old_links(dev, dev_old, event->test);
}
udev_device_unref(dev_old);
}
udev_device_update_db(dev);
err = udev_node_add(dev, event->mode, event->owner, event->group, event->test);
err = udev_node_add(dev, event->mode, event->uid, event->gid, event->test);
if (err != 0)
goto exit;
@ -629,7 +563,7 @@ int udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules)
if (strcmp(udev_device_get_subsystem(dev), "net") == 0 && strcmp(udev_device_get_action(dev), "add") == 0) {
dbg(event->udev, "netif add '%s'\n", udev_device_get_devpath(dev));
udev_rules_get_name(rules, event);
udev_rules_apply_to_event(rules, event);
if (event->ignore_device) {
info(event->udev, "device event will be ignored\n");
goto exit;
@ -684,7 +618,7 @@ int udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules)
udev_device_set_devnode(dev, devnode);
}
udev_rules_get_run(rules, event);
udev_rules_apply_to_event(rules, event);
if (event->ignore_device) {
info(event->udev, "device event will be ignored\n");
goto exit;
@ -700,7 +634,7 @@ int udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules)
}
/* default devices */
udev_rules_get_run(rules, event);
udev_rules_apply_to_event(rules, event);
if (event->ignore_device)
info(event->udev, "device event will be ignored\n");
exit:

View File

@ -390,49 +390,20 @@ void udev_node_update_old_links(struct udev_device *dev, struct udev_device *dev
}
}
int udev_node_add(struct udev_device *dev, mode_t mode, const char *owner, const char *group, int test)
int udev_node_add(struct udev_device *dev, mode_t mode, uid_t uid, gid_t gid, int test)
{
struct udev *udev = udev_device_get_udev(dev);
uid_t uid;
gid_t gid;
int i;
int num;
struct udev_list_entry *list_entry;
int err = 0;
util_create_path(udev, udev_device_get_devnode(dev));
if (strcmp(owner, "root") == 0)
uid = 0;
else {
char *endptr;
unsigned long id;
id = strtoul(owner, &endptr, 10);
if (endptr[0] == '\0')
uid = (uid_t) id;
else
uid = util_lookup_user(udev, owner);
}
if (strcmp(group, "root") == 0)
gid = 0;
else {
char *endptr;
unsigned long id;
id = strtoul(group, &endptr, 10);
if (endptr[0] == '\0')
gid = (gid_t) id;
else
gid = util_lookup_group(udev, group);
}
info(udev, "creating device node '%s', devnum=%d:%d, mode=%#o, uid=%d, gid=%d\n",
udev_device_get_devnode(dev),
major(udev_device_get_devnum(dev)), minor(udev_device_get_devnum(dev)),
mode, uid, gid);
util_create_path(udev, udev_device_get_devnode(dev));
if (!test)
if (udev_node_mknod(dev, NULL, makedev(0,0), mode, uid, gid) != 0) {
err = -1;

File diff suppressed because it is too large Load Diff

View File

@ -61,18 +61,19 @@ struct udev_event {
struct udev_device *dev;
struct udev_device *dev_parent;
int devlink_final;
int owner_final;
int group_final;
int mode_final;
char tmp_node[UTIL_PATH_SIZE];
char program_result[UTIL_PATH_SIZE];
int run_final;
char name[UTIL_PATH_SIZE];
int name_final;
int name_ignore;
char tmp_node[UTIL_PATH_SIZE];
mode_t mode;
char owner[UTIL_NAME_SIZE];
char group[UTIL_NAME_SIZE];
int mode_final;
uid_t uid;
int owner_final;
gid_t gid;
int group_final;
struct udev_list_node run_list;
int run_final;
int ignore_device;
int test;
@ -86,8 +87,7 @@ struct udev_event {
struct udev_rules;
extern struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names);
extern void udev_rules_unref(struct udev_rules *rules);
extern int udev_rules_get_name(struct udev_rules *rules, struct udev_event *event);
extern int udev_rules_get_run(struct udev_rules *rules, struct udev_event *event);
extern int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event);
/* udev-event.c */
extern struct udev_event *udev_event_new(struct udev_device *dev);
@ -100,7 +100,7 @@ extern int udev_event_apply_subsys_kernel(struct udev_event *event, const char *
/* udev-node.c */
extern int udev_node_mknod(struct udev_device *dev, const char *file, dev_t devnum, mode_t mode, uid_t uid, gid_t gid);
extern int udev_node_add(struct udev_device *dev, mode_t mode, const char *owner, const char *group, int test);
extern int udev_node_add(struct udev_device *dev, mode_t mode, uid_t uid, gid_t gid, int test);
extern int udev_node_remove(struct udev_device *dev, int test);
extern void udev_node_update_old_links(struct udev_device *dev, struct udev_device *dev_old, int test);

View File

@ -184,8 +184,7 @@
<varlistentry>
<term><option>ATTR{<replaceable>filename</replaceable>}</option></term>
<listitem>
<para>Match sysfs attribute values of the event device. Up to five
<option>ATTR</option> keys can be specified per rule. Trailing
<para>Match sysfs attribute values of the event device. Trailing
whitespace in the attribute values is ignored, if the specified match
value does not contain trailing whitespace itself. Depending on the type
of operator, this key is also used to set the value of a sysfs attribute.
@ -218,7 +217,7 @@
<term><option>ATTRS{<replaceable>filename</replaceable>}</option></term>
<listitem>
<para>Search the devpath upwards for a device with matching sysfs attribute values.
Up to five <option>ATTRS</option> keys can be specified per rule, but all of them
If multiple <option>ATTRS</option> matches are specified, all of them
must match on the same device. Trailing whitespace in the attribute values is ignored,
if the specified match value does not contain trailing whitespace itself.</para>
</listitem>
@ -227,9 +226,7 @@
<varlistentry>
<term><option>ENV{<replaceable>key</replaceable>}</option></term>
<listitem>
<para>Match against the value of an environment variable. Up to five <option>ENV</option>
keys can be specified per rule. Depending on the type of operator, this key is also used
to export a variable to the environment.</para>
<para>Match against a device property value.</para>
</listitem>
</varlistentry>
@ -244,10 +241,10 @@
<varlistentry>
<term><option>PROGRAM</option></term>
<listitem>
<para>Execute external program. The key is true, if the program returns
with exit code zero. The whole event environment is available to the
executed program. The program's output printed to stdout, is available in
the RESULT key.</para>
<para>Execute a program. The key is true, if the program returns
successfully. The device properties are made available to the
executed program in the environment. The program's output printed to
stdout, is available in the RESULT key.</para>
</listitem>
</varlistentry>
@ -329,8 +326,7 @@
<varlistentry>
<term><option>ENV{<replaceable>key</replaceable>}</option></term>
<listitem>
<para>Export a variable to the environment. Depending on the type of operator,
this key is also to match against an environment variable.</para>
<para>Set a device property value.</para>
</listitem>
</varlistentry>
@ -368,7 +364,7 @@
<varlistentry>
<term><option>IMPORT{<replaceable>type</replaceable>}</option></term>
<listitem>
<para>Import a set of variables into the event environment,
<para>Import a set of variables as device properties,
depending on <replaceable>type</replaceable>:</para>
<variablelist>
<varlistentry>
@ -473,7 +469,7 @@
<option>OWNER</option>, <option>GROUP</option>, <option>MODE</option> and <option>RUN</option>
fields support simple printf-like string substitutions. The <option>RUN</option>
format chars gets applied after all rules have been processed, right before the program
is executed. It allows the use of the complete environment set by earlier matching
is executed. It allows the use of device properties set by earlier matching
rules. For all other fields, substitutions are applied while the individual rule is
being processed. The available substitutions are:</para>
<variablelist>
@ -532,7 +528,7 @@
<varlistentry>
<term><option>$env{<replaceable>key</replaceable>}</option>, <option>%E{<replaceable>key</replaceable>}</option></term>
<listitem>
<para>The value of an environment variable.</para>
<para>A device property value.</para>
</listitem>
</varlistentry>