1
#include "source/common/filesystem/filesystem_impl.h"
2

            
3
#include <dirent.h>
4
#include <fcntl.h>
5
#include <sys/stat.h>
6
#include <unistd.h>
7

            
8
#include <cstdlib>
9
#include <filesystem>
10
#include <fstream>
11
#include <iostream>
12
#include <memory>
13
#include <sstream>
14

            
15
#include "envoy/common/exception.h"
16

            
17
#include "source/common/common/assert.h"
18
#include "source/common/common/fmt.h"
19
#include "source/common/common/logger.h"
20
#include "source/common/common/utility.h"
21
#include "source/common/runtime/runtime_features.h"
22

            
23
#include "absl/strings/match.h"
24
#include "absl/strings/str_cat.h"
25

            
26
namespace Envoy {
27
namespace Filesystem {
28

            
29
41660
FileImplPosix::~FileImplPosix() {
30
41660
  if (isOpen()) {
31
    // Explicit version of close because virtual calls in destructors are ambiguous.
32
21341
    const Api::IoCallBoolResult result = FileImplPosix::close();
33
21341
    ASSERT(result.return_value_);
34
21341
  }
35
41660
}
36

            
37
7
TmpFileImplPosix::~TmpFileImplPosix() {
38
7
  if (isOpen()) {
39
    // Explicit version of close because virtual calls in destructors are ambiguous.
40
    // (We have to duplicate the destructor to ensure the overridden close is called.)
41
4
    const Api::IoCallBoolResult result = TmpFileImplPosix::close();
42
4
    ASSERT(result.return_value_);
43
4
  }
44
7
}
45

            
46
33767
Api::IoCallBoolResult FileImplPosix::open(FlagSet in) {
47
33767
  if (isOpen()) {
48
1
    return resultSuccess(true);
49
1
  }
50
33766
  if (destinationType() == DestinationType::Stdout) {
51
8
    fd_ = ::dup(fileno(stdout));
52
8
    return fd_ != -1 ? resultSuccess(true) : resultFailure(false, errno);
53
8
  }
54
33758
  if (destinationType() == DestinationType::Stderr) {
55
1
    fd_ = ::dup(fileno(stderr));
56
1
    return fd_ != -1 ? resultSuccess(true) : resultFailure(false, errno);
57
1
  }
58
33757
  const auto flags_and_mode = translateFlag(in);
59
33757
  fd_ = ::open(filepath_and_type_.path_.c_str(), flags_and_mode.flags_, flags_and_mode.mode_);
60
33757
  return fd_ != -1 ? resultSuccess(true) : resultFailure(false, errno);
61
33758
}
62

            
63
3
Api::IoCallBoolResult TmpFileImplPosix::open(FlagSet in) {
64
3
  if (isOpen()) {
65
1
    return resultSuccess(true);
66
1
  }
67

            
68
2
  const auto flags_and_mode = translateFlag(in);
69
2
#ifdef O_TMPFILE
70
  // Try to create a temp file with no name. Only some file systems support this.
71
2
  fd_ = ::open(filepath_and_type_.path_.c_str(), (flags_and_mode.flags_ & ~O_CREAT) | O_TMPFILE,
72
2
               flags_and_mode.mode_);
73
2
  if (fd_ != -1) {
74
2
    return resultSuccess(true);
75
2
  }
76
#endif
77
  // If we couldn't do a nameless temp file, open a named temp file.
78
  return openNamedTmpFile(flags_and_mode);
79
2
}
80

            
81
Api::IoCallBoolResult TmpFileImplPosix::openNamedTmpFile(FlagsAndMode flags_and_mode,
82
5
                                                         bool with_unlink) {
83
10
  for (int tries = 5; tries > 0; tries--) {
84
9
    std::string try_path = generateTmpFilePath(path());
85
9
    fd_ = ::open(try_path.c_str(), flags_and_mode.flags_, flags_and_mode.mode_);
86
9
    if (fd_ != -1) {
87
      // Try to unlink the temp file while it's still open. Again this only works on
88
      // a (different) subset of file systems.
89
4
      if (!with_unlink || ::unlink(try_path.c_str()) != 0) {
90
        // If we couldn't unlink it, set tmp_file_path_, to unlink after close.
91
3
        tmp_file_path_ = try_path;
92
3
      }
93
4
      return resultSuccess(true);
94
4
    }
95
9
  }
96
1
  return resultFailure(false, errno);
97
5
}
98

            
99
37293
Api::IoCallSizeResult FileImplPosix::write(absl::string_view buffer) {
100
37293
  const ssize_t rc = ::write(fd_, buffer.data(), buffer.size());
101
37293
  return rc != -1 ? resultSuccess(rc) : resultFailure(rc, errno);
102
37293
};
103

            
104
33762
Api::IoCallBoolResult FileImplPosix::close() {
105
33762
  ASSERT(isOpen());
106
33762
  int rc = ::close(fd_);
107
33762
  fd_ = -1;
108
33762
  return (rc != -1) ? resultSuccess(true) : resultFailure(false, errno);
109
33762
}
110

            
111
6
Api::IoCallBoolResult TmpFileImplPosix::close() {
112
6
  ASSERT(isOpen());
113
6
  int rc = ::close(fd_);
114
6
  fd_ = -1;
115
6
  int rc2 = tmp_file_path_.empty() ? 0 : ::unlink(tmp_file_path_.c_str());
116
6
  return (rc != -1 && rc2 != -1) ? resultSuccess(true) : resultFailure(false, errno);
117
6
}
118

            
119
2
Api::IoCallSizeResult FileImplPosix::pread(void* buf, uint64_t count, uint64_t offset) {
120
2
  ASSERT(isOpen());
121
2
  ssize_t rc = ::pread(fd_, buf, count, offset);
122
2
  return (rc == -1) ? resultFailure(rc, errno) : resultSuccess(rc);
123
2
}
124

            
125
3
Api::IoCallSizeResult FileImplPosix::pwrite(const void* buf, uint64_t count, uint64_t offset) {
126
3
  ASSERT(isOpen());
127
3
  ssize_t rc = ::pwrite(fd_, buf, count, offset);
128
3
  return (rc == -1) ? resultFailure(rc, errno) : resultSuccess(rc);
129
3
}
130

            
131
6
static FileType typeFromStat(const struct stat& s) {
132
6
  if (S_ISDIR(s.st_mode)) {
133
3
    return FileType::Directory;
134
3
  }
135
3
  if (S_ISREG(s.st_mode)) {
136
2
    return FileType::Regular;
137
2
  }
138
1
  return FileType::Other;
139
3
}
140

            
141
21
static constexpr absl::optional<SystemTime> systemTimeFromTimespec(const struct timespec& t) {
142
21
  if (t.tv_sec == 0) {
143
    return absl::nullopt;
144
  }
145
21
  return timespecToChrono(t);
146
21
}
147

            
148
static Api::IoCallResult<FileInfo> infoFromStat(absl::string_view path, const struct stat& s,
149
7
                                                FileType type) {
150
7
  auto result_or_error = InstanceImplPosix().splitPathFromFilename(path);
151
7
  FileInfo ret = {
152
7
      result_or_error.status().ok() ? std::string{result_or_error.value().file_} : "",
153
7
      s.st_size,
154
7
      type,
155
#ifdef _DARWIN_FEATURE_64_BIT_INODE
156
      systemTimeFromTimespec(s.st_ctimespec),
157
      systemTimeFromTimespec(s.st_atimespec),
158
      systemTimeFromTimespec(s.st_mtimespec),
159
#else
160
7
      systemTimeFromTimespec(s.st_ctim),
161
7
      systemTimeFromTimespec(s.st_atim),
162
7
      systemTimeFromTimespec(s.st_mtim),
163
7
#endif
164
7
  };
165
7
  return result_or_error.status().ok() ? resultSuccess(ret) : resultFailure(ret, EINVAL);
166
7
}
167

            
168
6
static Api::IoCallResult<FileInfo> infoFromStat(absl::string_view path, const struct stat& s) {
169
6
  return infoFromStat(path, s, typeFromStat(s)); // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
170
6
}
171

            
172
2
Api::IoCallResult<FileInfo> FileImplPosix::info() {
173
2
  ASSERT(isOpen());
174
2
  struct stat s;
175
2
  if (::fstat(fd_, &s) != 0) {
176
1
    return resultFailure<FileInfo>({}, errno);
177
1
  }
178
1
  return infoFromStat(path(), s);
179
2
}
180

            
181
7
Api::IoCallResult<FileInfo> InstanceImplPosix::stat(absl::string_view path) {
182
7
  struct stat s;
183
7
  std::string full_path{path};
184
7
  if (::stat(full_path.c_str(), &s) != 0) {
185
2
    if (errno == ENOENT) {
186
2
      if (::lstat(full_path.c_str(), &s) == 0 && S_ISLNK(s.st_mode)) {
187
        // Special case. This directory entity is a symlink,
188
        // but the reference is broken as the target could not be stat()'ed.
189
        // After confirming this with an lstat, treat this file entity as
190
        // a regular file, which may be unlink()'ed.
191
        // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
192
1
        return infoFromStat(path, s, FileType::Regular);
193
1
      }
194
2
    }
195
1
    return resultFailure<FileInfo>({}, errno);
196
2
  }
197
5
  return infoFromStat(path, s);
198
7
}
199

            
200
4
Api::IoCallBoolResult InstanceImplPosix::createPath(absl::string_view path) {
201
  // Ideally we could just use std::filesystem::create_directories for this,
202
  // identical to ../win32/filesystem_impl.cc, but the OS version used in mobile
203
  // CI doesn't support it, so we have to do recursive path creation manually.
204
5
  while (!path.empty() && path.back() == '/') {
205
1
    path.remove_suffix(1);
206
1
  }
207
4
  if (directoryExists(std::string{path})) {
208
1
    return resultSuccess(false);
209
1
  }
210
3
  absl::string_view subpath = path;
211
6
  do {
212
6
    size_t slashpos = subpath.find_last_of('/');
213
6
    if (slashpos == absl::string_view::npos) {
214
1
      return resultFailure(false, ENOENT);
215
1
    }
216
5
    subpath = subpath.substr(0, slashpos);
217
5
  } while (!directoryExists(std::string{subpath}));
218
  // We're now at the most-nested directory that already exists.
219
  // Time to create paths recursively.
220
4
  while (subpath != path) {
221
2
    size_t slashpos = path.find_first_of('/', subpath.size() + 2);
222
2
    subpath = (slashpos == absl::string_view::npos) ? path : path.substr(0, slashpos);
223
2
    std::string dir_to_create{subpath};
224
2
    if (mkdir(dir_to_create.c_str(), 0777)) {
225
      return resultFailure(false, errno);
226
    }
227
2
  }
228
2
  return resultSuccess(true);
229
2
}
230

            
231
33764
FileImplPosix::FlagsAndMode FileImplPosix::translateFlag(FlagSet in) {
232
33764
  int out = 0;
233
33764
  mode_t mode = 0;
234
33764
  if (in.test(File::Operation::Create)) {
235
33757
    out |= O_CREAT;
236
33757
    mode |= S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
237
33757
  }
238

            
239
33764
  if (in.test(File::Operation::Append)) {
240
12287
    out |= O_APPEND;
241
21558
  } else if (in.test(File::Operation::Write) && !in.test(File::Operation::KeepExistingData)) {
242
21471
    out |= O_TRUNC;
243
21471
  }
244

            
245
33764
  if (in.test(File::Operation::Read) && in.test(File::Operation::Write)) {
246
22
    out |= O_RDWR;
247
33746
  } else if (in.test(File::Operation::Read)) {
248
2
    out |= O_RDONLY;
249
33740
  } else if (in.test(File::Operation::Write)) {
250
33738
    out |= O_WRONLY;
251
33738
  }
252

            
253
33764
  return {out, mode};
254
33764
}
255

            
256
41660
FilePtr InstanceImplPosix::createFile(const FilePathAndType& file_info) {
257
41660
  switch (file_info.file_type_) {
258
41642
  case DestinationType::File:
259
41642
    return std::make_unique<FileImplPosix>(file_info);
260
7
  case DestinationType::TmpFile:
261
7
    if (!file_info.path_.empty() && file_info.path_.back() == '/') {
262
6
      return std::make_unique<TmpFileImplPosix>(FilePathAndType{
263
6
          DestinationType::TmpFile, file_info.path_.substr(0, file_info.path_.size() - 1)});
264
6
    }
265
1
    return std::make_unique<TmpFileImplPosix>(file_info);
266
2
  case DestinationType::Stderr:
267
2
    return std::make_unique<FileImplPosix>(FilePathAndType{DestinationType::Stderr, "/dev/stderr"});
268
9
  case DestinationType::Stdout:
269
9
    return std::make_unique<FileImplPosix>(FilePathAndType{DestinationType::Stdout, "/dev/stdout"});
270
41660
  }
271
  return nullptr; // for gcc
272
41660
}
273

            
274
10472
bool InstanceImplPosix::fileExists(const std::string& path) {
275
10472
  std::ifstream input_file(path);
276
10472
  return input_file.is_open();
277
10472
}
278

            
279
2547
bool InstanceImplPosix::directoryExists(const std::string& path) {
280
2547
  DIR* const dir = ::opendir(path.c_str());
281
2547
  const bool dir_exists = nullptr != dir;
282
2547
  if (dir_exists) {
283
2273
    ::closedir(dir);
284
2273
  }
285

            
286
2547
  return dir_exists;
287
2547
}
288

            
289
40
ssize_t InstanceImplPosix::fileSize(const std::string& path) {
290
40
  struct stat info;
291
40
  if (::stat(path.c_str(), &info) != 0) {
292
1
    return -1;
293
1
  }
294
39
  return info.st_size;
295
40
}
296

            
297
75748
absl::StatusOr<std::string> InstanceImplPosix::fileReadToEnd(const std::string& path) {
298
75748
  if (illegalPath(path)) {
299
121
    return absl::InvalidArgumentError(absl::StrCat("Invalid path: ", path));
300
121
  }
301

            
302
75627
  std::ifstream file(path);
303
75627
  if (file.fail()) {
304
    return absl::InvalidArgumentError(absl::StrCat("unable to read file: ", path));
305
  }
306

            
307
75627
  std::stringstream file_string;
308
75627
  file_string << file.rdbuf();
309

            
310
75627
  return file_string.str();
311
75627
}
312

            
313
11126
absl::StatusOr<PathSplitResult> InstanceImplPosix::splitPathFromFilename(absl::string_view path) {
314
11126
  size_t last_slash = path.rfind('/');
315
11126
  if (last_slash == std::string::npos) {
316
21
    return absl::InvalidArgumentError(fmt::format("invalid file path {}", path));
317
21
  }
318
11105
  absl::string_view name = path.substr(last_slash + 1);
319
  // truncate all trailing slashes, except root slash
320
11105
  if (last_slash == 0) {
321
3
    ++last_slash;
322
3
  }
323
11105
  return PathSplitResult{path.substr(0, last_slash), name};
324
11126
}
325

            
326
75929
bool InstanceImplPosix::illegalPath(const std::string& path) {
327
  // Special case, allow /dev/fd/* access here so that config can be passed in a
328
  // file descriptor from a bootstrap script via exec. The reason we do this
329
  // _before_ canonicalizing the path is that different unix flavors implement
330
  // /dev/fd/* differently, for example on linux they are symlinks to /dev/pts/*
331
  // which are symlinks to /proc/self/fds/. On BSD (and darwin) they are not
332
  // symlinks at all. To avoid lots of platform, specifics, we allowlist
333
  // /dev/fd/* _before_ resolving the canonical path.
334
75929
  if (absl::StartsWith(path, "/dev/fd/")) {
335
1
    return false;
336
1
  }
337

            
338
  // Allow access to cgroup-related /proc and /sys files for container-aware CPU detection.
339
  // These are read-only system files that provide resource limit information.
340
  // Whitelisted paths:
341
  //   - /proc/self/'mountinfo': Discovers cgroup filesystem mount points
342
  //   - /proc/self/cgroup: Determines process cgroup assignments
343
  //   - /sys/fs/cgroup/*: Reads cgroup v1 and v2 CPU limit files
344
  //     - v2: /sys/fs/cgroup/*/cpu.max
345
  //     - v1: /sys/fs/cgroup/cpu/*/cpu.'cfs'_quota_us and cpu.'cfs'_period_us
346
75928
  if (path == "/proc/self/mountinfo" || path == "/proc/self/cgroup" ||
347
75928
      absl::StartsWith(path, "/sys/fs/cgroup/")) {
348
21
    return false;
349
21
  }
350

            
351
75907
  const Api::SysCallStringResult canonical_path = canonicalPath(path);
352
75907
  if (canonical_path.return_value_.empty()) {
353
43
    ENVOY_LOG_MISC(debug, "Unable to determine canonical path for {}: {}", path,
354
43
                   errorDetails(canonical_path.errno_));
355
43
    return true;
356
43
  }
357

            
358
  // Platform specific path sanity; we provide a convenience to avoid Envoy
359
  // instances poking in bad places. We may have to consider conditioning on
360
  // platform in the future, growing these or relaxing some constraints (e.g.
361
  // there are valid reasons to go via /proc for file paths).
362
  // TODO(htuch): Optimize this as a hash lookup if we grow any further.
363
  // It will allow the canonical path such as /sysroot/ which is not the
364
  // default reserved directories (/dev, /sys, /proc)
365
75864
  if (absl::StartsWith(canonical_path.return_value_, "/dev/") ||
366
75864
      absl::StartsWith(canonical_path.return_value_, "/sys/") ||
367
75864
      absl::StartsWith(canonical_path.return_value_, "/proc/") ||
368
75864
      canonical_path.return_value_ == "/dev" || canonical_path.return_value_ == "/sys" ||
369
75864
      canonical_path.return_value_ == "/proc") {
370
88
    return true;
371
88
  }
372
75776
  return false;
373
75864
}
374

            
375
75909
Api::SysCallStringResult InstanceImplPosix::canonicalPath(const std::string& path) {
376
75909
  char* resolved_path = ::realpath(path.c_str(), nullptr);
377
75909
  if (resolved_path == nullptr) {
378
44
    return {std::string(), errno};
379
44
  }
380
75865
  std::string resolved_path_string{resolved_path};
381
75865
  ::free(resolved_path);
382
75865
  return {resolved_path_string, 0};
383
75909
}
384

            
385
} // namespace Filesystem
386
} // namespace Envoy