Factor out service parameter processing
[oweals/dinit.git] / src / includes / load-service.h
1 #include <iostream>
2 #include <list>
3 #include <limits>
4 #include <csignal>
5 #include <cstring>
6 #include <utility>
7
8 #include <sys/types.h>
9 #include <sys/time.h>
10 #include <sys/resource.h>
11 #include <grp.h>
12 #include <pwd.h>
13
14 #include "dinit-utmp.h"
15 #include "dinit-util.h"
16
17 struct service_flags_t
18 {
19     // on-start flags:
20     bool rw_ready : 1;  // file system should be writable once this service starts
21     bool log_ready : 1; // syslog should be available once this service starts
22
23     // Other service options flags:
24     bool no_sigterm : 1;  // do not send SIGTERM
25     bool runs_on_console : 1;  // run "in the foreground"
26     bool starts_on_console : 1; // starts in the foreground
27     bool shares_console : 1;    // run on console, but not exclusively
28     bool pass_cs_fd : 1;  // pass this service a control socket connection via fd
29     bool start_interruptible : 1; // the startup of this service process is ok to interrupt with SIGINT
30     bool skippable : 1;   // if interrupted the service is skipped (scripted services)
31     bool signal_process_only : 1;  // signal the session process, not the whole group
32
33     service_flags_t() noexcept : rw_ready(false), log_ready(false), no_sigterm(false),
34             runs_on_console(false), starts_on_console(false), shares_console(false),
35             pass_cs_fd(false), start_interruptible(false), skippable(false), signal_process_only(false)
36     {
37     }
38 };
39
40 // Resource limits for a particular service & particular resource
41 struct service_rlimits
42 {
43     int resource_id; // RLIMIT_xxx identifying resource
44     bool soft_set : 1;
45     bool hard_set : 1;
46     struct rlimit limits;
47
48     service_rlimits(int id) : resource_id(id), soft_set(0), hard_set(0), limits({0,0}) { }
49 };
50
51 // Exception while loading a service
52 class service_load_exc
53 {
54     public:
55     std::string service_name;
56     std::string exc_description;
57
58     protected:
59     service_load_exc(const std::string &serviceName, std::string &&desc) noexcept
60         : service_name(serviceName), exc_description(std::move(desc))
61     {
62     }
63 };
64
65 class service_not_found : public service_load_exc
66 {
67     public:
68     service_not_found(const std::string &serviceName) noexcept
69         : service_load_exc(serviceName, "Service description not found.")
70     {
71     }
72 };
73
74 class service_cyclic_dependency : public service_load_exc
75 {
76     public:
77     service_cyclic_dependency(const std::string &serviceName) noexcept
78         : service_load_exc(serviceName, "Has cyclic dependency.")
79     {
80     }
81 };
82
83 class service_description_exc : public service_load_exc
84 {
85     public:
86     service_description_exc(const std::string &serviceName, std::string &&extraInfo) noexcept
87         : service_load_exc(serviceName, std::move(extraInfo))
88     {
89     }
90 };
91
92 namespace dinit_load {
93
94 using string = std::string;
95 using string_iterator = std::string::iterator;
96
97 // exception thrown when encountering a syntax issue when reading a setting value
98 class setting_exception
99 {
100     std::string info;
101
102     public:
103     setting_exception(const std::string &&exc_info) : info(std::move(exc_info))
104     {
105     }
106
107     std::string &get_info()
108     {
109         return info;
110     }
111 };
112
113
114 // Utility function to skip white space. Returns an iterator at the
115 // first non-white-space position (or at end).
116 inline string_iterator skipws(string_iterator i, string_iterator end)
117 {
118     using std::locale;
119     using std::isspace;
120
121     while (i != end) {
122       if (! isspace(*i, locale::classic())) {
123         break;
124       }
125       ++i;
126     }
127     return i;
128 }
129
130 // Convert a signal name to the corresponding signal number
131 inline int signal_name_to_number(std::string &signame)
132 {
133     if (signame == "HUP") return SIGHUP;
134     if (signame == "INT") return SIGINT;
135     if (signame == "QUIT") return SIGQUIT;
136     if (signame == "USR1") return SIGUSR1;
137     if (signame == "USR2") return SIGUSR2;
138     if (signame == "KILL") return SIGKILL;
139     return -1;
140 }
141
142 // Read a setting name.
143 inline string read_setting_name(string_iterator & i, string_iterator end)
144 {
145     using std::locale;
146     using std::ctype;
147     using std::use_facet;
148
149     const ctype<char> & facet = use_facet<ctype<char> >(locale::classic());
150
151     string rval;
152     // Allow alphabetical characters, and dash (-) in setting name
153     while (i != end && (*i == '-' || *i == '.' || facet.is(ctype<char>::alpha, *i))) {
154         rval += *i;
155         ++i;
156     }
157     return rval;
158 }
159
160 // Read a setting value.
161 //
162 // In general a setting value is a single-line string. It may contain multiple parts
163 // separated by white space (which is normally collapsed). A hash mark - # - denotes
164 // the end of the value and the beginning of a comment (it should be preceded by
165 // whitespace).
166 //
167 // Part of a value may be quoted using double quote marks, which prevents collapse
168 // of whitespace and interpretation of most special characters (the quote marks will
169 // not be considered part of the value). A backslash can precede a character (such
170 // as '#' or '"' or another backslash) to remove its special meaning. Newline
171 // characters are not allowed in values and cannot be quoted.
172 //
173 // This function expects the string to be in an ASCII-compatible encoding (the "classic" locale).
174 //
175 // Throws setting_exception on error.
176 //
177 // Params:
178 //    service_name - the name of the service to which the setting applies
179 //    i  -  reference to string iterator through the line
180 //    end -   iterator at end of line (not including newline character if any)
181 //    part_positions -  list of <int,int> to which the position of each setting value
182 //                      part will be added as [start,end). May be null.
183 inline string read_setting_value(string_iterator & i, string_iterator end,
184         std::list<std::pair<unsigned,unsigned>> * part_positions = nullptr)
185 {
186     using std::locale;
187     using std::isspace;
188
189     i = skipws(i, end);
190
191     string rval;
192     bool new_part = true;
193     int part_start;
194
195     while (i != end) {
196         char c = *i;
197         if (c == '\"') {
198             if (new_part) {
199                 part_start = rval.length();
200                 new_part = false;
201             }
202             // quoted string
203             ++i;
204             while (i != end) {
205                 c = *i;
206                 if (c == '\"') break;
207                 else if (c == '\\') {
208                     // A backslash escapes the following character.
209                     ++i;
210                     if (i != end) {
211                         c = *i;
212                         rval += c;
213                     }
214                     else {
215                         throw setting_exception("Line end follows backslash escape character (`\\')");
216                     }
217                 }
218                 else {
219                     rval += c;
220                 }
221                 ++i;
222             }
223             if (i == end) {
224                 // String wasn't terminated
225                 throw setting_exception("Unterminated quoted string");
226             }
227         }
228         else if (c == '\\') {
229             if (new_part) {
230                 part_start = rval.length();
231                 new_part = false;
232             }
233             // A backslash escapes the next character
234             ++i;
235             if (i != end) {
236                 rval += *i;
237             }
238             else {
239                 throw setting_exception("Backslash escape (`\\') not followed by character");
240             }
241         }
242         else if (isspace(c, locale::classic())) {
243             if (! new_part && part_positions != nullptr) {
244                 part_positions->emplace_back(part_start, rval.length());
245                 new_part = true;
246             }
247             i = skipws(i, end);
248             if (i == end) break;
249             if (*i == '#') break; // comment
250             rval += ' ';  // collapse ws to a single space
251             continue;
252         }
253         else if (c == '#') {
254             // Possibly intended a comment; we require leading whitespace to reduce occurrence of accidental
255             // comments in setting values.
256             throw setting_exception("hashmark (`#') comment must be separated from setting value by whitespace");
257         }
258         else {
259             if (new_part) {
260                 part_start = rval.length();
261                 new_part = false;
262             }
263             rval += c;
264         }
265         ++i;
266     }
267
268     // Got to end:
269     if (part_positions != nullptr) {
270         part_positions->emplace_back(part_start, rval.length());
271     }
272
273     return rval;
274 }
275
276 // Parse a userid parameter which may be a numeric user ID or a username. If a name, the
277 // userid is looked up via the system user database (getpwnam() function). In this case,
278 // the associated group is stored in the location specified by the group_p parameter iff
279 // it is not null and iff it contains the value -1.
280 inline uid_t parse_uid_param(const std::string &param, const std::string &service_name, const char *setting_name, gid_t *group_p)
281 {
282     const char * uid_err_msg = "Specified user id contains invalid numeric characters "
283             "or is outside allowed range.";
284
285     // Could be a name or a numeric id. But we should assume numeric first, just in case
286     // a user manages to give themselves a username that parses as a number.
287     std::size_t ind = 0;
288     try {
289         // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
290         // is is probably safe to assume that valid values are positive. We'll also assert
291         // that the value range fits within "unsigned long long" since it seems unlikely
292         // that would ever not be the case.
293         static_assert((uintmax_t)std::numeric_limits<uid_t>::max()
294                 <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "uid_t is too large");
295         unsigned long long v = std::stoull(param, &ind, 0);
296         if (v > static_cast<unsigned long long>(std::numeric_limits<uid_t>::max())
297                 || ind != param.length()) {
298             throw service_description_exc(service_name, std::string(setting_name) + ": " + uid_err_msg);
299         }
300         return v;
301     }
302     catch (std::out_of_range &exc) {
303         throw service_description_exc(service_name, uid_err_msg);
304     }
305     catch (std::invalid_argument &exc) {
306         // Ok, so it doesn't look like a number: proceed...
307     }
308
309     errno = 0;
310     struct passwd * pwent = getpwnam(param.c_str());
311     if (pwent == nullptr) {
312         // Maybe an error, maybe just no entry.
313         if (errno == 0) {
314             throw service_description_exc(service_name, std::string(setting_name) + ": Specified user \"" + param
315                     + "\" does not exist in system database.");
316         }
317         else {
318             throw service_description_exc(service_name, std::string("Error accessing user database: ")
319                     + strerror(errno));
320         }
321     }
322
323     if (group_p && *group_p != (gid_t)-1) {
324         *group_p = pwent->pw_gid;
325     }
326
327     return pwent->pw_uid;
328 }
329
330 inline gid_t parse_gid_param(const std::string &param, const char *setting_name, const std::string &service_name)
331 {
332     const char * gid_err_msg = "Specified group id contains invalid numeric characters or is "
333             "outside allowed range.";
334
335     // Could be a name or a numeric id. But we should assume numeric first, just in case
336     // a user manages to give themselves a username that parses as a number.
337     std::size_t ind = 0;
338     try {
339         // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
340         // is is probably safe to assume that valid values are positive. We'll also assume
341         // that the value range fits with "unsigned long long" since it seems unlikely
342         // that would ever not be the case.
343         static_assert((uintmax_t)std::numeric_limits<gid_t>::max()
344                 <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "gid_t is too large");
345         unsigned long long v = std::stoull(param, &ind, 0);
346         if (v > static_cast<unsigned long long>(std::numeric_limits<gid_t>::max())
347                 || ind != param.length()) {
348             throw service_description_exc(service_name, std::string(setting_name) + ": " + gid_err_msg);
349         }
350         return v;
351     }
352     catch (std::out_of_range &exc) {
353         throw service_description_exc(service_name, std::string(setting_name) + ": " + gid_err_msg);
354     }
355     catch (std::invalid_argument &exc) {
356         // Ok, so it doesn't look like a number: proceed...
357     }
358
359     errno = 0;
360     struct group * grent = getgrnam(param.c_str());
361     if (grent == nullptr) {
362         // Maybe an error, maybe just no entry.
363         if (errno == 0) {
364             throw service_description_exc(service_name, std::string(setting_name) + ": Specified group \"" + param
365                     + "\" does not exist in system database.");
366         }
367         else {
368             throw service_description_exc(service_name, std::string("Error accessing group database: ")
369                     + strerror(errno));
370         }
371     }
372
373     return grent->gr_gid;
374 }
375
376 // Parse a time, specified as a decimal number of seconds (with optional fractional component after decimal
377 // point or decimal comma).
378 inline void parse_timespec(const std::string &paramval, const std::string &servicename,
379         const char * paramname, timespec &ts)
380 {
381     decltype(ts.tv_sec) isec = 0;
382     decltype(ts.tv_nsec) insec = 0;
383     auto max_secs = std::numeric_limits<decltype(isec)>::max() / 10;
384     auto len = paramval.length();
385     decltype(len) i;
386     for (i = 0; i < len; i++) {
387         char ch = paramval[i];
388         if (ch == '.' || ch == ',') {
389             i++;
390             break;
391         }
392         if (ch < '0' || ch > '9') {
393             throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
394         }
395         // check for overflow
396         if (isec >= max_secs) {
397            throw service_description_exc(servicename, std::string("Too-large value for ") + paramname);
398         }
399         isec *= 10;
400         isec += ch - '0';
401     }
402     decltype(insec) insec_m = 100000000; // 10^8
403     for ( ; i < len; i++) {
404         char ch = paramval[i];
405         if (ch < '0' || ch > '9') {
406             throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
407         }
408         insec += (ch - '0') * insec_m;
409         insec_m /= 10;
410     }
411     ts.tv_sec = isec;
412     ts.tv_nsec = insec;
413 }
414
415 // Parse an unsigned numeric parameter value
416 inline unsigned long long parse_unum_param(const std::string &param, const std::string &service_name,
417         unsigned long long max = std::numeric_limits<unsigned long long>::max())
418 {
419     const char * num_err_msg = "Specified value contains invalid numeric characters or is outside "
420             "allowed range.";
421
422     std::size_t ind = 0;
423     try {
424         unsigned long long v = std::stoull(param, &ind, 0);
425         if (v > max || ind != param.length()) {
426             throw service_description_exc(service_name, num_err_msg);
427         }
428         return v;
429     }
430     catch (std::out_of_range &exc) {
431         throw service_description_exc(service_name, num_err_msg);
432     }
433     catch (std::invalid_argument &exc) {
434         throw service_description_exc(service_name, num_err_msg);
435     }
436 }
437
438 // In a vector, find or create rlimits for a particular resource type.
439 inline service_rlimits &find_rlimits(std::vector<service_rlimits> &all_rlimits, int resource_id)
440 {
441     for (service_rlimits &limits : all_rlimits) {
442         if (limits.resource_id == resource_id) {
443             return limits;
444         }
445     }
446
447     all_rlimits.emplace_back(resource_id);
448     return all_rlimits.back();
449 }
450
451 // Parse resource limits setting (can specify both hard and soft limit).
452 inline void parse_rlimit(const std::string &line, const std::string &service_name, const char *param_name,
453         service_rlimits &rlimit)
454 {
455     // Examples:
456     // 4:5 - soft:hard limits both set
457     // 4:-   soft set, hard set to unlimited
458     // 4:    soft set, hard limit unchanged
459     // 4     soft and hard limit set to same limit
460
461     if (line.empty()) {
462         throw service_description_exc(service_name, std::string(param_name) + ": Bad value.");
463     }
464
465     const char *cline = line.c_str();
466     rlimit.hard_set = rlimit.soft_set = false;
467
468     try {
469         const char * index = cline;
470         errno = 0;
471         if (cline[0] != ':') {
472             rlimit.soft_set = true;
473             if (cline[0] == '-') {
474                 rlimit.limits.rlim_cur = RLIM_INFINITY;
475                 index = cline + 1;
476             }
477             else {
478                 char *nindex;
479                 unsigned long long limit = std::strtoull(cline, &nindex, 0);
480                 index = nindex;
481                 if (errno == ERANGE || limit > std::numeric_limits<rlim_t>::max()) throw std::out_of_range("");
482                 if (index == cline) throw std::invalid_argument("");
483                 rlimit.limits.rlim_cur = limit;
484             }
485
486             if (*index == 0) {
487                 rlimit.hard_set = true;
488                 rlimit.limits.rlim_max = rlimit.limits.rlim_cur;
489                 return;
490             }
491
492             if (*index != ':') {
493                 throw service_description_exc(service_name, std::string(param_name) + ": Bad value.");
494             }
495         }
496
497         index++;
498         if (*index == 0) return;
499
500         if (*index == '-') {
501             rlimit.limits.rlim_max = RLIM_INFINITY;
502             if (index[1] != 0) {
503                 throw service_description_exc(service_name, std::string(param_name) + ": Bad value.");
504             }
505         }
506         else {
507             const char *hard_start = index;
508             char *nindex;
509             unsigned long long limit = std::strtoull(cline, &nindex, 0);
510             index = nindex;
511             if (errno == ERANGE || limit > std::numeric_limits<rlim_t>::max()) throw std::out_of_range("");
512             if (index == hard_start) throw std::invalid_argument("");
513             rlimit.limits.rlim_max = limit;
514         }
515     }
516     catch (std::invalid_argument &exc) {
517         throw service_description_exc(service_name, std::string(param_name) + ": Bad value.");
518     }
519     catch (std::out_of_range &exc) {
520         throw service_description_exc(service_name, std::string(param_name) + ": Too-large value.");
521     }
522 }
523
524 // Process an opened service file, line by line.
525 //    name - the service name
526 //    service_file - the service file input stream
527 //    func - a function of the form:
528 //             void(string &line, string &setting, string_iterator i, string_iterator end)
529 //           Called with:
530 //               line - the complete line (excluding newline character)
531 //               setting - the setting name, from the beginning of the line
532 //               i - iterator at the beginning of the setting value
533 //               end - iterator marking the end of the line
534 //
535 // May throw service load exceptions or I/O exceptions if enabled on stream.
536 template <typename T>
537 void process_service_file(string name, std::istream &service_file, T func)
538 {
539     string line;
540
541     while (getline(service_file, line)) {
542         string::iterator i = line.begin();
543         string::iterator end = line.end();
544
545         i = skipws(i, end);
546         if (i != end) {
547             if (*i == '#') {
548                 continue;  // comment line
549             }
550             string setting = read_setting_name(i, end);
551             i = skipws(i, end);
552             if (i == end || (*i != '=' && *i != ':')) {
553                 throw service_description_exc(name, "Badly formed line.");
554             }
555             i = skipws(++i, end);
556
557             func(line, setting, i, end);
558         }
559     }
560 }
561
562 // A wrapper type for service parameters. It is parameterised by dependency type.
563 template <class dep_type>
564 class service_settings_wrapper
565 {
566     template <typename A, typename B> using pair = std::pair<A,B>;
567     template <typename A> using list = std::list<A>;
568
569     public:
570
571     string command;
572     list<pair<unsigned,unsigned>> command_offsets;
573     string stop_command;
574     list<pair<unsigned,unsigned>> stop_command_offsets;
575     string working_dir;
576     string pid_file;
577     string env_file;
578
579     bool do_sub_vars = false;
580
581     service_type_t service_type = service_type_t::PROCESS;
582     std::list<dep_type> depends;
583     string logfile;
584     service_flags_t onstart_flags;
585     int term_signal = -1;  // additional termination signal
586     bool auto_restart = false;
587     bool smooth_recovery = false;
588     string socket_path;
589     int socket_perms = 0666;
590     // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
591     // invalid value, so it's safe to assume that we can do the same:
592     uid_t socket_uid = -1;
593     gid_t socket_gid = -1;
594     // Restart limit interval / count; default is 10 seconds, 3 restarts:
595     timespec restart_interval = { .tv_sec = 10, .tv_nsec = 0 };
596     int max_restarts = 3;
597     timespec restart_delay = { .tv_sec = 0, .tv_nsec = 200000000 };
598     timespec stop_timeout = { .tv_sec = 10, .tv_nsec = 0 };
599     timespec start_timeout = { .tv_sec = 60, .tv_nsec = 0 };
600     std::vector<service_rlimits> rlimits;
601
602     int readiness_fd = -1;      // readiness fd in service process
603     std::string readiness_var;  // environment var to hold readiness fd
604
605     uid_t run_as_uid = -1;
606     gid_t run_as_gid = -1;
607
608     string chain_to_name;
609
610     #if USE_UTMPX
611     char inittab_id[sizeof(utmpx().ut_id)] = {0};
612     char inittab_line[sizeof(utmpx().ut_line)] = {0};
613     #endif
614 };
615
616 // Process a service description line. In general, parse the setting value and record the parsed value
617 // in a service settings wrapper object. Errors will be reported via service_description_exc exception.
618 //
619 // type parameters:
620 //     settings_wrapper : wrapper for service settings
621 //     load_service_t   : type of load service function/lambda (see below)
622 //     process_dep_dir_t : type of process_dep_dir funciton/lambda (see below)
623 //
624 // parameters:
625 //     settings     : wrapper object for service settings
626 //     name         : name of the service being processed
627 //     line         : the current line of the service description file
628 //     setting      : the name of the setting (from the beginning of line)
629 //     i            : iterator at beginning of setting value (including whitespace)
630 //     end          : iterator at end of line
631 //     load_service : function to load a service
632 //                      arguments:  const char *service_name
633 //                      return: a value that can be used (with a dependency type) to construct a dependency
634 //                              in the 'depends' vector within the 'settings' object
635 //     process_dep_dir : function to process a dependency directory
636 //                      arguments: decltype(settings.depends) &dependencies
637 //                                 const string &waitsford - directory as specified in parameter
638 //                                 dependency_type dep_type - type of dependency to add
639 template <typename settings_wrapper,
640     typename load_service_t,
641     typename process_dep_dir_t>
642 void process_service_line(settings_wrapper &settings, const char *name, string &line, string &setting,
643         string::iterator &i, string::iterator &end, load_service_t load_service,
644         process_dep_dir_t process_dep_dir)
645 {
646     if (setting == "command") {
647         settings.command = read_setting_value(i, end, &settings.command_offsets);
648     }
649     else if (setting == "working-dir") {
650         settings.working_dir = read_setting_value(i, end, nullptr);
651     }
652     else if (setting == "env-file") {
653         settings.env_file = read_setting_value(i, end, nullptr);
654     }
655     else if (setting == "socket-listen") {
656         settings.socket_path = read_setting_value(i, end, nullptr);
657     }
658     else if (setting == "socket-permissions") {
659         string sock_perm_str = read_setting_value(i, end, nullptr);
660         std::size_t ind = 0;
661         try {
662             settings.socket_perms = std::stoi(sock_perm_str, &ind, 8);
663             if (ind != sock_perm_str.length()) {
664                 throw std::logic_error("");
665             }
666         }
667         catch (std::logic_error &exc) {
668             throw service_description_exc(name, "socket-permissions: Badly-formed or "
669                     "out-of-range numeric value");
670         }
671     }
672     else if (setting == "socket-uid") {
673         string sock_uid_s = read_setting_value(i, end, nullptr);
674         settings.socket_uid = parse_uid_param(sock_uid_s, name, "socket-uid", &settings.socket_gid);
675     }
676     else if (setting == "socket-gid") {
677         string sock_gid_s = read_setting_value(i, end, nullptr);
678         settings.socket_gid = parse_gid_param(sock_gid_s, "socket-gid", name);
679     }
680     else if (setting == "stop-command") {
681         settings.stop_command = read_setting_value(i, end, &settings.stop_command_offsets);
682     }
683     else if (setting == "pid-file") {
684         settings.pid_file = read_setting_value(i, end);
685     }
686     else if (setting == "depends-on") {
687         string dependency_name = read_setting_value(i, end);
688         settings.depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::REGULAR);
689     }
690     else if (setting == "depends-ms") {
691         string dependency_name = read_setting_value(i, end);
692         settings.depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::MILESTONE);
693     }
694     else if (setting == "waits-for") {
695         string dependency_name = read_setting_value(i, end);
696         settings.depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::WAITS_FOR);
697     }
698     else if (setting == "waits-for.d") {
699         string waitsford = read_setting_value(i, end);
700         process_dep_dir(settings.depends, waitsford, dependency_type::WAITS_FOR);
701     }
702     else if (setting == "logfile") {
703         settings.logfile = read_setting_value(i, end);
704     }
705     else if (setting == "restart") {
706         string restart = read_setting_value(i, end);
707         settings.auto_restart = (restart == "yes" || restart == "true");
708     }
709     else if (setting == "smooth-recovery") {
710         string recovery = read_setting_value(i, end);
711         settings.smooth_recovery = (recovery == "yes" || recovery == "true");
712     }
713     else if (setting == "type") {
714         string type_str = read_setting_value(i, end);
715         if (type_str == "scripted") {
716             settings.service_type = service_type_t::SCRIPTED;
717         }
718         else if (type_str == "process") {
719             settings.service_type = service_type_t::PROCESS;
720         }
721         else if (type_str == "bgprocess") {
722             settings.service_type = service_type_t::BGPROCESS;
723         }
724         else if (type_str == "internal") {
725             settings.service_type = service_type_t::INTERNAL;
726         }
727         else {
728             throw service_description_exc(name, "Service type must be one of: \"scripted\","
729                 " \"process\", \"bgprocess\" or \"internal\"");
730         }
731     }
732     else if (setting == "options") {
733         std::list<std::pair<unsigned,unsigned>> indices;
734         string onstart_cmds = read_setting_value(i, end, &indices);
735         for (auto indexpair : indices) {
736             string option_txt = onstart_cmds.substr(indexpair.first,
737                     indexpair.second - indexpair.first);
738             if (option_txt == "starts-rwfs") {
739                 settings.onstart_flags.rw_ready = true;
740             }
741             else if (option_txt == "starts-log") {
742                 settings.onstart_flags.log_ready = true;
743             }
744             else if (option_txt == "no-sigterm") {
745                 settings.onstart_flags.no_sigterm = true;
746             }
747             else if (option_txt == "runs-on-console") {
748                 settings.onstart_flags.runs_on_console = true;
749                 // A service that runs on the console necessarily starts on console:
750                 settings.onstart_flags.starts_on_console = true;
751                 settings.onstart_flags.shares_console = false;
752             }
753             else if (option_txt == "starts-on-console") {
754                 settings.onstart_flags.starts_on_console = true;
755                 settings.onstart_flags.shares_console = false;
756             }
757             else if (option_txt == "shares-console") {
758                 settings.onstart_flags.shares_console = true;
759                 settings.onstart_flags.runs_on_console = false;
760                 settings.onstart_flags.starts_on_console = false;
761             }
762             else if (option_txt == "pass-cs-fd") {
763                 settings.onstart_flags.pass_cs_fd = true;
764             }
765             else if (option_txt == "start-interruptible") {
766                 settings.onstart_flags.start_interruptible = true;
767             }
768             else if (option_txt == "skippable") {
769                 settings.onstart_flags.skippable = true;
770             }
771             else if (option_txt == "signal-process-only") {
772                 settings.onstart_flags.signal_process_only = true;
773             }
774             else {
775                 throw service_description_exc(name, "Unknown option: " + option_txt);
776             }
777         }
778     }
779     else if (setting == "load-options") {
780         std::list<std::pair<unsigned,unsigned>> indices;
781         string load_opts = read_setting_value(i, end, &indices);
782         for (auto indexpair : indices) {
783             string option_txt = load_opts.substr(indexpair.first,
784                     indexpair.second - indexpair.first);
785             if (option_txt == "sub-vars") {
786                 // substitute environment variables in command line
787                 settings.do_sub_vars = true;
788             }
789             else if (option_txt == "no-sub-vars") {
790                 settings.do_sub_vars = false;
791             }
792             else {
793                 throw service_description_exc(name, "Unknown load option: " + option_txt);
794             }
795         }
796     }
797     else if (setting == "term-signal" || setting == "termsignal") {
798         // Note: "termsignal" supported for legacy reasons.
799         string signame = read_setting_value(i, end, nullptr);
800         int signo = signal_name_to_number(signame);
801         if (signo == -1) {
802             throw service_description_exc(name, "Unknown/unsupported termination signal: "
803                     + signame);
804         }
805         else {
806             settings.term_signal = signo;
807         }
808     }
809     else if (setting == "restart-limit-interval") {
810         string interval_str = read_setting_value(i, end, nullptr);
811         parse_timespec(interval_str, name, "restart-limit-interval", settings.restart_interval);
812     }
813     else if (setting == "restart-delay") {
814         string rsdelay_str = read_setting_value(i, end, nullptr);
815         parse_timespec(rsdelay_str, name, "restart-delay", settings.restart_delay);
816     }
817     else if (setting == "restart-limit-count") {
818         string limit_str = read_setting_value(i, end, nullptr);
819         settings.max_restarts = parse_unum_param(limit_str, name, std::numeric_limits<int>::max());
820     }
821     else if (setting == "stop-timeout") {
822         string stoptimeout_str = read_setting_value(i, end, nullptr);
823         parse_timespec(stoptimeout_str, name, "stop-timeout", settings.stop_timeout);
824     }
825     else if (setting == "start-timeout") {
826         string starttimeout_str = read_setting_value(i, end, nullptr);
827         parse_timespec(starttimeout_str, name, "start-timeout", settings.start_timeout);
828     }
829     else if (setting == "run-as") {
830         string run_as_str = read_setting_value(i, end, nullptr);
831         settings.run_as_uid = parse_uid_param(run_as_str, name, "run-as", &settings.run_as_gid);
832     }
833     else if (setting == "chain-to") {
834         settings.chain_to_name = read_setting_value(i, end, nullptr);
835     }
836     else if (setting == "ready-notification") {
837         string notify_setting = read_setting_value(i, end, nullptr);
838         if (starts_with(notify_setting, "pipefd:")) {
839             settings.readiness_fd = parse_unum_param(notify_setting.substr(7 /* len 'pipefd:' */),
840                     name, std::numeric_limits<int>::max());
841         }
842         else if (starts_with(notify_setting, "pipevar:")) {
843             settings.readiness_var = notify_setting.substr(8 /* len 'pipevar:' */);
844             if (settings.readiness_var.empty()) {
845                 throw service_description_exc(name, "Invalid pipevar variable name "
846                         "in ready-notification");
847             }
848         }
849         else {
850             throw service_description_exc(name, "Unknown ready-notification setting: "
851                     + notify_setting);
852         }
853     }
854     else if (setting == "inittab-id") {
855         string inittab_setting = read_setting_value(i, end, nullptr);
856         #if USE_UTMPX
857         if (inittab_setting.length() > sizeof(settings.inittab_id)) {
858             throw service_description_exc(name, "inittab-id setting is too long");
859         }
860         strncpy(settings.inittab_id, inittab_setting.c_str(), sizeof(settings.inittab_id));
861         #endif
862     }
863     else if (setting == "inittab-line") {
864         string inittab_setting = read_setting_value(i, end, nullptr);
865         #if USE_UTMPX
866         if (inittab_setting.length() > sizeof(settings.inittab_line)) {
867             throw service_description_exc(name, "inittab-line setting is too long");
868         }
869         strncpy(settings.inittab_line, inittab_setting.c_str(), sizeof(settings.inittab_line));
870         #endif
871     }
872     else if (setting == "rlimit-nofile") {
873         string nofile_setting = read_setting_value(i, end, nullptr);
874         service_rlimits &nofile_limits = find_rlimits(settings.rlimits, RLIMIT_NOFILE);
875         parse_rlimit(line, name, "rlimit-nofile", nofile_limits);
876     }
877     else if (setting == "rlimit-core") {
878         string nofile_setting = read_setting_value(i, end, nullptr);
879         service_rlimits &nofile_limits = find_rlimits(settings.rlimits, RLIMIT_CORE);
880         parse_rlimit(line, name, "rlimit-core", nofile_limits);
881     }
882     else if (setting == "rlimit-data") {
883         string nofile_setting = read_setting_value(i, end, nullptr);
884         service_rlimits &nofile_limits = find_rlimits(settings.rlimits, RLIMIT_DATA);
885         parse_rlimit(line, name, "rlimit-data", nofile_limits);
886     }
887     else if (setting == "rlimit-addrspace") {
888         #if defined(RLIMIT_AS)
889             string nofile_setting = read_setting_value(i, end, nullptr);
890             service_rlimits &nofile_limits = find_rlimits(settings.rlimits, RLIMIT_AS);
891             parse_rlimit(line, name, "rlimit-addrspace", nofile_limits);
892         #endif
893     }
894     else {
895         throw service_description_exc(name, "Unknown setting: " + setting);
896     }
897 }
898
899 } // namespace dinit_load
900
901 using dinit_load::process_service_file;