Implement resource limit settings.
authorDavin McCall <davmac@davmac.org>
Sat, 22 Jun 2019 02:54:09 +0000 (03:54 +0100)
committerDavin McCall <davmac@davmac.org>
Sat, 22 Jun 2019 03:00:06 +0000 (04:00 +0100)
src/baseproc-service.cc
src/includes/proc-service.h
src/includes/service.h
src/load-service.cc
src/run-child-proc.cc
src/tests/test-run-child-proc.cc

index 88bb6e60a208c9fd1203bd21b6b72e5e7768a940..ba2227c017f27a3607b596f57ef571e304e3df3f 100644 (file)
@@ -156,7 +156,7 @@ bool base_process_service::start_ps_process(const std::vector<const char *> &cmd
         if (! working_dir.empty()) working_dir_c = working_dir.c_str();
         after_fork(getpid());
         run_child_proc(cmd.data(), working_dir_c, logfile, on_console, pipefd[1], control_socket[1],
-                socket_fd, notify_pipe[1], force_notification_fd, nullptr, run_as_uid, run_as_gid);
+                socket_fd, notify_pipe[1], force_notification_fd, nullptr, run_as_uid, run_as_gid, rlimits);
     }
     else {
         // Parent process
index fcb5f8c26f89b2e7c86c8aae9c1bb7222b0a82b8..ad48182ce6f905f37f452ee7ee78b7d1bf53d80c 100644 (file)
@@ -1,4 +1,5 @@
 #include <sys/types.h>
+#include <sys/resource.h>
 
 #include "baseproc-sys.h"
 #include "service.h"
 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}) { }
+};
+
 class base_process_service;
 
 // A timer for process restarting. Used to ensure a minimum delay between process restarts (and
@@ -93,6 +104,8 @@ class base_process_service : public service_record
 
     string working_dir;       // working directory (or empty)
 
+    std::vector<service_rlimits> rlimits; // resource limits
+
     service_child_watcher child_listener;
     exec_status_pipe_watcher child_status_listener;
     process_restart_timer restart_timer;
@@ -132,6 +145,27 @@ class base_process_service : public service_record
     bool reserved_child_watch : 1;
     bool tracking_child : 1;  // whether we expect to see child process status
 
+    // Run a child process (call after forking). Note that some arguments specify file descriptors,
+    // but in general file descriptors may be moved before the exec call.
+    // - args specifies the program arguments including the executable (argv[0])
+    // - working_dir specifies the working directory; may be null
+    // - logfile specifies the logfile (where stdout/stderr are directed)
+    // - on_console: if true, process is run with access to console
+    // - wpipefd: if the exec is unsuccessful, or another error occurs beforehand, the
+    //   error number (errno) is written to this file descriptor
+    // - csfd: the control socket fd; may be -1 to inhibit passing of control socket
+    // - socket_fd: the pre-opened socket file descriptor (may be -1)
+    // - notify_fd: the readiness notification fd; process should write to this descriptor when
+    //   is is ready
+    // - force_notify_fd: if not -1, specifies the file descriptor that notify_fd should be moved
+    //   to (via dup2 and close of the original).
+    // - notify_var: the name of an environment variable which will be set to contain the notification
+    //   fd
+    // - uid/gid: the identity to run the process as (may be both -1, otherwise both must be valid)
+    void run_child_proc(const char * const *args, const char *working_dir, const char *logfile,
+            bool on_console, int wpipefd, int csfd, int socket_fd, int notify_fd, int force_notify_fd,
+            const char *notify_var,uid_t uid, gid_t gid, const std::vector<service_rlimits> &rlimits) noexcept;
+
     // Launch the process with the given arguments, return true on success
     bool start_ps_process(const std::vector<const char *> &args, bool on_console) noexcept;
 
@@ -214,6 +248,11 @@ class base_process_service : public service_record
         stop_arg_parts = separate_args(stop_command, stop_command_offsets);
     }
 
+    void set_rlimits(std::vector<service_rlimits> &&rlimits_p)
+    {
+        rlimits = std::move(rlimits_p);
+    }
+
     void set_restart_interval(timespec interval, int max_restarts) noexcept
     {
         restart_interval = interval;
index 37e974c1ced6f545d49d0be0ed6cc346d620c5b0..1acec0851811d86539a985299c8f9a2fdb087254 100644 (file)
@@ -288,27 +288,6 @@ class service_record
     //   dep_failed: whether failure is recorded due to a dependency failing
     //   immediate_stop: whether to set state as STOPPED and handle complete stop.
     void failed_to_start(bool dep_failed = false, bool immediate_stop = true) noexcept;
-
-    // Run a child process (call after forking). Note that some arguments specify file descriptors,
-    // but in general file descriptors may be moved before the exec call.
-    // - args specifies the program arguments including the executable (argv[0])
-    // - working_dir specifies the working directory; may be null
-    // - logfile specifies the logfile (where stdout/stderr are directed)
-    // - on_console: if true, process is run with access to console
-    // - wpipefd: if the exec is unsuccessful, or another error occurs beforehand, the
-    //   error number (errno) is written to this file descriptor
-    // - csfd: the control socket fd; may be -1 to inhibit passing of control socket
-    // - socket_fd: the pre-opened socket file descriptor (may be -1)
-    // - notify_fd: the readiness notification fd; process should write to this descriptor when
-    //   is is ready
-    // - force_notify_fd: if not -1, specifies the file descriptor that notify_fd should be moved
-    //   to (via dup2 and close of the original).
-    // - notify_var: the name of an environment variable which will be set to contain the notification
-    //   fd
-    // - uid/gid: the identity to run the process as (may be both -1, otherwise both must be valid)
-    void run_child_proc(const char * const *args, const char *working_dir, const char *logfile,
-            bool on_console, int wpipefd, int csfd, int socket_fd, int notify_fd, int force_notify_fd,
-            const char *notify_var,uid_t uid, gid_t gid) noexcept;
     
     // A dependency has reached STARTED state
     void dependency_started() noexcept;
index d4b653a44d9d36622f621758eecfad552df0895f..1a6d7c73795de1c63352c5592414c0ab0cd9578e 100644 (file)
@@ -159,7 +159,6 @@ static gid_t parse_gid_param(const std::string &param, const std::string &servic
 
 // 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)
 {
@@ -197,6 +196,84 @@ static void parse_timespec(const std::string &paramval, const std::string &servi
     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 {
+        char * index;
+        errno = 0;
+        if (cline[0] != ':') {
+            rlimit.soft_set = true;
+            if (cline[0] == '-') {
+                rlimit.limits.rlim_cur = RLIM_INFINITY;
+            }
+            else {
+                unsigned long long limit = std::strtoull(cline, &index, 0);
+                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;
+        }
+        else {
+            char *hard_start = index;
+            unsigned long long limit = std::strtoull(cline, &index, 0);
+            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
@@ -373,6 +450,7 @@ service_record * dirload_service_set::load_service(const char * name)
     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
@@ -625,6 +703,26 @@ service_record * dirload_service_set::load_service(const char * name)
                 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") {
+                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);
+            }
             else {
                 throw service_description_exc(name, "Unknown setting: " + setting);
             }
@@ -649,13 +747,13 @@ service_record * dirload_service_set::load_service(const char * name)
                     auto rvalps = new process_service(this, string(name), std::move(command),
                             command_offsets, depends);
                     rvalps->set_workding_dir(working_dir);
+                    rvalps->set_rlimits(std::move(rlimits));
                     rvalps->set_restart_interval(restart_interval, max_restarts);
                     rvalps->set_restart_delay(restart_delay);
                     rvalps->set_stop_timeout(stop_timeout);
                     rvalps->set_start_timeout(start_timeout);
                     rvalps->set_extra_termination_signal(term_signal);
                     rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid);
-                    rvalps->set_workding_dir(working_dir);
                     rvalps->set_notification_fd(readiness_fd);
                     rvalps->set_notification_var(std::move(readiness_var));
                     #if USE_UTMPX
@@ -669,6 +767,7 @@ service_record * dirload_service_set::load_service(const char * name)
                     auto rvalps = new bgproc_service(this, string(name), std::move(command),
                             command_offsets, depends);
                     rvalps->set_workding_dir(working_dir);
+                    rvalps->set_rlimits(std::move(rlimits));
                     rvalps->set_pid_file(std::move(pid_file));
                     rvalps->set_restart_interval(restart_interval, max_restarts);
                     rvalps->set_restart_delay(restart_delay);
@@ -685,6 +784,7 @@ service_record * dirload_service_set::load_service(const char * name)
                             command_offsets, depends);
                     rvalps->set_stop_command(stop_command, stop_command_offsets);
                     rvalps->set_workding_dir(working_dir);
+                    rvalps->set_rlimits(std::move(rlimits));
                     rvalps->set_stop_timeout(stop_timeout);
                     rvalps->set_start_timeout(start_timeout);
                     rvalps->set_extra_termination_signal(term_signal);
index fa1cff1b3b9f39e61537badb06606246def24434..33d189d015e4803610b83ac2683adacefeb5ac07 100644 (file)
@@ -11,6 +11,7 @@
 #include <termios.h>
 
 #include "service.h"
+#include "proc-service.h"
 
 // Move an fd, if necessary, to another fd. The destination fd must be available (not open).
 // if fd is specified as -1, returns -1 immediately. Returns 0 on success.
@@ -27,10 +28,10 @@ static int move_fd(int fd, int dest)
     return 0;
 }
 
-void service_record::run_child_proc(const char * const *args, const char *working_dir,
+void base_process_service::run_child_proc(const char * const *args, const char *working_dir,
         const char *logfile, bool on_console, int wpipefd, int csfd, int socket_fd,
         int notify_fd, int force_notify_fd, const char *notify_var,
-        uid_t uid, gid_t gid) noexcept
+        uid_t uid, gid_t gid, const std::vector<service_rlimits> &rlimits) noexcept
 {
     // Child process. Must not risk throwing any uncaught exception from here until exit().
 
@@ -178,6 +179,18 @@ void service_record::run_child_proc(const char * const *args, const char *workin
         tcsetpgrp(0, getpgrp());
     }
 
+    // Resource limits
+    for (auto &limit : rlimits) {
+        rlimit setlimits;
+        if (!limit.hard_set || !limit.soft_set) {
+            // if either hard or soft limit is not set, use current:
+            if (getrlimit(limit.resource_id, &setlimits) != 0) goto failure_out;
+        }
+        if (limit.hard_set) setlimits.rlim_max = limit.limits.rlim_max;
+        if (limit.soft_set) setlimits.rlim_cur = limit.limits.rlim_cur;
+        if (setrlimit(limit.resource_id, &setlimits) != 0) goto failure_out;
+    }
+
     if (uid != uid_t(-1)) {
         if (setreuid(uid, uid) != 0) goto failure_out;
         if (setregid(gid, gid) != 0) goto failure_out;
index b3879927aed8445a7cdbb683bc9baeb58c08b61a..0bf952cdd68972108a8cc3142caa19359815e546 100644 (file)
@@ -1,10 +1,13 @@
-#include "service.h"
+#include <vector>
+
+#include "proc-service.h"
 
 // Stub out run_child_proc function, for testing purposes.
 
-void service_record::run_child_proc(const char * const *args, const char *working_dir,
+void base_process_service::run_child_proc(const char * const *args, const char *working_dir,
         const char *logfile, bool on_console, int wpipefd, int csfd, int socket_fd,
-        int notify_fd, int forced_notify_fd, const char * notify_var, uid_t uid, gid_t gid) noexcept
+        int notify_fd, int forced_notify_fd, const char * notify_var, uid_t uid, gid_t gid,
+        const std::vector<service_rlimits> &rlimits) noexcept
 {
 
 }