Beginnings of "dinitcheck" utility
authorDavin McCall <davmac@davmac.org>
Sat, 10 Aug 2019 04:20:40 +0000 (14:20 +1000)
committerDavin McCall <davmac@davmac.org>
Sun, 6 Oct 2019 01:59:32 +0000 (02:59 +0100)
This will be a utility to check service configuration for errors/lint.

src/Makefile
src/dinitcheck.cc [new file with mode: 0644]
src/includes/dinit-util.h
src/includes/load-service.h
src/includes/proc-service.h
src/includes/service.h
src/load-service.cc

index 9b0b3ef6cbe6d37797e64302131fcaecf9bd4b5f..b6dce35b18c3ba044e8fb36d736cfa86263e06a4 100644 (file)
@@ -12,9 +12,9 @@ endif
 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:
@@ -41,6 +41,9 @@ dinit: $(dinit_objects)
 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)
 
diff --git a/src/dinitcheck.cc b/src/dinitcheck.cc
new file mode 100644 (file)
index 0000000..2865e17
--- /dev/null
@@ -0,0 +1,533 @@
+#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);
+}
index 7185370f9d0a711941b7d18fa57ad7fbeb61560a..bd28c2e6db7df189a180474e7fc983e4e57fdc3c 100644 (file)
@@ -75,4 +75,15 @@ inline const char * base_name(const char *path)
     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
index 21cb4632ec5f84e5a8f8bd3e55a4c01ce618dac2..fcddec0d9ce815d66393942033058c66b6e321b8 100644 (file)
@@ -1,5 +1,47 @@
 #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
@@ -47,6 +89,40 @@ namespace dinit_load {
 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
 {
@@ -80,6 +156,18 @@ inline string_iterator skipws(string_iterator i, string_iterator end)
     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)
 {
@@ -98,7 +186,7 @@ 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
@@ -111,13 +199,14 @@ inline string read_setting_name(string_iterator & i, string_iterator end)
 // 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,
@@ -144,19 +233,16 @@ 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;
@@ -216,13 +302,261 @@ inline string read_setting_value(string_iterator & i, string_iterator end,
     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 &param, 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 &param, 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 &paramval, 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 &param, 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
index 085026bc5f7ae533ddd015c8181aa5cccab5e2a6..010037a18f83eb14db75fdcbc9d8a79eaa78e505 100644 (file)
 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
 {
index 7db10368118d125ff97925424edd2f2289f507c9..e46de700762f73e54ec95851dd89b6e3b51297e8 100644 (file)
  * 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;
@@ -927,35 +904,12 @@ class service_set
     }
 };
 
-// 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()
index b5565881174cfddbe7156414cbb3c3173eb399de..7453bb3e617d05f35be24d7eea90f3cc20e11b63 100644 (file)
 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 &param, 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 &param, 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 &param, 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 &paramval, 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
@@ -372,17 +112,6 @@ static void process_dep_dir(dirload_service_set &sset,
     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