| /* |
| * Copyright 2020 Google LLC |
| * |
| * 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 "apifs/apifs.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <sys/stat.h> // IWYU pragma: keep |
| #include <sys/types.h> // IWYU pragma: keep |
| #include <unistd.h> // IWYU pragma: keep |
| |
| #include <climits> |
| #include <cstddef> |
| #include <functional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/cleanup/cleanup.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/match.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/span.h" |
| #include "file/dir.h" |
| #include "file/path.h" |
| #include "g3/macros.h" |
| |
| namespace ecclesia { |
| namespace { |
| |
| // Returns if the given path (diretory or a file) exists. |
| bool PathExists(const std::string &path) { |
| return access(path.c_str(), F_OK) == 0; |
| } |
| |
| // List all the entries in a given directory and apply the given functor to get |
| // the return value for each entry. |
| absl::StatusOr<std::vector<std::string>> ListEntriesInDir( |
| const std::string &dir_path, |
| std::function<std::string(absl::string_view)> per_entry_func) { |
| if (!PathExists(dir_path)) { |
| return absl::NotFoundError( |
| absl::StrFormat("Directory not found at path: %s", dir_path)); |
| } |
| std::vector<std::string> entries; |
| ECCLESIA_RETURN_IF_ERROR( |
| WithEachFileInDirectory(dir_path, [&](absl::string_view entry) { |
| entries.push_back(per_entry_func(entry)); |
| })); |
| return entries; |
| } |
| |
| } // namespace |
| |
| ApifsDirectory::ApifsDirectory() = default; |
| |
| ApifsDirectory::ApifsDirectory(std::string path) : dir_path_(std::move(path)) {} |
| |
| ApifsDirectory::ApifsDirectory(const ApifsDirectory &directory, |
| std::string path) |
| : dir_path_(JoinFilePaths(directory.dir_path_, std::move(path))) {} |
| |
| bool ApifsDirectory::Exists() const { return PathExists(dir_path_); } |
| |
| bool ApifsDirectory::Exists(absl::string_view path) const { |
| return PathExists(JoinFilePaths(this->dir_path_, path)); |
| } |
| |
| absl::StatusOr<struct stat> ApifsDirectory::Stat(std::string path) const { |
| ApifsFile f(*this, std::move(path)); |
| return f.Stat(); |
| } |
| |
| absl::StatusOr<std::vector<std::string>> ApifsDirectory::ListEntryNames() |
| const { |
| return ListEntriesInDir( |
| dir_path_, [](absl::string_view entry) { return std::string(entry); }); |
| } |
| |
| absl::StatusOr<std::vector<std::string>> ApifsDirectory::ListEntryNames( |
| std::string path) const { |
| return ListEntriesInDir( |
| JoinFilePaths(dir_path_, std::move(path)), |
| [](absl::string_view entry) { return std::string(entry); }); |
| } |
| |
| absl::StatusOr<std::vector<std::string>> ApifsDirectory::ListEntryPaths() |
| const { |
| return ListEntriesInDir(dir_path_, [&](absl::string_view entry) { |
| return JoinFilePaths(this->dir_path_, entry); |
| }); |
| } |
| |
| absl::StatusOr<std::vector<std::string>> ApifsDirectory::ListEntryPaths( |
| std::string path) const { |
| std::string full_path = JoinFilePaths(dir_path_, std::move(path)); |
| return ListEntriesInDir(full_path, [&full_path](absl::string_view entry) { |
| return JoinFilePaths(full_path, entry); |
| }); |
| } |
| |
| absl::StatusOr<std::string> ApifsDirectory::Read(std::string path) const { |
| ApifsFile f(*this, std::move(path)); |
| return f.Read(); |
| } |
| |
| absl::Status ApifsDirectory::Write(std::string path, |
| absl::string_view value) const { |
| ApifsFile f(*this, std::move(path)); |
| return f.Write(value); |
| } |
| |
| absl::StatusOr<std::string> ApifsDirectory::ReadLink(std::string path) const { |
| ApifsFile f(*this, std::move(path)); |
| return f.ReadLink(); |
| } |
| |
| ApifsFile::ApifsFile() = default; |
| |
| ApifsFile::ApifsFile(std::string path) : path_(std::move(path)) {} |
| |
| ApifsFile::ApifsFile(const ApifsDirectory &directory, |
| absl::string_view entry_relative_path) |
| : path_(JoinFilePaths(directory.dir_path_, entry_relative_path)) {} |
| |
| bool ApifsFile::Exists() const { return PathExists(path_); } |
| |
| absl::StatusOr<struct stat> ApifsFile::Stat() const { |
| struct stat st; |
| if (stat(path_.c_str(), &st) < 0) { |
| return absl::InternalError(absl::StrFormat( |
| "failure while stat-ing file at path: %s, errno: %d", path_, errno)); |
| } |
| return st; |
| } |
| |
| absl::StatusOr<std::string> ApifsFile::Read() const { |
| const int fd = open(path_.c_str(), O_RDONLY); |
| if (fd < 0) { |
| if (errno == ENOENT) { |
| return absl::NotFoundError( |
| absl::StrFormat("File not found at path: %s", path_)); |
| } |
| return absl::InternalError(absl::StrFormat( |
| "unable to open the file at path: %s, errno: %d", path_, errno)); |
| } |
| absl::Cleanup fd_closer = [fd]() { close(fd); }; |
| |
| std::string value; |
| while (true) { |
| char buffer[4096]; |
| const ssize_t n = read(fd, buffer, sizeof(buffer)); |
| if (n < 0) { |
| const auto read_errno = errno; |
| if (read_errno == EINTR) { |
| continue; // Retry on EINTR. |
| } |
| return absl::InternalError(absl::StrFormat( |
| "failure while reading from file at path: %s, errno: %d", path_, |
| read_errno)); |
| break; |
| } else if (n == 0) { |
| break; // Nothing left to read. |
| } else { |
| value.append(buffer, n); |
| } |
| } |
| return value; |
| } |
| |
| absl::Status ApifsFile::Write(absl::string_view value) const { |
| const int fd = open(path_.c_str(), O_WRONLY | O_TRUNC); |
| if (fd < 0) { |
| if (errno == ENOENT) { |
| return absl::NotFoundError( |
| absl::StrFormat("File not found at path: %s", path_)); |
| } |
| return absl::InternalError( |
| absl::StrFormat("unable to open the file at path: %s", path_)); |
| } |
| absl::Cleanup fd_closer = [fd]() { close(fd); }; |
| const char *data = value.data(); |
| size_t size = value.size(); |
| while (size > 0) { |
| ssize_t result = write(fd, data, size); |
| if (result <= 0) { |
| const auto write_errno = errno; |
| if (write_errno == EINTR) continue; // Retry on EINTR. |
| return absl::InternalError(absl::StrFormat( |
| "failure while writing to file at path: %s, errno: %d", path_, |
| errno)); |
| break; |
| } |
| // We successfully wrote out 'result' bytes, advance the data pointer. |
| size -= result; |
| data += result; |
| } |
| return absl::OkStatus(); |
| } |
| |
| absl::Status ApifsFile::ReadRange(uint64_t offset, |
| absl::Span<char> value) const { |
| int fd = open(path_.c_str(), O_RDONLY); |
| |
| if (fd < 0) { |
| if (errno == ENOENT) { |
| return absl::NotFoundError( |
| absl::StrFormat("File not found at path: %s", path_)); |
| } |
| return absl::InternalError(absl::StrFormat( |
| "Unable to open the file at path: %s, errno: %d", path_, errno)); |
| } |
| absl::Cleanup fd_closer = [fd]() { close(fd); }; |
| // Read data. |
| size_t remaining_length = value.size(); |
| char *data = value.data(); |
| off_t read_offset = static_cast<off_t>(offset); |
| while (remaining_length > 0) { |
| ssize_t result = pread(fd, data, remaining_length, read_offset); |
| if (result < 0) { |
| const auto read_errno = errno; |
| if (read_errno == EINTR) { |
| continue; // Retry on EINTR. |
| } |
| return absl::InternalError(absl::StrFormat( |
| "Failure while reading from file at path: %s, errno: %d", path_, |
| read_errno)); |
| break; |
| } |
| if (result == 0) { |
| break; // Nothing left to read |
| } |
| // advance to read next chunk |
| remaining_length -= result; |
| data += result; |
| read_offset += result; |
| } |
| if (remaining_length != 0) { |
| return absl::InternalError(absl::StrFormat( |
| "Specified data length is larger than actual readback length, file path:" |
| " %s, delta: %d", path_, remaining_length)); |
| } |
| return absl::OkStatus(); |
| } |
| |
| absl::Status ApifsFile::WriteRange(uint64_t offset, |
| absl::Span<const char> value) const { |
| const int fd = open(path_.c_str(), O_WRONLY); |
| if (fd < 0) { |
| if (errno == ENOENT) { |
| return absl::NotFoundError( |
| absl::StrFormat("File not found at path: %s", path_)); |
| } |
| return absl::InternalError( |
| absl::StrFormat("Unable to open the file at path: %s", path_)); |
| } |
| absl::Cleanup fd_closer = [fd]() { close(fd); }; |
| // Write data. |
| size_t remaining_length = value.size(); |
| const char *data = value.data(); |
| off_t write_offset = static_cast<off_t>(offset); |
| while (remaining_length > 0) { |
| ssize_t result = pwrite(fd, data, remaining_length, write_offset); |
| if (result < 0) { |
| const auto write_errno = errno; |
| if (write_errno == EINTR) { |
| continue; // Retry on EINTR. |
| } |
| return absl::InternalError(absl::StrFormat( |
| "Failure while writing file at path: %s, errno: %d", path_, |
| write_errno)); |
| break; |
| } |
| if (result == 0) { |
| break; // Nothing left to write |
| } |
| // advance to write next chunk |
| remaining_length -= result; |
| data += result; |
| write_offset += result; |
| } |
| if (remaining_length != 0) { |
| return absl::InternalError(absl::StrFormat( |
| "Specified data length is larger than actual write length, file path:" |
| " %s, delta: %d", path_, remaining_length)); |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| absl::StatusOr<std::string> ApifsFile::ReadLink() const { |
| // Do an lstat of the path to determine the link size and verify the file is |
| // in fact a symlink. |
| struct stat st; |
| if (lstat(path_.c_str(), &st) < 0) { |
| if (errno == ENOENT) { |
| return absl::NotFoundError( |
| absl::StrFormat("file not found at path: %s", path_)); |
| } else { |
| return absl::InternalError(absl::StrFormat( |
| "failure while lstat-ing file at path: %s, errno: %d", path_, errno)); |
| } |
| } |
| if (!S_ISLNK(st.st_mode)) { |
| return absl::InvalidArgumentError( |
| absl::StrFormat("path: %s is not a symlink", path_)); |
| } |
| // Read the symlink using a std::string buffer size from the lstat. The +1 is |
| // used for determining whether the buffer returned by readlink was truncated. |
| ssize_t bufsize = st.st_size + 1; |
| // Some symlinks under /proc and /sys report st.st_size as zero. In that case, |
| // use PATH_MAX as a "good enough" estimate. |
| if (st.st_size == 0) { |
| bufsize = PATH_MAX; |
| } |
| std::string link(bufsize, '\0'); |
| ssize_t rc = readlink(path_.c_str(), &link[0], link.size()); |
| if (rc == -1) { |
| return absl::InternalError(absl::StrFormat( |
| "unable to read the link at path: %s, errno: %d", path_, errno)); |
| } |
| if (rc >= bufsize) { |
| // If this happens, it means the target symlinks filename size is larger |
| // than expected. Perhaps because the target was changed between lstat() and |
| // readlink(). Just consider that an error. |
| return absl::InternalError(absl::StrFormat( |
| "the link at: %s was changed while it was being read", path_)); |
| } |
| // The first "rc" characters in the string were populated. Return that. |
| return link.substr(0, rc); |
| } |
| |
| } // namespace ecclesia |