LCOV - code coverage report
Current view: top level - source/common/filesystem/posix - filesystem_impl.cc (source / functions) Hit Total Coverage
Test: coverage.dat Lines: 90 257 35.0 %
Date: 2024-01-05 06:35:25 Functions: 12 26 46.2 %

          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

Generated by: LCOV version 1.15