| // Copyright 2017-2020 The Verible Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "common/util/file_util.h" |
| |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <algorithm> |
| #include <filesystem> |
| #include <fstream> |
| #include <iostream> |
| #include <streambuf> |
| #include <string> |
| #include <system_error> |
| |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_join.h" |
| #include "absl/strings/str_split.h" |
| #include "absl/strings/string_view.h" |
| #include "common/util/logging.h" |
| |
| #ifndef _WIN32 |
| #include <fcntl.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #endif |
| |
| namespace fs = std::filesystem; |
| |
| namespace verible { |
| namespace file { |
| absl::string_view Basename(absl::string_view filename) { |
| auto last_slash_pos = filename.find_last_of("/\\"); |
| |
| return last_slash_pos == absl::string_view::npos |
| ? filename |
| : filename.substr(last_slash_pos + 1); |
| } |
| |
| absl::string_view Dirname(absl::string_view filename) { |
| auto last_slash_pos = filename.find_last_of("/\\"); |
| |
| return last_slash_pos == absl::string_view::npos |
| ? filename |
| : filename.substr(0, last_slash_pos); |
| } |
| |
| absl::string_view Stem(absl::string_view filename) { |
| auto last_dot_pos = filename.find_last_of('.'); |
| |
| return last_dot_pos == absl::string_view::npos |
| ? filename |
| : filename.substr(0, last_dot_pos); |
| } |
| |
| // This will always return an error, even if we can't determine anything |
| // from errno. Returns "fallback_msg" in that case. |
| static absl::Status CreateErrorStatusFromSysError(const char *fallback_msg, |
| int sys_error) { |
| using absl::StatusCode; |
| const char *const system_msg = |
| sys_error == 0 ? fallback_msg : strerror(sys_error); |
| switch (sys_error) { |
| case EPERM: |
| case EACCES: |
| return absl::Status(StatusCode::kPermissionDenied, system_msg); |
| case ENOENT: |
| return absl::Status(StatusCode::kNotFound, system_msg); |
| case EEXIST: |
| return absl::Status(StatusCode::kAlreadyExists, system_msg); |
| case EINVAL: |
| return absl::Status(StatusCode::kInvalidArgument, system_msg); |
| default: |
| return absl::Status(StatusCode::kUnknown, system_msg); |
| } |
| } |
| |
| static absl::Status CreateErrorStatusFromErrno(const char *fallback_msg) { |
| return CreateErrorStatusFromSysError(fallback_msg, errno); |
| } |
| static absl::Status CreateErrorStatusFromErr(const char *fallback_msg, |
| const std::error_code &err) { |
| // TODO: this assumes that err.value() returns errno-like values. Might not |
| // always be the case. |
| return CreateErrorStatusFromSysError(fallback_msg, err.value()); |
| } |
| |
| absl::Status UpwardFileSearch(absl::string_view start, |
| absl::string_view filename, std::string *result) { |
| std::error_code err; |
| const std::string search_file(filename); |
| fs::path absolute_path = fs::absolute(std::string(start), err); |
| if (err.value() != 0) |
| return CreateErrorStatusFromErr("invalid config path specified.", err); |
| |
| fs::path probe_dir = absolute_path; |
| for (;;) { |
| *result = (probe_dir / search_file).string(); |
| if (FileExists(*result).ok()) return absl::OkStatus(); |
| fs::path one_up = probe_dir.parent_path(); |
| if (one_up == probe_dir) break; |
| probe_dir = one_up; |
| } |
| return absl::NotFoundError("No matching file found."); |
| } |
| |
| absl::Status FileExists(const std::string &filename) { |
| std::error_code err; |
| fs::file_status stat = fs::status(filename, err); |
| |
| if (err.value() != 0) { |
| return absl::NotFoundError(absl::StrCat(filename, ": ", err.message())); |
| } |
| |
| if (fs::is_regular_file(stat) || fs::is_fifo(stat)) { |
| return absl::OkStatus(); |
| } |
| |
| if (fs::is_directory(stat)) { |
| return absl::InvalidArgumentError( |
| absl::StrCat(filename, ": is a directory, not a file")); |
| } |
| return absl::InvalidArgumentError( |
| absl::StrCat(filename, ": not a regular file.")); |
| } |
| |
| absl::Status GetContents(absl::string_view filename, std::string *content) { |
| std::ifstream fs; |
| std::istream *stream = nullptr; |
| const bool use_stdin = filename == "-"; // convention: honor "-" as stdin |
| if (use_stdin) { |
| stream = &std::cin; |
| } else { |
| const std::string filename_str = std::string(filename); |
| absl::Status usable_file = FileExists(filename_str); |
| if (!usable_file.ok()) return usable_file; // Bail |
| fs.open(filename_str.c_str()); |
| std::error_code err; |
| const size_t prealloc = fs::file_size(filename_str, err); |
| if (err.value() == 0) content->reserve(prealloc); |
| stream = &fs; |
| } |
| if (!stream->good()) return CreateErrorStatusFromErrno("can't read"); |
| char buffer[4096]; |
| while (stream->good() && !stream->eof()) { |
| stream->read(buffer, sizeof(buffer)); |
| content->append(buffer, stream->gcount()); |
| } |
| |
| // Allow stdin to be reopened for more input. |
| if (use_stdin && std::cin.eof()) std::cin.clear(); |
| return absl::OkStatus(); |
| } |
| |
| static absl::StatusOr<std::unique_ptr<MemBlock>> AttemptMemMapFile( |
| absl::string_view filename) { |
| #ifndef _WIN32 |
| class MemMapBlock final : public MemBlock { |
| public: |
| MemMapBlock(char *buffer, size_t size) : buffer_(buffer), size_(size) {} |
| ~MemMapBlock() final { munmap(buffer_, size_); } |
| absl::string_view AsStringView() const final { return {buffer_, size_}; } |
| |
| private: |
| char *const buffer_; |
| const size_t size_; |
| }; |
| |
| const std::string nul_terminated_filename(filename); |
| const int fd = open(nul_terminated_filename.c_str(), O_RDONLY); |
| if (fd < 0) { |
| return CreateErrorStatusFromErrno("Can't open file"); |
| } |
| |
| struct stat s; |
| if (fstat(fd, &s) < 0) { |
| close(fd); |
| return CreateErrorStatusFromErrno("can't stat"); |
| } |
| |
| const size_t file_size = s.st_size; |
| void *const buffer = mmap(NULL, file_size, PROT_READ, MAP_SHARED, fd, 0); |
| close(fd); |
| if (buffer == MAP_FAILED) { |
| return CreateErrorStatusFromErrno("Can't mmap file"); |
| } |
| |
| return std::make_unique<MemMapBlock>((char *)buffer, file_size); |
| #else |
| // TODO: implement some memory mapping on Windows. |
| return absl::UnimplementedError("No windows mmap implementation yet."); |
| #endif |
| } |
| |
| absl::StatusOr<std::unique_ptr<MemBlock>> GetContentAsMemBlock( |
| absl::string_view filename) { |
| auto mmap_result = AttemptMemMapFile(filename); |
| if (mmap_result.status().ok()) { |
| return mmap_result; |
| } |
| |
| // Still here ? Well, let's try the traditional way |
| auto mem_block = std::make_unique<StringMemBlock>(); |
| if (auto status = GetContents(filename, mem_block->mutable_content()); |
| !status.ok()) { |
| return status; |
| } |
| return mem_block; |
| } |
| |
| absl::Status SetContents(absl::string_view filename, |
| absl::string_view content) { |
| VLOG(1) << __FUNCTION__ << ": Writing file: " << filename; |
| std::ofstream f(std::string(filename).c_str()); |
| if (!f.good()) return CreateErrorStatusFromErrno("can't write."); |
| f << content; |
| return absl::OkStatus(); |
| } |
| |
| std::string JoinPath(absl::string_view base, absl::string_view name) { |
| // Make sure the second element is not already absolute, otherwise |
| // the fs::path() uses this as toplevel path. This is only an issue with |
| // Unix paths. Windows paths will have a c:\ for explicit full-pathness |
| while (!name.empty() && name[0] == '/') { |
| name = name.substr(1); |
| } |
| |
| fs::path p = fs::path(std::string(base)) / fs::path(std::string(name)); |
| return p.lexically_normal().string(); |
| } |
| |
| absl::Status CreateDir(absl::string_view dir) { |
| const std::string path(dir); |
| std::error_code err; |
| if (fs::create_directory(path, err) || err.value() == 0) |
| return absl::OkStatus(); |
| |
| return CreateErrorStatusFromErr("can't create directory", err); |
| } |
| |
| absl::StatusOr<Directory> ListDir(absl::string_view dir) { |
| std::error_code err; |
| Directory d; |
| |
| d.path = dir.empty() ? "." : std::string(dir); |
| |
| fs::file_status stat = fs::status(d.path, err); |
| if (err.value() != 0) |
| return CreateErrorStatusFromErr("Opening directory", err); |
| if (!fs::is_directory(stat)) { |
| return absl::InvalidArgumentError(absl::StrCat(dir, ": not a directory")); |
| } |
| |
| for (const fs::directory_entry &entry : fs::directory_iterator(d.path)) { |
| const std::string entry_name = entry.path().string(); |
| if (entry.is_directory()) { |
| d.directories.push_back(entry_name); |
| } else { |
| d.files.push_back(entry_name); |
| } |
| } |
| |
| std::sort(d.files.begin(), d.files.end()); |
| std::sort(d.directories.begin(), d.directories.end()); |
| return d; |
| } |
| |
| namespace testing { |
| |
| std::string RandomFileBasename(absl::string_view prefix) { |
| return absl::StrCat(prefix, "-", rand()); |
| } |
| |
| ScopedTestFile::ScopedTestFile(absl::string_view base_dir, |
| absl::string_view content, |
| absl::string_view use_this_filename) |
| // There is no secrecy needed for test files, |
| // file name just need to be unique enough. |
| : filename_(JoinPath(base_dir, use_this_filename.empty() |
| ? RandomFileBasename("scoped-file") |
| : use_this_filename)) { |
| const absl::Status status = SetContents(filename_, content); |
| CHECK(status.ok()) << status.message(); // ok for test-only code |
| } |
| |
| ScopedTestFile::~ScopedTestFile() { unlink(filename_.c_str()); } |
| } // namespace testing |
| } // namespace file |
| } // namespace verible |