From c52cb8abde4524398d111d7994c4b792e63bd541 Mon Sep 17 00:00:00 2001 From: jbnadal Date: Tue, 6 Dec 2016 17:47:39 +0100 Subject: [PATCH] Bump procd-2016-07-29-2c9f5d4af1559b840c42f1443ede9f9fe809c58b --- src/3P/procd/CMakeLists.txt | 123 +++ src/3P/procd/builders/cmake/CMakeLists.txt | 78 ++ src/3P/procd/initd/early.c | 98 ++ src/3P/procd/initd/init.c | 113 +++ src/3P/procd/initd/init.h | 36 + src/3P/procd/initd/mkdev.c | 125 +++ src/3P/procd/initd/preinit.c | 132 +++ src/3P/procd/initd/zram.c | 128 +++ src/3P/procd/inittab.c | 321 ++++++ src/3P/procd/jail/capabilities.c | 116 +++ src/3P/procd/jail/capabilities.h | 18 + src/3P/procd/jail/elf.c | 317 ++++++ src/3P/procd/jail/elf.h | 38 + src/3P/procd/jail/fs.c | 180 ++++ src/3P/procd/jail/fs.h | 21 + src/3P/procd/jail/jail.c | 415 ++++++++ src/3P/procd/jail/jail.h | 18 + src/3P/procd/jail/log.h | 31 + src/3P/procd/jail/preload.c | 78 ++ src/3P/procd/jail/seccomp-bpf.h | 89 ++ src/3P/procd/jail/seccomp.c | 140 +++ src/3P/procd/jail/seccomp.h | 26 + src/3P/procd/libc-compat.h | 10 + src/3P/procd/log.h | 30 + src/3P/procd/make_capabilities_h.sh | 10 + src/3P/procd/make_syscall_h.sh | 18 + src/3P/procd/plug/coldplug.c | 69 ++ src/3P/procd/plug/hotplug.c | 622 ++++++++++++ src/3P/procd/plug/hotplug.h | 49 + src/3P/procd/plug/udevtrigger.c | 264 +++++ src/3P/procd/preload.h | 56 ++ src/3P/procd/procd.c | 84 ++ src/3P/procd/procd.h | 57 ++ src/3P/procd/rcS.c | 182 ++++ src/3P/procd/rcS.h | 23 + src/3P/procd/service/instance.c | 1061 ++++++++++++++++++++ src/3P/procd/service/instance.h | 88 ++ src/3P/procd/service/service.c | 554 ++++++++++ src/3P/procd/service/service.h | 56 ++ src/3P/procd/service/setlbf.c | 6 + src/3P/procd/service/trigger.c | 303 ++++++ src/3P/procd/service/validate.c | 157 +++ src/3P/procd/service/watch.c | 119 +++ src/3P/procd/signal.c | 101 ++ src/3P/procd/state.c | 202 ++++ src/3P/procd/system.c | 444 ++++++++ src/3P/procd/trace/preload.c | 80 ++ src/3P/procd/trace/trace.c | 225 +++++ src/3P/procd/ubus.c | 87 ++ src/3P/procd/upgraded/CMakeLists.txt | 17 + src/3P/procd/upgraded/upgraded.c | 80 ++ src/3P/procd/utils/askfirst.c | 43 + src/3P/procd/utils/utils.c | 209 ++++ src/3P/procd/utils/utils.h | 59 ++ src/3P/procd/watchdog.c | 135 +++ src/3P/procd/watchdog.h | 66 ++ 56 files changed, 8207 insertions(+) create mode 100644 src/3P/procd/CMakeLists.txt create mode 100644 src/3P/procd/builders/cmake/CMakeLists.txt create mode 100644 src/3P/procd/initd/early.c create mode 100644 src/3P/procd/initd/init.c create mode 100644 src/3P/procd/initd/init.h create mode 100644 src/3P/procd/initd/mkdev.c create mode 100644 src/3P/procd/initd/preinit.c create mode 100644 src/3P/procd/initd/zram.c create mode 100644 src/3P/procd/inittab.c create mode 100644 src/3P/procd/jail/capabilities.c create mode 100644 src/3P/procd/jail/capabilities.h create mode 100644 src/3P/procd/jail/elf.c create mode 100644 src/3P/procd/jail/elf.h create mode 100644 src/3P/procd/jail/fs.c create mode 100644 src/3P/procd/jail/fs.h create mode 100644 src/3P/procd/jail/jail.c create mode 100644 src/3P/procd/jail/jail.h create mode 100644 src/3P/procd/jail/log.h create mode 100644 src/3P/procd/jail/preload.c create mode 100644 src/3P/procd/jail/seccomp-bpf.h create mode 100644 src/3P/procd/jail/seccomp.c create mode 100644 src/3P/procd/jail/seccomp.h create mode 100644 src/3P/procd/libc-compat.h create mode 100644 src/3P/procd/log.h create mode 100755 src/3P/procd/make_capabilities_h.sh create mode 100755 src/3P/procd/make_syscall_h.sh create mode 100644 src/3P/procd/plug/coldplug.c create mode 100644 src/3P/procd/plug/hotplug.c create mode 100644 src/3P/procd/plug/hotplug.h create mode 100644 src/3P/procd/plug/udevtrigger.c create mode 100644 src/3P/procd/preload.h create mode 100644 src/3P/procd/procd.c create mode 100644 src/3P/procd/procd.h create mode 100644 src/3P/procd/rcS.c create mode 100644 src/3P/procd/rcS.h create mode 100644 src/3P/procd/service/instance.c create mode 100644 src/3P/procd/service/instance.h create mode 100644 src/3P/procd/service/service.c create mode 100644 src/3P/procd/service/service.h create mode 100644 src/3P/procd/service/setlbf.c create mode 100644 src/3P/procd/service/trigger.c create mode 100644 src/3P/procd/service/validate.c create mode 100644 src/3P/procd/service/watch.c create mode 100644 src/3P/procd/signal.c create mode 100644 src/3P/procd/state.c create mode 100644 src/3P/procd/system.c create mode 100644 src/3P/procd/trace/preload.c create mode 100644 src/3P/procd/trace/trace.c create mode 100644 src/3P/procd/ubus.c create mode 100644 src/3P/procd/upgraded/CMakeLists.txt create mode 100644 src/3P/procd/upgraded/upgraded.c create mode 100644 src/3P/procd/utils/askfirst.c create mode 100644 src/3P/procd/utils/utils.c create mode 100644 src/3P/procd/utils/utils.h create mode 100644 src/3P/procd/watchdog.c create mode 100644 src/3P/procd/watchdog.h diff --git a/src/3P/procd/CMakeLists.txt b/src/3P/procd/CMakeLists.txt new file mode 100644 index 00000000..b66fad16 --- /dev/null +++ b/src/3P/procd/CMakeLists.txt @@ -0,0 +1,123 @@ +cmake_minimum_required(VERSION 2.6) + +PROJECT(procd C) +INCLUDE(GNUInstallDirs) +ADD_DEFINITIONS(-Os -ggdb -Wall -Werror --std=gnu99 -Wmissing-declarations) + +SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") + +IF(APPLE) + INCLUDE_DIRECTORIES(/opt/local/include) + LINK_DIRECTORIES(/opt/local/lib) +ENDIF() + + +ADD_LIBRARY(setlbf SHARED service/setlbf.c) +INSTALL(TARGETS setlbf + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + + +SET(SOURCES procd.c signal.c state.c inittab.c rcS.c ubus.c system.c + service/service.c service/instance.c service/validate.c service/trigger.c service/watch.c + utils/utils.c) +IF(NOT DISABLE_INIT) + SET(SOURCES ${SOURCES} watchdog.c plug/coldplug.c plug/hotplug.c) +ENDIF() + +SET(LIBS ubox ubus json-c blobmsg_json json_script) + +IF(DEBUG) + ADD_DEFINITIONS(-DDEBUG -g3) +ENDIF() + +IF(EARLY_PATH) + ADD_DEFINITIONS(-DEARLY_PATH="${EARLY_PATH}") +ENDIF() + +IF(ZRAM_TMPFS) + ADD_DEFINITIONS(-DZRAM_TMPFS) + SET(SOURCES_ZRAM initd/zram.c) +ENDIF() + +IF(BUILD_UPGRADED) + add_subdirectory(upgraded) +ENDIF() + +ADD_EXECUTABLE(procd ${SOURCES}) +TARGET_LINK_LIBRARIES(procd ${LIBS}) +INSTALL(TARGETS procd + RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} +) + +FIND_PATH(ubox_include_dir libubox/uloop.h) +INCLUDE_DIRECTORIES(${ubox_include_dir}) + +IF(DISABLE_INIT) +ADD_DEFINITIONS(-DDISABLE_INIT) +ELSE() +ADD_EXECUTABLE(init initd/init.c initd/early.c initd/preinit.c initd/mkdev.c watchdog.c + utils/utils.c ${SOURCES_ZRAM}) +TARGET_LINK_LIBRARIES(init ${LIBS}) +INSTALL(TARGETS init + RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} +) + +ADD_EXECUTABLE(udevtrigger plug/udevtrigger.c) +INSTALL(TARGETS udevtrigger + RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} +) +ENDIF() + + +ADD_EXECUTABLE(askfirst utils/askfirst.c) +INSTALL(TARGETS askfirst + RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} +) + +ADD_CUSTOM_COMMAND( + OUTPUT syscall-names.h + COMMAND ./make_syscall_h.sh ${CMAKE_C_COMPILER} > ./syscall-names.h + DEPENDS ./make_syscall_h.sh +) +ADD_CUSTOM_TARGET(syscall-names-h DEPENDS syscall-names.h) + +ADD_CUSTOM_COMMAND( + OUTPUT capabilities-names.h + COMMAND ./make_capabilities_h.sh ${CMAKE_C_COMPILER} > ./capabilities-names.h + DEPENDS ./make_capabilities_h.sh +) +ADD_CUSTOM_TARGET(capabilities-names-h DEPENDS capabilities-names.h) + +IF(SECCOMP_SUPPORT) +ADD_LIBRARY(preload-seccomp SHARED jail/preload.c jail/seccomp.c) +TARGET_LINK_LIBRARIES(preload-seccomp dl ubox blobmsg_json) +INSTALL(TARGETS preload-seccomp + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) +ADD_DEPENDENCIES(preload-seccomp syscall-names-h) +endif() + +IF(JAIL_SUPPORT) +ADD_EXECUTABLE(ujail jail/jail.c jail/elf.c jail/fs.c jail/capabilities.c) +TARGET_LINK_LIBRARIES(ujail ubox blobmsg_json) +INSTALL(TARGETS ujail + RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} +) +ADD_DEPENDENCIES(ujail capabilities-names-h) +endif() + +IF(UTRACE_SUPPORT) +ADD_EXECUTABLE(utrace trace/trace.c) +TARGET_LINK_LIBRARIES(utrace ubox ${json} blobmsg_json) +INSTALL(TARGETS utrace + RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} +) +ADD_DEPENDENCIES(utrace syscall-names-h) + +ADD_LIBRARY(preload-trace SHARED trace/preload.c) +TARGET_LINK_LIBRARIES(preload-trace dl) +INSTALL(TARGETS preload-trace + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) +endif() diff --git a/src/3P/procd/builders/cmake/CMakeLists.txt b/src/3P/procd/builders/cmake/CMakeLists.txt new file mode 100644 index 00000000..96afa6c1 --- /dev/null +++ b/src/3P/procd/builders/cmake/CMakeLists.txt @@ -0,0 +1,78 @@ +cmake_minimum_required (VERSION 3.0) + +project (procd) + +ADD_DEFINITIONS(-Os -ggdb -Wall -Werror --std=gnu99 -Wmissing-declarations) + +set (CMAKE_MODULE_PATH "${MODULE_PATH}") + +set(DISABLE_TARGET_OPTIMIZATION ON) + +include (aw) + +file ( + GLOB_RECURSE + init_source_files + + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/initd/init.c + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/initd/early.c + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/initd/preinit.c + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/initd/mkdev.c + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/watchdog.c + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/utils/utils.c + ) + +# Init +IF(NOT DISABLE_INIT) +add_executable (init ${init_source_files}) +target_link_libraries (init ubox ubus) + +install (TARGETS init RUNTIME DESTINATION ../sbin) +ENDIF() + +# procd +file ( + GLOB_RECURSE + procd_source_files + + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/procd.c + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/signal.c + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/state.c + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/inittab.c + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/rcS.c + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/ubus.c + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/system.c + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/service/service.c + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/service/instance.c + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/service/validate.c + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/service/trigger.c + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/service/watch.c + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/utils/utils.c + ) + +IF(DISABLE_INIT) + ADD_DEFINITIONS(-DDISABLE_INIT) +ENDIF() + +IF(NOT DISABLE_INIT) +list (APPEND procd_source_files + + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/watchdog.c + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/plug/coldplug.c + $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/plug/hotplug.c +) +ENDIF() + +IF(DEBUG) + ADD_DEFINITIONS(-DDEBUG -g3) +ENDIF() + +add_executable (procd ${procd_source_files}) +target_link_libraries (procd ubox ubus json-c blobmsg_json json_script) + +install (TARGETS procd RUNTIME DESTINATION ../sbin) + +# udevtrigger +add_executable (udevtrigger $ENV{AWOXCVS}/AwoxAudio/Products/External/procd/plug/udevtrigger.c) +install (TARGETS udevtrigger + RUNTIME DESTINATION ../sbin) diff --git a/src/3P/procd/initd/early.c b/src/3P/procd/initd/early.c new file mode 100644 index 00000000..bf519f61 --- /dev/null +++ b/src/3P/procd/initd/early.c @@ -0,0 +1,98 @@ +/* + * 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 "../utils/utils.h" +#include "init.h" +#include "../libc-compat.h" + +static void +early_dev(void) +{ + mkdev("*", 0600); + mknod("/dev/null", 0666, makedev(1, 3)); +} + +static void +early_console(const char *dev) +{ + struct stat s; + + if (stat(dev, &s)) { + ERROR("Failed to stat %s\n", dev); + return; + } + + if (patch_stdio(dev)) { + ERROR("Failed to setup i/o redirection\n"); + return; + } + + fcntl(STDERR_FILENO, F_SETFL, fcntl(STDERR_FILENO, F_GETFL) | O_NONBLOCK); +} + +static void +early_mounts(void) +{ + unsigned int oldumask = umask(0); + + mount("proc", "/proc", "proc", MS_NOATIME | MS_NODEV | MS_NOEXEC | MS_NOSUID, 0); + mount("sysfs", "/sys", "sysfs", MS_NOATIME | MS_NODEV | MS_NOEXEC | MS_NOSUID, 0); + mount("cgroup", "/sys/fs/cgroup", "cgroup", MS_NODEV | MS_NOEXEC | MS_NOSUID, 0); + mount("tmpfs", "/dev", "tmpfs", MS_NOATIME | MS_NOSUID, "mode=0755,size=512K"); + ignore(symlink("/tmp/shm", "/dev/shm")); + mkdir("/dev/pts", 0755); + mount("devpts", "/dev/pts", "devpts", MS_NOATIME | MS_NOEXEC | MS_NOSUID, "mode=600"); + early_dev(); + + early_console("/dev/console"); + if (mount_zram_on_tmp()) { + mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_NODEV | MS_NOATIME, 0); + mkdir("/tmp/shm", 01777); + } else { + mkdir("/tmp/shm", 01777); + mount("tmpfs", "/tmp/shm", "tmpfs", MS_NOSUID | MS_NODEV | MS_NOATIME, + "mode=01777"); + } + mkdir("/tmp/run", 0755); + mkdir("/tmp/lock", 0755); + mkdir("/tmp/state", 0755); + umask(oldumask); +} + +static void +early_env(void) +{ + setenv("PATH", EARLY_PATH, 1); +} + +void +early(void) +{ + if (getpid() != 1) + return; + + early_mounts(); + early_env(); + + LOG("Console is alive\n"); +} diff --git a/src/3P/procd/initd/init.c b/src/3P/procd/initd/init.c new file mode 100644 index 00000000..1683e012 --- /dev/null +++ b/src/3P/procd/initd/init.c @@ -0,0 +1,113 @@ +/* + * 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 +#include + +#include "../utils/utils.h" +#include "init.h" +#include "../watchdog.h" + +unsigned int debug = 0; + +static void +signal_shutdown(int signal, siginfo_t *siginfo, void *data) +{ + fprintf(stderr, "reboot\n"); + fflush(stderr); + sync(); + sleep(2); + reboot(RB_AUTOBOOT); + while (1) + ; +} + +static struct sigaction sa_shutdown = { + .sa_sigaction = signal_shutdown, + .sa_flags = SA_SIGINFO +}; + +static void +cmdline(void) +{ + char line[20]; + char* res; + long r; + + res = get_cmdline_val("init_debug", line, sizeof(line)); + if (res != NULL) { + r = strtol(line, NULL, 10); + if ((r != LONG_MIN) && (r != LONG_MAX)) + debug = (int) r; + } +} + +int +main(int argc, char **argv) +{ + pid_t pid; + + ulog_open(ULOG_KMSG, LOG_DAEMON, "init"); + + sigaction(SIGTERM, &sa_shutdown, NULL); + sigaction(SIGUSR1, &sa_shutdown, NULL); + sigaction(SIGUSR2, &sa_shutdown, NULL); + + early(); + cmdline(); + watchdog_init(1); + + pid = fork(); + if (!pid) { + char *kmod[] = { "/sbin/kmodloader", "/etc/modules-boot.d/", NULL }; + + if (debug < 3) + patch_stdio("/dev/null"); + + execvp(kmod[0], kmod); + ERROR("Failed to start kmodloader\n"); + exit(-1); + } + if (pid <= 0) { + ERROR("Failed to start kmodloader instance\n"); + } else { + int i; + + for (i = 0; i < 1200; i++) { + if (waitpid(pid, NULL, WNOHANG) > 0) + break; + usleep(10 * 1000); + watchdog_ping(); + } + } + uloop_init(); + preinit(); + uloop_run(); + + return 0; +} diff --git a/src/3P/procd/initd/init.h b/src/3P/procd/initd/init.h new file mode 100644 index 00000000..123e1146 --- /dev/null +++ b/src/3P/procd/initd/init.h @@ -0,0 +1,36 @@ +/* + * 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 _INIT_H__ +#define _INIT_H__ + +#include + +#include "../log.h" + +#ifndef EARLY_PATH +#define EARLY_PATH "/usr/sbin:/sbin:/usr/bin:/bin" +#endif + +void preinit(void); +void early(void); +int mkdev(const char *progname, int progmode); + +#ifdef ZRAM_TMPFS +int mount_zram_on_tmp(void); +#else +static inline int mount_zram_on_tmp(void) { + return -ENOSYS; +} +#endif +#endif diff --git a/src/3P/procd/initd/mkdev.c b/src/3P/procd/initd/mkdev.c new file mode 100644 index 00000000..e6d3d0c2 --- /dev/null +++ b/src/3P/procd/initd/mkdev.c @@ -0,0 +1,125 @@ +/* + * 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. + */ + +#define _DEFAULT_SOURCE + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "init.h" + +static char **patterns; +static int n_patterns; +static char buf[PATH_MAX]; +static char buf2[PATH_MAX]; +static unsigned int mode = 0600; + +static bool find_pattern(const char *name) +{ + int i; + + for (i = 0; i < n_patterns; i++) + if (!fnmatch(patterns[i], name, 0)) + return true; + + return false; +} + +static void make_dev(const char *path, bool block, int major, int minor) +{ + unsigned int oldumask = umask(0); + unsigned int _mode = mode | (block ? S_IFBLK : S_IFCHR); + + DEBUG(4, "Creating %s device %s(%d,%d)\n", + block ? "block" : "character", + path, major, minor); + + mknod(path, _mode, makedev(major, minor)); + umask(oldumask); +} + +static void find_devs(bool block) +{ + char *path = block ? "/sys/dev/block" : "/sys/dev/char"; + struct dirent *dp; + DIR *dir; + + dir = opendir(path); + if (!dir) + return; + + path = buf2 + sprintf(buf2, "%s/", path); + while ((dp = readdir(dir)) != NULL) { + char *c; + int major = 0, minor = 0; + int len; + + if (dp->d_type != DT_LNK) + continue; + + if (sscanf(dp->d_name, "%d:%d", &major, &minor) != 2) + continue; + + strcpy(path, dp->d_name); + len = readlink(buf2, buf, sizeof(buf)); + if (len <= 0) + continue; + + buf[len] = 0; + if (!find_pattern(buf)) + continue; + + c = strrchr(buf, '/'); + if (!c) + continue; + + c++; + make_dev(c, block, major, minor); + } + closedir(dir); +} + +static char *add_pattern(const char *name) +{ + char *str = malloc(strlen(name) + 2); + + str[0] = '*'; + strcpy(str + 1, name); + return str; +} + +int mkdev(const char *name, int _mode) +{ + char *pattern; + + if (chdir("/dev")) + return 1; + + pattern = add_pattern(name); + patterns = &pattern; + mode = _mode; + n_patterns = 1; + find_devs(true); + find_devs(false); + return chdir("/"); +} diff --git a/src/3P/procd/initd/preinit.c b/src/3P/procd/initd/preinit.c new file mode 100644 index 00000000..121fb172 --- /dev/null +++ b/src/3P/procd/initd/preinit.c @@ -0,0 +1,132 @@ +/* + * 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 "init.h" +#include "../watchdog.h" + +static struct uloop_process preinit_proc; +static struct uloop_process plugd_proc; + +static void +check_dbglvl(void) +{ + FILE *fp = fopen("/tmp/debug_level", "r"); + int lvl = 0; + + if (!fp) + return; + if (fscanf(fp, "%d", &lvl) == EOF) + ERROR("failed to read debug level\n"); + fclose(fp); + unlink("/tmp/debug_level"); + + if (lvl > 0 && lvl < 5) + debug = lvl; +} + +static void +spawn_procd(struct uloop_process *proc, int ret) +{ + char *wdt_fd = watchdog_fd(); + char *argv[] = { "/sbin/procd", NULL}; + struct stat s; + char dbg[2]; + + if (plugd_proc.pid > 0) + kill(plugd_proc.pid, SIGKILL); + + if (!stat("/tmp/sysupgrade", &s)) + while (true) + sleep(1); + + unsetenv("INITRAMFS"); + unsetenv("PREINIT"); + unlink("/tmp/.preinit"); + DEBUG(2, "Exec to real procd now\n"); + if (wdt_fd) + setenv("WDTFD", wdt_fd, 1); + check_dbglvl(); + if (debug > 0) { + snprintf(dbg, 2, "%d", debug); + setenv("DBGLVL", dbg, 1); + } + + execvp(argv[0], argv); +} + +static void +plugd_proc_cb(struct uloop_process *proc, int ret) +{ + proc->pid = 0; +} + +void +preinit(void) +{ + char *init[] = { "/bin/sh", "/etc/preinit", NULL }; + char *plug[] = { "/sbin/procd", "-h", "/etc/hotplug-preinit.json", NULL }; + int fd; + + LOG("- preinit -\n"); + + plugd_proc.cb = plugd_proc_cb; + plugd_proc.pid = fork(); + if (!plugd_proc.pid) { + execvp(plug[0], plug); + ERROR("Failed to start plugd\n"); + exit(-1); + } + if (plugd_proc.pid <= 0) { + ERROR("Failed to start new plugd instance\n"); + return; + } + uloop_process_add(&plugd_proc); + + setenv("PREINIT", "1", 1); + + fd = creat("/tmp/.preinit", 0600); + + if (fd < 0) + ERROR("Failed to create sentinel file\n"); + else + close(fd); + + preinit_proc.cb = spawn_procd; + preinit_proc.pid = fork(); + if (!preinit_proc.pid) { + execvp(init[0], init); + ERROR("Failed to start preinit\n"); + exit(-1); + } + if (preinit_proc.pid <= 0) { + ERROR("Failed to start new preinit instance\n"); + return; + } + uloop_process_add(&preinit_proc); + + DEBUG(4, "Launched preinit instance, pid=%d\n", (int) preinit_proc.pid); +} diff --git a/src/3P/procd/initd/zram.c b/src/3P/procd/initd/zram.c new file mode 100644 index 00000000..49480afa --- /dev/null +++ b/src/3P/procd/initd/zram.c @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../log.h" + +#include "init.h" + +#define KB(x) (x * 1024) + +#define ZRAM_MOD_PATH "/lib/modules/%s/zram.ko" +#define EXT4_MOD_PATH "/lib/modules/%s/ext4.ko" + +static long +proc_meminfo(void) +{ + FILE *fp; + char line[256]; + char *key; + long val = KB(16); + + fp = fopen("/proc/meminfo", "r"); + if (fp == NULL) { + ERROR("Can't open /proc/meminfo: %s\n", strerror(errno)); + return errno; + } + + while (fgets(line, sizeof(line), fp)) { + key = strtok(line, ":"); + if (strcasecmp(key, "MemTotal")) + continue; + val = atol(strtok(NULL, " kB\n")); + break; + } + fclose(fp); + + if (val > KB(32)) + val = KB(32); + + return val; +} + +static int +early_insmod(char *module) +{ + pid_t pid = fork(); + + if (!pid) { + char *modprobe[] = { "/usr/sbin/modprobe", NULL, NULL }; + char *path; + struct utsname ver; + + uname(&ver); + path = alloca(sizeof(module) + strlen(ver.release) + 1); + sprintf(path, module, ver.release); + modprobe[1] = path; + execvp(modprobe[0], modprobe); + ERROR("Can't exec /usr/sbin/modprobe\n"); + exit(-1); + } + + if (pid <= 0) { + ERROR("Can't exec /usr/sbin/modprobe\n"); + return -1; + } else { + waitpid(pid, NULL, 0); + } + + return 0; +} + + +int +mount_zram_on_tmp(void) +{ + char *mkfs[] = { "/usr/sbin/mkfs.ext4", "-b", "4096", "-F", "-L", "TEMP", "-m", "0", "/dev/zram0", NULL }; + FILE *fp; + long zramsize; + pid_t pid; + int ret; + + if (early_insmod(ZRAM_MOD_PATH) || early_insmod(EXT4_MOD_PATH)) { + ERROR("failed to insmod zram support\n"); + return -1; + } + + mkdev("*", 0600); + + zramsize = proc_meminfo() / 2; + fp = fopen("/sys/block/zram0/disksize", "r+"); + if (fp == NULL) { + ERROR("Can't open /sys/block/zram0/disksize: %s\n", strerror(errno)); + return errno; + } + fprintf(fp, "%ld", KB(zramsize)); + fclose(fp); + + pid = fork(); + if (!pid) { + execvp(mkfs[0], mkfs); + ERROR("Can't exec /sbin/mkfs.ext4\n"); + exit(-1); + } else if (pid <= 0) { + ERROR("Can't exec /sbin/mkfs.ext4\n"); + return -1; + } else { + waitpid(pid, NULL, 0); + } + + ret = mount("/dev/zram0", "/tmp", "ext4", MS_NOSUID | MS_NODEV | MS_NOATIME, "errors=continue,noquota"); + if (ret < 0) { + ERROR("Can't mount /dev/zram0 on /tmp: %s\n", strerror(errno)); + return errno; + } + + LOG("Using up to %ld kB of RAM as ZRAM storage on /mnt\n", zramsize); + + return 0; +} diff --git a/src/3P/procd/inittab.c b/src/3P/procd/inittab.c new file mode 100644 index 00000000..3ae561a4 --- /dev/null +++ b/src/3P/procd/inittab.c @@ -0,0 +1,321 @@ +/* + * 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. + */ + +//AWOX #define _GNU_SOURCE +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "utils/utils.h" +#include "procd.h" +#include "rcS.h" + +#define TAG_ID 0 +#define TAG_RUNLVL 1 +#define TAG_ACTION 2 +#define TAG_PROCESS 3 + +#define MAX_ARGS 8 + +struct init_action; +char *console = NULL; + +struct init_handler { + const char *name; + void (*cb) (struct init_action *a); + int multi; +}; + +struct init_action { + struct list_head list; + + char *id; + char *argv[MAX_ARGS]; + char *line; + + struct init_handler *handler; + struct uloop_process proc; + + int respawn; + struct uloop_timeout tout; +}; + +static const char *tab = "/etc/inittab"; +static char *ask = "/sbin/askfirst"; + +static LIST_HEAD(actions); + +static int dev_exist(const char *dev) +{ + int dfd, fd; + + dfd = open("/dev", O_PATH|O_DIRECTORY); + + if (dfd < 0) + return 0; + + fd = openat(dfd, dev, O_RDONLY); + close(dfd); + + if (fd < 0) + return 0; + + close(fd); + return 1; +} + +static void fork_worker(struct init_action *a) +{ + pid_t p; + + a->proc.pid = fork(); + if (!a->proc.pid) { + p = setsid(); + + if (patch_stdio(a->id)) + ERROR("Failed to setup i/o redirection\n"); + + ioctl(STDIN_FILENO, TIOCSCTTY, 1); + tcsetpgrp(STDIN_FILENO, p); + + execvp(a->argv[0], a->argv); + ERROR("Failed to execute %s\n", a->argv[0]); + exit(-1); + } + + if (a->proc.pid > 0) { + DEBUG(4, "Launched new %s action, pid=%d\n", + a->handler->name, + (int) a->proc.pid); + uloop_process_add(&a->proc); + } +} + +static void child_exit(struct uloop_process *proc, int ret) +{ + struct init_action *a = container_of(proc, struct init_action, proc); + + DEBUG(4, "pid:%d\n", proc->pid); + uloop_timeout_set(&a->tout, a->respawn); +} + +static void respawn(struct uloop_timeout *tout) +{ + struct init_action *a = container_of(tout, struct init_action, tout); + fork_worker(a); +} + +static void rcdone(struct runqueue *q) +{ + procd_state_next(); +} + +static void runrc(struct init_action *a) +{ + if (!a->argv[1] || !a->argv[2]) { + ERROR("valid format is rcS \n"); + return; + } + + /* proceed even if no init or shutdown scripts run */ + if (rcS(a->argv[1], a->argv[2], rcdone)) + rcdone(NULL); +} + +static void askfirst(struct init_action *a) +{ + int i; + + if (!dev_exist(a->id) || (console && !strcmp(console, a->id))) { + DEBUG(4, "Skipping %s\n", a->id); + return; + } + + a->tout.cb = respawn; + for (i = MAX_ARGS - 1; i >= 1; i--) + a->argv[i] = a->argv[i - 1]; + a->argv[0] = ask; + a->respawn = 500; + + a->proc.cb = child_exit; + fork_worker(a); +} + +static void askconsole(struct init_action *a) +{ + char line[256], *tty, *split; + int i; + + tty = get_cmdline_val("console", line, sizeof(line)); + if (tty != NULL) { + split = strchr(tty, ','); + if (split != NULL) + *split = '\0'; + + if (!dev_exist(tty)) { + DEBUG(4, "skipping %s\n", tty); + return; + } + + console = strdup(tty); + a->id = strdup(tty); + } + else { + console = NULL; + a->id = NULL; + } + + a->tout.cb = respawn; + for (i = MAX_ARGS - 1; i >= 1; i--) + a->argv[i] = a->argv[i - 1]; + a->argv[0] = ask; + a->respawn = 500; + + a->proc.cb = child_exit; + fork_worker(a); +} + +static void rcrespawn(struct init_action *a) +{ + a->tout.cb = respawn; + a->respawn = 500; + + a->proc.cb = child_exit; + fork_worker(a); +} + +static struct init_handler handlers[] = { + { + .name = "sysinit", + .cb = runrc, + }, { + .name = "shutdown", + .cb = runrc, + }, { + .name = "askfirst", + .cb = askfirst, + .multi = 1, + }, { + .name = "askconsole", + .cb = askconsole, + .multi = 1, + }, { + .name = "respawn", + .cb = rcrespawn, + .multi = 1, + } +}; + +static int add_action(struct init_action *a, const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(handlers); i++) + if (!strcmp(handlers[i].name, name)) { + a->handler = &handlers[i]; + list_add_tail(&a->list, &actions); + return 0; + } + ERROR("Unknown init handler %s\n", name); + return -1; +} + +void procd_inittab_run(const char *handler) +{ + struct init_action *a; + + list_for_each_entry(a, &actions, list) + if (!strcmp(a->handler->name, handler)) { + if (a->handler->multi) { + a->handler->cb(a); + continue; + } + a->handler->cb(a); + break; + } +} + +void procd_inittab(void) +{ +#define LINE_LEN 128 + FILE *fp = fopen(tab, "r"); + struct init_action *a; + regex_t pat_inittab; + regmatch_t matches[5]; + char *line; + + if (!fp) { + ERROR("Failed to open %s\n", tab); + return; + } + + regcomp(&pat_inittab, "([a-zA-Z0-9]*):([a-zA-Z0-9]*):([a-zA-Z0-9]*):(.*)", REG_EXTENDED); + line = malloc(LINE_LEN); + a = malloc(sizeof(struct init_action)); + memset(a, 0, sizeof(struct init_action)); + + while (fgets(line, LINE_LEN, fp)) { + char *tags[TAG_PROCESS + 1]; + char *tok; + int i; + int len = strlen(line); + + while (isspace(line[len - 1])) + len--; + line[len] = 0; + + if (*line == '#') + continue; + + if (regexec(&pat_inittab, line, 5, matches, 0)) + continue; + + DEBUG(4, "Parsing inittab - %s", line); + + for (i = TAG_ID; i <= TAG_PROCESS; i++) { + line[matches[i].rm_eo] = '\0'; + tags[i] = &line[matches[i + 1].rm_so]; + }; + + tok = strtok(tags[TAG_PROCESS], " "); + for (i = 0; i < (MAX_ARGS - 1) && tok; i++) { + a->argv[i] = tok; + tok = strtok(NULL, " "); + } + a->argv[i] = NULL; + a->id = tags[TAG_ID]; + a->line = line; + + if (add_action(a, tags[TAG_ACTION])) + continue; + line = malloc(LINE_LEN); + a = malloc(sizeof(struct init_action)); + memset(a, 0, sizeof(struct init_action)); + } + + fclose(fp); + free(line); + free(a); + regfree(&pat_inittab); +} diff --git a/src/3P/procd/jail/capabilities.c b/src/3P/procd/jail/capabilities.c new file mode 100644 index 00000000..b5ea9654 --- /dev/null +++ b/src/3P/procd/jail/capabilities.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2015 Etienne CHAMPETIER + * + * 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. + */ + +#define _GNU_SOURCE 1 +#include +#include + +#include +#include + +#include "log.h" +#include "../capabilities-names.h" +#include "capabilities.h" + +static int find_capabilities(const char *name) +{ + int i; + + for (i = 0; i <= CAP_LAST_CAP; i++) + if (capabilities_names[i] && !strcmp(capabilities_names[i], name)) + return i; + + return -1; +} + +int drop_capabilities(const char *file) +{ + enum { + CAP_KEEP, + CAP_DROP, + __CAP_MAX + }; + static const struct blobmsg_policy policy[__CAP_MAX] = { + [CAP_KEEP] = { .name = "cap.keep", .type = BLOBMSG_TYPE_ARRAY }, + [CAP_DROP] = { .name = "cap.drop", .type = BLOBMSG_TYPE_ARRAY }, + }; + struct blob_buf b = { 0 }; + struct blob_attr *tb[__CAP_MAX]; + struct blob_attr *cur; + int rem, cap; + char *name; + uint64_t capdrop = 0LLU; + + DEBUG("dropping capabilities\n"); + + blob_buf_init(&b, 0); + if (!blobmsg_add_json_from_file(&b, file)) { + ERROR("failed to load %s\n", file); + return -1; + } + + blobmsg_parse(policy, __CAP_MAX, tb, blob_data(b.head), blob_len(b.head)); + if (!tb[CAP_KEEP] && !tb[CAP_DROP]) { + ERROR("failed to parse %s\n", file); + return -1; + } + + blobmsg_for_each_attr(cur, tb[CAP_KEEP], rem) { + name = blobmsg_get_string(cur); + if (!name) { + ERROR("invalid capability name in cap.keep\n"); + return -1; + } + cap = find_capabilities(name); + if (cap == -1) { + ERROR("unknown capability %s in cap.keep\n", name); + return -1; + } + capdrop |= (1LLU << cap); + } + + if (capdrop == 0LLU) { + DEBUG("cap.keep empty -> only dropping capabilities from cap.drop (blacklist)\n"); + capdrop = 0xffffffffffffffffLLU; + } else { + DEBUG("cap.keep has at least one capability -> dropping every capabilities not in cap.keep (whitelist)\n"); + } + + blobmsg_for_each_attr(cur, tb[CAP_DROP], rem) { + name = blobmsg_get_string(cur); + if (!name) { + ERROR("invalid capability name in cap.drop\n"); + return -1; + } + cap = find_capabilities(name); + if (cap == -1) { + ERROR("unknown capability %s in cap.drop\n", name); + return -1; + } + capdrop &= ~(1LLU << cap); + } + + for (cap = 0; cap <= CAP_LAST_CAP; cap++) { + if ( (capdrop & (1LLU << cap)) == 0) { + DEBUG("dropping capability %s (%d)\n", capabilities_names[cap], cap); + if (prctl(PR_CAPBSET_DROP, cap, 0, 0, 0)) { + ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s\n", cap, strerror(errno)); + return errno; + } + } else { + DEBUG("keeping capability %s (%d)\n", capabilities_names[cap], cap); + } + } + + return 0; +} diff --git a/src/3P/procd/jail/capabilities.h b/src/3P/procd/jail/capabilities.h new file mode 100644 index 00000000..11b8cc27 --- /dev/null +++ b/src/3P/procd/jail/capabilities.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2015 Etienne CHAMPETIER + * + * 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 _JAIL_CAPABILITIES_H_ +#define _JAIL_CAPABILITIES_H_ + +int drop_capabilities(const char *file); + +#endif diff --git a/src/3P/procd/jail/elf.c b/src/3P/procd/jail/elf.c new file mode 100644 index 00000000..7c6076af --- /dev/null +++ b/src/3P/procd/jail/elf.c @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2015 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. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include + +#include "elf.h" +#include "fs.h" +#include "log.h" + +struct avl_tree libraries; +static LIST_HEAD(library_paths); + +static void alloc_library_path(const char *path) +{ + struct stat s; + if (stat(path, &s)) + return; + + struct library_path *p; + char *_path; + + p = calloc_a(sizeof(*p), + &_path, strlen(path) + 1); + if (!p) + return; + + p->path = strcpy(_path, path); + + list_add_tail(&p->list, &library_paths); + DEBUG("adding ld.so path %s\n", path); +} + +/* + * path = full path + * name = soname/avl key + */ +void alloc_library(const char *path, const char *name) +{ + struct library *l; + char *_name, *_path; + + l = calloc_a(sizeof(*l), + &_path, strlen(path) + 1, + &_name, strlen(name) + 1); + if (!l) + return; + + l->avl.key = l->name = strcpy(_name, name); + l->path = strcpy(_path, path); + + avl_insert(&libraries, &l->avl); + DEBUG("adding library %s (%s)\n", path, name); +} + +int lib_open(char **fullpath, const char *file) +{ + struct library_path *p; + char path[PATH_MAX]; + int fd = -1; + + *fullpath = NULL; + + list_for_each_entry(p, &library_paths, list) { + snprintf(path, sizeof(path), "%s/%s", p->path, file); + fd = open(path, O_RDONLY|O_CLOEXEC); + if (fd >= 0) { + *fullpath = strdup(path); + break; + } + } + + return fd; +} + +const char* find_lib(const char *file) +{ + struct library *l; + + l = avl_find_element(&libraries, file, l, avl); + if (!l) + return NULL; + + return l->path; +} + +static int elf64_find_section(const char *map, unsigned int type, unsigned int *offset, unsigned int *size, unsigned int *vaddr) +{ + Elf64_Ehdr *e; + Elf64_Phdr *ph; + int i; + + e = (Elf64_Ehdr *) map; + ph = (Elf64_Phdr *) (map + e->e_phoff); + + for (i = 0; i < e->e_phnum; i++) { + if (ph[i].p_type == type) { + *offset = ph[i].p_offset; + if (size) + *size = ph[i].p_filesz; + if (vaddr) + *vaddr = ph[i].p_vaddr; + return 0; + } + } + + return -1; +} + +static int elf32_find_section(const char *map, unsigned int type, unsigned int *offset, unsigned int *size, unsigned int *vaddr) +{ + Elf32_Ehdr *e; + Elf32_Phdr *ph; + int i; + + e = (Elf32_Ehdr *) map; + ph = (Elf32_Phdr *) (map + e->e_phoff); + + for (i = 0; i < e->e_phnum; i++) { + if (ph[i].p_type == type) { + *offset = ph[i].p_offset; + if (size) + *size = ph[i].p_filesz; + if (vaddr) + *vaddr = ph[i].p_vaddr; + return 0; + } + } + + return -1; +} + +static int elf_find_section(const char *map, unsigned int type, unsigned int *offset, unsigned int *size, unsigned int *vaddr) +{ + int clazz = map[EI_CLASS]; + + if (clazz == ELFCLASS32) + return elf32_find_section(map, type, offset, size, vaddr); + else if (clazz == ELFCLASS64) + return elf64_find_section(map, type, offset, size, vaddr); + + ERROR("unknown elf format %d\n", clazz); + + return -1; +} + +static int elf32_scan_dynamic(const char *map, int dyn_offset, int dyn_size, int load_offset) +{ + Elf32_Dyn *dynamic = (Elf32_Dyn *) (map + dyn_offset); + const char *strtab = NULL; + + while ((void *) dynamic < (void *) (map + dyn_offset + dyn_size)) { + Elf32_Dyn *curr = dynamic; + + dynamic++; + if (curr->d_tag != DT_STRTAB) + continue; + + strtab = map + (curr->d_un.d_ptr - load_offset); + break; + } + + if (!strtab) + return -1; + + dynamic = (Elf32_Dyn *) (map + dyn_offset); + while ((void *) dynamic < (void *) (map + dyn_offset + dyn_size)) { + Elf32_Dyn *curr = dynamic; + + dynamic++; + if (curr->d_tag != DT_NEEDED) + continue; + + if (add_path_and_deps(&strtab[curr->d_un.d_val], 1, -1, 1)) + return -1; + } + + return 0; +} + +static int elf64_scan_dynamic(const char *map, int dyn_offset, int dyn_size, int load_offset) +{ + Elf64_Dyn *dynamic = (Elf64_Dyn *) (map + dyn_offset); + const char *strtab = NULL; + + while ((void *) dynamic < (void *) (map + dyn_offset + dyn_size)) { + Elf64_Dyn *curr = dynamic; + + dynamic++; + if (curr->d_tag != DT_STRTAB) + continue; + + strtab = map + (curr->d_un.d_ptr - load_offset); + break; + } + + if (!strtab) + return -1; + + dynamic = (Elf64_Dyn *) (map + dyn_offset); + while ((void *) dynamic < (void *) (map + dyn_offset + dyn_size)) { + Elf64_Dyn *curr = dynamic; + + dynamic++; + if (curr->d_tag != DT_NEEDED) + continue; + + if (add_path_and_deps(&strtab[curr->d_un.d_val], 1, -1, 1)) + return -1; + } + + return 0; +} + +int elf_load_deps(const char *path, const char *map) +{ + unsigned int dyn_offset, dyn_size; + unsigned int load_offset, load_vaddr; + unsigned int interp_offset; + + if (elf_find_section(map, PT_LOAD, &load_offset, NULL, &load_vaddr)) { + ERROR("failed to load the .load section from %s\n", path); + return -1; + } + + if (elf_find_section(map, PT_DYNAMIC, &dyn_offset, &dyn_size, NULL)) { + ERROR("failed to load the .dynamic section from %s\n", path); + return -1; + } + + if (elf_find_section(map, PT_INTERP, &interp_offset, NULL, NULL) == 0) { + add_path_and_deps(map+interp_offset, 1, -1, 0); + } + + int clazz = map[EI_CLASS]; + + if (clazz == ELFCLASS32) + return elf32_scan_dynamic(map, dyn_offset, dyn_size, load_vaddr - load_offset); + else if (clazz == ELFCLASS64) + return elf64_scan_dynamic(map, dyn_offset, dyn_size, load_vaddr - load_offset); + + ERROR("unknown elf format %d\n", clazz); + return -1; +} + +static void load_ldso_conf(const char *conf) +{ + FILE* fp = fopen(conf, "r"); + char line[PATH_MAX]; + + if (!fp) { + DEBUG("failed to open %s\n", conf); + return; + } + + while (!feof(fp)) { + int len; + + if (!fgets(line, sizeof(line), fp)) + break; + len = strlen(line); + if (len < 2) + continue; + if (*line == '#') + continue; + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + if (!strncmp(line, "include ", 8)) { + char *sep = strstr(line, " "); + glob_t gl; + int i; + + if (!sep) + continue;; + while (*sep == ' ') + sep++; + if (glob(sep, GLOB_NOESCAPE | GLOB_MARK, NULL, &gl)) { + ERROR("glob failed on %s\n", sep); + continue; + } + for (i = 0; i < gl.gl_pathc; i++) + load_ldso_conf(gl.gl_pathv[i]); + globfree(&gl); + } else { + alloc_library_path(line); + } + } + + fclose(fp); +} + +void init_library_search(void) +{ + avl_init(&libraries, avl_strcmp, false, NULL); + alloc_library_path("/lib"); + alloc_library_path("/lib64"); + alloc_library_path("/usr/lib"); + load_ldso_conf("/etc/ld.so.conf"); +} diff --git a/src/3P/procd/jail/elf.h b/src/3P/procd/jail/elf.h new file mode 100644 index 00000000..78fedcd3 --- /dev/null +++ b/src/3P/procd/jail/elf.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 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 _JAIL_ELF_H_ +#define _JAIL_ELF_H_ + +#include +#include + +struct library { + struct avl_node avl; + char *name; + char *path; +}; + +struct library_path { + struct list_head list; + char *path; +}; + +extern struct avl_tree libraries; + +void alloc_library(const char *path, const char *name); +int elf_load_deps(const char *path, const char *map); +const char* find_lib(const char *file); +void init_library_search(void); +int lib_open(char **fullpath, const char *file); + +#endif diff --git a/src/3P/procd/jail/fs.c b/src/3P/procd/jail/fs.c new file mode 100644 index 00000000..c4cdcc93 --- /dev/null +++ b/src/3P/procd/jail/fs.c @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2015 John Crispin + * Copyright (C) 2015 Etienne Champetier + * + * 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. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "elf.h" +#include "fs.h" +#include "jail.h" +#include "log.h" + +struct mount { + struct avl_node avl; + const char *path; + int readonly; + int error; +}; + +struct avl_tree mounts; + +int add_mount(const char *path, int readonly, int error) +{ + assert(path != NULL); + + if (avl_find(&mounts, path)) + return 1; + + struct mount *m; + m = calloc(1, sizeof(struct mount)); + assert(m != NULL); + m->avl.key = m->path = strdup(path); + m->readonly = readonly; + m->error = error; + + avl_insert(&mounts, &m->avl); + DEBUG("adding mount %s ro(%d) err(%d)\n", m->path, m->readonly, m->error != 0); + return 0; +} + +int mount_all(const char *jailroot) { + struct library *l; + struct mount *m; + + avl_for_each_element(&libraries, l, avl) + add_mount(l->path, 1, -1); + + avl_for_each_element(&mounts, m, avl) + if (mount_bind(jailroot, m->path, m->readonly, m->error)) + return -1; + + return 0; +} + +void mount_list_init(void) { + avl_init(&mounts, avl_strcmp, false, NULL); +} + +static int add_script_interp(const char *path, const char *map, int size) +{ + int start = 2; + while (start < size && map[start] != '/') { + start++; + } + if (start >= size) { + ERROR("bad script interp (%s)\n", path); + return -1; + } + int stop = start + 1; + while (stop < size && map[stop] > 0x20 && map[stop] <= 0x7e) { + stop++; + } + if (stop >= size || (stop-start) > PATH_MAX) { + ERROR("bad script interp (%s)\n", path); + return -1; + } + char buf[PATH_MAX]; + strncpy(buf, map+start, stop-start); + return add_path_and_deps(buf, 1, -1, 0); +} + +int add_path_and_deps(const char *path, int readonly, int error, int lib) +{ + assert(path != NULL); + + if (lib == 0 && path[0] != '/') { + ERROR("%s is not an absolute path\n", path); + return error; + } + + char *map = NULL; + int fd, ret = -1; + if (path[0] == '/') { + if (avl_find(&mounts, path)) + return 0; + fd = open(path, O_RDONLY|O_CLOEXEC); + if (fd == -1) + return error; + add_mount(path, readonly, error); + } else { + if (avl_find(&libraries, path)) + return 0; + char *fullpath; + fd = lib_open(&fullpath, path); + if (fd == -1) + return error; + if (fullpath) { + alloc_library(fullpath, path); + free(fullpath); + } + } + + struct stat s; + if (fstat(fd, &s) == -1) { + ERROR("fstat(%s) failed: %s\n", path, strerror(errno)); + ret = error; + goto out; + } + + if (!S_ISREG(s.st_mode)) { + ret = 0; + goto out; + } + + /* too small to be an ELF or a script -> "normal" file */ + if (s.st_size < 4) { + ret = 0; + goto out; + } + + map = mmap(NULL, s.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (map == MAP_FAILED) { + ERROR("failed to mmap %s\n", path); + ret = -1; + goto out; + } + + if (map[0] == '#' && map[1] == '!') { + ret = add_script_interp(path, map, s.st_size); + goto out; + } + + if (map[0] == ELFMAG0 && map[1] == ELFMAG1 && map[2] == ELFMAG2 && map[3] == ELFMAG3) { + ret = elf_load_deps(path, map); + goto out; + } + + ret = 0; + +out: + if (fd >= 0) + close(fd); + if (map) + munmap(map, s.st_size); + + return ret; +} diff --git a/src/3P/procd/jail/fs.h b/src/3P/procd/jail/fs.h new file mode 100644 index 00000000..343335fb --- /dev/null +++ b/src/3P/procd/jail/fs.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2015 Etienne Champetier + * + * 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 _JAIL_FS_H_ +#define _JAIL_FS_H_ + +int add_mount(const char *path, int readonly, int error); +int add_path_and_deps(const char *path, int readonly, int error, int lib); +int mount_all(const char *jailroot); +void mount_list_init(void); + +#endif diff --git a/src/3P/procd/jail/jail.c b/src/3P/procd/jail/jail.c new file mode 100644 index 00000000..c4428471 --- /dev/null +++ b/src/3P/procd/jail/jail.c @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2015 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. + */ + +#define _GNU_SOURCE +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "capabilities.h" +#include "elf.h" +#include "fs.h" +#include "jail.h" +#include "log.h" + +#include + +#define STACK_SIZE (1024 * 1024) +#define OPT_ARGS "S:C:n:h:r:w:d:psuloc" + +static struct { + char *name; + char *hostname; + char **jail_argv; + char *seccomp; + char *capabilities; + int no_new_privs; + int namespace; + int procfs; + int ronly; + int sysfs; +} opts; + +extern int pivot_root(const char *new_root, const char *put_old); + +int debug = 0; + +static char child_stack[STACK_SIZE]; + +static int mkdir_p(char *dir, mode_t mask) +{ + char *l = strrchr(dir, '/'); + int ret; + + if (!l) + return 0; + + *l = '\0'; + + if (mkdir_p(dir, mask)) + return -1; + + *l = '/'; + + ret = mkdir(dir, mask); + if (ret && errno == EEXIST) + return 0; + + if (ret) + ERROR("mkdir(%s, %d) failed: %s\n", dir, mask, strerror(errno)); + + return ret; +} + +int mount_bind(const char *root, const char *path, int readonly, int error) +{ + struct stat s; + char new[PATH_MAX]; + int fd; + + if (stat(path, &s)) { + ERROR("stat(%s) failed: %s\n", path, strerror(errno)); + return error; + } + + snprintf(new, sizeof(new), "%s%s", root, path); + if (S_ISDIR(s.st_mode)) { + mkdir_p(new, 0755); + } else { + mkdir_p(dirname(new), 0755); + snprintf(new, sizeof(new), "%s%s", root, path); + fd = creat(new, 0644); + if (fd == -1) { + ERROR("creat(%s) failed: %s\n", new, strerror(errno)); + return -1; + } + close(fd); + } + + if (mount(path, new, NULL, MS_BIND, NULL)) { + ERROR("failed to mount -B %s %s: %s\n", path, new, strerror(errno)); + return -1; + } + + if (readonly && mount(NULL, new, NULL, MS_BIND | MS_REMOUNT | MS_RDONLY, NULL)) { + ERROR("failed to remount ro %s: %s\n", new, strerror(errno)); + return -1; + } + + DEBUG("mount -B %s %s (%s)\n", path, new, readonly?"ro":"rw"); + + return 0; +} + +static int build_jail_fs(void) +{ + char jail_root[] = "/tmp/ujail-XXXXXX"; + if (mkdtemp(jail_root) == NULL) { + ERROR("mkdtemp(%s) failed: %s\n", jail_root, strerror(errno)); + return -1; + } + + /* oldroot can't be MS_SHARED else pivot_root() fails */ + if (mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL)) { + ERROR("private mount failed %s\n", strerror(errno)); + return -1; + } + + if (mount("tmpfs", jail_root, "tmpfs", MS_NOATIME, "mode=0755")) { + ERROR("tmpfs mount failed %s\n", strerror(errno)); + return -1; + } + + if (chdir(jail_root)) { + ERROR("chdir(%s) (jail_root) failed: %s\n", jail_root, strerror(errno)); + return -1; + } + + if (mount_all(jail_root)) { + ERROR("mount_all() failed\n"); + return -1; + } + + char dirbuf[sizeof(jail_root) + 4]; + snprintf(dirbuf, sizeof(dirbuf), "%s/old", jail_root); + mkdir(dirbuf, 0755); + + if (pivot_root(jail_root, dirbuf) == -1) { + ERROR("pivot_root(%s, %s) failed: %s\n", jail_root, dirbuf, strerror(errno)); + return -1; + } + if (chdir("/")) { + ERROR("chdir(/) (after pivot_root) failed: %s\n", strerror(errno)); + return -1; + } + + snprintf(dirbuf, sizeof(dirbuf), "/old%s", jail_root); + rmdir(dirbuf); + umount2("/old", MNT_DETACH); + rmdir("/old"); + + if (opts.procfs) { + mkdir("/proc", 0755); + mount("proc", "/proc", "proc", MS_NOATIME | MS_NODEV | MS_NOEXEC | MS_NOSUID, 0); + } + if (opts.sysfs) { + mkdir("/sys", 0755); + mount("sysfs", "/sys", "sysfs", MS_NOATIME | MS_NODEV | MS_NOEXEC | MS_NOSUID, 0); + } + if (opts.ronly) + mount(NULL, "/", NULL, MS_RDONLY | MS_REMOUNT, 0); + + return 0; +} + +#define MAX_ENVP 8 +static char** build_envp(const char *seccomp) +{ + static char *envp[MAX_ENVP]; + static char preload_var[PATH_MAX]; + static char seccomp_var[PATH_MAX]; + static char debug_var[] = "LD_DEBUG=all"; + const char *preload_lib = find_lib("libpreload-seccomp.so"); + int count = 0; + + if (seccomp && !preload_lib) { + ERROR("failed to add preload-lib to env\n"); + return NULL; + } + if (seccomp) { + snprintf(seccomp_var, sizeof(seccomp_var), "SECCOMP_FILE=%s", seccomp); + envp[count++] = seccomp_var; + snprintf(preload_var, sizeof(preload_var), "LD_PRELOAD=%s", preload_lib); + envp[count++] = preload_var; + } + if (debug > 1) + envp[count++] = debug_var; + + return envp; +} + +static void usage(void) +{ + fprintf(stderr, "ujail -- \n"); + fprintf(stderr, " -d \tshow debug log (increase num to increase verbosity)\n"); + fprintf(stderr, " -S \tseccomp filter config\n"); + fprintf(stderr, " -C \tcapabilities drop config\n"); + fprintf(stderr, " -c\t\tset PR_SET_NO_NEW_PRIVS\n"); + fprintf(stderr, " -n \tthe name of the jail\n"); + fprintf(stderr, "namespace jail options:\n"); + fprintf(stderr, " -h \tchange the hostname of the jail\n"); + fprintf(stderr, " -r \treadonly files that should be staged\n"); + fprintf(stderr, " -w \twriteable files that should be staged\n"); + fprintf(stderr, " -p\t\tjail has /proc\n"); + fprintf(stderr, " -s\t\tjail has /sys\n"); + fprintf(stderr, " -l\t\tjail has /dev/log\n"); + fprintf(stderr, " -u\t\tjail has a ubus socket\n"); + fprintf(stderr, " -o\t\tremont jail root (/) read only\n"); + fprintf(stderr, "\nWarning: by default root inside the jail is the same\n\ +and he has the same powers as root outside the jail,\n\ +thus he can escape the jail and/or break stuff.\n\ +Please use seccomp/capabilities (-S/-C) to restrict his powers\n\n\ +If you use none of the namespace jail options,\n\ +ujail will not use namespace/build a jail,\n\ +and will only drop capabilities/apply seccomp filter.\n\n"); +} + +static int exec_jail(void *_notused) +{ + if (opts.capabilities && drop_capabilities(opts.capabilities)) + exit(EXIT_FAILURE); + + if (opts.no_new_privs && prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { + ERROR("prctl(PR_SET_NO_NEW_PRIVS) failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + if (opts.namespace && opts.hostname && strlen(opts.hostname) > 0 + && sethostname(opts.hostname, strlen(opts.hostname))) { + ERROR("sethostname(%s) failed: %s\n", opts.hostname, strerror(errno)); + exit(EXIT_FAILURE); + } + + if (opts.namespace && build_jail_fs()) { + ERROR("failed to build jail fs\n"); + exit(EXIT_FAILURE); + } + + char **envp = build_envp(opts.seccomp); + if (!envp) + exit(EXIT_FAILURE); + + INFO("exec-ing %s\n", *opts.jail_argv); + execve(*opts.jail_argv, opts.jail_argv, envp); + /* we get there only if execve fails */ + ERROR("failed to execve %s: %s\n", *opts.jail_argv, strerror(errno)); + exit(EXIT_FAILURE); +} + +static int jail_running = 1; +static int jail_return_code = 0; + +static void jail_process_handler(struct uloop_process *c, int ret) +{ + if (WIFEXITED(ret)) { + jail_return_code = WEXITSTATUS(ret); + INFO("jail (%d) exited with exit: %d\n", c->pid, jail_return_code); + } else { + jail_return_code = WTERMSIG(ret); + INFO("jail (%d) exited with signal: %d\n", c->pid, jail_return_code); + } + jail_running = 0; + uloop_end(); +} + +static struct uloop_process jail_process = { + .cb = jail_process_handler, +}; + +int main(int argc, char **argv) +{ + uid_t uid = getuid(); + char log[] = "/dev/log"; + char ubus[] = "/var/run/ubus.sock"; + int ch; + + if (uid) { + ERROR("not root, aborting: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + + umask(022); + mount_list_init(); + init_library_search(); + + while ((ch = getopt(argc, argv, OPT_ARGS)) != -1) { + switch (ch) { + case 'd': + debug = atoi(optarg); + break; + case 'p': + opts.namespace = 1; + opts.procfs = 1; + break; + case 'o': + opts.namespace = 1; + opts.ronly = 1; + break; + case 's': + opts.namespace = 1; + opts.sysfs = 1; + break; + case 'S': + opts.seccomp = optarg; + add_mount(optarg, 1, -1); + break; + case 'C': + opts.capabilities = optarg; + break; + case 'c': + opts.no_new_privs = 1; + break; + case 'n': + opts.name = optarg; + break; + case 'h': + opts.hostname = optarg; + break; + case 'r': + opts.namespace = 1; + add_path_and_deps(optarg, 1, 0, 0); + break; + case 'w': + opts.namespace = 1; + add_path_and_deps(optarg, 0, 0, 0); + break; + case 'u': + opts.namespace = 1; + add_mount(ubus, 0, -1); + break; + case 'l': + opts.namespace = 1; + add_mount(log, 0, -1); + break; + } + } + + /* no param found */ + if (argc - optind < 1) { + usage(); + return EXIT_FAILURE; + } + if (!(opts.namespace||opts.capabilities||opts.seccomp)) { + ERROR("Not using namespaces, capabilities or seccomp !!!\n\n"); + usage(); + return EXIT_FAILURE; + } + DEBUG("Using namespaces(%d), capabilities(%d), seccomp(%d)\n", + opts.namespace, + opts.capabilities != 0, + opts.seccomp != 0); + + opts.jail_argv = &argv[optind]; + + if (opts.namespace && add_path_and_deps(*opts.jail_argv, 1, -1, 0)) { + ERROR("failed to load dependencies\n"); + return -1; + } + + if (opts.namespace && opts.seccomp && add_path_and_deps("libpreload-seccomp.so", 1, -1, 1)) { + ERROR("failed to load libpreload-seccomp.so\n"); + return -1; + } + + if (opts.name) + prctl(PR_SET_NAME, opts.name, NULL, NULL, NULL); + + uloop_init(); + if (opts.namespace) { + int flags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | SIGCHLD; + if (opts.hostname) + flags |= CLONE_NEWUTS; + jail_process.pid = clone(exec_jail, child_stack + STACK_SIZE, flags, NULL); + } else { + jail_process.pid = fork(); + } + + if (jail_process.pid > 0) { + /* parent process */ + uloop_process_add(&jail_process); + uloop_run(); + uloop_done(); + if (jail_running) { + DEBUG("uloop interrupted, killing jail process\n"); + kill(jail_process.pid, SIGTERM); + waitpid(jail_process.pid, NULL, 0); + } + return jail_return_code; + } else if (jail_process.pid == 0) { + /* fork child process */ + return exec_jail(NULL); + } else { + ERROR("failed to clone/fork: %s\n", strerror(errno)); + return EXIT_FAILURE; + } +} diff --git a/src/3P/procd/jail/jail.h b/src/3P/procd/jail/jail.h new file mode 100644 index 00000000..5739d3d2 --- /dev/null +++ b/src/3P/procd/jail/jail.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2015 Etienne Champetier + * + * 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 _JAIL_JAIL_H_ +#define _JAIL_JAIL_H_ + +int mount_bind(const char *root, const char *path, int readonly, int error); + +#endif diff --git a/src/3P/procd/jail/log.h b/src/3P/procd/jail/log.h new file mode 100644 index 00000000..74a9f11f --- /dev/null +++ b/src/3P/procd/jail/log.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2015 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 _JAIL_LOG_H_ +#define _JAIL_LOG_H_ + +extern int debug; +#include +#include + +#define INFO(fmt, ...) do { \ + printf("jail: "fmt, ## __VA_ARGS__); \ + } while (0) +#define ERROR(fmt, ...) do { \ + syslog(LOG_ERR, "jail: "fmt, ## __VA_ARGS__); \ + fprintf(stderr,"jail: "fmt, ## __VA_ARGS__); \ + } while (0) +#define DEBUG(fmt, ...) do { \ + if (debug) printf("jail: "fmt, ## __VA_ARGS__); \ + } while (0) + +#endif diff --git a/src/3P/procd/jail/preload.c b/src/3P/procd/jail/preload.c new file mode 100644 index 00000000..5466f27d --- /dev/null +++ b/src/3P/procd/jail/preload.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015 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. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include "seccomp.h" +#include "../preload.h" + +static main_t __main__; + +static int __preload_main__(int argc, char **argv, char **envp) +{ + char *env_file = getenv("SECCOMP_FILE"); + + if (install_syscall_filter(*argv, env_file)) + return -1; + + unsetenv("LD_PRELOAD"); + unsetenv("SECCOMP_FILE"); + + return (*__main__)(argc, argv, envp); +} + +int __libc_start_main(main_t main, + int argc, + char **argv, + ElfW(auxv_t) *auxvec, + __typeof (main) init, + void (*fini) (void), + void (*rtld_fini) (void), + void *stack_end) +{ + start_main_t __start_main__; + + __start_main__ = dlsym(RTLD_NEXT, "__libc_start_main"); + if (!__start_main__) + INFO("failed to find __libc_start_main %s\n", dlerror()); + + __main__ = main; + + return (*__start_main__)(__preload_main__, argc, argv, auxvec, + init, fini, rtld_fini, stack_end); +} + +void __uClibc_main(main_t main, + int argc, + char **argv, + void (*app_init)(void), + void (*app_fini)(void), + void (*rtld_fini)(void), + void *stack_end attribute_unused) +{ + uClibc_main __start_main__; + + __start_main__ = dlsym(RTLD_NEXT, "__uClibc_main"); + if (!__start_main__) + INFO("failed to find __uClibc_main %s\n", dlerror()); + + __main__ = main; + + return (*__start_main__)(__preload_main__, argc, argv, + app_init, app_fini, rtld_fini, stack_end); +} diff --git a/src/3P/procd/jail/seccomp-bpf.h b/src/3P/procd/jail/seccomp-bpf.h new file mode 100644 index 00000000..82c06691 --- /dev/null +++ b/src/3P/procd/jail/seccomp-bpf.h @@ -0,0 +1,89 @@ +/* + * seccomp example for x86 (32-bit and 64-bit) with BPF macros + * + * Copyright (c) 2012 The Chromium OS Authors + * Authors: + * Will Drewry + * Kees Cook + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef _SECCOMP_BPF_H_ +#define _SECCOMP_BPF_H_ + +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#ifndef PR_SET_NO_NEW_PRIVS +# define PR_SET_NO_NEW_PRIVS 38 +#endif + +#include +#include +#include + +#ifdef HAVE_LINUX_SECCOMP_H +# include +#endif + +#ifndef SECCOMP_MODE_FILTER +#define SECCOMP_MODE_FILTER 2 /* uses user-supplied filter. */ +#define SECCOMP_RET_KILL 0x00000000U /* kill the task immediately */ +#define SECCOMP_RET_TRAP 0x00030000U /* disallow and force a SIGSYS */ +#define SECCOMP_RET_ERRNO 0x00050000U /* returns an errno */ +#define SECCOMP_RET_LOG 0x00070000U +#define SECCOMP_RET_ALLOW 0x7fff0000U /* allow */ +#define SECCOMP_RET_ERROR(x) (SECCOMP_RET_ERRNO | ((x) & 0x0000ffffU)) +#define SECCOMP_RET_LOGGER(x) (SECCOMP_RET_LOG | ((x) & 0x0000ffffU)) + +struct seccomp_data { + int nr; + __u32 arch; + __u64 instruction_pointer; + __u64 args[6]; +}; +#endif + +#ifndef SYS_SECCOMP +# define SYS_SECCOMP 1 +#endif + +#define syscall_nr (offsetof(struct seccomp_data, nr)) +#define arch_nr (offsetof(struct seccomp_data, arch)) + +#if defined(__i386__) +# define REG_SYSCALL REG_EAX +# define ARCH_NR AUDIT_ARCH_I386 +#elif defined(__x86_64__) +# define REG_SYSCALL REG_RAX +# define ARCH_NR AUDIT_ARCH_X86_64 +#elif defined(__mips__) +# define REG_SYSCALL regs[2] +# if __BYTE_ORDER == __LITTLE_ENDIAN +# define ARCH_NR AUDIT_ARCH_MIPSEL +# else +# define ARCH_NR AUDIT_ARCH_MIPS +# endif +#elif defined(__arm__) && (defined(__ARM_EABI__) || defined(__thumb__)) +# define REG_SYSCALL regs.uregs[7] +# if __BYTE_ORDER == __LITTLE_ENDIAN +# define ARCH_NR AUDIT_ARCH_ARM +# else +# define ARCH_NR AUDIT_ARCH_ARMEB +# endif +#else +# warning "Platform does not support seccomp filter yet" +# define REG_SYSCALL 0 +# define ARCH_NR 0 +#endif + +#endif /* _SECCOMP_BPF_H_ */ diff --git a/src/3P/procd/jail/seccomp.c b/src/3P/procd/jail/seccomp.c new file mode 100644 index 00000000..dcd19ecd --- /dev/null +++ b/src/3P/procd/jail/seccomp.c @@ -0,0 +1,140 @@ +/* + * seccomp example with syscall reporting + * + * Copyright (c) 2012 The Chromium OS Authors + * Authors: + * Kees Cook + * Will Drewry + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#define _GNU_SOURCE 1 +#include +#include +#include + +#include +#include +#include + +#include "seccomp-bpf.h" +#include "seccomp.h" +#include "../syscall-names.h" + +static int max_syscall = ARRAY_SIZE(syscall_names); + +static int find_syscall(const char *name) +{ + int i; + + for (i = 0; i < max_syscall; i++) + if (syscall_names[i] && !strcmp(syscall_names[i], name)) + return i; + + return -1; +} + +static void set_filter(struct sock_filter *filter, __u16 code, __u8 jt, __u8 jf, __u32 k) +{ + filter->code = code; + filter->jt = jt; + filter->jf = jf; + filter->k = k; +} + +int install_syscall_filter(const char *argv, const char *file) +{ + enum { + SECCOMP_WHITELIST, + SECCOMP_POLICY, + __SECCOMP_MAX + }; + static const struct blobmsg_policy policy[__SECCOMP_MAX] = { + [SECCOMP_WHITELIST] = { .name = "whitelist", .type = BLOBMSG_TYPE_ARRAY }, + [SECCOMP_POLICY] = { .name = "policy", .type = BLOBMSG_TYPE_INT32 }, + }; + struct blob_buf b = { 0 }; + struct blob_attr *tb[__SECCOMP_MAX]; + struct blob_attr *cur; + int rem; + + struct sock_filter *filter; + struct sock_fprog prog = { 0 }; + int sz = 5, idx = 0, default_policy = 0; + + INFO("%s: setting up syscall filter\n", argv); + + blob_buf_init(&b, 0); + if (!blobmsg_add_json_from_file(&b, file)) { + INFO("%s: failed to load %s\n", argv, file); + return -1; + } + + blobmsg_parse(policy, __SECCOMP_MAX, tb, blob_data(b.head), blob_len(b.head)); + if (!tb[SECCOMP_WHITELIST]) { + INFO("%s: %s is missing the syscall table\n", argv, file); + return -1; + } + + if (tb[SECCOMP_POLICY]) + default_policy = blobmsg_get_u32(tb[SECCOMP_POLICY]); + + blobmsg_for_each_attr(cur, tb[SECCOMP_WHITELIST], rem) + sz += 2; + + filter = calloc(sz, sizeof(struct sock_filter)); + if (!filter) { + INFO("failed to allocate filter memory\n"); + return -1; + } + + /* validate arch */ + set_filter(&filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, arch_nr); + set_filter(&filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 1, 0, ARCH_NR); + set_filter(&filter[idx++], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_KILL); + + /* get syscall */ + set_filter(&filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); + + blobmsg_for_each_attr(cur, tb[SECCOMP_WHITELIST], rem) { + char *name = blobmsg_get_string(cur); + int nr; + + if (!name) { + INFO("%s: invalid syscall name\n", argv); + continue; + } + + nr = find_syscall(name); + if (nr == -1) { + INFO("%s: unknown syscall %s\n", argv, name); + continue; + } + + /* add whitelist */ + set_filter(&filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 1, nr); + set_filter(&filter[idx++], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_ALLOW); + } + + if (default_policy) + /* return -1 and set errno */ + set_filter(&filter[idx], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_LOGGER(default_policy)); + else + /* kill the process */ + set_filter(&filter[idx], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_KILL); + + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { + INFO("%s: prctl(PR_SET_NO_NEW_PRIVS) failed: %s\n", argv, strerror(errno)); + return errno; + } + + prog.len = (unsigned short) idx + 1; + prog.filter = filter; + + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) { + INFO("%s: prctl(PR_SET_SECCOMP) failed: %s\n", argv, strerror(errno)); + return errno; + } + return 0; +} diff --git a/src/3P/procd/jail/seccomp.h b/src/3P/procd/jail/seccomp.h new file mode 100644 index 00000000..60328129 --- /dev/null +++ b/src/3P/procd/jail/seccomp.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 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 _JAIL_SECCOMP_H_ +#define _JAIL_SECCOMP_H_ + +#include +#include + +#define INFO(fmt, ...) do { \ + syslog(LOG_INFO,"preload-seccomp: "fmt, ## __VA_ARGS__); \ + fprintf(stderr,"preload-seccomp: "fmt, ## __VA_ARGS__); \ + } while (0) + +int install_syscall_filter(const char *argv, const char *file); + +#endif diff --git a/src/3P/procd/libc-compat.h b/src/3P/procd/libc-compat.h new file mode 100644 index 00000000..94353465 --- /dev/null +++ b/src/3P/procd/libc-compat.h @@ -0,0 +1,10 @@ +#ifndef __PROCD_LIBC_COMPAT_H +#define __PROCD_LIBC_COMPAT_H + +#if defined(__GLIBC__) && !defined(__UCLIBC__) +static inline int ignore(int x) {return x;} +#else +#define ignore(x) x +#endif + +#endif diff --git a/src/3P/procd/log.h b/src/3P/procd/log.h new file mode 100644 index 00000000..bf86f1ef --- /dev/null +++ b/src/3P/procd/log.h @@ -0,0 +1,30 @@ +/* + * 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 __LOG_H +#define __LOG_H + +#include + +#define DEBUG(level, fmt, ...) do { \ + if (debug >= level) { \ + ulog(LOG_DEBUG, fmt, ## __VA_ARGS__); \ + } } while (0) + +#define LOG ULOG_INFO +#define ERROR ULOG_ERR + +extern unsigned int debug; + +#endif diff --git a/src/3P/procd/make_capabilities_h.sh b/src/3P/procd/make_capabilities_h.sh new file mode 100755 index 00000000..635e7409 --- /dev/null +++ b/src/3P/procd/make_capabilities_h.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +CC=$1 +[ -n "$TARGET_CC_NOCACHE" ] && CC=$TARGET_CC_NOCACHE + +echo "#include " +echo "static const char *capabilities_names[] = {" +echo "#include " | ${CC} -E -dM - | grep '#define CAP' | grep -vE '(CAP_TO|CAP_LAST_CAP)' | \ + awk '{print $3" "$2}' | sort -n | awk '{print " ["$1"]\t= \""tolower($2)"\","}' +echo "};" diff --git a/src/3P/procd/make_syscall_h.sh b/src/3P/procd/make_syscall_h.sh new file mode 100755 index 00000000..3363bc70 --- /dev/null +++ b/src/3P/procd/make_syscall_h.sh @@ -0,0 +1,18 @@ +#!/bin/sh +# syscall reporting example for seccomp +# +# Copyright (c) 2012 The Chromium OS Authors +# Authors: +# Kees Cook +# +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +CC=$1 +[ -n "$TARGET_CC_NOCACHE" ] && CC=$TARGET_CC_NOCACHE + +echo "#include " +echo "static const char *syscall_names[] = {" +echo "#include " | ${CC} -E -dM - | grep '^#define __NR_' | \ + LC_ALL=C sed -r -n -e 's/^\#define[ \t]+__NR_([a-z0-9_]+)[ \t]+([ ()+0-9a-zNR_Linux]+)(.*)/ [\2] = "\1",/p' +echo "};" diff --git a/src/3P/procd/plug/coldplug.c b/src/3P/procd/plug/coldplug.c new file mode 100644 index 00000000..5fcb9a33 --- /dev/null +++ b/src/3P/procd/plug/coldplug.c @@ -0,0 +1,69 @@ +/* + * 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 "../libc-compat.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 }; + unsigned int oldumask = umask(0); + + umount2("/dev/pts", MNT_DETACH); + umount2("/dev/", MNT_DETACH); + mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755,size=512K"); + ignore(symlink("/tmp/shm", "/dev/shm")); + mkdir("/dev/pts", 0755); + umask(oldumask); + mount("devpts", "/dev/pts", "devpts", MS_NOEXEC | MS_NOSUID, 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/src/3P/procd/plug/hotplug.c b/src/3P/procd/plug/hotplug.c new file mode 100644 index 00000000..85959155 --- /dev/null +++ b/src/3P/procd/plug/hotplug.c @@ -0,0 +1,622 @@ +/* + * 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 +#include + +#include "../procd.h" + +#include "hotplug.h" + +#define HOTPLUG_WAIT 500 + +struct cmd_handler; +struct cmd_queue { + struct list_head list; + + struct blob_attr *msg; + struct blob_attr *data; + int timeout; + + void (*handler)(struct blob_attr *msg, struct blob_attr *data); + void (*start)(struct blob_attr *msg, struct blob_attr *data); + void (*complete)(struct blob_attr *msg, struct blob_attr *data, int ret); +}; + +struct button_timeout { + struct list_head list; + struct uloop_timeout timeout; + char *name; + int seen; + struct blob_attr *data; +}; + +static LIST_HEAD(cmd_queue); +static LIST_HEAD(button_timer); +static struct uloop_process queue_proc; +static struct uloop_timeout last_event; +static struct blob_buf b, button_buf; +static char *rule_file; +static struct blob_buf script; +static struct cmd_queue *current; + +static void queue_add(struct cmd_handler *h, struct blob_attr *msg, struct blob_attr *data); +static void handle_button_complete(struct blob_attr *msg, struct blob_attr *data, int ret); + +static void button_free(struct button_timeout *b) +{ + uloop_timeout_cancel(&b->timeout); + list_del(&b->list); + free(b->data); + free(b->name); + free(b); +} + +static void button_timeout_remove(char *button) +{ + struct button_timeout *b, *c; + + if (!list_empty(&button_timer)) list_for_each_entry_safe(b, c, &button_timer, list) { + if (!strcmp(b->name, button)) + button_free(b); + } +} + +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[3] = { + { .type = BLOBMSG_TYPE_STRING }, + { .type = BLOBMSG_TYPE_STRING }, + { .type = BLOBMSG_TYPE_STRING }, + }; + struct blob_attr *tb[3]; + 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, 3, 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))); + if (tb[2]) { + struct group *g = getgrnam(blobmsg_get_string(tb[2])); + + if (g) + chown(blobmsg_get_string(tb[0]), 0, g->gr_gid); + else + ERROR("cannot set group %s for %s\n", + blobmsg_get_string(tb[2]), + blobmsg_get_string(tb[0])); + } + } + 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_button_start(struct blob_attr *msg, struct blob_attr *data) +{ + char *button = hotplug_msg_find_var(msg, "BUTTON"); + + if (button) + button_timeout_remove(button); +} + +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"); + struct stat s = { 0 }; + char *path, loadpath[256], syspath[256]; + int fw, src, load, len; + static char buf[4096]; + + 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 = alloca(strlen(dir) + strlen(file) + 2); + sprintf(path, "%s/%s", dir, file); + + if (stat(path, &s)) { + ERROR("Could not find firmware %s\n", path); + src = -1; + s.st_size = 0; + goto send_to_kernel; + } + + src = open(path, O_RDONLY); + if (src < 0) { + ERROR("Failed to open %s\n", path); + s.st_size = 0; + goto send_to_kernel; + } + +send_to_kernel: + snprintf(loadpath, sizeof(loadpath), "/sys/%s/loading", dev); + load = open(loadpath, O_WRONLY); + if (!load) { + ERROR("Failed to open %s\n", loadpath); + exit(-1); + } + if (write(load, "1", 1) == -1) { + ERROR("Failed to write to %s\n", loadpath); + exit(-1); + } + close(load); + + snprintf(syspath, sizeof(syspath), "/sys/%s/data", dev); + fw = open(syspath, O_WRONLY); + if (fw < 0) { + ERROR("Failed to open %s\n", syspath); + exit(-1); + } + + len = s.st_size; + while (len) { + len = read(src, buf, sizeof(buf)); + if (len <= 0) + break; + + if (write(fw, buf, len) == -1) { + ERROR("failed to write firmware file %s/%s to %s\n", dir, file, dev); + break; + } + } + + if (src >= 0) + close(src); + close(fw); + + load = open(loadpath, O_WRONLY); + if (write(load, "0", 1) == -1) + ERROR("failed to write to %s\n", loadpath); + close(load); + + DEBUG(2, "Done loading %s\n", path); + + exit(-1); +} + +enum { + HANDLER_MKDEV = 0, + HANDLER_RM, + HANDLER_EXEC, + HANDLER_BUTTON, + HANDLER_FW, +}; + +static struct cmd_handler { + char *name; + int atomic; + void (*handler)(struct blob_attr *msg, struct blob_attr *data); + void (*start)(struct blob_attr *msg, struct blob_attr *data); + void (*complete)(struct blob_attr *msg, struct blob_attr *data, int ret); +} handlers[] = { + [HANDLER_MKDEV] = { + .name = "makedev", + .atomic = 1, + .handler = handle_makedev, + }, + [HANDLER_RM] = { + .name = "rm", + .atomic = 1, + .handler = handle_rm, + }, + [HANDLER_EXEC] = { + .name = "exec", + .handler = handle_exec, + }, + [HANDLER_BUTTON] = { + .name = "button", + .handler = handle_exec, + .start = handle_button_start, + .complete = handle_button_complete, + }, + [HANDLER_FW] = { + .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); + } + if (c->start) + c->start(c->msg, c->data); + list_del(&c->list); + if (c->complete) + current = c; + else + 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); + + if (current) { + current->complete(current->msg, current->data, ret); + free(current); + current = NULL; + } + 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; + c->complete = h->complete; + c->start = h->start; + list_add_tail(&c->list, &cmd_queue); + queue_next(); +} + +static void handle_button_timeout(struct uloop_timeout *t) +{ + struct button_timeout *b; + char seen[16]; + + b = container_of(t, struct button_timeout, timeout); + blob_buf_init(&button_buf, 0); + blobmsg_add_string(&button_buf, "BUTTON", b->name); + blobmsg_add_string(&button_buf, "ACTION", "timeout"); + snprintf(seen, sizeof(seen), "%d", b->seen); + blobmsg_add_string(&button_buf, "SEEN", seen); + queue_add(&handlers[HANDLER_EXEC], button_buf.head, b->data); + button_free(b); +} + +static void handle_button_complete(struct blob_attr *msg, struct blob_attr *data, int ret) +{ + char *name = hotplug_msg_find_var(msg, "BUTTON"); + struct button_timeout *b; + int timeout = ret >> 8; + + if (!timeout) + return; + + b = malloc(sizeof(*b)); + if (!b || !name) + return; + + memset(b, 0, sizeof(*b)); + + b->data = malloc(blob_pad_len(data)); + b->name = strdup(name); + b->seen = timeout; + + memcpy(b->data, data, blob_pad_len(data)); + b->timeout.cb = handle_button_timeout; + + uloop_timeout_set(&b->timeout, timeout * 1000); + list_add(&b->list, &button_timer); +} + +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 (!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_debug(struct blob_attr *data) +{ + char *str; + + if (debug < 3) + return; + + str = blobmsg_format_json(data, true); + DEBUG(3, "%s\n", str); + free(str); +} + +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); + hotplug_handler_debug(b.head); + 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; + int nlbufsize = 512 * 1024; + + 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); + } + + if (setsockopt(hotplug_fd.fd, SOL_SOCKET, SO_RCVBUFFORCE, &nlbufsize, sizeof(nlbufsize))) + ERROR("Failed to resize receive buffer: %s\n", strerror(errno)); + + 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/src/3P/procd/plug/hotplug.h b/src/3P/procd/plug/hotplug.h new file mode 100644 index 00000000..9e469d18 --- /dev/null +++ b/src/3P/procd/plug/hotplug.h @@ -0,0 +1,49 @@ +/* + * 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 + +#ifndef DISABLE_INIT +void hotplug(char *rules); +int hotplug_run(char *rules); +void hotplug_shutdown(void); +void hotplug_last_event(uloop_timeout_handler handler); +void procd_coldplug(void); +#else +static inline void hotplug(char *rules) +{ +} + +static inline int hotplug_run(char *rules) +{ + return 0; +} + +static inline void hotplug_shutdown(void) +{ +} + +static inline void hotplug_last_event(uloop_timeout_handler handler) +{ +} + +static inline void procd_coldplug(void) +{ +} +#endif + +#endif diff --git a/src/3P/procd/plug/udevtrigger.c b/src/3P/procd/plug/udevtrigger.c new file mode 100644 index 00000000..3eba19a6 --- /dev/null +++ b/src/3P/procd/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) - 1); + 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; +} diff --git a/src/3P/procd/preload.h b/src/3P/procd/preload.h new file mode 100644 index 00000000..5e663ace --- /dev/null +++ b/src/3P/procd/preload.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 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 + +#ifndef __unbounded +#define __unbounded +#endif + +#ifndef attribute_unused +#define attribute_unused __attribute__ ((unused)) +#endif +typedef int (*main_t)(int, char **, char **); + +typedef int (*start_main_t)(main_t main, int, char *__unbounded *__unbounded, + ElfW(auxv_t) *, + __typeof (main), + void (*fini) (void), + void (*rtld_fini) (void), + void *__unbounded stack_end); + +int __libc_start_main(main_t main, + int argc, + char **argv, + ElfW(auxv_t) *auxvec, + __typeof (main) init, + void (*fini) (void), + void (*rtld_fini) (void), + void *stack_end); + + +typedef void (*uClibc_main)(main_t main, + int argc, + char **argv, + void (*app_init)(void), + void (*app_fini)(void), + void (*rtld_fini)(void), + void *stack_end attribute_unused); + +void __uClibc_main(main_t main, + int argc, + char **argv, + void (*app_init)(void), + void (*app_fini)(void), + void (*rtld_fini)(void), + void *stack_end attribute_unused); diff --git a/src/3P/procd/procd.c b/src/3P/procd/procd.c new file mode 100644 index 00000000..dd4909ea --- /dev/null +++ b/src/3P/procd/procd.c @@ -0,0 +1,84 @@ +/* + * 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 "procd.h" +#include "watchdog.h" +#include "plug/hotplug.h" + +unsigned int debug; + +static int usage(const char *prog) +{ + fprintf(stderr, "Usage: %s [options]\n" + "Options:\n" + " -s Path to ubus socket\n" + " -h run as hotplug daemon\n" + " -d Enable debug messages\n" + " -S Print messages to stdout\n" + "\n", prog); + return 1; +} + +int main(int argc, char **argv) +{ + int ch; + char *dbglvl = getenv("DBGLVL"); + int ulog_channels = ULOG_KMSG; + + if (dbglvl) { + debug = atoi(dbglvl); + unsetenv("DBGLVL"); + } + + while ((ch = getopt(argc, argv, "d:s:h:S")) != -1) { + switch (ch) { + case 'h': + return hotplug_run(optarg); + case 's': + ubus_socket = optarg; + break; + case 'd': + debug = atoi(optarg); + break; + case 'S': + ulog_channels = ULOG_STDIO; + break; + default: + return usage(argv[0]); + } + } + + ulog_open(ulog_channels, LOG_DAEMON, "procd"); + + setsid(); + uloop_init(); + procd_signal(); + if (getpid() != 1) + procd_connect_ubus(); + else + procd_state_next(); + uloop_run(); + uloop_done(); + + return 0; +} diff --git a/src/3P/procd/procd.h b/src/3P/procd/procd.h new file mode 100644 index 00000000..6ec46d3a --- /dev/null +++ b/src/3P/procd/procd.h @@ -0,0 +1,57 @@ +/* + * 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_H +#define __PROCD_H + +#include +#include +#include + +#include +#include + +#include "log.h" + +#define __init __attribute__((constructor)) + +extern char *ubus_socket; +extern int upgrade_running; + +void procd_connect_ubus(void); +void procd_reconnect_ubus(int reconnect); +void ubus_init_service(struct ubus_context *ctx); +void ubus_init_system(struct ubus_context *ctx); + +void procd_state_next(void); +void procd_state_ubus_connect(void); +void procd_shutdown(int event); +void procd_early(void); +void procd_preinit(void); +void procd_signal(void); +void procd_signal_preinit(void); +void procd_inittab(void); +void procd_inittab_run(const char *action); +void procd_bcast_event(char *event, struct blob_attr *msg); + +struct trigger; +void trigger_event(const char *type, struct blob_attr *data); +void trigger_add(struct blob_attr *rule, void *id); +void trigger_del(void *id); + +void watch_add(const char *_name, void *id); +void watch_del(void *id); +void watch_ubus(struct ubus_context *ctx); + +#endif diff --git a/src/3P/procd/rcS.c b/src/3P/procd/rcS.c new file mode 100644 index 00000000..1e38d39e --- /dev/null +++ b/src/3P/procd/rcS.c @@ -0,0 +1,182 @@ +/* + * runqueue-example.c + * + * Copyright (C) 2013 Felix Fietkau + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "procd.h" +#include "rcS.h" + +static struct runqueue q, r; + +struct initd { + struct ustream_fd fd; + struct runqueue_process proc; + char *file; + char *param; +}; + +static void pipe_cb(struct ustream *s, int bytes) +{ + char *newline, *str; + int len; + + do { + str = ustream_get_read_buf(s, NULL); + if (!str) + break; + newline = strchr(str, '\n'); + if (!newline) + break; + *newline = 0; + len = newline + 1 - str; + syslog(LOG_NOTICE, "%s", str); +#ifdef SHOW_BOOT_ON_CONSOLE + fprintf(stderr, "%s\n", str); +#endif + ustream_consume(s, len); + } while (1); +} + +static void q_initd_run(struct runqueue *q, struct runqueue_task *t) +{ + struct initd *s = container_of(t, struct initd, proc.task); + int pipefd[2]; + pid_t pid; + + DEBUG(2, "start %s %s \n", s->file, s->param); + if (pipe(pipefd) == -1) { + ERROR("Failed to create pipe\n"); + return; + } + + pid = fork(); + if (pid < 0) + return; + + if (pid) { + close(pipefd[1]); + s->fd.stream.string_data = true, + s->fd.stream.notify_read = pipe_cb, + runqueue_process_add(q, &s->proc, pid); + ustream_fd_init(&s->fd, pipefd[0]); + return; + } + close(pipefd[0]); + dup2(pipefd[1], STDOUT_FILENO); + dup2(pipefd[1], STDERR_FILENO); + + execlp(s->file, s->file, s->param, NULL); + exit(1); +} + +static void q_initd_complete(struct runqueue *q, struct runqueue_task *p) +{ + struct initd *s = container_of(p, struct initd, proc.task); + + DEBUG(2, "stop %s %s \n", s->file, s->param); + ustream_free(&s->fd.stream); + close(s->fd.fd.fd); + free(s); +} + +static void add_initd(struct runqueue *q, char *file, char *param) +{ + static const struct runqueue_task_type initd_type = { + .run = q_initd_run, + .cancel = runqueue_process_cancel_cb, + .kill = runqueue_process_kill_cb, + }; + struct initd *s; + char *p, *f; + + s = calloc_a(sizeof(*s), &f, strlen(file) + 1, &p, strlen(param) + 1); + if (!s) { + ERROR("Out of memory in %s.\n", file); + return; + } + s->proc.task.type = &initd_type; + s->proc.task.complete = q_initd_complete; + if (!strcmp(param, "stop") || !strcmp(param, "shutdown")) + s->proc.task.run_timeout = 15000; + s->param = p; + s->file = f; + strcpy(s->param, param); + strcpy(s->file, file); + runqueue_task_add(q, &s->proc.task, false); +} + +static int _rc(struct runqueue *q, char *path, const char *file, char *pattern, char *param) +{ + char *dir = alloca(2 + strlen(path) + strlen(file) + strlen(pattern)); + glob_t gl; + int j; + + if (!dir) { + ERROR("Out of memory in %s.\n", file); + return -1; + } + + DEBUG(2, "running %s/%s%s %s\n", path, file, pattern, param); + sprintf(dir, "%s/%s%s", path, file, pattern); + if (glob(dir, GLOB_NOESCAPE | GLOB_MARK, NULL, &gl)) { + DEBUG(2, "glob failed on %s\n", dir); + return -1; + } + + for (j = 0; j < gl.gl_pathc; j++) + add_initd(q, gl.gl_pathv[j], param); + + globfree(&gl); + + return 0; +} + +int rcS(char *pattern, char *param, void (*q_empty)(struct runqueue *)) +{ + runqueue_init(&q); + q.empty_cb = q_empty; + q.max_running_tasks = 1; + + return _rc(&q, "/etc/rc.d", pattern, "*", param); +} + +int rc(const char *file, char *param) +{ + return _rc(&r, "/etc/init.d", file, "", param); +} + +static void r_empty(struct runqueue *q) +{ + +} + +static void __attribute__((constructor)) rc_init() { + runqueue_init(&r); + r.empty_cb = r_empty; + r.max_running_tasks = 8; +} diff --git a/src/3P/procd/rcS.h b/src/3P/procd/rcS.h new file mode 100644 index 00000000..91d37d1d --- /dev/null +++ b/src/3P/procd/rcS.h @@ -0,0 +1,23 @@ +/* + * 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_RCS_H +#define __PROCD_RCS_H + +#include + +extern int rcS(char *pattern, char *param, void (*q_empty)(struct runqueue *)); +extern int rc(const char *file, char *param); + +#endif diff --git a/src/3P/procd/service/instance.c b/src/3P/procd/service/instance.c new file mode 100644 index 00000000..ef4aac63 --- /dev/null +++ b/src/3P/procd/service/instance.c @@ -0,0 +1,1061 @@ +/* + * 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. + */ + +//AWOX #define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../procd.h" + +#include "service.h" +#include "instance.h" + + +enum { + INSTANCE_ATTR_COMMAND, + INSTANCE_ATTR_ENV, + INSTANCE_ATTR_DATA, + INSTANCE_ATTR_NETDEV, + INSTANCE_ATTR_FILE, + INSTANCE_ATTR_TRIGGER, + INSTANCE_ATTR_RESPAWN, + INSTANCE_ATTR_NICE, + INSTANCE_ATTR_LIMITS, + INSTANCE_ATTR_WATCH, + INSTANCE_ATTR_ERROR, + INSTANCE_ATTR_USER, + INSTANCE_ATTR_STDOUT, + INSTANCE_ATTR_STDERR, + INSTANCE_ATTR_NO_NEW_PRIVS, + INSTANCE_ATTR_JAIL, + INSTANCE_ATTR_TRACE, + INSTANCE_ATTR_SECCOMP, + INSTANCE_ATTR_PIDFILE, + __INSTANCE_ATTR_MAX +}; + +static const struct blobmsg_policy instance_attr[__INSTANCE_ATTR_MAX] = { + [INSTANCE_ATTR_COMMAND] = { "command", BLOBMSG_TYPE_ARRAY }, + [INSTANCE_ATTR_ENV] = { "env", BLOBMSG_TYPE_TABLE }, + [INSTANCE_ATTR_DATA] = { "data", BLOBMSG_TYPE_TABLE }, + [INSTANCE_ATTR_NETDEV] = { "netdev", BLOBMSG_TYPE_ARRAY }, + [INSTANCE_ATTR_FILE] = { "file", BLOBMSG_TYPE_ARRAY }, + [INSTANCE_ATTR_TRIGGER] = { "triggers", BLOBMSG_TYPE_ARRAY }, + [INSTANCE_ATTR_RESPAWN] = { "respawn", BLOBMSG_TYPE_ARRAY }, + [INSTANCE_ATTR_NICE] = { "nice", BLOBMSG_TYPE_INT32 }, + [INSTANCE_ATTR_LIMITS] = { "limits", BLOBMSG_TYPE_TABLE }, + [INSTANCE_ATTR_WATCH] = { "watch", BLOBMSG_TYPE_ARRAY }, + [INSTANCE_ATTR_ERROR] = { "error", BLOBMSG_TYPE_ARRAY }, + [INSTANCE_ATTR_USER] = { "user", BLOBMSG_TYPE_STRING }, + [INSTANCE_ATTR_STDOUT] = { "stdout", BLOBMSG_TYPE_BOOL }, + [INSTANCE_ATTR_STDERR] = { "stderr", BLOBMSG_TYPE_BOOL }, + [INSTANCE_ATTR_NO_NEW_PRIVS] = { "no_new_privs", BLOBMSG_TYPE_BOOL }, + [INSTANCE_ATTR_JAIL] = { "jail", BLOBMSG_TYPE_TABLE }, + [INSTANCE_ATTR_TRACE] = { "trace", BLOBMSG_TYPE_BOOL }, + [INSTANCE_ATTR_SECCOMP] = { "seccomp", BLOBMSG_TYPE_STRING }, + [INSTANCE_ATTR_PIDFILE] = { "pidfile", BLOBMSG_TYPE_STRING }, +}; + +enum { + JAIL_ATTR_NAME, + JAIL_ATTR_HOSTNAME, + JAIL_ATTR_PROCFS, + JAIL_ATTR_SYSFS, + JAIL_ATTR_UBUS, + JAIL_ATTR_LOG, + JAIL_ATTR_RONLY, + JAIL_ATTR_MOUNT, + __JAIL_ATTR_MAX, +}; + +static const struct blobmsg_policy jail_attr[__JAIL_ATTR_MAX] = { + [JAIL_ATTR_NAME] = { "name", BLOBMSG_TYPE_STRING }, + [JAIL_ATTR_HOSTNAME] = { "hostname", BLOBMSG_TYPE_STRING }, + [JAIL_ATTR_PROCFS] = { "procfs", BLOBMSG_TYPE_BOOL }, + [JAIL_ATTR_SYSFS] = { "sysfs", BLOBMSG_TYPE_BOOL }, + [JAIL_ATTR_UBUS] = { "ubus", BLOBMSG_TYPE_BOOL }, + [JAIL_ATTR_LOG] = { "log", BLOBMSG_TYPE_BOOL }, + [JAIL_ATTR_RONLY] = { "ronly", BLOBMSG_TYPE_BOOL }, + [JAIL_ATTR_MOUNT] = { "mount", BLOBMSG_TYPE_TABLE }, +}; + +struct instance_netdev { + struct blobmsg_list_node node; + int ifindex; +}; + +struct instance_file { + struct blobmsg_list_node node; + uint32_t md5[4]; +}; + +struct rlimit_name { + const char *name; + int resource; +}; + +static const struct rlimit_name rlimit_names[] = { + { "as", RLIMIT_AS }, + { "core", RLIMIT_CORE }, + { "cpu", RLIMIT_CPU }, + { "data", RLIMIT_DATA }, + { "fsize", RLIMIT_FSIZE }, + { "memlock", RLIMIT_MEMLOCK }, + { "nofile", RLIMIT_NOFILE }, + { "nproc", RLIMIT_NPROC }, + { "rss", RLIMIT_RSS }, + { "stack", RLIMIT_STACK }, +#ifdef linux + { "nice", RLIMIT_NICE }, + { "rtprio", RLIMIT_RTPRIO }, + { "msgqueue", RLIMIT_MSGQUEUE }, + { "sigpending", RLIMIT_SIGPENDING }, +#endif + { NULL, 0 } +}; + +static char trace[] = "/sbin/utrace"; + +static void closefd(int fd) +{ + if (fd > STDERR_FILENO) + close(fd); +} + +static void +instance_limits(const char *limit, const char *value) +{ + int i; + struct rlimit rlim; + unsigned long cur, max; + + for (i = 0; rlimit_names[i].name != NULL; i++) { + if (strcmp(rlimit_names[i].name, limit)) + continue; + if (!strcmp(value, "unlimited")) { + rlim.rlim_cur = RLIM_INFINITY; + rlim.rlim_max = RLIM_INFINITY; + } else { + if (getrlimit(rlimit_names[i].resource, &rlim)) + return; + + cur = rlim.rlim_cur; + max = rlim.rlim_max; + + if (sscanf(value, "%lu %lu", &cur, &max) < 1) + return; + + rlim.rlim_cur = cur; + rlim.rlim_max = max; + } + + setrlimit(rlimit_names[i].resource, &rlim); + return; + } +} + +static inline int +jail_run(struct service_instance *in, char **argv) +{ + struct blobmsg_list_node *var; + struct jail *jail = &in->jail; + int argc = 0; + + argv[argc++] = "/sbin/ujail"; + + if (jail->name) { + argv[argc++] = "-n"; + argv[argc++] = jail->name; + } + + if (jail->hostname) { + argv[argc++] = "-h"; + argv[argc++] = jail->hostname; + } + + if (in->seccomp) { + argv[argc++] = "-S"; + argv[argc++] = in->seccomp; + } + + if (in->no_new_privs) + argv[argc++] = "-c"; + + if (jail->procfs) + argv[argc++] = "-p"; + + if (jail->sysfs) + argv[argc++] = "-s"; + + if (jail->ubus) + argv[argc++] = "-u"; + + if (jail->log) + argv[argc++] = "-l"; + + if (jail->ronly) + argv[argc++] = "-o"; + + blobmsg_list_for_each(&jail->mount, var) { + const char *type = blobmsg_data(var->data); + + if (*type == '1') + argv[argc++] = "-w"; + else + argv[argc++] = "-r"; + argv[argc++] = (char *) blobmsg_name(var->data); + } + + argv[argc++] = "--"; + + return argc; +} + +static int +instance_removepid(struct service_instance *in) { + if (!in->pidfile) + return 0; + if (unlink(in->pidfile)) { + ERROR("Failed to removed pidfile: %s: %d - %s\n", + in->pidfile, errno, strerror(errno)); + return 1; + } + return 0; +} + +static int +instance_writepid(struct service_instance *in) +{ + FILE *_pidfile; + + if (!in->pidfile) { + return 0; + } + _pidfile = fopen(in->pidfile, "w"); + if (_pidfile == NULL) { + ERROR("failed to open pidfile for writing: %s: %d (%s)", + in->pidfile, errno, strerror(errno)); + return 1; + } + if (fprintf(_pidfile, "%d\n", in->proc.pid) < 0) { + ERROR("failed to write pidfile: %s: %d (%s)", + in->pidfile, errno, strerror(errno)); + return 2; + } + if (fclose(_pidfile)) { + ERROR("failed to close pidfile: %s: %d (%s)", + in->pidfile, errno, strerror(errno)); + return 3; + } + + return 0; +} + +static void +instance_run(struct service_instance *in, int _stdout, int _stderr) +{ + struct blobmsg_list_node *var; + struct blob_attr *cur; + char **argv; + char *ld_preload; + int argc = 1; /* NULL terminated */ + int rem, _stdin; + bool seccomp = !in->trace && !in->has_jail && in->seccomp; + bool setlbf = _stdout >= 0; + + if (in->nice) + setpriority(PRIO_PROCESS, 0, in->nice); + + blobmsg_for_each_attr(cur, in->command, rem) + argc++; + + blobmsg_list_for_each(&in->env, var) + setenv(blobmsg_name(var->data), blobmsg_data(var->data), 1); + + if (seccomp) + setenv("SECCOMP_FILE", in->seccomp, 1); + + if ((seccomp || setlbf) && asprintf(&ld_preload, "LD_PRELOAD=%s%s%s", + seccomp ? "/lib/libpreload-seccomp.so" : "", + seccomp && setlbf ? ":" : "", + setlbf ? "/lib/libsetlbf.so" : "") > 0) + putenv(ld_preload); + + blobmsg_list_for_each(&in->limits, var) + instance_limits(blobmsg_name(var->data), blobmsg_data(var->data)); + + if (in->trace) + argc += 1; + + argv = alloca(sizeof(char *) * (argc + in->jail.argc)); + argc = 0; + + if (in->trace) + argv[argc++] = trace; + + if (in->has_jail) + argc = jail_run(in, argv); + + blobmsg_for_each_attr(cur, in->command, rem) + argv[argc++] = blobmsg_data(cur); + + argv[argc] = NULL; + + _stdin = open("/dev/null", O_RDONLY); + + if (_stdout == -1) + _stdout = open("/dev/null", O_WRONLY); + + if (_stderr == -1) + _stderr = open("/dev/null", O_WRONLY); + + if (_stdin > -1) { + dup2(_stdin, STDIN_FILENO); + closefd(_stdin); + } + if (_stdout > -1) { + dup2(_stdout, STDOUT_FILENO); + closefd(_stdout); + } + if (_stderr > -1) { + dup2(_stderr, STDERR_FILENO); + closefd(_stderr); + } + + if (in->gid && setgid(in->gid)) { + ERROR("failed to set group id %d: %d (%s)\n", in->gid, errno, strerror(errno)); + exit(127); + } + if (in->uid && setuid(in->uid)) { + ERROR("failed to set user id %d: %d (%s)\n", in->uid, errno, strerror(errno)); + exit(127); + } + + execvp(argv[0], argv); + exit(127); +} + +static void +instance_free_stdio(struct service_instance *in) +{ + if (in->_stdout.fd.fd > -1) { + ustream_free(&in->_stdout.stream); + close(in->_stdout.fd.fd); + in->_stdout.fd.fd = -1; + } + + if (in->_stderr.fd.fd > -1) { + ustream_free(&in->_stderr.stream); + close(in->_stderr.fd.fd); + in->_stderr.fd.fd = -1; + } +} + +void +instance_start(struct service_instance *in) +{ + int pid; + int opipe[2] = { -1, -1 }; + int epipe[2] = { -1, -1 }; + + if (!avl_is_empty(&in->errors.avl)) { + LOG("Not starting instance %s::%s, an error was indicated\n", in->srv->name, in->name); + return; + } + + if (in->proc.pending) + return; + + instance_free_stdio(in); + if (in->_stdout.fd.fd > -2) { + if (pipe(opipe)) { + ULOG_WARN("pipe() failed: %d (%s)\n", errno, strerror(errno)); + opipe[0] = opipe[1] = -1; + } + } + + if (in->_stderr.fd.fd > -2) { + if (pipe(epipe)) { + ULOG_WARN("pipe() failed: %d (%s)\n", errno, strerror(errno)); + epipe[0] = epipe[1] = -1; + } + } + + in->restart = false; + in->halt = !in->respawn; + + if (!in->valid) + return; + + pid = fork(); + if (pid < 0) + return; + + if (!pid) { + uloop_done(); + closefd(opipe[0]); + closefd(epipe[0]); + instance_run(in, opipe[1], epipe[1]); + return; + } + + DEBUG(2, "Started instance %s::%s[%d]\n", in->srv->name, in->name, pid); + in->proc.pid = pid; + instance_writepid(in); + clock_gettime(CLOCK_MONOTONIC, &in->start); + uloop_process_add(&in->proc); + + if (opipe[0] > -1) { + ustream_fd_init(&in->_stdout, opipe[0]); + closefd(opipe[1]); + } + + if (epipe[0] > -1) { + ustream_fd_init(&in->_stderr, epipe[0]); + closefd(epipe[1]); + } + + service_event("instance.start", in->srv->name, in->name); +} + +static void +instance_stdio(struct ustream *s, int prio, struct service_instance *in) +{ + char *newline, *str, *arg0, ident[32]; + int len; + + arg0 = basename(blobmsg_data(blobmsg_data(in->command))); + snprintf(ident, sizeof(ident), "%s[%d]", arg0, in->proc.pid); + ulog_open(ULOG_SYSLOG, LOG_DAEMON, ident); + + do { + str = ustream_get_read_buf(s, NULL); + if (!str) + break; + + newline = strchr(str, '\n'); + if (!newline) + break; + + *newline = 0; + ulog(prio, "%s\n", str); + + len = newline + 1 - str; + ustream_consume(s, len); + } while (1); + + ulog_open(ULOG_SYSLOG, LOG_DAEMON, "procd"); +} + +static void +instance_stdout(struct ustream *s, int bytes) +{ + instance_stdio(s, LOG_INFO, + container_of(s, struct service_instance, _stdout.stream)); +} + +static void +instance_stderr(struct ustream *s, int bytes) +{ + instance_stdio(s, LOG_ERR, + container_of(s, struct service_instance, _stderr.stream)); +} + +static void +instance_timeout(struct uloop_timeout *t) +{ + struct service_instance *in; + + in = container_of(t, struct service_instance, timeout); + + if (!in->halt && (in->restart || in->respawn)) + instance_start(in); +} + +static void +instance_exit(struct uloop_process *p, int ret) +{ + struct service_instance *in; + struct timespec tp; + long runtime; + + in = container_of(p, struct service_instance, proc); + + clock_gettime(CLOCK_MONOTONIC, &tp); + runtime = tp.tv_sec - in->start.tv_sec; + + DEBUG(2, "Instance %s::%s exit with error code %d after %ld seconds\n", in->srv->name, in->name, ret, runtime); + if (upgrade_running) + return; + + uloop_timeout_cancel(&in->timeout); + if (in->halt) { + instance_removepid(in); + } else if (in->restart) { + instance_start(in); + } else if (in->respawn) { + if (runtime < in->respawn_threshold) + in->respawn_count++; + else + in->respawn_count = 0; + if (in->respawn_count > in->respawn_retry && in->respawn_retry > 0 ) { + LOG("Instance %s::%s s in a crash loop %d crashes, %ld seconds since last crash\n", + in->srv->name, in->name, in->respawn_count, runtime); + in->restart = in->respawn = 0; + in->halt = 1; + } else { + uloop_timeout_set(&in->timeout, in->respawn_timeout * 1000); + } + } + service_event("instance.stop", in->srv->name, in->name); +} + +void +instance_stop(struct service_instance *in) +{ + if (!in->proc.pending) + return; + in->halt = true; + in->restart = in->respawn = false; + kill(in->proc.pid, SIGTERM); +} + +static void +instance_restart(struct service_instance *in) +{ + if (!in->proc.pending) + return; + in->halt = false; + in->restart = true; + kill(in->proc.pid, SIGTERM); + instance_removepid(in); +} + +static bool +instance_config_changed(struct service_instance *in, struct service_instance *in_new) +{ + if (!in->valid) + return true; + + if (!blob_attr_equal(in->command, in_new->command)) + return true; + + if (!blobmsg_list_equal(&in->env, &in_new->env)) + return true; + + if (!blobmsg_list_equal(&in->data, &in_new->data)) + return true; + + if (!blobmsg_list_equal(&in->netdev, &in_new->netdev)) + return true; + + if (!blobmsg_list_equal(&in->file, &in_new->file)) + return true; + + if (in->nice != in_new->nice) + return true; + + if (in->uid != in_new->uid) + return true; + + if (in->gid != in_new->gid) + return true; + + if (in->pidfile && in_new->pidfile) + if (strcmp(in->pidfile, in_new->pidfile)) + return true; + + if (in->pidfile && !in_new->pidfile) + return true; + + if (!in->pidfile && in_new->pidfile) + return true; + + if (!blobmsg_list_equal(&in->limits, &in_new->limits)) + return true; + + if (!blobmsg_list_equal(&in->jail.mount, &in_new->jail.mount)) + return true; + + if (!blobmsg_list_equal(&in->errors, &in_new->errors)) + return true; + + return false; +} + +static bool +instance_netdev_cmp(struct blobmsg_list_node *l1, struct blobmsg_list_node *l2) +{ + struct instance_netdev *n1 = container_of(l1, struct instance_netdev, node); + struct instance_netdev *n2 = container_of(l2, struct instance_netdev, node); + + return n1->ifindex == n2->ifindex; +} + +static void +instance_netdev_update(struct blobmsg_list_node *l) +{ + struct instance_netdev *n = container_of(l, struct instance_netdev, node); + + n->ifindex = if_nametoindex(n->node.avl.key); +} + +static bool +instance_file_cmp(struct blobmsg_list_node *l1, struct blobmsg_list_node *l2) +{ + struct instance_file *f1 = container_of(l1, struct instance_file, node); + struct instance_file *f2 = container_of(l2, struct instance_file, node); + + return !memcmp(f1->md5, f2->md5, sizeof(f1->md5)); +} + +static void +instance_file_update(struct blobmsg_list_node *l) +{ + struct instance_file *f = container_of(l, struct instance_file, node); + md5_ctx_t md5; + char buf[256]; + int len, fd; + + memset(f->md5, 0, sizeof(f->md5)); + + fd = open(l->avl.key, O_RDONLY); + if (fd < 0) + return; + + md5_begin(&md5); + do { + len = read(fd, buf, sizeof(buf)); + if (len < 0) { + if (errno == EINTR) + continue; + + break; + } + if (!len) + break; + + md5_hash(buf, len, &md5); + } while(1); + + md5_end(f->md5, &md5); + close(fd); +} + +static void +instance_fill_any(struct blobmsg_list *l, struct blob_attr *cur) +{ + if (!cur) + return; + + blobmsg_list_fill(l, blobmsg_data(cur), blobmsg_data_len(cur), false); +} + +static bool +instance_fill_array(struct blobmsg_list *l, struct blob_attr *cur, blobmsg_update_cb cb, bool array) +{ + struct blobmsg_list_node *node; + + if (!cur) + return true; + + if (!blobmsg_check_attr_list(cur, BLOBMSG_TYPE_STRING)) + return false; + + blobmsg_list_fill(l, blobmsg_data(cur), blobmsg_data_len(cur), array); + if (cb) { + blobmsg_list_for_each(l, node) + cb(node); + } + return true; +} + +static int +instance_jail_parse(struct service_instance *in, struct blob_attr *attr) +{ + struct blob_attr *tb[__JAIL_ATTR_MAX]; + struct jail *jail = &in->jail; + struct stat s; + + if (stat("/sbin/ujail", &s)) + return 0; + + blobmsg_parse(jail_attr, __JAIL_ATTR_MAX, tb, + blobmsg_data(attr), blobmsg_data_len(attr)); + + jail->argc = 2; + + if (tb[JAIL_ATTR_NAME]) { + jail->name = blobmsg_get_string(tb[JAIL_ATTR_NAME]); + jail->argc += 2; + } + if (tb[JAIL_ATTR_HOSTNAME]) { + jail->hostname = blobmsg_get_string(tb[JAIL_ATTR_HOSTNAME]); + jail->argc += 2; + } + if (tb[JAIL_ATTR_PROCFS]) { + jail->procfs = blobmsg_get_bool(tb[JAIL_ATTR_PROCFS]); + jail->argc++; + } + if (tb[JAIL_ATTR_SYSFS]) { + jail->sysfs = blobmsg_get_bool(tb[JAIL_ATTR_SYSFS]); + jail->argc++; + } + if (tb[JAIL_ATTR_UBUS]) { + jail->ubus = blobmsg_get_bool(tb[JAIL_ATTR_UBUS]); + jail->argc++; + } + if (tb[JAIL_ATTR_LOG]) { + jail->log = blobmsg_get_bool(tb[JAIL_ATTR_LOG]); + jail->argc++; + } + if (tb[JAIL_ATTR_RONLY]) { + jail->ronly = blobmsg_get_bool(tb[JAIL_ATTR_RONLY]); + jail->argc++; + } + if (tb[JAIL_ATTR_MOUNT]) { + struct blob_attr *cur; + int rem; + + blobmsg_for_each_attr(cur, tb[JAIL_ATTR_MOUNT], rem) + jail->argc += 2; + instance_fill_array(&jail->mount, tb[JAIL_ATTR_MOUNT], NULL, false); + } + if (in->seccomp) + jail->argc += 2; + + return 1; +} + +static bool +instance_config_parse(struct service_instance *in) +{ + struct blob_attr *tb[__INSTANCE_ATTR_MAX]; + struct blob_attr *cur, *cur2; + int argc = 0; + int rem; + + blobmsg_parse(instance_attr, __INSTANCE_ATTR_MAX, tb, + blobmsg_data(in->config), blobmsg_data_len(in->config)); + + cur = tb[INSTANCE_ATTR_COMMAND]; + if (!cur) + return false; + + if (!blobmsg_check_attr_list(cur, BLOBMSG_TYPE_STRING)) + return false; + + blobmsg_for_each_attr(cur2, cur, rem) { + argc++; + break; + } + if (!argc) + return false; + + in->command = cur; + + if (tb[INSTANCE_ATTR_RESPAWN]) { + int i = 0; + uint32_t vals[3] = { 3600, 5, 5}; + + blobmsg_for_each_attr(cur2, tb[INSTANCE_ATTR_RESPAWN], rem) { + if ((i >= 3) && (blobmsg_type(cur2) == BLOBMSG_TYPE_STRING)) + continue; + vals[i] = atoi(blobmsg_get_string(cur2)); + i++; + } + in->respawn = true; + in->respawn_count = 0; + in->respawn_threshold = vals[0]; + in->respawn_timeout = vals[1]; + in->respawn_retry = vals[2]; + } + if (tb[INSTANCE_ATTR_TRIGGER]) { + in->trigger = tb[INSTANCE_ATTR_TRIGGER]; + trigger_add(in->trigger, in); + } + + if (tb[INSTANCE_ATTR_WATCH]) { + blobmsg_for_each_attr(cur2, tb[INSTANCE_ATTR_WATCH], rem) { + if (blobmsg_type(cur2) != BLOBMSG_TYPE_STRING) + continue; + DEBUG(3, "watch for %s\n", blobmsg_get_string(cur2)); + watch_add(blobmsg_get_string(cur2), in); + } + } + + if ((cur = tb[INSTANCE_ATTR_NICE])) { + in->nice = (int8_t) blobmsg_get_u32(cur); + if (in->nice < -20 || in->nice > 20) + return false; + } + + if (tb[INSTANCE_ATTR_USER]) { + struct passwd *p = getpwnam(blobmsg_get_string(tb[INSTANCE_ATTR_USER])); + if (p) { + in->uid = p->pw_uid; + in->gid = p->pw_gid; + } + } + + if (tb[INSTANCE_ATTR_TRACE]) + in->trace = blobmsg_get_bool(tb[INSTANCE_ATTR_TRACE]); + + if (tb[INSTANCE_ATTR_NO_NEW_PRIVS]) + in->no_new_privs = blobmsg_get_bool(tb[INSTANCE_ATTR_NO_NEW_PRIVS]); + + if (!in->trace && tb[INSTANCE_ATTR_SECCOMP]) { + char *seccomp = blobmsg_get_string(tb[INSTANCE_ATTR_SECCOMP]); + struct stat s; + + if (stat(seccomp, &s)) + ERROR("%s: not starting seccomp as %s is missing\n", in->name, seccomp); + else + in->seccomp = seccomp; + } + + if (tb[INSTANCE_ATTR_PIDFILE]) { + char *pidfile = blobmsg_get_string(tb[INSTANCE_ATTR_PIDFILE]); + if (pidfile) + in->pidfile = pidfile; + } + + if (!in->trace && tb[INSTANCE_ATTR_JAIL]) + in->has_jail = instance_jail_parse(in, tb[INSTANCE_ATTR_JAIL]); + + if (tb[INSTANCE_ATTR_STDOUT] && blobmsg_get_bool(tb[INSTANCE_ATTR_STDOUT])) + in->_stdout.fd.fd = -1; + + if (tb[INSTANCE_ATTR_STDERR] && blobmsg_get_bool(tb[INSTANCE_ATTR_STDERR])) + in->_stderr.fd.fd = -1; + + instance_fill_any(&in->data, tb[INSTANCE_ATTR_DATA]); + + if (!instance_fill_array(&in->env, tb[INSTANCE_ATTR_ENV], NULL, false)) + return false; + + if (!instance_fill_array(&in->netdev, tb[INSTANCE_ATTR_NETDEV], instance_netdev_update, true)) + return false; + + if (!instance_fill_array(&in->file, tb[INSTANCE_ATTR_FILE], instance_file_update, true)) + return false; + + if (!instance_fill_array(&in->limits, tb[INSTANCE_ATTR_LIMITS], NULL, false)) + return false; + + if (!instance_fill_array(&in->errors, tb[INSTANCE_ATTR_ERROR], NULL, true)) + return false; + + return true; +} + +static void +instance_config_cleanup(struct service_instance *in) +{ + blobmsg_list_free(&in->env); + blobmsg_list_free(&in->data); + blobmsg_list_free(&in->netdev); + blobmsg_list_free(&in->file); + blobmsg_list_free(&in->limits); + blobmsg_list_free(&in->errors); + blobmsg_list_free(&in->jail.mount); +} + +static void +instance_config_move(struct service_instance *in, struct service_instance *in_src) +{ + instance_config_cleanup(in); + blobmsg_list_move(&in->env, &in_src->env); + blobmsg_list_move(&in->data, &in_src->data); + blobmsg_list_move(&in->netdev, &in_src->netdev); + blobmsg_list_move(&in->file, &in_src->file); + blobmsg_list_move(&in->limits, &in_src->limits); + blobmsg_list_move(&in->errors, &in_src->errors); + blobmsg_list_move(&in->jail.mount, &in_src->jail.mount); + in->trigger = in_src->trigger; + in->command = in_src->command; + in->pidfile = in_src->pidfile; + in->name = in_src->name; + in->node.avl.key = in_src->node.avl.key; + + free(in->config); + in->config = in_src->config; + in_src->config = NULL; +} + +bool +instance_update(struct service_instance *in, struct service_instance *in_new) +{ + bool changed = instance_config_changed(in, in_new); + bool running = in->proc.pending; + + if (!changed && running) + return false; + + if (!running) { + if (changed) + instance_config_move(in, in_new); + instance_start(in); + } else { + instance_restart(in); + instance_config_move(in, in_new); + /* restart happens in the child callback handler */ + } + return true; +} + +void +instance_free(struct service_instance *in) +{ + instance_free_stdio(in); + uloop_process_delete(&in->proc); + uloop_timeout_cancel(&in->timeout); + trigger_del(in); + watch_del(in); + instance_config_cleanup(in); + free(in->config); + free(in); +} + +void +instance_init(struct service_instance *in, struct service *s, struct blob_attr *config) +{ + config = blob_memdup(config); + in->srv = s; + in->name = blobmsg_name(config); + in->config = config; + in->timeout.cb = instance_timeout; + in->proc.cb = instance_exit; + + in->_stdout.fd.fd = -2; + in->_stdout.stream.string_data = true; + in->_stdout.stream.notify_read = instance_stdout; + + in->_stderr.fd.fd = -2; + in->_stderr.stream.string_data = true; + in->_stderr.stream.notify_read = instance_stderr; + + blobmsg_list_init(&in->netdev, struct instance_netdev, node, instance_netdev_cmp); + blobmsg_list_init(&in->file, struct instance_file, node, instance_file_cmp); + blobmsg_list_simple_init(&in->env); + blobmsg_list_simple_init(&in->data); + blobmsg_list_simple_init(&in->limits); + blobmsg_list_simple_init(&in->errors); + blobmsg_list_simple_init(&in->jail.mount); + in->valid = instance_config_parse(in); +} + +void instance_dump(struct blob_buf *b, struct service_instance *in, int verbose) +{ + void *i; + + if (!in->valid) + return; + + i = blobmsg_open_table(b, in->name); + blobmsg_add_u8(b, "running", in->proc.pending); + if (in->proc.pending) + blobmsg_add_u32(b, "pid", in->proc.pid); + blobmsg_add_blob(b, in->command); + + if (!avl_is_empty(&in->errors.avl)) { + struct blobmsg_list_node *var; + void *e = blobmsg_open_array(b, "errors"); + blobmsg_list_for_each(&in->errors, var) + blobmsg_add_string(b, NULL, blobmsg_data(var->data)); + blobmsg_close_table(b, e); + } + + if (!avl_is_empty(&in->env.avl)) { + struct blobmsg_list_node *var; + void *e = blobmsg_open_table(b, "env"); + blobmsg_list_for_each(&in->env, var) + blobmsg_add_string(b, blobmsg_name(var->data), blobmsg_data(var->data)); + blobmsg_close_table(b, e); + } + + if (!avl_is_empty(&in->data.avl)) { + struct blobmsg_list_node *var; + void *e = blobmsg_open_table(b, "data"); + blobmsg_list_for_each(&in->data, var) + blobmsg_add_blob(b, var->data); + blobmsg_close_table(b, e); + } + + if (!avl_is_empty(&in->limits.avl)) { + struct blobmsg_list_node *var; + void *e = blobmsg_open_table(b, "limits"); + blobmsg_list_for_each(&in->limits, var) + blobmsg_add_string(b, blobmsg_name(var->data), blobmsg_data(var->data)); + blobmsg_close_table(b, e); + } + + if (in->respawn) { + void *r = blobmsg_open_table(b, "respawn"); + blobmsg_add_u32(b, "threshold", in->respawn_threshold); + blobmsg_add_u32(b, "timeout", in->respawn_timeout); + blobmsg_add_u32(b, "retry", in->respawn_retry); + blobmsg_close_table(b, r); + } + + if (in->trace) + blobmsg_add_u8(b, "trace", true); + + if (in->no_new_privs) + blobmsg_add_u8(b, "no_new_privs", true); + + if (in->seccomp) + blobmsg_add_string(b, "seccomp", in->seccomp); + + if (in->pidfile) + blobmsg_add_string(b, "pidfile", in->pidfile); + + if (in->has_jail) { + void *r = blobmsg_open_table(b, "jail"); + if (in->jail.name) + blobmsg_add_string(b, "name", in->jail.name); + if (in->jail.hostname) + blobmsg_add_string(b, "hostname", in->jail.hostname); + blobmsg_add_u8(b, "procfs", in->jail.procfs); + blobmsg_add_u8(b, "sysfs", in->jail.sysfs); + blobmsg_add_u8(b, "ubus", in->jail.ubus); + blobmsg_add_u8(b, "log", in->jail.log); + blobmsg_add_u8(b, "ronly", in->jail.ronly); + blobmsg_close_table(b, r); + if (!avl_is_empty(&in->jail.mount.avl)) { + struct blobmsg_list_node *var; + void *e = blobmsg_open_table(b, "mount"); + blobmsg_list_for_each(&in->jail.mount, var) + blobmsg_add_string(b, blobmsg_name(var->data), blobmsg_data(var->data)); + blobmsg_close_table(b, e); + } + } + + if (verbose && in->trigger) + blobmsg_add_blob(b, in->trigger); + + blobmsg_close_table(b, i); +} diff --git a/src/3P/procd/service/instance.h b/src/3P/procd/service/instance.h new file mode 100644 index 00000000..1ee04292 --- /dev/null +++ b/src/3P/procd/service/instance.h @@ -0,0 +1,88 @@ +/* + * 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_INSTANCE_H +#define __PROCD_INSTANCE_H + +#include +#include +#include +#include "../utils/utils.h" + +#define RESPAWN_ERROR (5 * 60) + +struct jail { + bool procfs; + bool sysfs; + bool ubus; + bool log; + bool ronly; + char *name; + char *hostname; + struct blobmsg_list mount; + int argc; +}; + +struct service_instance { + struct vlist_node node; + struct service *srv; + const char *name; + + int8_t nice; + bool valid; + + uid_t uid; + gid_t gid; + + bool halt; + bool restart; + bool respawn; + int respawn_count; + struct timespec start; + + bool trace; + bool has_jail; + bool no_new_privs; + struct jail jail; + char *seccomp; + char *pidfile; + + uint32_t respawn_timeout; + uint32_t respawn_threshold; + uint32_t respawn_retry; + + struct blob_attr *config; + struct uloop_process proc; + struct uloop_timeout timeout; + struct ustream_fd _stdout; + struct ustream_fd _stderr; + + struct blob_attr *command; + struct blob_attr *trigger; + struct blobmsg_list env; + struct blobmsg_list data; + struct blobmsg_list netdev; + struct blobmsg_list file; + struct blobmsg_list limits; + struct blobmsg_list errors; +}; + +void instance_start(struct service_instance *in); +void instance_stop(struct service_instance *in); +bool instance_update(struct service_instance *in, struct service_instance *in_new); +void instance_init(struct service_instance *in, struct service *s, struct blob_attr *config); +void instance_free(struct service_instance *in); +void instance_dump(struct blob_buf *b, struct service_instance *in, int debug); + +#endif diff --git a/src/3P/procd/service/service.c b/src/3P/procd/service/service.c new file mode 100644 index 00000000..0796adbb --- /dev/null +++ b/src/3P/procd/service/service.c @@ -0,0 +1,554 @@ +/* + * 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 "../procd.h" + +#include "service.h" +#include "instance.h" + +#include "../rcS.h" + +AVL_TREE(services, avl_strcmp, false, NULL); +static struct blob_buf b; +static struct ubus_context *ctx; + +static void +service_instance_add(struct service *s, struct blob_attr *attr) +{ + struct service_instance *in; + + if (blobmsg_type(attr) != BLOBMSG_TYPE_TABLE) + return; + + in = calloc(1, sizeof(*in)); + if (!in) + return; + + instance_init(in, s, attr); + vlist_add(&s->instances, &in->node, (void *) in->name); +} + +static void +service_instance_update(struct vlist_tree *tree, struct vlist_node *node_new, + struct vlist_node *node_old) +{ + struct service_instance *in_o = NULL, *in_n = NULL; + + if (node_old) + in_o = container_of(node_old, struct service_instance, node); + + if (node_new) + in_n = container_of(node_new, struct service_instance, node); + + if (in_o && in_n) { + DEBUG(2, "Update instance %s::%s\n", in_o->srv->name, in_o->name); + instance_update(in_o, in_n); + instance_free(in_n); + } else if (in_o) { + DEBUG(2, "Free instance %s::%s\n", in_o->srv->name, in_o->name); + instance_stop(in_o); + instance_free(in_o); + } else if (in_n) { + DEBUG(2, "Create instance %s::%s\n", in_n->srv->name, in_n->name); + instance_start(in_n); + } + blob_buf_init(&b, 0); + trigger_event("instance.update", b.head); +} + +static struct service * +service_alloc(const char *name) +{ + struct service *s; + char *new_name; + + s = calloc_a(sizeof(*s), &new_name, strlen(name) + 1); + strcpy(new_name, name); + + vlist_init(&s->instances, avl_strcmp, service_instance_update); + s->instances.keep_old = true; + s->name = new_name; + s->avl.key = s->name; + INIT_LIST_HEAD(&s->validators); + + return s; +} + +enum { + SERVICE_SET_NAME, + SERVICE_SET_SCRIPT, + SERVICE_SET_INSTANCES, + SERVICE_SET_TRIGGER, + SERVICE_SET_VALIDATE, + __SERVICE_SET_MAX +}; + +static const struct blobmsg_policy service_set_attrs[__SERVICE_SET_MAX] = { + [SERVICE_SET_NAME] = { "name", BLOBMSG_TYPE_STRING }, + [SERVICE_SET_SCRIPT] = { "script", BLOBMSG_TYPE_STRING }, + [SERVICE_SET_INSTANCES] = { "instances", BLOBMSG_TYPE_TABLE }, + [SERVICE_SET_TRIGGER] = { "triggers", BLOBMSG_TYPE_ARRAY }, + [SERVICE_SET_VALIDATE] = { "validate", BLOBMSG_TYPE_ARRAY }, +}; + +static int +service_update(struct service *s, struct blob_attr **tb, bool add) +{ + struct blob_attr *cur; + int rem; + + if (s->trigger) { + trigger_del(s); + free(s->trigger); + s->trigger = NULL; + } + + service_validate_del(s); + + if (tb[SERVICE_SET_TRIGGER] && blobmsg_data_len(tb[SERVICE_SET_TRIGGER])) { + s->trigger = blob_memdup(tb[SERVICE_SET_TRIGGER]); + if (!s->trigger) + return -1; + trigger_add(s->trigger, s); + } + + if (tb[SERVICE_SET_VALIDATE] && blobmsg_data_len(tb[SERVICE_SET_VALIDATE])) { + blobmsg_for_each_attr(cur, tb[SERVICE_SET_VALIDATE], rem) + service_validate_add(s, cur); + } + + if (tb[SERVICE_SET_INSTANCES]) { + if (!add) + vlist_update(&s->instances); + blobmsg_for_each_attr(cur, tb[SERVICE_SET_INSTANCES], rem) { + service_instance_add(s, cur); + } + if (!add) + vlist_flush(&s->instances); + } + + rc(s->name, "running"); + + return 0; +} + +static void +service_delete(struct service *s) +{ + service_event("service.stop", s->name, NULL); + vlist_flush_all(&s->instances); + avl_delete(&services, &s->avl); + trigger_del(s); + free(s->trigger); + free(s); + service_validate_del(s); +} + +enum { + SERVICE_ATTR_NAME, + __SERVICE_ATTR_MAX, +}; + +static const struct blobmsg_policy service_attrs[__SERVICE_ATTR_MAX] = { + [SERVICE_ATTR_NAME] = { "name", BLOBMSG_TYPE_STRING }, +}; + +enum { + SERVICE_DEL_ATTR_NAME, + SERVICE_DEL_ATTR_INSTANCE, + __SERVICE_DEL_ATTR_MAX, +}; + +static const struct blobmsg_policy service_del_attrs[__SERVICE_DEL_ATTR_MAX] = { + [SERVICE_DEL_ATTR_NAME] = { "name", BLOBMSG_TYPE_STRING }, + [SERVICE_DEL_ATTR_INSTANCE] = { "instance", BLOBMSG_TYPE_STRING }, +}; + +enum { + SERVICE_LIST_ATTR_NAME, + SERVICE_LIST_ATTR_VERBOSE, + __SERVICE_LIST_ATTR_MAX, +}; + +static const struct blobmsg_policy service_list_attrs[__SERVICE_LIST_ATTR_MAX] = { + [SERVICE_LIST_ATTR_NAME] = { "name", BLOBMSG_TYPE_STRING }, + [SERVICE_LIST_ATTR_VERBOSE] = { "verbose", BLOBMSG_TYPE_BOOL }, +}; + +enum { + EVENT_TYPE, + EVENT_DATA, + __EVENT_MAX +}; + +static const struct blobmsg_policy event_policy[__EVENT_MAX] = { + [EVENT_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_STRING }, + [EVENT_DATA] = { .name = "data", .type = BLOBMSG_TYPE_TABLE }, +}; + +enum { + VALIDATE_PACKAGE, + VALIDATE_TYPE, + VALIDATE_SERVICE, + __VALIDATE_MAX +}; + +static const struct blobmsg_policy validate_policy[__VALIDATE_MAX] = { + [VALIDATE_PACKAGE] = { .name = "package", .type = BLOBMSG_TYPE_STRING }, + [VALIDATE_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_STRING }, + [VALIDATE_SERVICE] = { .name = "service", .type = BLOBMSG_TYPE_STRING }, +}; + +enum { + DATA_NAME, + DATA_INSTANCE, + DATA_TYPE, + __DATA_MAX +}; + +static const struct blobmsg_policy get_data_policy[] = { + [DATA_NAME] = { "name", BLOBMSG_TYPE_STRING }, + [DATA_INSTANCE] = { "instance", BLOBMSG_TYPE_STRING }, + [DATA_TYPE] = { "type", BLOBMSG_TYPE_STRING }, +}; + +static int +service_handle_set(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__SERVICE_SET_MAX], *cur; + struct service *s = NULL; + const char *name; + bool add = !strcmp(method, "add"); + int ret; + + blobmsg_parse(service_set_attrs, __SERVICE_SET_MAX, tb, blob_data(msg), blob_len(msg)); + cur = tb[SERVICE_ATTR_NAME]; + if (!cur) + return UBUS_STATUS_INVALID_ARGUMENT; + + name = blobmsg_data(cur); + + s = avl_find_element(&services, name, s, avl); + if (s) { + DEBUG(2, "Update service %s\n", name); + return service_update(s, tb, add); + } + + DEBUG(2, "Create service %s\n", name); + s = service_alloc(name); + if (!s) + return UBUS_STATUS_UNKNOWN_ERROR; + + ret = service_update(s, tb, add); + if (ret) + return ret; + + avl_insert(&services, &s->avl); + + service_event("service.start", s->name, NULL); + + return 0; +} + +static void +service_dump(struct service *s, bool verbose) +{ + struct service_instance *in; + void *c, *i; + + c = blobmsg_open_table(&b, s->name); + + if (!avl_is_empty(&s->instances.avl)) { + i = blobmsg_open_table(&b, "instances"); + vlist_for_each_element(&s->instances, in, node) + instance_dump(&b, in, verbose); + blobmsg_close_table(&b, i); + } + if (verbose && s->trigger) + blobmsg_add_blob(&b, s->trigger); + if (verbose && !list_empty(&s->validators)) + service_validate_dump(&b, s); + blobmsg_close_table(&b, c); +} + +static int +service_handle_list(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__SERVICE_LIST_ATTR_MAX]; + struct service *s; + const char *name = NULL; + bool verbose = false; + + blobmsg_parse(service_list_attrs, __SERVICE_LIST_ATTR_MAX, tb, blob_data(msg), blob_len(msg)); + + if (tb[SERVICE_LIST_ATTR_VERBOSE]) + verbose = blobmsg_get_bool(tb[SERVICE_LIST_ATTR_VERBOSE]); + if (tb[SERVICE_LIST_ATTR_NAME]) + name = blobmsg_get_string(tb[SERVICE_LIST_ATTR_NAME]); + + blob_buf_init(&b, 0); + avl_for_each_element(&services, s, avl) { + if (name && strcmp(s->name, name) != 0) + continue; + + service_dump(s, verbose); + } + + ubus_send_reply(ctx, req, b.head); + + return 0; +} + +static int +service_handle_delete(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__SERVICE_DEL_ATTR_MAX], *cur; + struct service *s; + struct service_instance *in; + + blobmsg_parse(service_del_attrs, __SERVICE_DEL_ATTR_MAX, tb, blob_data(msg), blob_len(msg)); + + cur = tb[SERVICE_DEL_ATTR_NAME]; + if (!cur) + return UBUS_STATUS_NOT_FOUND; + + s = avl_find_element(&services, blobmsg_data(cur), s, avl); + if (!s) + return UBUS_STATUS_NOT_FOUND; + + cur = tb[SERVICE_DEL_ATTR_INSTANCE]; + if (!cur) { + service_delete(s); + return 0; + } + + in = vlist_find(&s->instances, blobmsg_data(cur), in, node); + if (!in) { + ERROR("instance %s not found\n", (char *) blobmsg_data(cur)); + return UBUS_STATUS_NOT_FOUND; + } + + vlist_delete(&s->instances, &in->node); + + return 0; +} + +static int +service_handle_update(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__SERVICE_ATTR_MAX], *cur; + struct service *s; + + blobmsg_parse(service_attrs, __SERVICE_ATTR_MAX, tb, blob_data(msg), blob_len(msg)); + + cur = tb[SERVICE_ATTR_NAME]; + if (!cur) + return UBUS_STATUS_INVALID_ARGUMENT; + + s = avl_find_element(&services, blobmsg_data(cur), s, avl); + if (!s) + return UBUS_STATUS_NOT_FOUND; + + if (!strcmp(method, "update_start")) + vlist_update(&s->instances); + else + vlist_flush(&s->instances); + + return 0; +} + +static int +service_handle_event(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__EVENT_MAX]; + + if (!msg) + return UBUS_STATUS_INVALID_ARGUMENT; + + blobmsg_parse(event_policy, __EVENT_MAX, tb, blob_data(msg), blob_len(msg)); + if (!tb[EVENT_TYPE] || !tb[EVENT_DATA]) + return UBUS_STATUS_INVALID_ARGUMENT; + + trigger_event(blobmsg_get_string(tb[EVENT_TYPE]), tb[EVENT_DATA]); + + return 0; +} + +static int +service_handle_validate(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__VALIDATE_MAX]; + char *p = NULL, *t = NULL; + + if (!msg) + return UBUS_STATUS_INVALID_ARGUMENT; + + blobmsg_parse(validate_policy, __VALIDATE_MAX, tb, blob_data(msg), blob_len(msg)); + if (tb[VALIDATE_SERVICE]) { + return 0; + } + if (tb[VALIDATE_PACKAGE]) + p = blobmsg_get_string(tb[VALIDATE_PACKAGE]); + + if (tb[VALIDATE_TYPE]) + t = blobmsg_get_string(tb[VALIDATE_TYPE]); + + blob_buf_init(&b, 0); + service_validate_dump_all(&b, p, t); + ubus_send_reply(ctx, req, b.head); + + return 0; +} + +static int +service_get_data(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct service_instance *in; + struct service *s; + struct blob_attr *tb[__DATA_MAX]; + const char *name = NULL; + const char *instance = NULL; + const char *type = NULL; + + blobmsg_parse(get_data_policy, __DATA_MAX, tb, blob_data(msg), blob_len(msg)); + if (tb[DATA_NAME]) + name = blobmsg_data(tb[DATA_NAME]); + if (tb[DATA_INSTANCE]) + instance = blobmsg_data(tb[DATA_INSTANCE]); + if (tb[DATA_TYPE]) + type = blobmsg_data(tb[DATA_TYPE]); + + blob_buf_init(&b, 0); + avl_for_each_element(&services, s, avl) { + void *cs = NULL; + + if (name && strcmp(name, s->name)) + continue; + + vlist_for_each_element(&s->instances, in, node) { + struct blobmsg_list_node *var; + void *ci = NULL; + + if (instance && strcmp(instance, in->name)) + continue; + + blobmsg_list_for_each(&in->data, var) { + if (type && + strcmp(blobmsg_name(var->data), type)) + continue; + + if (!cs) + cs = blobmsg_open_table(&b, s->name); + if (!ci) + ci = blobmsg_open_table(&b, in->name); + + blobmsg_add_blob(&b, var->data); + } + + if (ci) + blobmsg_close_table(&b, ci); + } + + if (cs) + blobmsg_close_table(&b, cs); + } + + ubus_send_reply(ctx, req, b.head); + return 0; +} + +static struct ubus_method main_object_methods[] = { + UBUS_METHOD("set", service_handle_set, service_set_attrs), + UBUS_METHOD("add", service_handle_set, service_set_attrs), + UBUS_METHOD("list", service_handle_list, service_list_attrs), + UBUS_METHOD("delete", service_handle_delete, service_del_attrs), + UBUS_METHOD("update_start", service_handle_update, service_attrs), + UBUS_METHOD("update_complete", service_handle_update, service_attrs), + UBUS_METHOD("event", service_handle_event, event_policy), + UBUS_METHOD("validate", service_handle_validate, validate_policy), + UBUS_METHOD("get_data", service_get_data, get_data_policy), +}; + +static struct ubus_object_type main_object_type = + UBUS_OBJECT_TYPE("service", main_object_methods); + +static struct ubus_object main_object = { + .name = "service", + .type = &main_object_type, + .methods = main_object_methods, + .n_methods = ARRAY_SIZE(main_object_methods), +}; + +int +service_start_early(char *name, char *cmdline) +{ + void *instances, *instance, *command, *respawn; + char *t; + + blob_buf_init(&b, 0); + blobmsg_add_string(&b, "name", name); + instances = blobmsg_open_table(&b, "instances"); + instance = blobmsg_open_table(&b, "instance1"); + command = blobmsg_open_array(&b, "command"); + t = strtok(cmdline, " "); + while (t) { + blobmsg_add_string(&b, NULL, t); + t = strtok(NULL, " "); + } + blobmsg_close_array(&b, command); + respawn = blobmsg_open_array(&b, "respawn"); + blobmsg_add_string(&b, NULL, "3600"); + blobmsg_add_string(&b, NULL, "1"); + blobmsg_add_string(&b, NULL, "0"); + blobmsg_close_array(&b, respawn); + blobmsg_close_table(&b, instance); + blobmsg_close_table(&b, instances); + + return service_handle_set(NULL, NULL, NULL, "add", b.head); +} + +void service_event(const char *type, const char *service, const char *instance) +{ + if (!ctx) + return; + + blob_buf_init(&b, 0); + blobmsg_add_string(&b, "service", service); + if (instance) + blobmsg_add_string(&b, "instance", instance); + ubus_notify(ctx, &main_object, type, b.head, -1); +} + +void ubus_init_service(struct ubus_context *_ctx) +{ + ctx = _ctx; + ubus_add_object(ctx, &main_object); +} diff --git a/src/3P/procd/service/service.h b/src/3P/procd/service/service.h new file mode 100644 index 00000000..c3f2964e --- /dev/null +++ b/src/3P/procd/service/service.h @@ -0,0 +1,56 @@ +/* + * 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_SERVICE_H +#define __PROCD_SERVICE_H + +#include +#include +#include + +extern struct avl_tree services; + +struct vrule { + struct avl_node avl; + char *option; + char *rule; +}; + +struct validate { + struct avl_node avl; + struct list_head list; + + char *package; + char *type; + + struct avl_tree rules; +}; + +struct service { + struct avl_node avl; + const char *name; + + struct blob_attr *trigger; + struct vlist_tree instances; + struct list_head validators; +}; + +void service_validate_add(struct service *s, struct blob_attr *attr); +void service_validate_dump(struct blob_buf *b, struct service *s); +void service_validate_dump_all(struct blob_buf *b, char *p, char *s); +int service_start_early(char *name, char *cmdline); +void service_validate_del(struct service *s); +void service_event(const char *type, const char *service, const char *instance); + +#endif diff --git a/src/3P/procd/service/setlbf.c b/src/3P/procd/service/setlbf.c new file mode 100644 index 00000000..94c251e0 --- /dev/null +++ b/src/3P/procd/service/setlbf.c @@ -0,0 +1,6 @@ +#include + +static void __attribute__((constructor)) setlbf(void) +{ + setbuf(stdout, NULL); +} diff --git a/src/3P/procd/service/trigger.c b/src/3P/procd/service/trigger.c new file mode 100644 index 00000000..440830b2 --- /dev/null +++ b/src/3P/procd/service/trigger.c @@ -0,0 +1,303 @@ +/* + * 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 +#include + +#include "../procd.h" + +struct trigger { + struct list_head list; + + void *id; + char *type; + int timeout; + + struct blob_attr *rule; + struct blob_attr *data; + + struct json_script_ctx jctx; +}; + +struct trigger_command { + struct avl_node avl; + struct uloop_timeout delay; + bool requeue; + + struct runqueue_process proc; + struct json_script_ctx jctx; + + struct blob_attr data[]; +}; + +static LIST_HEAD(triggers); +static RUNQUEUE(q, 1); +static AVL_TREE(trigger_pending, avl_blobcmp, false, NULL); + +static const char* rule_handle_var(struct json_script_ctx *ctx, const char *name, struct blob_attr *vars) +{ + return NULL; +} + +static struct json_script_file * +rule_load_script(struct json_script_ctx *ctx, const char *name) +{ + struct trigger *t = container_of(ctx, struct trigger, jctx); + + if (strcmp(name, t->type) != 0) + return NULL; + + return json_script_file_from_blobmsg(t->type, t->rule, blob_pad_len(t->rule)); +} + +static void trigger_free(struct trigger *t) +{ + json_script_free(&t->jctx); + free(t->data); + list_del(&t->list); + free(t); +} + +static void trigger_command_complete(struct runqueue *q, struct runqueue_task *p) +{ + struct trigger_command *cmd = container_of(p, struct trigger_command, proc.task); + + if (cmd->requeue) { + cmd->requeue = false; + runqueue_task_add(q, p, false); + return; + } + + avl_delete(&trigger_pending, &cmd->avl); + free(cmd); +} + +static void trigger_command_run(struct runqueue *q, struct runqueue_task *t) +{ + struct trigger_command *cmd = container_of(t, struct trigger_command, proc.task); + struct blob_attr *cur; + char **argv; + pid_t pid; + int n = 0; + int rem; + + pid = fork(); + if (pid < 0) { + trigger_command_complete(q, t); + return; + } + + if (pid) { + runqueue_process_add(q, &cmd->proc, pid); + return; + } + + if (debug < 3) { + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + } + + blobmsg_for_each_attr(cur, cmd->data, rem) + n++; + + argv = alloca((n + 1) * sizeof(*argv)); + n = 0; + blobmsg_for_each_attr(cur, cmd->data, rem) + argv[n++] = blobmsg_get_string(cur); + argv[n] = NULL; + + if (n > 0) + execvp(argv[0], &argv[0]); + + exit(1); +} + +static void trigger_command_start(struct uloop_timeout *timeout) +{ + static const struct runqueue_task_type trigger_command_type = { + .run = trigger_command_run, + .cancel = runqueue_process_cancel_cb, + .kill = runqueue_process_kill_cb, + }; + struct trigger_command *cmd = container_of(timeout, struct trigger_command, delay); + + cmd->proc.task.type = &trigger_command_type; + cmd->proc.task.complete = trigger_command_complete; + runqueue_task_add(&q, &cmd->proc.task, false); +} + +static void trigger_command_add(struct trigger *t, struct blob_attr *data) +{ + struct trigger_command *cmd; + int remaining; + + cmd = avl_find_element(&trigger_pending, data, cmd, avl); + if (cmd) { + /* Command currently running? */ + if (!cmd->delay.pending) { + cmd->requeue = true; + return; + } + + /* Extend timer if trigger timeout is bigger than remaining time */ + remaining = uloop_timeout_remaining(&cmd->delay); + if (remaining < t->timeout) + uloop_timeout_set(&cmd->delay, t->timeout); + + return; + } + + cmd = calloc(1, sizeof(*cmd) + blob_pad_len(data)); + if (!cmd) + return; + + cmd->avl.key = cmd->data; + cmd->delay.cb = trigger_command_start; + memcpy(cmd->data, data, blob_pad_len(data)); + avl_insert(&trigger_pending, &cmd->avl); + uloop_timeout_set(&cmd->delay, t->timeout > 0 ? t->timeout : 1); +} + +static void rule_handle_command(struct json_script_ctx *ctx, const char *name, + struct blob_attr *exec, struct blob_attr *vars) +{ + struct trigger *t = container_of(ctx, struct trigger, jctx); + + if (!strcmp(name, "run_script")) { + trigger_command_add(t, exec); + return; + } +} + +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 trigger* _trigger_add(char *type, struct blob_attr *rule, int timeout, void *id) +{ + char *_t; + struct blob_attr *_r; + struct trigger *t = calloc_a(sizeof(*t), &_t, strlen(type) + 1, &_r, blob_pad_len(rule)); + + t->type = _t; + t->rule = _r; + t->timeout = timeout; + t->id = id; + t->jctx.handle_var = rule_handle_var, + t->jctx.handle_error = rule_handle_error, + t->jctx.handle_command = rule_handle_command, + t->jctx.handle_file = rule_load_script, + + strcpy(t->type, type); + memcpy(t->rule, rule, blob_pad_len(rule)); + + list_add(&t->list, &triggers); + json_script_init(&t->jctx); + + return t; +} + +void trigger_add(struct blob_attr *rule, void *id) +{ + struct blob_attr *cur; + int rem; + + blobmsg_for_each_attr(cur, rule, rem) { + struct blob_attr *_cur, *type = NULL, *script = NULL, *timeout = NULL; + int _rem; + int i = 0; + + if (blobmsg_type(cur) != BLOBMSG_TYPE_ARRAY) + continue; + + blobmsg_for_each_attr(_cur, cur, _rem) { + switch (i++) { + case 0: + if (blobmsg_type(_cur) == BLOBMSG_TYPE_STRING) + type = _cur; + break; + + case 1: + if (blobmsg_type(_cur) == BLOBMSG_TYPE_ARRAY) + script = _cur; + break; + + case 2: + if (blobmsg_type(_cur) == BLOBMSG_TYPE_INT32) + timeout = _cur; + break; + } + } + + if (type && script) { + int t = 0; + + if (timeout) + t = blobmsg_get_u32(timeout); + _trigger_add(blobmsg_get_string(type), script, t, id); + } + } +} + +void trigger_del(void *id) +{ + struct trigger *t, *n; + + list_for_each_entry_safe(t, n, &triggers, list) { + if (t->id != id) + continue; + + trigger_free(t); + } +} + +static bool trigger_match(const char *event, const char *match) +{ + char *wildcard = strstr(match, ".*"); + if (wildcard) + return !strncmp(event, match, wildcard - match); + return !strcmp(event, match); +} + +void trigger_event(const char *type, struct blob_attr *data) +{ + struct trigger *t; + + list_for_each_entry(t, &triggers, list) { + if (!trigger_match(type, t->type)) + continue; + json_script_run(&t->jctx, t->type, data); + } +} diff --git a/src/3P/procd/service/validate.c b/src/3P/procd/service/validate.c new file mode 100644 index 00000000..cf04490f --- /dev/null +++ b/src/3P/procd/service/validate.c @@ -0,0 +1,157 @@ +/* + * 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 "../procd.h" + +#include "service.h" + +enum { + SERVICE_VAL_PACKAGE, + SERVICE_VAL_TYPE, + SERVICE_VAL_DATA, + __SERVICE_VAL_MAX +}; + +static const struct blobmsg_policy service_validate_attrs[__SERVICE_VAL_MAX] = { + [SERVICE_VAL_PACKAGE] = { "package", BLOBMSG_TYPE_STRING }, + [SERVICE_VAL_TYPE] = { "type", BLOBMSG_TYPE_STRING }, + [SERVICE_VAL_DATA] = { "data", BLOBMSG_TYPE_TABLE }, +}; + +static AVL_TREE(validators, avl_strcmp, true, NULL); + +void +service_validate_dump_all(struct blob_buf *b, char *p, char *s) +{ + struct json_object *r = json_object_new_object(); + struct validate *v; + + if (!r) + return; + + avl_for_each_element(&validators, v, avl) { + struct json_object *o, *t; + struct vrule *vr; + + if (p && strcmp(p, v->package)) + continue; + + if (s && strcmp(s, v->type)) + continue; + + json_object_object_get_ex(r, v->package, &o); + if (!o) { + o = json_object_new_object(); + json_object_object_add(r, v->package, o); + } + json_object_object_get_ex(o, v->type, &t); + if (!t) { + t = json_object_new_object(); + json_object_object_add(o, v->type, t); + } + avl_for_each_element(&v->rules, vr, avl) + json_object_object_add(t, vr->option, json_object_new_string(vr->rule)); + } + blobmsg_add_object(b, r); + json_object_put(r); +} + +void +service_validate_dump(struct blob_buf *b, struct service *s) +{ + struct validate *v; + void *i = blobmsg_open_array(b, "validate"); + + list_for_each_entry(v, &s->validators, list) { + struct vrule *vr; + void *k, *j = blobmsg_open_table(b, "validate"); + + blobmsg_add_string(b, "package", v->package); + blobmsg_add_string(b, "type", v->type); + k = blobmsg_open_table(b, "rules"); + avl_for_each_element(&v->rules, vr, avl) + blobmsg_add_string(b, vr->option, vr->rule); + blobmsg_close_table(b, k); + blobmsg_close_table(b, j); + } + blobmsg_close_array(b, i); +} + +void +service_validate_del(struct service *s) +{ + struct validate *v, *n; + + if (list_empty(&s->validators)) + return; + + list_for_each_entry_safe(v, n, &s->validators, list) { + struct vrule *vr, *a; + + avl_remove_all_elements(&v->rules, vr, avl, a) + free(vr); + + avl_delete(&validators, &v->avl); + list_del(&v->list); + free(v); + } +} + +void +service_validate_add(struct service *s, struct blob_attr *msg) +{ + struct blob_attr *tb[__SERVICE_VAL_MAX]; + struct validate *v; + char *type, *package; + struct blob_attr *cur; + int rem; + + blobmsg_parse(service_validate_attrs, __SERVICE_VAL_MAX, tb, blobmsg_data(msg), blobmsg_data_len(msg)); + if (!tb[SERVICE_VAL_PACKAGE] || !tb[SERVICE_VAL_TYPE] || !tb[SERVICE_VAL_DATA]) + return; + + v = calloc_a(sizeof(*v), &package, blobmsg_data_len(tb[SERVICE_VAL_PACKAGE]) + 1, + &type, blobmsg_data_len(tb[SERVICE_VAL_TYPE]) + 1); + if (!v) + return; + + v->type = type; + v->avl.key = v->package = package; + strcpy(v->package, blobmsg_get_string(tb[SERVICE_VAL_PACKAGE])); + strcpy(v->type, blobmsg_get_string(tb[SERVICE_VAL_TYPE])); + + list_add(&v->list, &s->validators); + if (avl_insert(&validators, &v->avl)) { + free(v); + return; + } + avl_init(&v->rules, avl_strcmp, false, NULL); + + blobmsg_for_each_attr(cur, tb[SERVICE_VAL_DATA], rem) { + char *option; + char *rule; + struct vrule *vr = calloc_a(sizeof(*vr), &option, strlen(blobmsg_name(cur)) + 1, + &rule, strlen(blobmsg_get_string(cur)) + 1); + + vr->avl.key = vr->option = option; + vr->rule = rule; + strcpy(vr->option, blobmsg_name(cur)); + strcpy(vr->rule, blobmsg_get_string(cur)); + if (avl_insert(&v->rules, &vr->avl)) + free(vr); + } +} diff --git a/src/3P/procd/service/watch.c b/src/3P/procd/service/watch.c new file mode 100644 index 00000000..349b484a --- /dev/null +++ b/src/3P/procd/service/watch.c @@ -0,0 +1,119 @@ +/* + * 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 "../procd.h" + +struct watch_object { + struct list_head list; + + void *id; + char *name; +}; + +static struct ubus_event_handler watch_event; +static struct ubus_subscriber watch_subscribe; +static LIST_HEAD(watch_objects); + +static void watch_subscribe_cb(struct ubus_context *ctx, struct ubus_event_handler *ev, + const char *type, struct blob_attr *msg) +{ + static const struct blobmsg_policy policy = { + "path", BLOBMSG_TYPE_STRING + }; + struct watch_object *o; + struct blob_attr *attr; + const char *path; + + DEBUG(3, "ubus event %s\n", type); + if (strcmp(type, "ubus.object.add") != 0) + return; + + blobmsg_parse(&policy, 1, &attr, blob_data(msg), blob_len(msg)); + if (!attr) + return; + + path = blobmsg_data(attr); + DEBUG(3, "ubus path %s\n", path); + + list_for_each_entry(o, &watch_objects, list) { + unsigned int id; + + if (strcmp(o->name, path)) + continue; + if (ubus_lookup_id(ctx, path, &id)) + continue; + if (!ubus_subscribe(ctx, &watch_subscribe, id)) + return; + ERROR("failed to subscribe %d\n", id); + } +} + +void +watch_add(const char *_name, void *id) +{ + int len = strlen(_name); + char *name; + struct watch_object *o = calloc_a(sizeof(*o), &name, len + 1); + + o->name = name; + strcpy(name, _name); + o->id = id; + list_add(&o->list, &watch_objects); +} + +void +watch_del(void *id) +{ + struct watch_object *t, *n; + + list_for_each_entry_safe(t, n, &watch_objects, list) { + if (t->id != id) + continue; + list_del(&t->list); + free(t); + } +} + +static int +watch_notify_cb(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + if (debug >= 3) { + char *str; + + str = blobmsg_format_json(msg, true); + DEBUG(3, "Received ubus notify '%s': %s\n", method, str); + free(str); + } + + trigger_event(method, msg); + return 0; +} + +void +watch_ubus(struct ubus_context *ctx) +{ + watch_event.cb = watch_subscribe_cb; + watch_subscribe.cb = watch_notify_cb; + if (ubus_register_subscriber(ctx, &watch_subscribe)) + ERROR("failed to register ubus subscriber\n"); + if (ubus_register_event_handler(ctx, &watch_event, "ubus.object.add")) + ERROR("failed to add ubus event handler\n"); +} diff --git a/src/3P/procd/signal.c b/src/3P/procd/signal.c new file mode 100644 index 00000000..07dda9a4 --- /dev/null +++ b/src/3P/procd/signal.c @@ -0,0 +1,101 @@ +/* + * 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 "procd.h" + +static void do_reboot(void) +{ + LOG("reboot\n"); + fflush(stderr); + sync(); + sleep(2); + reboot(RB_AUTOBOOT); + while (1) + ; +} + +static void signal_shutdown(int signal, siginfo_t *siginfo, void *data) +{ + int event = 0; + char *msg = NULL; + +#ifndef DISABLE_INIT + switch(signal) { + case SIGINT: + case SIGTERM: + event = RB_AUTOBOOT; + msg = "reboot"; + break; + case SIGUSR1: + case SIGUSR2: + event = RB_POWER_OFF; + msg = "poweroff"; + break; + } +#endif + + DEBUG(1, "Triggering %s\n", msg); + if (event) + procd_shutdown(event); +} + +struct sigaction sa_shutdown = { + .sa_sigaction = signal_shutdown, + .sa_flags = SA_SIGINFO +}; + +static void signal_crash(int signal, siginfo_t *siginfo, void *data) +{ + ERROR("Rebooting as procd has crashed\n"); + do_reboot(); +} + +struct sigaction sa_crash = { + .sa_sigaction = signal_crash, + .sa_flags = SA_SIGINFO +}; + +static void signal_dummy(int signal, siginfo_t *siginfo, void *data) +{ + ERROR("Got unexpected signal %d\n", signal); +} + +struct sigaction sa_dummy = { + .sa_sigaction = signal_dummy, + .sa_flags = SA_SIGINFO +}; + +void procd_signal(void) +{ + signal(SIGPIPE, SIG_IGN); + if (getpid() != 1) + return; + sigaction(SIGTERM, &sa_shutdown, NULL); + sigaction(SIGINT, &sa_shutdown, NULL); + sigaction(SIGUSR1, &sa_shutdown, NULL); + sigaction(SIGUSR2, &sa_shutdown, NULL); + sigaction(SIGSEGV, &sa_crash, NULL); + sigaction(SIGBUS, &sa_crash, NULL); + sigaction(SIGHUP, &sa_dummy, NULL); + sigaction(SIGKILL, &sa_dummy, NULL); + sigaction(SIGSTOP, &sa_dummy, NULL); +#ifndef DISABLE_INIT + reboot(RB_DISABLE_CAD); +#endif +} diff --git a/src/3P/procd/state.c b/src/3P/procd/state.c new file mode 100644 index 00000000..4ad9e2d8 --- /dev/null +++ b/src/3P/procd/state.c @@ -0,0 +1,202 @@ +/* + * 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 "procd.h" +#include "syslog.h" +#include "plug/hotplug.h" +#include "watchdog.h" +#include "service/service.h" +#include "utils/utils.h" + +enum { + STATE_NONE = 0, + STATE_EARLY, + STATE_UBUS, + STATE_INIT, + STATE_RUNNING, + STATE_SHUTDOWN, + STATE_HALT, + __STATE_MAX, +}; + +static int state = STATE_NONE; +static int reboot_event; + +static void set_stdio(const char* tty) +{ + if (chdir("/dev") || + !freopen(tty, "r", stdin) || + !freopen(tty, "w", stdout) || + !freopen(tty, "w", stderr) || + chdir("/")) + ERROR("failed to set stdio\n"); + else + fcntl(STDERR_FILENO, F_SETFL, fcntl(STDERR_FILENO, F_GETFL) | O_NONBLOCK); +} + +static void set_console(void) +{ + const char* tty; + char* split; + char line[ 20 ]; + const char* try[] = { "tty0", "console", NULL }; /* Try the most common outputs */ + int f, i = 0; + + tty = get_cmdline_val("console",line,sizeof(line)); + if (tty != NULL) { + split = strchr(tty, ','); + if ( split != NULL ) + *split = '\0'; + } else { + // Try a default + tty=try[i]; + i++; + } + + if (chdir("/dev")) { + ERROR("failed to change dir to /dev\n"); + return; + } + while (tty!=NULL) { + f = open(tty, O_RDONLY); + if (f >= 0) { + close(f); + break; + } + + tty=try[i]; + i++; + } + if (chdir("/")) + ERROR("failed to change dir to /\n"); + + if (tty != NULL) + set_stdio(tty); +} + +static void state_enter(void) +{ + char ubus_cmd[] = "/sbin/ubusd"; + + switch (state) { + case STATE_EARLY: + LOG("- early -\n"); + watchdog_init(0); + hotplug("/etc/hotplug.json"); + procd_coldplug(); + break; + + case STATE_UBUS: + // try to reopen incase the wdt was not available before coldplug + watchdog_init(0); + set_stdio("console"); + LOG("- ubus -\n"); + procd_connect_ubus(); + service_start_early("ubus", ubus_cmd); + break; + + case STATE_INIT: + LOG("- init -\n"); + procd_inittab(); + procd_inittab_run("respawn"); + procd_inittab_run("askconsole"); + procd_inittab_run("askfirst"); + procd_inittab_run("sysinit"); + + // switch to syslog log channel + ulog_open(ULOG_SYSLOG, LOG_DAEMON, "procd"); + break; + + case STATE_RUNNING: + LOG("- init complete -\n"); + break; + + case STATE_SHUTDOWN: + /* Redirect output to the console for the users' benefit */ + set_console(); + LOG("- shutdown -\n"); + procd_inittab_run("shutdown"); + sync(); + break; + + case STATE_HALT: + // To prevent killed processes from interrupting the sleep + signal(SIGCHLD, SIG_IGN); + LOG("- SIGTERM processes -\n"); + kill(-1, SIGTERM); + sync(); + sleep(1); + LOG("- SIGKILL processes -\n"); + kill(-1, SIGKILL); + sync(); + sleep(1); +#ifndef DISABLE_INIT + if (reboot_event == RB_POWER_OFF) + LOG("- power down -\n"); + else + LOG("- reboot -\n"); + + /* Allow time for last message to reach serial console, etc */ + sleep(1); + + /* We have to fork here, since the kernel calls do_exit(EXIT_SUCCESS) + * in linux/kernel/sys.c, which can cause the machine to panic when + * the init process exits... */ + if (!vfork( )) { /* child */ + reboot(reboot_event); + _exit(EXIT_SUCCESS); + } + while (1) + sleep(1); +#else + exit(0); +#endif + break; + + default: + ERROR("Unhandled state %d\n", state); + return; + }; +} + +void procd_state_next(void) +{ + DEBUG(4, "Change state %d -> %d\n", state, state + 1); + state++; + state_enter(); +} + +void procd_state_ubus_connect(void) +{ + if (state == STATE_UBUS) + procd_state_next(); +} + +void procd_shutdown(int event) +{ + if (state >= STATE_SHUTDOWN) + return; + DEBUG(2, "Shutting down system with event %x\n", event); + reboot_event = event; + state = STATE_SHUTDOWN; + state_enter(); +} diff --git a/src/3P/procd/system.c b/src/3P/procd/system.c new file mode 100644 index 00000000..1e31ce68 --- /dev/null +++ b/src/3P/procd/system.c @@ -0,0 +1,444 @@ +/* + * 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 +#ifdef linux +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "procd.h" +#include "watchdog.h" + +static struct blob_buf b; +static int notify; +static struct ubus_context *_ctx; + +int upgrade_running = 0; + +static int system_board(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + void *c; + char line[256]; + char *key, *val, *next; + struct utsname utsname; + FILE *f; + + blob_buf_init(&b, 0); + + if (uname(&utsname) >= 0) + { + blobmsg_add_string(&b, "kernel", utsname.release); + blobmsg_add_string(&b, "hostname", utsname.nodename); + } + + if ((f = fopen("/proc/cpuinfo", "r")) != NULL) + { + while(fgets(line, sizeof(line), f)) + { + key = strtok(line, "\t:"); + val = strtok(NULL, "\t\n"); + + if (!key || !val) + continue; + + if (!strcasecmp(key, "system type") || + !strcasecmp(key, "processor") || + !strcasecmp(key, "model name")) + { + strtoul(val + 2, &key, 0); + + if (key == (val + 2) || *key != 0) + { + blobmsg_add_string(&b, "system", val + 2); + break; + } + } + } + + fclose(f); + } + + if ((f = fopen("/tmp/sysinfo/model", "r")) != NULL || + (f = fopen("/proc/device-tree/model", "r")) != NULL) + { + if (fgets(line, sizeof(line), f)) + { + val = strtok(line, "\t\n"); + + if (val) + blobmsg_add_string(&b, "model", val); + } + + fclose(f); + } + else if ((f = fopen("/proc/cpuinfo", "r")) != NULL) + { + while(fgets(line, sizeof(line), f)) + { + key = strtok(line, "\t:"); + val = strtok(NULL, "\t\n"); + + if (!key || !val) + continue; + + if (!strcasecmp(key, "machine") || + !strcasecmp(key, "hardware")) + { + blobmsg_add_string(&b, "model", val + 2); + break; + } + } + + fclose(f); + } + + if ((f = fopen("/etc/openwrt_release", "r")) != NULL) + { + c = blobmsg_open_table(&b, "release"); + + while (fgets(line, sizeof(line), f)) + { + char *dest; + char ch; + + key = line; + val = strchr(line, '='); + if (!val) + continue; + + *(val++) = 0; + + if (!strcasecmp(key, "DISTRIB_ID")) + key = "distribution"; + else if (!strcasecmp(key, "DISTRIB_RELEASE")) + key = "version"; + else if (!strcasecmp(key, "DISTRIB_REVISION")) + key = "revision"; + else if (!strcasecmp(key, "DISTRIB_CODENAME")) + key = "codename"; + else if (!strcasecmp(key, "DISTRIB_TARGET")) + key = "target"; + else if (!strcasecmp(key, "DISTRIB_DESCRIPTION")) + key = "description"; + else + continue; + + dest = blobmsg_alloc_string_buffer(&b, key, strlen(val)); + if (!dest) { + ERROR("Failed to allocate blob.\n"); + continue; + } + + while (val && (ch = *(val++)) != 0) { + switch (ch) { + case '\'': + case '"': + next = strchr(val, ch); + if (next) + *next = 0; + + strcpy(dest, val); + + if (next) + val = next + 1; + + dest += strlen(dest); + break; + case '\\': + *(dest++) = *(val++); + break; + } + } + blobmsg_add_string_buffer(&b); + } + + blobmsg_close_array(&b, c); + + fclose(f); + } + + ubus_send_reply(ctx, req, b.head); + + return UBUS_STATUS_OK; +} + +static int system_info(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + time_t now; + struct tm *tm; +#ifdef linux + struct sysinfo info; + void *c; + + if (sysinfo(&info)) + return UBUS_STATUS_UNKNOWN_ERROR; +#endif + + now = time(NULL); + + if (!(tm = localtime(&now))) + return UBUS_STATUS_UNKNOWN_ERROR; + + blob_buf_init(&b, 0); + + blobmsg_add_u32(&b, "localtime", now + tm->tm_gmtoff); + +#ifdef linux + blobmsg_add_u32(&b, "uptime", info.uptime); + + c = blobmsg_open_array(&b, "load"); + blobmsg_add_u32(&b, NULL, info.loads[0]); + blobmsg_add_u32(&b, NULL, info.loads[1]); + blobmsg_add_u32(&b, NULL, info.loads[2]); + blobmsg_close_array(&b, c); + + c = blobmsg_open_table(&b, "memory"); + blobmsg_add_u64(&b, "total", info.mem_unit * info.totalram); + blobmsg_add_u64(&b, "free", info.mem_unit * info.freeram); + blobmsg_add_u64(&b, "shared", info.mem_unit * info.sharedram); + blobmsg_add_u64(&b, "buffered", info.mem_unit * info.bufferram); + blobmsg_close_table(&b, c); + + c = blobmsg_open_table(&b, "swap"); + blobmsg_add_u64(&b, "total", info.mem_unit * info.totalswap); + blobmsg_add_u64(&b, "free", info.mem_unit * info.freeswap); + blobmsg_close_table(&b, c); +#endif + + ubus_send_reply(ctx, req, b.head); + + return UBUS_STATUS_OK; +} + +static int system_upgrade(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + upgrade_running = 1; + return 0; +} + +static int system_reboot(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + procd_shutdown(RB_AUTOBOOT); + return 0; +} + +enum { + WDT_FREQUENCY, + WDT_TIMEOUT, + WDT_STOP, + __WDT_MAX +}; + +static const struct blobmsg_policy watchdog_policy[__WDT_MAX] = { + [WDT_FREQUENCY] = { .name = "frequency", .type = BLOBMSG_TYPE_INT32 }, + [WDT_TIMEOUT] = { .name = "timeout", .type = BLOBMSG_TYPE_INT32 }, + [WDT_STOP] = { .name = "stop", .type = BLOBMSG_TYPE_BOOL }, +}; + +static int watchdog_set(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__WDT_MAX]; + const char *status; + + if (!msg) + return UBUS_STATUS_INVALID_ARGUMENT; + + blobmsg_parse(watchdog_policy, __WDT_MAX, tb, blob_data(msg), blob_len(msg)); + if (tb[WDT_FREQUENCY]) { + unsigned int timeout = watchdog_timeout(0); + unsigned int freq = blobmsg_get_u32(tb[WDT_FREQUENCY]); + + if (freq) { + if (freq > timeout / 2) + freq = timeout / 2; + watchdog_frequency(freq); + } + } + + if (tb[WDT_TIMEOUT]) { + unsigned int timeout = blobmsg_get_u32(tb[WDT_TIMEOUT]); + unsigned int frequency = watchdog_frequency(0); + + if (timeout <= frequency) + timeout = frequency * 2; + watchdog_timeout(timeout); + } + + if (tb[WDT_STOP]) + watchdog_set_stopped(blobmsg_get_bool(tb[WDT_STOP])); + + if (watchdog_fd() == NULL) + status = "offline"; + else if (watchdog_get_stopped()) + status = "stopped"; + else + status = "running"; + + blob_buf_init(&b, 0); + blobmsg_add_string(&b, "status", status); + blobmsg_add_u32(&b, "timeout", watchdog_timeout(0)); + blobmsg_add_u32(&b, "frequency", watchdog_frequency(0)); + ubus_send_reply(ctx, req, b.head); + + return 0; +} + +enum { + SIGNAL_PID, + SIGNAL_NUM, + __SIGNAL_MAX +}; + +static const struct blobmsg_policy signal_policy[__SIGNAL_MAX] = { + [SIGNAL_PID] = { .name = "pid", .type = BLOBMSG_TYPE_INT32 }, + [SIGNAL_NUM] = { .name = "signum", .type = BLOBMSG_TYPE_INT32 }, +}; + +static int proc_signal(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__SIGNAL_MAX]; + + if (!msg) + return UBUS_STATUS_INVALID_ARGUMENT; + + blobmsg_parse(signal_policy, __SIGNAL_MAX, tb, blob_data(msg), blob_len(msg)); + if (!tb[SIGNAL_PID || !tb[SIGNAL_NUM]]) + return UBUS_STATUS_INVALID_ARGUMENT; + + kill(blobmsg_get_u32(tb[SIGNAL_PID]), blobmsg_get_u32(tb[SIGNAL_NUM])); + + return 0; +} + +enum { + NAND_PATH, + __NAND_MAX +}; + +static const struct blobmsg_policy nand_policy[__NAND_MAX] = { + [NAND_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, +}; + +static void +procd_spawn_upgraded(char *path) +{ + char *wdt_fd = watchdog_fd(); + char *argv[] = { "/tmp/upgraded", NULL, NULL}; + + argv[1] = path; + + DEBUG(2, "Exec to upgraded now\n"); + if (wdt_fd) { + watchdog_no_cloexec(); + setenv("WDTFD", wdt_fd, 1); + } + execvp(argv[0], argv); +} + +static int nand_set(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__NAND_MAX]; + + if (!msg) + return UBUS_STATUS_INVALID_ARGUMENT; + + blobmsg_parse(nand_policy, __NAND_MAX, tb, blob_data(msg), blob_len(msg)); + if (!tb[NAND_PATH]) + return UBUS_STATUS_INVALID_ARGUMENT; + + procd_spawn_upgraded(blobmsg_get_string(tb[NAND_PATH])); + fprintf(stderr, "Yikees, something went wrong. no /sbin/upgraded ?\n"); + return 0; +} + +static void +procd_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj) +{ + notify = obj->has_subscribers; +} + + +static const struct ubus_method system_methods[] = { + UBUS_METHOD_NOARG("board", system_board), + UBUS_METHOD_NOARG("info", system_info), + UBUS_METHOD_NOARG("upgrade", system_upgrade), + UBUS_METHOD_NOARG("reboot", system_reboot), + UBUS_METHOD("watchdog", watchdog_set, watchdog_policy), + UBUS_METHOD("signal", proc_signal, signal_policy), + + /* must remain at the end as it ia not always loaded */ + UBUS_METHOD("nandupgrade", nand_set, nand_policy), +}; + +static struct ubus_object_type system_object_type = + UBUS_OBJECT_TYPE("system", system_methods); + +static struct ubus_object system_object = { + .name = "system", + .type = &system_object_type, + .methods = system_methods, + .n_methods = ARRAY_SIZE(system_methods), + .subscribe_cb = procd_subscribe_cb, +}; + +void +procd_bcast_event(char *event, struct blob_attr *msg) +{ + int ret; + + if (!notify) + return; + + ret = ubus_notify(_ctx, &system_object, event, msg, -1); + if (ret) + fprintf(stderr, "Failed to notify log: %s\n", ubus_strerror(ret)); +} + +void ubus_init_system(struct ubus_context *ctx) +{ + struct stat s; + int ret; + + if (stat("/sbin/upgraded", &s)) + system_object.n_methods -= 1; + + _ctx = ctx; + ret = ubus_add_object(ctx, &system_object); + if (ret) + ERROR("Failed to add object: %s\n", ubus_strerror(ret)); +} diff --git a/src/3P/procd/trace/preload.c b/src/3P/procd/trace/preload.c new file mode 100644 index 00000000..99dd9067 --- /dev/null +++ b/src/3P/procd/trace/preload.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2015 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. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../preload.h" + +#define ERROR(fmt, ...) do { \ + fprintf(stderr,"perload-jail: "fmt, ## __VA_ARGS__); \ + } while (0) + +static main_t __main__; + +static int __preload_main__(int argc, char **argv, char **envp) +{ + unsetenv("LD_PRELOAD"); + ptrace(PTRACE_TRACEME); + kill(getpid(), SIGSTOP); + + return (*__main__)(argc, argv, envp); +} + +int __libc_start_main(main_t main, + int argc, + char **argv, + ElfW(auxv_t) *auxvec, + __typeof (main) init, + void (*fini) (void), + void (*rtld_fini) (void), + void *stack_end) +{ + start_main_t __start_main__; + + __start_main__ = dlsym(RTLD_NEXT, "__libc_start_main"); + if (!__start_main__) + ERROR("failed to find __libc_start_main %s\n", dlerror()); + + __main__ = main; + + return (*__start_main__)(__preload_main__, argc, argv, auxvec, + init, fini, rtld_fini, stack_end); +} + +void __uClibc_main(main_t main, + int argc, + char **argv, + void (*app_init)(void), + void (*app_fini)(void), + void (*rtld_fini)(void), + void *stack_end attribute_unused) +{ + uClibc_main __start_main__; + + __start_main__ = dlsym(RTLD_NEXT, "__uClibc_main"); + if (!__start_main__) + ERROR("failed to find __uClibc_main %s\n", dlerror()); + + __main__ = main; + + return (*__start_main__)(__preload_main__, argc, argv, + app_init, app_fini, rtld_fini, stack_end); +} diff --git a/src/3P/procd/trace/trace.c b/src/3P/procd/trace/trace.c new file mode 100644 index 00000000..b0005b8a --- /dev/null +++ b/src/3P/procd/trace/trace.c @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2015 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. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../syscall-names.h" + +#define _offsetof(a, b) __builtin_offsetof(a,b) +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +#ifdef __amd64__ +#define reg_syscall_nr _offsetof(struct user, regs.orig_rax) +#elif defined(__i386__) +#define reg_syscall_nr _offsetof(struct user, regs.orig_eax) +#elif defined(__mips) +# ifndef EF_REG2 +# define EF_REG2 8 +# endif +#define reg_syscall_nr (EF_REG2 / 4) +#elif defined(__arm__) +#define reg_syscall_nr _offsetof(struct user, regs.uregs[7]) +#else +#error tracing is not supported on this architecture +#endif + +#define INFO(fmt, ...) do { \ + fprintf(stderr, "utrace: "fmt, ## __VA_ARGS__); \ +} while (0) + +#define ERROR(fmt, ...) do { \ + syslog(LOG_ERR, "utrace: "fmt, ## __VA_ARGS__); \ + fprintf(stderr, "utrace: "fmt, ## __VA_ARGS__); \ +} while (0) + +static struct uloop_process tracer; +static int *syscall_count; +static struct blob_buf b; +static int syscall_max; +static int in_syscall; +static int debug; + +static int max_syscall = ARRAY_SIZE(syscall_names); + +static void set_syscall(const char *name, int val) +{ + int i; + + for (i = 0; i < max_syscall; i++) + if (syscall_names[i] && !strcmp(syscall_names[i], name)) { + syscall_count[i] = val; + return; + } +} + +static void print_syscalls(int policy, const char *json) +{ + void *c; + int i; + + set_syscall("rt_sigaction", 1); + set_syscall("sigreturn", 1); + set_syscall("rt_sigreturn", 1); + set_syscall("exit_group", 1); + set_syscall("exit", 1); + + blob_buf_init(&b, 0); + c = blobmsg_open_array(&b, "whitelist"); + + for (i = 0; i < ARRAY_SIZE(syscall_names); i++) { + if (!syscall_count[i]) + continue; + if (syscall_names[i]) { + if (debug) + printf("syscall %d (%s) was called %d times\n", + i, syscall_names[i], syscall_count[i]); + blobmsg_add_string(&b, NULL, syscall_names[i]); + } else { + ERROR("no name found for syscall(%d)\n", i); + } + } + blobmsg_close_array(&b, c); + blobmsg_add_u32(&b, "policy", policy); + if (json) { + FILE *fp = fopen(json, "w"); + if (fp) { + fprintf(fp, "%s", blobmsg_format_json_indent(b.head, true, 0)); + fclose(fp); + INFO("saving syscall trace to %s\n", json); + } else { + ERROR("failed to open %s\n", json); + } + } else { + printf("%s\n", + blobmsg_format_json_indent(b.head, true, 0)); + } + +} + +static void tracer_cb(struct uloop_process *c, int ret) +{ + if (WIFSTOPPED(ret) && WSTOPSIG(ret) & 0x80) { + if (!in_syscall) { + int syscall = ptrace(PTRACE_PEEKUSER, c->pid, reg_syscall_nr); + + if (syscall < syscall_max) { + syscall_count[syscall]++; + if (debug) + fprintf(stderr, "%s()\n", syscall_names[syscall]); + } else if (debug) { + fprintf(stderr, "syscal(%d)\n", syscall); + } + } + in_syscall = !in_syscall; + } else if (WIFEXITED(ret)) { + uloop_end(); + return; + } + ptrace(PTRACE_SYSCALL, c->pid, 0, 0); + uloop_process_add(&tracer); +} + +int main(int argc, char **argv, char **envp) +{ + char *json = NULL; + int status, ch, policy = EPERM; + pid_t child; + + while ((ch = getopt(argc, argv, "f:p:")) != -1) { + switch (ch) { + case 'f': + json = optarg; + break; + case 'p': + policy = atoi(optarg); + break; + } + } + + argc -= optind; + argv += optind; + + if (!argc) + return -1; + + if (getenv("TRACE_DEBUG")) + debug = 1; + unsetenv("TRACE_DEBUG"); + + child = fork(); + + if (child == 0) { + char **_argv = calloc(argc + 1, sizeof(char *)); + char **_envp; + char preload[] = "LD_PRELOAD=/lib/libpreload-trace.so"; + int envc = 1; + int ret; + + memcpy(_argv, argv, argc * sizeof(char *)); + + while (envp[envc++]) + ; + + _envp = calloc(envc, sizeof(char *)); + memcpy(&_envp[1], _envp, envc * sizeof(char *)); + *envp = preload; + + ret = execve(_argv[0], _argv, envp); + ERROR("failed to exec %s: %s\n", _argv[0], strerror(errno)); + return ret; + } + + if (child < 0) + return -1; + + syscall_max = ARRAY_SIZE(syscall_names); + syscall_count = calloc(syscall_max, sizeof(int)); + waitpid(child, &status, 0); + if (!WIFSTOPPED(status)) { + ERROR("failed to start %s\n", *argv); + return -1; + } + + ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD); + ptrace(PTRACE_SYSCALL, child, 0, 0); + + uloop_init(); + tracer.pid = child; + tracer.cb = tracer_cb; + uloop_process_add(&tracer); + uloop_run(); + uloop_done(); + + if (!json) + if (asprintf(&json, "/tmp/%s.%u.json", basename(*argv), child) < 0) + ERROR("failed to allocate output path: %s\n", strerror(errno)); + + print_syscalls(policy, json); + + return 0; +} diff --git a/src/3P/procd/ubus.c b/src/3P/procd/ubus.c new file mode 100644 index 00000000..8d521acf --- /dev/null +++ b/src/3P/procd/ubus.c @@ -0,0 +1,87 @@ +/* + * 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" + +char *ubus_socket = NULL; +static struct ubus_context *ctx; +static struct uloop_timeout ubus_timer; +static int timeout; + +static void reset_timeout(void) +{ + timeout = 50; +} + +static void timeout_retry(void) +{ + uloop_timeout_set(&ubus_timer, timeout); + timeout *= 2; + if (timeout > 1000) + timeout = 1000; +} + +static void +ubus_reconnect_cb(struct uloop_timeout *timeout) +{ + if (!ubus_reconnect(ctx, ubus_socket)) { + ubus_add_uloop(ctx); + return; + } + + timeout_retry(); +} + +static void +ubus_disconnect_cb(struct ubus_context *ctx) +{ + ubus_timer.cb = ubus_reconnect_cb; + reset_timeout(); + timeout_retry(); +} + +static void +ubus_connect_cb(struct uloop_timeout *timeout) +{ + ctx = ubus_connect(ubus_socket); + + if (!ctx) { + DEBUG(4, "Connection to ubus failed\n"); + timeout_retry(); + return; + } + + ctx->connection_lost = ubus_disconnect_cb; + ubus_init_service(ctx); + ubus_init_system(ctx); + watch_ubus(ctx); + + DEBUG(2, "Connected to ubus, id=%08x\n", ctx->local_id); + reset_timeout(); + ubus_add_uloop(ctx); + procd_state_ubus_connect(); +} + +void +procd_connect_ubus(void) +{ + ubus_timer.cb = ubus_connect_cb; + reset_timeout(); + timeout_retry(); +} diff --git a/src/3P/procd/upgraded/CMakeLists.txt b/src/3P/procd/upgraded/CMakeLists.txt new file mode 100644 index 00000000..093dba2f --- /dev/null +++ b/src/3P/procd/upgraded/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 2.6) + +PROJECT(upgraded C) +ADD_DEFINITIONS(-Os -ggdb -Wall -Werror --std=gnu99 -Wmissing-declarations) +set(CMAKE_EXE_LINKER_FLAGS "-static -fPIC") +set(CMAKE_FIND_LIBRARY_SUFFIXES .a) +set(CMAKE_EXE_LINK_DYNAMIC_C_FLAGS) +set(CMAKE_EXE_LINK_DYNAMIC_CXX_FLAGS) +set(CMAKE_SHARED_LIBRARY_C_FLAGS) +set(CMAKE_SHARED_LIBRARY_CXX_FLAGS) +set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS) +set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS) +ADD_EXECUTABLE(upgraded upgraded.c ../watchdog.c) +TARGET_LINK_LIBRARIES(upgraded ubox rt -lc -lgcc_pic) +INSTALL(TARGETS upgraded + RUNTIME DESTINATION sbin +) diff --git a/src/3P/procd/upgraded/upgraded.c b/src/3P/procd/upgraded/upgraded.c new file mode 100644 index 00000000..d7433e76 --- /dev/null +++ b/src/3P/procd/upgraded/upgraded.c @@ -0,0 +1,80 @@ +/* + * 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 "../watchdog.h" + +static struct uloop_process upgrade_proc; +unsigned int debug = 2; + +static void upgrade_proc_cb(struct uloop_process *proc, int ret) +{ + if (ret) + fprintf(stderr, "sysupgrade aborted with return code: %d\n", ret); + uloop_end(); +} + +static void sysupgarde(char *folder) +{ + char *args[] = { "/sbin/sysupgrade", "nand", NULL, NULL }; + + args[2] = folder; + upgrade_proc.cb = upgrade_proc_cb; + upgrade_proc.pid = fork(); + if (!upgrade_proc.pid) { + execvp(args[0], args); + fprintf(stderr, "Failed to fork sysupgrade\n"); + exit(-1); + } + if (upgrade_proc.pid <= 0) { + fprintf(stderr, "Failed to start sysupgarde\n"); + uloop_end(); + } +} + +int main(int argc, char **argv) +{ + pid_t p = getpid(); + + if (p != 1) { + fprintf(stderr, "this tool needs to run as pid 1\n"); + return -1; + } + if (chdir("/tmp") == -1) { + fprintf(stderr, "failed to chdir to /tmp: %s\n", strerror(errno)); + return -1; + } + if (argc != 2) { + fprintf(stderr, "sysupgrade stage 2 failed, no folder specified\n"); + return -1; + } + + uloop_init(); + watchdog_init(0); + sysupgarde(argv[1]); + uloop_run(); + + reboot(RB_AUTOBOOT); + + return 0; +} diff --git a/src/3P/procd/utils/askfirst.c b/src/3P/procd/utils/askfirst.c new file mode 100644 index 00000000..69de76fa --- /dev/null +++ b/src/3P/procd/utils/askfirst.c @@ -0,0 +1,43 @@ +/* + * 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 + +int main(int argc, char **argv) +{ + int c; + + printf("Please press Enter to activate this console.\n"); + do { + c = getchar(); + if (c == EOF) + return -1; + } + while (c != 0xA); + + if (argc < 2) { + printf("%s needs to be called with at least 1 parameter\n", argv[0]); + return -1; + } + + execvp(argv[1], &argv[1]); + printf("Failed to execute %s\n", argv[1]); + + return -1; +} diff --git a/src/3P/procd/utils/utils.c b/src/3P/procd/utils/utils.c new file mode 100644 index 00000000..eef1bdbb --- /dev/null +++ b/src/3P/procd/utils/utils.c @@ -0,0 +1,209 @@ +/* + * 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. + */ + +//AWOX #define _GNU_SOURCE +#include +#include +#include "utils.h" +#include +#include +#include +#include +#include +#include +#include + +#include "../log.h" + +void +__blobmsg_list_init(struct blobmsg_list *list, int offset, int len, blobmsg_list_cmp cmp) +{ + avl_init(&list->avl, avl_strcmp, false, NULL); + list->node_offset = offset; + list->node_len = len; + list->cmp = cmp; +} + +int +blobmsg_list_fill(struct blobmsg_list *list, void *data, int len, bool array) +{ + struct avl_tree *tree = &list->avl; + struct blobmsg_list_node *node; + struct blob_attr *cur; + void *ptr; + int count = 0; + int rem = len; + + __blob_for_each_attr(cur, data, rem) { + if (!blobmsg_check_attr(cur, !array)) + continue; + + ptr = calloc(1, list->node_len); + if (!ptr) + return -1; + + node = (void *) ((char *)ptr + list->node_offset); + if (array) + node->avl.key = blobmsg_data(cur); + else + node->avl.key = blobmsg_name(cur); + node->data = cur; + if (avl_insert(tree, &node->avl)) { + free(ptr); + continue; + } + + count++; + } + + return count; +} + +void +blobmsg_list_move(struct blobmsg_list *list, struct blobmsg_list *src) +{ + struct blobmsg_list_node *node, *tmp; + void *ptr; + + avl_remove_all_elements(&src->avl, node, avl, tmp) { + if (avl_insert(&list->avl, &node->avl)) { + ptr = ((char *) node - list->node_offset); + free(ptr); + } + } +} + +void +blobmsg_list_free(struct blobmsg_list *list) +{ + struct blobmsg_list_node *node, *tmp; + void *ptr; + + avl_remove_all_elements(&list->avl, node, avl, tmp) { + ptr = ((char *) node - list->node_offset); + free(ptr); + } +} + +bool +blobmsg_list_equal(struct blobmsg_list *l1, struct blobmsg_list *l2) +{ + struct blobmsg_list_node *n1, *n2; + int count = l1->avl.count; + + if (count != l2->avl.count) + return false; + + n1 = avl_first_element(&l1->avl, n1, avl); + n2 = avl_first_element(&l2->avl, n2, avl); + + while (count-- > 0) { + int len; + + len = blob_len(n1->data); + if (len != blob_len(n2->data)) + return false; + + if (memcmp(n1->data, n2->data, len) != 0) + return false; + + if (l1->cmp && !l1->cmp(n1, n2)) + return false; + + if (!count) + break; + + n1 = avl_next_element(n1, avl); + n2 = avl_next_element(n2, avl); + } + + return true; +} + +char* get_cmdline_val(const char* name, char* out, int len) +{ + char line[CMDLINE_SIZE + 1], *c, *sptr; + int fd = open("/proc/cmdline", O_RDONLY); + ssize_t r = read(fd, line, sizeof(line) - 1); + close(fd); + + if (r <= 0) + return NULL; + + line[r] = 0; + + for (c = strtok_r(line, " \t\n", &sptr); c; + c = strtok_r(NULL, " \t\n", &sptr)) { + char *sep = strchr(c, '='); + ssize_t klen = sep - c; + if (klen < 0 || strncmp(name, c, klen) || name[klen] != 0) + continue; + + strncpy(out, &sep[1], len); + out[len-1] = 0; + return out; + } + + return NULL; +} + +int patch_fd(const char *device, int fd, int flags) +{ + int dfd, nfd; + + if (device == NULL) + device = "/dev/null"; + + if (*device != '/') { + dfd = open("/dev", O_PATH|O_DIRECTORY); + + if (dfd < 0) + return -1; + + nfd = openat(dfd, device, flags); + + close(dfd); + } else { + nfd = open(device, flags); + } + + if (nfd < 0 && strcmp(device, "/dev/null")) + nfd = open("/dev/null", flags); + + if (nfd < 0) + return -1; + + fd = dup2(nfd, fd); + + if (nfd > STDERR_FILENO) + close(nfd); + + return (fd < 0) ? -1 : 0; +} + +int patch_stdio(const char *device) +{ + int fd, rv = 0; + const char *fdname[3] = { "stdin", "stdout", "stderr" }; + + for (fd = STDIN_FILENO; fd <= STDERR_FILENO; fd++) { + if (patch_fd(device, fd, fd ? O_WRONLY : O_RDONLY)) { + ERROR("Failed to redirect %s to %s: %d (%s)\n", + fdname[fd], device, errno, strerror(errno)); + rv = -1; + } + } + + return rv; +} diff --git a/src/3P/procd/utils/utils.h b/src/3P/procd/utils/utils.h new file mode 100644 index 00000000..908c3145 --- /dev/null +++ b/src/3P/procd/utils/utils.h @@ -0,0 +1,59 @@ +/* + * 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_UTILS_H +#define __PROCD_UTILS_H + +#include +#include +#include + +#define CMDLINE_SIZE 2048 + +struct blobmsg_list_node { + struct avl_node avl; + struct blob_attr *data; +}; + +typedef bool (*blobmsg_list_cmp)(struct blobmsg_list_node *l1, struct blobmsg_list_node *l2); +typedef void (*blobmsg_update_cb)(struct blobmsg_list_node *n); + +struct blobmsg_list { + struct avl_tree avl; + int node_offset; + int node_len; + + blobmsg_list_cmp cmp; +}; + +#define blobmsg_list_simple_init(list) \ + __blobmsg_list_init(list, 0, sizeof(struct blobmsg_list_node), NULL) + +#define blobmsg_list_init(list, type, field, cmp) \ + __blobmsg_list_init(list, offsetof(type, field), sizeof(type), cmp) + +#define blobmsg_list_for_each(list, element) \ + avl_for_each_element(&(list)->avl, element, avl) + +void __blobmsg_list_init(struct blobmsg_list *list, int offset, int len, blobmsg_list_cmp cmp); +int blobmsg_list_fill(struct blobmsg_list *list, void *data, int len, bool array); +void blobmsg_list_free(struct blobmsg_list *list); +bool blobmsg_list_equal(struct blobmsg_list *l1, struct blobmsg_list *l2); +void blobmsg_list_move(struct blobmsg_list *list, struct blobmsg_list *src); +char* get_cmdline_val(const char* name, char* out, int len); + +int patch_fd(const char *device, int fd, int flags); +int patch_stdio(const char *device); + +#endif diff --git a/src/3P/procd/watchdog.c b/src/3P/procd/watchdog.c new file mode 100644 index 00000000..592ae7e7 --- /dev/null +++ b/src/3P/procd/watchdog.c @@ -0,0 +1,135 @@ +/* + * 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 "procd.h" +#include "watchdog.h" + +#define WDT_PATH "/dev/watchdog" + +static struct uloop_timeout wdt_timeout; +static int wdt_fd = -1; +static int wdt_frequency = 5; + +void watchdog_ping(void) +{ + DEBUG(4, "Ping\n"); + if (wdt_fd >= 0 && write(wdt_fd, "X", 1) < 0) + ERROR("WDT failed to write: %s\n", strerror(errno)); +} + +static void watchdog_timeout_cb(struct uloop_timeout *t) +{ + watchdog_ping(); + uloop_timeout_set(t, wdt_frequency * 1000); +} + +void watchdog_set_stopped(bool val) +{ + if (val) + uloop_timeout_cancel(&wdt_timeout); + else + watchdog_timeout_cb(&wdt_timeout); +} + +bool watchdog_get_stopped(void) +{ + return !wdt_timeout.pending; +} + +int watchdog_timeout(int timeout) +{ + if (wdt_fd < 0) + return 0; + + if (timeout) { + DEBUG(4, "Set watchdog timeout: %ds\n", timeout); + ioctl(wdt_fd, WDIOC_SETTIMEOUT, &timeout); + } + ioctl(wdt_fd, WDIOC_GETTIMEOUT, &timeout); + + return timeout; +} + +int watchdog_frequency(int frequency) +{ + if (wdt_fd < 0) + return 0; + + if (frequency) { + DEBUG(4, "Set watchdog frequency: %ds\n", frequency); + wdt_frequency = frequency; + } + + return wdt_frequency; +} + +char* watchdog_fd(void) +{ + static char fd_buf[3]; + + if (wdt_fd < 0) + return NULL; + snprintf(fd_buf, sizeof(fd_buf), "%d", wdt_fd); + + return fd_buf; +} + +void watchdog_init(int preinit) +{ + char *env = getenv("WDTFD"); + + if (wdt_fd >= 0) + return; + + wdt_timeout.cb = watchdog_timeout_cb; + if (env) { + DEBUG(2, "Watchdog handover: fd=%s\n", env); + wdt_fd = atoi(env); + unsetenv("WDTFD"); + } else { + wdt_fd = open("/dev/watchdog", O_WRONLY); + } + + if (wdt_fd < 0) + return; + + if (!preinit) + fcntl(wdt_fd, F_SETFD, fcntl(wdt_fd, F_GETFD) | FD_CLOEXEC); + + LOG("- watchdog -\n"); + watchdog_timeout(30); + watchdog_timeout_cb(&wdt_timeout); + + DEBUG(4, "Opened watchdog with timeout %ds\n", watchdog_timeout(0)); +} + + +void watchdog_no_cloexec(void) +{ + if (wdt_fd < 0) + return; + + fcntl(wdt_fd, F_SETFD, fcntl(wdt_fd, F_GETFD) & ~FD_CLOEXEC); +} diff --git a/src/3P/procd/watchdog.h b/src/3P/procd/watchdog.h new file mode 100644 index 00000000..e5c696a3 --- /dev/null +++ b/src/3P/procd/watchdog.h @@ -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. + */ + +#ifndef __PROCD_WATCHDOG_H +#define __PROCD_WATCHDOG_H + +#ifndef DISABLE_INIT +void watchdog_init(int preinit); +char* watchdog_fd(void); +int watchdog_timeout(int timeout); +int watchdog_frequency(int frequency); +void watchdog_set_stopped(bool val); +bool watchdog_get_stopped(void); +void watchdog_no_cloexec(void); +void watchdog_ping(void); +#else +static inline void watchdog_init(int preinit) +{ +} + +static inline char* watchdog_fd(void) +{ + return ""; +} + +static inline int watchdog_timeout(int timeout) +{ + return 0; +} + +static inline int watchdog_frequency(int frequency) +{ + return 0; +} + +static inline void watchdog_set_stopped(bool val) +{ +} + +static inline bool watchdog_get_stopped(void) +{ + return true; +} + +static inline void watchdog_no_cloexec(void) +{ +} + +static inline void watchdog_ping(void) +{ +} + +#endif + +#endif