build: add target to run cppcheck.
[oweals/dinit.git] / src / load-service.cc
1 #include <algorithm>
2 #include <string>
3 #include <fstream>
4 #include <locale>
5 #include <iostream>
6 #include <limits>
7
8 #include <cstring>
9 #include <cstdlib>
10
11 #include <sys/stat.h>
12 #include <sys/types.h>
13 #include <pwd.h>
14 #include <grp.h>
15 #include <dirent.h>
16
17 #include "proc-service.h"
18 #include "dinit-log.h"
19
20 using string = std::string;
21 using string_iterator = std::string::iterator;
22
23 // Utility function to skip white space. Returns an iterator at the
24 // first non-white-space position (or at end).
25 static string_iterator skipws(string_iterator i, string_iterator end)
26 {
27     using std::locale;
28     using std::isspace;
29     
30     while (i != end) {
31       if (! isspace(*i, locale::classic())) {
32         break;
33       }
34       ++i;
35     }
36     return i;
37 }
38
39 // Read a setting name.
40 static string read_setting_name(string_iterator & i, string_iterator end)
41 {
42     using std::locale;
43     using std::ctype;
44     using std::use_facet;
45     
46     const ctype<char> & facet = use_facet<ctype<char> >(locale::classic());
47
48     string rval;
49     // Allow alphabetical characters, and dash (-) in setting name
50     while (i != end && (*i == '-' || facet.is(ctype<char>::alpha, *i))) {
51         rval += *i;
52         ++i;
53     }
54     return rval;
55 }
56
57 namespace {
58     class setting_exception
59     {
60         std::string info;
61         
62         public:
63         setting_exception(const std::string &&exc_info) : info(std::move(exc_info))
64         {
65         }
66         
67         std::string &get_info()
68         {
69             return info;
70         }
71     };
72 }
73
74
75 // Read a setting value
76 //
77 // In general a setting value is a single-line string. It may contain multiple parts
78 // separated by white space (which is normally collapsed). A hash mark - # - denotes
79 // the end of the value and the beginning of a comment (it should be preceded by
80 // whitespace).
81 //
82 // Part of a value may be quoted using double quote marks, which prevents collapse
83 // of whitespace and interpretation of most special characters (the quote marks will
84 // not be considered part of the value). A backslash can precede a character (such
85 // as '#' or '"' or another backslash) to remove its special meaning. Newline
86 // characters are not allowed in values and cannot be quoted.
87 //
88 // This function expects the string to be in an ASCII-compatible, single byte
89 // encoding (the "classic" locale).
90 //
91 // Params:
92 //    service_name - the name of the service to which the setting applies
93 //    i  -  reference to string iterator through the line
94 //    end -   iterator at end of line
95 //    part_positions -  list of <int,int> to which the position of each setting value
96 //                      part will be added as [start,end). May be null.
97 static string read_setting_value(string_iterator & i, string_iterator end,
98         std::list<std::pair<unsigned,unsigned>> * part_positions = nullptr)
99 {
100     using std::locale;
101     using std::isspace;
102
103     i = skipws(i, end);
104     
105     string rval;
106     bool new_part = true;
107     int part_start;
108     
109     while (i != end) {
110         char c = *i;
111         if (c == '\"') {
112             if (new_part) {
113                 part_start = rval.length();
114                 new_part = false;
115             }
116             // quoted string
117             ++i;
118             while (i != end) {
119                 c = *i;
120                 if (c == '\"') break;
121                 if (c == '\n') {
122                     throw setting_exception("Line end inside quoted string");
123                 }
124                 else if (c == '\\') {
125                     // A backslash escapes the following character.
126                     ++i;
127                     if (i != end) {
128                         c = *i;
129                         if (c == '\n') {
130                             throw setting_exception("Line end follows backslash escape character (`\\')");
131                         }
132                         rval += c;
133                     }
134                 }
135                 else {
136                     rval += c;
137                 }
138                 ++i;
139             }
140             if (i == end) {
141                 // String wasn't terminated
142                 throw setting_exception("Unterminated quoted string");
143             }
144         }
145         else if (c == '\\') {
146             if (new_part) {
147                 part_start = rval.length();
148                 new_part = false;
149             }
150             // A backslash escapes the next character
151             ++i;
152             if (i != end) {
153                 rval += *i;
154             }
155             else {
156                 throw setting_exception("Backslash escape (`\\') not followed by character");
157             }
158         }
159         else if (isspace(c, locale::classic())) {
160             if (! new_part && part_positions != nullptr) {
161                 part_positions->emplace_back(part_start, rval.length());
162                 new_part = true;
163             }
164             i = skipws(i, end);
165             if (i == end) break;
166             if (*i == '#') break; // comment
167             rval += ' ';  // collapse ws to a single space
168             continue;
169         }
170         else if (c == '#') {
171             // Possibly intended a comment; we require leading whitespace to reduce occurrence of accidental
172             // comments in setting values.
173             throw setting_exception("hashmark (`#') comment must be separated from setting value by whitespace");
174         }
175         else {
176             if (new_part) {
177                 part_start = rval.length();
178                 new_part = false;
179             }
180             rval += c;
181         }
182         ++i;
183     }
184
185     // Got to end:
186     if (part_positions != nullptr) {
187         part_positions->emplace_back(part_start, rval.length());
188     }
189
190     return rval;
191 }
192
193 static int signal_name_to_number(std::string &signame)
194 {
195     if (signame == "HUP") return SIGHUP;
196     if (signame == "INT") return SIGINT;
197     if (signame == "QUIT") return SIGQUIT;
198     if (signame == "USR1") return SIGUSR1;
199     if (signame == "USR2") return SIGUSR2;
200     return -1;
201 }
202
203 static const char * uid_err_msg = "Specified user id contains invalid numeric characters or is outside allowed range.";
204
205 // Parse a userid parameter which may be a numeric user ID or a username. If a name, the
206 // userid is looked up via the system user database (getpwnam() function). In this case,
207 // the associated group is stored in the location specified by the group_p parameter iff
208 // it is not null and iff it contains the value -1.
209 static uid_t parse_uid_param(const std::string &param, const std::string &service_name, gid_t *group_p)
210 {
211     // Could be a name or a numeric id. But we should assume numeric first, just in case
212     // a user manages to give themselves a username that parses as a number.
213     std::size_t ind = 0;
214     try {
215         // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
216         // is is probably safe to assume that valid values are positive. We'll also assert
217         // that the value range fits within "unsigned long long" since it seems unlikely
218         // that would ever not be the case.
219         static_assert((uintmax_t)std::numeric_limits<uid_t>::max()
220                 <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "uid_t is too large");
221         unsigned long long v = std::stoull(param, &ind, 0);
222         if (v > static_cast<unsigned long long>(std::numeric_limits<uid_t>::max()) || ind != param.length()) {
223             throw service_description_exc(service_name, uid_err_msg);
224         }
225         return v;
226     }
227     catch (std::out_of_range &exc) {
228         throw service_description_exc(service_name, uid_err_msg);
229     }
230     catch (std::invalid_argument &exc) {
231         // Ok, so it doesn't look like a number: proceed...
232     }
233
234     errno = 0;
235     struct passwd * pwent = getpwnam(param.c_str());
236     if (pwent == nullptr) {
237         // Maybe an error, maybe just no entry.
238         if (errno == 0) {
239             throw service_description_exc(service_name, "Specified user \"" + param + "\" does not exist in system database.");
240         }
241         else {
242             throw service_description_exc(service_name, std::string("Error accessing user database: ") + strerror(errno));
243         }
244     }
245     
246     if (group_p && *group_p != (gid_t)-1) {
247         *group_p = pwent->pw_gid;
248     }
249     
250     return pwent->pw_uid;
251 }
252
253 static const char * num_err_msg = "Specified value contains invalid numeric characters or is outside allowed range.";
254
255 // Parse an unsigned numeric parameter value
256 static unsigned long long parse_unum_param(const std::string &param, const std::string &service_name,
257         unsigned long long max = std::numeric_limits<unsigned long long>::max())
258 {
259     std::size_t ind = 0;
260     try {
261         unsigned long long v = std::stoull(param, &ind, 0);
262         if (v > max || ind != param.length()) {
263             throw service_description_exc(service_name, num_err_msg);
264         }
265         return v;
266     }
267     catch (std::out_of_range &exc) {
268         throw service_description_exc(service_name, num_err_msg);
269     }
270     catch (std::invalid_argument &exc) {
271         throw service_description_exc(service_name, num_err_msg);
272     }
273 }
274
275 static const char * gid_err_msg = "Specified group id contains invalid numeric characters or is outside allowed range.";
276
277 static gid_t parse_gid_param(const std::string &param, const std::string &service_name)
278 {
279     // Could be a name or a numeric id. But we should assume numeric first, just in case
280     // a user manages to give themselves a username that parses as a number.
281     std::size_t ind = 0;
282     try {
283         // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
284         // is is probably safe to assume that valid values are positive. We'll also assume
285         // that the value range fits with "unsigned long long" since it seems unlikely
286         // that would ever not be the case.
287         static_assert((uintmax_t)std::numeric_limits<gid_t>::max()
288                 <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "gid_t is too large");
289         unsigned long long v = std::stoull(param, &ind, 0);
290         if (v > static_cast<unsigned long long>(std::numeric_limits<gid_t>::max()) || ind != param.length()) {
291             throw service_description_exc(service_name, gid_err_msg);
292         }
293         return v;
294     }
295     catch (std::out_of_range &exc) {
296         throw service_description_exc(service_name, gid_err_msg);
297     }
298     catch (std::invalid_argument &exc) {
299         // Ok, so it doesn't look like a number: proceed...
300     }
301
302     errno = 0;
303     struct group * grent = getgrnam(param.c_str());
304     if (grent == nullptr) {
305         // Maybe an error, maybe just no entry.
306         if (errno == 0) {
307             throw service_description_exc(service_name, "Specified group \"" + param + "\" does not exist in system database.");
308         }
309         else {
310             throw service_description_exc(service_name, std::string("Error accessing group database: ") + strerror(errno));
311         }
312     }
313     
314     return grent->gr_gid;
315 }
316
317 // Parse a time, specified as a decimal number of seconds (with optional fractional component after decimal
318 // point or decimal comma).
319 //
320 static void parse_timespec(const std::string &paramval, const std::string &servicename,
321         const char * paramname, timespec &ts)
322 {
323     decltype(ts.tv_sec) isec = 0;
324     decltype(ts.tv_nsec) insec = 0;
325     auto max_secs = std::numeric_limits<decltype(isec)>::max() / 10;
326     auto len = paramval.length();
327     decltype(len) i;
328     for (i = 0; i < len; i++) {
329         char ch = paramval[i];
330         if (ch == '.' || ch == ',') {
331             i++;
332             break;
333         }
334         if (ch < '0' || ch > '9') {
335             throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
336         }
337         // check for overflow
338         if (isec >= max_secs) {
339            throw service_description_exc(servicename, std::string("Too-large value for ") + paramname);
340         }
341         isec *= 10;
342         isec += ch - '0';
343     }
344     decltype(insec) insec_m = 100000000; // 10^8
345     for ( ; i < len; i++) {
346         char ch = paramval[i];
347         if (ch < '0' || ch > '9') {
348             throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
349         }
350         insec += (ch - '0') * insec_m;
351         insec_m /= 10;
352     }
353     ts.tv_sec = isec;
354     ts.tv_nsec = insec;
355 }
356
357 // Perform environment variable substitution on a command line, if specified.
358 //   line -  the string storing the command and arguments
359 //   offsets - the [start,end) pair of offsets of the command and each argument within the string
360 //
361 static void do_env_subst(std::string &line, std::list<std::pair<unsigned,unsigned>> &offsets, bool do_sub_vars)
362 {
363     if (do_sub_vars) {
364         auto i = offsets.begin();
365         std::string r_line = line.substr(i->first, i->second - i->first); // copy command part
366         for (++i; i != offsets.end(); ++i) {
367             auto &offset_pair = *i;
368             if (line[offset_pair.first] == '$') {
369                 // Do subsitution for this part:
370                 auto env_name = line.substr(offset_pair.first + 1, offset_pair.second - offset_pair.first - 1);
371                 char *env_val = getenv(env_name.c_str());
372                 if (env_val != nullptr) {
373                     auto val_len = strlen(env_val);
374                     r_line += " ";
375                     offset_pair.first = r_line.length();
376                     offset_pair.second = offset_pair.first + val_len;
377                     r_line += env_val;
378                 }
379                 else {
380                     // specified enironment variable not set: treat as an empty string
381                     offset_pair.first = r_line.length();
382                     offset_pair.second = offset_pair.first;
383                 }
384             }
385             else {
386                 // No subsitution for this part:
387                 r_line += " ";
388                 auto new_offs = r_line.length();
389                 auto len = offset_pair.second - offset_pair.first;
390                 r_line += line.substr(offset_pair.first, len);
391                 offset_pair.first = new_offs;
392                 offset_pair.second = new_offs + len;
393             }
394         }
395         line = std::move(r_line);
396     }
397 }
398
399 // Process a dependency directory - filenames contained within correspond to service names which
400 // are loaded and added as a dependency of the given type. Expected use is with a directory
401 // containing symbolic links to other service descriptions, but this isn't required.
402 // Failure to read the directory contents, or to find a service listed within, is not considered
403 // a fatal error.
404 static void process_dep_dir(dirload_service_set &sset,
405         const char *servicename,
406         std::list<prelim_dep> &deplist, const std::string &depdirpath,
407         dependency_type dep_type)
408 {
409     DIR *depdir = opendir(depdirpath.c_str());
410     if (depdir == nullptr) {
411         log(loglevel_t::WARN, "Could not open dependency directory '", depdirpath,
412                 "' for ", servicename, " service.");
413         return;
414     }
415
416     errno = 0;
417     dirent * dent = readdir(depdir);
418     while (dent != nullptr) {
419         char * name =  dent->d_name;
420         if (name[0] != '.') {
421             try {
422                 service_record * sr = sset.load_service(name);
423                 deplist.emplace_back(sr, dep_type);
424             }
425             catch (service_not_found &) {
426                 log(loglevel_t::WARN, "Ignoring unresolved dependency '", name,
427                         "' in dependency directory '", depdirpath,
428                         "' for ", servicename, " service.");
429             }
430         }
431         dent = readdir(depdir);
432     }
433
434     if (errno != 0) {
435         log(loglevel_t::WARN, "Error reading dependency directory '", depdirpath,
436                 "' for ", servicename, " service.");
437     }
438 }
439
440 // Find a service record, or load it from file. If the service has
441 // dependencies, load those also.
442 //
443 // Might throw a ServiceLoadExc exception if a dependency cycle is found or if another
444 // problem occurs (I/O error, service description not found etc). Throws std::bad_alloc
445 // if a memory allocation failure occurs.
446 //
447 service_record * dirload_service_set::load_service(const char * name)
448 {
449     using std::string;
450     using std::ifstream;
451     using std::ios;
452     using std::ios_base;
453     using std::locale;
454     using std::isspace;
455     
456     using std::list;
457     using std::pair;
458     
459     // First try and find an existing record...
460     service_record * rval = find_service(string(name));
461     if (rval != 0) {
462         if (rval->is_dummy()) {
463             throw service_cyclic_dependency(name);
464         }
465         return rval;
466     }
467
468     ifstream service_file;
469
470     // Couldn't find one. Have to load it.
471     for (auto &service_dir : service_dirs) {
472         string service_filename = service_dir.get_dir();
473         if (*(service_filename.rbegin()) != '/') {
474             service_filename += '/';
475         }
476         service_filename += name;
477
478         service_file.open(service_filename.c_str(), ios::in);
479         if (service_file) break;
480     }
481     
482     if (! service_file) {
483         throw service_not_found(string(name));
484     }
485
486     string command;
487     list<pair<unsigned,unsigned>> command_offsets;
488     string stop_command;
489     list<pair<unsigned,unsigned>> stop_command_offsets;
490     string working_dir;
491     string pid_file;
492
493     bool do_sub_vars = false;
494
495     service_type_t service_type = service_type_t::PROCESS;
496     std::list<prelim_dep> depends;
497     string logfile;
498     service_flags_t onstart_flags;
499     int term_signal = -1;  // additional termination signal
500     bool auto_restart = false;
501     bool smooth_recovery = false;
502     string socket_path;
503     int socket_perms = 0666;
504     // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
505     // invalid value, so it's safe to assume that we can do the same:
506     uid_t socket_uid = -1;
507     gid_t socket_gid = -1;
508     // Restart limit interval / count; default is 10 seconds, 3 restarts:
509     timespec restart_interval = { .tv_sec = 10, .tv_nsec = 0 };
510     int max_restarts = 3;
511     timespec restart_delay = { .tv_sec = 0, .tv_nsec = 200000000 };
512     timespec stop_timeout = { .tv_sec = 10, .tv_nsec = 0 };
513     timespec start_timeout = { .tv_sec = 60, .tv_nsec = 0 };
514     
515     uid_t run_as_uid = -1;
516     gid_t run_as_gid = -1;
517
518     string line;
519     service_file.exceptions(ios::badbit | ios::failbit);
520     
521     // Add a dummy service record now to prevent infinite recursion in case of cyclic dependency.
522     // We replace this with the real service later (or remove it if we find a configuration error).
523     rval = new service_record(this, string(name));
524     add_service(rval);
525     
526     try {
527         // getline can set failbit if it reaches end-of-file, we don't want an exception in that case:
528         service_file.exceptions(ios::badbit);
529         
530         while (getline(service_file, line)) {
531             string::iterator i = line.begin();
532             string::iterator end = line.end();
533           
534             i = skipws(i, end);
535             if (i != end) {
536                 if (*i == '#') {
537                     continue;  // comment line
538                 }
539                 string setting = read_setting_name(i, end);
540                 i = skipws(i, end);
541                 if (i == end || (*i != '=' && *i != ':')) {
542                     throw service_description_exc(name, "Badly formed line.");
543                 }
544                 i = skipws(++i, end);
545                 
546                 if (setting == "command") {
547                     command = read_setting_value(i, end, &command_offsets);
548                 }
549                 else if (setting == "working-dir") {
550                     working_dir = read_setting_value(i, end, nullptr);
551                 }
552                 else if (setting == "socket-listen") {
553                     socket_path = read_setting_value(i, end, nullptr);
554                 }
555                 else if (setting == "socket-permissions") {
556                     string sock_perm_str = read_setting_value(i, end, nullptr);
557                     std::size_t ind = 0;
558                     try {
559                         socket_perms = std::stoi(sock_perm_str, &ind, 8);
560                         if (ind != sock_perm_str.length()) {
561                             throw std::logic_error("");
562                         }
563                     }
564                     catch (std::logic_error &exc) {
565                         throw service_description_exc(name, "socket-permissions: Badly-formed or out-of-range numeric value");
566                     }
567                 }
568                 else if (setting == "socket-uid") {
569                     string sock_uid_s = read_setting_value(i, end, nullptr);
570                     socket_uid = parse_uid_param(sock_uid_s, name, &socket_gid);
571                 }
572                 else if (setting == "socket-gid") {
573                     string sock_gid_s = read_setting_value(i, end, nullptr);
574                     socket_gid = parse_gid_param(sock_gid_s, name);
575                 }
576                 else if (setting == "stop-command") {
577                     stop_command = read_setting_value(i, end, &stop_command_offsets);
578                 }
579                 else if (setting == "pid-file") {
580                     pid_file = read_setting_value(i, end);
581                 }
582                 else if (setting == "depends-on") {
583                     string dependency_name = read_setting_value(i, end);
584                     depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::REGULAR);
585                 }
586                 else if (setting == "depends-ms") {
587                     string dependency_name = read_setting_value(i, end);
588                     depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::MILESTONE);
589                 }
590                 else if (setting == "waits-for") {
591                     string dependency_name = read_setting_value(i, end);
592                     depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::WAITS_FOR);
593                 }
594                 else if (setting == "waits-for.d") {
595                     string waitsford = read_setting_value(i, end);
596                     process_dep_dir(*this, name, depends, waitsford, dependency_type::WAITS_FOR);
597                 }
598                 else if (setting == "logfile") {
599                     logfile = read_setting_value(i, end);
600                 }
601                 else if (setting == "restart") {
602                     string restart = read_setting_value(i, end);
603                     auto_restart = (restart == "yes" || restart == "true");
604                 }
605                 else if (setting == "smooth-recovery") {
606                     string recovery = read_setting_value(i, end);
607                     smooth_recovery = (recovery == "yes" || recovery == "true");
608                 }
609                 else if (setting == "type") {
610                     string type_str = read_setting_value(i, end);
611                     if (type_str == "scripted") {
612                         service_type = service_type_t::SCRIPTED;
613                     }
614                     else if (type_str == "process") {
615                         service_type = service_type_t::PROCESS;
616                     }
617                     else if (type_str == "bgprocess") {
618                         service_type = service_type_t::BGPROCESS;
619                     }
620                     else if (type_str == "internal") {
621                         service_type = service_type_t::INTERNAL;
622                     }
623                     else {
624                         throw service_description_exc(name, "Service type must be one of: \"scripted\","
625                             " \"process\", \"bgprocess\" or \"internal\"");
626                     }
627                 }
628                 else if (setting == "options") {
629                     std::list<std::pair<unsigned,unsigned>> indices;
630                     string onstart_cmds = read_setting_value(i, end, &indices);
631                     for (auto indexpair : indices) {
632                         string option_txt = onstart_cmds.substr(indexpair.first, indexpair.second - indexpair.first);
633                         if (option_txt == "starts-rwfs") {
634                             onstart_flags.rw_ready = true;
635                         }
636                         else if (option_txt == "starts-log") {
637                             onstart_flags.log_ready = true;
638                         }
639                         else if (option_txt == "no-sigterm") {
640                             onstart_flags.no_sigterm = true;
641                         }
642                         else if (option_txt == "runs-on-console") {
643                             onstart_flags.runs_on_console = true;
644                             // A service that runs on the console necessarily starts on console:
645                             onstart_flags.starts_on_console = true;
646                         }
647                         else if (option_txt == "starts-on-console") {
648                             onstart_flags.starts_on_console = true;
649                         }
650                         else if (option_txt == "pass-cs-fd") {
651                             onstart_flags.pass_cs_fd = true;
652                         }
653                         else if (option_txt == "start-interruptible") {
654                             onstart_flags.start_interruptible = true;
655                         }
656                         else if (option_txt == "skippable") {
657                             onstart_flags.skippable = true;
658                         }
659                         else if (option_txt == "signal-process-only") {
660                             onstart_flags.signal_process_only = true;
661                         }
662                         else {
663                             throw service_description_exc(name, "Unknown option: " + option_txt);
664                         }
665                     }
666                 }
667                 else if (setting == "load-options") {
668                     std::list<std::pair<unsigned,unsigned>> indices;
669                     string load_opts = read_setting_value(i, end, &indices);
670                     for (auto indexpair : indices) {
671                         string option_txt = load_opts.substr(indexpair.first, indexpair.second - indexpair.first);
672                         if (option_txt == "sub-vars") {
673                             // substitute environment variables in command line
674                             do_sub_vars = true;
675                         }
676                         else if (option_txt == "no-sub-vars") {
677                             do_sub_vars = false;
678                         }
679                         else {
680                             throw service_description_exc(name, "Unknown load option: " + option_txt);
681                         }
682                     }
683                 }
684                 else if (setting == "termsignal") {
685                     string signame = read_setting_value(i, end, nullptr);
686                     int signo = signal_name_to_number(signame);
687                     if (signo == -1) {
688                         throw service_description_exc(name, "Unknown/unsupported termination signal: " + signame);
689                     }
690                     else {
691                         term_signal = signo;
692                     }
693                 }
694                 else if (setting == "restart-limit-interval") {
695                     string interval_str = read_setting_value(i, end, nullptr);
696                     parse_timespec(interval_str, name, "restart-limit-interval", restart_interval);
697                 }
698                 else if (setting == "restart-delay") {
699                     string rsdelay_str = read_setting_value(i, end, nullptr);
700                     parse_timespec(rsdelay_str, name, "restart-delay", restart_delay);
701                 }
702                 else if (setting == "restart-limit-count") {
703                     string limit_str = read_setting_value(i, end, nullptr);
704                     max_restarts = parse_unum_param(limit_str, name, std::numeric_limits<int>::max());
705                 }
706                 else if (setting == "stop-timeout") {
707                     string stoptimeout_str = read_setting_value(i, end, nullptr);
708                     parse_timespec(stoptimeout_str, name, "stop-timeout", stop_timeout);
709                 }
710                 else if (setting == "start-timeout") {
711                     string starttimeout_str = read_setting_value(i, end, nullptr);
712                     parse_timespec(starttimeout_str, name, "start-timeout", start_timeout);
713                 }
714                 else if (setting == "run-as") {
715                     string run_as_str = read_setting_value(i, end, nullptr);
716                     run_as_uid = parse_uid_param(run_as_str, name, &run_as_gid);
717                 }
718                 else {
719                     throw service_description_exc(name, "Unknown setting: " + setting);
720                 }
721             }
722         }
723         
724         service_file.close();
725         
726         if (service_type == service_type_t::PROCESS || service_type == service_type_t::BGPROCESS
727                 || service_type == service_type_t::SCRIPTED) {
728             if (command.length() == 0) {
729                 throw service_description_exc(name, "Service command not specified");
730             }
731         }
732         
733         // Now replace the dummy service record with a real record:
734         for (auto iter = records.begin(); iter != records.end(); iter++) {
735             if (*iter == rval) {
736                 // We've found the dummy record
737                 delete rval;
738                 if (service_type == service_type_t::PROCESS) {
739                     do_env_subst(command, command_offsets, do_sub_vars);
740                     auto rvalps = new process_service(this, string(name), std::move(command),
741                             command_offsets, depends);
742                     rvalps->set_workding_dir(working_dir);
743                     rvalps->set_restart_interval(restart_interval, max_restarts);
744                     rvalps->set_restart_delay(restart_delay);
745                     rvalps->set_stop_timeout(stop_timeout);
746                     rvalps->set_start_timeout(start_timeout);
747                     rvalps->set_extra_termination_signal(term_signal);
748                     rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid);
749                     rvalps->set_workding_dir(working_dir);
750                     // process service start / run on console must be the same:
751                     onstart_flags.starts_on_console = onstart_flags.runs_on_console;
752                     rval = rvalps;
753                 }
754                 else if (service_type == service_type_t::BGPROCESS) {
755                     do_env_subst(command, command_offsets, do_sub_vars);
756                     auto rvalps = new bgproc_service(this, string(name), std::move(command),
757                             command_offsets, depends);
758                     rvalps->set_workding_dir(working_dir);
759                     rvalps->set_pid_file(std::move(pid_file));
760                     rvalps->set_restart_interval(restart_interval, max_restarts);
761                     rvalps->set_restart_delay(restart_delay);
762                     rvalps->set_stop_timeout(stop_timeout);
763                     rvalps->set_start_timeout(start_timeout);
764                     rvalps->set_extra_termination_signal(term_signal);
765                     rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid);
766                     onstart_flags.runs_on_console = false;
767                     rval = rvalps;
768                 }
769                 else if (service_type == service_type_t::SCRIPTED) {
770                     do_env_subst(command, command_offsets, do_sub_vars);
771                     auto rvalps = new scripted_service(this, string(name), std::move(command),
772                             command_offsets, depends);
773                     rvalps->set_stop_command(stop_command, stop_command_offsets);
774                     rvalps->set_workding_dir(working_dir);
775                     rvalps->set_stop_timeout(stop_timeout);
776                     rvalps->set_start_timeout(start_timeout);
777                     rvalps->set_extra_termination_signal(term_signal);
778                     rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid);
779                     rval = rvalps;
780                 }
781                 else {
782                     rval = new service_record(this, string(name), service_type, depends);
783                 }
784                 rval->set_log_file(logfile);
785                 rval->set_auto_restart(auto_restart);
786                 rval->set_smooth_recovery(smooth_recovery);
787                 rval->set_flags(onstart_flags);
788                 rval->set_socket_details(std::move(socket_path), socket_perms, socket_uid, socket_gid);
789                 *iter = rval;
790                 break;
791             }
792         }
793         
794         return rval;
795     }
796     catch (setting_exception &setting_exc)
797     {
798         // Must remove the dummy service record.
799         records.erase(std::find(records.begin(), records.end(), rval));
800         delete rval;
801         throw service_description_exc(name, std::move(setting_exc.get_info()));
802     }
803     catch (...) {
804         // Must remove the dummy service record.
805         records.erase(std::find(records.begin(), records.end(), rval));
806         delete rval;
807         throw;
808     }
809 }