| // Copyright 2016 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 <fuse/fuse.h> |
| #include <time.h> |
| |
| #include <base/bind.h> |
| #include <base/files/file_path.h> |
| #include <base/logging.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() {} |
| |
| 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); |
| DirectoryEntry entry; |
| bool found = false; |
| auto entry_finder = [&name, &entry, &found]( |
| 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; |
| found = true; |
| return false; |
| } |
| return true; |
| }; |
| if (!g_volume->ReadDirectory(current_directory_start_sector, |
| base::Bind(&decltype(entry_finder)::operator(), |
| base::Unretained(&entry_finder))) || |
| !found) { |
| return false; |
| } |
| pos = next_slash + 1; |
| if (pos >= path.size()) { |
| *out = entry; |
| 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) { |
| 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) { |
| 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) { |
| 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); |
| } |
| auto filler_adaptor = [&buf, &filler](const base::StringPiece16& name, |
| const DirectoryEntry& entry) { |
| filler(buf, base::UTF16ToUTF8(name).c_str(), nullptr, 0); |
| return true; |
| }; |
| if (!g_volume->ReadDirectory(start_sector, |
| base::Bind(&decltype(filler_adaptor)::operator(), |
| base::Unretained(&filler_adaptor)))) { |
| return -EIO; |
| } |
| return 0; |
| } |
| |
| } // namespace |
| |
| int main(int argc, char** argv) { |
| brillo::InitLog(brillo::kLogToSyslog | brillo::kLogToStderr); |
| if (argc != 5) { |
| LOG(ERROR) << "Usage: " << argv[0] |
| << " obb_filename mount_path owner_uid owner_gid"; |
| return 1; |
| } |
| const char* file_system_name = argv[0]; |
| const char* obb_filename = argv[1]; |
| const char* mount_path = argv[2]; |
| const char* owner_uid = argv[3]; |
| const char* owner_gid = argv[4]; |
| |
| 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, mount_path, "-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(arraysize(fuse_argv), const_cast<char**>(fuse_argv), &fat_ops, |
| nullptr); |
| return 0; |
| } |