This will be a utility to check service configuration for errors/lint.
dinit_objects = dinit.o load-service.o service.o proc-service.o baseproc-service.o control.o dinit-log.o \
dinit-main.o run-child-proc.o
-objects = $(dinit_objects) dinitctl.o shutdown.o
+objects = $(dinit_objects) dinitctl.o dinitcheck.o shutdown.o
-all: dinit dinitctl $(SHUTDOWN)
+all: dinit dinitctl dinitcheck $(SHUTDOWN)
# Look for a suitable build config file and use it.
../mconfig:
dinitctl: dinitctl.o
$(CXX) -o dinitctl dinitctl.o $(LDFLAGS)
+dinitcheck: dinitcheck.o
+ $(CXX) -o dinitcheck dinitcheck.o $(LDFLAGS)
+
shutdown: shutdown.o
$(CXX) -o shutdown shutdown.o $(LDFLAGS)
--- /dev/null
+#include <iostream>
+#include <fstream>
+#include <cstring>
+#include <string>
+#include <vector>
+#include <list>
+#include <map>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <pwd.h>
+#include <dirent.h>
+
+#include "dinit-util.h"
+#include "service-constants.h"
+#include "load-service.h"
+
+// dinitcheck: utility to check Dinit configuration for correctness/lint
+
+using string = std::string;
+using string_iterator = std::string::iterator;
+
+static const char *user_home_path = nullptr;
+
+// Get user home (and set user_home_path). (The return may become invalid after
+// changing the evironment (HOME variable) or using the getpwuid() function).
+static const char * get_user_home()
+{
+ if (user_home_path == nullptr) {
+ user_home_path = getenv("HOME");
+ if (user_home_path == nullptr) {
+ struct passwd * pwuid_p = getpwuid(getuid());
+ if (pwuid_p != nullptr) {
+ user_home_path = pwuid_p->pw_dir;
+ }
+ }
+ }
+ return user_home_path;
+}
+
+class prelim_dep
+{
+ std::string name;
+ dependency_type dep_type;
+
+ public:
+ prelim_dep(std::string &name_p, dependency_type dep_type_p)
+ : name(name_p), dep_type(dep_type_p) { }
+ prelim_dep(std::string &&name_p, dependency_type dep_type_p)
+ : name(std::move(name_p)), dep_type(dep_type_p) { }
+};
+
+class service_record
+{
+public:
+ service_record(std::string name, std::list<prelim_dep> dependencies_p) : dependencies(dependencies_p) {}
+
+ std::string name;
+ bool finished_loading = false; // flag used to detect cyclic dependencies
+ std::list<prelim_dep> dependencies;
+};
+
+using service_set_t = std::map<std::string, service_record *>;
+
+service_record *load_service(service_set_t &services, const std::string &name,
+ const std::vector<dinit_load::dir_entry> &service_dirs);
+
+int main(int argc, char **argv)
+{
+ using namespace std;
+
+ bool add_all_service_dirs = false;
+ bool for_system = false;
+ const char * service_dir = nullptr;
+ bool service_dir_dynamic = false; // service_dir dynamically allocated?
+
+ std::vector<std::string> services_to_check;
+
+ // Figure out service dirs
+ /* service directory name */
+ if (service_dir == nullptr && ! for_system) {
+ const char * userhome = get_user_home();
+ if (userhome != nullptr) {
+ const char * user_home = get_user_home();
+ size_t user_home_len = strlen(user_home);
+ size_t dinit_d_len = strlen("/dinit.d");
+ size_t full_len = user_home_len + dinit_d_len + 1;
+ char *service_dir_w = new char[full_len];
+ std::memcpy(service_dir_w, user_home, user_home_len);
+ std::memcpy(service_dir_w + user_home_len, "/dinit.d", dinit_d_len);
+ service_dir_w[full_len - 1] = 0;
+
+ service_dir = service_dir_w;
+ service_dir_dynamic = true;
+ }
+ }
+
+ if (service_dir == nullptr) {
+ service_dir = "/etc/dinit.d";
+ add_all_service_dirs = true;
+ }
+
+ std::vector<dinit_load::dir_entry> service_dirs;
+
+ service_dirs.emplace_back(service_dir, service_dir_dynamic);
+ if (add_all_service_dirs) {
+ service_dirs.emplace_back("/usr/local/lib/dinit.d", false);
+ service_dirs.emplace_back("/lib/dinit.d", false);
+ }
+
+ // Temporary, for testing:
+ services_to_check.push_back("boot");
+
+ // Load named service(s)
+ std::map<std::string, service_record *> service_set;
+
+ // - load the service, store dependencies as strings
+ // - recurse
+
+ // additional: check chain-to, other lint
+
+ for (const auto &name : services_to_check) {
+ try {
+ load_service(service_set, name, service_dirs);
+ }
+ catch (service_load_exc &exc) {
+ std::cerr << "Unable to load service '" << name << "': " << exc.exc_description << "\n";
+ }
+ }
+
+ return 0;
+}
+
+static void report_error(dinit_load::setting_exception &exc, const std::string &service_name, const char *setting_name)
+{
+ std::cerr << "Service '" << service_name << "', " << setting_name << ": " << exc.get_info() << "\n";
+}
+
+static void report_error(std::system_error &exc, const std::string &service_name)
+{
+ std::cerr << "Service '" << service_name << "', error reading service description: " << exc.what() << "\n";
+}
+
+static void report_dir_error(const char *service_name, const std::string &dirpath)
+{
+ std::cerr << "Service '" << service_name << "', error reading dependencies from directory " << dirpath
+ << ": " << strerror(errno) << "\n";
+}
+
+// Process a dependency directory - filenames contained within correspond to service names which
+// are loaded and added as a dependency of the given type. Expected use is with a directory
+// containing symbolic links to other service descriptions, but this isn't required.
+// Failure to read the directory contents, or to find a service listed within, is not considered
+// a fatal error.
+static void process_dep_dir(const char *servicename,
+ const string &service_filename,
+ std::list<prelim_dep> &deplist, const std::string &depdirpath,
+ dependency_type dep_type)
+{
+ std::string depdir_fname = combine_paths(parent_path(service_filename), depdirpath.c_str());
+
+ DIR *depdir = opendir(depdir_fname.c_str());
+ if (depdir == nullptr) {
+ report_dir_error(servicename, depdirpath);
+ return;
+ }
+
+ errno = 0;
+ dirent * dent = readdir(depdir);
+ while (dent != nullptr) {
+ char * name = dent->d_name;
+ if (name[0] != '.') {
+ deplist.emplace_back(name, dep_type);
+ }
+ dent = readdir(depdir);
+ }
+
+ if (errno != 0) {
+ report_dir_error(servicename, depdirpath);
+ }
+
+ closedir(depdir);
+}
+
+service_record *load_service(service_set_t &services, const std::string &name,
+ const std::vector<dinit_load::dir_entry> &service_dirs)
+{
+ using namespace std;
+ using namespace dinit_load;
+
+ auto found = services.find(name);
+ if (found != services.end()) {
+ return found->second;
+ }
+
+ string service_filename;
+ ifstream service_file;
+
+ // Couldn't find one. Have to load it.
+ for (auto &service_dir : service_dirs) {
+ service_filename = service_dir.get_dir();
+ if (*(service_filename.rbegin()) != '/') {
+ service_filename += '/';
+ }
+ service_filename += name;
+
+ service_file.open(service_filename.c_str(), ios::in);
+ if (service_file) break;
+ }
+
+ if (! service_file) {
+ throw service_not_found(string(name));
+ }
+
+ string command;
+ list<pair<unsigned,unsigned>> command_offsets;
+ string stop_command;
+ list<pair<unsigned,unsigned>> stop_command_offsets;
+ string working_dir;
+ string pid_file;
+ string env_file;
+
+ bool do_sub_vars = false;
+
+ service_type_t service_type = service_type_t::PROCESS;
+ std::list<prelim_dep> depends;
+ string logfile;
+ service_flags_t onstart_flags;
+ int term_signal = -1; // additional termination signal
+ bool auto_restart = false;
+ bool smooth_recovery = false;
+ string socket_path;
+ int socket_perms = 0666;
+ // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
+ // invalid value, so it's safe to assume that we can do the same:
+ uid_t socket_uid = -1;
+ gid_t socket_gid = -1;
+ // Restart limit interval / count; default is 10 seconds, 3 restarts:
+ timespec restart_interval = { .tv_sec = 10, .tv_nsec = 0 };
+ int max_restarts = 3;
+ timespec restart_delay = { .tv_sec = 0, .tv_nsec = 200000000 };
+ timespec stop_timeout = { .tv_sec = 10, .tv_nsec = 0 };
+ timespec start_timeout = { .tv_sec = 60, .tv_nsec = 0 };
+ std::vector<service_rlimits> rlimits;
+
+ int readiness_fd = -1; // readiness fd in service process
+ std::string readiness_var; // environment var to hold readiness fd
+
+ uid_t run_as_uid = -1;
+ gid_t run_as_gid = -1;
+
+ string chain_to_name;
+
+ #if USE_UTMPX
+ char inittab_id[sizeof(utmpx().ut_id)] = {0};
+ char inittab_line[sizeof(utmpx().ut_line)] = {0};
+ #endif
+
+ string line;
+ service_file.exceptions(ios::badbit);
+
+ try {
+ process_service_file(name, service_file,
+ [&](string &line, string &setting, string_iterator &i, string_iterator &end) -> void {
+ try {
+ if (setting == "command") {
+ command = read_setting_value(i, end, &command_offsets);
+ }
+ else if (setting == "working-dir") {
+ working_dir = read_setting_value(i, end, nullptr);
+ }
+ else if (setting == "env-file") {
+ env_file = read_setting_value(i, end, nullptr);
+ }
+ else if (setting == "socket-listen") {
+ socket_path = read_setting_value(i, end, nullptr);
+ }
+ else if (setting == "socket-permissions") {
+ string sock_perm_str = read_setting_value(i, end, nullptr);
+ std::size_t ind = 0;
+ try {
+ socket_perms = std::stoi(sock_perm_str, &ind, 8);
+ if (ind != sock_perm_str.length()) {
+ throw std::logic_error("");
+ }
+ }
+ catch (std::logic_error &exc) {
+ throw service_description_exc(name, "socket-permissions: Badly-formed or "
+ "out-of-range numeric value");
+ }
+ }
+ else if (setting == "socket-uid") {
+ string sock_uid_s = read_setting_value(i, end, nullptr);
+ socket_uid = parse_uid_param(sock_uid_s, name, &socket_gid);
+ }
+ else if (setting == "socket-gid") {
+ string sock_gid_s = read_setting_value(i, end, nullptr);
+ socket_gid = parse_gid_param(sock_gid_s, name);
+ }
+ else if (setting == "stop-command") {
+ stop_command = read_setting_value(i, end, &stop_command_offsets);
+ }
+ else if (setting == "pid-file") {
+ pid_file = read_setting_value(i, end);
+ }
+ else if (setting == "depends-on") {
+ string dependency_name = read_setting_value(i, end);
+ depends.emplace_back(std::move(dependency_name), dependency_type::REGULAR);
+ }
+ else if (setting == "depends-ms") {
+ string dependency_name = read_setting_value(i, end);
+ depends.emplace_back(dependency_name, dependency_type::MILESTONE);
+ }
+ else if (setting == "waits-for") {
+ string dependency_name = read_setting_value(i, end);
+ depends.emplace_back(dependency_name, dependency_type::WAITS_FOR);
+ }
+ else if (setting == "waits-for.d") {
+ string waitsford = read_setting_value(i, end);
+ process_dep_dir(name.c_str(), service_filename, depends, waitsford,
+ dependency_type::WAITS_FOR);
+ }
+ else if (setting == "logfile") {
+ logfile = read_setting_value(i, end);
+ }
+ else if (setting == "restart") {
+ string restart = read_setting_value(i, end);
+ auto_restart = (restart == "yes" || restart == "true");
+ }
+ else if (setting == "smooth-recovery") {
+ string recovery = read_setting_value(i, end);
+ smooth_recovery = (recovery == "yes" || recovery == "true");
+ }
+ else if (setting == "type") {
+ string type_str = read_setting_value(i, end);
+ if (type_str == "scripted") {
+ service_type = service_type_t::SCRIPTED;
+ }
+ else if (type_str == "process") {
+ service_type = service_type_t::PROCESS;
+ }
+ else if (type_str == "bgprocess") {
+ service_type = service_type_t::BGPROCESS;
+ }
+ else if (type_str == "internal") {
+ service_type = service_type_t::INTERNAL;
+ }
+ else {
+ throw service_description_exc(name, "Service type must be one of: \"scripted\","
+ " \"process\", \"bgprocess\" or \"internal\"");
+ }
+ }
+ else if (setting == "options") {
+ std::list<std::pair<unsigned,unsigned>> indices;
+ string onstart_cmds = read_setting_value(i, end, &indices);
+ for (auto indexpair : indices) {
+ string option_txt = onstart_cmds.substr(indexpair.first,
+ indexpair.second - indexpair.first);
+ if (option_txt == "starts-rwfs") {
+ onstart_flags.rw_ready = true;
+ }
+ else if (option_txt == "starts-log") {
+ onstart_flags.log_ready = true;
+ }
+ else if (option_txt == "no-sigterm") {
+ onstart_flags.no_sigterm = true;
+ }
+ else if (option_txt == "runs-on-console") {
+ onstart_flags.runs_on_console = true;
+ // A service that runs on the console necessarily starts on console:
+ onstart_flags.starts_on_console = true;
+ onstart_flags.shares_console = false;
+ }
+ else if (option_txt == "starts-on-console") {
+ onstart_flags.starts_on_console = true;
+ onstart_flags.shares_console = false;
+ }
+ else if (option_txt == "shares-console") {
+ onstart_flags.shares_console = true;
+ onstart_flags.runs_on_console = false;
+ onstart_flags.starts_on_console = false;
+ }
+ else if (option_txt == "pass-cs-fd") {
+ onstart_flags.pass_cs_fd = true;
+ }
+ else if (option_txt == "start-interruptible") {
+ onstart_flags.start_interruptible = true;
+ }
+ else if (option_txt == "skippable") {
+ onstart_flags.skippable = true;
+ }
+ else if (option_txt == "signal-process-only") {
+ onstart_flags.signal_process_only = true;
+ }
+ else {
+ throw service_description_exc(name, "Unknown option: " + option_txt);
+ }
+ }
+ }
+ else if (setting == "load-options") {
+ std::list<std::pair<unsigned,unsigned>> indices;
+ string load_opts = read_setting_value(i, end, &indices);
+ for (auto indexpair : indices) {
+ string option_txt = load_opts.substr(indexpair.first,
+ indexpair.second - indexpair.first);
+ if (option_txt == "sub-vars") {
+ // substitute environment variables in command line
+ do_sub_vars = true;
+ }
+ else if (option_txt == "no-sub-vars") {
+ do_sub_vars = false;
+ }
+ else {
+ throw service_description_exc(name, "Unknown load option: " + option_txt);
+ }
+ }
+ }
+ else if (setting == "term-signal" || setting == "termsignal") {
+ // Note: "termsignal" supported for legacy reasons.
+ string signame = read_setting_value(i, end, nullptr);
+ int signo = signal_name_to_number(signame);
+ if (signo == -1) {
+ throw service_description_exc(name, "Unknown/unsupported termination signal: "
+ + signame);
+ }
+ else {
+ term_signal = signo;
+ }
+ }
+ else if (setting == "restart-limit-interval") {
+ string interval_str = read_setting_value(i, end, nullptr);
+ parse_timespec(interval_str, name, "restart-limit-interval", restart_interval);
+ }
+ else if (setting == "restart-delay") {
+ string rsdelay_str = read_setting_value(i, end, nullptr);
+ parse_timespec(rsdelay_str, name, "restart-delay", restart_delay);
+ }
+ else if (setting == "restart-limit-count") {
+ string limit_str = read_setting_value(i, end, nullptr);
+ max_restarts = parse_unum_param(limit_str, name, std::numeric_limits<int>::max());
+ }
+ else if (setting == "stop-timeout") {
+ string stoptimeout_str = read_setting_value(i, end, nullptr);
+ parse_timespec(stoptimeout_str, name, "stop-timeout", stop_timeout);
+ }
+ else if (setting == "start-timeout") {
+ string starttimeout_str = read_setting_value(i, end, nullptr);
+ parse_timespec(starttimeout_str, name, "start-timeout", start_timeout);
+ }
+ else if (setting == "run-as") {
+ string run_as_str = read_setting_value(i, end, nullptr);
+ run_as_uid = parse_uid_param(run_as_str, name, &run_as_gid);
+ }
+ else if (setting == "chain-to") {
+ chain_to_name = read_setting_value(i, end, nullptr);
+ }
+ else if (setting == "ready-notification") {
+ string notify_setting = read_setting_value(i, end, nullptr);
+ if (starts_with(notify_setting, "pipefd:")) {
+ readiness_fd = parse_unum_param(notify_setting.substr(7 /* len 'pipefd:' */),
+ name, std::numeric_limits<int>::max());
+ }
+ else if (starts_with(notify_setting, "pipevar:")) {
+ readiness_var = notify_setting.substr(8 /* len 'pipevar:' */);
+ if (readiness_var.empty()) {
+ throw service_description_exc(name, "Invalid pipevar variable name "
+ "in ready-notification");
+ }
+ }
+ else {
+ throw service_description_exc(name, "Unknown ready-notification setting: "
+ + notify_setting);
+ }
+ }
+ else if (setting == "inittab-id") {
+ string inittab_setting = read_setting_value(i, end, nullptr);
+ #if USE_UTMPX
+ if (inittab_setting.length() > sizeof(inittab_id)) {
+ throw service_description_exc(name, "inittab-id setting is too long");
+ }
+ strncpy(inittab_id, inittab_setting.c_str(), sizeof(inittab_id));
+ #endif
+ }
+ else if (setting == "inittab-line") {
+ string inittab_setting = read_setting_value(i, end, nullptr);
+ #if USE_UTMPX
+ if (inittab_setting.length() > sizeof(inittab_line)) {
+ throw service_description_exc(name, "inittab-line setting is too long");
+ }
+ strncpy(inittab_line, inittab_setting.c_str(), sizeof(inittab_line));
+ #endif
+ }
+ else if (setting == "rlimit-nofile") {
+ string nofile_setting = read_setting_value(i, end, nullptr);
+ service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_NOFILE);
+ parse_rlimit(line, name, "rlimit-nofile", nofile_limits);
+ }
+ else if (setting == "rlimit-core") {
+ string nofile_setting = read_setting_value(i, end, nullptr);
+ service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_CORE);
+ parse_rlimit(line, name, "rlimit-core", nofile_limits);
+ }
+ else if (setting == "rlimit-data") {
+ string nofile_setting = read_setting_value(i, end, nullptr);
+ service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_DATA);
+ parse_rlimit(line, name, "rlimit-data", nofile_limits);
+ }
+ else if (setting == "rlimit-addrspace") {
+ #if defined(RLIMIT_AS)
+ string nofile_setting = read_setting_value(i, end, nullptr);
+ service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_AS);
+ parse_rlimit(line, name, "rlimit-addrspace", nofile_limits);
+ #endif
+ }
+ else {
+ throw service_description_exc(name, "Unknown setting: " + setting);
+ }
+ }
+ catch (setting_exception &exc) {
+ report_error(exc, name, setting.c_str());
+ }
+ });
+ }
+ catch (std::system_error &sys_err)
+ {
+ report_error(sys_err, name);
+ return nullptr;
+ }
+
+ return new service_record(name, depends);
+}
return basen;
}
+// Check if one string starts with another
+inline bool starts_with(std::string s, const char *prefix)
+{
+ const char * sp = s.c_str();
+ while (*sp != 0 && *prefix != 0) {
+ if (*sp != *prefix) return false;
+ sp++; prefix++;
+ }
+ return *prefix == 0;
+}
+
#endif
#include <iostream>
#include <list>
+#include <limits>
+#include <csignal>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <grp.h>
+#include <pwd.h>
+
+struct service_flags_t
+{
+ // on-start flags:
+ bool rw_ready : 1; // file system should be writable once this service starts
+ bool log_ready : 1; // syslog should be available once this service starts
+
+ // Other service options flags:
+ bool no_sigterm : 1; // do not send SIGTERM
+ bool runs_on_console : 1; // run "in the foreground"
+ bool starts_on_console : 1; // starts in the foreground
+ bool shares_console : 1; // run on console, but not exclusively
+ bool pass_cs_fd : 1; // pass this service a control socket connection via fd
+ bool start_interruptible : 1; // the startup of this service process is ok to interrupt with SIGINT
+ bool skippable : 1; // if interrupted the service is skipped (scripted services)
+ bool signal_process_only : 1; // signal the session process, not the whole group
+
+ service_flags_t() noexcept : rw_ready(false), log_ready(false), no_sigterm(false),
+ runs_on_console(false), starts_on_console(false), shares_console(false),
+ pass_cs_fd(false), start_interruptible(false), skippable(false), signal_process_only(false)
+ {
+ }
+};
+
+// Resource limits for a particular service & particular resource
+struct service_rlimits
+{
+ int resource_id; // RLIMIT_xxx identifying resource
+ bool soft_set : 1;
+ bool hard_set : 1;
+ struct rlimit limits;
+
+ service_rlimits(int id) : resource_id(id), soft_set(0), hard_set(0), limits({0,0}) { }
+};
// Exception while loading a service
class service_load_exc
using string = std::string;
using string_iterator = std::string::iterator;
+// A service directory entry, tracking the directory as a nul-terminated string, which may either
+// be static or dynamically allocated (via new char[...]).
+class dir_entry
+{
+ const char *dir;
+ bool dir_dyn_allocd; // dynamically allocated?
+
+ public:
+ dir_entry(const char *dir_p, bool dir_dyn_allocd_p) :
+ dir(dir_p), dir_dyn_allocd(dir_dyn_allocd_p)
+ { }
+
+ dir_entry(dir_entry &&other)
+ {
+ dir = other.dir;
+ dir_dyn_allocd = other.dir_dyn_allocd;
+ other.dir_dyn_allocd = false;
+ }
+
+ dir_entry(const dir_entry &other) = delete;
+
+ ~dir_entry()
+ {
+ if (dir_dyn_allocd) {
+ delete[] dir;
+ }
+ }
+
+ const char *get_dir() const
+ {
+ return dir;
+ }
+};
+
// exception thrown when encountering a syntax issue when reading a setting value
class setting_exception
{
return i;
}
+// Convert a signal name to the corresponding signal number
+inline int signal_name_to_number(std::string &signame)
+{
+ if (signame == "HUP") return SIGHUP;
+ if (signame == "INT") return SIGINT;
+ if (signame == "QUIT") return SIGQUIT;
+ if (signame == "USR1") return SIGUSR1;
+ if (signame == "USR2") return SIGUSR2;
+ if (signame == "KILL") return SIGKILL;
+ return -1;
+}
+
// Read a setting name.
inline string read_setting_name(string_iterator & i, string_iterator end)
{
return rval;
}
-// Read a setting value
+// Read a setting value.
//
// In general a setting value is a single-line string. It may contain multiple parts
// separated by white space (which is normally collapsed). A hash mark - # - denotes
// as '#' or '"' or another backslash) to remove its special meaning. Newline
// characters are not allowed in values and cannot be quoted.
//
-// This function expects the string to be in an ASCII-compatible, single byte
-// encoding (the "classic" locale).
+// This function expects the string to be in an ASCII-compatible encoding (the "classic" locale).
+//
+// Throws setting_exception on error.
//
// Params:
// service_name - the name of the service to which the setting applies
// i - reference to string iterator through the line
-// end - iterator at end of line
+// end - iterator at end of line (not including newline character if any)
// part_positions - list of <int,int> to which the position of each setting value
// part will be added as [start,end). May be null.
inline string read_setting_value(string_iterator & i, string_iterator end,
while (i != end) {
c = *i;
if (c == '\"') break;
- if (c == '\n') {
- throw setting_exception("Line end inside quoted string");
- }
else if (c == '\\') {
// A backslash escapes the following character.
++i;
if (i != end) {
c = *i;
- if (c == '\n') {
- throw setting_exception("Line end follows backslash escape character (`\\')");
- }
rval += c;
}
+ else {
+ throw setting_exception("Line end follows backslash escape character (`\\')");
+ }
}
else {
rval += c;
return rval;
}
+// Parse a userid parameter which may be a numeric user ID or a username. If a name, the
+// userid is looked up via the system user database (getpwnam() function). In this case,
+// the associated group is stored in the location specified by the group_p parameter iff
+// it is not null and iff it contains the value -1.
+inline uid_t parse_uid_param(const std::string ¶m, const std::string &service_name, gid_t *group_p)
+{
+ const char * uid_err_msg = "Specified user id contains invalid numeric characters "
+ "or is outside allowed range.";
+
+ // Could be a name or a numeric id. But we should assume numeric first, just in case
+ // a user manages to give themselves a username that parses as a number.
+ std::size_t ind = 0;
+ try {
+ // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
+ // is is probably safe to assume that valid values are positive. We'll also assert
+ // that the value range fits within "unsigned long long" since it seems unlikely
+ // that would ever not be the case.
+ static_assert((uintmax_t)std::numeric_limits<uid_t>::max()
+ <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "uid_t is too large");
+ unsigned long long v = std::stoull(param, &ind, 0);
+ if (v > static_cast<unsigned long long>(std::numeric_limits<uid_t>::max())
+ || ind != param.length()) {
+ throw service_description_exc(service_name, uid_err_msg);
+ }
+ return v;
+ }
+ catch (std::out_of_range &exc) {
+ throw service_description_exc(service_name, uid_err_msg);
+ }
+ catch (std::invalid_argument &exc) {
+ // Ok, so it doesn't look like a number: proceed...
+ }
+
+ errno = 0;
+ struct passwd * pwent = getpwnam(param.c_str());
+ if (pwent == nullptr) {
+ // Maybe an error, maybe just no entry.
+ if (errno == 0) {
+ throw service_description_exc(service_name, "Specified user \"" + param
+ + "\" does not exist in system database.");
+ }
+ else {
+ throw service_description_exc(service_name, std::string("Error accessing user database: ")
+ + strerror(errno));
+ }
+ }
+
+ if (group_p && *group_p != (gid_t)-1) {
+ *group_p = pwent->pw_gid;
+ }
+
+ return pwent->pw_uid;
+}
+
+inline gid_t parse_gid_param(const std::string ¶m, const std::string &service_name)
+{
+ const char * gid_err_msg = "Specified group id contains invalid numeric characters or is "
+ "outside allowed range.";
+
+ // Could be a name or a numeric id. But we should assume numeric first, just in case
+ // a user manages to give themselves a username that parses as a number.
+ std::size_t ind = 0;
+ try {
+ // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
+ // is is probably safe to assume that valid values are positive. We'll also assume
+ // that the value range fits with "unsigned long long" since it seems unlikely
+ // that would ever not be the case.
+ static_assert((uintmax_t)std::numeric_limits<gid_t>::max()
+ <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "gid_t is too large");
+ unsigned long long v = std::stoull(param, &ind, 0);
+ if (v > static_cast<unsigned long long>(std::numeric_limits<gid_t>::max())
+ || ind != param.length()) {
+ throw service_description_exc(service_name, gid_err_msg);
+ }
+ return v;
+ }
+ catch (std::out_of_range &exc) {
+ throw service_description_exc(service_name, gid_err_msg);
+ }
+ catch (std::invalid_argument &exc) {
+ // Ok, so it doesn't look like a number: proceed...
+ }
+
+ errno = 0;
+ struct group * grent = getgrnam(param.c_str());
+ if (grent == nullptr) {
+ // Maybe an error, maybe just no entry.
+ if (errno == 0) {
+ throw service_description_exc(service_name, "Specified group \"" + param
+ + "\" does not exist in system database.");
+ }
+ else {
+ throw service_description_exc(service_name, std::string("Error accessing group database: ")
+ + strerror(errno));
+ }
+ }
+
+ return grent->gr_gid;
+}
+
+// Parse a time, specified as a decimal number of seconds (with optional fractional component after decimal
+// point or decimal comma).
+inline void parse_timespec(const std::string ¶mval, const std::string &servicename,
+ const char * paramname, timespec &ts)
+{
+ decltype(ts.tv_sec) isec = 0;
+ decltype(ts.tv_nsec) insec = 0;
+ auto max_secs = std::numeric_limits<decltype(isec)>::max() / 10;
+ auto len = paramval.length();
+ decltype(len) i;
+ for (i = 0; i < len; i++) {
+ char ch = paramval[i];
+ if (ch == '.' || ch == ',') {
+ i++;
+ break;
+ }
+ if (ch < '0' || ch > '9') {
+ throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
+ }
+ // check for overflow
+ if (isec >= max_secs) {
+ throw service_description_exc(servicename, std::string("Too-large value for ") + paramname);
+ }
+ isec *= 10;
+ isec += ch - '0';
+ }
+ decltype(insec) insec_m = 100000000; // 10^8
+ for ( ; i < len; i++) {
+ char ch = paramval[i];
+ if (ch < '0' || ch > '9') {
+ throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
+ }
+ insec += (ch - '0') * insec_m;
+ insec_m /= 10;
+ }
+ ts.tv_sec = isec;
+ ts.tv_nsec = insec;
+}
+
+// Parse an unsigned numeric parameter value
+inline unsigned long long parse_unum_param(const std::string ¶m, const std::string &service_name,
+ unsigned long long max = std::numeric_limits<unsigned long long>::max())
+{
+ const char * num_err_msg = "Specified value contains invalid numeric characters or is outside "
+ "allowed range.";
+
+ std::size_t ind = 0;
+ try {
+ unsigned long long v = std::stoull(param, &ind, 0);
+ if (v > max || ind != param.length()) {
+ throw service_description_exc(service_name, num_err_msg);
+ }
+ return v;
+ }
+ catch (std::out_of_range &exc) {
+ throw service_description_exc(service_name, num_err_msg);
+ }
+ catch (std::invalid_argument &exc) {
+ throw service_description_exc(service_name, num_err_msg);
+ }
+}
+
+// In a vector, find or create rlimits for a particular resource type.
+inline service_rlimits &find_rlimits(std::vector<service_rlimits> &all_rlimits, int resource_id)
+{
+ for (service_rlimits &limits : all_rlimits) {
+ if (limits.resource_id == resource_id) {
+ return limits;
+ }
+ }
+
+ all_rlimits.emplace_back(resource_id);
+ return all_rlimits.back();
+}
+
+// Parse resource limits setting (can specify both hard and soft limit).
+inline void parse_rlimit(const std::string &line, const std::string &service_name, const char *param_name,
+ service_rlimits &rlimit)
+{
+ // Examples:
+ // 4:5 - soft:hard limits both set
+ // 4:- soft set, hard set to unlimited
+ // 4: soft set, hard limit unchanged
+ // 4 soft and hard limit set to same limit
+
+ if (line.empty()) {
+ throw service_description_exc(service_name, std::string("Bad value for ") + param_name);
+ }
+
+ const char *cline = line.c_str();
+ rlimit.hard_set = rlimit.soft_set = false;
+
+ try {
+ const char * index = cline;
+ errno = 0;
+ if (cline[0] != ':') {
+ rlimit.soft_set = true;
+ if (cline[0] == '-') {
+ rlimit.limits.rlim_cur = RLIM_INFINITY;
+ index = cline + 1;
+ }
+ else {
+ char *nindex;
+ unsigned long long limit = std::strtoull(cline, &nindex, 0);
+ index = nindex;
+ if (errno == ERANGE || limit > std::numeric_limits<rlim_t>::max()) throw std::out_of_range("");
+ if (index == cline) throw std::invalid_argument("");
+ rlimit.limits.rlim_cur = limit;
+ }
+
+ if (*index == 0) {
+ rlimit.hard_set = true;
+ rlimit.limits.rlim_max = rlimit.limits.rlim_cur;
+ return;
+ }
+
+ if (*index != ':') {
+ throw service_description_exc(service_name, std::string("Bad value for ") + param_name);
+ }
+ }
+
+ index++;
+ if (*index == 0) return;
+
+ if (*index == '-') {
+ rlimit.limits.rlim_max = RLIM_INFINITY;
+ if (index[1] != 0) {
+ throw service_description_exc(service_name, std::string("Bad value for ") + param_name);
+ }
+ }
+ else {
+ const char *hard_start = index;
+ char *nindex;
+ unsigned long long limit = std::strtoull(cline, &nindex, 0);
+ index = nindex;
+ if (errno == ERANGE || limit > std::numeric_limits<rlim_t>::max()) throw std::out_of_range("");
+ if (index == hard_start) throw std::invalid_argument("");
+ rlimit.limits.rlim_max = limit;
+ }
+ }
+ catch (std::invalid_argument &exc) {
+ throw service_description_exc(service_name, std::string("Bad value for ") + param_name);
+ }
+ catch (std::out_of_range &exc) {
+ throw service_description_exc(service_name, std::string("Too-large value for ") + param_name);
+ }
+}
+
// Process an opened service file, line by line.
// name - the service name
// service_file - the service file input stream
// func - a function of the form:
// void(string &line, string &setting, string_iterator i, string_iterator end)
// Called with:
-// line - the complete line
+// line - the complete line (excluding newline character)
// setting - the setting name, from the beginning of the line
// i - iterator at the beginning of the setting value
// end - iterator marking the end of the line
std::vector<const char *> separate_args(std::string &s,
const std::list<std::pair<unsigned,unsigned>> &arg_indices);
-struct service_rlimits
-{
- int resource_id; // RLIMIT_xxx identifying resource
- bool soft_set : 1;
- bool hard_set : 1;
- struct rlimit limits;
-
- service_rlimits(int id) : resource_id(id), soft_set(0), hard_set(0), limits({0,0}) { }
-};
-
// Parameters for process execution
struct run_proc_params
{
* transition stage, at the latest.
*/
-struct service_flags_t
-{
- // on-start flags:
- bool rw_ready : 1; // file system should be writable once this service starts
- bool log_ready : 1; // syslog should be available once this service starts
-
- // Other service options flags:
- bool no_sigterm : 1; // do not send SIGTERM
- bool runs_on_console : 1; // run "in the foreground"
- bool starts_on_console : 1; // starts in the foreground
- bool shares_console : 1; // run on console, but not exclusively
- bool pass_cs_fd : 1; // pass this service a control socket connection via fd
- bool start_interruptible : 1; // the startup of this service process is ok to interrupt with SIGINT
- bool skippable : 1; // if interrupted the service is skipped (scripted services)
- bool signal_process_only : 1; // signal the session process, not the whole group
-
- service_flags_t() noexcept : rw_ready(false), log_ready(false), no_sigterm(false),
- runs_on_console(false), starts_on_console(false), shares_console(false),
- pass_cs_fd(false), start_interruptible(false), skippable(false), signal_process_only(false)
- {
- }
-};
-
class service_record;
class service_set;
class base_process_service;
}
};
-// A service directory entry, tracking the directory as a nul-terminated string, which may either
-// be static or dynamically allocated (via new char[...]).
-class service_dir_entry
-{
- const char *dir;
- bool dir_dyn_allocd; // dynamically allocated?
-
- public:
- service_dir_entry(const char *dir_p, bool dir_dyn_allocd_p) :
- dir(dir_p), dir_dyn_allocd(dir_dyn_allocd_p)
- { }
-
- ~service_dir_entry()
- {
- if (dir_dyn_allocd) {
- delete[] dir;
- }
- }
-
- const char *get_dir() const
- {
- return dir;
- }
-};
-
// A service set which loads services from one of several service directories.
class dirload_service_set : public service_set
{
- std::vector<service_dir_entry> service_dirs; // directories containing service descriptions
+ using dir_entry = dinit_load::dir_entry;
+
+ std::vector<dir_entry> service_dirs; // directories containing service descriptions
public:
dirload_service_set() : service_set()
using string = std::string;
using string_iterator = std::string::iterator;
-// Convert a signal name to the corresponding signal number
-static int signal_name_to_number(std::string &signame)
-{
- if (signame == "HUP") return SIGHUP;
- if (signame == "INT") return SIGINT;
- if (signame == "QUIT") return SIGQUIT;
- if (signame == "USR1") return SIGUSR1;
- if (signame == "USR2") return SIGUSR2;
- if (signame == "KILL") return SIGKILL;
- return -1;
-}
-
-static const char * uid_err_msg = "Specified user id contains invalid numeric characters "
- "or is outside allowed range.";
-
-// Parse a userid parameter which may be a numeric user ID or a username. If a name, the
-// userid is looked up via the system user database (getpwnam() function). In this case,
-// the associated group is stored in the location specified by the group_p parameter iff
-// it is not null and iff it contains the value -1.
-static uid_t parse_uid_param(const std::string ¶m, const std::string &service_name, gid_t *group_p)
-{
- // Could be a name or a numeric id. But we should assume numeric first, just in case
- // a user manages to give themselves a username that parses as a number.
- std::size_t ind = 0;
- try {
- // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
- // is is probably safe to assume that valid values are positive. We'll also assert
- // that the value range fits within "unsigned long long" since it seems unlikely
- // that would ever not be the case.
- static_assert((uintmax_t)std::numeric_limits<uid_t>::max()
- <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "uid_t is too large");
- unsigned long long v = std::stoull(param, &ind, 0);
- if (v > static_cast<unsigned long long>(std::numeric_limits<uid_t>::max())
- || ind != param.length()) {
- throw service_description_exc(service_name, uid_err_msg);
- }
- return v;
- }
- catch (std::out_of_range &exc) {
- throw service_description_exc(service_name, uid_err_msg);
- }
- catch (std::invalid_argument &exc) {
- // Ok, so it doesn't look like a number: proceed...
- }
-
- errno = 0;
- struct passwd * pwent = getpwnam(param.c_str());
- if (pwent == nullptr) {
- // Maybe an error, maybe just no entry.
- if (errno == 0) {
- throw service_description_exc(service_name, "Specified user \"" + param
- + "\" does not exist in system database.");
- }
- else {
- throw service_description_exc(service_name, std::string("Error accessing user database: ")
- + strerror(errno));
- }
- }
-
- if (group_p && *group_p != (gid_t)-1) {
- *group_p = pwent->pw_gid;
- }
-
- return pwent->pw_uid;
-}
-
-static const char * num_err_msg = "Specified value contains invalid numeric characters or is outside "
- "allowed range.";
-
-// Parse an unsigned numeric parameter value
-static unsigned long long parse_unum_param(const std::string ¶m, const std::string &service_name,
- unsigned long long max = std::numeric_limits<unsigned long long>::max())
-{
- std::size_t ind = 0;
- try {
- unsigned long long v = std::stoull(param, &ind, 0);
- if (v > max || ind != param.length()) {
- throw service_description_exc(service_name, num_err_msg);
- }
- return v;
- }
- catch (std::out_of_range &exc) {
- throw service_description_exc(service_name, num_err_msg);
- }
- catch (std::invalid_argument &exc) {
- throw service_description_exc(service_name, num_err_msg);
- }
-}
-
-static const char * gid_err_msg = "Specified group id contains invalid numeric characters or is "
- "outside allowed range.";
-
-static gid_t parse_gid_param(const std::string ¶m, const std::string &service_name)
-{
- // Could be a name or a numeric id. But we should assume numeric first, just in case
- // a user manages to give themselves a username that parses as a number.
- std::size_t ind = 0;
- try {
- // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
- // is is probably safe to assume that valid values are positive. We'll also assume
- // that the value range fits with "unsigned long long" since it seems unlikely
- // that would ever not be the case.
- static_assert((uintmax_t)std::numeric_limits<gid_t>::max()
- <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "gid_t is too large");
- unsigned long long v = std::stoull(param, &ind, 0);
- if (v > static_cast<unsigned long long>(std::numeric_limits<gid_t>::max())
- || ind != param.length()) {
- throw service_description_exc(service_name, gid_err_msg);
- }
- return v;
- }
- catch (std::out_of_range &exc) {
- throw service_description_exc(service_name, gid_err_msg);
- }
- catch (std::invalid_argument &exc) {
- // Ok, so it doesn't look like a number: proceed...
- }
-
- errno = 0;
- struct group * grent = getgrnam(param.c_str());
- if (grent == nullptr) {
- // Maybe an error, maybe just no entry.
- if (errno == 0) {
- throw service_description_exc(service_name, "Specified group \"" + param
- + "\" does not exist in system database.");
- }
- else {
- throw service_description_exc(service_name, std::string("Error accessing group database: ")
- + strerror(errno));
- }
- }
-
- return grent->gr_gid;
-}
-
-// Parse a time, specified as a decimal number of seconds (with optional fractional component after decimal
-// point or decimal comma).
-static void parse_timespec(const std::string ¶mval, const std::string &servicename,
- const char * paramname, timespec &ts)
-{
- decltype(ts.tv_sec) isec = 0;
- decltype(ts.tv_nsec) insec = 0;
- auto max_secs = std::numeric_limits<decltype(isec)>::max() / 10;
- auto len = paramval.length();
- decltype(len) i;
- for (i = 0; i < len; i++) {
- char ch = paramval[i];
- if (ch == '.' || ch == ',') {
- i++;
- break;
- }
- if (ch < '0' || ch > '9') {
- throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
- }
- // check for overflow
- if (isec >= max_secs) {
- throw service_description_exc(servicename, std::string("Too-large value for ") + paramname);
- }
- isec *= 10;
- isec += ch - '0';
- }
- decltype(insec) insec_m = 100000000; // 10^8
- for ( ; i < len; i++) {
- char ch = paramval[i];
- if (ch < '0' || ch > '9') {
- throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
- }
- insec += (ch - '0') * insec_m;
- insec_m /= 10;
- }
- ts.tv_sec = isec;
- ts.tv_nsec = insec;
-}
-
-// In a vector, find or create rlimits for a particular resource type.
-static service_rlimits &find_rlimits(std::vector<service_rlimits> &all_rlimits, int resource_id)
-{
- for (service_rlimits &limits : all_rlimits) {
- if (limits.resource_id == resource_id) {
- return limits;
- }
- }
-
- all_rlimits.emplace_back(resource_id);
- return all_rlimits.back();
-}
-
-// Parse resource limits setting (can specify both hard and soft limit).
-static void parse_rlimit(const std::string &line, const std::string &service_name, const char *param_name,
- service_rlimits &rlimit)
-{
- // Examples:
- // 4:5 - soft:hard limits both set
- // 4:- soft set, hard set to unlimited
- // 4: soft set, hard limit unchanged
- // 4 soft and hard limit set to same limit
-
- if (line.empty()) {
- throw service_description_exc(service_name, std::string("Bad value for ") + param_name);
- }
-
- const char *cline = line.c_str();
- rlimit.hard_set = rlimit.soft_set = false;
-
- try {
- const char * index = cline;
- errno = 0;
- if (cline[0] != ':') {
- rlimit.soft_set = true;
- if (cline[0] == '-') {
- rlimit.limits.rlim_cur = RLIM_INFINITY;
- index = cline + 1;
- }
- else {
- char *nindex;
- unsigned long long limit = std::strtoull(cline, &nindex, 0);
- index = nindex;
- if (errno == ERANGE || limit > std::numeric_limits<rlim_t>::max()) throw std::out_of_range("");
- if (index == cline) throw std::invalid_argument("");
- rlimit.limits.rlim_cur = limit;
- }
-
- if (*index == 0) {
- rlimit.hard_set = true;
- rlimit.limits.rlim_max = rlimit.limits.rlim_cur;
- return;
- }
-
- if (*index != ':') {
- throw service_description_exc(service_name, std::string("Bad value for ") + param_name);
- }
- }
-
- index++;
- if (*index == 0) return;
-
- if (*index == '-') {
- rlimit.limits.rlim_max = RLIM_INFINITY;
- if (index[1] != 0) {
- throw service_description_exc(service_name, std::string("Bad value for ") + param_name);
- }
- }
- else {
- const char *hard_start = index;
- char *nindex;
- unsigned long long limit = std::strtoull(cline, &nindex, 0);
- index = nindex;
- if (errno == ERANGE || limit > std::numeric_limits<rlim_t>::max()) throw std::out_of_range("");
- if (index == hard_start) throw std::invalid_argument("");
- rlimit.limits.rlim_max = limit;
- }
- }
- catch (std::invalid_argument &exc) {
- throw service_description_exc(service_name, std::string("Bad value for ") + param_name);
- }
- catch (std::out_of_range &exc) {
- throw service_description_exc(service_name, std::string("Too-large value for ") + param_name);
- }
-}
-
// Perform environment variable substitution on a command line, if specified.
// line - the string storing the command and arguments
// offsets - the [start,end) pair of offsets of the command and each argument within the string
closedir(depdir);
}
-// Check if one string starts with another
-static bool starts_with(string s, const char *prefix)
-{
- const char * sp = s.c_str();
- while (*sp != 0 && *prefix != 0) {
- if (*sp != *prefix) return false;
- sp++; prefix++;
- }
- return *prefix == 0;
-}
-
// Find a service record, or load it from file. If the service has dependencies, load those also.
//
// Throws service_load_exc (or subclass) if a dependency cycle is found or if another