15 typedef std::string string;
16 typedef std::string::iterator string_iterator;
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)
26 if (! isspace(*i, locale::classic())) {
34 // Read a setting name.
35 static string read_setting_name(string_iterator & i, string_iterator end)
41 const ctype<char> & facet = use_facet<ctype<char> >(locale::classic());
44 // Allow alphabetical characters, and dash (-) in setting name
45 while (i != end && (*i == '-' || facet.is(ctype<char>::alpha, *i))) {
52 // Read a setting value
54 // In general a setting value is a single-line string. It may contain multiple parts
55 // separated by white space (which is normally collapsed). A hash mark - # - denotes
56 // the end of the value and the beginning of a comment (it should be preceded by
59 // Part of a value may be quoted using double quote marks, which prevents collapse
60 // of whitespace and interpretation of most special characters (the quote marks will
61 // not be considered part of the value). A backslash can precede a character (such
62 // as '#' or '"' or another backslash) to remove its special meaning. Newline
63 // characters are not allowed in values and cannot be quoted.
65 // This function expects the string to be in an ASCII-compatible, single byte
66 // encoding (the "classic" locale).
69 // i - reference to string iterator through the line
70 // end - iterator at end of line
71 // part_positions - list of <int,int> to which the position of each setting value
72 // part will be added as [start,end). May be null.
73 static string read_setting_value(string_iterator & i, string_iterator end,
74 std::list<std::pair<unsigned,unsigned>> * part_positions = nullptr)
89 part_start = rval.length();
100 else if (c == '\\') {
101 // A backslash escapes the following character.
117 // String wasn't terminated
122 else if (c == '\\') {
124 part_start = rval.length();
127 // A backslash escapes the next character
136 else if (isspace(c, locale::classic())) {
137 if (! new_part && part_positions != nullptr) {
138 part_positions->emplace_back(part_start, rval.length());
143 if (*i == '#') break; // comment
144 rval += ' '; // collapse ws to a single space
148 // hmm... comment? Probably, though they should have put a space
149 // before it really. TODO throw an exception, and document
150 // that '#' for comments must be preceded by space, and in values
156 part_start = rval.length();
165 if (part_positions != nullptr) {
166 part_positions->emplace_back(part_start, rval.length());
172 static int signalNameToNumber(std::string &signame)
174 if (signame == "HUP") return SIGHUP;
175 if (signame == "INT") return SIGINT;
176 if (signame == "QUIT") return SIGQUIT;
177 if (signame == "USR1") return SIGUSR1;
178 if (signame == "USR2") return SIGUSR2;
182 static const char * uid_err_msg = "Specified user id contains invalid numeric characters or is outside allowed range.";
184 // Parse a userid parameter which may be a numeric user ID or a username. If a name, the
185 // userid is looked up via the system user database (getpwnam() function). In this case,
186 // the associated group is stored in the location specified by the group_p parameter iff
187 // it is not null and iff it contains the value -1.
188 static uid_t parse_uid_param(const std::string ¶m, const std::string &service_name, gid_t *group_p)
190 // Could be a name or a numeric id. But we should assume numeric first, just in case
191 // a user manages to give themselves a username that parses as a number.
194 // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
195 // is is probably safe to assume that valid values are positive. We'll also assume
196 // that the value range fits with "unsigned long long" since it seems unlikely
197 // that would ever not be the case.
199 // TODO perhaps write a number parser, since even the unsigned variants of the C/C++
200 // functions accept a leading minus sign...
201 static_assert((uintmax_t)std::numeric_limits<uid_t>::max() <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "uid_t is too large");
202 unsigned long long v = std::stoull(param, &ind, 0);
203 if (v > static_cast<unsigned long long>(std::numeric_limits<uid_t>::max()) || ind != param.length()) {
204 throw ServiceDescriptionExc(service_name, uid_err_msg);
208 catch (std::out_of_range &exc) {
209 throw ServiceDescriptionExc(service_name, uid_err_msg);
211 catch (std::invalid_argument &exc) {
212 // Ok, so it doesn't look like a number: proceed...
216 struct passwd * pwent = getpwnam(param.c_str());
217 if (pwent == nullptr) {
218 // Maybe an error, maybe just no entry.
220 throw new ServiceDescriptionExc(service_name, "Specified user \"" + param + "\" does not exist in system database.");
223 throw new ServiceDescriptionExc(service_name, std::string("Error accessing user database: ") + strerror(errno));
227 if (group_p && *group_p != (gid_t)-1) {
228 *group_p = pwent->pw_gid;
231 return pwent->pw_uid;
234 static const char * gid_err_msg = "Specified group id contains invalid numeric characters or is outside allowed range.";
236 static gid_t parse_gid_param(const std::string ¶m, const std::string &service_name)
238 // Could be a name or a numeric id. But we should assume numeric first, just in case
239 // a user manages to give themselves a username that parses as a number.
242 // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
243 // is is probably safe to assume that valid values are positive. We'll also assume
244 // that the value range fits with "unsigned long long" since it seems unlikely
245 // that would ever not be the case.
247 // TODO perhaps write a number parser, since even the unsigned variants of the C/C++
248 // functions accept a leading minus sign...
249 unsigned long long v = std::stoull(param, &ind, 0);
250 if (v > static_cast<unsigned long long>(std::numeric_limits<gid_t>::max()) || ind != param.length()) {
251 throw ServiceDescriptionExc(service_name, gid_err_msg);
255 catch (std::out_of_range &exc) {
256 throw ServiceDescriptionExc(service_name, gid_err_msg);
258 catch (std::invalid_argument &exc) {
259 // Ok, so it doesn't look like a number: proceed...
263 struct group * grent = getgrnam(param.c_str());
264 if (grent == nullptr) {
265 // Maybe an error, maybe just no entry.
267 throw new ServiceDescriptionExc(service_name, "Specified group \"" + param + "\" does not exist in system database.");
270 throw new ServiceDescriptionExc(service_name, std::string("Error accessing group database: ") + strerror(errno));
274 return grent->gr_gid;
277 // Find a service record, or load it from file. If the service has
278 // dependencies, load those also.
280 // Might throw a ServiceLoadExc exception if a dependency cycle is found or if another
281 // problem occurs (I/O error, service description not found etc). Throws std::bad_alloc
282 // if a memory allocation failure occurs.
283 ServiceRecord * ServiceSet::loadServiceRecord(const char * name)
295 // First try and find an existing record...
296 ServiceRecord * rval = findService(string(name));
298 if (rval->isDummy()) {
299 throw ServiceCyclicDependency(name);
304 // Couldn't find one. Have to load it.
305 string service_filename = service_dir;
306 if (*(service_filename.rbegin()) != '/') {
307 service_filename += '/';
309 service_filename += name;
312 list<pair<unsigned,unsigned>> command_offsets;
314 list<pair<unsigned,unsigned>> stop_command_offsets;
317 ServiceType service_type = ServiceType::PROCESS;
318 std::list<ServiceRecord *> depends_on;
319 std::list<ServiceRecord *> depends_soft;
321 OnstartFlags onstart_flags;
322 int term_signal = -1; // additional termination signal
323 bool auto_restart = false;
324 bool smooth_recovery = false;
326 int socket_perms = 0666;
327 // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
328 // invalid value, so it's safe to assume that we can do the same:
329 uid_t socket_uid = -1;
330 gid_t socket_gid = -1;
333 ifstream service_file;
334 service_file.exceptions(ios::badbit | ios::failbit);
337 service_file.open(service_filename.c_str(), ios::in);
339 catch (std::ios_base::failure &exc) {
340 throw ServiceNotFound(name);
343 // Add a dummy service record now to prevent infinite recursion in case of cyclic dependency
344 rval = new ServiceRecord(this, string(name));
345 records.push_back(rval);
348 // getline can set failbit if it reaches end-of-file, we don't want an exception in that case:
349 service_file.exceptions(ios::badbit);
351 while (! (service_file.rdstate() & ios::eofbit)) {
352 getline(service_file, line);
353 string::iterator i = line.begin();
354 string::iterator end = line.end();
359 continue; // comment line
361 string setting = read_setting_name(i, end);
363 if (i == end || (*i != '=' && *i != ':')) {
364 throw ServiceDescriptionExc(name, "Badly formed line.");
366 i = skipws(++i, end);
368 if (setting == "command") {
369 command = read_setting_value(i, end, &command_offsets);
371 else if (setting == "socket-listen") {
372 socket_path = read_setting_value(i, end, nullptr);
374 else if (setting == "socket-permissions") {
375 string sock_perm_str = read_setting_value(i, end, nullptr);
378 socket_perms = std::stoi(sock_perm_str, &ind, 8);
379 if (ind != sock_perm_str.length()) {
380 throw std::logic_error("");
383 catch (std::logic_error &exc) {
384 throw ServiceDescriptionExc(name, "socket-permissions: Badly-formed or out-of-range numeric value");
387 else if (setting == "socket-uid") {
388 string sock_uid_s = read_setting_value(i, end, nullptr);
389 socket_uid = parse_uid_param(sock_uid_s, name, &socket_gid);
391 else if (setting == "socket-gid") {
392 string sock_gid_s = read_setting_value(i, end, nullptr);
393 socket_gid = parse_gid_param(sock_gid_s, name);
395 else if (setting == "stop-command") {
396 stop_command = read_setting_value(i, end, &stop_command_offsets);
398 else if (setting == "pid-file") {
399 pid_file = read_setting_value(i, end);
401 else if (setting == "depends-on") {
402 string dependency_name = read_setting_value(i, end);
403 depends_on.push_back(loadServiceRecord(dependency_name.c_str()));
405 else if (setting == "waits-for") {
406 string dependency_name = read_setting_value(i, end);
407 depends_soft.push_back(loadServiceRecord(dependency_name.c_str()));
409 else if (setting == "logfile") {
410 logfile = read_setting_value(i, end);
412 else if (setting == "restart") {
413 string restart = read_setting_value(i, end);
414 auto_restart = (restart == "yes" || restart == "true");
416 else if (setting == "smooth-recovery") {
417 string recovery = read_setting_value(i, end);
418 smooth_recovery = (recovery == "yes" || recovery == "true");
420 else if (setting == "type") {
421 string type_str = read_setting_value(i, end);
422 if (type_str == "scripted") {
423 service_type = ServiceType::SCRIPTED;
425 else if (type_str == "process") {
426 service_type = ServiceType::PROCESS;
428 else if (type_str == "bgprocess") {
429 service_type = ServiceType::BGPROCESS;
431 else if (type_str == "internal") {
432 service_type = ServiceType::INTERNAL;
435 throw ServiceDescriptionExc(name, "Service type must be one of: \"scripted\","
436 " \"process\", \"bgprocess\" or \"internal\"");
439 else if (setting == "onstart") {
441 std::list<std::pair<unsigned,unsigned>> indices;
442 string onstart_cmds = read_setting_value(i, end, &indices);
443 for (auto indexpair : indices) {
444 string onstart_cmd = onstart_cmds.substr(indexpair.first, indexpair.second - indexpair.first);
445 if (onstart_cmd == "rw_ready") {
446 onstart_flags.rw_ready = true;
448 else if (onstart_cmd == "log_ready") {
449 onstart_flags.log_ready = true;
452 throw new ServiceDescriptionExc(name, "Unknown onstart command: " + onstart_cmd);
456 else if (setting == "options") {
457 std::list<std::pair<unsigned,unsigned>> indices;
458 string onstart_cmds = read_setting_value(i, end, &indices);
459 for (auto indexpair : indices) {
460 string option_txt = onstart_cmds.substr(indexpair.first, indexpair.second - indexpair.first);
461 if (option_txt == "starts-rwfs") {
462 onstart_flags.rw_ready = true;
464 else if (option_txt == "starts-log") {
465 onstart_flags.log_ready = true;
467 else if (option_txt == "nosigterm") {
468 onstart_flags.no_sigterm = true;
470 else if (option_txt == "runs-on-console") {
471 onstart_flags.runs_on_console = true;
474 throw new ServiceDescriptionExc(name, "Unknown option: " + option_txt);
479 else if (setting == "termsignal") {
480 string signame = read_setting_value(i, end, nullptr);
481 int signo = signalNameToNumber(signame);
483 throw new ServiceDescriptionExc(name, "Unknown/unsupported termination signal: " + signame);
489 else if (setting == "nosigterm") {
491 string sigtermsetting = read_setting_value(i, end);
492 onstart_flags.no_sigterm = (sigtermsetting == "yes" || sigtermsetting == "true");
494 else if (setting == "runs-on-console") {
496 string runconsolesetting = read_setting_value(i, end);
497 onstart_flags.runs_on_console = (runconsolesetting == "yes" || runconsolesetting == "true");
500 throw ServiceDescriptionExc(name, "Unknown setting: " + setting);
505 service_file.close();
506 // TODO check we actually have all the settings - type, command
508 // Now replace the dummy service record with a real record:
509 for (auto iter = records.begin(); iter != records.end(); iter++) {
511 // We've found the dummy record
513 rval = new ServiceRecord(this, string(name), service_type, std::move(command), command_offsets,
514 & depends_on, & depends_soft);
515 rval->setStopCommand(stop_command, stop_command_offsets);
516 rval->setLogfile(logfile);
517 rval->setAutoRestart(auto_restart);
518 rval->setSmoothRecovery(smooth_recovery);
519 rval->setOnstartFlags(onstart_flags);
520 rval->setExtraTerminationSignal(term_signal);
521 rval->set_pid_file(std::move(pid_file));
522 rval->set_socket_details(std::move(socket_path), socket_perms, socket_uid, socket_gid);
531 // Must remove the dummy service record.
532 std::remove(records.begin(), records.end(), rval);