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