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))) {
53 class SettingException
58 SettingException(const std::string &exc_info) : info(exc_info)
62 const std::string &getInfo()
70 // Read a setting value
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
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.
83 // This function expects the string to be in an ASCII-compatible, single byte
84 // encoding (the "classic" locale).
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)
101 bool new_part = true;
108 part_start = rval.length();
115 if (c == '\"') break;
117 throw SettingException("Line end inside quoted string");
119 else if (c == '\\') {
120 // A backslash escapes the following character.
125 throw SettingException("Line end follows backslash escape character (`\\')");
136 // String wasn't terminated
137 throw SettingException("Unterminated quoted string");
140 else if (c == '\\') {
142 part_start = rval.length();
145 // A backslash escapes the next character
151 throw SettingException("Backslash escape (`\\') not followed by character");
154 else if (isspace(c, locale::classic())) {
155 if (! new_part && part_positions != nullptr) {
156 part_positions->emplace_back(part_start, rval.length());
161 if (*i == '#') break; // comment
162 rval += ' '; // collapse ws to a single space
166 // Possibly intended a comment; we require leading whitespace to reduce occurrence of accidental
167 // comments in setting values.
168 throw SettingException("hashmark (`#') comment must be separated from setting value by whitespace");
172 part_start = rval.length();
181 if (part_positions != nullptr) {
182 part_positions->emplace_back(part_start, rval.length());
188 static int signalNameToNumber(std::string &signame)
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;
198 static const char * uid_err_msg = "Specified user id contains invalid numeric characters or is outside allowed range.";
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 ¶m, const std::string &service_name, gid_t *group_p)
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.
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 assume
212 // that the value range fits with "unsigned long long" since it seems unlikely
213 // that would ever not be the case.
215 // TODO perhaps write a number parser, since even the unsigned variants of the C/C++
216 // functions accept a leading minus sign...
217 static_assert((uintmax_t)std::numeric_limits<uid_t>::max() <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "uid_t is too large");
218 unsigned long long v = std::stoull(param, &ind, 0);
219 if (v > static_cast<unsigned long long>(std::numeric_limits<uid_t>::max()) || ind != param.length()) {
220 throw ServiceDescriptionExc(service_name, uid_err_msg);
224 catch (std::out_of_range &exc) {
225 throw ServiceDescriptionExc(service_name, uid_err_msg);
227 catch (std::invalid_argument &exc) {
228 // Ok, so it doesn't look like a number: proceed...
232 struct passwd * pwent = getpwnam(param.c_str());
233 if (pwent == nullptr) {
234 // Maybe an error, maybe just no entry.
236 throw new ServiceDescriptionExc(service_name, "Specified user \"" + param + "\" does not exist in system database.");
239 throw new ServiceDescriptionExc(service_name, std::string("Error accessing user database: ") + strerror(errno));
243 if (group_p && *group_p != (gid_t)-1) {
244 *group_p = pwent->pw_gid;
247 return pwent->pw_uid;
250 static const char * gid_err_msg = "Specified group id contains invalid numeric characters or is outside allowed range.";
252 static gid_t parse_gid_param(const std::string ¶m, const std::string &service_name)
254 // Could be a name or a numeric id. But we should assume numeric first, just in case
255 // a user manages to give themselves a username that parses as a number.
258 // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
259 // is is probably safe to assume that valid values are positive. We'll also assume
260 // that the value range fits with "unsigned long long" since it seems unlikely
261 // that would ever not be the case.
263 // TODO perhaps write a number parser, since even the unsigned variants of the C/C++
264 // functions accept a leading minus sign...
265 unsigned long long v = std::stoull(param, &ind, 0);
266 if (v > static_cast<unsigned long long>(std::numeric_limits<gid_t>::max()) || ind != param.length()) {
267 throw ServiceDescriptionExc(service_name, gid_err_msg);
271 catch (std::out_of_range &exc) {
272 throw ServiceDescriptionExc(service_name, gid_err_msg);
274 catch (std::invalid_argument &exc) {
275 // Ok, so it doesn't look like a number: proceed...
279 struct group * grent = getgrnam(param.c_str());
280 if (grent == nullptr) {
281 // Maybe an error, maybe just no entry.
283 throw new ServiceDescriptionExc(service_name, "Specified group \"" + param + "\" does not exist in system database.");
286 throw new ServiceDescriptionExc(service_name, std::string("Error accessing group database: ") + strerror(errno));
290 return grent->gr_gid;
293 // Find a service record, or load it from file. If the service has
294 // dependencies, load those also.
296 // Might throw a ServiceLoadExc exception if a dependency cycle is found or if another
297 // problem occurs (I/O error, service description not found etc). Throws std::bad_alloc
298 // if a memory allocation failure occurs.
299 ServiceRecord * ServiceSet::loadServiceRecord(const char * name)
311 // First try and find an existing record...
312 ServiceRecord * rval = findService(string(name));
314 if (rval->isDummy()) {
315 throw ServiceCyclicDependency(name);
320 // Couldn't find one. Have to load it.
321 string service_filename = service_dir;
322 if (*(service_filename.rbegin()) != '/') {
323 service_filename += '/';
325 service_filename += name;
328 list<pair<unsigned,unsigned>> command_offsets;
330 list<pair<unsigned,unsigned>> stop_command_offsets;
333 ServiceType service_type = ServiceType::PROCESS;
334 std::list<ServiceRecord *> depends_on;
335 std::list<ServiceRecord *> depends_soft;
337 OnstartFlags onstart_flags;
338 int term_signal = -1; // additional termination signal
339 bool auto_restart = false;
340 bool smooth_recovery = false;
342 int socket_perms = 0666;
343 // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
344 // invalid value, so it's safe to assume that we can do the same:
345 uid_t socket_uid = -1;
346 gid_t socket_gid = -1;
349 ifstream service_file;
350 service_file.exceptions(ios::badbit | ios::failbit);
353 service_file.open(service_filename.c_str(), ios::in);
355 catch (std::ios_base::failure &exc) {
356 throw ServiceNotFound(name);
359 // Add a dummy service record now to prevent infinite recursion in case of cyclic dependency
360 rval = new ServiceRecord(this, string(name));
361 records.push_back(rval);
364 // getline can set failbit if it reaches end-of-file, we don't want an exception in that case:
365 service_file.exceptions(ios::badbit);
367 while (! (service_file.rdstate() & ios::eofbit)) {
368 getline(service_file, line);
369 string::iterator i = line.begin();
370 string::iterator end = line.end();
375 continue; // comment line
377 string setting = read_setting_name(i, end);
379 if (i == end || (*i != '=' && *i != ':')) {
380 throw ServiceDescriptionExc(name, "Badly formed line.");
382 i = skipws(++i, end);
384 if (setting == "command") {
385 command = read_setting_value(i, end, &command_offsets);
387 else if (setting == "socket-listen") {
388 socket_path = read_setting_value(i, end, nullptr);
390 else if (setting == "socket-permissions") {
391 string sock_perm_str = read_setting_value(i, end, nullptr);
394 socket_perms = std::stoi(sock_perm_str, &ind, 8);
395 if (ind != sock_perm_str.length()) {
396 throw std::logic_error("");
399 catch (std::logic_error &exc) {
400 throw ServiceDescriptionExc(name, "socket-permissions: Badly-formed or out-of-range numeric value");
403 else if (setting == "socket-uid") {
404 string sock_uid_s = read_setting_value(i, end, nullptr);
405 socket_uid = parse_uid_param(sock_uid_s, name, &socket_gid);
407 else if (setting == "socket-gid") {
408 string sock_gid_s = read_setting_value(i, end, nullptr);
409 socket_gid = parse_gid_param(sock_gid_s, name);
411 else if (setting == "stop-command") {
412 stop_command = read_setting_value(i, end, &stop_command_offsets);
414 else if (setting == "pid-file") {
415 pid_file = read_setting_value(i, end);
417 else if (setting == "depends-on") {
418 string dependency_name = read_setting_value(i, end);
419 depends_on.push_back(loadServiceRecord(dependency_name.c_str()));
421 else if (setting == "waits-for") {
422 string dependency_name = read_setting_value(i, end);
423 depends_soft.push_back(loadServiceRecord(dependency_name.c_str()));
425 else if (setting == "logfile") {
426 logfile = read_setting_value(i, end);
428 else if (setting == "restart") {
429 string restart = read_setting_value(i, end);
430 auto_restart = (restart == "yes" || restart == "true");
432 else if (setting == "smooth-recovery") {
433 string recovery = read_setting_value(i, end);
434 smooth_recovery = (recovery == "yes" || recovery == "true");
436 else if (setting == "type") {
437 string type_str = read_setting_value(i, end);
438 if (type_str == "scripted") {
439 service_type = ServiceType::SCRIPTED;
441 else if (type_str == "process") {
442 service_type = ServiceType::PROCESS;
444 else if (type_str == "bgprocess") {
445 service_type = ServiceType::BGPROCESS;
447 else if (type_str == "internal") {
448 service_type = ServiceType::INTERNAL;
451 throw ServiceDescriptionExc(name, "Service type must be one of: \"scripted\","
452 " \"process\", \"bgprocess\" or \"internal\"");
455 else if (setting == "options") {
456 std::list<std::pair<unsigned,unsigned>> indices;
457 string onstart_cmds = read_setting_value(i, end, &indices);
458 for (auto indexpair : indices) {
459 string option_txt = onstart_cmds.substr(indexpair.first, indexpair.second - indexpair.first);
460 if (option_txt == "starts-rwfs") {
461 onstart_flags.rw_ready = true;
463 else if (option_txt == "starts-log") {
464 onstart_flags.log_ready = true;
466 else if (option_txt == "no-sigterm") {
467 onstart_flags.no_sigterm = true;
469 else if (option_txt == "runs-on-console") {
470 onstart_flags.runs_on_console = true;
473 throw new ServiceDescriptionExc(name, "Unknown option: " + option_txt);
477 else if (setting == "termsignal") {
478 string signame = read_setting_value(i, end, nullptr);
479 int signo = signalNameToNumber(signame);
481 throw new ServiceDescriptionExc(name, "Unknown/unsupported termination signal: " + signame);
488 throw ServiceDescriptionExc(name, "Unknown setting: " + setting);
493 service_file.close();
495 if (service_type == ServiceType::PROCESS || service_type == ServiceType::BGPROCESS || service_type == ServiceType::SCRIPTED) {
496 if (command.length() == 0) {
497 throw ServiceDescriptionExc(name, "Service command not specified");
501 // Now replace the dummy service record with a real record:
502 for (auto iter = records.begin(); iter != records.end(); iter++) {
504 // We've found the dummy record
506 rval = new ServiceRecord(this, string(name), service_type, std::move(command), command_offsets,
507 & depends_on, & depends_soft);
508 rval->setStopCommand(stop_command, stop_command_offsets);
509 rval->setLogfile(logfile);
510 rval->setAutoRestart(auto_restart);
511 rval->setSmoothRecovery(smooth_recovery);
512 rval->setOnstartFlags(onstart_flags);
513 rval->setExtraTerminationSignal(term_signal);
514 rval->set_pid_file(std::move(pid_file));
515 rval->set_socket_details(std::move(socket_path), socket_perms, socket_uid, socket_gid);
523 catch (SettingException &setting_exc)
525 // Must remove the dummy service record.
526 std::remove(records.begin(), records.end(), rval);
528 throw ServiceDescriptionExc(name, setting_exc.getInfo());
531 // Must remove the dummy service record.
532 std::remove(records.begin(), records.end(), rval);