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