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