blob: 9b16fb4b7e658ad6497715d8f3da6694d3175f86 [file] [log] [blame]
// Copyright (c) 2012 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 "cros-disks/archive_manager.h"
#include <sys/mount.h>
#include <memory>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <brillo/cryptohome.h>
#include "cros-disks/error_logger.h"
#include "cros-disks/fuse_helper.h"
#include "cros-disks/fuse_mounter.h"
#include "cros-disks/metrics.h"
#include "cros-disks/mount_info.h"
#include "cros-disks/mount_options.h"
#include "cros-disks/platform.h"
#include "cros-disks/quote.h"
#include "cros-disks/system_mounter.h"
// TODO(benchan): Remove entire archive manager after deprecating the rar
// support (see chromium:707327).
namespace cros_disks {
namespace {
// Mapping from a base path to its corresponding path inside the AVFS mount.
struct AVFSPathMapping {
const char* const base_path;
const char* const avfs_path;
};
const char kAVFSMountGroup[] = "chronos-access";
const char kAVFSMountUser[] = "avfs";
// TODO(wad,benchan): Revisit the location of policy files once more system
// daemons are sandboxed with seccomp filters.
const char kAVFSSeccompFilterPolicyFile[] =
"/usr/share/policy/avfsd-seccomp.policy";
const char kAVFSMountProgram[] = "/usr/bin/avfsd";
const char kAVFSRootDirectory[] = "/run/avfsroot";
const mode_t kAVFSDirectoryPermissions = 0770; // rwx by avfs user and group
const char kAVFSLogFile[] = "/run/avfsroot/avfs.log";
const char kAVFSMediaDirectory[] = "/run/avfsroot/media";
const char kAVFSUsersDirectory[] = "/run/avfsroot/users";
const char kMediaDirectory[] = "/media";
const char kUserRootDirectory[] = "/home/chronos";
const AVFSPathMapping kAVFSPathMapping[] = {
{kMediaDirectory, kAVFSMediaDirectory},
{kUserRootDirectory, kAVFSUsersDirectory},
};
const char kAVFSModulesOption[] = "modules=subdir";
const char kAVFSSubdirOptionPrefix[] = "subdir=";
} // namespace
ArchiveManager::ArchiveManager(const std::string& mount_root,
Platform* platform,
Metrics* metrics,
brillo::ProcessReaper* process_reaper)
: MountManager(mount_root, platform, metrics, process_reaper),
avfs_started_(false) {}
ArchiveManager::~ArchiveManager() {
// StopAVFS() unmounts all mounted archives as well as AVFS mount points.
StopAVFS();
}
bool ArchiveManager::Initialize() {
RegisterDefaultFileExtensions();
return MountManager::Initialize();
}
bool ArchiveManager::StopSession() {
return StopAVFS();
}
bool ArchiveManager::CanMount(const std::string& source_path) const {
// The following paths can be mounted:
// /home/chronos/u-<user-id>/Downloads/...<file>
// /home/chronos/u-<user-id>/MyFiles/...<file>
// /home/chronos/u-<user-id>/GCache/...<file>
// /media/<dir>/<dir>/...<file>
//
base::FilePath file_path(source_path);
if (base::FilePath(kUserRootDirectory).IsParent(file_path)) {
std::vector<std::string> components;
file_path.StripTrailingSeparators().GetComponents(&components);
// The file path of an archive file under a user's Downloads or GCache
// directory path is split into the following components:
// '/', 'home', 'chronos', 'u-<userid>', 'Downloads', ..., 'doc.zip'
// '/', 'home', 'chronos', 'u-<userid>', 'GCache', ..., 'doc.zip'
if (components.size() > 5 &&
(base::StartsWith(components[3], "u-",
base::CompareCase::INSENSITIVE_ASCII) &&
brillo::cryptohome::home::IsSanitizedUserName(
components[3].substr(2))) &&
(components[4] == "Downloads" || components[4] == "GCache" ||
components[4] == "MyFiles")) {
return true;
}
}
if (base::FilePath(kMediaDirectory).IsParent(file_path)) {
std::vector<std::string> components;
file_path.StripTrailingSeparators().GetComponents(&components);
// A mount directory is always created under /media/<sub type>/<mount dir>,
// so the file path of an archive file under a mount directory is split
// into more than 4 components:
// '/', 'media', 'removable', 'usb', ..., 'doc.zip'
if (components.size() > 4)
return true;
}
return false;
}
MountErrorType ArchiveManager::DoMount(const std::string& source_path,
const std::string& source_format,
const std::vector<std::string>& options,
const std::string& mount_path,
MountOptions* applied_options) {
CHECK(!source_path.empty()) << "Invalid source path argument";
CHECK(!mount_path.empty()) << "Invalid mount path argument";
std::string extension = GetFileExtension(source_format);
if (extension.empty())
extension = GetFileExtension(source_path);
metrics()->RecordArchiveType(extension);
std::string avfs_path = GetAVFSPath(source_path, extension);
if (avfs_path.empty()) {
LOG(ERROR) << "Path " << quote(source_path)
<< " is not a supported archive";
return MOUNT_ERROR_UNSUPPORTED_ARCHIVE;
}
MountErrorType avfs_start_error = StartAVFS();
if (avfs_start_error != MOUNT_ERROR_NONE) {
LOG(ERROR) << "Failed to start AVFS mounts: " << avfs_start_error;
return avfs_start_error;
}
// Perform a bind mount from the archive path under the AVFS mount
// to /media/archive/<archive name>.
std::vector<std::string> extended_options = options;
extended_options.push_back(MountOptions::kOptionBind);
MountOptions mount_options;
mount_options.WhitelistOption(MountOptions::kOptionNoSymFollow);
mount_options.Initialize(extended_options, false, "", "");
MounterCompat mounter(std::make_unique<SystemMounter>("", platform()),
avfs_path, base::FilePath(mount_path), mount_options);
MountErrorType error_type = mounter.Mount();
if (error_type != MOUNT_ERROR_NONE) {
return error_type;
}
AddMountVirtualPath(mount_path, avfs_path);
return MOUNT_ERROR_NONE;
}
MountErrorType ArchiveManager::DoUnmount(const std::string& path) {
CHECK(!path.empty()) << "Invalid path argument";
// Since all archives are read-only, always use lazy unmount.
const MountErrorType error = platform()->Unmount(path, MNT_DETACH);
if (error != MOUNT_ERROR_NONE) {
return error;
}
// DoUnmount() is always called with |path| being the mount path.
RemoveMountVirtualPath(path);
return MOUNT_ERROR_NONE;
}
std::string ArchiveManager::SuggestMountPath(
const std::string& source_path) const {
// Use the archive name to name the mount directory.
base::FilePath base_name = base::FilePath(source_path).BaseName();
return base::FilePath(mount_root()).Append(base_name).value();
}
void ArchiveManager::RegisterDefaultFileExtensions() {
// Different archive formats can now be supported via an extension (built-in
// or installed by user) using the chrome.fileSystemProvider API. Thus, zip,
// tar, and gzip/bzip2 compressed tar formats are no longer supported here.
// rar is still supported until there is a replacement using a built-in
// extension.
RegisterFileExtension("rar", "#urar");
}
void ArchiveManager::RegisterFileExtension(const std::string& extension,
const std::string& avfs_handler) {
extension_handlers_[extension] = avfs_handler;
}
std::string ArchiveManager::GetFileExtension(const std::string& path) const {
base::FilePath file_path(path);
std::string extension = file_path.Extension();
if (!extension.empty()) {
// Strip the leading dot and convert the extension to lower case.
extension.erase(0, 1);
extension = base::ToLowerASCII(extension);
}
return extension;
}
std::string ArchiveManager::GetAVFSPath(const std::string& path,
const std::string& extension) const {
// When mounting an archive within another mounted archive, we need to
// resolve the virtual path of the inner archive to the "unfolded"
// form within the AVFS mount, such as
// "/run/avfsroot/media/layer2.zip#/test/doc/layer1.zip#"
// instead of the "nested" form, such as
// "/run/avfsroot/media/archive/layer2.zip/test/doc/layer1.zip#"
// where "/media/archive/layer2.zip" is a mount point to the virtual
// path "/run/avfsroot/media/layer2.zip#".
//
// Mounting the inner archive using the nested form may cause problems
// reading files from the inner archive. To avoid that, we first try to
// find the longest parent path of |path| that is an existing mount
// point to a virtual path within the AVFS mount. If such a parent path
// is found, we construct the virtual path of |path| within the AVFS
// mount as a subpath of its parent's virtual path.
//
// e.g. Given |path| is "/media/archive/layer2.zip/test/doc/layer1.zip",
// and "/media/archive/layer2.zip" is a mount point to the virtual
// path "/run/avfsroot/media/layer2.zip#" within the AVFS mount.
// The following code should return the virtual path of |path| as
// "/run/avfsroot/media/layer2.zip#/test/doc/layer1.zip#".
std::map<std::string, std::string>::const_iterator handler_iterator =
extension_handlers_.find(extension);
if (handler_iterator == extension_handlers_.end())
return std::string();
base::FilePath file_path(path);
base::FilePath current_path = file_path.DirName();
base::FilePath parent_path = current_path.DirName();
while (current_path != parent_path) { // Search till the root
VirtualPathMap::const_iterator path_iterator =
virtual_paths_.find(current_path.value());
if (path_iterator != virtual_paths_.end()) {
base::FilePath avfs_path(path_iterator->second);
// As current_path is a parent of file_path, AppendRelativePath()
// should return true here.
CHECK(current_path.AppendRelativePath(file_path, &avfs_path));
return avfs_path.value() + handler_iterator->second;
}
current_path = parent_path;
parent_path = parent_path.DirName();
}
// If no parent path is a mounted via AVFS, we are not mounting a nested
// archive and thus construct the virtual path of the archive based on a
// corresponding AVFS mount path.
for (const auto& mapping : kAVFSPathMapping) {
base::FilePath base_path(mapping.base_path);
base::FilePath avfs_path(mapping.avfs_path);
if (base_path.AppendRelativePath(file_path, &avfs_path)) {
return avfs_path.value() + handler_iterator->second;
}
}
return std::string();
}
MountErrorType ArchiveManager::StartAVFS() {
if (avfs_started_)
return MOUNT_ERROR_NONE;
// As cros-disks is now a non-privileged process, the directory tree under
// |kAVFSRootDirectory| is created by the pre-start script of the cros-disks
// upstart job. We simply check to make sure the directory tree is created
// with the expected file ownership and permissions.
uid_t avfs_user_id, dir_user_id;
gid_t avfs_group_id, dir_group_id;
mode_t dir_mode;
if (!platform()->PathExists(kAVFSRootDirectory) ||
!platform()->GetUserAndGroupId(kAVFSMountUser, &avfs_user_id,
&avfs_group_id) ||
!platform()->GetOwnership(kAVFSRootDirectory, &dir_user_id,
&dir_group_id) ||
!platform()->GetPermissions(kAVFSRootDirectory, &dir_mode) ||
(dir_user_id != avfs_user_id) || (dir_group_id != avfs_group_id) ||
((dir_mode & 07777) != kAVFSDirectoryPermissions)) {
LOG(ERROR) << kAVFSRootDirectory << " isn't created properly";
return MOUNT_ERROR_INTERNAL;
}
// Set the AVFS_LOGFILE environment variable so that the AVFS daemon
// writes log messages to a file instead of syslog. Otherwise, writing
// to syslog may trigger the socket/connect/send system calls, which are
// disabled by the seccomp filters policy file. This only affects the
// child processes spawned by cros-disks and does not persist after
// cros-disks restarts.
setenv("AVFS_LOGFILE", kAVFSLogFile, 1);
avfs_started_ = true;
for (const auto& mapping : kAVFSPathMapping) {
MountErrorType mount_error =
MountAVFSPath(mapping.base_path, mapping.avfs_path);
if (mount_error != MOUNT_ERROR_NONE) {
LOG(ERROR) << "Cannot mount AVFS path " << quote(mapping.avfs_path)
<< ": " << mount_error;
StopAVFS();
return mount_error;
}
}
return MOUNT_ERROR_NONE;
}
bool ArchiveManager::StopAVFS() {
if (!avfs_started_)
return true;
avfs_started_ = false;
// Unmounts all mounted archives before unmounting AVFS mounts.
bool all_unmounted = UnmountAll();
for (const auto& mapping : kAVFSPathMapping) {
const std::string& path = mapping.avfs_path;
if (!platform()->PathExists(path))
continue;
const MountErrorType error = platform()->Unmount(path, 0);
if (error != MOUNT_ERROR_NONE)
all_unmounted = false;
}
return all_unmounted;
}
bool ArchiveManager::CreateMountDirectory(const std::string& path) const {
// If an empty directory was left behind for any reason, remove it first.
if (platform()->DirectoryExists(path) &&
!platform()->RemoveEmptyDirectory(path)) {
return false;
}
// Create directory. This works because /run/avfsroot is owned by avfs:avfs,
// and cros-disks is in the avfs group.
if (!platform()->CreateDirectory(path)) {
return false;
}
uid_t uid;
gid_t gid;
// Set directory's permissions and owner.
if (!platform()->SetPermissions(path, kAVFSDirectoryPermissions) ||
!platform()->GetUserAndGroupId(kAVFSMountUser, &uid, &gid) ||
!platform()->SetOwnership(path, uid, gid)) {
// Remove directory in case of error.
platform()->RemoveEmptyDirectory(path);
return false;
}
return true;
}
MountErrorType ArchiveManager::MountAVFSPath(
const std::string& base_path, const std::string& avfs_path) const {
MountInfo mount_info;
if (!mount_info.RetrieveFromCurrentProcess())
return MOUNT_ERROR_INTERNAL;
if (mount_info.HasMountPath(avfs_path)) {
LOG(WARNING) << "Path " << quote(avfs_path) << " is already mounted";
// Not using MOUNT_ERROR_PATH_ALREADY_MOUNTED here because that implies an
// error on the user-requested mount. The error here is for the avfsd
// daemon.
return MOUNT_ERROR_INTERNAL;
}
// Create avfs_path with the right uid, gid and permissions.
if (!CreateMountDirectory(avfs_path)) {
LOG(ERROR) << "Cannot create mount directory " << quote(avfs_path);
return MOUNT_ERROR_INTERNAL;
}
MountOptions mount_options;
mount_options.WhitelistOption(FUSEHelper::kOptionAllowOther);
mount_options.WhitelistOption(kAVFSModulesOption);
mount_options.WhitelistOptionPrefix(kAVFSSubdirOptionPrefix);
std::vector<std::string> options = {
MountOptions::kOptionReadOnly,
kAVFSModulesOption,
kAVFSSubdirOptionPrefix + base_path,
};
mount_options.Initialize(options, false, "", "");
std::unique_ptr<FUSEMounter> fuse_mounter = std::make_unique<FUSEMounter>(
"", avfs_path, "avfs", mount_options, platform(), process_reaper(),
kAVFSMountProgram, kAVFSMountUser, kAVFSSeccompFilterPolicyFile,
std::vector<FUSEMounter::BindPath>({
// This needs to be recursively bind mounted so that any external
// media (mounted under /media) or user (under /home/chronos) mounts
// are visible to AVFS.
{base_path, false /* writable*/, true /* recursive */},
}),
false /* permit_network_access */, kAVFSMountGroup);
MountErrorType mount_error = fuse_mounter->Mount();
if (mount_error != MOUNT_ERROR_NONE) {
return mount_error;
}
if (!mount_info.RetrieveFromCurrentProcess() ||
!mount_info.HasMountPath(avfs_path)) {
LOG(WARNING) << "Cannot mount " << quote(base_path) << " to "
<< quote(avfs_path) << " via AVFS";
return MOUNT_ERROR_INTERNAL;
}
LOG(INFO) << "Mounted " << quote(base_path) << " to " << quote(avfs_path)
<< " via AVFS";
return MOUNT_ERROR_NONE;
}
void ArchiveManager::AddMountVirtualPath(const std::string& mount_path,
const std::string& virtual_path) {
virtual_paths_[mount_path] = virtual_path;
}
void ArchiveManager::RemoveMountVirtualPath(const std::string& mount_path) {
virtual_paths_.erase(mount_path);
}
} // namespace cros_disks