GCC Code Coverage Report
Directory: cvmfs/ Exec Total Coverage
File: cvmfs/options.cc Lines: 232 304 76.3 %
Date: 2019-02-03 02:48:13 Branches: 141 237 59.5 %

Line Branch Exec Source
1
/**
2
 * This file is part of the CernVM File System.
3
 *
4
 * Fills an internal map of key-value pairs from ASCII files in key=value
5
 * style.  Parameters can be overwritten.  Used to read configuration from
6
 * /etc/cvmfs/...
7
 */
8
9
#include "cvmfs_config.h"
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 "logging.h"
22
#include "sanitizer.h"
23
#include "util/posix.h"
24
#include "util/string.h"
25
26
using namespace std;  // NOLINT
27
28
#ifdef CVMFS_NAMESPACE_GUARD
29
namespace CVMFS_NAMESPACE_GUARD {
30
#endif
31
32
33
934
static string EscapeShell(const std::string &raw) {
34
13145
  for (unsigned i = 0, l = raw.length(); i < l; ++i) {
35






12217
    if (!(((raw[i] >= '0') && (raw[i] <= '9')) ||
36
          ((raw[i] >= 'A') && (raw[i] <= 'Z')) ||
37
          ((raw[i] >= 'a') && (raw[i] <= 'z')) ||
38
          (raw[i] == '/') || (raw[i] == ':') || (raw[i] == '.') ||
39
          (raw[i] == '_') || (raw[i] == '-') || (raw[i] == ',')))
40
          {
41
6
      goto escape_shell_quote;
42
    }
43
  }
44
928
  return raw;
45
46
 escape_shell_quote:
47
6
  string result = "'";
48
296
  for (unsigned i = 0, l = raw.length(); i < l; ++i) {
49
290
    if (raw[i] == '\'')
50
      result += "\\";
51
290
    result += raw[i];
52
  }
53
6
  result += "'";
54
6
  return result;
55
}
56
57
58
1087
string OptionsManager::TrimParameter(const string &parameter) {
59
1087
  string result = Trim(parameter);
60
  // Strip "readonly"
61
1087
  if (result.find("readonly ") == 0) {
62
    result = result.substr(9);
63
    result = Trim(result);
64
1087
  } else if (result.find("export ") == 0) {
65
12
    result = result.substr(7);
66
12
    result = Trim(result);
67
1075
  } else if (result.find("eval ") == 0) {
68
    result = result.substr(5);
69
    result = Trim(result);
70
  }
71
1087
  return result;
72
}
73
74
10
void OptionsManager::SwitchTemplateManager(
75
  OptionsTemplateManager *opt_templ_mgr_param) {
76
10
  delete opt_templ_mgr_;
77
10
  if (opt_templ_mgr_param != NULL) {
78
10
    opt_templ_mgr_ = opt_templ_mgr_param;
79
  } else {
80
    opt_templ_mgr_ = new OptionsTemplateManager();
81
  }
82
14
  for (std::map<std::string, std::string>::iterator it
83
10
    = templatable_values_.begin();
84
    it != templatable_values_.end();
85
    it++) {
86
4
    config_[it->first].value = it->second;
87
4
    opt_templ_mgr_->ParseString(&(config_[it->first].value));
88
4
    UpdateEnvironment(it->first, config_[it->first]);
89
  }
90
10
}
91
92
93
9
bool SimpleOptionsParser::TryParsePath(const string &config_file) {
94
  LogCvmfs(kLogCvmfs, kLogDebug, "Fast-parsing config file %s",
95
9
      config_file.c_str());
96
9
  string line;
97
9
  FILE *fconfig = fopen(config_file.c_str(), "r");
98
9
  if (fconfig == NULL)
99
1
    return false;
100
101
  // Read line by line and extract parameters
102

8
  while (GetLineFile(fconfig, &line)) {
103
116
    size_t comment_idx = line.find("#");
104
116
    if (comment_idx != string::npos)
105
12
      line = line.substr(0, comment_idx);
106
116
    line = Trim(line);
107
116
    if (line.empty())
108
12
      continue;
109
104
    vector<string> tokens = SplitString(line, '=');
110
104
    if (tokens.size() < 2)
111
6
      continue;
112
98
    string parameter = TrimParameter(tokens[0]);
113
98
    if (parameter.find(" ") != string::npos)
114
      continue;
115
98
    if (parameter.empty())
116
12
      continue;
117
118
    // Strip quotes from value
119
86
    tokens.erase(tokens.begin());
120
86
    string value = Trim(JoinStrings(tokens, "="));
121
86
    unsigned value_length = value.length();
122
86
    if (value_length > 2) {
123


62
      if ( ((value[0] == '"') && ((value[value_length - 1] == '"'))) ||
124
           ((value[0] == '\'') && ((value[value_length - 1] == '\''))) )
125
      {
126
12
        value = value.substr(1, value_length - 2);
127
      }
128
    }
129
130
86
    ConfigValue config_value;
131
86
    config_value.source = config_file;
132
86
    config_value.value = value;
133
86
    PopulateParameter(parameter, config_value);
134
  }
135
8
  fclose(fconfig);
136
8
  return true;
137
}
138
139
108
void BashOptionsManager::ParsePath(const string &config_file,
140
                                   const bool external) {
141
108
  LogCvmfs(kLogCvmfs, kLogDebug, "Parsing config file %s", config_file.c_str());
142
  int retval;
143
  int pipe_open[2];
144
  int pipe_quit[2];
145
108
  pid_t pid_child = 0;
146
108
  if (external) {
147
    // cvmfs can run in the process group of automount in which case
148
    // autofs won't mount an additional config repository.  We create a
149
    // short-lived process that detaches from the process group and triggers
150
    // autofs to mount the config repository, if necessary.  It holds a file
151
    // handle to the config file until the main process opened the file, too.
152
    MakePipe(pipe_open);
153
    MakePipe(pipe_quit);
154
    switch (pid_child = fork()) {
155
      case -1:
156
        abort();
157
      case 0: {  // Child
158
        close(pipe_open[0]);
159
        close(pipe_quit[1]);
160
        // If this is not a process group leader, create a new session
161
        if (getpgrp() != getpid()) {
162
          pid_t new_session = setsid();
163
          assert(new_session != (pid_t)-1);
164
        }
165
        (void)open(config_file.c_str(), O_RDONLY);
166
        char ready = 'R';
167
        WritePipe(pipe_open[1], &ready, 1);
168
        retval = read(pipe_quit[0], &ready, 1);
169
        _exit(retval);  // Don't flush shared file descriptors
170
      }
171
    }
172
    // Parent
173
    close(pipe_open[1]);
174
    close(pipe_quit[0]);
175
    char ready = 0;
176
    ReadPipe(pipe_open[0], &ready, 1);
177
    assert(ready == 'R');
178
    close(pipe_open[0]);
179
  }
180
108
  const string config_path = GetParentPath(config_file);
181
108
  FILE *fconfig = fopen(config_file.c_str(), "r");
182
108
  if (pid_child > 0) {
183
    char c = 'C';
184
    WritePipe(pipe_quit[1], &c, 1);
185
    int statloc;
186
    waitpid(pid_child, &statloc, 0);
187
    close(pipe_quit[1]);
188
  }
189
108
  if (!fconfig) {
190

1
    if (external && !DirectoryExists(config_path)) {
191
      string repo_required;
192
      if (GetValue("CVMFS_CONFIG_REPO_REQUIRED", &repo_required) &&
193
        IsOn(repo_required)) {
194
          LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
195
               "required configuration repository directory does not exist: %s",
196
               config_path.c_str());
197
          // Do not crash as in abort(), which can trigger core file creation
198
          // from the mount helper
199
          exit(1);
200
      }
201
202
      LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn,
203
               "configuration repository directory does not exist: %s",
204
               config_path.c_str());
205
    }
206
    return;
207
  }
208
209
  int fd_stdin;
210
  int fd_stdout;
211
  int fd_stderr;
212
107
  retval = Shell(&fd_stdin, &fd_stdout, &fd_stderr);
213
107
  assert(retval);
214
215
  // Let the shell read the file
216
107
  string line;
217
107
  const string newline = "\n";
218
  const string cd = "cd \"" + ((config_path == "") ? "/" : config_path) + "\"" +
219


107
                    newline;
220
107
  WritePipe(fd_stdin, cd.data(), cd.length());
221
1221
  while (GetLineFile(fconfig, &line)) {
222
1007
    WritePipe(fd_stdin, line.data(), line.length());
223
1007
    WritePipe(fd_stdin, newline.data(), newline.length());
224
  }
225
107
  rewind(fconfig);
226
227
  // Read line by line and extract parameters
228


107
  while (GetLineFile(fconfig, &line)) {
229
1007
    line = Trim(line);
230


1007
    if (line.empty() || line[0] == '#' || line.find("if ") == 0)
231
12
      continue;
232
995
    vector<string> tokens = SplitString(line, '=');
233
995
    if (tokens.size() < 2)
234
6
      continue;
235
236
989
    ConfigValue value;
237
989
    value.source = config_file;
238
989
    string parameter = TrimParameter(tokens[0]);
239
989
    if (parameter.empty())
240
12
      continue;
241
242
977
    const string sh_echo = "echo $" + parameter + "\n";
243
977
    WritePipe(fd_stdin, sh_echo.data(), sh_echo.length());
244
977
    GetLineFd(fd_stdout, &value.value);
245
977
    PopulateParameter(parameter, value);
246
  }
247
248
107
  close(fd_stderr);
249
107
  close(fd_stdout);
250
107
  close(fd_stdin);
251
107
  fclose(fconfig);
252
}
253
254
255
59
bool OptionsManager::HasConfigRepository(const string &fqrn,
256
                                         string *config_path) {
257
59
  string cvmfs_mount_dir;
258

59
  if (!GetValue("CVMFS_MOUNT_DIR", &cvmfs_mount_dir)) {
259
    LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr, "CVMFS_MOUNT_DIR missing");
260
    return false;
261
  }
262
263
59
  string config_repository;
264

59
  if (GetValue("CVMFS_CONFIG_REPOSITORY", &config_repository)) {
265

9
    if (config_repository.empty() || (config_repository == fqrn))
266
      return false;
267
9
    sanitizer::RepositorySanitizer repository_sanitizer;
268
9
    if (!repository_sanitizer.IsValid(config_repository)) {
269
      LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
270
               "invalid CVMFS_CONFIG_REPOSITORY: %s",
271
               config_repository.c_str());
272
      return false;
273
    }
274
9
    *config_path = cvmfs_mount_dir + "/" + config_repository + "/etc/cvmfs/";
275
9
    return true;
276
  }
277
50
  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 =
290
    FindFilesBySuffix("/etc/cvmfs/default.d", ".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",
309
        true);
310
    ParsePath("/etc/cvmfs/domain.d/" + domain + ".conf", false);
311
    ParsePath("/etc/cvmfs/domain.d/" + domain + ".local", false);
312
313
    if (HasConfigRepository(fqrn, &external_config_path))
314
      ParsePath(external_config_path + "config.d/" + fqrn + ".conf", true);
315
    ParsePath("/etc/cvmfs/config.d/" + fqrn + ".conf", false);
316
    ParsePath("/etc/cvmfs/config.d/" + fqrn + ".local", false);
317
  }
318
}
319
320
321
1620
void OptionsManager::PopulateParameter(
322
  const string &param,
323
  ConfigValue val) {
324
1620
  map<string, string>::const_iterator iter = protected_parameters_.find(param);
325

1620
  if ((iter != protected_parameters_.end()) && (iter->second != val.value)) {
326
    LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
327
             "error in cvmfs configuration: attempt to change protected %s "
328
             "from %s to %s",
329
2
             param.c_str(), iter->second.c_str(), val.value.c_str());
330
2
    return;
331
  }
332
1618
  ParseValue(param, &val);
333
1618
  config_[param] = val;
334
1618
  UpdateEnvironment(param, val);
335
}
336
337
1622
void OptionsManager::UpdateEnvironment(
338
  const string &param,
339
  ConfigValue val) {
340
1622
  if (taint_environment_) {
341
1510
    int retval = setenv(param.c_str(), val.value.c_str(), 1);
342
1510
    assert(retval == 0);
343
  }
344
1622
}
345
346
1618
void OptionsManager::ParseValue(std::string param, ConfigValue *val) {
347
1618
  string orig = val->value;
348
1618
  bool has_templ = opt_templ_mgr_->ParseString(&(val->value));
349
1618
  if (has_templ) {
350
14
    templatable_values_[param] = orig;
351
  }
352
1618
}
353
354
355
2
void OptionsManager::ProtectParameter(const string &param) {
356
2
  string value;
357
  // We don't care about the result.  If param does not yet exists, we lock it
358
  // to the empty string.
359
2
  (void) GetValue(param, &value);
360
2
  protected_parameters_[param] = value;
361
2
}
362
363
364
2
void OptionsManager::ClearConfig() {
365
2
  config_.clear();
366
2
}
367
368
369
694
bool OptionsManager::IsDefined(const std::string &key) {
370
694
  map<string, ConfigValue>::const_iterator iter = config_.find(key);
371
694
  return iter != config_.end();
372
}
373
374
375
7656
bool OptionsManager::GetValue(const string &key, string *value) {
376
7656
  map<string, ConfigValue>::const_iterator iter = config_.find(key);
377
7656
  if (iter != config_.end()) {
378
2751
    *value = iter->second.value;
379
2751
    return true;
380
  }
381
4905
  *value = "";
382
4905
  return false;
383
}
384
385
386
946
bool OptionsManager::GetSource(const string &key, string *value) {
387
946
  map<string, ConfigValue>::const_iterator iter = config_.find(key);
388
946
  if (iter != config_.end()) {
389
946
    *value = iter->second.source;
390
946
    return true;
391
  }
392
  *value = "";
393
  return false;
394
}
395
396
397
183
bool OptionsManager::IsOn(const std::string &param_value) {
398
183
  const string uppercase = ToUpper(param_value);
399
  return ((uppercase == "YES") || (uppercase == "ON") || (uppercase == "1") ||
400


183
          (uppercase == "TRUE"));
401
}
402
403
404
141
vector<string> OptionsManager::GetAllKeys() {
405
141
  vector<string> result;
406
1244
  for (map<string, ConfigValue>::const_iterator i = config_.begin(),
407
141
       iEnd = config_.end(); i != iEnd; ++i)
408
  {
409
962
    result.push_back(i->first);
410
  }
411
141
  return result;
412
}
413
414
415
68
vector<string> OptionsManager::GetEnvironmentSubset(
416
  const string &key_prefix,
417
  bool strip_prefix)
418
{
419
68
  vector<string> result;
420
248
  for (map<string, ConfigValue>::const_iterator i = config_.begin(),
421
68
       iEnd = config_.end(); i != iEnd; ++i)
422
  {
423
112
    const bool ignore_prefix = false;
424
112
    if (HasPrefix(i->first, key_prefix, ignore_prefix)) {
425
      const string output_key = strip_prefix
426
        ? i->first.substr(key_prefix.length())
427
14
        : i->first;
428
14
      result.push_back(output_key + "=" + i->second.value);
429
    }
430
  }
431
68
  return result;
432
}
433
434
435
137
string OptionsManager::Dump() {
436
137
  string result;
437
137
  vector<string> keys = GetAllKeys();
438

137
  for (unsigned i = 0, l = keys.size(); i < l; ++i) {
439
    bool retval;
440
934
    string value;
441
934
    string source;
442
443
934
    retval = GetValue(keys[i], &value);
444
934
    assert(retval);
445
934
    retval = GetSource(keys[i], &source);
446
934
    assert(retval);
447
    result += keys[i] + "=" + EscapeShell(value) +
448
934
              "    # from " + source + "\n";
449
  }
450
  return result;
451
}
452
453
454
557
void OptionsManager::SetValue(const string &key, const string &value) {
455
557
  ConfigValue config_value;
456
557
  config_value.source = "@INTERNAL@";
457
557
  config_value.value = value;
458
557
  PopulateParameter(key, config_value);
459
557
}
460
461
462
35
void OptionsManager::UnsetValue(const string &key) {
463
35
  protected_parameters_.erase(key);
464
35
  config_.erase(key);
465
35
  if (taint_environment_)
466
33
    unsetenv(key.c_str());
467
35
}
468
469
const char *DefaultOptionsTemplateManager
470
  ::kTemplateIdentFqrn = "fqrn";
471
472
const char *DefaultOptionsTemplateManager
473
  ::kTemplateIdentOrg = "org";
474
475
113
DefaultOptionsTemplateManager::DefaultOptionsTemplateManager(
476
113
  std::string fqrn) {
477
113
  SetTemplate(kTemplateIdentFqrn, fqrn);
478
113
  vector<string> fqrn_parts = SplitString(fqrn, '.');
479
113
  SetTemplate(kTemplateIdentOrg, fqrn_parts[0]);
480
}
481
482
244
void OptionsTemplateManager::SetTemplate(std::string name, std::string val) {
483
244
  templates_[name] = val;
484
244
}
485
486
94
std::string OptionsTemplateManager::GetTemplate(std::string name) {
487
94
  if (templates_.count(name)) {
488
38
    return templates_[name];
489
  } else {
490
56
    std::string var_name = "@" + name + "@";
491
    LogCvmfs(kLogCvmfs, kLogDebug, "Undeclared variable: %s",
492
56
      var_name.c_str());
493
56
    return var_name;
494
  }
495
}
496
497
1638
bool OptionsTemplateManager::ParseString(std::string *input) {
498
1638
  std::string result;
499
1638
  std::string in = *input;
500
1638
  bool has_vars = false;
501
1638
  int mode = 0;
502
1638
  std::string stock;
503
77112
  for (std::string::size_type i = 0; i < in.size(); i++) {
504
75474
    switch (mode) {
505
      case 0:
506
75078
        if (in[i] == '@') {
507
86
          mode = 1;
508
        } else {
509
74992
          result += in[i];
510
        }
511
75078
      break;
512
      case 1:
513
396
        if (in[i] == '@') {
514
66
          mode = 0;
515
66
          result += GetTemplate(stock);
516
66
          stock = "";
517
66
          has_vars = true;
518
        } else {
519
330
          stock += in[i];
520
        }
521
      break;
522
    }
523
  }
524
1638
  if (mode == 1) {
525
20
    result += "@" + stock;
526
  }
527
1638
  *input = result;
528
1638
  return has_vars;
529
}
530
531
28
bool OptionsTemplateManager::HasTemplate(std::string name) {
532
28
  return templates_.count(name);
533
}
534
535
#ifdef CVMFS_NAMESPACE_GUARD
536
}  // namespace CVMFS_NAMESPACE_GUARD
537
#endif