linux-hotplug.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] udev: add rule based program execution
@ 2005-03-29 14:54 Kay Sievers
  2005-03-29 15:20 ` Kevin P. Fleming
                   ` (9 more replies)
  0 siblings, 10 replies; 11+ messages in thread
From: Kay Sievers @ 2005-03-29 14:54 UTC (permalink / raw)
  To: linux-hotplug

This patch on top of the current bk-tree allows to specify programs to
be executed after the device handling. This is meant as a general
replacement for the dev.d/ logic which is known to be the main reason for
the boot-time delay on udev systems.

This patch introduces the HOTPLUG key. Every matching rule can add one
HOTPLUG key to a list of program to be executed after the node is
created/removed.

The HOTPLUG key can be used in any kind of rule:
  HOTPLUG="/sbin/program"
    will execute the program for every event that reaches udev. Just
    like /etc/dev.d/default/*.

  SUBSYSTEM="block", HOTPLUG="/sbin/program"
    will execute the program only for block device events.

  ACTION="remove", SUBSYSTEM="block", HOTPLUG="/sbin/program"
    will execute the program, if a block device is removed.

  ACTION="add, BUS="usb", KERNEL="video*", SYSFS{idProduct}="a511", HOTPLUG="/sbin/webcam-program"
    will execute the program only if my old webcam is connected.

To get this working, I changed the logic to read all rules and not to
stop at the first match. If a rule should be the last one to be applied
to a device it must use OPTION="last_rule". After the first rule that
assigns a NAME to a device, all later rules with a NAME key will be
ignored, so it should not change the current behavior too much.
The rules are also read on "remove" events now.

On my box I've removed all scripts from /etc/dev.d and replaced it with
following rules in the main rules file:

  ACTION="add", SUBSYSTEM="sound", HOTPLUG="/etc/udev/scripts/alsa.dev"
  ACTION="add", KERNEL="fd*", HOTPLUG="/etc/udev/scripts/MAKEDEV.dev"
  KERNEL="ttyUSB*", HOTPLUG="/etc/udev/scripts/test-serial-device"
  SUBSYSTEM="tty", OPTIONS="last_rule"
  HOTPLUG="/etc/udev/scripts/pam_console.dev"
  HOTPLUG="/etc/udev/sripts/log.dev"

This cuts down the execution-time of udevstart from ~10 seconds to ~2
seconds, cause the hundreds of tty events will not call any script.

Thanks,
Kay

=== udev.8.in 1.83 vs edited ==--- 1.83/udev.8.in	2005-03-26 16:11:01 +01:00
+++ edited/udev.8.in	2005-03-29 14:51:32 +02:00
@@ -111,6 +111,9 @@ Match the kernel device name.
 .B SUBSYSTEM
 Match the kernel subsystem name.
 .TP
+.B ACTION
+Match the kernel action name.
+.TP
 .B DRIVER
 Match the kernel driver name.
 .TP
@@ -170,6 +173,9 @@ distribution provided rules file.
 The permissions for the device node. Every specified value overwrites the
 compiled-in default value.
 .TP
+.B HOTPLUG
+Add a program to the list of programs to be executed for a specific device.
+.TP
 .B OPTIONS
 .B last_rule
 will be the last rule applied. No later rules will have any effect.
@@ -234,6 +240,9 @@ The node name of the parent device.
 .BI %s{ filename }
 The content of a sysfs attribute.
 .TP
+.B %r
+The udev_root value.
+.TP
 .B %e
 If a device node already exists with the name, the smallest positive
 decimal integer N is substituted such that the resulting name doesn't
@@ -344,6 +353,9 @@ config file.
 .B UDEV_LOG
 Overrides the log priority specified in the config file.
 .TP
+.B UDEV_HOTPLUG
+If set to "0", it disables the execution of programs added by rules.
+.TP
 .B UDEV_NO_DEVD
 The default behavior of
 .B udev
@@ -356,7 +368,6 @@ will skip this step.
 .nf
 /sbin/udev                           udev program
 /etc/udev/*                          udev config files
-/etc/hotplug.d/default/udev.hotplug  hotplug symlink to udev program
 /etc/dev.d/*                         programs invoked by udev
 .fi
 .SH "SEE ALSO"
=== udev.c 1.113 vs edited ==--- 1.113/udev.c	2005-03-29 03:25:52 +02:00
+++ edited/udev.c	2005-03-29 15:20:13 +02:00
@@ -157,6 +157,8 @@ int main(int argc, char *argv[], char *e
 	}
 
 	if (udev.type = DEV_BLOCK || udev.type = DEV_CLASS || udev.type = DEV_NET) {
+		udev_rules_init();
+
 		if (strcmp(action, "add") = 0) {
 			/* wait for sysfs and possibly add node */
 			dbg("udev add");
@@ -178,9 +180,6 @@ int main(int argc, char *argv[], char *e
 
 			wait_for_class_device(class_dev, &error);
 
-			/* init rules */
-			udev_rules_init();
-
 			/* name, create node, store in db */
 			retval = udev_add_device(&udev, class_dev);
 
@@ -195,8 +194,23 @@ int main(int argc, char *argv[], char *e
 				goto hotplug;
 			}
 
+			udev_rules_get_hotplug(&udev);
+			if (udev.ignore_device) {
+				dbg("device event will be ignored");
+				goto hotplug;
+			}
+
 			/* get node from db, remove db-entry, delete created node */
 			retval = udev_remove_device(&udev);
+		}
+
+		/* execute collected hotplug programs */
+		if (udev_hotplug && !list_empty(&udev.hotplug_list)) {
+			struct name_entry *name_loop;
+
+			dbg("executing hotplug program list");
+			list_for_each_entry(name_loop, &udev.hotplug_list, node)
+				execute_command(name_loop->name, udev.subsystem);
 		}
 
 		/* run dev.d/ scripts if we created/deleted a node or changed a netif name */
=== udev.h 1.93 vs edited ==--- 1.93/udev.h	2005-03-26 16:11:01 +01:00
+++ edited/udev.h	2005-03-29 15:15:52 +02:00
@@ -58,6 +58,7 @@ enum device_type {
 struct udevice {
 	char devpath[PATH_SIZE];
 	char subsystem[NAME_SIZE];
+	char action[NAME_SIZE];
 
 	enum device_type type;
 	char name[PATH_SIZE];
@@ -67,9 +68,11 @@ struct udevice {
 	char group[USER_SIZE];
 	mode_t mode;
 	dev_t devt;
+	struct list_head hotplug_list;
 
 	char tmp_node[PATH_SIZE];
 	int partitions;
+	int ignore_device;
 	int ignore_remove;
 	int config_line;
 	char config_file[PATH_SIZE];
@@ -93,6 +96,7 @@ extern char udev_db_path[PATH_SIZE];
 extern char udev_config_filename[PATH_SIZE];
 extern char udev_rules_filename[PATH_SIZE];
 extern int udev_log_priority;
+extern int udev_hotplug;
 extern int udev_dev_d;
 extern int udev_hotplug_d;
 
=== udev_add.c 1.105 vs edited ==--- 1.105/udev_add.c	2005-03-26 16:11:01 +01:00
+++ edited/udev_add.c	2005-03-29 15:22:52 +02:00
@@ -276,8 +276,11 @@ int udev_add_device(struct udevice *udev
 		}
 	}
 
-	if (udev_rules_get_name(udev, class_dev) != 0)
+	udev_rules_get_name(udev, class_dev);
+	if (udev->ignore_device) {
+		dbg("device event will be ignored");
 		return 0;
+	}
 
 	dbg("adding name='%s'", udev->name);
 
=== udev_config.c 1.38 vs edited ==--- 1.38/udev_config.c	2005-03-27 04:39:11 +02:00
+++ edited/udev_config.c	2005-03-29 14:51:32 +02:00
@@ -44,6 +44,7 @@ char udev_db_path[PATH_SIZE];
 char udev_config_filename[PATH_SIZE];
 char udev_rules_filename[PATH_SIZE];
 int udev_log_priority;
+int udev_hotplug;
 int udev_dev_d;
 int udev_hotplug_d;
 
@@ -217,9 +218,15 @@ void udev_init_config(void)
 	strcpy(udev_config_filename, UDEV_CONFIG_FILE);
 	strcpy(udev_rules_filename, UDEV_RULES_FILE);
 	udev_log_priority = LOG_ERR;
+	udev_hotplug = 1;
 	udev_dev_d = 1;
 	udev_hotplug_d = 1;
 	sysfs_get_mnt_path(sysfs_path, sizeof(sysfs_path));
+
+	/* disable HOTPLUG program execution */
+	env = getenv("UDEV_HOTPLUG");
+	if (env && !string_is_true(env))
+		udev_hotplug = 0;
 
 	env = getenv("UDEV_NO_DEVD");
 	if (env && string_is_true(env))
=== udev_rules.c 1.213 vs edited ==--- 1.213/udev_rules.c	2005-03-29 03:25:52 +02:00
+++ edited/udev_rules.c	2005-03-29 15:16:47 +02:00
@@ -474,6 +474,21 @@ static int match_rule(struct udevice *ud
 {
 	struct sysfs_device *parent_device = sysfs_device;
 
+	if (rule->action_operation != KEY_OP_UNSET) {
+		dbg("check for " KEY_ACTION " rule->action='%s' udev->action='%s'",
+		    rule->action, udev->action);
+		if (strcmp_pattern(rule->action, udev->action) != 0) {
+			dbg(KEY_ACTION " is not matching");
+			if (rule->action_operation != KEY_OP_NOMATCH)
+				goto exit;
+		} else {
+			dbg(KEY_ACTION " matches");
+			if (rule->action_operation = KEY_OP_NOMATCH)
+				goto exit;
+		}
+		dbg(KEY_ACTION " key is true");
+	}
+
 	if (rule->kernel_operation != KEY_OP_UNSET) {
 		dbg("check for " KEY_KERNEL " rule->kernel='%s' udev_kernel_name='%s'",
 		    rule->kernel, udev->kernel_name);
@@ -716,12 +731,17 @@ int udev_rules_get_name(struct udevice *
 	list_for_each_entry(rule, &udev_rule_list, node) {
 		dbg("process rule");
 		if (match_rule(udev, rule, class_dev, sysfs_device) = 0) {
+			if (udev->name[0] != '\0' && rule->name[0] != '\0') {
+				dbg("node name already set, rule ignored");
+				continue;
+			}
 
 			/* apply options */
 			if (rule->ignore_device) {
 				info("configured rule in '%s[%i]' applied, '%s' is ignored",
 				     rule->config_file, rule->config_line, udev->kernel_name);
-				return -1;
+				udev->ignore_device = 1;
+				return 0;
 			}
 			if (rule->ignore_remove) {
 				udev->ignore_remove = 1;
@@ -773,7 +793,7 @@ int udev_rules_get_name(struct udevice *
 				name_list_add(&udev->symlink_list, pos, 0);
 			}
 
-			/* rule matches */
+			/* set name, later rules with name set will be ignored */
 			if (rule->name[0] != '\0') {
 				info("configured rule in '%s[%i]' applied, '%s' becomes '%s'",
 				     rule->config_file, rule->config_line, udev->kernel_name, rule->name);
@@ -786,20 +806,25 @@ int udev_rules_get_name(struct udevice *
 				if (udev->type != DEV_NET)
 					dbg("name, '%s' is going to have owner='%s', group='%s', mode=%#o partitions=%i",
 					    udev->name, udev->owner, udev->group, udev->mode, udev->partitions);
+			}
 
-				break;
+			if (rule->hotplug[0] != '\0') {
+				char program[PATH_SIZE];
+
+				strlcpy(program, rule->hotplug, sizeof(program));
+				apply_format(udev, program, sizeof(program), class_dev, sysfs_device);
+				dbg("add hotplug program '%s'", program);
+				name_list_add(&udev->hotplug_list, program, 0);
 			}
 
 			if (rule->last_rule) {
 				dbg("last rule to be applied");
 				break;
 			}
-
 		}
 	}
 
 	if (udev->name[0] = '\0') {
-		/* no rule matched, so we use the kernel name */
 		strlcpy(udev->name, udev->kernel_name, sizeof(udev->name));
 		info("no rule found, use kernel name '%s'", udev->name);
 	}
@@ -808,6 +833,119 @@ int udev_rules_get_name(struct udevice *
 		dbg("removing temporary device node");
 		unlink_secure(udev->tmp_node);
 		udev->tmp_node[0] = '\0';
+	}
+
+	return 0;
+}
+
+int udev_rules_get_hotplug(struct udevice *udev)
+{
+	struct udev_rule *rule;
+
+	/* look for a matching rule to apply */
+	list_for_each_entry(rule, &udev_rule_list, node) {
+		dbg("process rule");
+
+		if (rule->name[0] != '\0' || rule->symlink[0] != '\0' ||
+		    rule->mode != 0000 || rule->owner[0] != '\0' || rule->group[0] != '\0') {
+			dbg("skip rule that names a device");
+			continue;
+		}
+
+		if (rule->action_operation != KEY_OP_UNSET) {
+			dbg("check for " KEY_ACTION " rule->action='%s' udev->action='%s'",
+			    rule->action, udev->action);
+			if (strcmp_pattern(rule->action, udev->action) != 0) {
+				dbg(KEY_ACTION " is not matching");
+				if (rule->action_operation != KEY_OP_NOMATCH)
+					continue;
+			} else {
+				dbg(KEY_ACTION " matches");
+				if (rule->action_operation = KEY_OP_NOMATCH)
+					continue;
+			}
+			dbg(KEY_ACTION " key is true");
+		}
+
+		if (rule->kernel_operation != KEY_OP_UNSET) {
+			dbg("check for " KEY_KERNEL " rule->kernel='%s' udev->kernel_name='%s'",
+			    rule->kernel, udev->kernel_name);
+			if (strcmp_pattern(rule->kernel, udev->kernel_name) != 0) {
+				dbg(KEY_KERNEL " is not matching");
+				if (rule->kernel_operation != KEY_OP_NOMATCH)
+					continue;
+		} else {
+				dbg(KEY_KERNEL " matches");
+				if (rule->kernel_operation = KEY_OP_NOMATCH)
+					continue;
+			}
+			dbg(KEY_KERNEL " key is true");
+		}
+
+		if (rule->subsystem_operation != KEY_OP_UNSET) {
+			dbg("check for " KEY_SUBSYSTEM " rule->subsystem='%s' udev->subsystem='%s'",
+			    rule->subsystem, udev->subsystem);
+			if (strcmp_pattern(rule->subsystem, udev->subsystem) != 0) {
+				dbg(KEY_SUBSYSTEM " is not matching");
+				if (rule->subsystem_operation != KEY_OP_NOMATCH)
+					continue;
+			} else {
+				dbg(KEY_SUBSYSTEM " matches");
+				if (rule->subsystem_operation = KEY_OP_NOMATCH)
+					continue;
+			}
+			dbg(KEY_SUBSYSTEM " key is true");
+		}
+
+		if (rule->env_pair_count) {
+			int i;
+
+			dbg("check for " KEY_ENV " pairs");
+			for (i = 0; i < rule->env_pair_count; i++) {
+				struct key_pair *pair;
+				const char *value;
+
+				pair = &rule->env_pair[i];
+				value = getenv(pair->name);
+				if (!value) {
+					dbg(KEY_ENV "{'%s'} is not found", pair->name);
+					continue;
+				}
+				if (strcmp_pattern(pair->value, value) != 0) {
+					dbg(KEY_ENV "{'%s'} is not matching", pair->name);
+					if (pair->operation != KEY_OP_NOMATCH)
+						continue;
+				} else {
+					dbg(KEY_ENV "{'%s'} matches", pair->name);
+					if (pair->operation = KEY_OP_NOMATCH)
+						continue;
+				}
+			}
+			dbg(KEY_ENV " key is true");
+		}
+
+		/* rule matches */
+
+		if (rule->ignore_device) {
+			info("configured rule in '%s[%i]' applied, '%s' is ignored",
+			     rule->config_file, rule->config_line, udev->kernel_name);
+			udev->ignore_device = 1;
+			return 0;
+		}
+
+		if (rule->hotplug[0] != '\0') {
+			char program[PATH_SIZE];
+
+			strlcpy(program, rule->hotplug, sizeof(program));
+			apply_format(udev, program, sizeof(program), NULL, NULL);
+			dbg("add hotplug program '%s'", program);
+			name_list_add(&udev->hotplug_list, program, 0);
+		}
+
+		if (rule->last_rule) {
+			dbg("last rule to be applied");
+			break;
+		}
 	}
 
 	return 0;
=== udev_rules.h 1.51 vs edited ==--- 1.51/udev_rules.h	2005-03-28 04:20:05 +02:00
+++ edited/udev_rules.h	2005-03-29 14:51:32 +02:00
@@ -30,6 +30,7 @@
 
 #define KEY_KERNEL		"KERNEL"
 #define KEY_SUBSYSTEM		"SUBSYSTEM"
+#define KEY_ACTION		"ACTION"
 #define KEY_BUS			"BUS"
 #define KEY_ID			"ID"
 #define KEY_PROGRAM		"PROGRAM"
@@ -42,6 +43,7 @@
 #define KEY_OWNER		"OWNER"
 #define KEY_GROUP		"GROUP"
 #define KEY_MODE		"MODE"
+#define KEY_HOTPLUG		"HOTPLUG"
 #define KEY_OPTIONS		"OPTIONS"
 
 #define OPTION_LAST_RULE	"last_rule"
@@ -75,6 +77,8 @@ struct udev_rule {
 	enum key_operation kernel_operation;
 	char subsystem[NAME_SIZE];
 	enum key_operation subsystem_operation;
+	char action[NAME_SIZE];
+	enum key_operation action_operation;
 	char bus[NAME_SIZE];
 	enum key_operation bus_operation;
 	char id[NAME_SIZE];
@@ -95,6 +99,7 @@ struct udev_rule {
 	char owner[USER_SIZE];
 	char group[USER_SIZE];
 	mode_t mode;
+	char hotplug[PATH_SIZE];
 
 	int last_rule;
 	int ignore_device;
@@ -109,6 +114,7 @@ extern struct list_head udev_rule_list;
 
 extern int udev_rules_init(void);
 extern int udev_rules_get_name(struct udevice *udev, struct sysfs_class_device *class_dev);
+extern int udev_rules_get_hotplug(struct udevice *udev);
 extern void udev_rules_close(void);
 
 #endif
=== udev_rules_parse.c 1.67 vs edited ==--- 1.67/udev_rules_parse.c	2005-03-28 04:20:05 +02:00
+++ edited/udev_rules_parse.c	2005-03-29 14:51:33 +02:00
@@ -256,6 +256,13 @@ static int rules_parse(const char *filen
 				continue;
 			}
 
+			if (strcasecmp(key, KEY_ACTION) = 0) {
+				strlcpy(rule.action, value, sizeof(rule.action));
+				rule.action_operation = operation;
+				valid = 1;
+				continue;
+			}
+
 			if (strcasecmp(key, KEY_BUS) = 0) {
 				strlcpy(rule.bus, value, sizeof(rule.bus));
 				rule.bus_operation = operation;
@@ -375,6 +382,12 @@ static int rules_parse(const char *filen
 
 			if (strcasecmp(key, KEY_MODE) = 0) {
 				rule.mode = strtol(value, NULL, 8);
+				valid = 1;
+				continue;
+			}
+
+			if (strcasecmp(key, KEY_HOTPLUG) = 0) {
+				strlcpy(rule.hotplug, value, sizeof(rule.hotplug));
 				valid = 1;
 				continue;
 			}
=== udev_utils.c 1.43 vs edited ==--- 1.43/udev_utils.c	2005-03-29 03:25:52 +02:00
+++ edited/udev_utils.c	2005-03-29 14:51:33 +02:00
@@ -45,9 +45,13 @@ int udev_init_device(struct udevice *ude
 
 	memset(udev, 0x00, sizeof(struct udevice));
 	INIT_LIST_HEAD(&udev->symlink_list);
+	INIT_LIST_HEAD(&udev->hotplug_list);
 
 	if (subsystem)
 		strlcpy(udev->subsystem, subsystem, sizeof(udev->subsystem));
+
+	if (action)
+		strlcpy(udev->action, action, sizeof(udev->action));
 
 	if (devpath) {
 		strlcpy(udev->devpath, devpath, sizeof(udev->devpath));
=== udevstart.c 1.36 vs edited ==--- 1.36/udevstart.c	2005-03-29 03:25:52 +02:00
+++ edited/udevstart.c	2005-03-29 14:51:33 +02:00
@@ -126,6 +126,15 @@ static int add_device(const char *path, 
 	udev_init_device(&udev, devpath, subsystem, "add");
 	udev_add_device(&udev, class_dev);
 
+	/* execute collected hotplug programs */
+	if (udev_hotplug && !list_empty(&udev.hotplug_list)) {
+		struct name_entry *name_loop;
+
+		dbg("executing hotplug program list");
+		list_for_each_entry(name_loop, &udev.hotplug_list, node)
+			execute_command(name_loop->name, udev.subsystem);
+	}
+
 	/* run dev.d/ scripts if we created a node or changed a netif name */
 	if (udev_dev_d && udev.devname[0] != '\0') {
 		setenv("DEVNAME", udev.devname, 1);
=== test/udev-test.pl 1.97 vs edited ==--- 1.97/test/udev-test.pl	2005-03-28 04:20:05 +02:00
+++ edited/test/udev-test.pl	2005-03-29 14:51:33 +02:00
@@ -1245,6 +1245,53 @@ KERNEL="sda", SYSFS{vendor}!="", NAME="
 KERNEL="sda", SYSFS{vendor}="", NAME="not-3-ok"
 EOF
 	},
+	{
+		desc		=> "check ACTION value",
+		subsys		=> "block",
+		devpath		=> "/block/sda",
+		exp_name	=> "ok",
+		rules		=> <<EOF
+ACTION="unknown", KERNEL="sda", NAME="unknown-not-ok"
+ACTION="add", KERNEL="sda", NAME="ok"
+EOF
+	},
+	{
+		desc		=> "apply NAME only once",
+		subsys		=> "block",
+		devpath		=> "/block/sda",
+		exp_name	=> "link",
+		exp_target	=> "ok",
+		rules		=> <<EOF
+KERNEL="sda", NAME="ok"
+KERNEL="sda", NAME="not-ok"
+KERNEL="sda", SYMLINK+="link"
+EOF
+	},
+	{
+		desc		=> "test HOTPLUG key",
+		subsys		=> "block",
+		devpath		=> "/block/sda",
+		exp_name	=> "testsymlink",
+		exp_target	=> "ok",
+		exp_rem_error	=> "yes",
+		option		=> "clean",
+		rules		=> <<EOF
+KERNEL="sda", NAME="ok", HOTPLUG+="/bin/ln -s ok %r/testsymlink"
+KERNEL="sda", NAME="not-ok"
+EOF
+	},
+	{
+		desc		=> "test HOTPLUG key remove",
+		subsys		=> "block",
+		devpath		=> "/block/sda",
+		exp_name	=> "testsymlink2",
+		exp_target	=> "ok2",
+		rules		=> <<EOF
+KERNEL="sda", NAME="ok2", HOTPLUG+="/bin/ln -s ok2 %r/testsymlink2"
+KERNEL="sda", ACTION="remove", HOTPLUG+="/bin/rm -f %r/testsymlink2"
+KERNEL="sda", NAME="not-ok2"
+EOF
+	},
 );
 
 # set env



-------------------------------------------------------
SF email is sponsored by - The IT Product Guide
Read honest & candid reviews on hundreds of IT Products from real users.
Discover which products truly live up to the hype. Start reading now.
http://ads.osdn.com/?ad_ide95&alloc_id\x14396&op=click
_______________________________________________
Linux-hotplug-devel mailing list  http://linux-hotplug.sourceforge.net
Linux-hotplug-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linux-hotplug-devel

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2005-04-02  6:54 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2005-03-29 14:54 [PATCH] udev: add rule based program execution Kay Sievers
2005-03-29 15:20 ` Kevin P. Fleming
2005-03-30  7:39 ` Roman Kagan
2005-03-30 12:05 ` Kay Sievers
2005-03-30 14:08 ` Roman Kagan
2005-03-30 17:29 ` Kay Sievers
2005-03-30 19:21 ` Greg KH
2005-03-30 20:20 ` Kay Sievers
2005-04-01  0:18 ` Greg KH
2005-04-01  7:30 ` Kay Sievers
2005-04-02  6:54 ` Greg KH

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).