Coverage Report

Created: 2024-09-19 09:45

/proc/self/cwd/source/common/config/datasource.cc
Line
Count
Source (jump to first uncovered line)
1
#include "source/common/config/datasource.h"
2
3
#include "envoy/config/core/v3/base.pb.h"
4
5
#include "source/common/config/utility.h"
6
7
#include "fmt/format.h"
8
9
namespace Envoy {
10
namespace Config {
11
namespace DataSource {
12
13
namespace {
14
/**
15
 * Read contents of the file.
16
 * @param path file path.
17
 * @param api reference to the Api.
18
 * @param allow_empty return an empty string if the file is empty.
19
 * @param max_size max size limit of file to read, default 0 means no limit, and if the file data
20
 * would exceed the limit, it will return an error status.
21
 * @return std::string with file contents. or an error status if the file does not exist or
22
 * cannot be read.
23
 */
24
absl::StatusOr<std::string> readFile(const std::string& path, Api::Api& api, bool allow_empty,
25
207
                                     uint64_t max_size) {
26
207
  auto& file_system = api.fileSystem();
27
28
207
  if (max_size > 0) {
29
38
    if (!file_system.fileExists(path)) {
30
37
      return absl::InvalidArgumentError(fmt::format("file {} does not exist", path));
31
37
    }
32
1
    const ssize_t size = file_system.fileSize(path);
33
1
    if (size < 0) {
34
0
      return absl::InvalidArgumentError(absl::StrCat("cannot determine size of file ", path));
35
0
    }
36
1
    if (static_cast<uint64_t>(size) > max_size) {
37
0
      return absl::InvalidArgumentError(
38
0
          fmt::format("file {} size is {} bytes; maximum is {}", path, size, max_size));
39
0
    }
40
1
  }
41
42
170
  auto file_content_or_error = file_system.fileReadToEnd(path);
43
170
  RETURN_IF_NOT_OK_REF(file_content_or_error.status());
44
45
44
  if (!allow_empty && file_content_or_error.value().empty()) {
46
0
    return absl::InvalidArgumentError(fmt::format("file {} is empty", path));
47
0
  }
48
49
44
  return file_content_or_error.value();
50
44
}
51
} // namespace
52
53
absl::StatusOr<std::string> read(const envoy::config::core::v3::DataSource& source,
54
10.5k
                                 bool allow_empty, Api::Api& api, uint64_t max_size) {
55
10.5k
  std::string data;
56
10.5k
  switch (source.specifier_case()) {
57
207
  case envoy::config::core::v3::DataSource::SpecifierCase::kFilename:
58
207
    return readFile(source.filename(), api, allow_empty, max_size);
59
3.96k
  case envoy::config::core::v3::DataSource::SpecifierCase::kInlineBytes:
60
3.96k
    data = source.inline_bytes();
61
3.96k
    break;
62
4.81k
  case envoy::config::core::v3::DataSource::SpecifierCase::kInlineString:
63
4.81k
    data = source.inline_string();
64
4.81k
    break;
65
185
  case envoy::config::core::v3::DataSource::SpecifierCase::kEnvironmentVariable: {
66
185
    const char* environment_variable = std::getenv(source.environment_variable().c_str());
67
185
    if (environment_variable == nullptr) {
68
71
      return absl::InvalidArgumentError(
69
71
          fmt::format("Environment variable doesn't exist: {}", source.environment_variable()));
70
71
    }
71
114
    data = environment_variable;
72
114
    break;
73
185
  }
74
1.33k
  default:
75
1.33k
    if (!allow_empty) {
76
0
      return absl::InvalidArgumentError(fmt::format("Unexpected DataSource::specifier_case(): {}",
77
0
                                                    static_cast<int>(source.specifier_case())));
78
0
    }
79
10.5k
  }
80
10.2k
  if (!allow_empty && data.empty()) {
81
0
    return absl::InvalidArgumentError("DataSource cannot be empty");
82
0
  }
83
10.2k
  return data;
84
10.2k
}
85
86
1
absl::optional<std::string> getPath(const envoy::config::core::v3::DataSource& source) {
87
1
  return source.specifier_case() == envoy::config::core::v3::DataSource::SpecifierCase::kFilename
88
1
             ? absl::make_optional(source.filename())
89
1
             : absl::nullopt;
90
1
}
91
92
DynamicData::DynamicData(Event::Dispatcher& main_dispatcher,
93
                         ThreadLocal::TypedSlotPtr<ThreadLocalData> slot,
94
                         Filesystem::WatcherPtr watcher)
95
1
    : dispatcher_(main_dispatcher), slot_(std::move(slot)), watcher_(std::move(watcher)) {}
96
97
2
DynamicData::~DynamicData() {
98
2
  if (!dispatcher_.isThreadSafe()) {
99
0
    dispatcher_.post([to_delete = std::move(slot_)] {});
100
0
  }
101
2
}
102
103
0
const std::string& DynamicData::data() const {
104
0
  const auto thread_local_data = slot_->get();
105
0
  return thread_local_data.has_value() ? *thread_local_data->data_ : EMPTY_STRING;
106
0
}
107
108
88
const std::string& DataSourceProvider::data() const {
109
88
  if (absl::holds_alternative<std::string>(data_)) {
110
88
    return absl::get<std::string>(data_);
111
88
  }
112
0
  return absl::get<DynamicData>(data_).data();
113
88
}
114
115
absl::StatusOr<DataSourceProviderPtr> DataSourceProvider::create(const ProtoDataSource& source,
116
                                                                 Event::Dispatcher& main_dispatcher,
117
                                                                 ThreadLocal::SlotAllocator& tls,
118
                                                                 Api::Api& api, bool allow_empty,
119
2.95k
                                                                 uint64_t max_size) {
120
2.95k
  auto initial_data_or_error = read(source, allow_empty, api, max_size);
121
2.95k
  RETURN_IF_NOT_OK_REF(initial_data_or_error.status());
122
123
  // read() only validates the size of the file and does not check the size of inline data.
124
  // We check the size of inline data here.
125
  // TODO(wbpcode): consider moving this check to read() to avoid duplicate checks.
126
2.89k
  if (max_size > 0 && initial_data_or_error.value().length() > max_size) {
127
8
    return absl::InvalidArgumentError(fmt::format("response body size is {} bytes; maximum is {}",
128
8
                                                  initial_data_or_error.value().length(),
129
8
                                                  max_size));
130
8
  }
131
132
2.89k
  if (!source.has_watched_directory() ||
133
2.89k
      source.specifier_case() != envoy::config::core::v3::DataSource::kFilename) {
134
2.89k
    return std::unique_ptr<DataSourceProvider>(
135
2.89k
        new DataSourceProvider(std::move(initial_data_or_error).value()));
136
2.89k
  }
137
138
1
  auto slot = ThreadLocal::TypedSlot<DynamicData::ThreadLocalData>::makeUnique(tls);
139
1
  slot->set([initial_data = std::make_shared<std::string>(
140
1
                 std::move(initial_data_or_error.value()))](Event::Dispatcher&) {
141
1
    return std::make_shared<DynamicData::ThreadLocalData>(initial_data);
142
1
  });
143
144
1
  const auto& filename = source.filename();
145
1
  auto watcher = main_dispatcher.createFilesystemWatcher();
146
  // DynamicData will ensure that the watcher is destroyed before the slot is destroyed.
147
  // TODO(wbpcode): use Config::WatchedDirectory instead of directly creating a watcher
148
  // if the Config::WatchedDirectory is exception-free in the future.
149
1
  auto watcher_status = watcher->addWatch(
150
1
      absl::StrCat(source.watched_directory().path(), "/"), Filesystem::Watcher::Events::MovedTo,
151
1
      [slot_ptr = slot.get(), &api, filename, allow_empty, max_size](uint32_t) -> absl::Status {
152
0
        auto new_data_or_error = readFile(filename, api, allow_empty, max_size);
153
0
        if (!new_data_or_error.ok()) {
154
          // Log an error but don't fail the watch to avoid throwing EnvoyException at runtime.
155
0
          ENVOY_LOG_TO_LOGGER(Logger::Registry::getLog(Logger::Id::config), error,
156
0
                              "Failed to read file: {}", new_data_or_error.status().message());
157
0
          return absl::OkStatus();
158
0
        }
159
0
        slot_ptr->runOnAllThreads(
160
0
            [new_data = std::make_shared<std::string>(std::move(new_data_or_error.value()))](
161
0
                OptRef<DynamicData::ThreadLocalData> obj) {
162
0
              if (obj.has_value()) {
163
0
                obj->data_ = new_data;
164
0
              }
165
0
            });
166
0
        return absl::OkStatus();
167
0
      });
168
1
  RETURN_IF_NOT_OK(watcher_status);
169
170
1
  return std::unique_ptr<DataSourceProvider>(
171
1
      new DataSourceProvider(DynamicData(main_dispatcher, std::move(slot), std::move(watcher))));
172
1
}
173
174
} // namespace DataSource
175
} // namespace Config
176
} // namespace Envoy