/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 |