8 typedef std::string string;
9 typedef std::string::iterator string_iterator;
11 // Utility function to skip white space. Returns an iterator at the
12 // first non-white-space position (or at end).
13 static string_iterator skipws(string_iterator i, string_iterator end)
19 if (! isspace(*i, locale::classic())) {
27 // Read a setting name.
28 static string read_setting_name(string_iterator & i, string_iterator end)
34 const ctype<char> & facet = use_facet<ctype<char> >(locale::classic());
37 // Allow alphabetical characters, and dash (-) in setting name
38 while (i != end && (*i == '-' || facet.is(ctype<char>::alpha, *i))) {
45 // Read a setting value
47 // In general a setting value is a single-line string. It may contain multiple parts
48 // separated by white space (which is normally collapsed). A hash mark - # - denotes
49 // the end of the value and the beginning of a comment (it should be preceded by
52 // Part of a value may be quoted using double quote marks, which prevents collapse
53 // of whitespace and interpretation of most special characters (the quote marks will
54 // not be considered part of the value). A backslash can precede a character (such
55 // as '#' or '"' or another backslash) to remove its special meaning. Newline
56 // characters are not allowed in values and cannot be quoted.
58 // This function expects the string to be in an ASCII-compatible, single byte
59 // encoding (the "classic" locale).
62 // i - reference to string iterator through the line
63 // end - iterator at end of line
64 // part_positions - list of <int,int> to which the position of each setting value
65 // part will be added as [start,end). May be null.
66 static string read_setting_value(string_iterator & i, string_iterator end,
67 std::list<std::pair<unsigned,unsigned>> * part_positions = nullptr)
82 part_start = rval.length();
94 // A backslash escapes the following character.
110 // String wasn't terminated
115 else if (c == '\\') {
117 part_start = rval.length();
120 // A backslash escapes the next character
129 else if (isspace(c, locale::classic())) {
130 if (! new_part && part_positions != nullptr) {
131 part_positions->emplace_back(part_start, rval.length());
136 if (*i == '#') break; // comment
137 rval += ' '; // collapse ws to a single space
141 // hmm... comment? Probably, though they should have put a space
142 // before it really. TODO throw an exception, and document
143 // that '#' for comments must be preceded by space, and in values
149 part_start = rval.length();
158 if (part_positions != nullptr) {
159 part_positions->emplace_back(part_start, rval.length());
165 static int signalNameToNumber(std::string &signame)
167 if (signame == "HUP") return SIGHUP;
168 if (signame == "INT") return SIGINT;
169 if (signame == "QUIT") return SIGQUIT;
170 if (signame == "USR1") return SIGUSR1;
171 if (signame == "USR2") return SIGUSR2;
175 // Find a service record, or load it from file. If the service has
176 // dependencies, load those also.
178 // Might throw a ServiceLoadExc exception if a dependency cycle is found or if another
179 // problem occurs (I/O error, service description not found etc). Throws std::bad_alloc
180 // if a memory allocation failure occurs.
181 ServiceRecord * ServiceSet::loadServiceRecord(const char * name)
193 // First try and find an existing record...
194 ServiceRecord * rval = findService(string(name));
196 if (rval->isDummy()) {
197 throw ServiceCyclicDependency(name);
202 // Couldn't find one. Have to load it.
203 string service_filename = service_dir;
204 if (*(service_filename.rbegin()) != '/') {
205 service_filename += '/';
207 service_filename += name;
210 list<pair<unsigned,unsigned>> command_offsets;
212 list<pair<unsigned,unsigned>> stop_command_offsets;
215 ServiceType service_type = ServiceType::PROCESS;
216 std::list<ServiceRecord *> depends_on;
217 std::list<ServiceRecord *> depends_soft;
219 OnstartFlags onstart_flags;
220 int term_signal = -1; // additional termination signal
221 bool auto_restart = false;
222 bool smooth_recovery = false;
224 int socket_perms = 0666;
227 ifstream service_file;
228 service_file.exceptions(ios::badbit | ios::failbit);
231 service_file.open(service_filename.c_str(), ios::in);
233 catch (std::ios_base::failure &exc) {
234 throw ServiceNotFound(name);
237 // Add a dummy service record now to prevent infinite recursion in case of cyclic dependency
238 rval = new ServiceRecord(this, string(name));
239 records.push_back(rval);
242 // getline can set failbit if it reaches end-of-file, we don't want an exception in that case:
243 service_file.exceptions(ios::badbit);
245 while (! (service_file.rdstate() & ios::eofbit)) {
246 getline(service_file, line);
247 string::iterator i = line.begin();
248 string::iterator end = line.end();
253 continue; // comment line
255 string setting = read_setting_name(i, end);
257 if (i == end || (*i != '=' && *i != ':')) {
258 throw ServiceDescriptionExc(name, "Badly formed line.");
260 i = skipws(++i, end);
262 if (setting == "command") {
263 command = read_setting_value(i, end, &command_offsets);
265 else if (setting == "socket-listen") {
266 socket_path = read_setting_value(i, end, nullptr);
268 else if (setting == "socket-permissions") {
269 string sock_perm_str = read_setting_value(i, end, nullptr);
272 socket_perms = std::stoi(sock_perm_str, &ind, 8);
273 if (ind != sock_perm_str.length()) {
274 throw std::logic_error("");
277 catch (std::logic_error &exc) {
278 throw ServiceDescriptionExc(name, "socket-permissions: Badly-formed or out-of-range numeric value");
281 else if (setting == "stop-command") {
282 stop_command = read_setting_value(i, end, &stop_command_offsets);
284 else if (setting == "pid-file") {
285 pid_file = read_setting_value(i, end);
287 else if (setting == "depends-on") {
288 string dependency_name = read_setting_value(i, end);
289 depends_on.push_back(loadServiceRecord(dependency_name.c_str()));
291 else if (setting == "waits-for") {
292 string dependency_name = read_setting_value(i, end);
293 depends_soft.push_back(loadServiceRecord(dependency_name.c_str()));
295 else if (setting == "logfile") {
296 logfile = read_setting_value(i, end);
298 else if (setting == "restart") {
299 string restart = read_setting_value(i, end);
300 auto_restart = (restart == "yes" || restart == "true");
302 else if (setting == "smooth-recovery") {
303 string recovery = read_setting_value(i, end);
304 smooth_recovery = (recovery == "yes" || recovery == "true");
306 else if (setting == "type") {
307 string type_str = read_setting_value(i, end);
308 if (type_str == "scripted") {
309 service_type = ServiceType::SCRIPTED;
311 else if (type_str == "process") {
312 service_type = ServiceType::PROCESS;
314 else if (type_str == "bgprocess") {
315 service_type = ServiceType::BGPROCESS;
317 else if (type_str == "internal") {
318 service_type = ServiceType::INTERNAL;
321 throw ServiceDescriptionExc(name, "Service type must be one of: \"scripted\","
322 " \"process\", \"bgprocess\" or \"internal\"");
325 else if (setting == "onstart") {
326 std::list<std::pair<unsigned,unsigned>> indices;
327 string onstart_cmds = read_setting_value(i, end, &indices);
328 for (auto indexpair : indices) {
329 string onstart_cmd = onstart_cmds.substr(indexpair.first, indexpair.second - indexpair.first);
330 if (onstart_cmd == "rw_ready") {
331 onstart_flags.rw_ready = true;
334 throw new ServiceDescriptionExc(name, "Unknown onstart command: " + onstart_cmd);
338 else if (setting == "termsignal") {
339 string signame = read_setting_value(i, end, nullptr);
340 int signo = signalNameToNumber(signame);
342 throw new ServiceDescriptionExc(name, "Unknown/unsupported termination signal: " + signame);
348 else if (setting == "nosigterm") {
349 string sigtermsetting = read_setting_value(i, end);
350 onstart_flags.no_sigterm = (sigtermsetting == "yes" || sigtermsetting == "true");
352 else if (setting == "runs-on-console") {
353 string runconsolesetting = read_setting_value(i, end);
354 onstart_flags.runs_on_console = (runconsolesetting == "yes" || runconsolesetting == "true");
357 throw ServiceDescriptionExc(name, "Unknown setting: " + setting);
362 service_file.close();
363 // TODO check we actually have all the settings - type, command
365 // Now replace the dummy service record with a real record:
366 for (auto iter = records.begin(); iter != records.end(); iter++) {
368 // We've found the dummy record
370 rval = new ServiceRecord(this, string(name), service_type, std::move(command), command_offsets,
371 & depends_on, & depends_soft);
372 rval->setStopCommand(stop_command, stop_command_offsets);
373 rval->setLogfile(logfile);
374 rval->setAutoRestart(auto_restart);
375 rval->setSmoothRecovery(smooth_recovery);
376 rval->setOnstartFlags(onstart_flags);
377 rval->setExtraTerminationSignal(term_signal);
378 rval->set_pid_file(std::move(pid_file));
379 rval->set_socket_details(std::move(socket_path), socket_perms);
388 // Must remove the dummy service record.
389 std::remove(records.begin(), records.end(), rval);