/* vi: set sw=4 ts=4: */ /* * * mdev - Mini udev for busybox * * Copyright 2005 Rob Landley <rob@landley.net> * Copyright 2005 Frank Sorenson <frank@tuxrocks.com> * * Licensed under GPL version 2, see file LICENSE in this tarball for details. */ #include "busybox.h" #include "xregex.h" #define DEV_PATH "/dev" struct mdev_globals { int root_major, root_minor; } mdev_globals; #define bbg mdev_globals /* mknod in /dev based on a path like "/sys/block/hda/hda1" */ static void make_device(char *path, int delete) { char *device_name; int major, minor, type, len; int mode = 0660; uid_t uid = 0; gid_t gid = 0; char *temp = path + strlen(path); char *command = NULL; /* 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 * because sscanf() will stop at the first nondigit, which \n is. We * also depend on path having writeable space after it. */ if (!delete) { strcat(path, "/dev"); len = open_read_close(path, temp + 1, 64); *temp++ = 0; if (len < 1) return; } /* Determine device name, type, major and minor */ device_name = strrchr(path, '/') + 1; type = path[5]=='c' ? S_IFCHR : S_IFBLK; /* If we have a config file, look up permissions for this device */ if (ENABLE_FEATURE_MDEV_CONF) { char *conf, *pos, *end; int line, fd; /* mmap the config file */ fd = open("/etc/mdev.conf", O_RDONLY); if (fd < 0) goto end_parse; len = xlseek(fd, 0, SEEK_END); conf = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); if (!conf) goto end_parse; line = 0; /* Loop through lines in mmaped file*/ for (pos=conf; pos-conf<len;) { int field; char *end2; line++; /* find end of this line */ for (end=pos; end-conf<len && *end!='\n'; end++) ; /* Three fields: regex, uid:gid, mode */ for (field=0; field < (3 + ENABLE_FEATURE_MDEV_EXEC); field++) { /* Skip whitespace */ while (pos<end && isspace(*pos)) pos++; if (pos==end || *pos=='#') break; for (end2=pos; end2<end && !isspace(*end2) && *end2!='#'; end2++) ; if (field == 0) { /* Regex to match this device */ char *regex = strndupa(pos, end2-pos); regex_t match; regmatch_t off; int result; /* Is this it? */ xregcomp(&match,regex, REG_EXTENDED); result = regexec(&match, device_name, 1, &off, 0); regfree(&match); /* If not this device, skip rest of line */ if (result || off.rm_so || off.rm_eo != strlen(device_name)) break; } if (field == 1) { /* uid:gid */ char *s, *s2; /* Find : */ for (s=pos; s<end2 && *s!=':'; s++) ; if (s == end2) break; /* Parse UID */ uid = strtoul(pos, &s2, 10); if (s != s2) { struct passwd *pass; pass = getpwnam(strndupa(pos, s-pos)); if (!pass) break; uid = pass->pw_uid; } s++; /* parse GID */ gid = strtoul(s, &s2, 10); if (end2 != s2) { struct group *grp; grp = getgrnam(strndupa(s, end2-s)); if (!grp) break; gid = grp->gr_gid; } } if (field == 2) { /* mode */ mode = strtoul(pos, &pos, 8); if (pos != end2) break; } if (ENABLE_FEATURE_MDEV_EXEC && field == 3) { // Command to run char *s = "@$*", *s2; s2 = strchr(s, *pos++); if (!s2) { // Force error field = 1; break; } if ((s2-s+1) & (1<<delete)) command = xstrndup(pos, end-pos); } pos = end2; } /* Did everything parse happily? */ if (field > 2) break; if (field) bb_error_msg_and_die("bad line %d",line); /* Next line */ pos = ++end; } munmap(conf, len); end_parse: /* nothing */ ; } umask(0); if (!delete) { if (sscanf(temp, "%d:%d", &major, &minor) != 2) return; if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST) bb_perror_msg_and_die("mknod %s", device_name); if (major == bbg.root_major && minor == bbg.root_minor) symlink(device_name, "root"); if (ENABLE_FEATURE_MDEV_CONF) chown(device_name, uid, gid); } if (command) { int rc; char *s; s = 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("cannot run %s", command); } if (delete) unlink(device_name); } /* Recursive search of /sys/block or /sys/class. path must be a writeable * buffer of size PATH_MAX containing the directory string to start at. */ static void find_dev(char *path) { DIR *dir; size_t len = strlen(path); struct dirent *entry; dir = opendir(path); if (dir == NULL) return; while ((entry = readdir(dir)) != NULL) { struct stat st; /* Skip "." and ".." (also skips hidden files, which is ok) */ if (entry->d_name[0] == '.') continue; // uClibc doesn't fill out entry->d_type reliably. so we use lstat(). snprintf(path+len, PATH_MAX-len, "/%s", entry->d_name); if (!lstat(path, &st) && S_ISDIR(st.st_mode)) find_dev(path); path[len] = 0; /* If there's a dev entry, mknod it */ if (!strcmp(entry->d_name, "dev")) make_device(path, 0); } closedir(dir); } int mdev_main(int argc, char *argv[]) { char *action; char *env_path; RESERVE_CONFIG_BUFFER(temp,PATH_MAX); xchdir(DEV_PATH); /* Scan */ if (argc == 2 && !strcmp(argv[1],"-s")) { struct stat st; xstat("/", &st); bbg.root_major = major(st.st_dev); bbg.root_minor = minor(st.st_dev); strcpy(temp,"/sys/block"); find_dev(temp); strcpy(temp,"/sys/class"); find_dev(temp); /* Hotplug */ } else { action = getenv("ACTION"); env_path = getenv("DEVPATH"); if (!action || !env_path) bb_show_usage(); sprintf(temp, "/sys%s", env_path); if (!strcmp(action, "add")) make_device(temp,0); else if (!strcmp(action, "remove")) make_device(temp,1); } if (ENABLE_FEATURE_CLEAN_UP) RELEASE_CONFIG_BUFFER(temp); return 0; }