dinit-env.h 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. #ifndef DINIT_ENV_H_INCLUDED
  2. #define DINIT_ENV_H_INCLUDED 1
  3. #include <fstream>
  4. #include <unordered_map>
  5. #include <string>
  6. #include <dinit-util.h>
  7. #include <baseproc-sys.h>
  8. class environment;
  9. extern environment main_env;
  10. // Read an environment file and set variables in the current environment.
  11. // file - the file to read
  12. // log_warnings - if true, syntactic errors are logged
  13. // throw_on_open_failure - if true, failure to open file will result in a std::system_error exception
  14. // May throw bad_alloc or system_error. This function is available within dinit only, not utilities,
  15. // but see read_env_file_inline below.
  16. void read_env_file(const char *file, bool log_warnings, environment &env, bool throw_on_open_failure);
  17. // Note that our sets (defined as part of environment class below) allow searching based on a name
  18. // only (string_view) or "NAME=VALUE" assignment pair (std::string). It is important to always
  19. // search using the correct type.
  20. // Hash environment variable name only (not including value)
  21. struct hash_env_name
  22. {
  23. size_t operator()(const std::string &s) const
  24. {
  25. size_t eq_pos = s.find('=');
  26. return hash(string_view(s.data(), eq_pos));
  27. }
  28. size_t operator()(string_view s) const
  29. {
  30. return hash(s);
  31. }
  32. };
  33. // Comparison predicate for environment variables, checking name only
  34. struct env_equal_name
  35. {
  36. bool operator()(const std::string &a, const std::string &b) const noexcept
  37. {
  38. size_t a_eq_pos = a.find('=');
  39. size_t b_eq_pos = b.find('=');
  40. return string_view(a.data(), a_eq_pos) == string_view(b.data(), b_eq_pos);
  41. }
  42. // For comparison between a string and string_view, we can assume the view is just the name
  43. bool operator()(const std::string &a, string_view b) const noexcept
  44. {
  45. size_t a_eq_pos = a.find('=');
  46. return string_view(a.data(), a_eq_pos) == b;
  47. }
  48. bool operator()(string_view a, const std::string &b) const noexcept
  49. {
  50. return operator()(b, a);
  51. }
  52. };
  53. class environment
  54. {
  55. // Whether to keep the parent environment, as a whole. Individual variables can still be
  56. // modified or unset.
  57. bool keep_parent_env = true;
  58. // TODO keep natural order somehow
  59. using env_set = dinit_unordered_set<std::string, hash_env_name, env_equal_name>;
  60. using env_names = dinit_unordered_set<std::string,hash_sv,dinit_equal_to>;
  61. // Which specific variables to keep from parent environment (if keep_parent_env is false)
  62. env_names import_from_parent;
  63. // Which specific variables to remove (if keep_parent_env is true)
  64. env_names undefine;
  65. // set of variables modified or set:
  66. env_set set_vars;
  67. string_view find_var_name(string_view var)
  68. {
  69. const char *var_ch;
  70. for (var_ch = var.data(); *var_ch != '='; ++var_ch) {
  71. if (*var_ch == '\0') break;
  72. }
  73. return {var.data(), (size_t)(var_ch - var.data())};
  74. }
  75. public:
  76. environment() = default;
  77. environment(environment &&other) noexcept = default;
  78. environment &operator=(environment &&other) noexcept = default;
  79. // force move semantics
  80. environment(const environment &other) = delete;
  81. environment &operator=(const environment &other) = delete;
  82. struct env_map {
  83. // *non-owning* list of environment variables, i.e. list as suitable for exec, including
  84. // null at end of list
  85. std::vector<const char *> env_list;
  86. // map of variable name (via string_view) to its index in env_list
  87. std::unordered_map<string_view, unsigned, hash_sv> var_map;
  88. const char *lookup(string_view sv) const {
  89. auto it = var_map.find(sv);
  90. if (it != var_map.end()) {
  91. return env_list[it->second] + sv.size() + 1;
  92. }
  93. return nullptr;
  94. }
  95. };
  96. // return environment variable in form NAME=VALUE. Assumes that the real environment is the parent.
  97. string_view get(const std::string &name) const
  98. {
  99. auto it = set_vars.find(string_view(name));
  100. if (it != set_vars.end()) {
  101. return *it;
  102. }
  103. if (!keep_parent_env && !import_from_parent.contains(name)) {
  104. return {};
  105. }
  106. const char *val = bp_sys::getenv(name.c_str());
  107. if (val == nullptr) {
  108. return {};
  109. }
  110. const char *name_and_val = val - (name.length() + 1);
  111. size_t val_len = strlen(val);
  112. return {name_and_val, name.length() + 1 + val_len};
  113. }
  114. // Build a mapping excluding named variables (only called if the parent is the real environment).
  115. // Note that the return is non-owning, i.e. the variable values are backed by the environment object
  116. // and their lifetime is bounded to it.
  117. env_map build(const env_names &exclude) const
  118. {
  119. env_map mapping;
  120. if (keep_parent_env) {
  121. // import all from parent, excluding our own undefines + the exclude set
  122. if (bp_sys::environ != nullptr) {
  123. unsigned pos = 0;
  124. for (char **env = bp_sys::environ; *env != nullptr; ++env) {
  125. // find '='
  126. char *var_ch;
  127. for (var_ch = *env; *var_ch != '='; ++var_ch) {
  128. if (*var_ch == '\0') break;
  129. }
  130. // if this variable doesn't contain '=', ignore it
  131. if (*var_ch == '\0') continue;
  132. string_view name_view {*env, (size_t)(var_ch - *env)};
  133. if (undefine.contains(name_view) || exclude.contains(name_view)) {
  134. goto next_env_var;
  135. }
  136. mapping.env_list.push_back(*env);
  137. mapping.var_map.insert({name_view, pos});
  138. ++pos;
  139. next_env_var: ;
  140. }
  141. }
  142. }
  143. else {
  144. // import specific items from parent
  145. for (const std::string &import_name : import_from_parent) {
  146. // POSIX allows that getenv return its result in a static, per-thread buffer. Since this is
  147. // ridiculous, we'll assume that all implementations do the sane thing of simply returning
  148. // a pointer to the value part of the NAME=VALUE string in the actual environment array:
  149. const char *value = getenv(import_name.c_str());
  150. if (value == nullptr) continue;
  151. // go back through the name and the '=' to the beginning of NAME=VALUE:
  152. const char *name_and_val = value - (import_name.length() + 1);
  153. mapping.var_map.insert({import_name, mapping.env_list.size()});
  154. mapping.env_list.push_back(name_and_val);
  155. }
  156. }
  157. // add our own (excluding exclude set)
  158. for (const std::string &set_var : set_vars) {
  159. size_t eq_pos = set_var.find('=');
  160. string_view set_var_name = string_view(set_var.data(), eq_pos);
  161. if (!exclude.contains(set_var_name)) {
  162. auto iter = mapping.var_map.find(set_var_name);
  163. if (iter != mapping.var_map.end()) {
  164. unsigned pos = iter->second;
  165. mapping.env_list[pos] = set_var.c_str();
  166. }
  167. else {
  168. mapping.var_map[set_var_name] = mapping.env_list.size();
  169. mapping.env_list.push_back(set_var.c_str());
  170. }
  171. }
  172. }
  173. mapping.env_list.push_back(nullptr);
  174. return mapping;
  175. }
  176. env_map build(const environment &parent_env) const
  177. {
  178. env_map mapping;
  179. // first build base: variables from the parent(s) excluding those specifically excluded
  180. if (keep_parent_env) {
  181. mapping = parent_env.build(undefine);
  182. // remove final null entry:
  183. mapping.env_list.resize(mapping.env_list.size() - 1);
  184. }
  185. else {
  186. // import only those specifically chosen
  187. for (const std::string &import_name : import_from_parent) {
  188. string_view name_and_val = parent_env.get(import_name);
  189. if (name_and_val.empty()) continue;
  190. mapping.var_map.insert({import_name, mapping.env_list.size()});
  191. mapping.env_list.push_back(name_and_val.data());
  192. }
  193. }
  194. // add our own (excluding exclude set)
  195. for (const std::string &set_var : set_vars) {
  196. size_t eq_pos = set_var.find('=');
  197. string_view set_var_name = string_view(set_var.data(), eq_pos);
  198. auto iter = mapping.var_map.find(set_var_name);
  199. if (iter != mapping.var_map.end()) {
  200. unsigned pos = iter->second;
  201. mapping.env_list[pos] = set_var.c_str();
  202. }
  203. else {
  204. mapping.var_map[set_var_name] = mapping.env_list.size();
  205. mapping.env_list.push_back(set_var.c_str());
  206. }
  207. }
  208. mapping.env_list.push_back(nullptr);
  209. return mapping;
  210. }
  211. // build a mapping, where parent is the real environment
  212. env_map build() const
  213. {
  214. return build(env_names());
  215. }
  216. void set_var(std::string &&var_and_val)
  217. {
  218. string_view var_name = find_var_name(var_and_val);
  219. import_from_parent.erase(var_name);
  220. undefine.erase(var_name);
  221. auto insert_result = set_vars.insert(std::move(var_and_val));
  222. if (!insert_result.second) {
  223. *insert_result.first = var_and_val;
  224. }
  225. }
  226. void import_parent_var(std::string &&var_name)
  227. {
  228. undefine.erase(var_name);
  229. set_vars.erase(string_view(var_name));
  230. if (!keep_parent_env) {
  231. import_from_parent.insert(std::move(var_name));
  232. }
  233. }
  234. void undefine_var(std::string &&var_name)
  235. {
  236. import_from_parent.erase(var_name);
  237. set_vars.erase(string_view(var_name));
  238. if (keep_parent_env) {
  239. undefine.insert(std::move(var_name));
  240. }
  241. }
  242. void clear_no_inherit()
  243. {
  244. keep_parent_env = false;
  245. import_from_parent.clear();
  246. undefine.clear();
  247. set_vars.clear();
  248. }
  249. };
  250. // Read and set environment variables from a file. May throw std::bad_alloc, std::system_error.
  251. template <typename LOG_INV_SETTING, typename LOG_BAD_COMMAND>
  252. inline void read_env_file_inline(const char *env_file_path, bool log_warnings, environment &env,
  253. bool throw_on_open_failure, LOG_INV_SETTING &log_inv_setting, LOG_BAD_COMMAND &log_bad_cmd)
  254. {
  255. std::ifstream env_file(env_file_path);
  256. if (!env_file) {
  257. if (throw_on_open_failure) {
  258. throw std::system_error(errno, std::generic_category());
  259. }
  260. return;
  261. }
  262. env_file.exceptions(std::ios::badbit);
  263. auto &clocale = std::locale::classic();
  264. std::string line;
  265. int linenum = 0;
  266. while (std::getline(env_file, line)) {
  267. linenum++;
  268. auto lpos = line.begin();
  269. auto lend = line.end();
  270. while (lpos != lend && std::isspace(*lpos, clocale)) {
  271. ++lpos;
  272. }
  273. if (lpos == lend) continue; // empty line
  274. if (*lpos == '#') {
  275. continue;
  276. }
  277. if (*lpos == '=') {
  278. if (log_warnings) {
  279. log_inv_setting(linenum);
  280. }
  281. continue;
  282. }
  283. // "!COMMAND" form.
  284. if (*lpos == '!') {
  285. ++lpos; // lpos = first char of command
  286. auto epos = lpos;
  287. do {
  288. ++epos;
  289. } while(epos != lend && !std::isspace(*epos, clocale));
  290. const char *lpos_p = line.data() + (lpos - line.begin());
  291. string_view cmd {lpos_p, (size_t)(epos - lpos)};
  292. std::vector<string_view> cmd_args;
  293. while (epos != lend) {
  294. // skip whitespace
  295. while (std::isspace(*epos, clocale)) {
  296. ++epos;
  297. if (epos == lend) goto process_cmd; // no more args
  298. }
  299. // all non-ws is argument until next ws
  300. const char *arg_begin = line.c_str() + (epos - line.begin());
  301. auto arg_begin_i = epos;
  302. while (epos != lend && !std::isspace(*epos)) {
  303. ++epos;
  304. }
  305. cmd_args.push_back(string_view {arg_begin, (size_t)(epos - arg_begin_i)});
  306. }
  307. process_cmd:
  308. if (cmd == "clear") {
  309. env.clear_no_inherit();
  310. }
  311. else if (cmd == "unset") {
  312. for (string_view arg : cmd_args) {
  313. env.undefine_var(std::string(arg.data(), arg.length()));
  314. }
  315. }
  316. else if (cmd == "import") {
  317. for (string_view arg : cmd_args) {
  318. env.import_parent_var(std::string(arg.data(), arg.length()));
  319. }
  320. }
  321. else if (log_warnings) {
  322. log_bad_cmd(linenum);
  323. }
  324. continue;
  325. }
  326. // ENV=VALUE form.
  327. auto name_begin = lpos++;
  328. // skip until '=' or whitespace:
  329. while (lpos != lend && *lpos != '=' && !std::isspace(*lpos, clocale)) ++lpos;
  330. auto name_end = lpos;
  331. // skip whitespace:
  332. while (lpos != lend && std::isspace(*lpos, clocale)) ++lpos;
  333. if (lpos == lend || *lpos != '=') {
  334. if (log_warnings) {
  335. log_inv_setting(linenum);
  336. }
  337. continue;
  338. }
  339. ++lpos;
  340. auto val_begin = lpos;
  341. auto val_end = lend;
  342. if (val_begin != (name_end + 1) || name_begin != line.begin()) {
  343. // there are spaces that we need to eliminate
  344. std::string name_and_val;
  345. name_and_val.reserve((name_end - name_begin) + 1 + (val_end - val_begin));
  346. name_and_val = line.substr(name_begin - line.begin(), name_end - name_begin);
  347. name_and_val.append(1, '=');
  348. name_and_val.append(val_begin, val_end);
  349. env.set_var(std::move(name_and_val));
  350. }
  351. else {
  352. line.shrink_to_fit();
  353. env.set_var(std::move(line));
  354. }
  355. }
  356. }
  357. #endif /* DINIT_ENV_H_INCLUDED */