blob: 271a4782a42031a3c9ebbb59dc091c82da8c9061 [file] [log] [blame]
// Copyright 2019 The Chromium OS 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 "arc/obb-mounter/mount_obb_fuse_main.h"
#include <fuse/fuse.h>
#include <time.h>
#include <utility>
#include <base/bind.h>
#include <base/callback.h>
#include <base/files/file.h>
#include <base/files/file_path.h>
#include <base/logging.h>
#include <base/macros.h>
#include <base/optional.h>
#include <base/stl_util.h>
#include <base/strings/string_util.h>
#include <base/strings/utf_string_conversions.h>
#include <base/synchronization/lock.h>
#include <brillo/syslog_logging.h>
#include "arc/obb-mounter/volume.h"
namespace {
const mode_t kFileMode = S_IRUSR | S_IRGRP | S_IFREG;
const mode_t kDirMode = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IFDIR;
fat::Volume* g_volume = nullptr;
using DirectoryEntry = fat::Volume::DirectoryEntry;
// Uses base::Lock to use FileReader in a thread-safe manner.
class FileReaderThreadSafe {
public:
FileReaderThreadSafe(fat::Volume* volume,
int64_t start_cluster,
int64_t file_size)
: reader_(volume, start_cluster, file_size) {}
~FileReaderThreadSafe() = default;
int64_t Read(char* buf, int64_t size, int64_t offset) {
base::AutoLock auto_lock(lock_);
return reader_.Read(buf, size, offset);
}
private:
fat::Volume::FileReader reader_;
base::Lock lock_;
DISALLOW_COPY_AND_ASSIGN(FileReaderThreadSafe);
};
// Converts DirectoryEntry to stat.
void ConvertDirectoryEntryToStat(const DirectoryEntry& entry,
struct stat* stat) {
if (entry.is_directory) {
stat->st_mode = kDirMode;
stat->st_nlink = 2;
} else {
stat->st_mode = kFileMode;
stat->st_nlink = 1;
stat->st_size = entry.file_size;
}
stat->st_mtime = entry.last_modification.ToBaseTime().ToTimeT();
}
// Gets a DirectoryEntry with the given path.
bool GetDirectoryEntry(const base::StringPiece16& path,
DirectoryEntry* out) {
if (path.empty() || path[0] != '/') {
return false;
}
int64_t current_directory_start_sector = g_volume->root_dir_start_sector();
size_t pos = 1;
while (true) {
size_t next_slash = path.find('/', pos);
if (next_slash == base::StringPiece::npos) {
next_slash = path.size();
}
base::StringPiece16 name(path.data() + pos, next_slash - pos);
base::Optional<DirectoryEntry> entry;
if (!g_volume->ReadDirectory(current_directory_start_sector,
base::BindRepeating(
[](const base::StringPiece16& name,
base::Optional<DirectoryEntry>* entry,
const base::StringPiece16& name_in,
const DirectoryEntry& entry_in) {
// TODO(hashimoto): Consider using
// base::i18n::ToLower to be
// case-insensitive for non-ASCII
// characters.
if (base::EqualsCaseInsensitiveASCII(
name, name_in)) {
*entry = entry_in;
return false;
}
return true;
},
name, &entry)) ||
!entry.has_value()) {
return false;
}
pos = next_slash + 1;
if (pos >= path.size()) {
*out = entry.value();
return true;
}
if (!entry->is_directory) {
return false;
}
current_directory_start_sector =
g_volume->GetClusterStartSector(entry->start_cluster);
}
}
int fat_getattr(const char* path, struct stat* stat) {
VLOG(1) << "fat_getattr: " << path;
if (strcmp(path, "/") == 0) {
stat->st_mode = kDirMode;
stat->st_nlink = 2;
return 0;
}
DirectoryEntry entry;
if (!GetDirectoryEntry(base::UTF8ToUTF16(path), &entry)) {
return -ENOENT;
}
ConvertDirectoryEntryToStat(entry, stat);
return 0;
}
int fat_open(const char* path, struct fuse_file_info* fi) {
VLOG(1) << "fat_open: " << path;
if ((fi->flags & O_ACCMODE) != O_RDONLY) {
return -EACCES;
}
DirectoryEntry entry;
if (!GetDirectoryEntry(base::UTF8ToUTF16(path), &entry)) {
return -ENOENT;
}
if (entry.is_directory) {
return -EISDIR;
}
fi->keep_cache = 1;
fi->fh = reinterpret_cast<uint64_t>(
new FileReaderThreadSafe(g_volume, entry.start_cluster, entry.file_size));
return 0;
}
int fat_read(const char* path,
char* buf,
size_t size,
off_t off,
struct fuse_file_info* fi) {
int64_t result =
reinterpret_cast<FileReaderThreadSafe*>(fi->fh)->Read(buf, size, off);
if (result < 0) {
return -EIO;
}
return result;
}
int fat_release(const char* path, struct fuse_file_info* fi) {
delete reinterpret_cast<FileReaderThreadSafe*>(fi->fh);
return 0;
}
int fat_readdir(const char* path,
void* buf,
fuse_fill_dir_t filler,
off_t offset,
struct fuse_file_info* fi) {
VLOG(1) << "fat_readdir: " << path;
filler(buf, ".", nullptr, 0);
filler(buf, "..", nullptr, 0);
int64_t start_sector = 0;
if (strcmp(path, "/") == 0) {
start_sector = g_volume->root_dir_start_sector();
} else {
DirectoryEntry entry;
if (!GetDirectoryEntry(base::UTF8ToUTF16(path), &entry)) {
return -ENOENT;
}
if (!entry.is_directory) {
return -ENOTDIR;
}
start_sector = g_volume->GetClusterStartSector(entry.start_cluster);
}
if (!g_volume->ReadDirectory(
start_sector,
base::BindRepeating(
[](fuse_fill_dir_t filler, void* buf,
const base::StringPiece16& name, const DirectoryEntry& entry) {
filler(buf, base::UTF16ToUTF8(name).c_str(), nullptr, 0);
return true;
},
filler, buf))) {
return -EIO;
}
return 0;
}
} // namespace
int mount_obb_fuse_main(const std::string& file_system_name,
const std::string& obb_filename,
const std::string& mount_path,
const std::string& owner_uid,
const std::string& owner_gid) {
base::File file(base::FilePath(obb_filename),
base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!file.IsValid()) {
LOG(ERROR) << "Failed to open: " << obb_filename;
return 1;
}
fat::Volume volume;
if (!volume.Initialize(std::move(file))) {
LOG(ERROR) << "Failed to initialize volume: " << obb_filename;
return 1;
}
g_volume = &volume;
const std::string mount_options =
std::string("allow_other,default_permissions,uid=") + owner_uid +
",gid=" + owner_gid;
const char* fuse_argv[] = {
file_system_name.c_str(), mount_path.c_str(),
"-f", "-o", mount_options.c_str(),
};
struct fuse_operations fat_ops = {};
#define SET_FAT_OP(name) fat_ops.name = fat_##name
SET_FAT_OP(getattr);
SET_FAT_OP(open);
SET_FAT_OP(read);
SET_FAT_OP(release);
SET_FAT_OP(readdir);
#undef SET_FAT_OP
fuse_main(base::size(fuse_argv), const_cast<char**>(fuse_argv), &fat_ops,
nullptr);
return 0;
}