| /* | |
| * Copyright 2016 The ChromiumOS Authors | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #define FUSE_USE_VERSION 26 | |
| #include <base/command_line.h> | |
| #include <base/files/file_path.h> | |
| #include <base/logging.h> | |
| #include <base/notreached.h> | |
| #include <base/strings/string_split.h> | |
| #include <base/strings/stringprintf.h> | |
| #include <brillo/flag_helper.h> | |
| #include <brillo/syslog_logging.h> | |
| #include <dirent.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <fuse/fuse.h> | |
| #include <fuse/fuse_common.h> | |
| #include <fuse/fuse_lowlevel.h> | |
| #include <linux/fs.h> | |
| #include <linux/limits.h> | |
| #include <signal.h> | |
| #include <stdint.h> | |
| #include <string.h> | |
| #include <sys/file.h> | |
| #include <sys/ioctl.h> | |
| #include <sys/stat.h> | |
| #include <sys/types.h> | |
| #include <sys/vfs.h> | |
| #include <sys/xattr.h> | |
| #include <unistd.h> | |
| #include <fstream> | |
| #include <sstream> | |
| #include <string> | |
| #include "arc/mount-passthrough/mount-passthrough-util.h" | |
| #define USER_NS_SHIFT 655360 | |
| #define CHRONOS_UID 1000 | |
| #define CHRONOS_GID 1000 | |
| #define WRAP_FS_CALL(res) ((res) < 0 ? -errno : 0) | |
| namespace { | |
| // Android UID and GID values are taken from | |
| // system/core/libcutils/include/private/android_filesystem_config.h in the | |
| // Android codebase. | |
| constexpr uid_t kAndroidRootUid = 0; | |
| constexpr uid_t kAndroidMediaRwUid = 1023; | |
| constexpr uid_t kAndroidAppUidStart = 10000; | |
| constexpr uid_t kAndroidAppUidEnd = 19999; | |
| constexpr uid_t kAndroidAppUidStartInCrOS = kAndroidAppUidStart + USER_NS_SHIFT; | |
| constexpr uid_t kAndroidAppUidEndInCrOS = kAndroidAppUidEnd + USER_NS_SHIFT; | |
| constexpr gid_t kAndroidSdcardGid = 1015; | |
| constexpr gid_t kAndroidMediaRwGid = 1023; | |
| constexpr gid_t kAndroidExternalStorageGid = 1077; | |
| constexpr gid_t kAndroidEverybodyGid = 9997; | |
| constexpr char kCrosMountPassthroughFsContext[] = | |
| "u:object_r:cros_mount_passthrough_fs:s0"; | |
| constexpr char kMediaRwDataFileContext[] = "u:object_r:media_rw_data_file:s0"; | |
| struct FusePrivateData { | |
| std::string android_app_access_type; | |
| base::FilePath root; | |
| bool enable_casefold_lookup = false; | |
| }; | |
| // Given android_app_access_type, figure out the source of /storage mount in | |
| // Android. | |
| std::string get_storage_source(const std::string& android_app_access_type) { | |
| std::string storage_source; | |
| // Either full (if no Android permission check is needed), read (for Android | |
| // READ_EXTERNAL_STORAGE permission check), or write (for Android | |
| // WRITE_EXTERNAL_STORAGE_PERMISSION). | |
| if (android_app_access_type == "full") { | |
| return std::string(); | |
| } else if (android_app_access_type == "read") { | |
| return "/runtime/read"; | |
| } else if (android_app_access_type == "write") { | |
| return "/runtime/write"; | |
| } else { | |
| NOTREACHED(); | |
| return "notreached"; | |
| } | |
| } | |
| // Perform the following checks (only for Android apps): | |
| // 1. if android_app_access_type is read, checks if READ_EXTERNAL_STORAGE | |
| // permission is granted | |
| // 2. if android_app_access_type is write, checks if WRITE_EXTERNAL_STORAGE | |
| // permission is granted | |
| // 3. if android_app_access_type is full, performs no check. | |
| // Caveat: This method is implemented based on Android storage permission that | |
| // uses mount namespace. If Android changes their permission in the future | |
| // release, than this method needs to be adjusted. | |
| int check_allowed() { | |
| fuse_context* context = fuse_get_context(); | |
| // We only check Android app process for the Android external storage | |
| // permissions. Other kind of permissions (such as uid/gid) should be checked | |
| // through the standard Linux permission checks. | |
| if (context->uid < kAndroidAppUidStartInCrOS || | |
| context->uid > kAndroidAppUidEndInCrOS) { | |
| return 0; | |
| } | |
| std::string storage_source = | |
| get_storage_source(static_cast<FusePrivateData*>(context->private_data) | |
| ->android_app_access_type); | |
| // No check is required because the android_app_access_type is "full". | |
| if (storage_source.empty()) { | |
| return 0; | |
| } | |
| std::string mountinfo_path = | |
| base::StringPrintf("/proc/%d/mountinfo", context->pid); | |
| std::ifstream in(mountinfo_path); | |
| if (!in.is_open()) { | |
| PLOG(ERROR) << "Failed to open " << mountinfo_path; | |
| return -EPERM; | |
| } | |
| while (!in.eof()) { | |
| std::string line; | |
| std::getline(in, line); | |
| if (in.bad()) { | |
| return -EPERM; | |
| } | |
| std::vector<std::string> tokens = base::SplitString( | |
| line, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); | |
| if (tokens.size() < 5) { | |
| continue; | |
| } | |
| std::string source = tokens[3]; | |
| std::string target = tokens[4]; | |
| if (source == storage_source && target == "/storage") { | |
| return 0; | |
| } | |
| } | |
| return -EPERM; | |
| } | |
| // Converts the given relative path to an absolute path. | |
| base::FilePath GetAbsolutePath(const char* path) { | |
| DCHECK_EQ(path[0], '/'); | |
| FusePrivateData* private_data = | |
| static_cast<FusePrivateData*>(fuse_get_context()->private_data); | |
| const base::FilePath absolute_path = private_data->root.Append(path + 1); | |
| if (private_data->enable_casefold_lookup && | |
| access(absolute_path.value().c_str(), F_OK) != 0) { | |
| // Fall back to casefold lookup only when there is no exact match. | |
| return arc::CasefoldLookup(private_data->root, absolute_path); | |
| } | |
| return absolute_path; | |
| } | |
| int passthrough_create(const char* path, | |
| mode_t mode, | |
| struct fuse_file_info* fi) { | |
| int check_allowed_result = check_allowed(); | |
| if (check_allowed_result < 0) { | |
| return check_allowed_result; | |
| } | |
| // Ignore specified |mode| and always use a fixed mode since we do not allow | |
| // chmod anyway. Note that we explicitly set the umask in main(). | |
| int fd = open(GetAbsolutePath(path).value().c_str(), fi->flags, 0644); | |
| if (fd < 0) { | |
| return -errno; | |
| } | |
| fi->fh = fd; | |
| return 0; | |
| } | |
| int passthrough_fgetattr(const char*, | |
| struct stat* buf, | |
| struct fuse_file_info* fi) { | |
| int fd = static_cast<int>(fi->fh); | |
| // File owner is overridden by uid/gid options passed to fuse. | |
| return WRAP_FS_CALL(fstat(fd, buf)); | |
| } | |
| int passthrough_fsync(const char*, int datasync, struct fuse_file_info* fi) { | |
| int fd = static_cast<int>(fi->fh); | |
| return datasync ? WRAP_FS_CALL(fdatasync(fd)) : WRAP_FS_CALL(fsync(fd)); | |
| } | |
| int passthrough_fsyncdir(const char*, int datasync, struct fuse_file_info* fi) { | |
| DIR* dirp = reinterpret_cast<DIR*>(fi->fh); | |
| int fd = dirfd(dirp); | |
| return datasync ? WRAP_FS_CALL(fdatasync(fd)) : WRAP_FS_CALL(fsync(fd)); | |
| } | |
| int passthrough_ftruncate(const char*, off_t size, struct fuse_file_info* fi) { | |
| int fd = static_cast<int>(fi->fh); | |
| return WRAP_FS_CALL(ftruncate(fd, size)); | |
| } | |
| int passthrough_getattr(const char* path, struct stat* buf) { | |
| // File owner is overridden by uid/gid options passed to fuse. | |
| // Unfortunately, we dont have check_allowed() here because getattr is called | |
| // by kernel VFS during fstat (which receives fd). We couldn't prohibit such | |
| // fd calls to happen, so we need to relax this. | |
| return WRAP_FS_CALL(lstat(GetAbsolutePath(path).value().c_str(), buf)); | |
| } | |
| int passthrough_getxattr(const char* path, | |
| const char* name, | |
| char* value, | |
| size_t size) { | |
| int check_allowed_result = check_allowed(); | |
| if (check_allowed_result < 0) { | |
| return check_allowed_result; | |
| } | |
| return WRAP_FS_CALL( | |
| lgetxattr(GetAbsolutePath(path).value().c_str(), name, value, size)); | |
| } | |
| void* passthrough_init(struct fuse_conn_info* fci) { | |
| fuse_context* context = fuse_get_context(); | |
| #ifdef FUSE_CAP_PARALLEL_DIROPS | |
| fci->want |= FUSE_CAP_PARALLEL_DIROPS; | |
| #endif | |
| return context->private_data; | |
| } | |
| int passthrough_ioctl(const char*, | |
| int cmd, | |
| void* arg, | |
| struct fuse_file_info* fi, | |
| unsigned int flags, | |
| void* data) { | |
| int check_allowed_result = check_allowed(); | |
| if (check_allowed_result < 0) { | |
| return check_allowed_result; | |
| } | |
| // NOTE: We don't check if FUSE_IOCTL_COMPAT is included in the flags because | |
| // currently all supported ioctl commands are not affected by the difference | |
| // between 32-bit and 64-bit. | |
| int fd = static_cast<int>(fi->fh); | |
| switch (static_cast<unsigned int>(cmd)) { | |
| case FS_IOC_FSGETXATTR: | |
| return WRAP_FS_CALL(ioctl(fd, FS_IOC_FSGETXATTR, data)); | |
| case FS_IOC_FSSETXATTR: | |
| return WRAP_FS_CALL(ioctl(fd, FS_IOC_FSSETXATTR, data)); | |
| default: | |
| return -ENOTTY; | |
| } | |
| } | |
| int passthrough_mkdir(const char* path, mode_t mode) { | |
| int check_allowed_result = check_allowed(); | |
| if (check_allowed_result < 0) { | |
| return check_allowed_result; | |
| } | |
| return WRAP_FS_CALL(mkdir(GetAbsolutePath(path).value().c_str(), mode)); | |
| } | |
| int passthrough_open(const char* path, struct fuse_file_info* fi) { | |
| int check_allowed_result = check_allowed(); | |
| if (check_allowed_result < 0) { | |
| return check_allowed_result; | |
| } | |
| int fd = open(GetAbsolutePath(path).value().c_str(), fi->flags); | |
| if (fd < 0) { | |
| return -errno; | |
| } | |
| fi->fh = fd; | |
| return 0; | |
| } | |
| int passthrough_opendir(const char* path, struct fuse_file_info* fi) { | |
| int check_allowed_result = check_allowed(); | |
| if (check_allowed_result < 0) { | |
| return check_allowed_result; | |
| } | |
| DIR* dirp = opendir(GetAbsolutePath(path).value().c_str()); | |
| if (!dirp) { | |
| return -errno; | |
| } | |
| fi->fh = reinterpret_cast<uint64_t>(dirp); | |
| return 0; | |
| } | |
| int passthrough_read( | |
| const char*, char* buf, size_t size, off_t off, struct fuse_file_info* fi) { | |
| int fd = static_cast<int>(fi->fh); | |
| int res = pread(fd, buf, size, off); | |
| if (res < 0) { | |
| return -errno; | |
| } | |
| return res; | |
| } | |
| int passthrough_read_buf(const char*, | |
| struct fuse_bufvec** srcp, | |
| size_t size, | |
| off_t off, | |
| struct fuse_file_info* fi) { | |
| int fd = static_cast<int>(fi->fh); | |
| struct fuse_bufvec* src = | |
| static_cast<struct fuse_bufvec*>(malloc(sizeof(struct fuse_bufvec))); | |
| *src = FUSE_BUFVEC_INIT(size); | |
| src->buf[0].flags = | |
| static_cast<fuse_buf_flags>(FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK); | |
| src->buf[0].fd = fd; | |
| src->buf[0].pos = off; | |
| *srcp = src; | |
| return 0; | |
| } | |
| int passthrough_readdir(const char*, | |
| void* buf, | |
| fuse_fill_dir_t filler, | |
| off_t off, | |
| struct fuse_file_info* fi) { | |
| // TODO(b/202085840): This implementation returns all files at once and thus | |
| // inefficient. Make use of offset and be better to memory. | |
| DIR* dirp = reinterpret_cast<DIR*>(fi->fh); | |
| // Parallel dir operation is enabled, lock the directory to prevent dir rewind | |
| // during the parallel readdir for the same directory. | |
| int fd = dirfd(dirp); | |
| if (flock(fd, LOCK_EX)) { | |
| PLOG(ERROR) << "flock failed"; | |
| return -errno; | |
| } | |
| // Call rewinddir so that all entries are added by filler every time this | |
| // function is called. | |
| rewinddir(dirp); | |
| errno = 0; | |
| for (;;) { | |
| struct dirent* entry = readdir(dirp); | |
| if (entry == nullptr) { | |
| break; | |
| } | |
| // Only IF part of st_mode matters. See fill_dir() in fuse.c. | |
| struct stat stbuf = {}; | |
| stbuf.st_mode = DTTOIF(entry->d_type); | |
| filler(buf, entry->d_name, &stbuf, 0); | |
| } | |
| flock(fd, LOCK_UN); | |
| return -errno; | |
| } | |
| int passthrough_release(const char*, struct fuse_file_info* fi) { | |
| int fd = static_cast<int>(fi->fh); | |
| return WRAP_FS_CALL(close(fd)); | |
| } | |
| int passthrough_releasedir(const char*, struct fuse_file_info* fi) { | |
| DIR* dirp = reinterpret_cast<DIR*>(fi->fh); | |
| return WRAP_FS_CALL(closedir(dirp)); | |
| } | |
| int passthrough_rename(const char* oldpath, const char* newpath) { | |
| int check_allowed_result = check_allowed(); | |
| if (check_allowed_result < 0) { | |
| return check_allowed_result; | |
| } | |
| return WRAP_FS_CALL(rename(GetAbsolutePath(oldpath).value().c_str(), | |
| GetAbsolutePath(newpath).value().c_str())); | |
| } | |
| int passthrough_rmdir(const char* path) { | |
| int check_allowed_result = check_allowed(); | |
| if (check_allowed_result < 0) { | |
| return check_allowed_result; | |
| } | |
| return WRAP_FS_CALL(rmdir(GetAbsolutePath(path).value().c_str())); | |
| } | |
| int passthrough_statfs(const char* path, struct statvfs* buf) { | |
| int check_allowed_result = check_allowed(); | |
| if (check_allowed_result < 0) { | |
| return check_allowed_result; | |
| } | |
| return WRAP_FS_CALL(statvfs(GetAbsolutePath(path).value().c_str(), buf)); | |
| } | |
| int passthrough_truncate(const char* path, off_t size) { | |
| int check_allowed_result = check_allowed(); | |
| if (check_allowed_result < 0) { | |
| return check_allowed_result; | |
| } | |
| return WRAP_FS_CALL(truncate(GetAbsolutePath(path).value().c_str(), size)); | |
| } | |
| int passthrough_unlink(const char* path) { | |
| int check_allowed_result = check_allowed(); | |
| if (check_allowed_result < 0) { | |
| return check_allowed_result; | |
| } | |
| return WRAP_FS_CALL(unlink(GetAbsolutePath(path).value().c_str())); | |
| } | |
| int passthrough_utimens(const char* path, const struct timespec tv[2]) { | |
| int check_allowed_result = check_allowed(); | |
| if (check_allowed_result < 0) { | |
| return check_allowed_result; | |
| } | |
| return WRAP_FS_CALL( | |
| utimensat(AT_FDCWD, GetAbsolutePath(path).value().c_str(), tv, 0)); | |
| } | |
| int passthrough_write(const char*, | |
| const char* buf, | |
| size_t size, | |
| off_t off, | |
| struct fuse_file_info* fi) { | |
| int fd = static_cast<int>(fi->fh); | |
| int res = pwrite(fd, buf, size, off); | |
| if (res < 0) { | |
| return -errno; | |
| } | |
| return res; | |
| } | |
| int passthrough_write_buf(const char*, | |
| struct fuse_bufvec* src, | |
| off_t off, | |
| struct fuse_file_info* fi) { | |
| int fd = static_cast<int>(fi->fh); | |
| struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(src)); | |
| dst.buf[0].flags = | |
| static_cast<fuse_buf_flags>(FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK); | |
| dst.buf[0].fd = fd; | |
| dst.buf[0].pos = off; | |
| return fuse_buf_copy(&dst, src, static_cast<fuse_buf_copy_flags>(0)); | |
| } | |
| void setup_passthrough_ops(struct fuse_operations* passthrough_ops) { | |
| memset(passthrough_ops, 0, sizeof(*passthrough_ops)); | |
| #define FILL_OP(name) passthrough_ops->name = passthrough_##name | |
| FILL_OP(create); | |
| FILL_OP(fgetattr); | |
| FILL_OP(fsync); | |
| FILL_OP(fsyncdir); | |
| FILL_OP(ftruncate); | |
| FILL_OP(getattr); | |
| FILL_OP(getxattr); | |
| FILL_OP(init); | |
| FILL_OP(ioctl); | |
| FILL_OP(mkdir); | |
| FILL_OP(open); | |
| FILL_OP(opendir); | |
| FILL_OP(read); | |
| FILL_OP(read_buf); | |
| FILL_OP(readdir); | |
| FILL_OP(release); | |
| FILL_OP(releasedir); | |
| FILL_OP(rename); | |
| FILL_OP(rmdir); | |
| FILL_OP(statfs); | |
| FILL_OP(truncate); | |
| FILL_OP(unlink); | |
| FILL_OP(utimens); | |
| FILL_OP(write); | |
| FILL_OP(write_buf); | |
| #undef FILL_OP | |
| passthrough_ops->flag_nullpath_ok = 1; | |
| passthrough_ops->flag_nopath = 1; | |
| } | |
| } // namespace | |
| int main(int argc, char** argv) { | |
| DEFINE_string(source, "", "Source path of FUSE mount (required)"); | |
| DEFINE_string(dest, "", "Target path of FUSE mount (required)"); | |
| DEFINE_string(fuse_umask, "", | |
| "Umask to set filesystem permissions in FUSE (required)"); | |
| DEFINE_int32(fuse_uid, -1, "UID set as file owner in FUSE (required)"); | |
| DEFINE_int32(fuse_gid, -1, "GID set as file group in FUSE (required)"); | |
| DEFINE_string( | |
| android_app_access_type, "", | |
| "What type of permission checks should be done for Android apps." | |
| " Must be either full, read, or write (required)"); | |
| DEFINE_bool(use_default_selinux_context, false, | |
| "Use the default \"fuse\" SELinux context"); | |
| DEFINE_int32( | |
| media_provider_uid, -1, | |
| "UID of Android's MediaProvider " | |
| "(required in Android R+ for setting non-default SELinux context)"); | |
| DEFINE_bool(enable_casefold_lookup, false, "Enable casefold lookup"); | |
| // Use "arc-" prefix so that the log is recorded in /var/log/arc.log. | |
| brillo::OpenLog("arc-mount-passthrough", true /*log_pid*/); | |
| brillo::InitLog(brillo::kLogToSyslog | brillo::kLogHeader | | |
| brillo::kLogToStderrIfTty); | |
| brillo::FlagHelper::Init(argc, argv, "mount-passthrough"); | |
| if (FLAGS_source.empty()) { | |
| LOG(ERROR) << "--source must be specified."; | |
| return 1; | |
| } | |
| if (FLAGS_dest.empty()) { | |
| LOG(ERROR) << "--dest must be specified."; | |
| return 1; | |
| } | |
| if (FLAGS_fuse_umask.empty()) { | |
| LOG(ERROR) << "--fuse_umask must be specified."; | |
| return 1; | |
| } | |
| if (USE_ARCPP) { | |
| if (FLAGS_fuse_uid != kAndroidRootUid && | |
| FLAGS_fuse_uid != kAndroidMediaRwUid) { | |
| LOG(ERROR) << "Invalid FUSE file system UID: " << FLAGS_fuse_uid; | |
| return 1; | |
| } | |
| if (FLAGS_fuse_gid != kAndroidSdcardGid && | |
| FLAGS_fuse_gid != kAndroidMediaRwGid && | |
| FLAGS_fuse_gid != kAndroidEverybodyGid) { | |
| LOG(ERROR) << "Invalid FUSE file system GID: " << FLAGS_fuse_gid; | |
| return 1; | |
| } | |
| } else { | |
| if (FLAGS_fuse_uid < kAndroidAppUidStart || | |
| FLAGS_fuse_uid > kAndroidAppUidEnd) { | |
| LOG(ERROR) << "Invalid FUSE file system UID: " << FLAGS_fuse_uid; | |
| return 1; | |
| } | |
| if (FLAGS_fuse_gid != kAndroidExternalStorageGid) { | |
| LOG(ERROR) << "Invalid FUSE file system GID: " << FLAGS_fuse_gid; | |
| return 1; | |
| } | |
| } | |
| if (FLAGS_android_app_access_type.empty()) { | |
| LOG(ERROR) << "--android_app_access_type must be specified."; | |
| return 1; | |
| } | |
| if (FLAGS_android_app_access_type != "full" && | |
| FLAGS_android_app_access_type != "read" && | |
| FLAGS_android_app_access_type != "write") { | |
| LOG(ERROR) << "Invalid android_app_access_type: " | |
| << FLAGS_android_app_access_type | |
| << ". It must be either full, read, or write."; | |
| return 1; | |
| } | |
| if (!USE_ARC_CONTAINER_P && !FLAGS_use_default_selinux_context) { | |
| // MediaProvider UID needs to be specified in R+ to calculate the | |
| // non-default SELinux context. | |
| if (FLAGS_media_provider_uid < kAndroidAppUidStart || | |
| FLAGS_media_provider_uid > kAndroidAppUidEnd) { | |
| LOG(ERROR) << "Invalid MediaProvider UID: " << FLAGS_media_provider_uid; | |
| return 1; | |
| } | |
| } | |
| if (getuid() != CHRONOS_UID) { | |
| LOG(ERROR) << "This daemon must run as chronos user."; | |
| return 1; | |
| } | |
| if (getgid() != CHRONOS_GID) { | |
| LOG(ERROR) << "This daemon must run as chronos group."; | |
| return 1; | |
| } | |
| const uid_t fuse_uid = FLAGS_fuse_uid + USER_NS_SHIFT; | |
| const gid_t fuse_gid = FLAGS_fuse_gid + USER_NS_SHIFT; | |
| struct fuse_operations passthrough_ops; | |
| setup_passthrough_ops(&passthrough_ops); | |
| const std::string fuse_uid_opt("uid=" + std::to_string(fuse_uid)); | |
| const std::string fuse_gid_opt("gid=" + std::to_string(fuse_gid)); | |
| const std::string fuse_umask_opt("umask=" + FLAGS_fuse_umask); | |
| LOG(INFO) << "uid_opt(" << fuse_uid_opt << ") " << "gid_opt(" << fuse_gid_opt | |
| << ") " << "umask_opt(" << fuse_umask_opt << ")"; | |
| std::vector<std::string> fuse_args({ | |
| argv[0], | |
| FLAGS_dest, | |
| "-f", | |
| "-o", | |
| "allow_other", | |
| "-o", | |
| "default_permissions", | |
| // Never cache attr/dentry since our backend storage is not exclusive to | |
| // this process. | |
| "-o", | |
| "attr_timeout=0", | |
| "-o", | |
| "entry_timeout=0", | |
| "-o", | |
| "negative_timeout=0", | |
| "-o", | |
| "ac_attr_timeout=0", | |
| "-o", | |
| "fsname=passthrough", | |
| "-o", | |
| fuse_uid_opt, | |
| "-o", | |
| fuse_gid_opt, | |
| "-o", | |
| "direct_io", | |
| "-o", | |
| fuse_umask_opt, | |
| "-o", | |
| "noexec", | |
| }); | |
| if (!FLAGS_use_default_selinux_context) { | |
| // NOTE: Commas are escaped by "\\" to avoid being processed by lifuse's | |
| // option parsing code. | |
| std::string security_context; | |
| if (USE_ARC_CONTAINER_P) { | |
| // In Android P, the security context of directories under /data/media/0 | |
| // is "u:object_r:media_rw_data_file:s0:c512,c768". | |
| security_context = std::string(kMediaRwDataFileContext) + ":c512\\,c768"; | |
| } else { | |
| // In Android R, the security context of directories under /data/media/0 | |
| // looks like "u:object_r:media_rw_data_file:s0:c64,c256,c512,c768". | |
| // | |
| // Calculate the categories in the same way as set_range_from_level() in | |
| // Android's external/selinux/libselinux/src/android/android_platform.c. | |
| const uid_t media_provider_app_id = | |
| FLAGS_media_provider_uid - kAndroidAppUidStart; | |
| security_context = | |
| std::string(kMediaRwDataFileContext) + | |
| base::StringPrintf(":c%d\\,c%d\\,c512\\,c768", | |
| media_provider_app_id & 0xff, | |
| 256 + ((media_provider_app_id >> 8) & 0xff)); | |
| } | |
| // The context string is quoted using "\"" so that the kernel won't split | |
| // the mount option string at commas. | |
| std::string fuse_context_opt(std::string("context=\"") + security_context + | |
| "\""); | |
| std::string fuse_fscontext_opt(std::string("fscontext=") + | |
| kCrosMountPassthroughFsContext); | |
| fuse_args.push_back("-o"); | |
| fuse_args.push_back(std::move(fuse_context_opt)); | |
| fuse_args.push_back("-o"); | |
| fuse_args.push_back(std::move(fuse_fscontext_opt)); | |
| } | |
| umask(0022); | |
| FusePrivateData private_data; | |
| private_data.android_app_access_type = FLAGS_android_app_access_type; | |
| private_data.root = base::FilePath(FLAGS_source); | |
| private_data.enable_casefold_lookup = FLAGS_enable_casefold_lookup; | |
| std::vector<const char*> fuse_argv(fuse_args.size()); | |
| std::transform( | |
| fuse_args.begin(), fuse_args.end(), std::begin(fuse_argv), | |
| [](const std::string& arg) -> const char* { return arg.c_str(); }); | |
| // The code below does the same thing as fuse_main() except that it ignores | |
| // signals during shutdown to perform clean shutdown. b/183343552 | |
| // TODO(hashimoto): Stop using deprecated libfuse functions b/185322557. | |
| char* mountpoint = nullptr; | |
| int multithreaded = 0; | |
| struct fuse* fuse = fuse_setup( | |
| fuse_argv.size(), const_cast<char**>(fuse_argv.data()), &passthrough_ops, | |
| sizeof(passthrough_ops), &mountpoint, &multithreaded, &private_data); | |
| if (fuse == nullptr) | |
| return 1; | |
| int res = 0; | |
| if (multithreaded) | |
| res = fuse_loop_mt(fuse); | |
| else | |
| res = fuse_loop(fuse); | |
| // The code below does the same thing fuse_teardown() except that it ignores | |
| // signals instead of calling fuse_remove_signal_handlers(). | |
| // Ignore signals after this point. We're already shutting down. | |
| struct sigaction sa = {}; | |
| sigemptyset(&sa.sa_mask); | |
| sa.sa_flags = 0; | |
| sa.sa_handler = SIG_IGN; | |
| sigaction(SIGHUP, &sa, nullptr); | |
| sigaction(SIGINT, &sa, nullptr); | |
| sigaction(SIGTERM, &sa, nullptr); | |
| sigaction(SIGPIPE, &sa, nullptr); | |
| struct fuse_session* se = fuse_get_session(fuse); | |
| struct fuse_chan* ch = fuse_session_next_chan(se, nullptr); | |
| fuse_unmount(mountpoint, ch); | |
| fuse_destroy(fuse); | |
| free(mountpoint); | |
| return res == -1 ? 1 : 0; | |
| } |