Update Dasynq to 1.0.2.
[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 "service.h"
14
15 typedef std::string string;
16 typedef std::string::iterator 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 &getInfo()
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 signalNameToNumber(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 static void parse_timespec(const std::string &paramval, const std::string &servicename,
311         const char * paramname, timespec &ts)
312 {
313     decltype(ts.tv_sec) isec = 0;
314     decltype(ts.tv_nsec) insec = 0;
315     auto max_secs = std::numeric_limits<decltype(isec)>::max() / 10;
316     auto len = paramval.length();
317     decltype(len) i;
318     for (i = 0; i < len; i++) {
319         char ch = paramval[i];
320         if (ch == '.') {
321             i++;
322             break;
323         }
324         if (ch < '0' || ch > '9') {
325             throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
326         }
327         // check for overflow
328         if (isec >= max_secs) {
329            throw service_description_exc(servicename, std::string("Too-large value for ") + paramname);
330         }
331         isec *= 10;
332         isec += ch - '0';
333     }
334     decltype(insec) insec_m = 100000000; // 10^8
335     for ( ; i < len; i++) {
336         char ch = paramval[i];
337         if (ch < '0' || ch > '9') {
338             throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
339         }
340         insec += (ch - '0') * insec_m;
341         insec_m /= 10;
342     }
343     ts.tv_sec = isec;
344     ts.tv_nsec = insec;
345 }
346
347 // Find a service record, or load it from file. If the service has
348 // dependencies, load those also.
349 //
350 // Might throw a ServiceLoadExc exception if a dependency cycle is found or if another
351 // problem occurs (I/O error, service description not found etc). Throws std::bad_alloc
352 // if a memory allocation failure occurs.
353 service_record * dirload_service_set::load_service(const char * name)
354 {
355     using std::string;
356     using std::ifstream;
357     using std::ios;
358     using std::ios_base;
359     using std::locale;
360     using std::isspace;
361     
362     using std::list;
363     using std::pair;
364     
365     // First try and find an existing record...
366     service_record * rval = find_service(string(name));
367     if (rval != 0) {
368         if (rval->isDummy()) {
369             throw service_cyclic_dependency(name);
370         }
371         return rval;
372     }
373
374     // Couldn't find one. Have to load it.    
375     string service_filename = service_dir;
376     if (*(service_filename.rbegin()) != '/') {
377         service_filename += '/';
378     }
379     service_filename += name;
380     
381     string command;
382     list<pair<unsigned,unsigned>> command_offsets;
383     string stop_command;
384     list<pair<unsigned,unsigned>> stop_command_offsets;
385     string pid_file;
386
387     service_type service_type = service_type::PROCESS;
388     std::list<prelim_dep> depends;
389     string logfile;
390     onstart_flags_t onstart_flags;
391     int term_signal = -1;  // additional termination signal
392     bool auto_restart = false;
393     bool smooth_recovery = false;
394     string socket_path;
395     int socket_perms = 0666;
396     // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
397     // invalid value, so it's safe to assume that we can do the same:
398     uid_t socket_uid = -1;
399     gid_t socket_gid = -1;
400     // Restart limit interval / count; default is 10 seconds, 3 restarts:
401     timespec restart_interval = { .tv_sec = 10, .tv_nsec = 0 };
402     int max_restarts = 3;
403     timespec restart_delay = { .tv_sec = 0, .tv_nsec = 200000000 };
404     timespec stop_timeout = { .tv_sec = 10, .tv_nsec = 0 };
405     
406     string line;
407     ifstream service_file;
408     service_file.exceptions(ios::badbit | ios::failbit);
409     
410     try {
411         service_file.open(service_filename.c_str(), ios::in);
412     }
413     catch (std::ios_base::failure &exc) {
414         throw service_not_found(name);
415     }
416     
417     // Add a dummy service record now to prevent infinite recursion in case of cyclic dependency
418     rval = new service_record(this, string(name));
419     add_service(rval);
420     
421     try {
422         // getline can set failbit if it reaches end-of-file, we don't want an exception in that case:
423         service_file.exceptions(ios::badbit);
424         
425         while (! (service_file.rdstate() & ios::eofbit)) {
426             getline(service_file, line);
427             string::iterator i = line.begin();
428             string::iterator end = line.end();
429           
430             i = skipws(i, end);
431             if (i != end) {
432                 if (*i == '#') {
433                     continue;  // comment line
434                 }
435                 string setting = read_setting_name(i, end);
436                 i = skipws(i, end);
437                 if (i == end || (*i != '=' && *i != ':')) {
438                     throw service_description_exc(name, "Badly formed line.");
439                 }
440                 i = skipws(++i, end);
441                 
442                 if (setting == "command") {
443                     command = read_setting_value(i, end, &command_offsets);
444                 }
445                 else if (setting == "socket-listen") {
446                     socket_path = read_setting_value(i, end, nullptr);
447                 }
448                 else if (setting == "socket-permissions") {
449                     string sock_perm_str = read_setting_value(i, end, nullptr);
450                     std::size_t ind = 0;
451                     try {
452                         socket_perms = std::stoi(sock_perm_str, &ind, 8);
453                         if (ind != sock_perm_str.length()) {
454                             throw std::logic_error("");
455                         }
456                     }
457                     catch (std::logic_error &exc) {
458                         throw service_description_exc(name, "socket-permissions: Badly-formed or out-of-range numeric value");
459                     }
460                 }
461                 else if (setting == "socket-uid") {
462                     string sock_uid_s = read_setting_value(i, end, nullptr);
463                     socket_uid = parse_uid_param(sock_uid_s, name, &socket_gid);
464                 }
465                 else if (setting == "socket-gid") {
466                     string sock_gid_s = read_setting_value(i, end, nullptr);
467                     socket_gid = parse_gid_param(sock_gid_s, name);
468                 }
469                 else if (setting == "stop-command") {
470                     stop_command = read_setting_value(i, end, &stop_command_offsets);
471                 }
472                 else if (setting == "pid-file") {
473                     pid_file = read_setting_value(i, end);
474                 }
475                 else if (setting == "depends-on") {
476                     string dependency_name = read_setting_value(i, end);
477                     depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::REGULAR);
478                 }
479                 else if (setting == "depends-ms") {
480                     string dependency_name = read_setting_value(i, end);
481                     depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::MILESTONE);
482                 }
483                 else if (setting == "waits-for") {
484                     string dependency_name = read_setting_value(i, end);
485                     depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::WAITS_FOR);
486                 }
487                 else if (setting == "logfile") {
488                     logfile = read_setting_value(i, end);
489                 }
490                 else if (setting == "restart") {
491                     string restart = read_setting_value(i, end);
492                     auto_restart = (restart == "yes" || restart == "true");
493                 }
494                 else if (setting == "smooth-recovery") {
495                     string recovery = read_setting_value(i, end);
496                     smooth_recovery = (recovery == "yes" || recovery == "true");
497                 }
498                 else if (setting == "type") {
499                     string type_str = read_setting_value(i, end);
500                     if (type_str == "scripted") {
501                         service_type = service_type::SCRIPTED;
502                     }
503                     else if (type_str == "process") {
504                         service_type = service_type::PROCESS;
505                     }
506                     else if (type_str == "bgprocess") {
507                         service_type = service_type::BGPROCESS;
508                     }
509                     else if (type_str == "internal") {
510                         service_type = service_type::INTERNAL;
511                     }
512                     else {
513                         throw service_description_exc(name, "Service type must be one of: \"scripted\","
514                             " \"process\", \"bgprocess\" or \"internal\"");
515                     }
516                 }
517                 else if (setting == "options") {
518                     std::list<std::pair<unsigned,unsigned>> indices;
519                     string onstart_cmds = read_setting_value(i, end, &indices);
520                     for (auto indexpair : indices) {
521                         string option_txt = onstart_cmds.substr(indexpair.first, indexpair.second - indexpair.first);
522                         if (option_txt == "starts-rwfs") {
523                             onstart_flags.rw_ready = true;
524                         }
525                         else if (option_txt == "starts-log") {
526                             onstart_flags.log_ready = true;
527                         }
528                         else if (option_txt == "no-sigterm") {
529                             onstart_flags.no_sigterm = true;
530                         }
531                         else if (option_txt == "runs-on-console") {
532                             onstart_flags.runs_on_console = true;
533                             // A service that runs on the console necessarily starts on console:
534                             onstart_flags.starts_on_console = true;
535                         }
536                         else if (option_txt == "starts-on-console") {
537                             onstart_flags.starts_on_console = true;
538                         }
539                         else if (option_txt == "pass-cs-fd") {
540                             onstart_flags.pass_cs_fd = true;
541                         }
542                         else {
543                             throw service_description_exc(name, "Unknown option: " + option_txt);
544                         }
545                     }
546                 }
547                 else if (setting == "termsignal") {
548                     string signame = read_setting_value(i, end, nullptr);
549                     int signo = signalNameToNumber(signame);
550                     if (signo == -1) {
551                         throw service_description_exc(name, "Unknown/unsupported termination signal: " + signame);
552                     }
553                     else {
554                         term_signal = signo;
555                     }
556                 }
557                 else if (setting == "restart-limit-interval") {
558                     string interval_str = read_setting_value(i, end, nullptr);
559                     parse_timespec(interval_str, name, "restart-limit-interval", restart_interval);
560                 }
561                 else if (setting == "restart-delay") {
562                     string rsdelay_str = read_setting_value(i, end, nullptr);
563                     parse_timespec(rsdelay_str, name, "restart-delay", restart_delay);
564                 }
565                 else if (setting == "restart-limit-count") {
566                     string limit_str = read_setting_value(i, end, nullptr);
567                     max_restarts = parse_unum_param(limit_str, name, std::numeric_limits<int>::max());
568                 }
569                 else if (setting == "stop-timeout") {
570                     string stoptimeout_str = read_setting_value(i, end, nullptr);
571                     parse_timespec(stoptimeout_str, name, "stop-timeout", stop_timeout);
572                 }
573                 else {
574                     throw service_description_exc(name, "Unknown setting: " + setting);
575                 }
576             }
577         }
578         
579         service_file.close();
580         
581         if (service_type == service_type::PROCESS || service_type == service_type::BGPROCESS || service_type == service_type::SCRIPTED) {
582             if (command.length() == 0) {
583                 throw service_description_exc(name, "Service command not specified");
584             }
585         }
586         
587         // Now replace the dummy service record with a real record:
588         for (auto iter = records.begin(); iter != records.end(); iter++) {
589             if (*iter == rval) {
590                 // We've found the dummy record
591                 delete rval;
592                 if (service_type == service_type::PROCESS) {
593                     auto rvalps = new process_service(this, string(name), std::move(command),
594                             command_offsets, depends);
595                     rvalps->set_restart_interval(restart_interval, max_restarts);
596                     rvalps->set_restart_delay(restart_delay);
597                     rvalps->set_stop_timeout(stop_timeout);
598                     rval = rvalps;
599                 }
600                 else if (service_type == service_type::BGPROCESS) {
601                     auto rvalps = new bgproc_service(this, string(name), std::move(command),
602                             command_offsets, depends);
603                     rvalps->set_pid_file(std::move(pid_file));
604                     rvalps->set_restart_interval(restart_interval, max_restarts);
605                     rvalps->set_restart_delay(restart_delay);
606                     rvalps->set_stop_timeout(stop_timeout);
607                     rval = rvalps;
608                 }
609                 else if (service_type == service_type::SCRIPTED) {
610                     auto rvalps = new scripted_service(this, string(name), std::move(command),
611                             command_offsets, depends);
612                     rvalps->set_stop_command(stop_command, stop_command_offsets);
613                     rvalps->set_stop_timeout(stop_timeout);
614                     rval = rvalps;
615                 }
616                 else {
617                     rval = new service_record(this, string(name), service_type,
618                             std::move(command), command_offsets,
619                             depends);
620                 }
621                 rval->set_log_file(logfile);
622                 rval->set_auto_restart(auto_restart);
623                 rval->set_smooth_recovery(smooth_recovery);
624                 rval->set_flags(onstart_flags);
625                 rval->set_extra_termination_signal(term_signal);
626                 rval->set_socket_details(std::move(socket_path), socket_perms, socket_uid, socket_gid);
627                 *iter = rval;
628                 break;
629             }
630         }
631         
632         return rval;
633     }
634     catch (setting_exception &setting_exc)
635     {
636         // Must remove the dummy service record.
637         std::remove(records.begin(), records.end(), rval);
638         delete rval;
639         throw service_description_exc(name, std::move(setting_exc.getInfo()));
640     }
641     catch (...) {
642         // Must remove the dummy service record.
643         std::remove(records.begin(), records.end(), rval);
644         delete rval;
645         throw;
646     }
647 }