Implement socket activation (single, unix-family socket only)
[oweals/dinit.git] / load_service.cc
1 #include "service.h"
2 #include <algorithm>
3 #include <string>
4 #include <fstream>
5 #include <locale>
6 #include <iostream>
7
8 typedef std::string string;
9 typedef std::string::iterator string_iterator;
10
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)
14 {
15     using std::locale;
16     using std::isspace;
17     
18     while (i != end) {
19       if (! isspace(*i, locale::classic())) {
20         break;
21       }
22       ++i;
23     }
24     return i;
25 }
26
27 // Read a setting name.
28 static string read_setting_name(string_iterator & i, string_iterator end)
29 {
30     using std::locale;
31     using std::ctype;
32     using std::use_facet;
33     
34     const ctype<char> & facet = use_facet<ctype<char> >(locale::classic());
35
36     string rval;
37     // Allow alphabetical characters, and dash (-) in setting name
38     while (i != end && (*i == '-' || facet.is(ctype<char>::alpha, *i))) {
39         rval += *i;
40         ++i;
41     }
42     return rval;
43 }
44
45 // Read a setting value
46 //
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
50 // whitespace).
51 //
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.
57 //
58 // This function expects the string to be in an ASCII-compatible, single byte
59 // encoding (the "classic" locale).
60 //
61 // Params:
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)
68 {
69     using std::locale;
70     using std::isspace;
71
72     i = skipws(i, end);
73     
74     string rval;
75     bool new_part = true;
76     int part_start;
77     
78     while (i != end) {
79         char c = *i;
80         if (c == '\"') {
81             if (new_part) {
82                 part_start = rval.length();
83                 new_part = false;
84             }
85             // quoted string
86             ++i;
87             while (i != end) {
88                 c = *i;
89                 if (c == '\"') break;
90                 if (c == '\n') {
91                     // TODO error here.
92                 }
93                 else if (c == '\\') {
94                     // A backslash escapes the following character.
95                     ++i;
96                     if (i != end) {
97                         c = *i;
98                         if (c == '\n') {
99                             // TODO error here.
100                         }
101                         rval += c;
102                     }
103                 }
104                 else {
105                     rval += c;
106                 }
107                 ++i;
108             }
109             if (i == end) {
110                 // String wasn't terminated
111                 // TODO error here
112                 break;
113             }
114         }
115         else if (c == '\\') {
116             if (new_part) {
117                 part_start = rval.length();
118                 new_part = false;
119             }
120             // A backslash escapes the next character
121             ++i;
122             if (i != end) {
123                 rval += *i;
124             }
125             else {
126                 // TODO error here
127             }
128         }
129         else if (isspace(c, locale::classic())) {
130             if (! new_part && part_positions != nullptr) {
131                 part_positions->emplace_back(part_start, rval.length());
132                 new_part = true;
133             }
134             i = skipws(i, end);
135             if (i == end) break;
136             if (*i == '#') break; // comment
137             rval += ' ';  // collapse ws to a single space
138             continue;
139         }
140         else if (c == '#') {
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
144             // must be quoted.
145             break;
146         }
147         else {
148             if (new_part) {
149                 part_start = rval.length();
150                 new_part = false;
151             }
152             rval += c;
153         }
154         ++i;
155     }
156
157     // Got to end:
158     if (part_positions != nullptr) {
159         part_positions->emplace_back(part_start, rval.length());
160     }
161
162     return rval;
163 }
164
165 static int signalNameToNumber(std::string &signame)
166 {
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;
172     return -1;
173 }
174
175 // Find a service record, or load it from file. If the service has
176 // dependencies, load those also.
177 //
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)
182 {
183     using std::string;
184     using std::ifstream;
185     using std::ios;
186     using std::ios_base;
187     using std::locale;
188     using std::isspace;
189     
190     using std::list;
191     using std::pair;
192     
193     // First try and find an existing record...
194     ServiceRecord * rval = findService(string(name));
195     if (rval != 0) {
196         if (rval->isDummy()) {
197             throw ServiceCyclicDependency(name);
198         }
199         return rval;
200     }
201
202     // Couldn't find one. Have to load it.    
203     string service_filename = service_dir;
204     if (*(service_filename.rbegin()) != '/') {
205         service_filename += '/';
206     }
207     service_filename += name;
208     
209     string command;
210     list<pair<unsigned,unsigned>> command_offsets;
211     string stop_command;
212     list<pair<unsigned,unsigned>> stop_command_offsets;
213     string pid_file;
214
215     ServiceType service_type = ServiceType::PROCESS;
216     std::list<ServiceRecord *> depends_on;
217     std::list<ServiceRecord *> depends_soft;
218     string logfile;
219     OnstartFlags onstart_flags;
220     int term_signal = -1;  // additional termination signal
221     bool auto_restart = false;
222     bool smooth_recovery = false;
223     string socket_path;
224     int socket_perms = 0666;
225     
226     string line;
227     ifstream service_file;
228     service_file.exceptions(ios::badbit | ios::failbit);
229     
230     try {
231         service_file.open(service_filename.c_str(), ios::in);
232     }
233     catch (std::ios_base::failure &exc) {
234         throw ServiceNotFound(name);
235     }
236     
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);
240     
241     try {
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);
244         
245         while (! (service_file.rdstate() & ios::eofbit)) {
246             getline(service_file, line);
247             string::iterator i = line.begin();
248             string::iterator end = line.end();
249           
250             i = skipws(i, end);
251             if (i != end) {
252                 if (*i == '#') {
253                     continue;  // comment line
254                 }
255                 string setting = read_setting_name(i, end);
256                 i = skipws(i, end);
257                 if (i == end || (*i != '=' && *i != ':')) {
258                     throw ServiceDescriptionExc(name, "Badly formed line.");
259                 }
260                 i = skipws(++i, end);
261                 
262                 if (setting == "command") {
263                     command = read_setting_value(i, end, &command_offsets);
264                 }
265                 else if (setting == "socket-listen") {
266                     socket_path = read_setting_value(i, end, nullptr);
267                 }
268                 else if (setting == "socket-permissions") {
269                     string sock_perm_str = read_setting_value(i, end, nullptr);
270                     std::size_t ind = 0;
271                     try {
272                         socket_perms = std::stoi(sock_perm_str, &ind, 8);
273                         if (ind != sock_perm_str.length()) {
274                             throw std::logic_error("");
275                         }
276                     }
277                     catch (std::logic_error &exc) {
278                         throw ServiceDescriptionExc(name, "socket-permissions: Badly-formed or out-of-range numeric value");
279                     }
280                 }
281                 else if (setting == "stop-command") {
282                     stop_command = read_setting_value(i, end, &stop_command_offsets);
283                 }
284                 else if (setting == "pid-file") {
285                     pid_file = read_setting_value(i, end);
286                 }
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()));
290                 }
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()));
294                 }
295                 else if (setting == "logfile") {
296                     logfile = read_setting_value(i, end);
297                 }
298                 else if (setting == "restart") {
299                     string restart = read_setting_value(i, end);
300                     auto_restart = (restart == "yes" || restart == "true");
301                 }
302                 else if (setting == "smooth-recovery") {
303                     string recovery = read_setting_value(i, end);
304                     smooth_recovery = (recovery == "yes" || recovery == "true");
305                 }
306                 else if (setting == "type") {
307                     string type_str = read_setting_value(i, end);
308                     if (type_str == "scripted") {
309                         service_type = ServiceType::SCRIPTED;
310                     }
311                     else if (type_str == "process") {
312                         service_type = ServiceType::PROCESS;
313                     }
314                     else if (type_str == "bgprocess") {
315                         service_type = ServiceType::BGPROCESS;
316                     }
317                     else if (type_str == "internal") {
318                         service_type = ServiceType::INTERNAL;
319                     }
320                     else {
321                         throw ServiceDescriptionExc(name, "Service type must be one of: \"scripted\","
322                             " \"process\", \"bgprocess\" or \"internal\"");
323                     }
324                 }
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;
332                         }
333                         else {
334                             throw new ServiceDescriptionExc(name, "Unknown onstart command: " + onstart_cmd);
335                         }
336                     }
337                 }
338                 else if (setting == "termsignal") {
339                     string signame = read_setting_value(i, end, nullptr);
340                     int signo = signalNameToNumber(signame);
341                     if (signo == -1) {
342                         throw new ServiceDescriptionExc(name, "Unknown/unsupported termination signal: " + signame);
343                     }
344                     else {
345                         term_signal = signo;
346                     }
347                 }
348                 else if (setting == "nosigterm") {
349                     string sigtermsetting = read_setting_value(i, end);
350                     onstart_flags.no_sigterm = (sigtermsetting == "yes" || sigtermsetting == "true");
351                 }
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");
355                 }
356                 else {
357                     throw ServiceDescriptionExc(name, "Unknown setting: " + setting);
358                 }
359             }
360         }
361         
362         service_file.close();
363         // TODO check we actually have all the settings - type, command
364         
365         // Now replace the dummy service record with a real record:
366         for (auto iter = records.begin(); iter != records.end(); iter++) {
367             if (*iter == rval) {
368                 // We've found the dummy record
369                 delete rval;
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);
380                 *iter = rval;
381                 break;
382             }
383         }
384         
385         return rval;
386     }
387     catch (...) {
388         // Must remove the dummy service record.
389         std::remove(records.begin(), records.end(), rval);
390         delete rval;
391         throw;
392     }
393 }