diff options
Diffstat (limited to 'util-linux/mdev.c')
-rw-r--r-- | util-linux/mdev.c | 119 |
1 files changed, 80 insertions, 39 deletions
diff --git a/util-linux/mdev.c b/util-linux/mdev.c index 59dbcf0cd..dbbcbc655 100644 --- a/util-linux/mdev.c +++ b/util-linux/mdev.c | |||
@@ -80,12 +80,14 @@ | |||
80 | //kbuild:lib-$(CONFIG_MDEV) += mdev.o | 80 | //kbuild:lib-$(CONFIG_MDEV) += mdev.o |
81 | 81 | ||
82 | //usage:#define mdev_trivial_usage | 82 | //usage:#define mdev_trivial_usage |
83 | //usage: "[-s]" IF_FEATURE_MDEV_DAEMON(" | [-df]") | 83 | //usage: "[-vS] " IF_FEATURE_MDEV_DAEMON("{ ") "[-s]" IF_FEATURE_MDEV_DAEMON(" | [-df] }") |
84 | //usage:#define mdev_full_usage "\n\n" | 84 | //usage:#define mdev_full_usage "\n\n" |
85 | //usage: "mdev -s is to be run during boot to scan /sys and populate /dev.\n" | 85 | //usage: " -v verbose\n" |
86 | //usage: " -S log to syslog too\n" | ||
87 | //usage: " -s scan /sys and populate /dev\n" | ||
86 | //usage: IF_FEATURE_MDEV_DAEMON( | 88 | //usage: IF_FEATURE_MDEV_DAEMON( |
87 | //usage: "mdev -d[f]: daemon, listen on netlink.\n" | 89 | //usage: " -d daemon, listen on netlink\n" |
88 | //usage: " -f: stay in foreground.\n" | 90 | //usage: " -f stay in foreground\n" |
89 | //usage: ) | 91 | //usage: ) |
90 | //usage: "\n" | 92 | //usage: "\n" |
91 | //usage: "Bare mdev is a kernel hotplug helper. To activate it:\n" | 93 | //usage: "Bare mdev is a kernel hotplug helper. To activate it:\n" |
@@ -113,6 +115,7 @@ | |||
113 | #include "common_bufsiz.h" | 115 | #include "common_bufsiz.h" |
114 | #include "xregex.h" | 116 | #include "xregex.h" |
115 | #include <linux/netlink.h> | 117 | #include <linux/netlink.h> |
118 | #include <syslog.h> | ||
116 | 119 | ||
117 | /* "mdev -s" scans /sys/class/xxx, looking for directories which have dev | 120 | /* "mdev -s" scans /sys/class/xxx, looking for directories which have dev |
118 | * file (it is of the form "M:m\n"). Example: /sys/class/tty/tty0/dev | 121 | * file (it is of the form "M:m\n"). Example: /sys/class/tty/tty0/dev |
@@ -269,10 +272,6 @@ | |||
269 | # define dbg3s(msg) ((void)0) | 272 | # define dbg3s(msg) ((void)0) |
270 | #endif | 273 | #endif |
271 | 274 | ||
272 | |||
273 | #ifndef SO_RCVBUFFORCE | ||
274 | #define SO_RCVBUFFORCE 33 | ||
275 | #endif | ||
276 | static const char keywords[] ALIGN1 = "add\0remove\0"; // "change\0" | 275 | static const char keywords[] ALIGN1 = "add\0remove\0"; // "change\0" |
277 | enum { OP_add, OP_remove }; | 276 | enum { OP_add, OP_remove }; |
278 | 277 | ||
@@ -297,7 +296,7 @@ struct rule { | |||
297 | 296 | ||
298 | struct globals { | 297 | struct globals { |
299 | int root_major, root_minor; | 298 | int root_major, root_minor; |
300 | smallint verbose; | 299 | int verbose; |
301 | char *subsystem; | 300 | char *subsystem; |
302 | char *subsys_env; /* for putenv("SUBSYSTEM=subsystem") */ | 301 | char *subsys_env; /* for putenv("SUBSYSTEM=subsystem") */ |
303 | #if ENABLE_FEATURE_MDEV_CONF | 302 | #if ENABLE_FEATURE_MDEV_CONF |
@@ -921,7 +920,7 @@ static void load_firmware(const char *firmware, const char *sysfs_path) | |||
921 | loading_fd = open("loading", O_WRONLY); | 920 | loading_fd = open("loading", O_WRONLY); |
922 | if (loading_fd >= 0) | 921 | if (loading_fd >= 0) |
923 | goto loading; | 922 | goto loading; |
924 | sleep(1); | 923 | sleep1(); |
925 | } | 924 | } |
926 | goto out; | 925 | goto out; |
927 | 926 | ||
@@ -964,7 +963,7 @@ static void load_firmware(const char *firmware, const char *sysfs_path) | |||
964 | static char *curtime(void) | 963 | static char *curtime(void) |
965 | { | 964 | { |
966 | struct timeval tv; | 965 | struct timeval tv; |
967 | gettimeofday(&tv, NULL); | 966 | xgettimeofday(&tv); |
968 | sprintf( | 967 | sprintf( |
969 | strftime_HHMMSS(G.timestr, sizeof(G.timestr), &tv.tv_sec), | 968 | strftime_HHMMSS(G.timestr, sizeof(G.timestr), &tv.tv_sec), |
970 | ".%06u", | 969 | ".%06u", |
@@ -1152,15 +1151,50 @@ static void initial_scan(char *temp) | |||
1152 | 1151 | ||
1153 | #if ENABLE_FEATURE_MDEV_DAEMON | 1152 | #if ENABLE_FEATURE_MDEV_DAEMON |
1154 | 1153 | ||
1155 | /* uevent applet uses 16k buffer, and mmaps it before every read */ | 1154 | /* |
1156 | # define BUFFER_SIZE (2 * 1024) | 1155 | * The kernel (as of v5.4) will pass up to 32 environment variables with a |
1157 | # define RCVBUF (2 * 1024 * 1024) | 1156 | * total of 2kiB on each event. On top of that the action string and device |
1157 | * path are added. Using a 3kiB buffer for the event should suffice in any | ||
1158 | * case. | ||
1159 | * | ||
1160 | * As far as the socket receive buffer size is concerned 2MiB proved to be too | ||
1161 | * small (see [1]). Udevd seems to use a whooping 128MiB. The socket receive | ||
1162 | * buffer size is just a resource limit. The buffers are allocated lazily so | ||
1163 | * the memory is not wasted. | ||
1164 | * | ||
1165 | * [1] http://lists.busybox.net/pipermail/busybox/2019-December/087665.html | ||
1166 | */ | ||
1167 | # define USER_RCVBUF (3 * 1024) | ||
1168 | # define KERN_RCVBUF (128 * 1024 * 1024) | ||
1158 | # define MAX_ENV 32 | 1169 | # define MAX_ENV 32 |
1159 | 1170 | ||
1171 | static int daemon_init(char *temp) | ||
1172 | { | ||
1173 | int fd; | ||
1174 | |||
1175 | /* Subscribe for UEVENT kernel messages */ | ||
1176 | /* Without a sufficiently big RCVBUF, a ton of simultaneous events | ||
1177 | * can trigger ENOBUFS on read, which is unrecoverable. | ||
1178 | * Reproducer: | ||
1179 | * mdev -d | ||
1180 | * find /sys -name uevent -exec sh -c 'echo add >"{}"' ';' | ||
1181 | */ | ||
1182 | fd = create_and_bind_to_netlink(NETLINK_KOBJECT_UEVENT, 1 << 0, KERN_RCVBUF); | ||
1183 | |||
1184 | /* | ||
1185 | * Make inital scan after the uevent socket is alive and | ||
1186 | * _before_ we fork away. Already open mdev.log because we work | ||
1187 | * in daemon mode. | ||
1188 | */ | ||
1189 | initial_scan(temp); | ||
1190 | |||
1191 | return fd; | ||
1192 | } | ||
1193 | |||
1160 | static void daemon_loop(char *temp, int fd) | 1194 | static void daemon_loop(char *temp, int fd) |
1161 | { | 1195 | { |
1162 | for (;;) { | 1196 | for (;;) { |
1163 | char netbuf[BUFFER_SIZE]; | 1197 | char netbuf[USER_RCVBUF]; |
1164 | char *env[MAX_ENV]; | 1198 | char *env[MAX_ENV]; |
1165 | char *s, *end; | 1199 | char *s, *end; |
1166 | ssize_t len; | 1200 | ssize_t len; |
@@ -1168,6 +1202,16 @@ static void daemon_loop(char *temp, int fd) | |||
1168 | 1202 | ||
1169 | len = safe_read(fd, netbuf, sizeof(netbuf) - 1); | 1203 | len = safe_read(fd, netbuf, sizeof(netbuf) - 1); |
1170 | if (len < 0) { | 1204 | if (len < 0) { |
1205 | if (errno == ENOBUFS) { | ||
1206 | /* | ||
1207 | * We ran out of socket receive buffer space. | ||
1208 | * Start from scratch. | ||
1209 | */ | ||
1210 | dbg1s("uevent overrun, rescanning"); | ||
1211 | close(fd); | ||
1212 | fd = daemon_init(temp); | ||
1213 | continue; | ||
1214 | } | ||
1171 | bb_simple_perror_msg_and_die("read"); | 1215 | bb_simple_perror_msg_and_die("read"); |
1172 | } | 1216 | } |
1173 | end = netbuf + len; | 1217 | end = netbuf + len; |
@@ -1196,8 +1240,9 @@ int mdev_main(int argc UNUSED_PARAM, char **argv) | |||
1196 | { | 1240 | { |
1197 | enum { | 1241 | enum { |
1198 | MDEV_OPT_SCAN = 1 << 0, | 1242 | MDEV_OPT_SCAN = 1 << 0, |
1199 | MDEV_OPT_DAEMON = 1 << 1, | 1243 | MDEV_OPT_SYSLOG = 1 << 1, |
1200 | MDEV_OPT_FOREGROUND = 1 << 2, | 1244 | MDEV_OPT_DAEMON = 1 << 2, |
1245 | MDEV_OPT_FOREGROUND = 1 << 3, | ||
1201 | }; | 1246 | }; |
1202 | int opt; | 1247 | int opt; |
1203 | RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE); | 1248 | RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE); |
@@ -1213,7 +1258,11 @@ int mdev_main(int argc UNUSED_PARAM, char **argv) | |||
1213 | 1258 | ||
1214 | xchdir("/dev"); | 1259 | xchdir("/dev"); |
1215 | 1260 | ||
1216 | opt = getopt32(argv, "s" IF_FEATURE_MDEV_DAEMON("df")); | 1261 | opt = getopt32(argv, "^" |
1262 | "sS" IF_FEATURE_MDEV_DAEMON("df") "v" | ||
1263 | "\0" | ||
1264 | "vv", | ||
1265 | &G.verbose); | ||
1217 | 1266 | ||
1218 | #if ENABLE_FEATURE_MDEV_CONF | 1267 | #if ENABLE_FEATURE_MDEV_CONF |
1219 | G.filename = "/etc/mdev.conf"; | 1268 | G.filename = "/etc/mdev.conf"; |
@@ -1223,32 +1272,24 @@ int mdev_main(int argc UNUSED_PARAM, char **argv) | |||
1223 | } | 1272 | } |
1224 | #endif | 1273 | #endif |
1225 | 1274 | ||
1275 | if (opt & MDEV_OPT_SYSLOG) { | ||
1276 | openlog(applet_name, LOG_PID, LOG_DAEMON); | ||
1277 | logmode |= LOGMODE_SYSLOG; | ||
1278 | } | ||
1279 | |||
1226 | #if ENABLE_FEATURE_MDEV_DAEMON | 1280 | #if ENABLE_FEATURE_MDEV_DAEMON |
1227 | if (opt & MDEV_OPT_DAEMON) { | 1281 | if (opt & MDEV_OPT_DAEMON) { |
1228 | /* | 1282 | /* Daemon mode listening on uevent netlink socket. Fork away |
1229 | * Daemon mode listening on uevent netlink socket. | 1283 | * after initial scan so that caller can be sure everything |
1230 | */ | 1284 | * is up-to-date when mdev process returns. |
1231 | int fd; | ||
1232 | |||
1233 | /* Subscribe for UEVENT kernel messages */ | ||
1234 | /* Without a sufficiently big RCVBUF, a ton of simultaneous events | ||
1235 | * can trigger ENOBUFS on read, which is unrecoverable. | ||
1236 | * Reproducer: | ||
1237 | * mdev -d | ||
1238 | * find /sys -name uevent -exec sh -c 'echo add >"{}"' ';' | ||
1239 | */ | 1285 | */ |
1240 | fd = create_and_bind_to_netlink(NETLINK_KOBJECT_UEVENT, 1 << 0, RCVBUF); | 1286 | int fd = daemon_init(temp); |
1241 | 1287 | ||
1242 | /* | 1288 | if (!(opt & MDEV_OPT_FOREGROUND)) { |
1243 | * Make inital scan after the uevent socket is alive and | 1289 | /* there is no point in logging to /dev/null */ |
1244 | * _before_ we fork away. | 1290 | logmode &= ~LOGMODE_STDIO; |
1245 | */ | ||
1246 | initial_scan(temp); | ||
1247 | |||
1248 | if (!(opt & MDEV_OPT_FOREGROUND)) | ||
1249 | bb_daemonize_or_rexec(0, argv); | 1291 | bb_daemonize_or_rexec(0, argv); |
1250 | 1292 | } | |
1251 | open_mdev_log(NULL, getpid()); | ||
1252 | 1293 | ||
1253 | daemon_loop(temp, fd); | 1294 | daemon_loop(temp, fd); |
1254 | } | 1295 | } |