Line data Source code
1 : #include <dirent.h>
2 : #include <fcntl.h>
3 : #include <sys/stat.h>
4 : #include <unistd.h>
5 :
6 : #include <cstdlib>
7 : #include <filesystem>
8 : #include <fstream>
9 : #include <iostream>
10 : #include <memory>
11 : #include <sstream>
12 :
13 : #include "envoy/common/exception.h"
14 :
15 : #include "source/common/common/assert.h"
16 : #include "source/common/common/fmt.h"
17 : #include "source/common/common/logger.h"
18 : #include "source/common/common/utility.h"
19 : #include "source/common/filesystem/filesystem_impl.h"
20 : #include "source/common/runtime/runtime_features.h"
21 :
22 : #include "absl/strings/match.h"
23 : #include "absl/strings/str_cat.h"
24 :
25 : namespace Envoy {
26 : namespace Filesystem {
27 :
28 206 : FileImplPosix::~FileImplPosix() {
29 206 : if (isOpen()) {
30 : // Explicit version of close because virtual calls in destructors are ambiguous.
31 38 : const Api::IoCallBoolResult result = FileImplPosix::close();
32 38 : ASSERT(result.return_value_);
33 38 : }
34 206 : }
35 :
36 0 : TmpFileImplPosix::~TmpFileImplPosix() {
37 0 : if (isOpen()) {
38 : // Explicit version of close because virtual calls in destructors are ambiguous.
39 : // (We have to duplicate the destructor to ensure the overridden close is called.)
40 0 : const Api::IoCallBoolResult result = TmpFileImplPosix::close();
41 0 : ASSERT(result.return_value_);
42 0 : }
43 0 : }
44 :
45 136 : Api::IoCallBoolResult FileImplPosix::open(FlagSet in) {
46 136 : if (isOpen()) {
47 0 : return resultSuccess(true);
48 0 : }
49 :
50 136 : const auto flags_and_mode = translateFlag(in);
51 136 : fd_ = ::open(path().c_str(), flags_and_mode.flags_, flags_and_mode.mode_);
52 136 : return fd_ != -1 ? resultSuccess(true) : resultFailure(false, errno);
53 136 : }
54 :
55 0 : Api::IoCallBoolResult TmpFileImplPosix::open(FlagSet in) {
56 0 : if (isOpen()) {
57 0 : return resultSuccess(true);
58 0 : }
59 :
60 0 : const auto flags_and_mode = translateFlag(in);
61 0 : #ifdef O_TMPFILE
62 : // Try to create a temp file with no name. Only some file systems support this.
63 0 : fd_ =
64 0 : ::open(path().c_str(), (flags_and_mode.flags_ & ~O_CREAT) | O_TMPFILE, flags_and_mode.mode_);
65 0 : if (fd_ != -1) {
66 0 : return resultSuccess(true);
67 0 : }
68 0 : #endif
69 : // If we couldn't do a nameless temp file, open a named temp file.
70 0 : return openNamedTmpFile(flags_and_mode);
71 0 : }
72 :
73 : Api::IoCallBoolResult TmpFileImplPosix::openNamedTmpFile(FlagsAndMode flags_and_mode,
74 0 : bool with_unlink) {
75 0 : for (int tries = 5; tries > 0; tries--) {
76 0 : std::string try_path = generateTmpFilePath(path());
77 0 : fd_ = ::open(try_path.c_str(), flags_and_mode.flags_, flags_and_mode.mode_);
78 0 : if (fd_ != -1) {
79 : // Try to unlink the temp file while it's still open. Again this only works on
80 : // a (different) subset of file systems.
81 0 : if (!with_unlink || ::unlink(try_path.c_str()) != 0) {
82 : // If we couldn't unlink it, set tmp_file_path_, to unlink after close.
83 0 : tmp_file_path_ = try_path;
84 0 : }
85 0 : return resultSuccess(true);
86 0 : }
87 0 : }
88 0 : return resultFailure(false, errno);
89 0 : }
90 :
91 289 : Api::IoCallSizeResult FileImplPosix::write(absl::string_view buffer) {
92 289 : const ssize_t rc = ::write(fd_, buffer.data(), buffer.size());
93 289 : return rc != -1 ? resultSuccess(rc) : resultFailure(rc, errno);
94 289 : };
95 :
96 136 : Api::IoCallBoolResult FileImplPosix::close() {
97 136 : ASSERT(isOpen());
98 136 : int rc = ::close(fd_);
99 136 : fd_ = -1;
100 136 : return (rc != -1) ? resultSuccess(true) : resultFailure(false, errno);
101 136 : }
102 :
103 0 : Api::IoCallBoolResult TmpFileImplPosix::close() {
104 0 : ASSERT(isOpen());
105 0 : int rc = ::close(fd_);
106 0 : fd_ = -1;
107 0 : int rc2 = tmp_file_path_.empty() ? 0 : ::unlink(tmp_file_path_.c_str());
108 0 : return (rc != -1 && rc2 != -1) ? resultSuccess(true) : resultFailure(false, errno);
109 0 : }
110 :
111 0 : Api::IoCallSizeResult FileImplPosix::pread(void* buf, uint64_t count, uint64_t offset) {
112 0 : ASSERT(isOpen());
113 0 : ssize_t rc = ::pread(fd_, buf, count, offset);
114 0 : return (rc == -1) ? resultFailure(rc, errno) : resultSuccess(rc);
115 0 : }
116 :
117 0 : Api::IoCallSizeResult FileImplPosix::pwrite(const void* buf, uint64_t count, uint64_t offset) {
118 0 : ASSERT(isOpen());
119 0 : ssize_t rc = ::pwrite(fd_, buf, count, offset);
120 0 : return (rc == -1) ? resultFailure(rc, errno) : resultSuccess(rc);
121 0 : }
122 :
123 0 : static FileType typeFromStat(const struct stat& s) {
124 0 : if (S_ISDIR(s.st_mode)) {
125 0 : return FileType::Directory;
126 0 : }
127 0 : if (S_ISREG(s.st_mode)) {
128 0 : return FileType::Regular;
129 0 : }
130 0 : return FileType::Other;
131 0 : }
132 :
133 0 : static constexpr absl::optional<SystemTime> systemTimeFromTimespec(const struct timespec& t) {
134 0 : if (t.tv_sec == 0) {
135 0 : return absl::nullopt;
136 0 : }
137 0 : return timespecToChrono(t);
138 0 : }
139 :
140 : static Api::IoCallResult<FileInfo> infoFromStat(absl::string_view path, const struct stat& s,
141 0 : FileType type) {
142 0 : auto result_or_error = InstanceImplPosix().splitPathFromFilename(path);
143 0 : FileInfo ret = {
144 0 : result_or_error.status().ok() ? std::string{result_or_error.value().file_} : "",
145 0 : s.st_size,
146 0 : type,
147 : #ifdef _DARWIN_FEATURE_64_BIT_INODE
148 : systemTimeFromTimespec(s.st_ctimespec),
149 : systemTimeFromTimespec(s.st_atimespec),
150 : systemTimeFromTimespec(s.st_mtimespec),
151 : #else
152 0 : systemTimeFromTimespec(s.st_ctim),
153 0 : systemTimeFromTimespec(s.st_atim),
154 0 : systemTimeFromTimespec(s.st_mtim),
155 0 : #endif
156 0 : };
157 0 : return result_or_error.status().ok() ? resultSuccess(ret) : resultFailure(ret, EINVAL);
158 0 : }
159 :
160 0 : static Api::IoCallResult<FileInfo> infoFromStat(absl::string_view path, const struct stat& s) {
161 0 : return infoFromStat(path, s, typeFromStat(s));
162 0 : }
163 :
164 0 : Api::IoCallResult<FileInfo> FileImplPosix::info() {
165 0 : ASSERT(isOpen());
166 0 : struct stat s;
167 0 : if (::fstat(fd_, &s) != 0) {
168 0 : return resultFailure<FileInfo>({}, errno);
169 0 : }
170 0 : return infoFromStat(path(), s);
171 0 : }
172 :
173 0 : Api::IoCallResult<FileInfo> InstanceImplPosix::stat(absl::string_view path) {
174 0 : struct stat s;
175 0 : std::string full_path{path};
176 0 : if (::stat(full_path.c_str(), &s) != 0) {
177 0 : if (errno == ENOENT) {
178 0 : if (::lstat(full_path.c_str(), &s) == 0 && S_ISLNK(s.st_mode)) {
179 : // Special case. This directory entity is a symlink,
180 : // but the reference is broken as the target could not be stat()'ed.
181 : // After confirming this with an lstat, treat this file entity as
182 : // a regular file, which may be unlink()'ed.
183 0 : return infoFromStat(path, s, FileType::Regular);
184 0 : }
185 0 : }
186 0 : return resultFailure<FileInfo>({}, errno);
187 0 : }
188 0 : return infoFromStat(path, s);
189 0 : }
190 :
191 0 : Api::IoCallBoolResult InstanceImplPosix::createPath(absl::string_view path) {
192 : // Ideally we could just use std::filesystem::create_directories for this,
193 : // identical to ../win32/filesystem_impl.cc, but the OS version used in mobile
194 : // CI doesn't support it, so we have to do recursive path creation manually.
195 0 : while (!path.empty() && path.back() == '/') {
196 0 : path.remove_suffix(1);
197 0 : }
198 0 : if (directoryExists(std::string{path})) {
199 0 : return resultSuccess(false);
200 0 : }
201 0 : absl::string_view subpath = path;
202 0 : do {
203 0 : size_t slashpos = subpath.find_last_of('/');
204 0 : if (slashpos == absl::string_view::npos) {
205 0 : return resultFailure(false, ENOENT);
206 0 : }
207 0 : subpath = subpath.substr(0, slashpos);
208 0 : } while (!directoryExists(std::string{subpath}));
209 : // We're now at the most-nested directory that already exists.
210 : // Time to create paths recursively.
211 0 : while (subpath != path) {
212 0 : size_t slashpos = path.find_first_of('/', subpath.size() + 2);
213 0 : subpath = (slashpos == absl::string_view::npos) ? path : path.substr(0, slashpos);
214 0 : std::string dir_to_create{subpath};
215 0 : if (mkdir(dir_to_create.c_str(), 0777)) {
216 0 : return resultFailure(false, errno);
217 0 : }
218 0 : }
219 0 : return resultSuccess(true);
220 0 : }
221 :
222 136 : FileImplPosix::FlagsAndMode FileImplPosix::translateFlag(FlagSet in) {
223 136 : int out = 0;
224 136 : mode_t mode = 0;
225 136 : if (in.test(File::Operation::Create)) {
226 136 : out |= O_CREAT;
227 136 : mode |= S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
228 136 : }
229 :
230 136 : if (in.test(File::Operation::Append)) {
231 98 : out |= O_APPEND;
232 103 : } else if (in.test(File::Operation::Write) && !in.test(File::Operation::KeepExistingData)) {
233 38 : out |= O_TRUNC;
234 38 : }
235 :
236 136 : if (in.test(File::Operation::Read) && in.test(File::Operation::Write)) {
237 0 : out |= O_RDWR;
238 136 : } else if (in.test(File::Operation::Read)) {
239 0 : out |= O_RDONLY;
240 136 : } else if (in.test(File::Operation::Write)) {
241 136 : out |= O_WRONLY;
242 136 : }
243 :
244 136 : return {out, mode};
245 136 : }
246 :
247 206 : FilePtr InstanceImplPosix::createFile(const FilePathAndType& file_info) {
248 206 : switch (file_info.file_type_) {
249 206 : case DestinationType::File:
250 206 : return std::make_unique<FileImplPosix>(file_info);
251 0 : case DestinationType::TmpFile:
252 0 : if (!file_info.path_.empty() && file_info.path_.back() == '/') {
253 0 : return std::make_unique<TmpFileImplPosix>(FilePathAndType{
254 0 : DestinationType::TmpFile, file_info.path_.substr(0, file_info.path_.size() - 1)});
255 0 : }
256 0 : return std::make_unique<TmpFileImplPosix>(file_info);
257 0 : case DestinationType::Stderr:
258 0 : return std::make_unique<FileImplPosix>(FilePathAndType{DestinationType::Stderr, "/dev/stderr"});
259 0 : case DestinationType::Stdout:
260 0 : return std::make_unique<FileImplPosix>(FilePathAndType{DestinationType::Stdout, "/dev/stdout"});
261 206 : }
262 0 : return nullptr; // for gcc
263 206 : }
264 :
265 591 : bool InstanceImplPosix::fileExists(const std::string& path) {
266 591 : std::ifstream input_file(path);
267 591 : return input_file.is_open();
268 591 : }
269 :
270 124 : bool InstanceImplPosix::directoryExists(const std::string& path) {
271 124 : DIR* const dir = ::opendir(path.c_str());
272 124 : const bool dir_exists = nullptr != dir;
273 124 : if (dir_exists) {
274 87 : ::closedir(dir);
275 87 : }
276 :
277 124 : return dir_exists;
278 124 : }
279 :
280 0 : ssize_t InstanceImplPosix::fileSize(const std::string& path) {
281 0 : struct stat info;
282 0 : if (::stat(path.c_str(), &info) != 0) {
283 0 : return -1;
284 0 : }
285 0 : return info.st_size;
286 0 : }
287 :
288 75 : absl::StatusOr<std::string> InstanceImplPosix::fileReadToEnd(const std::string& path) {
289 75 : if (illegalPath(path)) {
290 0 : return absl::InvalidArgumentError(absl::StrCat("Invalid path: ", path));
291 0 : }
292 :
293 75 : std::ifstream file(path);
294 75 : if (file.fail()) {
295 0 : return absl::InvalidArgumentError(absl::StrCat("unable to read file: ", path));
296 0 : }
297 :
298 75 : std::stringstream file_string;
299 75 : file_string << file.rdbuf();
300 :
301 75 : return file_string.str();
302 75 : }
303 :
304 107 : absl::StatusOr<PathSplitResult> InstanceImplPosix::splitPathFromFilename(absl::string_view path) {
305 107 : size_t last_slash = path.rfind('/');
306 107 : if (last_slash == std::string::npos) {
307 0 : return absl::InvalidArgumentError(fmt::format("invalid file path {}", path));
308 0 : }
309 107 : absl::string_view name = path.substr(last_slash + 1);
310 : // truncate all trailing slashes, except root slash
311 107 : if (last_slash == 0) {
312 0 : ++last_slash;
313 0 : }
314 107 : return PathSplitResult{path.substr(0, last_slash), name};
315 107 : }
316 :
317 75 : bool InstanceImplPosix::illegalPath(const std::string& path) {
318 : // Special case, allow /dev/fd/* access here so that config can be passed in a
319 : // file descriptor from a bootstrap script via exec. The reason we do this
320 : // _before_ canonicalizing the path is that different unix flavors implement
321 : // /dev/fd/* differently, for example on linux they are symlinks to /dev/pts/*
322 : // which are symlinks to /proc/self/fds/. On BSD (and darwin) they are not
323 : // symlinks at all. To avoid lots of platform, specifics, we allowlist
324 : // /dev/fd/* _before_ resolving the canonical path.
325 75 : if (absl::StartsWith(path, "/dev/fd/")) {
326 0 : return false;
327 0 : }
328 :
329 75 : const Api::SysCallStringResult canonical_path = canonicalPath(path);
330 75 : if (canonical_path.return_value_.empty()) {
331 0 : ENVOY_LOG_MISC(debug, "Unable to determine canonical path for {}: {}", path,
332 0 : errorDetails(canonical_path.errno_));
333 0 : return true;
334 0 : }
335 :
336 : // Platform specific path sanity; we provide a convenience to avoid Envoy
337 : // instances poking in bad places. We may have to consider conditioning on
338 : // platform in the future, growing these or relaxing some constraints (e.g.
339 : // there are valid reasons to go via /proc for file paths).
340 : // TODO(htuch): Optimize this as a hash lookup if we grow any further.
341 75 : if (absl::StartsWith(canonical_path.return_value_, "/dev") ||
342 75 : absl::StartsWith(canonical_path.return_value_, "/sys") ||
343 75 : absl::StartsWith(canonical_path.return_value_, "/proc")) {
344 0 : return true;
345 0 : }
346 75 : return false;
347 75 : }
348 :
349 75 : Api::SysCallStringResult InstanceImplPosix::canonicalPath(const std::string& path) {
350 75 : char* resolved_path = ::realpath(path.c_str(), nullptr);
351 75 : if (resolved_path == nullptr) {
352 0 : return {std::string(), errno};
353 0 : }
354 75 : std::string resolved_path_string{resolved_path};
355 75 : ::free(resolved_path);
356 75 : return {resolved_path_string, 0};
357 75 : }
358 :
359 : } // namespace Filesystem
360 : } // namespace Envoy
|