From 916f95cb58604038695347ee41a430d8ca1f0556 Mon Sep 17 00:00:00 2001 From: John Crispin Date: Thu, 14 Nov 2013 13:41:13 +0100 Subject: debloat and reorganize code split app into procd and init binaries remove log support, this is an external service now Signed-off-by: John Crispin --- plug/coldplug.c | 66 ++++++++ plug/hotplug.c | 478 +++++++++++++++++++++++++++++++++++++++++++++++++++++ plug/hotplug.h | 25 +++ plug/udevtrigger.c | 264 +++++++++++++++++++++++++++++ 4 files changed, 833 insertions(+) create mode 100644 plug/coldplug.c create mode 100644 plug/hotplug.c create mode 100644 plug/hotplug.h create mode 100644 plug/udevtrigger.c (limited to 'plug') diff --git a/plug/coldplug.c b/plug/coldplug.c new file mode 100644 index 0000000..466b759 --- /dev/null +++ b/plug/coldplug.c @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2013 Felix Fietkau + * Copyright (C) 2013 John Crispin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include + +#include + +#include "../procd.h" + +#include "hotplug.h" + +static struct uloop_process udevtrigger; + +static void coldplug_complete(struct uloop_timeout *t) +{ + DEBUG(4, "Coldplug complete\n"); + hotplug_last_event(NULL); + procd_state_next(); +} + +static void udevtrigger_complete(struct uloop_process *proc, int ret) +{ + DEBUG(4, "Finished udevtrigger\n"); + hotplug_last_event(coldplug_complete); +} + +void procd_coldplug(void) +{ + char *argv[] = { "udevtrigger", NULL }; + + umount2("/dev/pts", MNT_DETACH); + umount2("/dev/", MNT_DETACH); + mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755,size=512K"); + mkdir("/dev/shm", 0755); + mkdir("/dev/pts", 0755); + mount("devpts", "/dev/pts", "devpts", 0, 0); + udevtrigger.cb = udevtrigger_complete; + udevtrigger.pid = fork(); + if (!udevtrigger.pid) { + execvp(argv[0], argv); + ERROR("Failed to start coldplug\n"); + exit(-1); + } + + if (udevtrigger.pid <= 0) { + ERROR("Failed to start new coldplug instance\n"); + return; + } + + uloop_process_add(&udevtrigger); + + DEBUG(4, "Launched coldplug instance, pid=%d\n", (int) udevtrigger.pid); +} diff --git a/plug/hotplug.c b/plug/hotplug.c new file mode 100644 index 0000000..ca1e823 --- /dev/null +++ b/plug/hotplug.c @@ -0,0 +1,478 @@ +/* + * Copyright (C) 2013 Felix Fietkau + * Copyright (C) 2013 John Crispin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "../procd.h" + +#include "hotplug.h" + +#define HOTPLUG_WAIT 500 + +struct cmd_queue { + struct list_head list; + + struct blob_attr *msg; + struct blob_attr *data; + void (*handler)(struct blob_attr *msg, struct blob_attr *data); +}; + +static LIST_HEAD(cmd_queue); +static struct uloop_process queue_proc; +static struct uloop_timeout last_event; +static struct blob_buf b; +static char *rule_file; +static struct blob_buf script; + +static char *hotplug_msg_find_var(struct blob_attr *msg, const char *name) +{ + struct blob_attr *cur; + int rem; + + blobmsg_for_each_attr(cur, msg, rem) { + if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) + continue; + + if (strcmp(blobmsg_name(cur), name) != 0) + continue; + + return blobmsg_data(cur); + } + + return NULL; +} + +static void mkdir_p(char *dir) +{ + char *l = strrchr(dir, '/'); + + if (l) { + *l = '\0'; + mkdir_p(dir); + *l = '/'; + mkdir(dir, 0755); + } +} + +static void handle_makedev(struct blob_attr *msg, struct blob_attr *data) +{ + unsigned int oldumask = umask(0); + static struct blobmsg_policy mkdev_policy[2] = { + { .type = BLOBMSG_TYPE_STRING }, + { .type = BLOBMSG_TYPE_STRING }, + }; + struct blob_attr *tb[2]; + char *minor = hotplug_msg_find_var(msg, "MINOR"); + char *major = hotplug_msg_find_var(msg, "MAJOR"); + char *subsystem = hotplug_msg_find_var(msg, "SUBSYSTEM"); + + blobmsg_parse_array(mkdev_policy, 2, tb, blobmsg_data(data), blobmsg_data_len(data)); + if (tb[0] && tb[1] && minor && major && subsystem) { + mode_t m = S_IFCHR; + char *d = strdup(blobmsg_get_string(tb[0])); + + d = dirname(d); + mkdir_p(d); + free(d); + + if (!strcmp(subsystem, "block")) + m = S_IFBLK; + mknod(blobmsg_get_string(tb[0]), + m | strtoul(blobmsg_data(tb[1]), NULL, 8), + makedev(atoi(major), atoi(minor))); + } + umask(oldumask); +} + +static void handle_rm(struct blob_attr *msg, struct blob_attr *data) +{ + static struct blobmsg_policy rm_policy = { + .type = BLOBMSG_TYPE_STRING, + }; + struct blob_attr *tb; + + blobmsg_parse_array(&rm_policy, 1, &tb, blobmsg_data(data), blobmsg_data_len(data)); + if (tb) + unlink(blobmsg_data(tb)); +} + +static void handle_exec(struct blob_attr *msg, struct blob_attr *data) +{ + char *argv[8]; + struct blob_attr *cur; + int rem, fd; + int i = 0; + + blobmsg_for_each_attr(cur, msg, rem) + setenv(blobmsg_name(cur), blobmsg_data(cur), 1); + + blobmsg_for_each_attr(cur, data, rem) { + argv[i] = blobmsg_data(cur); + i++; + if (i == 7) + break; + } + + if (debug < 3) { + fd = open("/dev/null", O_RDWR); + if (fd > -1) { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + if (fd > STDERR_FILENO) + close(fd); + } + } + + if (i > 0) { + argv[i] = NULL; + execvp(argv[0], &argv[0]); + } + exit(-1); +} + +static void handle_firmware(struct blob_attr *msg, struct blob_attr *data) +{ + char *dir = blobmsg_get_string(blobmsg_data(data)); + char *file = hotplug_msg_find_var(msg, "FIRMWARE"); + char *dev = hotplug_msg_find_var(msg, "DEVPATH"); + void *fw_data; + struct stat s; + char *path, loadpath[256], syspath[256]; + int fw, load, sys, len; + + DEBUG(2, "Firmware request for %s/%s\n", dir, file); + + if (!file || !dir || !dev) { + ERROR("Request for unknown firmware %s/%s\n", dir, file); + exit(-1); + } + + path = malloc(strlen(dir) + strlen(file) + 2); + if (!path) { + ERROR("Failed to allocate memory\n"); + exit(-1); + } + sprintf(path, "%s/%s", dir, file); + + if (stat(path, &s)) { + ERROR("Could not find firmware %s\n", path); + exit(-1); + } + + fw_data = malloc(s.st_size); + if (!fw_data) { + ERROR("Failed to allocate firmware data memory\n"); + exit(-1); + } + + fw = open(path, O_RDONLY); + if (!fw) { + ERROR("Failed to open %s\n", path); + exit(-1); + } + if (read(fw, fw_data, s.st_size) != s.st_size) { + ERROR("Failed to read firmware data\n"); + exit(-1); + } + close(fw); + + snprintf(loadpath, sizeof(loadpath), "/sys/%s/loading", dev); + load = open(loadpath, O_WRONLY); + if (!load) { + ERROR("Failed to open %s\n", loadpath); + exit(-1); + } + write(load, "1", 1); + close(load); + + snprintf(syspath, sizeof(syspath), "/sys/%s/data", dev); + sys = open(syspath, O_WRONLY); + if (!sys) { + ERROR("Failed to open %s\n", syspath); + exit(-1); + } + + len = s.st_size; + while (len > 4096) { + write(fw, fw_data, 4096); + len -= 4096; + } + if (len) + write(fw, fw_data, len); + close(fw); + + load = open(loadpath, O_WRONLY); + write(load, "0", 1); + close(load); + + DEBUG(2, "Done loading %s\n", path); + + exit(-1); +} + +static struct cmd_handler { + char *name; + int atomic; + void (*handler)(struct blob_attr *msg, struct blob_attr *data); +} handlers[] = { + { + .name = "makedev", + .atomic = 1, + .handler = handle_makedev, + }, { + .name = "rm", + .atomic = 1, + .handler = handle_rm, + }, { + .name = "exec", + .handler = handle_exec, + }, { + .name = "load-firmware", + .handler = handle_firmware, + }, +}; + +static void queue_next(void) +{ + struct cmd_queue *c; + + if (queue_proc.pending || list_empty(&cmd_queue)) + return; + + c = list_first_entry(&cmd_queue, struct cmd_queue, list); + + queue_proc.pid = fork(); + if (!queue_proc.pid) { + uloop_done(); + c->handler(c->msg, c->data); + exit(0); + } + + list_del(&c->list); + free(c); + + if (queue_proc.pid <= 0) { + queue_next(); + return; + } + + uloop_process_add(&queue_proc); + + DEBUG(4, "Launched hotplug exec instance, pid=%d\n", (int) queue_proc.pid); +} + +static void queue_proc_cb(struct uloop_process *c, int ret) +{ + DEBUG(4, "Finished hotplug exec instance, pid=%d\n", (int) c->pid); + + queue_next(); +} + +static void queue_add(struct cmd_handler *h, struct blob_attr *msg, struct blob_attr *data) +{ + struct cmd_queue *c = NULL; + struct blob_attr *_msg, *_data; + + c = calloc_a(sizeof(struct cmd_queue), + &_msg, blob_pad_len(msg), + &_data, blob_pad_len(data), + NULL); + + c->msg = _msg; + c->data = _data; + + if (!c) + return; + + memcpy(c->msg, msg, blob_pad_len(msg)); + memcpy(c->data, data, blob_pad_len(data)); + c->handler = h->handler; + list_add_tail(&c->list, &cmd_queue); + queue_next(); +} + +static const char* rule_handle_var(struct json_script_ctx *ctx, const char *name, struct blob_attr *vars) +{ + const char *str, *sep; + + if (!strcmp(name, "DEVICENAME") || !strcmp(name, "DEVNAME")) { + str = json_script_find_var(ctx, vars, "DEVPATH"); + if (!str) + return NULL; + + sep = strrchr(str, '/'); + if (sep) + return sep + 1; + + return str; + } + + return NULL; +} + +static struct json_script_file * +rule_handle_file(struct json_script_ctx *ctx, const char *name) +{ + json_object *obj; + + obj = json_object_from_file((char*)name); + if (is_error(obj)) + return NULL; + + blob_buf_init(&script, 0); + blobmsg_add_json_element(&script, "", obj); + + return json_script_file_from_blobmsg(name, blob_data(script.head), blob_len(script.head)); +} + +static void rule_handle_command(struct json_script_ctx *ctx, const char *name, + struct blob_attr *data, struct blob_attr *vars) +{ + struct blob_attr *cur; + int rem, i; + + if (debug > 3) { + DEBUG(4, "Command: %s", name); + blobmsg_for_each_attr(cur, data, rem) + DEBUG(4, " %s", (char *) blobmsg_data(cur)); + DEBUG(4, "\n"); + + DEBUG(4, "Message:"); + blobmsg_for_each_attr(cur, vars, rem) + DEBUG(4, " %s=%s", blobmsg_name(cur), (char *) blobmsg_data(cur)); + DEBUG(4, "\n"); + } + + for (i = 0; i < ARRAY_SIZE(handlers); i++) + if (!strcmp(handlers[i].name, name)) { + if (handlers[i].atomic) + handlers[i].handler(vars, data); + else + queue_add(&handlers[i], vars, data); + break; + } + + if (last_event.cb) + uloop_timeout_set(&last_event, HOTPLUG_WAIT); +} + +static void rule_handle_error(struct json_script_ctx *ctx, const char *msg, + struct blob_attr *context) +{ + char *s; + + s = blobmsg_format_json(context, false); + ERROR("ERROR: %s in block: %s\n", msg, s); + free(s); +} + +static struct json_script_ctx jctx = { + .handle_var = rule_handle_var, + .handle_error = rule_handle_error, + .handle_command = rule_handle_command, + .handle_file = rule_handle_file, +}; + +static void hotplug_handler(struct uloop_fd *u, unsigned int ev) +{ + int i = 0; + static char buf[4096]; + int len = recv(u->fd, buf, sizeof(buf), MSG_DONTWAIT); + void *index; + if (len < 1) + return; + + blob_buf_init(&b, 0); + index = blobmsg_open_table(&b, NULL); + while (i < len) { + int l = strlen(buf + i) + 1; + char *e = strstr(&buf[i], "="); + + if (e) { + *e = '\0'; + blobmsg_add_string(&b, &buf[i], &e[1]); + } + i += l; + } + blobmsg_close_table(&b, index); + DEBUG(3, "%s\n", blobmsg_format_json(b.head, true)); + json_script_run(&jctx, rule_file, blob_data(b.head)); +} + +static struct uloop_fd hotplug_fd = { + .cb = hotplug_handler, +}; + +void hotplug_last_event(uloop_timeout_handler handler) +{ + last_event.cb = handler; + if (handler) + uloop_timeout_set(&last_event, HOTPLUG_WAIT); + else + uloop_timeout_cancel(&last_event); +} + +void hotplug(char *rules) +{ + struct sockaddr_nl nls; + + rule_file = strdup(rules); + memset(&nls,0,sizeof(struct sockaddr_nl)); + nls.nl_family = AF_NETLINK; + nls.nl_pid = getpid(); + nls.nl_groups = -1; + + if ((hotplug_fd.fd = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT)) == -1) { + ERROR("Failed to open hotplug socket: %s\n", strerror(errno)); + exit(1); + } + if (bind(hotplug_fd.fd, (void *)&nls, sizeof(struct sockaddr_nl))) { + ERROR("Failed to bind hotplug socket: %s\n", strerror(errno)); + exit(1); + } + + json_script_init(&jctx); + queue_proc.cb = queue_proc_cb; + uloop_fd_add(&hotplug_fd, ULOOP_READ); +} + +int hotplug_run(char *rules) +{ + uloop_init(); + hotplug(rules); + uloop_run(); + + return 0; +} + +void hotplug_shutdown(void) +{ + uloop_fd_delete(&hotplug_fd); + close(hotplug_fd.fd); +} diff --git a/plug/hotplug.h b/plug/hotplug.h new file mode 100644 index 0000000..2a44442 --- /dev/null +++ b/plug/hotplug.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2013 Felix Fietkau + * Copyright (C) 2013 John Crispin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __PROCD_HOTPLUG_H +#define __PROCD_HOTPLUG_H + +#include + +void hotplug(char *rules); +int hotplug_run(char *rules); +void hotplug_shutdown(void); +void hotplug_last_event(uloop_timeout_handler handler); + +#endif diff --git a/plug/udevtrigger.c b/plug/udevtrigger.c new file mode 100644 index 0000000..5013189 --- /dev/null +++ b/plug/udevtrigger.c @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2004-2006 Kay Sievers + * Copyright (C) 2006 Hannes Reinecke + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PATH_SIZE 512 + +#ifndef strlcpy +#define strlcpy(d,s,l) (strncpy(d,s,l), (d)[(l)-1] = '\0') +#endif + +#ifndef strlcat +#define strlcat(d,s,l) strncat(d,s,(l)-strlen(d)-1) +#endif + +static int verbose; +static int dry_run; + +static void log_message(int priority, const char *format, ...) +{ + va_list args; + + va_start(args, format); + vsyslog(priority, format, args); + va_end(args); +} + +#undef err +#define err(format, arg...) \ + do { \ + log_message(LOG_ERR ,"%s: " format ,__FUNCTION__ ,## arg); \ + } while (0) + +#undef info +#define info(format, arg...) \ + do { \ + log_message(LOG_INFO ,"%s: " format ,__FUNCTION__ ,## arg); \ + } while (0) + +#ifdef DEBUG +#undef dbg +#define dbg(format, arg...) \ + do { \ + log_message(LOG_DEBUG ,"%s: " format ,__FUNCTION__ ,## arg); \ + } while (0) +#else +#define dbg(...) do {} while(0) +#endif + + +static void trigger_uevent(const char *devpath) +{ + char filename[PATH_SIZE]; + int fd; + + strlcpy(filename, "/sys", sizeof(filename)); + strlcat(filename, devpath, sizeof(filename)); + strlcat(filename, "/uevent", sizeof(filename)); + + if (verbose) + printf("%s\n", devpath); + + if (dry_run) + return; + + fd = open(filename, O_WRONLY); + if (fd < 0) { + dbg("error on opening %s: %s\n", filename, strerror(errno)); + return; + } + + if (write(fd, "add", 3) < 0) + info("error on triggering %s: %s\n", filename, strerror(errno)); + + close(fd); +} + +static int sysfs_resolve_link(char *devpath, size_t size) +{ + char link_path[PATH_SIZE]; + char link_target[PATH_SIZE]; + int len; + int i; + int back; + + strlcpy(link_path, "/sys", sizeof(link_path)); + strlcat(link_path, devpath, sizeof(link_path)); + len = readlink(link_path, link_target, sizeof(link_target)); + if (len <= 0) + return -1; + link_target[len] = '\0'; + dbg("path link '%s' points to '%s'", devpath, link_target); + + for (back = 0; strncmp(&link_target[back * 3], "../", 3) == 0; back++) + ; + dbg("base '%s', tail '%s', back %i", devpath, &link_target[back * 3], back); + for (i = 0; i <= back; i++) { + char *pos = strrchr(devpath, '/'); + + if (pos == NULL) + return -1; + pos[0] = '\0'; + } + dbg("after moving back '%s'", devpath); + strlcat(devpath, "/", size); + strlcat(devpath, &link_target[back * 3], size); + return 0; +} + +static bool device_has_attribute(const char *path, const char *attr, + mode_t mode) +{ + char filename[PATH_SIZE]; + struct stat statbuf; + + strlcpy(filename, path, sizeof(filename)); + strlcat(filename, attr, sizeof(filename)); + + if (stat(filename, &statbuf) < 0) + return false; + + if (!(statbuf.st_mode & mode)) + return false; + + return true; +} + +static int device_list_insert(const char *path) +{ + char devpath[PATH_SIZE]; + struct stat statbuf; + + dbg("add '%s'" , path); + + /* we only have a device, if we have a dev and an uevent file */ + if (!device_has_attribute(path, "/dev", S_IRUSR) || + !device_has_attribute(path, "/uevent", S_IWUSR)) + return -1; + + strlcpy(devpath, &path[4], sizeof(devpath)); + + /* resolve possible link to real target */ + if (lstat(path, &statbuf) < 0) + return -1; + if (S_ISLNK(statbuf.st_mode)) + if (sysfs_resolve_link(devpath, sizeof(devpath)) != 0) + return -1; + + trigger_uevent(devpath); + return 0; +} + +static void scan_subdir(const char *base, const char *subdir, + bool insert, int depth) +{ + DIR *dir; + struct dirent *dent; + + dir = opendir(base); + if (dir == NULL) + return; + + for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { + char dirname[PATH_SIZE]; + + if (dent->d_name[0] == '.') + continue; + + strlcpy(dirname, base, sizeof(dirname)); + strlcat(dirname, "/", sizeof(dirname)); + strlcat(dirname, dent->d_name, sizeof(dirname)); + + if (insert) { + int err; + + err = device_list_insert(dirname); + if (err) + continue; + } + + if (subdir) + strlcat(dirname, subdir, sizeof(base)); + + if (depth) + scan_subdir(dirname, NULL, true, depth - 1); + } + + closedir(dir); +} + +int main(int argc, char *argv[], char *envp[]) +{ + struct stat statbuf; + int option; + + openlog("udevtrigger", LOG_PID | LOG_CONS, LOG_DAEMON); + + while (1) { + option = getopt(argc, argv, "vnh"); + if (option == -1) + break; + + switch (option) { + case 'v': + verbose = 1; + break; + case 'n': + dry_run = 1; + break; + case 'h': + printf("Usage: udevtrigger OPTIONS\n" + " -v print the list of devices while running\n" + " -n do not actually trigger the events\n" + " -h print this text\n" + "\n"); + goto exit; + default: + goto exit; + } + } + + + /* if we have /sys/subsystem, forget all the old stuff */ + scan_subdir("/sys/bus", "/devices", false, 1); + scan_subdir("/sys/class", NULL, false, 1); + + /* scan "block" if it isn't a "class" */ + if (stat("/sys/class/block", &statbuf) != 0) + scan_subdir("/sys/block", NULL, true, 1); + +exit: + + closelog(); + return 0; +} -- cgit v1.2.3