CernVM-FS  2.13.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
options.cc
Go to the documentation of this file.
1 
10 #include "options.h"
11 
12 #include <fcntl.h>
13 #include <sys/wait.h>
14 #include <unistd.h>
15 
16 #include <cassert>
17 #include <cstdio>
18 #include <cstdlib>
19 #include <utility>
20 
21 #include "sanitizer.h"
22 #include "util/exception.h"
23 #include "util/logging.h"
24 #include "util/posix.h"
25 #include "util/string.h"
26 
27 using namespace std; // NOLINT
28 
29 #ifdef CVMFS_NAMESPACE_GUARD
30 namespace CVMFS_NAMESPACE_GUARD {
31 #endif
32 
33 
34 static string EscapeShell(const std::string &raw) {
35  for (unsigned i = 0, l = raw.length(); i < l; ++i) {
36  if (!(((raw[i] >= '0') && (raw[i] <= '9'))
37  || ((raw[i] >= 'A') && (raw[i] <= 'Z'))
38  || ((raw[i] >= 'a') && (raw[i] <= 'z')) || (raw[i] == '/')
39  || (raw[i] == ':') || (raw[i] == '.') || (raw[i] == '_')
40  || (raw[i] == '-') || (raw[i] == ','))) {
41  goto escape_shell_quote;
42  }
43  }
44  return raw;
45 
46 escape_shell_quote:
47  string result = "'";
48  for (unsigned i = 0, l = raw.length(); i < l; ++i) {
49  if (raw[i] == '\'')
50  result += "\\";
51  result += raw[i];
52  }
53  result += "'";
54  return result;
55 }
56 
57 
58 string OptionsManager::TrimParameter(const string &parameter) {
59  string result = Trim(parameter);
60  // Strip "readonly"
61  if (result.find("readonly ") == 0) {
62  result = result.substr(9);
63  result = Trim(result);
64  } else if (result.find("export ") == 0) {
65  result = result.substr(7);
66  result = Trim(result);
67  } else if (result.find("eval ") == 0) {
68  result = result.substr(5);
69  result = Trim(result);
70  }
71  return result;
72 }
73 
75  vector<string> *tokens) {
76  size_t comment_idx = line->find("#");
77  if (comment_idx != string::npos)
78  *line = line->substr(0, comment_idx);
79  *line = Trim(*line);
80  if (line->empty())
81  return "";
82  *tokens = SplitString(*line, '=');
83  if (tokens->size() < 2)
84  return "";
85  string parameter = TrimParameter((*tokens)[0]);
86  if (parameter.find(" ") != string::npos)
87  return "";
88  return parameter;
89 }
90 
92  OptionsTemplateManager *opt_templ_mgr_param) {
93  delete opt_templ_mgr_;
94  if (opt_templ_mgr_param != NULL) {
95  opt_templ_mgr_ = opt_templ_mgr_param;
96  } else {
97  opt_templ_mgr_ = new OptionsTemplateManager();
98  }
99  for (std::map<std::string, std::string>::iterator it =
100  templatable_values_.begin();
101  it != templatable_values_.end();
102  it++) {
103  config_[it->first].value = it->second;
104  opt_templ_mgr_->ParseString(&(config_[it->first].value));
105  UpdateEnvironment(it->first, config_[it->first]);
106  }
107 }
108 
109 bool SimpleOptionsParser::TryParsePath(const string &config_file) {
110  LogCvmfs(kLogCvmfs, kLogDebug, "Fast-parsing config file %s",
111  config_file.c_str());
112  string line;
113  FILE *fconfig = fopen(config_file.c_str(), "r");
114  if (fconfig == NULL)
115  return false;
116 
117  // Read line by line and extract parameters
118  while (GetLineFile(fconfig, &line)) {
119  vector<string> tokens;
120  string parameter = SanitizeParameterAssignment(&line, &tokens);
121  if (parameter.empty())
122  continue;
123 
124  // Strip quotes from value
125  tokens.erase(tokens.begin());
126  string value = Trim(JoinStrings(tokens, "="));
127  unsigned value_length = value.length();
128  if (value_length > 2) {
129  if (((value[0] == '"') && ((value[value_length - 1] == '"')))
130  || ((value[0] == '\'') && ((value[value_length - 1] == '\'')))) {
131  value = value.substr(1, value_length - 2);
132  }
133  }
134 
135  ConfigValue config_value;
136  config_value.source = config_file;
137  config_value.value = value;
138  PopulateParameter(parameter, config_value);
139  }
140  fclose(fconfig);
141  return true;
142 }
143 
144 void BashOptionsManager::ParsePath(const string &config_file,
145  const bool external) {
146  LogCvmfs(kLogCvmfs, kLogDebug, "Parsing config file %s", config_file.c_str());
147  int retval;
148  int pipe_open[2];
149  int pipe_quit[2];
150  pid_t pid_child = 0;
151  if (external) {
152  // cvmfs can run in the process group of automount in which case
153  // autofs won't mount an additional config repository. We create a
154  // short-lived process that detaches from the process group and triggers
155  // autofs to mount the config repository, if necessary. It holds a file
156  // handle to the config file until the main process opened the file, too.
157  MakePipe(pipe_open);
158  MakePipe(pipe_quit);
159  switch (pid_child = fork()) {
160  case -1:
161  PANIC(NULL);
162  case 0: { // Child
163  close(pipe_open[0]);
164  close(pipe_quit[1]);
165  // If this is not a process group leader, create a new session
166  if (getpgrp() != getpid()) {
167  pid_t new_session = setsid();
168  assert(new_session != (pid_t)-1);
169  }
170  (void)open(config_file.c_str(), O_RDONLY);
171  char ready = 'R';
172  WritePipe(pipe_open[1], &ready, 1);
173  retval = read(pipe_quit[0], &ready, 1);
174  _exit(retval); // Don't flush shared file descriptors
175  }
176  }
177  // Parent
178  close(pipe_open[1]);
179  close(pipe_quit[0]);
180  char ready = 0;
181  ReadPipe(pipe_open[0], &ready, 1);
182  assert(ready == 'R');
183  close(pipe_open[0]);
184  }
185  const string config_path = GetParentPath(config_file);
186  FILE *fconfig = fopen(config_file.c_str(), "r");
187  if (pid_child > 0) {
188  char c = 'C';
189  WritePipe(pipe_quit[1], &c, 1);
190  int statloc;
191  waitpid(pid_child, &statloc, 0);
192  close(pipe_quit[1]);
193  }
194  if (!fconfig) {
195  if (external && !DirectoryExists(config_path)) {
196  string repo_required;
197  if (GetValue("CVMFS_CONFIG_REPO_REQUIRED", &repo_required)
198  && IsOn(repo_required)) {
199  LogCvmfs(
201  "required configuration repository directory does not exist: %s",
202  config_path.c_str());
203  // Do not crash as in abort(), which can trigger core file creation
204  // from the mount helper
205  exit(1);
206  }
207 
209  "configuration repository directory does not exist: %s",
210  config_path.c_str());
211  }
212  return;
213  }
214 
215  int fd_stdin;
216  int fd_stdout;
217  int fd_stderr;
218  retval = Shell(&fd_stdin, &fd_stdout, &fd_stderr);
219  assert(retval);
220 
221  // Let the shell read the file
222  string line;
223  const string newline = "\n";
224  const string cd = "cd \"" + ((config_path == "") ? "/" : config_path) + "\""
225  + newline;
226  WritePipe(fd_stdin, cd.data(), cd.length());
227  while (GetLineFile(fconfig, &line)) {
228  WritePipe(fd_stdin, line.data(), line.length());
229  WritePipe(fd_stdin, newline.data(), newline.length());
230  }
231  rewind(fconfig);
232 
233  // Read line by line and extract parameters
234  while (GetLineFile(fconfig, &line)) {
235  vector<string> tokens;
236  string parameter = SanitizeParameterAssignment(&line, &tokens);
237  if (parameter.empty())
238  continue;
239 
240  ConfigValue value;
241  value.source = config_file;
242  const string sh_echo = "echo $" + parameter + "\n";
243  WritePipe(fd_stdin, sh_echo.data(), sh_echo.length());
244  GetLineFd(fd_stdout, &value.value);
245  PopulateParameter(parameter, value);
246  }
247 
248  close(fd_stderr);
249  close(fd_stdout);
250  close(fd_stdin);
251  fclose(fconfig);
252 }
253 
254 
255 bool OptionsManager::HasConfigRepository(const string &fqrn,
256  string *config_path) {
257  string cvmfs_mount_dir;
258  if (!GetValue("CVMFS_MOUNT_DIR", &cvmfs_mount_dir)) {
259  LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr, "CVMFS_MOUNT_DIR missing");
260  return false;
261  }
262 
263  string config_repository;
264  if (GetValue("CVMFS_CONFIG_REPOSITORY", &config_repository)) {
265  if (config_repository.empty() || (config_repository == fqrn))
266  return false;
267  sanitizer::RepositorySanitizer repository_sanitizer;
268  if (!repository_sanitizer.IsValid(config_repository)) {
270  "invalid CVMFS_CONFIG_REPOSITORY: %s",
271  config_repository.c_str());
272  return false;
273  }
274  *config_path = cvmfs_mount_dir + "/" + config_repository + "/etc/cvmfs/";
275  return true;
276  }
277  return false;
278 }
279 
280 
281 void OptionsManager::ParseDefault(const string &fqrn) {
282  if (taint_environment_) {
283  int retval = setenv("CVMFS_FQRN", fqrn.c_str(), 1);
284  assert(retval == 0);
285  }
286 
287  protected_parameters_.clear();
288  ParsePath("/etc/cvmfs/default.conf", false);
289  vector<string> dist_defaults = FindFilesBySuffix("/etc/cvmfs/default.d",
290  ".conf");
291  for (unsigned i = 0; i < dist_defaults.size(); ++i) {
292  ParsePath(dist_defaults[i], false);
293  }
294  ProtectParameter("CVMFS_CONFIG_REPOSITORY");
295  string external_config_path;
296  if ((fqrn != "") && HasConfigRepository(fqrn, &external_config_path))
297  ParsePath(external_config_path + "default.conf", true);
298  ParsePath("/etc/cvmfs/default.local", false);
299 
300  if (fqrn != "") {
301  string domain;
302  vector<string> tokens = SplitString(fqrn, '.');
303  assert(tokens.size() > 1);
304  tokens.erase(tokens.begin());
305  domain = JoinStrings(tokens, ".");
306 
307  if (HasConfigRepository(fqrn, &external_config_path))
308  ParsePath(external_config_path + "domain.d/" + domain + ".conf", true);
309  ParsePath("/etc/cvmfs/domain.d/" + domain + ".conf", false);
310  ParsePath("/etc/cvmfs/domain.d/" + domain + ".local", false);
311 
312  if (HasConfigRepository(fqrn, &external_config_path))
313  ParsePath(external_config_path + "config.d/" + fqrn + ".conf", true);
314  ParsePath("/etc/cvmfs/config.d/" + fqrn + ".conf", false);
315  ParsePath("/etc/cvmfs/config.d/" + fqrn + ".local", false);
316  }
317 }
318 
319 
320 void OptionsManager::PopulateParameter(const string &param, ConfigValue val) {
321  map<string, string>::const_iterator iter = protected_parameters_.find(param);
322  if ((iter != protected_parameters_.end()) && (iter->second != val.value)) {
324  "error in cvmfs configuration: attempt to change protected %s "
325  "from %s to %s",
326  param.c_str(), iter->second.c_str(), val.value.c_str());
327  return;
328  }
329  ParseValue(param, &val);
330  config_[param] = val;
331  UpdateEnvironment(param, val);
332 }
333 
334 void OptionsManager::UpdateEnvironment(const string &param, ConfigValue val) {
335  if (taint_environment_) {
336  int retval = setenv(param.c_str(), val.value.c_str(), 1);
337  assert(retval == 0);
338  }
339 }
340 
341 void OptionsManager::ParseValue(std::string param, ConfigValue *val) {
342  string orig = val->value;
343  bool has_templ = opt_templ_mgr_->ParseString(&(val->value));
344  if (has_templ) {
345  templatable_values_[param] = orig;
346  }
347 }
348 
349 
350 void OptionsManager::ProtectParameter(const string &param) {
351  string value;
352  // We don't care about the result. If param does not yet exists, we lock it
353  // to the empty string.
354  (void)GetValue(param, &value);
355  protected_parameters_[param] = value;
356 }
357 
358 
359 void OptionsManager::ClearConfig() { config_.clear(); }
360 
361 
362 bool OptionsManager::IsDefined(const std::string &key) {
363  map<string, ConfigValue>::const_iterator iter = config_.find(key);
364  return iter != config_.end();
365 }
366 
367 
368 bool OptionsManager::GetValue(const string &key, string *value) const {
369  map<string, ConfigValue>::const_iterator iter = config_.find(key);
370  if (iter != config_.end()) {
371  *value = iter->second.value;
372  return true;
373  }
374  *value = "";
375  return false;
376 }
377 
378 
379 std::string OptionsManager::GetValueOrDie(const string &key) {
380  std::string value;
381  bool retval = GetValue(key, &value);
382  if (!retval) {
383  PANIC(kLogStderr | kLogDebug, "%s configuration parameter missing",
384  key.c_str());
385  }
386  return value;
387 }
388 
389 
390 bool OptionsManager::GetSource(const string &key, string *value) {
391  map<string, ConfigValue>::const_iterator iter = config_.find(key);
392  if (iter != config_.end()) {
393  *value = iter->second.source;
394  return true;
395  }
396  *value = "";
397  return false;
398 }
399 
400 
401 bool OptionsManager::IsOn(const std::string &param_value) const {
402  const string uppercase = ToUpper(param_value);
403  return ((uppercase == "YES") || (uppercase == "ON") || (uppercase == "1")
404  || (uppercase == "TRUE"));
405 }
406 
407 
408 bool OptionsManager::IsOff(const std::string &param_value) const {
409  const string uppercase = ToUpper(param_value);
410  return ((uppercase == "NO") || (uppercase == "OFF") || (uppercase == "0")
411  || (uppercase == "FALSE"));
412 }
413 
414 
415 vector<string> OptionsManager::GetAllKeys() {
416  vector<string> result;
417  for (map<string, ConfigValue>::const_iterator i = config_.begin(),
418  iEnd = config_.end();
419  i != iEnd;
420  ++i) {
421  result.push_back(i->first);
422  }
423  return result;
424 }
425 
426 
427 vector<string> OptionsManager::GetEnvironmentSubset(const string &key_prefix,
428  bool strip_prefix) {
429  vector<string> result;
430  for (map<string, ConfigValue>::const_iterator i = config_.begin(),
431  iEnd = config_.end();
432  i != iEnd;
433  ++i) {
434  const bool ignore_prefix = false;
435  if (HasPrefix(i->first, key_prefix, ignore_prefix)) {
436  const string output_key = strip_prefix
437  ? i->first.substr(key_prefix.length())
438  : i->first;
439  result.push_back(output_key + "=" + i->second.value);
440  }
441  }
442  return result;
443 }
444 
445 
447  string result;
448  vector<string> keys = GetAllKeys();
449  for (unsigned i = 0, l = keys.size(); i < l; ++i) {
450  bool retval;
451  string value;
452  string source;
453 
454  retval = GetValue(keys[i], &value);
455  assert(retval);
456  retval = GetSource(keys[i], &source);
457  assert(retval);
458  result += keys[i] + "=" + EscapeShell(value) + " # from " + source
459  + "\n";
460  }
461  return result;
462 }
463 
464 void OptionsManager::SetValueFromTalk(const string &key, const string &value) {
465  ConfigValue config_value;
466  config_value.source = "cvmfs_talk";
467  config_value.value = value;
468  PopulateParameter(key, config_value);
469 }
470 
471 void OptionsManager::SetValue(const string &key, const string &value) {
472  ConfigValue config_value;
473  config_value.source = "@INTERNAL@";
474  config_value.value = value;
475  PopulateParameter(key, config_value);
476 }
477 
478 
479 void OptionsManager::UnsetValue(const string &key) {
480  protected_parameters_.erase(key);
481  config_.erase(key);
482  if (taint_environment_)
483  unsetenv(key.c_str());
484 }
485 
487 
489 
491  SetTemplate(kTemplateIdentFqrn, fqrn);
492  vector<string> fqrn_parts = SplitString(fqrn, '.');
493  SetTemplate(kTemplateIdentOrg, fqrn_parts[0]);
494 }
495 
496 void OptionsTemplateManager::SetTemplate(std::string name, std::string val) {
497  templates_[name] = val;
498 }
499 
500 std::string OptionsTemplateManager::GetTemplate(std::string name) {
501  if (templates_.count(name)) {
502  return templates_[name];
503  } else {
504  std::string var_name = "@" + name + "@";
505  LogCvmfs(kLogCvmfs, kLogDebug, "Undeclared variable: %s", var_name.c_str());
506  return var_name;
507  }
508 }
509 
510 bool OptionsTemplateManager::ParseString(std::string *input) {
511  std::string result;
512  std::string in = *input;
513  bool has_vars = false;
514  int mode = 0;
515  std::string stock;
516  for (std::string::size_type i = 0; i < in.size(); i++) {
517  switch (mode) {
518  case 0:
519  if (in[i] == '@') {
520  mode = 1;
521  } else {
522  result += in[i];
523  }
524  break;
525  case 1:
526  if (in[i] == '@') {
527  mode = 0;
528  result += GetTemplate(stock);
529  stock = "";
530  has_vars = true;
531  } else {
532  stock += in[i];
533  }
534  break;
535  }
536  }
537  if (mode == 1) {
538  result += "@" + stock;
539  }
540  *input = result;
541  return has_vars;
542 }
543 
544 bool OptionsTemplateManager::HasTemplate(std::string name) {
545  return templates_.count(name);
546 }
547 
548 #ifdef CVMFS_NAMESPACE_GUARD
549 } // namespace CVMFS_NAMESPACE_GUARD
550 #endif
std::string GetValueOrDie(const std::string &key)
Definition: options.cc:379
std::string GetTemplate(std::string name)
Definition: options.cc:500
static const char * kTemplateIdentOrg
Definition: options.h:39
static const char * kTemplateIdentFqrn
Definition: options.h:38
#define PANIC(...)
Definition: exception.h:29
string Trim(const string &raw, bool trim_newline)
Definition: string.cc:466
CVMFS_EXPORT const LogSource source
Definition: exception.h:33
bool GetSource(const std::string &key, std::string *value)
Definition: options.cc:390
string JoinStrings(const vector< string > &strings, const string &joint)
Definition: string.cc:356
static string EscapeShell(const std::string &raw)
Definition: options.cc:34
DefaultOptionsTemplateManager(std::string fqrn)
Definition: options.cc:490
bool Shell(int *fd_stdin, int *fd_stdout, int *fd_stderr)
Definition: posix.cc:1768
bool IsOn(const std::string &param_value) const
Definition: options.cc:401
std::vector< std::string > GetEnvironmentSubset(const std::string &key_prefix, bool strip_prefix)
Definition: options.cc:427
void PopulateParameter(const std::string &param, const ConfigValue val)
Definition: options.cc:320
std::string SanitizeParameterAssignment(std::string *line, std::vector< std::string > *tokens)
Definition: options.cc:74
assert((mem||(size==0))&&"Out Of Memory")
bool IsOff(const std::string &param_value) const
Definition: options.cc:408
void ParseDefault(const std::string &fqrn)
Definition: options.cc:281
bool HasConfigRepository(const std::string &fqrn, std::string *config_path)
Definition: options.cc:255
void MakePipe(int pipe_fd[2])
Definition: posix.cc:487
void SwitchTemplateManager(OptionsTemplateManager *opt_templ_mgr_param)
Definition: options.cc:91
bool IsValid(const std::string &input) const
Definition: sanitizer.cc:112
void SetValue(const std::string &key, const std::string &value)
Definition: options.cc:471
bool GetLineFile(FILE *f, std::string *line)
Definition: string.cc:422
string ToUpper(const string &mixed_case)
Definition: string.cc:508
bool TryParsePath(const std::string &config_file)
Definition: options.cc:109
vector< string > SplitString(const string &str, char delim)
Definition: string.cc:306
void SetValueFromTalk(const std::string &key, const std::string &value)
Definition: options.cc:464
std::string Dump()
Definition: options.cc:446
std::vector< std::string > GetAllKeys()
Definition: options.cc:415
void ParseValue(const std::string param, ConfigValue *val)
Definition: options.cc:341
bool HasPrefix(const string &str, const string &prefix, const bool ignore_case)
Definition: string.cc:279
bool GetValue(const std::string &key, std::string *value) const
Definition: options.cc:368
bool DirectoryExists(const std::string &path)
Definition: posix.cc:824
std::string TrimParameter(const std::string &parameter)
Definition: options.cc:58
void ParsePath(const std::string &config_file, const bool external)
Definition: options.cc:144
bool GetLineFd(const int fd, std::string *line)
Definition: string.cc:441
void SetTemplate(std::string name, std::string val)
Definition: options.cc:496
void UpdateEnvironment(const std::string &param, ConfigValue val)
Definition: options.cc:334
bool ParseString(std::string *input)
Definition: options.cc:510
PathString GetParentPath(const PathString &path)
Definition: shortstring.cc:14
void ClearConfig()
Definition: options.cc:359
bool IsDefined(const std::string &key)
Definition: options.cc:362
void WritePipe(int fd, const void *buf, size_t nbyte)
Definition: posix.cc:496
void ReadPipe(int fd, void *buf, size_t nbyte)
Definition: posix.cc:508
std::vector< std::string > FindFilesBySuffix(const std::string &dir, const std::string &suffix)
Definition: posix.cc:1128
void ProtectParameter(const std::string &param)
Definition: options.cc:350
void UnsetValue(const std::string &key)
Definition: options.cc:479
bool HasTemplate(std::string name)
Definition: options.cc:544
CVMFS_EXPORT void LogCvmfs(const LogSource source, const int mask, const char *format,...)
Definition: logging.cc:545