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