mdev: add "!" syntax support

Based on the patch by Steve Bennett <steveb@workware.net.au>

function                                             old     new   delta
make_device                                         1640    1673     +33

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/docs/mdev.txt b/docs/mdev.txt
index 7bd00fe..2d03bd8 100644
--- a/docs/mdev.txt
+++ b/docs/mdev.txt
@@ -51,8 +51,8 @@
 660 permissions.
 
 The file has the format:
-    <device regex>       <uid>:<gid> <octal permissions>
- or @<maj[,min1[-min2]]> <uid>:<gid> <octal permissions>
+    <device regex>       <uid>:<gid> <permissions>
+ or @<maj[,min1[-min2]]> <uid>:<gid> <permissions>
 
 For example:
     hd[a-z][0-9]* 0:3 660
@@ -63,7 +63,7 @@
 	.* 1:1 777
 
 You can rename/move device nodes by using the next optional field.
-	<device regex> <uid>:<gid> <octal permissions> [=path]
+	<device regex> <uid>:<gid> <permissions> [=path]
 So if you want to place the device node into a subdirectory, make sure the path
 has a trailing /.  If you want to rename the device node, just place the name.
 	hda 0:3 660 =drives/
@@ -74,11 +74,17 @@
 Similarly, ">path" renames/moves the device but it also creates
 a direct symlink /dev/DEVNAME to the renamed/moved device.
 
+You can also prevent creation of device nodes with the 4th field as "!":
+	tty[a-z]. 0:0 660 !
+	pty[a-z]. 0:0 660 !
+
 If you also enable support for executing your own commands, then the file has
 the format:
-	<device regex> <uid>:<gid> <octal permissions> [=path] [@|$|*<command>]
+	<device regex> <uid>:<gid> <permissions> [=path] [@|$|*<command>]
     or
-	<device regex> <uid>:<gid> <octal permissions> [>path] [@|$|*<command>]
+	<device regex> <uid>:<gid> <permissions> [>path] [@|$|*<command>]
+    or
+	<device regex> <uid>:<gid> <permissions> [!] [@|$|*<command>]
 
 For example:
 ---8<---
diff --git a/util-linux/mdev.c b/util-linux/mdev.c
index b0efd98..2170756 100644
--- a/util-linux/mdev.c
+++ b/util-linux/mdev.c
@@ -204,7 +204,8 @@
 				if (major < 0)
 					continue; /* no dev, no match */
 				sc = sscanf(val, "@%u,%u-%u", &cmaj, &cmin0, &cmin1);
-				if (sc < 1 || major != cmaj
+				if (sc < 1
+				 || major != cmaj
 				 || (sc == 2 && minor != cmin0)
 				 || (sc == 3 && (minor < cmin0 || minor > cmin1))
 				) {
@@ -243,7 +244,8 @@
 
 				/* If no match, skip rest of line */
 				/* (regexec returns whole pattern as "range" 0) */
-				if (result || off[0].rm_so
+				if (result
+				 || off[0].rm_so
 				 || ((int)off[0].rm_eo != (int)strlen(str_to_match))
 				) {
 					continue; /* this line doesn't match */
@@ -258,28 +260,34 @@
 				bb_error_msg("unknown user/group %s on line %d", tokens[1], parser->lineno);
 
 			/* 3rd field: mode - device permissions */
-			/* mode = strtoul(tokens[2], NULL, 8); */
 			bb_parse_mode(tokens[2], &mode);
 
 			val = tokens[3];
-			/* 4th field (opt): >|=alias */
+			/* 4th field (opt): ">|=alias" or "!" to not create the node */
 
 			if (ENABLE_FEATURE_MDEV_RENAME && val) {
-				aliaslink = val[0];
-				if (aliaslink == '>' || aliaslink == '=') {
-					char *a, *s, *st;
-					char *p;
-					unsigned i, n;
+				char *a, *s, *st;
 
-					a = val;
-					s = strchrnul(val, ' ');
-					st = strchrnul(val, '\t');
-					if (st < s)
-						s = st;
-					val = (s[0] && s[1]) ? s+1 : NULL;
+				a = val;
+				s = strchrnul(val, ' ');
+				st = strchrnul(val, '\t');
+				if (st < s)
+					s = st;
+				st = (s[0] && s[1]) ? s+1 : NULL;
+
+				aliaslink = a[0];
+				if (aliaslink == '!' && s == a+1) {
+					val = st;
+					/* "!": suppress node creation/deletion */
+					major = -1;
+				}
+				else if (aliaslink == '>' || aliaslink == '=') {
+					val = st;
 					s[0] = '\0';
-
 					if (ENABLE_FEATURE_MDEV_RENAME_REGEXP) {
+						char *p;
+						unsigned i, n;
+
 						/* substitute %1..9 with off[1..9], if any */
 						n = 0;
 						s = a;
@@ -323,8 +331,13 @@
 				/* Are we running this command now?
 				 * Run $cmd on delete, @cmd on create, *cmd on both
 				 */
-				if (s2-s != delete)
+				if (s2 - s != delete) {
+					/* We are here if: '*',
+					 * or: '@' and delete = 0,
+					 * or: '$' and delete = 1
+					 */
 					command = xstrdup(val + 1);
+				}
 			}
 		}
 
@@ -368,7 +381,7 @@
 				free(command);
 			}
 
-			if (delete) {
+			if (delete && major >= 0) {
 				if (ENABLE_FEATURE_MDEV_RENAME && alias) {
 					if (aliaslink == '>')
 						unlink(device_name);