// Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "flutter/fml/file.h" #include #include #include #include #include #include #include #include #include "flutter/fml/eintr_wrapper.h" #include "flutter/fml/mapping.h" #include "spdlog/spdlog.h" namespace fml { std::string CreateTemporaryDirectory() { char directory_name[] = "/tmp/flutter_XXXXXXXX"; auto* result = ::mkdtemp(directory_name); if (result == nullptr) { return ""; } return {result}; } static int ToPosixAccessFlags(FilePermission permission) { int flags = 0; switch (permission) { case FilePermission::kRead: flags |= O_RDONLY; // read only break; case FilePermission::kWrite: flags |= O_WRONLY; // write only break; case FilePermission::kReadWrite: flags |= O_RDWR; // read-write break; } return flags; } static int ToPosixCreateModeFlags(FilePermission permission) { int mode = 0; switch (permission) { case FilePermission::kRead: mode |= S_IRUSR; break; case FilePermission::kWrite: mode |= S_IWUSR; break; case FilePermission::kReadWrite: mode |= S_IRUSR | S_IWUSR; break; } return mode; } fml::UniqueFD OpenFile(const char* path, bool create_if_necessary, FilePermission permission) { return OpenFile(fml::UniqueFD{AT_FDCWD}, path, create_if_necessary, permission); } fml::UniqueFD OpenFile(const fml::UniqueFD& base_directory, const char* path, bool create_if_necessary, FilePermission permission) { if (path == nullptr) { return {}; } int flags = 0; int mode = 0; if (create_if_necessary && !FileExists(base_directory, path)) { flags = ToPosixAccessFlags(permission) | O_CREAT | O_TRUNC; mode = ToPosixCreateModeFlags(permission); } else { flags = ToPosixAccessFlags(permission); mode = 0; // Not creating since it already exists. } return fml::UniqueFD{ FML_HANDLE_EINTR(::openat(base_directory.get(), path, flags, mode))}; } fml::UniqueFD OpenDirectory(const char* path, bool create_if_necessary, FilePermission permission) { return OpenDirectory(fml::UniqueFD{AT_FDCWD}, path, create_if_necessary, permission); } fml::UniqueFD OpenDirectory(const fml::UniqueFD& base_directory, const char* path, bool create_if_necessary, FilePermission permission) { if (path == nullptr) { return {}; } if (create_if_necessary && !FileExists(base_directory, path)) { if (::mkdirat(base_directory.get(), path, static_cast<__mode_t>(ToPosixCreateModeFlags(permission) | S_IXUSR)) != 0) { return {}; } } return fml::UniqueFD{FML_HANDLE_EINTR( ::openat(base_directory.get(), path, O_RDONLY | O_DIRECTORY))}; } fml::UniqueFD Duplicate(fml::UniqueFD::element_type descriptor) { return fml::UniqueFD{FML_HANDLE_EINTR(::dup(descriptor))}; } bool IsDirectory(const fml::UniqueFD& directory) { if (!directory.is_valid()) { return false; } struct stat stat_result = {}; if (::fstat(directory.get(), &stat_result) != 0) { return false; } return S_ISDIR(stat_result.st_mode); } bool IsDirectory(const fml::UniqueFD& base_directory, const char* path) { UniqueFD file = OpenFileReadOnly(base_directory, path); return (file.is_valid() && IsDirectory(file)); } bool IsFile(const std::string& path) { struct stat buf{}; if (stat(path.c_str(), &buf) != 0) { return false; } return S_ISREG(buf.st_mode); } bool TruncateFile(const fml::UniqueFD& file, size_t size) { if (!file.is_valid()) { return false; } return ::ftruncate(file.get(), static_cast(size)) == 0; } bool UnlinkDirectory(const char* path) { return UnlinkDirectory(fml::UniqueFD{AT_FDCWD}, path); } bool UnlinkDirectory(const fml::UniqueFD& base_directory, const char* path) { return ::unlinkat(base_directory.get(), path, AT_REMOVEDIR) == 0; } bool UnlinkFile(const char* path) { return UnlinkFile(fml::UniqueFD{AT_FDCWD}, path); } bool UnlinkFile(const fml::UniqueFD& base_directory, const char* path) { int code = ::unlinkat(base_directory.get(), path, 0); if (code != 0) { SPDLOG_ERROR(strerror(errno)); } return code == 0; } bool FileExists(const fml::UniqueFD& base_directory, const char* path) { if (!base_directory.is_valid()) { return false; } return ::faccessat(base_directory.get(), path, F_OK, 0) == 0; } bool WriteAtomically(const fml::UniqueFD& base_directory, const char* file_name, const Mapping& data) { if (file_name == nullptr || data.GetMapping() == nullptr) { return false; } std::stringstream stream; stream << file_name << ".temp"; const auto temp_file_name = stream.str(); auto temp_file = OpenFile(base_directory, temp_file_name.c_str(), true, FilePermission::kReadWrite); if (!temp_file.is_valid()) { return false; } if (!TruncateFile(temp_file, data.GetSize())) { return false; } auto remaining = static_cast(data.GetSize()); ssize_t written = 0; ssize_t offset = 0; while (remaining > 0) { written = FML_HANDLE_EINTR( ::write(temp_file.get(), data.GetMapping() + offset, static_cast(remaining))); if (written == -1) { return false; } remaining -= written; offset += written; } if (::fsync(temp_file.get()) != 0) { return false; } return ::renameat(base_directory.get(), temp_file_name.c_str(), base_directory.get(), file_name) == 0; } bool VisitFiles(const fml::UniqueFD& directory, const FileVisitor& visitor) { fml::UniqueFD dup_fd(dup(directory.get())); if (!dup_fd.is_valid()) { SPDLOG_ERROR("Can't dup the directory fd. Error: {}", strerror(errno)); return true; // continue to visit other files } fml::UniqueDir dir(::fdopendir(dup_fd.get())); if (!dir.is_valid()) { SPDLOG_ERROR("Can't open the directory. Error: {}", strerror(errno)); return true; // continue to visit other files } // The directory fd will be closed by `closedir`. (void)dup_fd.release(); // Without `rewinddir`, `readir` will directly return NULL (end of dir is // reached) after a previuos `VisitFiles` call for the same `const // fml::UniqueFd& directory`. rewinddir(dir.get()); while (dirent* ent = readdir(dir.get())) { std::string filename = ent->d_name; if (filename != "." && filename != "..") { if (!visitor(directory, filename)) { return false; } } } return true; } } // namespace fml