load-service.h 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194
  1. #include <iostream>
  2. #include <list>
  3. #include <limits>
  4. #include <utility>
  5. #include <vector>
  6. #include <iterator>
  7. #include <csignal>
  8. #include <cstring>
  9. #include <cstdlib>
  10. #include <sys/types.h>
  11. #include <sys/time.h>
  12. #include <sys/resource.h>
  13. #include <grp.h>
  14. #include <pwd.h>
  15. #include "dinit-utmp.h"
  16. #include "dinit-util.h"
  17. #include "service-constants.h"
  18. #include "mconfig.h"
  19. struct service_flags_t
  20. {
  21. // on-start flags:
  22. bool rw_ready : 1; // file system should be writable once this service starts
  23. bool log_ready : 1; // syslog should be available once this service starts
  24. // Other service options flags:
  25. bool runs_on_console : 1; // run "in the foreground"
  26. bool starts_on_console : 1; // starts in the foreground
  27. bool shares_console : 1; // run on console, but not exclusively
  28. bool pass_cs_fd : 1; // pass this service a control socket connection via fd
  29. bool start_interruptible : 1; // the startup of this service process is ok to interrupt with SIGINT
  30. bool skippable : 1; // if interrupted the service is skipped (scripted services)
  31. bool signal_process_only : 1; // signal the session process, not the whole group
  32. bool always_chain : 1; // always start chain-to service on exit
  33. service_flags_t() noexcept : rw_ready(false), log_ready(false),
  34. runs_on_console(false), starts_on_console(false), shares_console(false),
  35. pass_cs_fd(false), start_interruptible(false), skippable(false), signal_process_only(false),
  36. always_chain(false)
  37. {
  38. }
  39. };
  40. // Resource limits for a particular service & particular resource
  41. struct service_rlimits
  42. {
  43. int resource_id; // RLIMIT_xxx identifying resource
  44. bool soft_set : 1;
  45. bool hard_set : 1;
  46. struct rlimit limits;
  47. service_rlimits(int id) : resource_id(id), soft_set(0), hard_set(0), limits({0,0}) { }
  48. };
  49. // Exception while loading a service
  50. class service_load_exc
  51. {
  52. public:
  53. std::string service_name;
  54. std::string exc_description;
  55. service_load_exc(const std::string &serviceName, std::string &&desc)
  56. : service_name(serviceName), exc_description(std::move(desc))
  57. {
  58. }
  59. };
  60. class service_not_found : public service_load_exc
  61. {
  62. public:
  63. service_not_found(const std::string &serviceName)
  64. : service_load_exc(serviceName, "service description not found.")
  65. {
  66. }
  67. };
  68. class service_load_error : public service_load_exc
  69. {
  70. public:
  71. service_load_error(const std::string &serviceName, std::string &&path, int fail_errno)
  72. : service_load_exc(serviceName, path + ": " + strerror(fail_errno))
  73. {
  74. }
  75. };
  76. class service_cyclic_dependency : public service_load_exc
  77. {
  78. public:
  79. service_cyclic_dependency(const std::string &serviceName)
  80. : service_load_exc(serviceName, "has cyclic dependency.")
  81. {
  82. }
  83. };
  84. class service_description_exc : public service_load_exc
  85. {
  86. public:
  87. const unsigned line_num = -1;
  88. const char * const setting_name = nullptr;
  89. service_description_exc(const std::string &serviceName, std::string &&extraInfo, unsigned line_num)
  90. : service_load_exc(serviceName, std::move(extraInfo)), line_num(line_num)
  91. {
  92. }
  93. service_description_exc(const std::string &serviceName, std::string &&extraInfo, const char *setting_name)
  94. : service_load_exc(serviceName, std::move(extraInfo)), setting_name(setting_name)
  95. {
  96. }
  97. };
  98. namespace dinit_load {
  99. using string = std::string;
  100. using string_iterator = std::string::iterator;
  101. // exception thrown when encountering a syntax issue when reading a setting value
  102. class setting_exception
  103. {
  104. std::string info;
  105. public:
  106. const unsigned line_num = -1;
  107. const char *setting_name = nullptr;
  108. setting_exception(unsigned line_num, std::string &&exc_info)
  109. : info(std::move(exc_info)), line_num(line_num)
  110. {
  111. }
  112. setting_exception(const char *setting_name, std::string &&exc_info)
  113. : info(std::move(exc_info)), setting_name(setting_name)
  114. {
  115. }
  116. std::string &get_info()
  117. {
  118. return info;
  119. }
  120. };
  121. // Utility function to skip white space. Returns an iterator at the
  122. // first non-white-space position (or at end).
  123. inline string_iterator skipws(string_iterator i, string_iterator end) noexcept
  124. {
  125. using std::locale;
  126. using std::isspace;
  127. while (i != end) {
  128. if (! isspace(*i, locale::classic())) {
  129. break;
  130. }
  131. ++i;
  132. }
  133. return i;
  134. }
  135. // Convert a signal name to the corresponding signal number
  136. inline int signal_name_to_number(std::string &signame) noexcept
  137. {
  138. if (signame == "none" || signame == "NONE") return 0;
  139. if (signame == "HUP") return SIGHUP;
  140. if (signame == "INT") return SIGINT;
  141. if (signame == "TERM") return SIGTERM;
  142. if (signame == "QUIT") return SIGQUIT;
  143. if (signame == "USR1") return SIGUSR1;
  144. if (signame == "USR2") return SIGUSR2;
  145. if (signame == "KILL") return SIGKILL;
  146. return -1;
  147. }
  148. // Read a setting/variable name; return empty string if no valid name
  149. inline string read_config_name(string_iterator & i, string_iterator end) noexcept
  150. {
  151. using std::locale;
  152. using std::ctype;
  153. using std::use_facet;
  154. // To avoid the horror of locales, we'll use the classic facet only, to identify digits, control
  155. // characters and punctuation. (Unless something is totally crazy, we are talking about ASCII or
  156. // a superset of it, but using the facet allows us to avoid that assumption). However, we're only
  157. // working with "narrow" char type so accuracy is limited. In general, that's not going to matter
  158. // much, but may allow certain unicode punctuation characters to be used as part of a name for example.
  159. const ctype<char> & facet = use_facet<ctype<char>>(locale::classic());
  160. string rval;
  161. // Don't allow empty name, numeric digit, or dash/dot at start of setting name
  162. if (i == end || (*i == '-' || *i == '.' || facet.is(ctype<char>::digit, *i))) {
  163. return {};
  164. }
  165. // Within the setting name, allow dash and dot; also allow any non-control, non-punctuation,
  166. // non-space character.
  167. while (i != end && (*i == '-' || *i == '.' || *i == '_'
  168. || (!facet.is(ctype<char>::cntrl, *i) && !facet.is(ctype<char>::punct, *i)
  169. && !facet.is(ctype<char>::space, *i)))) {
  170. rval += *i;
  171. ++i;
  172. }
  173. return rval;
  174. }
  175. // Read a setting value.
  176. //
  177. // In general a setting value is a single-line string. It may contain multiple parts
  178. // separated by white space (which is normally collapsed). A hash mark - # - denotes
  179. // the end of the value and the beginning of a comment (it should be preceded by
  180. // whitespace).
  181. //
  182. // Part of a value may be quoted using double quote marks, which prevents collapse
  183. // of whitespace and interpretation of most special characters (the quote marks will
  184. // not be considered part of the value). A backslash can precede a character (such
  185. // as '#' or '"' or another backslash) to remove its special meaning. Newline
  186. // characters are not allowed in values and cannot be quoted.
  187. //
  188. // This function expects the string to be in an ASCII-compatible encoding (the "classic" locale).
  189. //
  190. // Throws setting_exception on error.
  191. //
  192. // Params:
  193. // service_name - the name of the service to which the setting applies
  194. // i - reference to string iterator through the line
  195. // end - iterator at end of line (not including newline character if any)
  196. // part_positions - list of <int,int> to which the position of each setting value
  197. // part will be added as [start,end). May be null.
  198. inline string read_setting_value(unsigned line_num, string_iterator & i, string_iterator end,
  199. std::list<std::pair<unsigned,unsigned>> * part_positions = nullptr)
  200. {
  201. using std::locale;
  202. using std::isspace;
  203. i = skipws(i, end);
  204. string rval;
  205. bool new_part = true;
  206. int part_start;
  207. while (i != end) {
  208. char c = *i;
  209. if (c == '\"') {
  210. if (new_part) {
  211. part_start = rval.length();
  212. new_part = false;
  213. }
  214. // quoted string
  215. ++i;
  216. while (i != end) {
  217. c = *i;
  218. if (c == '\"') break;
  219. else if (c == '\\') {
  220. // A backslash escapes the following character.
  221. ++i;
  222. if (i != end) {
  223. c = *i;
  224. rval += c;
  225. }
  226. else {
  227. throw setting_exception(line_num, "line end follows backslash escape character (`\\')");
  228. }
  229. }
  230. else {
  231. rval += c;
  232. }
  233. ++i;
  234. }
  235. if (i == end) {
  236. // String wasn't terminated
  237. throw setting_exception(line_num, "unterminated quoted string");
  238. }
  239. }
  240. else if (c == '\\') {
  241. if (new_part) {
  242. part_start = rval.length();
  243. new_part = false;
  244. }
  245. // A backslash escapes the next character
  246. ++i;
  247. if (i != end) {
  248. rval += *i;
  249. }
  250. else {
  251. throw setting_exception(line_num, "backslash escape (`\\') not followed by character");
  252. }
  253. }
  254. else if (isspace(c, locale::classic())) {
  255. if (! new_part && part_positions != nullptr) {
  256. part_positions->emplace_back(part_start, rval.length());
  257. new_part = true;
  258. }
  259. i = skipws(i, end);
  260. if (i == end) break;
  261. if (*i == '#') break; // comment
  262. rval += ' '; // collapse ws to a single space
  263. continue;
  264. }
  265. else if (c == '#') {
  266. // Possibly intended a comment; we require leading whitespace to reduce occurrence of accidental
  267. // comments in setting values.
  268. throw setting_exception(line_num, "hashmark (`#') comment must be separated from setting value by whitespace");
  269. }
  270. else {
  271. if (new_part) {
  272. part_start = rval.length();
  273. new_part = false;
  274. }
  275. rval += c;
  276. }
  277. ++i;
  278. }
  279. // Got to end:
  280. if (part_positions != nullptr) {
  281. part_positions->emplace_back(part_start, rval.length());
  282. }
  283. return rval;
  284. }
  285. // Parse a userid parameter which may be a numeric user ID or a username. If a name, the
  286. // userid is looked up via the system user database (getpwnam() function). In this case,
  287. // the associated group is stored in the location specified by the group_p parameter if
  288. // it is not null.
  289. inline uid_t parse_uid_param(unsigned line_num, const std::string &param, const std::string &service_name,
  290. const char *setting_name, gid_t *group_p)
  291. {
  292. const char * uid_err_msg = "specified user id contains invalid numeric characters "
  293. "or is outside allowed range.";
  294. // Could be a name or a numeric id. But we should assume numeric first, just in case
  295. // a user manages to give themselves a username that parses as a number.
  296. std::size_t ind = 0;
  297. try {
  298. // POSIX does not specify whether uid_t is a signed or unsigned type, but regardless
  299. // is is probably safe to assume that valid values are positive. We'll also assert
  300. // that the value range fits within "unsigned long long" since it seems unlikely
  301. // that would ever not be the case.
  302. static_assert((uintmax_t)std::numeric_limits<uid_t>::max()
  303. <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "uid_t is too large");
  304. unsigned long long v = std::stoull(param, &ind, 0);
  305. if (v > static_cast<unsigned long long>(std::numeric_limits<uid_t>::max())
  306. || ind != param.length()) {
  307. throw service_description_exc(service_name, std::string(setting_name) + ": " + uid_err_msg, line_num);
  308. }
  309. return v;
  310. }
  311. catch (std::out_of_range &exc) {
  312. throw service_description_exc(service_name, uid_err_msg, line_num);
  313. }
  314. catch (std::invalid_argument &exc) {
  315. // Ok, so it doesn't look like a number: proceed...
  316. }
  317. errno = 0;
  318. struct passwd * pwent = getpwnam(param.c_str());
  319. if (pwent == nullptr) {
  320. // Maybe an error, maybe just no entry.
  321. if (errno == 0) {
  322. throw service_description_exc(service_name, std::string(setting_name) + ": specified user \"" + param
  323. + "\" does not exist in system database.", line_num);
  324. }
  325. else {
  326. throw service_description_exc(service_name, std::string("error accessing user database: ")
  327. + strerror(errno), line_num);
  328. }
  329. }
  330. if (group_p) {
  331. *group_p = pwent->pw_gid;
  332. }
  333. return pwent->pw_uid;
  334. }
  335. inline gid_t parse_gid_param(unsigned line_num, const std::string &param, const char *setting_name,
  336. const std::string &service_name)
  337. {
  338. const char * gid_err_msg = "specified group id contains invalid numeric characters or is "
  339. "outside allowed range.";
  340. // Could be a name or a numeric id. But we should assume numeric first, just in case
  341. // a user manages to give themselves a username that parses as a number.
  342. std::size_t ind = 0;
  343. try {
  344. // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
  345. // is is probably safe to assume that valid values are positive. We'll also assume
  346. // that the value range fits with "unsigned long long" since it seems unlikely
  347. // that would ever not be the case.
  348. static_assert((uintmax_t)std::numeric_limits<gid_t>::max()
  349. <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "gid_t is too large");
  350. unsigned long long v = std::stoull(param, &ind, 0);
  351. if (v > static_cast<unsigned long long>(std::numeric_limits<gid_t>::max())
  352. || ind != param.length()) {
  353. throw service_description_exc(service_name, std::string(setting_name) + ": " + gid_err_msg, line_num);
  354. }
  355. return v;
  356. }
  357. catch (std::out_of_range &exc) {
  358. throw service_description_exc(service_name, std::string(setting_name) + ": " + gid_err_msg, line_num);
  359. }
  360. catch (std::invalid_argument &exc) {
  361. // Ok, so it doesn't look like a number: proceed...
  362. }
  363. errno = 0;
  364. struct group * grent = getgrnam(param.c_str());
  365. if (grent == nullptr) {
  366. // Maybe an error, maybe just no entry.
  367. if (errno == 0) {
  368. throw service_description_exc(service_name, std::string(setting_name) + ": specified group \"" + param
  369. + "\" does not exist in system database.", line_num);
  370. }
  371. else {
  372. throw service_description_exc(service_name, std::string("error accessing group database: ")
  373. + strerror(errno), line_num);
  374. }
  375. }
  376. return grent->gr_gid;
  377. }
  378. // Parse a time, specified as a decimal number of seconds (with optional fractional component after decimal
  379. // point or decimal comma).
  380. inline void parse_timespec(unsigned line_num, const std::string &paramval, const std::string &servicename,
  381. const char * paramname, timespec &ts)
  382. {
  383. decltype(ts.tv_sec) isec = 0;
  384. decltype(ts.tv_nsec) insec = 0;
  385. auto max_secs = std::numeric_limits<decltype(isec)>::max() / 10;
  386. auto len = paramval.length();
  387. decltype(len) i;
  388. for (i = 0; i < len; i++) {
  389. char ch = paramval[i];
  390. if (ch == '.' || ch == ',') {
  391. i++;
  392. break;
  393. }
  394. if (ch < '0' || ch > '9') {
  395. throw service_description_exc(servicename, std::string("bad value for ") + paramname, line_num);
  396. }
  397. // check for overflow
  398. if (isec >= max_secs) {
  399. throw service_description_exc(servicename, std::string("too-large value for ") + paramname, line_num);
  400. }
  401. isec *= 10;
  402. isec += ch - '0';
  403. }
  404. decltype(insec) insec_m = 100000000; // 10^8
  405. for ( ; i < len; i++) {
  406. char ch = paramval[i];
  407. if (ch < '0' || ch > '9') {
  408. throw service_description_exc(servicename, std::string("bad value for ") + paramname, line_num);
  409. }
  410. insec += (ch - '0') * insec_m;
  411. insec_m /= 10;
  412. }
  413. ts.tv_sec = isec;
  414. ts.tv_nsec = insec;
  415. }
  416. // Parse an unsigned numeric parameter value
  417. inline unsigned long long parse_unum_param(unsigned line_num, const std::string &param,
  418. const std::string &service_name, unsigned long long max = std::numeric_limits<unsigned long long>::max())
  419. {
  420. const char * num_err_msg = "specified value contains invalid numeric characters or is outside "
  421. "allowed range.";
  422. std::size_t ind = 0;
  423. try {
  424. unsigned long long v = std::stoull(param, &ind, 0);
  425. if (v > max || ind != param.length()) {
  426. throw service_description_exc(service_name, num_err_msg, line_num);
  427. }
  428. return v;
  429. }
  430. catch (std::out_of_range &exc) {
  431. throw service_description_exc(service_name, num_err_msg, line_num);
  432. }
  433. catch (std::invalid_argument &exc) {
  434. throw service_description_exc(service_name, num_err_msg, line_num);
  435. }
  436. }
  437. // In a vector, find or create rlimits for a particular resource type.
  438. inline service_rlimits &find_rlimits(std::vector<service_rlimits> &all_rlimits, int resource_id)
  439. {
  440. for (service_rlimits &limits : all_rlimits) {
  441. if (limits.resource_id == resource_id) {
  442. return limits;
  443. }
  444. }
  445. all_rlimits.emplace_back(resource_id);
  446. return all_rlimits.back();
  447. }
  448. // Parse resource limits setting (can specify both hard and soft limit).
  449. inline void parse_rlimit(const std::string &line, unsigned line_num, const std::string &service_name,
  450. const char *param_name, service_rlimits &rlimit)
  451. {
  452. // Examples:
  453. // 4:5 - soft:hard limits both set
  454. // 4:- soft set, hard set to unlimited
  455. // 4: soft set, hard limit unchanged
  456. // 4 soft and hard limit set to same limit
  457. if (line.empty()) {
  458. throw service_description_exc(service_name, std::string(param_name) + ": bad value.", line_num);
  459. }
  460. const char *cline = line.c_str();
  461. rlimit.hard_set = rlimit.soft_set = false;
  462. try {
  463. const char * index = cline;
  464. errno = 0;
  465. if (cline[0] != ':') {
  466. rlimit.soft_set = true;
  467. if (cline[0] == '-') {
  468. rlimit.limits.rlim_cur = RLIM_INFINITY;
  469. index = cline + 1;
  470. }
  471. else {
  472. errno = 0;
  473. char *nindex;
  474. unsigned long long limit = std::strtoull(cline, &nindex, 0);
  475. index = nindex;
  476. if (errno == ERANGE || limit > std::numeric_limits<rlim_t>::max()) throw std::out_of_range("");
  477. if (index == cline) throw std::invalid_argument("");
  478. rlimit.limits.rlim_cur = limit;
  479. }
  480. if (*index == 0) {
  481. rlimit.hard_set = true;
  482. rlimit.limits.rlim_max = rlimit.limits.rlim_cur;
  483. return;
  484. }
  485. if (*index != ':') {
  486. throw service_description_exc(service_name, std::string(param_name) + ": bad value.", line_num);
  487. }
  488. }
  489. index++;
  490. if (*index == 0) return;
  491. rlimit.hard_set = true;
  492. if (*index == '-') {
  493. rlimit.limits.rlim_max = RLIM_INFINITY;
  494. if (index[1] != 0) {
  495. throw service_description_exc(service_name, std::string(param_name) + ": bad value.", line_num);
  496. }
  497. }
  498. else {
  499. const char *hard_start = index;
  500. char *nindex;
  501. errno = 0;
  502. unsigned long long limit = std::strtoull(hard_start, &nindex, 0);
  503. index = nindex;
  504. if (errno == ERANGE || limit > std::numeric_limits<rlim_t>::max()) throw std::out_of_range("");
  505. if (index == hard_start) throw std::invalid_argument("");
  506. rlimit.limits.rlim_max = limit;
  507. }
  508. }
  509. catch (std::invalid_argument &exc) {
  510. throw service_description_exc(service_name, std::string(param_name) + ": bad value.", line_num);
  511. }
  512. catch (std::out_of_range &exc) {
  513. throw service_description_exc(service_name, std::string(param_name) + ": too-large value.", line_num);
  514. }
  515. }
  516. // Process an opened service file, line by line.
  517. // name - the service name
  518. // service_file - the service file input stream
  519. // func - a function of the form:
  520. // void(string &line, string &setting, string_iterator i, string_iterator end)
  521. // Called with:
  522. // line - the complete line (excluding newline character)
  523. // setting - the setting name, from the beginning of the line
  524. // i - iterator at the beginning of the setting value
  525. // end - iterator marking the end of the line
  526. //
  527. // May throw service load exceptions or I/O exceptions if enabled on stream.
  528. template <typename T>
  529. void process_service_file(string name, std::istream &service_file, T func)
  530. {
  531. string line;
  532. unsigned line_num = 0;
  533. while (getline(service_file, line)) {
  534. ++line_num;
  535. string::iterator i = line.begin();
  536. string::iterator end = line.end();
  537. i = skipws(i, end);
  538. if (i != end) {
  539. if (*i == '#') {
  540. continue; // comment line
  541. }
  542. string setting = read_config_name(i, end);
  543. i = skipws(i, end);
  544. if (setting.empty() || i == end || (*i != '=' && *i != ':')) {
  545. throw service_description_exc(name, "badly formed line.", line_num);
  546. }
  547. i = skipws(++i, end);
  548. func(line, line_num, setting, i, end);
  549. }
  550. }
  551. }
  552. // A dummy lint-reporting "function".
  553. static auto dummy_lint = [](const char *){};
  554. // Resolve leading variables in paths using the environment
  555. static auto resolve_env_var = [](const string &name){
  556. const char *r = getenv(name.c_str());
  557. if (r == nullptr) {
  558. return "";
  559. }
  560. return r;
  561. };
  562. // Resolve a path with variable substitutions ($varname). '$$' resolves to a single '$'.
  563. // Throws setting_exception on failure.
  564. // p - path to resolve
  565. // var_resolve - function to translate names to values; returning string or const char *;
  566. // may throw setting_exception
  567. template <typename T>
  568. inline std::string resolve_path(const char *setting_name, std::string &&p, T &var_resolve)
  569. {
  570. auto dpos = p.find('$');
  571. if (dpos == string::npos) {
  572. // shortcut the case where there are no substitutions:
  573. return std::move(p);
  574. }
  575. string r;
  576. string::size_type last_pos = 0;
  577. do {
  578. r.append(p, last_pos, dpos - last_pos); // non-substituted portion
  579. ++dpos;
  580. if (dpos < p.size() && p[dpos] == '$') {
  581. // double '$' resolves to a single '$' in output
  582. r += '$';
  583. last_pos = dpos + 1;
  584. dpos = p.find('$', last_pos);
  585. continue;
  586. }
  587. auto i = std::next(p.begin(), dpos);
  588. string name = read_config_name(i, p.end());
  589. if (name.empty()) {
  590. throw setting_exception(setting_name, "invalid/missing variable name after '$'");
  591. }
  592. string value = var_resolve(name);
  593. r.append(value);
  594. last_pos = i - p.begin();
  595. dpos = p.find('$', last_pos);
  596. } while (dpos != string::npos);
  597. r.append(p, last_pos, string::npos);
  598. return r;
  599. }
  600. // Substitute variable references in a command line with their values. Specified offsets must give
  601. // the location of separate arguments after word splitting and are adjusted appropriately.
  602. //
  603. // throws: setting_exception if a $-substitution is ill-formed, or if the command line is too long;
  604. // bad_alloc on allocation failure
  605. template <typename T>
  606. static void cmdline_var_subst(const char *setting_name, std::string &line,
  607. std::list<std::pair<unsigned,unsigned>> &offsets, T &var_resolve)
  608. {
  609. auto dindx = line.find('$');
  610. if (dindx == string::npos) {
  611. return;
  612. }
  613. if (line.length() > (size_t)std::numeric_limits<int>::max()) {
  614. // (avoid potential for overflow later)
  615. throw setting_exception(setting_name, "command line too long");
  616. }
  617. auto i = offsets.begin();
  618. unsigned xpos = 0; // position to copy from
  619. std::string r_line;
  620. int offadj = 0;
  621. while (i != offsets.end()) {
  622. i->first += offadj; // don't adjust end yet
  623. while (i->second > dindx) {
  624. r_line.append(line, xpos, dindx - xpos); // copy unmatched part
  625. if (line[dindx + 1] == '$') {
  626. // double dollar, collapse to single
  627. r_line += '$';
  628. xpos = dindx + 2;
  629. --offadj;
  630. }
  631. else {
  632. // variable
  633. auto i = std::next(line.begin(), dindx + 1);
  634. string name = read_config_name(i, line.end());
  635. if (name.empty()) {
  636. throw setting_exception(setting_name, "invalid/missing variable name after '$'");
  637. }
  638. size_t line_len_before = r_line.size();
  639. r_line.append(var_resolve(name));
  640. size_t line_len_after = r_line.size();
  641. if (line_len_after > (size_t)std::numeric_limits<int>::max()) {
  642. // (avoid potential overflow)
  643. throw setting_exception(setting_name, "command line too long (after substitution)");
  644. }
  645. xpos = i - line.begin();
  646. int name_len = xpos - dindx;
  647. offadj += (int)line_len_after - (int)line_len_before - name_len;
  648. }
  649. dindx = line.find('$', xpos);
  650. }
  651. i->second += offadj;
  652. ++i;
  653. while (i != offsets.end() && i->second < dindx) {
  654. i->first += offadj;
  655. i->second += offadj;
  656. ++i;
  657. }
  658. }
  659. r_line.append(line, xpos); // copy final unmatched part
  660. line = std::move(r_line);
  661. return;
  662. }
  663. // A wrapper type for service parameters. It is parameterised by dependency type.
  664. template <class dep_type>
  665. class service_settings_wrapper
  666. {
  667. template <typename A, typename B> using pair = std::pair<A,B>;
  668. template <typename A> using list = std::list<A>;
  669. public:
  670. ha_string command;
  671. list<pair<unsigned,unsigned>> command_offsets; // [start,end) offset of each arg (inc. executable)
  672. ha_string stop_command;
  673. list<pair<unsigned,unsigned>> stop_command_offsets;
  674. string working_dir;
  675. string pid_file;
  676. string env_file;
  677. bool do_sub_vars = false;
  678. service_type_t service_type = service_type_t::INTERNAL;
  679. list<dep_type> depends;
  680. list<std::string> before_svcs;
  681. string logfile;
  682. service_flags_t onstart_flags;
  683. int term_signal = SIGTERM; // termination signal
  684. bool auto_restart = false;
  685. bool smooth_recovery = false;
  686. string socket_path;
  687. int socket_perms = 0666;
  688. // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
  689. // invalid value, so it's safe to assume that we can do the same:
  690. uid_t socket_uid = -1;
  691. gid_t socket_uid_gid = -1; // primary group of socket user if known
  692. gid_t socket_gid = -1;
  693. // Restart limit interval / count; default is 10 seconds, 3 restarts:
  694. timespec restart_interval = { .tv_sec = 10, .tv_nsec = 0 };
  695. int max_restarts = 3;
  696. timespec restart_delay = { .tv_sec = 0, .tv_nsec = 200000000 };
  697. timespec stop_timeout = { .tv_sec = 10, .tv_nsec = 0 };
  698. timespec start_timeout = { .tv_sec = 60, .tv_nsec = 0 };
  699. std::vector<service_rlimits> rlimits;
  700. int readiness_fd = -1; // readiness fd in service process
  701. string readiness_var; // environment var to hold readiness fd
  702. uid_t run_as_uid = -1;
  703. gid_t run_as_uid_gid = -1; // primary group of "run as" uid if known
  704. gid_t run_as_gid = -1;
  705. string chain_to_name;
  706. #if SUPPORT_CGROUPS
  707. string run_in_cgroup;
  708. #endif
  709. #if USE_UTMPX
  710. char inittab_id[sizeof(utmpx().ut_id)] = {0};
  711. char inittab_line[sizeof(utmpx().ut_line)] = {0};
  712. #endif
  713. // Finalise settings (after processing all setting lines), perform some basic sanity checks and
  714. // optionally some additional lint checks. May throw service_description_exc
  715. //
  716. // Note: we have the do_report_lint parameter to prevent code (and strings) being emitted for lint
  717. // checks even when the dummy_lint function is used. (Ideally the compiler would optimise them away).
  718. template <typename T, typename U = decltype(dummy_lint), typename V = decltype(resolve_env_var),
  719. bool do_report_lint = !std::is_same<U, decltype(dummy_lint)>::value>
  720. void finalise(T &report_error, U &report_lint = dummy_lint, V &var_subst = resolve_env_var)
  721. {
  722. if (service_type == service_type_t::PROCESS || service_type == service_type_t::BGPROCESS
  723. || service_type == service_type_t::SCRIPTED) {
  724. if (command.empty()) {
  725. report_error("'command' setting not specified.");
  726. }
  727. }
  728. if (do_report_lint && service_type == service_type_t::INTERNAL) {
  729. if (!command.empty()) {
  730. report_lint("'command' specified, but 'type' is internal (or not specified).");
  731. }
  732. if (!stop_command.empty()) {
  733. report_lint("'stop-command' specified, but 'type' is internal (or not specified).");
  734. }
  735. if (!working_dir.empty()) {
  736. report_lint("'working-dir' specified, but 'type' is internal (or not specified).");
  737. }
  738. #if SUPPORT_CGROUPS
  739. if (!run_in_cgroup.empty()) {
  740. report_lint("'run-in-cgroup' specified, but 'type' is internal (or not specified).");
  741. }
  742. #endif
  743. if (run_as_uid != (uid_t)-1) {
  744. report_lint("'run-as' specified, but 'type' is internal (or not specified).");
  745. }
  746. if (!socket_path.empty()) {
  747. report_lint("'socket-listen' specified, but 'type' is internal (or not specified).");
  748. }
  749. #if USE_UTMPX
  750. if (inittab_id[0] != 0 || inittab_line[0] != 0) {
  751. report_lint("'inittab_line' or 'inittab_id' specified, but 'type' is internal (or not specified).");
  752. }
  753. #endif
  754. if (onstart_flags.signal_process_only || onstart_flags.start_interruptible) {
  755. report_lint("signal options were specified, but 'type' is internal (or not specified).");
  756. }
  757. if (onstart_flags.pass_cs_fd) {
  758. report_lint("option 'pass_cs_fd' was specified, but 'type' is internal (or not specified).");
  759. }
  760. if (onstart_flags.skippable) {
  761. report_lint("option 'skippable' was specified, but 'type' is internal (or not specified).");
  762. }
  763. }
  764. if (service_type == service_type_t::BGPROCESS) {
  765. if (pid_file.empty()) {
  766. report_error("process ID file ('pid-file') not specified for bgprocess service.");
  767. }
  768. if (readiness_fd != -1 || !readiness_var.empty()) {
  769. report_error("readiness notification ('ready-notification') is not supported "
  770. "for bgprocess services.");
  771. }
  772. }
  773. // Resolve paths via variable substitution
  774. {
  775. auto do_resolve = [&](const char *setting_name, string &setting_value) {
  776. try {
  777. setting_value = resolve_path(setting_name, std::move(setting_value), var_subst);
  778. }
  779. catch (setting_exception &exc) {
  780. report_error((string() + setting_name + ": " + exc.get_info()).c_str());
  781. }
  782. };
  783. do_resolve("socket-listen", socket_path);
  784. do_resolve("logfile", logfile);
  785. do_resolve("env-file", env_file);
  786. do_resolve("working-dir", working_dir);
  787. do_resolve("pid-file", pid_file);
  788. }
  789. // If socket_gid hasn't been explicitly set, but the socket_uid was specified as a name (and
  790. // we therefore recovered the primary group), use the primary group of the specified user.
  791. if (socket_gid == (gid_t)-1) socket_gid = socket_uid_gid;
  792. // likewise for "run as" gid/uid.
  793. if (run_as_gid == (gid_t)-1) run_as_gid = run_as_uid_gid;
  794. }
  795. };
  796. // Process a service description line. In general, parse the setting value and record the parsed value
  797. // in a service settings wrapper object. Errors will be reported via service_description_exc exception.
  798. //
  799. // type parameters:
  800. // settings_wrapper : wrapper for service settings
  801. // load_service_t : type of load service function/lambda (see below)
  802. // process_dep_dir_t : type of process_dep_dir funciton/lambda (see below)
  803. //
  804. // parameters:
  805. // settings : wrapper object for service settings
  806. // name : name of the service being processed
  807. // line : the current line of the service description file
  808. // setting : the name of the setting (from the beginning of line)
  809. // i : iterator at beginning of setting value (including whitespace)
  810. // end : iterator at end of line
  811. // load_service : function to load a service
  812. // arguments: const char *service_name
  813. // return: a value that can be used (with a dependency type) to construct a dependency
  814. // in the 'depends' vector within the 'settings' object
  815. // process_dep_dir : function to process a dependency directory
  816. // arguments: decltype(settings.depends) &dependencies
  817. // const string &waitsford - directory as specified in parameter
  818. // dependency_type dep_type - type of dependency to add
  819. template <typename settings_wrapper,
  820. typename load_service_t,
  821. typename process_dep_dir_t>
  822. void process_service_line(settings_wrapper &settings, const char *name, string &line, unsigned line_num,
  823. string &setting, string::iterator &i, string::iterator &end, load_service_t load_service,
  824. process_dep_dir_t process_dep_dir)
  825. {
  826. if (setting == "command") {
  827. settings.command = read_setting_value(line_num, i, end, &settings.command_offsets);
  828. }
  829. else if (setting == "working-dir") {
  830. settings.working_dir = read_setting_value(line_num, i, end, nullptr);
  831. }
  832. else if (setting == "env-file") {
  833. settings.env_file = read_setting_value(line_num, i, end, nullptr);
  834. }
  835. #if SUPPORT_CGROUPS
  836. else if (setting == "run-in-cgroup") {
  837. settings.run_in_cgroup = read_setting_value(line_num, i, end, nullptr);
  838. }
  839. #endif
  840. else if (setting == "socket-listen") {
  841. settings.socket_path = read_setting_value(line_num, i, end, nullptr);
  842. }
  843. else if (setting == "socket-permissions") {
  844. string sock_perm_str = read_setting_value(line_num, i, end, nullptr);
  845. std::size_t ind = 0;
  846. try {
  847. settings.socket_perms = std::stoi(sock_perm_str, &ind, 8);
  848. if (ind != sock_perm_str.length()) {
  849. throw std::logic_error("");
  850. }
  851. }
  852. catch (std::logic_error &exc) {
  853. throw service_description_exc(name, "socket-permissions: badly-formed or "
  854. "out-of-range numeric value", line_num);
  855. }
  856. }
  857. else if (setting == "socket-uid") {
  858. string sock_uid_s = read_setting_value(line_num, i, end, nullptr);
  859. settings.socket_uid = parse_uid_param(line_num, sock_uid_s, name, "socket-uid", &settings.socket_uid_gid);
  860. }
  861. else if (setting == "socket-gid") {
  862. string sock_gid_s = read_setting_value(line_num, i, end, nullptr);
  863. settings.socket_gid = parse_gid_param(line_num, sock_gid_s, "socket-gid", name);
  864. }
  865. else if (setting == "stop-command") {
  866. settings.stop_command = read_setting_value(line_num, i, end, &settings.stop_command_offsets);
  867. }
  868. else if (setting == "pid-file") {
  869. settings.pid_file = read_setting_value(line_num, i, end);
  870. }
  871. else if (setting == "depends-on") {
  872. string dependency_name = read_setting_value(line_num, i, end);
  873. settings.depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::REGULAR);
  874. }
  875. else if (setting == "depends-ms") {
  876. string dependency_name = read_setting_value(line_num, i, end);
  877. settings.depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::MILESTONE);
  878. }
  879. else if (setting == "waits-for") {
  880. string dependency_name = read_setting_value(line_num, i, end);
  881. settings.depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::WAITS_FOR);
  882. }
  883. else if (setting == "waits-for.d") {
  884. string waitsford = read_setting_value(line_num, i, end);
  885. process_dep_dir(settings.depends, waitsford, dependency_type::WAITS_FOR);
  886. }
  887. else if (setting == "before") {
  888. string before_name = read_setting_value(line_num, i, end);
  889. settings.before_svcs.emplace_back(std::move(before_name));
  890. }
  891. else if (setting == "logfile") {
  892. settings.logfile = read_setting_value(line_num, i, end);
  893. }
  894. else if (setting == "restart") {
  895. string restart = read_setting_value(line_num, i, end);
  896. settings.auto_restart = (restart == "yes" || restart == "true");
  897. }
  898. else if (setting == "smooth-recovery") {
  899. string recovery = read_setting_value(line_num, i, end);
  900. settings.smooth_recovery = (recovery == "yes" || recovery == "true");
  901. }
  902. else if (setting == "type") {
  903. string type_str = read_setting_value(line_num, i, end);
  904. if (type_str == "scripted") {
  905. settings.service_type = service_type_t::SCRIPTED;
  906. }
  907. else if (type_str == "process") {
  908. settings.service_type = service_type_t::PROCESS;
  909. }
  910. else if (type_str == "bgprocess") {
  911. settings.service_type = service_type_t::BGPROCESS;
  912. }
  913. else if (type_str == "internal") {
  914. settings.service_type = service_type_t::INTERNAL;
  915. }
  916. else {
  917. throw service_description_exc(name, "service type must be one of: \"scripted\","
  918. " \"process\", \"bgprocess\" or \"internal\"", line_num);
  919. }
  920. }
  921. else if (setting == "options") {
  922. std::list<std::pair<unsigned,unsigned>> indices;
  923. string onstart_cmds = read_setting_value(line_num, i, end, &indices);
  924. for (auto indexpair : indices) {
  925. string option_txt = onstart_cmds.substr(indexpair.first,
  926. indexpair.second - indexpair.first);
  927. if (option_txt == "starts-rwfs") {
  928. settings.onstart_flags.rw_ready = true;
  929. }
  930. else if (option_txt == "starts-log") {
  931. settings.onstart_flags.log_ready = true;
  932. }
  933. else if (option_txt == "runs-on-console") {
  934. settings.onstart_flags.runs_on_console = true;
  935. // A service that runs on the console necessarily starts on console:
  936. settings.onstart_flags.starts_on_console = true;
  937. settings.onstart_flags.shares_console = false;
  938. }
  939. else if (option_txt == "starts-on-console") {
  940. settings.onstart_flags.starts_on_console = true;
  941. settings.onstart_flags.shares_console = false;
  942. }
  943. else if (option_txt == "shares-console") {
  944. settings.onstart_flags.shares_console = true;
  945. settings.onstart_flags.runs_on_console = false;
  946. settings.onstart_flags.starts_on_console = false;
  947. }
  948. else if (option_txt == "pass-cs-fd") {
  949. settings.onstart_flags.pass_cs_fd = true;
  950. }
  951. else if (option_txt == "start-interruptible") {
  952. settings.onstart_flags.start_interruptible = true;
  953. }
  954. else if (option_txt == "skippable") {
  955. settings.onstart_flags.skippable = true;
  956. }
  957. else if (option_txt == "signal-process-only") {
  958. settings.onstart_flags.signal_process_only = true;
  959. }
  960. else if (option_txt == "always-chain") {
  961. settings.onstart_flags.always_chain = true;
  962. }
  963. else {
  964. throw service_description_exc(name, "Unknown option: " + option_txt, line_num);
  965. }
  966. }
  967. }
  968. else if (setting == "load-options") {
  969. std::list<std::pair<unsigned,unsigned>> indices;
  970. string load_opts = read_setting_value(line_num, i, end, &indices);
  971. for (auto indexpair : indices) {
  972. string option_txt = load_opts.substr(indexpair.first,
  973. indexpair.second - indexpair.first);
  974. if (option_txt == "sub-vars") {
  975. // substitute environment variables in command line
  976. settings.do_sub_vars = true;
  977. }
  978. else if (option_txt == "no-sub-vars") {
  979. settings.do_sub_vars = false;
  980. }
  981. else {
  982. throw service_description_exc(name, "unknown load option: " + option_txt, line_num);
  983. }
  984. }
  985. }
  986. else if (setting == "term-signal" || setting == "termsignal") {
  987. // Note: "termsignal" supported for legacy reasons.
  988. string signame = read_setting_value(line_num, i, end, nullptr);
  989. int signo = signal_name_to_number(signame);
  990. if (signo == -1) {
  991. throw service_description_exc(name, "unknown/unsupported termination signal: "
  992. + signame, line_num);
  993. }
  994. else {
  995. settings.term_signal = signo;
  996. }
  997. }
  998. else if (setting == "restart-limit-interval") {
  999. string interval_str = read_setting_value(line_num, i, end, nullptr);
  1000. parse_timespec(line_num, interval_str, name, "restart-limit-interval", settings.restart_interval);
  1001. }
  1002. else if (setting == "restart-delay") {
  1003. string rsdelay_str = read_setting_value(line_num, i, end, nullptr);
  1004. parse_timespec(line_num, rsdelay_str, name, "restart-delay", settings.restart_delay);
  1005. }
  1006. else if (setting == "restart-limit-count") {
  1007. string limit_str = read_setting_value(line_num, i, end, nullptr);
  1008. settings.max_restarts = parse_unum_param(line_num, limit_str, name, std::numeric_limits<int>::max());
  1009. }
  1010. else if (setting == "stop-timeout") {
  1011. string stoptimeout_str = read_setting_value(line_num, i, end, nullptr);
  1012. parse_timespec(line_num, stoptimeout_str, name, "stop-timeout", settings.stop_timeout);
  1013. }
  1014. else if (setting == "start-timeout") {
  1015. string starttimeout_str = read_setting_value(line_num, i, end, nullptr);
  1016. parse_timespec(line_num, starttimeout_str, name, "start-timeout", settings.start_timeout);
  1017. }
  1018. else if (setting == "run-as") {
  1019. string run_as_str = read_setting_value(line_num, i, end, nullptr);
  1020. settings.run_as_uid = parse_uid_param(line_num, run_as_str, name, "run-as", &settings.run_as_uid_gid);
  1021. }
  1022. else if (setting == "chain-to") {
  1023. settings.chain_to_name = read_setting_value(line_num, i, end, nullptr);
  1024. }
  1025. else if (setting == "ready-notification") {
  1026. string notify_setting = read_setting_value(line_num, i, end, nullptr);
  1027. if (starts_with(notify_setting, "pipefd:")) {
  1028. settings.readiness_fd = parse_unum_param(line_num, notify_setting.substr(7 /* len 'pipefd:' */),
  1029. name, std::numeric_limits<int>::max());
  1030. }
  1031. else if (starts_with(notify_setting, "pipevar:")) {
  1032. settings.readiness_var = notify_setting.substr(8 /* len 'pipevar:' */);
  1033. if (settings.readiness_var.empty()) {
  1034. throw service_description_exc(name, "invalid pipevar variable name "
  1035. "in ready-notification", line_num);
  1036. }
  1037. }
  1038. else {
  1039. throw service_description_exc(name, "unknown ready-notification setting: "
  1040. + notify_setting, line_num);
  1041. }
  1042. }
  1043. else if (setting == "inittab-id") {
  1044. string inittab_setting = read_setting_value(line_num, i, end, nullptr);
  1045. #if USE_UTMPX
  1046. if (inittab_setting.length() > sizeof(settings.inittab_id)) {
  1047. throw service_description_exc(name, "inittab-id setting is too long", line_num);
  1048. }
  1049. strncpy(settings.inittab_id, inittab_setting.c_str(), sizeof(settings.inittab_id));
  1050. #endif
  1051. }
  1052. else if (setting == "inittab-line") {
  1053. string inittab_setting = read_setting_value(line_num, i, end, nullptr);
  1054. #if USE_UTMPX
  1055. if (inittab_setting.length() > sizeof(settings.inittab_line)) {
  1056. throw service_description_exc(name, "inittab-line setting is too long", line_num);
  1057. }
  1058. strncpy(settings.inittab_line, inittab_setting.c_str(), sizeof(settings.inittab_line));
  1059. #endif
  1060. }
  1061. else if (setting == "rlimit-nofile") {
  1062. string nofile_setting = read_setting_value(line_num, i, end, nullptr);
  1063. service_rlimits &nofile_limits = find_rlimits(settings.rlimits, RLIMIT_NOFILE);
  1064. parse_rlimit(nofile_setting, line_num, name, "rlimit-nofile", nofile_limits);
  1065. }
  1066. else if (setting == "rlimit-core") {
  1067. string core_setting = read_setting_value(line_num, i, end, nullptr);
  1068. service_rlimits &nofile_limits = find_rlimits(settings.rlimits, RLIMIT_CORE);
  1069. parse_rlimit(core_setting, line_num, name, "rlimit-core", nofile_limits);
  1070. }
  1071. else if (setting == "rlimit-data") {
  1072. string data_setting = read_setting_value(line_num, i, end, nullptr);
  1073. service_rlimits &nofile_limits = find_rlimits(settings.rlimits, RLIMIT_DATA);
  1074. parse_rlimit(data_setting, line_num, name, "rlimit-data", nofile_limits);
  1075. }
  1076. else if (setting == "rlimit-addrspace") {
  1077. #if defined(RLIMIT_AS)
  1078. string addrspace_setting = read_setting_value(line_num, i, end, nullptr);
  1079. service_rlimits &nofile_limits = find_rlimits(settings.rlimits, RLIMIT_AS);
  1080. parse_rlimit(addrspace_setting, line_num, name, "rlimit-addrspace", nofile_limits);
  1081. #endif
  1082. }
  1083. else {
  1084. throw service_description_exc(name, "unknown setting: '" + setting + "'.", line_num);
  1085. }
  1086. }
  1087. } // namespace dinit_load
  1088. using dinit_load::process_service_file;