// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. // // File class - provides a simple android specific file implementation #include "file_store.h" #include #include #include #include #include #include #include #include #include #include "file_utils.h" #include "log.h" #include "string_conversions.h" #include // Size of the thread-local error string buffer. Used for calls // to strerror_r(3). #define ERRORSTR_BUF_SIZE 1024 namespace wvutil { namespace { // Maximum number of attempts to read or write on a file. constexpr size_t kMaxIoAttempts = 5; // Stand in constant for a closed file descriptor. constexpr int kClosedFd = -1; // A reset value of |errno|. Although unlikely, it is possible that a // system call could fail and not set |errno| to a new value. This // would technically be a bug with glibc, but it could cause our error // handling code to enter a bad state. constexpr int kNoError = 0; constexpr char kEmptyOrigin[] = ""; // Reads from file specified by |fd| into the provided |buffer| up to // the number of bytes specified by |count|. // This is an internal function and assumes that all parameters are // valid. // // Returns: // 0 to |count| - Number of bytes successfully read from file. // -1 - Error occurred, check |errno| for read(2). ssize_t SafeRead(int fd, char* buffer, size_t count) { size_t attempts = 0; size_t total_bytes_read = 0; while (total_bytes_read < count && attempts < kMaxIoAttempts) { const size_t to_read = count - total_bytes_read; errno = kNoError; const ssize_t res = read(fd, buffer, to_read); if (res > 0) { attempts = 0; // Clearing |attempts| on success. // It is possible that fewer bytes than |to_read| were read. // In this case, try reading again. Non-critical errors will // likely result in success or |errno| being set to EAGAIN on // the second call. Critical errors will result in a different // error that the caller will need to handle. total_bytes_read += static_cast(res); continue; } if (res == 0) return total_bytes_read; // EOF. attempts++; if ((errno != EINTR && errno != EAGAIN) || attempts >= kMaxIoAttempts) { // Caller must handle all other errors, or if max attempts // have been reached. return -1; } // read() was interrupted by signal, safe to try again. } return total_bytes_read; } // Writes to the file specified by |fd| from the provided |buffer|. // Function will attempt to write all bytes specified by |count|. // This is an internal function and assumes that all parameters are // valid. // // Returns: // |count| - Successfully wrote all bytes to file. // -1 - Error occurred, check |errno| for write(2). ssize_t SafeWrite(int fd, const char* buffer, size_t count) { size_t attempts = 0; size_t total_bytes_written = 0; while (total_bytes_written < count && attempts < kMaxIoAttempts) { const size_t to_write = count - total_bytes_written; errno = kNoError; const ssize_t res = write(fd, &buffer[total_bytes_written], to_write); if (res > 0) { attempts = 0; // Clearing |attempts| on success. // It is possible that fewer bytes than |to_write| were written. // In this case, try writing again. Non-critical errors will // likely result in success or |errno| being set to EAGAIN on // the second call. Critical errors will result in a different // error that the caller will need to handle. total_bytes_written += static_cast(res); continue; } if (res == 0) return total_bytes_written; // Possible EOF. attempts++; if ((errno != EINTR && errno != EAGAIN) || attempts >= kMaxIoAttempts) { // Caller must handle all other errors, or if max attempts // have been reached. return -1; } // write() was interrupted by signal, safe to try again. } return total_bytes_written; } // Converts the provided error number to its string representation. // Supports a subset of error numbers expected from the system calls // used in this module. // TODO(b/183653374): Replace this with strerrorname_np(). const char* ErrnoToString(int num) { switch (num) { case kNoError: return "ZERO"; case EACCES: return "EACCES"; case EAGAIN: return "EAGAIN"; case EBADF: return "EBADF"; case EBUSY: return "EBUSY"; case EDESTADDRREQ: return "EDESTADDRREQ"; case EDQUOT: return "EDQUOT"; case EEXIST: return "EEXIST"; case EFAULT: return "EFAULT"; case EFBIG: return "EFBIG"; case EINTR: return "EINTR"; case EINVAL: return "EINVAL"; case EIO: return "EIO"; case EISDIR: return "EISDIR"; case ELOOP: return "ELOOP"; case EMFILE: return "EMFILE"; case ENAMETOOLONG: return "ENAMETOOLONG"; case ENFILE: return "ENFILE"; case ENODEV: return "ENODEV"; case ENOENT: return "ENOENT"; case ENOMEM: return "ENOMEM"; case ENOSPC: return "ENOSPC"; case ENOTDIR: return "ENOTDIR"; case ENXIO: return "ENXIO"; case EOPNOTSUPP: return "EOPNOTSUPP"; case EOVERFLOW: return "EOVERFLOW"; case EPERM: return "EPERM"; case EPIPE: return "EPIPE"; case EROFS: return "EROFS"; case ETXTBSY: return "ETXTBSY"; #if EWOULDBLOCK != EAGAIN case EWOULDBLOCK: return "EWOULDBLOCK"; #endif } return "UNKNOWN"; } // Safely converts the provided error number to its standard // description string as provided by strerror_r(). // This function is guaranteed to return null-terminated string, // and is thread safe. const char* ErrnoToDescription(int num) { static thread_local char error_buf[ERRORSTR_BUF_SIZE]; if (num == kNoError) { return "Unspecified error"; } // Always ensure there is a null term. error_buf[sizeof(error_buf) - 1] = 0; // See strerror_l(3) manual page for details. #if (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && !_GNU_SOURCE // Portable version: // int strerror_r(int num, char* buf, size_t buflen) // Returns: // 0 on success // -1 or a positive value on error (depends on glibc version) const int res = strerror_r(num, error_buf, sizeof(error_buf)); return res != 0 ? "Unknown" : error_buf; #else // GNU specific version: // char* strerror_r(int num, char* buf, size_t buflen) // Returns: // Pointer to |buf| or internal string on success // Null on error const char* res = strerror_r(num, error_buf, sizeof(error_buf)); return res == nullptr ? "Unknown" : res; #endif } // Converts the provided file |mode| to a "ls" style file mode. std::string StatModeToString(unsigned int mode) { std::string mode_rep; mode_rep.reserve(11); // 1 file type + 9 permissions + 1 term. // File type. if (S_ISREG(mode)) { mode_rep.append("-"); } else if (S_ISDIR(mode)) { mode_rep.append("d"); } else if (S_ISLNK(mode)) { mode_rep.append("l"); } else if (S_ISCHR(mode)) { mode_rep.append("c"); } else if (S_ISBLK(mode)) { mode_rep.append("b"); } else if (S_ISSOCK(mode)) { mode_rep.append("s"); } else { mode_rep.append("?"); } // User owner permission. mode_rep.append((mode & S_IRUSR) ? "r" : "-"); mode_rep.append((mode & S_IWUSR) ? "w" : "-"); mode_rep.append((mode & S_IXUSR) ? "x" : "-"); // Group owner permission. mode_rep.append((mode & S_IRGRP) ? "r" : "-"); mode_rep.append((mode & S_IWGRP) ? "w" : "-"); mode_rep.append((mode & S_IXGRP) ? "x" : "-"); // Others permission. mode_rep.append((mode & S_IROTH) ? "r" : "-"); mode_rep.append((mode & S_IWOTH) ? "w" : "-"); mode_rep.append((mode & S_IXOTH) ? "x" : "-"); return mode_rep; } // Coverts the provided system time in seconds to a string representation // in the system's local time. // Time format is a modified ISO8601. // Example: 2021-03-01 12:32:53 // If the time cannot be converted into this format, then the decimal // representation of seconds are returned. std::string PosixTimeToString(time_t seconds) { struct tm timestamp; if (localtime_r(&seconds, ×tamp) == nullptr) { // Only possible failure is an overflow. return std::to_string(seconds); } char buffer[32]; memset(buffer, 0, sizeof(buffer)); const size_t length = strftime(buffer, sizeof(buffer), "%F %T", ×tamp); if (length == 0) { // Unexpected error. Just return seconds. return std::to_string(seconds); } return std::string(buffer, length); } std::string GetFileNameSafeHash(const std::string& input) { std::vector hash(MD5_DIGEST_LENGTH); MD5(reinterpret_cast(input.data()), input.size(), hash.data()); return wvutil::Base64SafeEncode(hash); } std::string GetFileNameForIdentifier(const std::string path, const std::string identifier) { std::string file_name = path; std::string dir_path; const size_t delimiter_pos = path.rfind(kDirectoryDelimiter); if (delimiter_pos != std::string::npos) { dir_path = file_name.substr(0, delimiter_pos); file_name = path.substr(delimiter_pos + 1); } if (file_name == kCertificateFileName && !identifier.empty()) { const std::string hash = GetFileNameSafeHash(identifier); file_name = kCertificateFileNamePrefix + hash + kCertificateFileNameExt; } else if (file_name == kLegacyCertificateFileName && !identifier.empty()) { const std::string hash = GetFileNameSafeHash(identifier); file_name = kLegacyCertificateFileNamePrefix + hash + kCertificateFileNameExt; } if (dir_path.empty()) return file_name; return dir_path + kDirectoryDelimiter + file_name; } } // namespace class AndroidFile : public File { public: // Parameters: // |fd| - Open file descriptor for a regular file. // |flags| - Bit field of flags originally passed to FileSystem::Open() // |file_path| - Path used to open file. AndroidFile(int fd, int flags, const std::string& file_path) : fd_(fd), flags_(flags), file_path_(file_path) {} ~AndroidFile() { Close(); } bool IsOpen() const { return fd_ != kClosedFd; } bool CanWrite() const { return !(flags_ & FileSystem::kReadOnly); } // Used for logging. const char* file_path() const { return file_path_.c_str(); } ssize_t Read(char* buffer, size_t bytes) override { if (!buffer) { LOGE("Output |buffer| is null"); return -1; } if (!IsOpen()) { LOGE("File not open: path = %s", file_path()); return -1; } const ssize_t res = SafeRead(fd_, buffer, bytes); if (res < 0) { const int saved_errno = errno; LOGE("Read failed: errno = %s (%d), desc = %s", ErrnoToString(saved_errno), saved_errno, ErrnoToDescription(saved_errno)); return -1; } else if (res < bytes) { LOGD("Read output truncated: expected = %zu, actual = %zd", bytes, res); } return res; } ssize_t Write(const char* buffer, size_t bytes) override { if (!buffer) { LOGE("Input |buffer| is null"); return -1; } if (!IsOpen()) { LOGE("File not open: path = %s", file_path()); return -1; } if (!CanWrite()) { LOGE("File is read only: path = %s", file_path()); return -1; } const ssize_t res = SafeWrite(fd_, buffer, bytes); if (res < 0) { const int saved_errno = errno; LOGE("Write failed: errno = %s (%d), desc = %s", ErrnoToString(saved_errno), saved_errno, ErrnoToDescription(saved_errno)); return -1; } FlushFile(); return res; } private: void FlushFile() { fsync(fd_); } void Close() { if (IsOpen()) { FlushFile(); close(fd_); fd_ = kClosedFd; } } // Logs the contents of the file's stat info. void LogStat() const { if (!IsOpen()) { LOGD("No stat info available"); return; } struct stat st; errno = kNoError; if (fstat(fd_, &st) != 0) { const int saved_errno = errno; if (errno != EBADF) { // No logs if an issue with FD, caller would have indicated the problem. LOGE("Stat failed: errno = %s (%d), desc = %s", ErrnoToString(saved_errno), saved_errno, ErrnoToDescription(saved_errno)); } return; } LOGD( "Stat: path = %s, st_dev = %lu, st_ino = %lu, st_mode = 0%o (%s), " "st_uid = %u, st_gid = %u, st_size = %ld, st_atime = %s, " "st_mtime = %s, st_ctime = %s", file_path(), st.st_dev, st.st_ino, st.st_mode, StatModeToString(st.st_mode).c_str(), st.st_uid, st.st_gid, st.st_size, PosixTimeToString(st.st_atime).c_str(), PosixTimeToString(st.st_mtime).c_str(), PosixTimeToString(st.st_ctime).c_str()); } // File descriptor of the opened file. Set to -1 when closed. int fd_ = kClosedFd; // Bit field of OpenFlags. int flags_ = 0; // Path used to open the file descriptor. std::string file_path_; }; class FileSystem::Impl {}; FileSystem::FileSystem() : FileSystem(kEmptyOrigin, nullptr) {} FileSystem::FileSystem(const std::string& origin, void* /* extra_data */) : origin_(origin) {} FileSystem::~FileSystem() {} std::unique_ptr FileSystem::Open(const std::string& file_name, int flags) { const std::string file_path = GetFileNameForIdentifier(file_name, identifier_); // Verify flags. if ((flags & kReadOnly) && (flags & kTruncate)) { LOGE( "Cannot be both truncated and be read-only: " "file = %s, identifier = %s", file_name.c_str(), identifier_.c_str()); return nullptr; } // Create the enclosing directory if it does not exist. const size_t delimiter_pos = file_path.rfind(kDirectoryDelimiter); if (delimiter_pos != std::string::npos) { const std::string dir_path = file_path.substr(0, delimiter_pos); if ((flags & FileSystem::kCreate) && !Exists(dir_path)) FileUtils::CreateDirectory(dir_path); } const bool exists = Exists(file_path); if (!(flags & kCreate) && !exists) { LOGD("File does not exist: file = %s, identifier = %s", file_name.c_str(), identifier_.c_str()); return nullptr; } const int open_flags = ((flags & kCreate) ? O_CREAT : 0) | ((flags & kReadOnly) ? O_RDONLY : O_RDWR) | ((flags & kTruncate) ? O_TRUNC : 0) | O_CLOEXEC; // Never share on calls to exec(). constexpr mode_t kDefaultMode = S_IRUSR | S_IWUSR; errno = kNoError; const int fd = open(file_path.c_str(), open_flags, kDefaultMode); if (fd < 0) { const int saved_errno = errno; LOGE("%s failed: errno = %s (%d), desc = %s, path = %s", exists ? "Open" : "Create", ErrnoToString(saved_errno), saved_errno, ErrnoToDescription(saved_errno), file_path.c_str()); return nullptr; } // Check that the opened file is a regular file. struct stat st; errno = kNoError; if (fstat(fd, &st) != 0) { const int saved_errno = errno; LOGE("Stat failed: errno = %s (%d), desc = %s", ErrnoToString(saved_errno), saved_errno, ErrnoToDescription(saved_errno)); close(fd); return nullptr; } if (!S_ISREG(st.st_mode)) { LOGE("Not a file: path = %s, st_mode = 0%o (%s)", file_path.c_str(), st.st_mode, StatModeToString(st.st_mode).c_str()); close(fd); return nullptr; } return std::unique_ptr(new AndroidFile(fd, flags, file_path)); } bool FileSystem::Exists(const std::string& path) { return FileUtils::Exists(GetFileNameForIdentifier(path, identifier_)); } bool FileSystem::Remove(const std::string& path) { return FileUtils::Remove(GetFileNameForIdentifier(path, identifier_)); } ssize_t FileSystem::FileSize(const std::string& file_name) { const std::string file_path = GetFileNameForIdentifier(file_name, identifier_); struct stat st; errno = kNoError; if (stat(file_path.c_str(), &st) == 0) { if (st.st_size == 0) { LOGW("File is empty: name = %s", file_name.c_str()); } return st.st_size; } // Else, error occurred. const int saved_errno = errno; LOGE("Stat failed: errno = %s (%d), desc = %s", ErrnoToString(saved_errno), saved_errno, ErrnoToDescription(saved_errno)); return -1; } bool FileSystem::List(const std::string& path, std::vector* filenames) { return FileUtils::List(GetFileNameForIdentifier(path, origin_), filenames); } void FileSystem::set_origin(const std::string& origin) { origin_ = origin; } void FileSystem::set_identifier(const std::string& identifier) { identifier_ = identifier; } } // namespace wvutil