blob: ab64b5cc2de717861ff6a55b01349bfa2e8c2f30 [file] [log] [blame]
// 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 <dirent.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <memory>
#include <sstream>
#include "flutter/fml/eintr_wrapper.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/mapping.h"
#include "flutter/fml/unique_fd.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,
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(), 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) {
FML_DLOG(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;
}
FileMapping mapping(temp_file, {FileMapping::Protection::kWrite});
if (mapping.GetMutableMapping() == nullptr ||
data.GetSize() != mapping.GetSize()) {
return false;
}
::memcpy(mapping.GetMutableMapping(), data.GetMapping(), data.GetSize());
if (::msync(mapping.GetMutableMapping(), data.GetSize(), MS_SYNC) != 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()) {
FML_DLOG(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()) {
FML_DLOG(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