c7c4f5a902
It's essential that files created by vold get the correct SELinux labels, so make sure to check for errors when setting them. ENOENT (no label defined) is expected on some files such as /mnt/appfuse/*, so allow ENOENT but log a DEBUG message. This will help debug b/269567270. This is not a fix for b/269567270. Bug: 269567270 Test: Created user and checked SELinux labels of user's directories Test: atest CtsBlobStoreHostTestCases Change-Id: Ife005bdd896952653943c57336deb33456f7c5d8
1795 lines
58 KiB
C++
1795 lines
58 KiB
C++
/*
|
|
* Copyright (C) 2015 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "Utils.h"
|
|
|
|
#include "Process.h"
|
|
#include "sehandle.h"
|
|
|
|
#include <android-base/chrono_utils.h>
|
|
#include <android-base/file.h>
|
|
#include <android-base/logging.h>
|
|
#include <android-base/properties.h>
|
|
#include <android-base/scopeguard.h>
|
|
#include <android-base/stringprintf.h>
|
|
#include <android-base/strings.h>
|
|
#include <android-base/unique_fd.h>
|
|
#include <cutils/fs.h>
|
|
#include <logwrap/logwrap.h>
|
|
#include <private/android_filesystem_config.h>
|
|
#include <private/android_projectid_config.h>
|
|
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/posix_acl.h>
|
|
#include <linux/posix_acl_xattr.h>
|
|
#include <mntent.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/statvfs.h>
|
|
#include <sys/sysmacros.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/xattr.h>
|
|
#include <unistd.h>
|
|
|
|
#include <filesystem>
|
|
#include <list>
|
|
#include <mutex>
|
|
#include <regex>
|
|
#include <thread>
|
|
|
|
#ifndef UMOUNT_NOFOLLOW
|
|
#define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */
|
|
#endif
|
|
|
|
using namespace std::chrono_literals;
|
|
using android::base::EndsWith;
|
|
using android::base::ReadFileToString;
|
|
using android::base::StartsWith;
|
|
using android::base::StringPrintf;
|
|
using android::base::unique_fd;
|
|
|
|
namespace android {
|
|
namespace vold {
|
|
|
|
char* sBlkidContext = nullptr;
|
|
char* sBlkidUntrustedContext = nullptr;
|
|
char* sFsckContext = nullptr;
|
|
char* sFsckUntrustedContext = nullptr;
|
|
|
|
bool sSleepOnUnmount = true;
|
|
|
|
static const char* kBlkidPath = "/system/bin/blkid";
|
|
static const char* kKeyPath = "/data/misc/vold";
|
|
|
|
static const char* kProcDevices = "/proc/devices";
|
|
static const char* kProcFilesystems = "/proc/filesystems";
|
|
|
|
static const char* kAndroidDir = "/Android/";
|
|
static const char* kAppDataDir = "/Android/data/";
|
|
static const char* kAppMediaDir = "/Android/media/";
|
|
static const char* kAppObbDir = "/Android/obb/";
|
|
|
|
static const char* kMediaProviderCtx = "u:r:mediaprovider:";
|
|
static const char* kMediaProviderAppCtx = "u:r:mediaprovider_app:";
|
|
|
|
// Lock used to protect process-level SELinux changes from racing with each
|
|
// other between multiple threads.
|
|
static std::mutex kSecurityLock;
|
|
|
|
std::string GetFuseMountPathForUser(userid_t user_id, const std::string& relative_upper_path) {
|
|
return StringPrintf("/mnt/user/%d/%s", user_id, relative_upper_path.c_str());
|
|
}
|
|
|
|
status_t CreateDeviceNode(const std::string& path, dev_t dev) {
|
|
std::lock_guard<std::mutex> lock(kSecurityLock);
|
|
const char* cpath = path.c_str();
|
|
auto clearfscreatecon = android::base::make_scope_guard([] { setfscreatecon(nullptr); });
|
|
auto secontext = std::unique_ptr<char, void (*)(char*)>(nullptr, freecon);
|
|
char* tmp_secontext;
|
|
|
|
if (selabel_lookup(sehandle, &tmp_secontext, cpath, S_IFBLK) == 0) {
|
|
secontext.reset(tmp_secontext);
|
|
if (setfscreatecon(secontext.get()) != 0) {
|
|
LOG(ERROR) << "Failed to setfscreatecon for device node " << path;
|
|
return -EINVAL;
|
|
}
|
|
} else if (errno == ENOENT) {
|
|
LOG(DEBUG) << "No selabel defined for device node " << path;
|
|
} else {
|
|
PLOG(ERROR) << "Failed to look up selabel for device node " << path;
|
|
return -errno;
|
|
}
|
|
|
|
mode_t mode = 0660 | S_IFBLK;
|
|
if (mknod(cpath, mode, dev) < 0) {
|
|
if (errno != EEXIST) {
|
|
PLOG(ERROR) << "Failed to create device node for " << major(dev) << ":" << minor(dev)
|
|
<< " at " << path;
|
|
return -errno;
|
|
}
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
status_t DestroyDeviceNode(const std::string& path) {
|
|
const char* cpath = path.c_str();
|
|
if (TEMP_FAILURE_RETRY(unlink(cpath))) {
|
|
return -errno;
|
|
} else {
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
// Sets a default ACL on the directory.
|
|
status_t SetDefaultAcl(const std::string& path, mode_t mode, uid_t uid, gid_t gid,
|
|
std::vector<gid_t> additionalGids) {
|
|
if (IsSdcardfsUsed()) {
|
|
// sdcardfs magically takes care of this
|
|
return OK;
|
|
}
|
|
|
|
size_t num_entries = 3 + (additionalGids.size() > 0 ? additionalGids.size() + 1 : 0);
|
|
size_t size = sizeof(posix_acl_xattr_header) + num_entries * sizeof(posix_acl_xattr_entry);
|
|
auto buf = std::make_unique<uint8_t[]>(size);
|
|
|
|
posix_acl_xattr_header* acl_header = reinterpret_cast<posix_acl_xattr_header*>(buf.get());
|
|
acl_header->a_version = POSIX_ACL_XATTR_VERSION;
|
|
|
|
posix_acl_xattr_entry* entry =
|
|
reinterpret_cast<posix_acl_xattr_entry*>(buf.get() + sizeof(posix_acl_xattr_header));
|
|
|
|
int tag_index = 0;
|
|
|
|
entry[tag_index].e_tag = ACL_USER_OBJ;
|
|
// The existing mode_t mask has the ACL in the lower 9 bits:
|
|
// the lowest 3 for "other", the next 3 the group, the next 3 for the owner
|
|
// Use the mode_t masks to get these bits out, and shift them to get the
|
|
// correct value per entity.
|
|
//
|
|
// Eg if mode_t = 0700, rwx for the owner, then & S_IRWXU >> 6 results in 7
|
|
entry[tag_index].e_perm = (mode & S_IRWXU) >> 6;
|
|
entry[tag_index].e_id = uid;
|
|
tag_index++;
|
|
|
|
entry[tag_index].e_tag = ACL_GROUP_OBJ;
|
|
entry[tag_index].e_perm = (mode & S_IRWXG) >> 3;
|
|
entry[tag_index].e_id = gid;
|
|
tag_index++;
|
|
|
|
if (additionalGids.size() > 0) {
|
|
for (gid_t additional_gid : additionalGids) {
|
|
entry[tag_index].e_tag = ACL_GROUP;
|
|
entry[tag_index].e_perm = (mode & S_IRWXG) >> 3;
|
|
entry[tag_index].e_id = additional_gid;
|
|
tag_index++;
|
|
}
|
|
|
|
entry[tag_index].e_tag = ACL_MASK;
|
|
entry[tag_index].e_perm = (mode & S_IRWXG) >> 3;
|
|
entry[tag_index].e_id = 0;
|
|
tag_index++;
|
|
}
|
|
|
|
entry[tag_index].e_tag = ACL_OTHER;
|
|
entry[tag_index].e_perm = mode & S_IRWXO;
|
|
entry[tag_index].e_id = 0;
|
|
|
|
int ret = setxattr(path.c_str(), XATTR_NAME_POSIX_ACL_DEFAULT, acl_header, size, 0);
|
|
|
|
if (ret != 0) {
|
|
PLOG(ERROR) << "Failed to set default ACL on " << path;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int SetQuotaInherit(const std::string& path) {
|
|
unsigned int flags;
|
|
|
|
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
|
|
if (fd == -1) {
|
|
PLOG(ERROR) << "Failed to open " << path << " to set project id inheritance.";
|
|
return -1;
|
|
}
|
|
|
|
int ret = ioctl(fd, FS_IOC_GETFLAGS, &flags);
|
|
if (ret == -1) {
|
|
PLOG(ERROR) << "Failed to get flags for " << path << " to set project id inheritance.";
|
|
return ret;
|
|
}
|
|
|
|
flags |= FS_PROJINHERIT_FL;
|
|
|
|
ret = ioctl(fd, FS_IOC_SETFLAGS, &flags);
|
|
if (ret == -1) {
|
|
PLOG(ERROR) << "Failed to set flags for " << path << " to set project id inheritance.";
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int SetQuotaProjectId(const std::string& path, long projectId) {
|
|
struct fsxattr fsx;
|
|
|
|
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
|
|
if (fd == -1) {
|
|
PLOG(ERROR) << "Failed to open " << path << " to set project id.";
|
|
return -1;
|
|
}
|
|
|
|
int ret = ioctl(fd, FS_IOC_FSGETXATTR, &fsx);
|
|
if (ret == -1) {
|
|
PLOG(ERROR) << "Failed to get extended attributes for " << path << " to get project id.";
|
|
return ret;
|
|
}
|
|
|
|
fsx.fsx_projid = projectId;
|
|
ret = ioctl(fd, FS_IOC_FSSETXATTR, &fsx);
|
|
if (ret == -1) {
|
|
PLOG(ERROR) << "Failed to set project id on " << path;
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int PrepareDirWithProjectId(const std::string& path, mode_t mode, uid_t uid, gid_t gid,
|
|
long projectId) {
|
|
int ret = fs_prepare_dir(path.c_str(), mode, uid, gid);
|
|
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (!IsSdcardfsUsed()) {
|
|
ret = SetQuotaProjectId(path, projectId);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int FixupAppDir(const std::string& path, mode_t mode, uid_t uid, gid_t gid, long projectId) {
|
|
namespace fs = std::filesystem;
|
|
|
|
// Setup the directory itself correctly
|
|
int ret = PrepareDirWithProjectId(path, mode, uid, gid, projectId);
|
|
if (ret != OK) {
|
|
return ret;
|
|
}
|
|
|
|
// Fixup all of its file entries
|
|
for (const auto& itEntry : fs::directory_iterator(path)) {
|
|
ret = lchown(itEntry.path().c_str(), uid, gid);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = chmod(itEntry.path().c_str(), mode);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (!IsSdcardfsUsed()) {
|
|
ret = SetQuotaProjectId(itEntry.path(), projectId);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
int PrepareAppDirFromRoot(const std::string& path, const std::string& root, int appUid,
|
|
bool fixupExisting) {
|
|
long projectId;
|
|
size_t pos;
|
|
int ret = 0;
|
|
bool sdcardfsSupport = IsSdcardfsUsed();
|
|
|
|
// Make sure the Android/ directories exist and are setup correctly
|
|
ret = PrepareAndroidDirs(root);
|
|
if (ret != 0) {
|
|
LOG(ERROR) << "Failed to prepare Android/ directories.";
|
|
return ret;
|
|
}
|
|
|
|
// Now create the application-specific subdir(s)
|
|
// path is something like /data/media/0/Android/data/com.foo/files
|
|
// First, chop off the volume root, eg /data/media/0
|
|
std::string pathFromRoot = path.substr(root.length());
|
|
|
|
uid_t uid = appUid;
|
|
gid_t gid = AID_MEDIA_RW;
|
|
std::vector<gid_t> additionalGids;
|
|
std::string appDir;
|
|
|
|
// Check that the next part matches one of the allowed Android/ dirs
|
|
if (StartsWith(pathFromRoot, kAppDataDir)) {
|
|
appDir = kAppDataDir;
|
|
if (!sdcardfsSupport) {
|
|
gid = AID_EXT_DATA_RW;
|
|
// Also add the app's own UID as a group; since apps belong to a group
|
|
// that matches their UID, this ensures that they will always have access to
|
|
// the files created in these dirs, even if they are created by other processes
|
|
additionalGids.push_back(uid);
|
|
}
|
|
} else if (StartsWith(pathFromRoot, kAppMediaDir)) {
|
|
appDir = kAppMediaDir;
|
|
if (!sdcardfsSupport) {
|
|
gid = AID_MEDIA_RW;
|
|
}
|
|
} else if (StartsWith(pathFromRoot, kAppObbDir)) {
|
|
appDir = kAppObbDir;
|
|
if (!sdcardfsSupport) {
|
|
gid = AID_EXT_OBB_RW;
|
|
// See comments for kAppDataDir above
|
|
additionalGids.push_back(uid);
|
|
}
|
|
} else {
|
|
LOG(ERROR) << "Invalid application directory: " << path;
|
|
return -EINVAL;
|
|
}
|
|
|
|
// mode = 770, plus sticky bit on directory to inherit GID when apps
|
|
// create subdirs
|
|
mode_t mode = S_IRWXU | S_IRWXG | S_ISGID;
|
|
// the project ID for application-specific directories is directly
|
|
// derived from their uid
|
|
|
|
// Chop off the generic application-specific part, eg /Android/data/
|
|
// this leaves us with something like com.foo/files/
|
|
std::string leftToCreate = pathFromRoot.substr(appDir.length());
|
|
if (!EndsWith(leftToCreate, "/")) {
|
|
leftToCreate += "/";
|
|
}
|
|
std::string pathToCreate = root + appDir;
|
|
int depth = 0;
|
|
// Derive initial project ID
|
|
if (appDir == kAppDataDir || appDir == kAppMediaDir) {
|
|
projectId = uid - AID_APP_START + PROJECT_ID_EXT_DATA_START;
|
|
} else if (appDir == kAppObbDir) {
|
|
projectId = uid - AID_APP_START + PROJECT_ID_EXT_OBB_START;
|
|
}
|
|
|
|
while ((pos = leftToCreate.find('/')) != std::string::npos) {
|
|
std::string component = leftToCreate.substr(0, pos + 1);
|
|
leftToCreate = leftToCreate.erase(0, pos + 1);
|
|
pathToCreate = pathToCreate + component;
|
|
|
|
if (appDir == kAppDataDir && depth == 1 && component == "cache/") {
|
|
// All dirs use the "app" project ID, except for the cache dirs in
|
|
// Android/data, eg Android/data/com.foo/cache
|
|
// Note that this "sticks" - eg subdirs of this dir need the same
|
|
// project ID.
|
|
projectId = uid - AID_APP_START + PROJECT_ID_EXT_CACHE_START;
|
|
}
|
|
|
|
if (fixupExisting && access(pathToCreate.c_str(), F_OK) == 0) {
|
|
// Fixup all files in this existing directory with the correct UID/GID
|
|
// and project ID.
|
|
ret = FixupAppDir(pathToCreate, mode, uid, gid, projectId);
|
|
} else {
|
|
ret = PrepareDirWithProjectId(pathToCreate, mode, uid, gid, projectId);
|
|
}
|
|
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (depth == 0) {
|
|
// Set the default ACL on the top-level application-specific directories,
|
|
// to ensure that even if applications run with a umask of 0077,
|
|
// new directories within these directories will allow the GID
|
|
// specified here to write; this is necessary for apps like
|
|
// installers and MTP, that require access here.
|
|
//
|
|
// See man (5) acl for more details.
|
|
ret = SetDefaultAcl(pathToCreate, mode, uid, gid, additionalGids);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (!sdcardfsSupport) {
|
|
// Set project ID inheritance, so that future subdirectories inherit the
|
|
// same project ID
|
|
ret = SetQuotaInherit(pathToCreate);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
depth++;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
int SetAttrs(const std::string& path, unsigned int attrs) {
|
|
unsigned int flags;
|
|
android::base::unique_fd fd(
|
|
TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC)));
|
|
|
|
if (fd == -1) {
|
|
PLOG(ERROR) << "Failed to open " << path;
|
|
return -1;
|
|
}
|
|
|
|
if (ioctl(fd, FS_IOC_GETFLAGS, &flags)) {
|
|
PLOG(ERROR) << "Failed to get flags for " << path;
|
|
return -1;
|
|
}
|
|
|
|
if ((flags & attrs) == attrs) return 0;
|
|
flags |= attrs;
|
|
if (ioctl(fd, FS_IOC_SETFLAGS, &flags)) {
|
|
PLOG(ERROR) << "Failed to set flags for " << path << "(0x" << std::hex << attrs << ")";
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
status_t PrepareDir(const std::string& path, mode_t mode, uid_t uid, gid_t gid,
|
|
unsigned int attrs) {
|
|
std::lock_guard<std::mutex> lock(kSecurityLock);
|
|
const char* cpath = path.c_str();
|
|
auto clearfscreatecon = android::base::make_scope_guard([] { setfscreatecon(nullptr); });
|
|
auto secontext = std::unique_ptr<char, void (*)(char*)>(nullptr, freecon);
|
|
char* tmp_secontext;
|
|
|
|
if (selabel_lookup(sehandle, &tmp_secontext, cpath, S_IFDIR) == 0) {
|
|
secontext.reset(tmp_secontext);
|
|
if (setfscreatecon(secontext.get()) != 0) {
|
|
LOG(ERROR) << "Failed to setfscreatecon for directory " << path;
|
|
return -EINVAL;
|
|
}
|
|
} else if (errno == ENOENT) {
|
|
LOG(DEBUG) << "No selabel defined for directory " << path;
|
|
} else {
|
|
PLOG(ERROR) << "Failed to look up selabel for directory " << path;
|
|
return -errno;
|
|
}
|
|
|
|
if (fs_prepare_dir(cpath, mode, uid, gid) != 0) return -errno;
|
|
if (attrs && SetAttrs(path, attrs) != 0) return -errno;
|
|
return OK;
|
|
}
|
|
|
|
status_t ForceUnmount(const std::string& path) {
|
|
const char* cpath = path.c_str();
|
|
if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) {
|
|
return OK;
|
|
}
|
|
// Apps might still be handling eject request, so wait before
|
|
// we start sending signals
|
|
if (sSleepOnUnmount) sleep(5);
|
|
|
|
KillProcessesWithOpenFiles(path, SIGINT);
|
|
if (sSleepOnUnmount) sleep(5);
|
|
if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) {
|
|
return OK;
|
|
}
|
|
|
|
KillProcessesWithOpenFiles(path, SIGTERM);
|
|
if (sSleepOnUnmount) sleep(5);
|
|
if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) {
|
|
return OK;
|
|
}
|
|
|
|
KillProcessesWithOpenFiles(path, SIGKILL);
|
|
if (sSleepOnUnmount) sleep(5);
|
|
if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) {
|
|
return OK;
|
|
}
|
|
PLOG(INFO) << "ForceUnmount failed";
|
|
return -errno;
|
|
}
|
|
|
|
status_t KillProcessesWithTmpfsMountPrefix(const std::string& path) {
|
|
if (KillProcessesWithTmpfsMounts(path, SIGINT) == 0) {
|
|
return OK;
|
|
}
|
|
if (sSleepOnUnmount) sleep(5);
|
|
|
|
if (KillProcessesWithTmpfsMounts(path, SIGTERM) == 0) {
|
|
return OK;
|
|
}
|
|
if (sSleepOnUnmount) sleep(5);
|
|
|
|
if (KillProcessesWithTmpfsMounts(path, SIGKILL) == 0) {
|
|
return OK;
|
|
}
|
|
if (sSleepOnUnmount) sleep(5);
|
|
|
|
// Send SIGKILL a second time to determine if we've
|
|
// actually killed everyone mount
|
|
if (KillProcessesWithTmpfsMounts(path, SIGKILL) == 0) {
|
|
return OK;
|
|
}
|
|
PLOG(ERROR) << "Failed to kill processes using " << path;
|
|
return -EBUSY;
|
|
}
|
|
|
|
status_t KillProcessesUsingPath(const std::string& path) {
|
|
if (KillProcessesWithOpenFiles(path, SIGINT, false /* killFuseDaemon */) == 0) {
|
|
return OK;
|
|
}
|
|
if (sSleepOnUnmount) sleep(5);
|
|
|
|
if (KillProcessesWithOpenFiles(path, SIGTERM, false /* killFuseDaemon */) == 0) {
|
|
return OK;
|
|
}
|
|
if (sSleepOnUnmount) sleep(5);
|
|
|
|
if (KillProcessesWithOpenFiles(path, SIGKILL, false /* killFuseDaemon */) == 0) {
|
|
return OK;
|
|
}
|
|
if (sSleepOnUnmount) sleep(5);
|
|
|
|
// Send SIGKILL a second time to determine if we've
|
|
// actually killed everyone with open files
|
|
// This time, we also kill the FUSE daemon if found
|
|
if (KillProcessesWithOpenFiles(path, SIGKILL, true /* killFuseDaemon */) == 0) {
|
|
return OK;
|
|
}
|
|
PLOG(ERROR) << "Failed to kill processes using " << path;
|
|
return -EBUSY;
|
|
}
|
|
|
|
status_t BindMount(const std::string& source, const std::string& target) {
|
|
if (UnmountTree(target) < 0) {
|
|
return -errno;
|
|
}
|
|
if (TEMP_FAILURE_RETRY(mount(source.c_str(), target.c_str(), nullptr, MS_BIND, nullptr)) < 0) {
|
|
PLOG(ERROR) << "Failed to bind mount " << source << " to " << target;
|
|
return -errno;
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
status_t Symlink(const std::string& target, const std::string& linkpath) {
|
|
if (Unlink(linkpath) < 0) {
|
|
return -errno;
|
|
}
|
|
if (TEMP_FAILURE_RETRY(symlink(target.c_str(), linkpath.c_str())) < 0) {
|
|
PLOG(ERROR) << "Failed to create symlink " << linkpath << " to " << target;
|
|
return -errno;
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
status_t Unlink(const std::string& linkpath) {
|
|
if (TEMP_FAILURE_RETRY(unlink(linkpath.c_str())) < 0 && errno != EINVAL && errno != ENOENT) {
|
|
PLOG(ERROR) << "Failed to unlink " << linkpath;
|
|
return -errno;
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
status_t CreateDir(const std::string& dir, mode_t mode) {
|
|
struct stat sb;
|
|
if (TEMP_FAILURE_RETRY(stat(dir.c_str(), &sb)) == 0) {
|
|
if (S_ISDIR(sb.st_mode)) {
|
|
return OK;
|
|
} else if (TEMP_FAILURE_RETRY(unlink(dir.c_str())) == -1) {
|
|
PLOG(ERROR) << "Failed to unlink " << dir;
|
|
return -errno;
|
|
}
|
|
} else if (errno != ENOENT) {
|
|
PLOG(ERROR) << "Failed to stat " << dir;
|
|
return -errno;
|
|
}
|
|
if (TEMP_FAILURE_RETRY(mkdir(dir.c_str(), mode)) == -1 && errno != EEXIST) {
|
|
PLOG(ERROR) << "Failed to mkdir " << dir;
|
|
return -errno;
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
bool FindValue(const std::string& raw, const std::string& key, std::string* value) {
|
|
auto qual = key + "=\"";
|
|
size_t start = 0;
|
|
while (true) {
|
|
start = raw.find(qual, start);
|
|
if (start == std::string::npos) return false;
|
|
if (start == 0 || raw[start - 1] == ' ') {
|
|
break;
|
|
}
|
|
start += 1;
|
|
}
|
|
start += qual.length();
|
|
|
|
auto end = raw.find("\"", start);
|
|
if (end == std::string::npos) return false;
|
|
|
|
*value = raw.substr(start, end - start);
|
|
return true;
|
|
}
|
|
|
|
static status_t readMetadata(const std::string& path, std::string* fsType, std::string* fsUuid,
|
|
std::string* fsLabel, bool untrusted) {
|
|
fsType->clear();
|
|
fsUuid->clear();
|
|
fsLabel->clear();
|
|
|
|
std::vector<std::string> cmd;
|
|
cmd.push_back(kBlkidPath);
|
|
cmd.push_back("-c");
|
|
cmd.push_back("/dev/null");
|
|
cmd.push_back("-s");
|
|
cmd.push_back("TYPE");
|
|
cmd.push_back("-s");
|
|
cmd.push_back("UUID");
|
|
cmd.push_back("-s");
|
|
cmd.push_back("LABEL");
|
|
cmd.push_back(path);
|
|
|
|
std::vector<std::string> output;
|
|
status_t res = ForkExecvp(cmd, &output, untrusted ? sBlkidUntrustedContext : sBlkidContext);
|
|
if (res != OK) {
|
|
LOG(WARNING) << "blkid failed to identify " << path;
|
|
return res;
|
|
}
|
|
|
|
for (const auto& line : output) {
|
|
// Extract values from blkid output, if defined
|
|
FindValue(line, "TYPE", fsType);
|
|
FindValue(line, "UUID", fsUuid);
|
|
FindValue(line, "LABEL", fsLabel);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
status_t ReadMetadata(const std::string& path, std::string* fsType, std::string* fsUuid,
|
|
std::string* fsLabel) {
|
|
return readMetadata(path, fsType, fsUuid, fsLabel, false);
|
|
}
|
|
|
|
status_t ReadMetadataUntrusted(const std::string& path, std::string* fsType, std::string* fsUuid,
|
|
std::string* fsLabel) {
|
|
return readMetadata(path, fsType, fsUuid, fsLabel, true);
|
|
}
|
|
|
|
static std::vector<const char*> ConvertToArgv(const std::vector<std::string>& args) {
|
|
std::vector<const char*> argv;
|
|
argv.reserve(args.size() + 1);
|
|
for (const auto& arg : args) {
|
|
if (argv.empty()) {
|
|
LOG(DEBUG) << arg;
|
|
} else {
|
|
LOG(DEBUG) << " " << arg;
|
|
}
|
|
argv.emplace_back(arg.data());
|
|
}
|
|
argv.emplace_back(nullptr);
|
|
return argv;
|
|
}
|
|
|
|
static status_t ReadLinesFromFdAndLog(std::vector<std::string>* output,
|
|
android::base::unique_fd ufd) {
|
|
std::unique_ptr<FILE, int (*)(FILE*)> fp(android::base::Fdopen(std::move(ufd), "r"), fclose);
|
|
if (!fp) {
|
|
PLOG(ERROR) << "fdopen in ReadLinesFromFdAndLog";
|
|
return -errno;
|
|
}
|
|
if (output) output->clear();
|
|
char line[1024];
|
|
while (fgets(line, sizeof(line), fp.get()) != nullptr) {
|
|
LOG(DEBUG) << line;
|
|
if (output) output->emplace_back(line);
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
status_t ForkExecvp(const std::vector<std::string>& args, std::vector<std::string>* output,
|
|
char* context) {
|
|
auto argv = ConvertToArgv(args);
|
|
|
|
android::base::unique_fd pipe_read, pipe_write;
|
|
if (!android::base::Pipe(&pipe_read, &pipe_write)) {
|
|
PLOG(ERROR) << "Pipe in ForkExecvp";
|
|
return -errno;
|
|
}
|
|
|
|
pid_t pid = fork();
|
|
if (pid == 0) {
|
|
if (context) {
|
|
if (setexeccon(context)) {
|
|
LOG(ERROR) << "Failed to setexeccon in ForkExecvp";
|
|
abort();
|
|
}
|
|
}
|
|
pipe_read.reset();
|
|
if (dup2(pipe_write.get(), STDOUT_FILENO) == -1) {
|
|
PLOG(ERROR) << "dup2 in ForkExecvp";
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
pipe_write.reset();
|
|
execvp(argv[0], const_cast<char**>(argv.data()));
|
|
PLOG(ERROR) << "exec in ForkExecvp";
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
if (pid == -1) {
|
|
PLOG(ERROR) << "fork in ForkExecvp";
|
|
return -errno;
|
|
}
|
|
|
|
pipe_write.reset();
|
|
auto st = ReadLinesFromFdAndLog(output, std::move(pipe_read));
|
|
if (st != 0) return st;
|
|
|
|
int status;
|
|
if (waitpid(pid, &status, 0) == -1) {
|
|
PLOG(ERROR) << "waitpid in ForkExecvp";
|
|
return -errno;
|
|
}
|
|
if (!WIFEXITED(status)) {
|
|
LOG(ERROR) << "Process did not exit normally, status: " << status;
|
|
return -ECHILD;
|
|
}
|
|
if (WEXITSTATUS(status)) {
|
|
LOG(ERROR) << "Process exited with code: " << WEXITSTATUS(status);
|
|
return WEXITSTATUS(status);
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
status_t ForkExecvpTimeout(const std::vector<std::string>& args, std::chrono::seconds timeout,
|
|
char* context) {
|
|
int status;
|
|
|
|
pid_t wait_timeout_pid = fork();
|
|
if (wait_timeout_pid == 0) {
|
|
pid_t pid = ForkExecvpAsync(args, context);
|
|
if (pid == -1) {
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
pid_t timer_pid = fork();
|
|
if (timer_pid == 0) {
|
|
std::this_thread::sleep_for(timeout);
|
|
_exit(ETIMEDOUT);
|
|
}
|
|
if (timer_pid == -1) {
|
|
PLOG(ERROR) << "fork in ForkExecvpAsync_timeout";
|
|
kill(pid, SIGTERM);
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
pid_t finished = wait(&status);
|
|
if (finished == pid) {
|
|
kill(timer_pid, SIGTERM);
|
|
} else {
|
|
kill(pid, SIGTERM);
|
|
}
|
|
if (!WIFEXITED(status)) {
|
|
_exit(ECHILD);
|
|
}
|
|
_exit(WEXITSTATUS(status));
|
|
}
|
|
if (waitpid(wait_timeout_pid, &status, 0) == -1) {
|
|
PLOG(ERROR) << "waitpid in ForkExecvpAsync_timeout";
|
|
return -errno;
|
|
}
|
|
if (!WIFEXITED(status)) {
|
|
LOG(ERROR) << "Process did not exit normally, status: " << status;
|
|
return -ECHILD;
|
|
}
|
|
if (WEXITSTATUS(status)) {
|
|
LOG(ERROR) << "Process exited with code: " << WEXITSTATUS(status);
|
|
return WEXITSTATUS(status);
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
pid_t ForkExecvpAsync(const std::vector<std::string>& args, char* context) {
|
|
auto argv = ConvertToArgv(args);
|
|
|
|
pid_t pid = fork();
|
|
if (pid == 0) {
|
|
close(STDIN_FILENO);
|
|
close(STDOUT_FILENO);
|
|
close(STDERR_FILENO);
|
|
if (context) {
|
|
if (setexeccon(context)) {
|
|
LOG(ERROR) << "Failed to setexeccon in ForkExecvpAsync";
|
|
abort();
|
|
}
|
|
}
|
|
|
|
execvp(argv[0], const_cast<char**>(argv.data()));
|
|
PLOG(ERROR) << "exec in ForkExecvpAsync";
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
if (pid == -1) {
|
|
PLOG(ERROR) << "fork in ForkExecvpAsync";
|
|
return -1;
|
|
}
|
|
return pid;
|
|
}
|
|
|
|
status_t ReadRandomBytes(size_t bytes, std::string& out) {
|
|
out.resize(bytes);
|
|
return ReadRandomBytes(bytes, &out[0]);
|
|
}
|
|
|
|
status_t ReadRandomBytes(size_t bytes, char* buf) {
|
|
int fd = TEMP_FAILURE_RETRY(open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOFOLLOW));
|
|
if (fd == -1) {
|
|
return -errno;
|
|
}
|
|
|
|
ssize_t n;
|
|
while ((n = TEMP_FAILURE_RETRY(read(fd, &buf[0], bytes))) > 0) {
|
|
bytes -= n;
|
|
buf += n;
|
|
}
|
|
close(fd);
|
|
|
|
if (bytes == 0) {
|
|
return OK;
|
|
} else {
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
status_t GenerateRandomUuid(std::string& out) {
|
|
status_t res = ReadRandomBytes(16, out);
|
|
if (res == OK) {
|
|
out[6] &= 0x0f; /* clear version */
|
|
out[6] |= 0x40; /* set to version 4 */
|
|
out[8] &= 0x3f; /* clear variant */
|
|
out[8] |= 0x80; /* set to IETF variant */
|
|
}
|
|
return res;
|
|
}
|
|
|
|
status_t HexToStr(const std::string& hex, std::string& str) {
|
|
str.clear();
|
|
bool even = true;
|
|
char cur = 0;
|
|
for (size_t i = 0; i < hex.size(); i++) {
|
|
int val = 0;
|
|
switch (hex[i]) {
|
|
// clang-format off
|
|
case ' ': case '-': case ':': continue;
|
|
case 'f': case 'F': val = 15; break;
|
|
case 'e': case 'E': val = 14; break;
|
|
case 'd': case 'D': val = 13; break;
|
|
case 'c': case 'C': val = 12; break;
|
|
case 'b': case 'B': val = 11; break;
|
|
case 'a': case 'A': val = 10; break;
|
|
case '9': val = 9; break;
|
|
case '8': val = 8; break;
|
|
case '7': val = 7; break;
|
|
case '6': val = 6; break;
|
|
case '5': val = 5; break;
|
|
case '4': val = 4; break;
|
|
case '3': val = 3; break;
|
|
case '2': val = 2; break;
|
|
case '1': val = 1; break;
|
|
case '0': val = 0; break;
|
|
default: return -EINVAL;
|
|
// clang-format on
|
|
}
|
|
|
|
if (even) {
|
|
cur = val << 4;
|
|
} else {
|
|
cur += val;
|
|
str.push_back(cur);
|
|
cur = 0;
|
|
}
|
|
even = !even;
|
|
}
|
|
return even ? OK : -EINVAL;
|
|
}
|
|
|
|
static const char* kLookup = "0123456789abcdef";
|
|
|
|
status_t StrToHex(const std::string& str, std::string& hex) {
|
|
hex.clear();
|
|
for (size_t i = 0; i < str.size(); i++) {
|
|
hex.push_back(kLookup[(str[i] & 0xF0) >> 4]);
|
|
hex.push_back(kLookup[str[i] & 0x0F]);
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
status_t StrToHex(const KeyBuffer& str, KeyBuffer& hex) {
|
|
hex.clear();
|
|
for (size_t i = 0; i < str.size(); i++) {
|
|
hex.push_back(kLookup[(str.data()[i] & 0xF0) >> 4]);
|
|
hex.push_back(kLookup[str.data()[i] & 0x0F]);
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
status_t NormalizeHex(const std::string& in, std::string& out) {
|
|
std::string tmp;
|
|
if (HexToStr(in, tmp)) {
|
|
return -EINVAL;
|
|
}
|
|
return StrToHex(tmp, out);
|
|
}
|
|
|
|
status_t GetBlockDevSize(int fd, uint64_t* size) {
|
|
if (ioctl(fd, BLKGETSIZE64, size)) {
|
|
return -errno;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
status_t GetBlockDevSize(const std::string& path, uint64_t* size) {
|
|
int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
|
|
status_t res = OK;
|
|
|
|
if (fd < 0) {
|
|
return -errno;
|
|
}
|
|
|
|
res = GetBlockDevSize(fd, size);
|
|
|
|
close(fd);
|
|
|
|
return res;
|
|
}
|
|
|
|
status_t GetBlockDev512Sectors(const std::string& path, uint64_t* nr_sec) {
|
|
uint64_t size;
|
|
status_t res = GetBlockDevSize(path, &size);
|
|
|
|
if (res != OK) {
|
|
return res;
|
|
}
|
|
|
|
*nr_sec = size / 512;
|
|
|
|
return OK;
|
|
}
|
|
|
|
uint64_t GetFreeBytes(const std::string& path) {
|
|
struct statvfs sb;
|
|
if (statvfs(path.c_str(), &sb) == 0) {
|
|
return (uint64_t)sb.f_bavail * sb.f_frsize;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// TODO: borrowed from frameworks/native/libs/diskusage/ which should
|
|
// eventually be migrated into system/
|
|
static int64_t stat_size(struct stat* s) {
|
|
int64_t blksize = s->st_blksize;
|
|
// count actual blocks used instead of nominal file size
|
|
int64_t size = s->st_blocks * 512;
|
|
|
|
if (blksize) {
|
|
/* round up to filesystem block size */
|
|
size = (size + blksize - 1) & (~(blksize - 1));
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
// TODO: borrowed from frameworks/native/libs/diskusage/ which should
|
|
// eventually be migrated into system/
|
|
int64_t calculate_dir_size(int dfd) {
|
|
int64_t size = 0;
|
|
struct stat s;
|
|
DIR* d;
|
|
struct dirent* de;
|
|
|
|
d = fdopendir(dfd);
|
|
if (d == NULL) {
|
|
close(dfd);
|
|
return 0;
|
|
}
|
|
|
|
while ((de = readdir(d))) {
|
|
const char* name = de->d_name;
|
|
if (fstatat(dfd, name, &s, AT_SYMLINK_NOFOLLOW) == 0) {
|
|
size += stat_size(&s);
|
|
}
|
|
if (de->d_type == DT_DIR) {
|
|
int subfd;
|
|
|
|
/* always skip "." and ".." */
|
|
if (IsDotOrDotDot(*de)) continue;
|
|
|
|
subfd = openat(dfd, name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
|
if (subfd >= 0) {
|
|
size += calculate_dir_size(subfd);
|
|
}
|
|
}
|
|
}
|
|
closedir(d);
|
|
return size;
|
|
}
|
|
|
|
uint64_t GetTreeBytes(const std::string& path) {
|
|
int dirfd = open(path.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
|
if (dirfd < 0) {
|
|
PLOG(WARNING) << "Failed to open " << path;
|
|
return -1;
|
|
} else {
|
|
return calculate_dir_size(dirfd);
|
|
}
|
|
}
|
|
|
|
// TODO: Use a better way to determine if it's media provider app.
|
|
bool IsFuseDaemon(const pid_t pid) {
|
|
auto path = StringPrintf("/proc/%d/mounts", pid);
|
|
char* tmp;
|
|
if (lgetfilecon(path.c_str(), &tmp) < 0) {
|
|
return false;
|
|
}
|
|
bool result = android::base::StartsWith(tmp, kMediaProviderAppCtx)
|
|
|| android::base::StartsWith(tmp, kMediaProviderCtx);
|
|
freecon(tmp);
|
|
return result;
|
|
}
|
|
|
|
bool IsFilesystemSupported(const std::string& fsType) {
|
|
std::string supported;
|
|
if (!ReadFileToString(kProcFilesystems, &supported)) {
|
|
PLOG(ERROR) << "Failed to read supported filesystems";
|
|
return false;
|
|
}
|
|
return supported.find(fsType + "\n") != std::string::npos;
|
|
}
|
|
|
|
bool IsSdcardfsUsed() {
|
|
return IsFilesystemSupported("sdcardfs") &&
|
|
base::GetBoolProperty(kExternalStorageSdcardfs, true);
|
|
}
|
|
|
|
status_t WipeBlockDevice(const std::string& path) {
|
|
status_t res = -1;
|
|
const char* c_path = path.c_str();
|
|
uint64_t range[2] = {0, 0};
|
|
|
|
int fd = TEMP_FAILURE_RETRY(open(c_path, O_RDWR | O_CLOEXEC));
|
|
if (fd == -1) {
|
|
PLOG(ERROR) << "Failed to open " << path;
|
|
goto done;
|
|
}
|
|
|
|
if (GetBlockDevSize(fd, &range[1]) != OK) {
|
|
PLOG(ERROR) << "Failed to determine size of " << path;
|
|
goto done;
|
|
}
|
|
|
|
LOG(INFO) << "About to discard " << range[1] << " on " << path;
|
|
if (ioctl(fd, BLKDISCARD, &range) == 0) {
|
|
LOG(INFO) << "Discard success on " << path;
|
|
res = 0;
|
|
} else {
|
|
PLOG(ERROR) << "Discard failure on " << path;
|
|
}
|
|
|
|
done:
|
|
close(fd);
|
|
return res;
|
|
}
|
|
|
|
static bool isValidFilename(const std::string& name) {
|
|
if (name.empty() || (name == ".") || (name == "..") || (name.find('/') != std::string::npos)) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
std::string BuildKeyPath(const std::string& partGuid) {
|
|
return StringPrintf("%s/expand_%s.key", kKeyPath, partGuid.c_str());
|
|
}
|
|
|
|
std::string BuildDataSystemLegacyPath(userid_t userId) {
|
|
return StringPrintf("%s/system/users/%u", BuildDataPath("").c_str(), userId);
|
|
}
|
|
|
|
std::string BuildDataSystemCePath(userid_t userId) {
|
|
return StringPrintf("%s/system_ce/%u", BuildDataPath("").c_str(), userId);
|
|
}
|
|
|
|
std::string BuildDataSystemDePath(userid_t userId) {
|
|
return StringPrintf("%s/system_de/%u", BuildDataPath("").c_str(), userId);
|
|
}
|
|
|
|
std::string BuildDataMiscLegacyPath(userid_t userId) {
|
|
return StringPrintf("%s/misc/user/%u", BuildDataPath("").c_str(), userId);
|
|
}
|
|
|
|
// Keep in sync with installd (frameworks/native/cmds/installd/utils.h)
|
|
std::string BuildDataProfilesDePath(userid_t userId) {
|
|
return StringPrintf("%s/misc/profiles/cur/%u", BuildDataPath("").c_str(), userId);
|
|
}
|
|
|
|
std::string BuildDataVendorCePath(userid_t userId) {
|
|
return StringPrintf("%s/vendor_ce/%u", BuildDataPath("").c_str(), userId);
|
|
}
|
|
|
|
std::string BuildDataVendorDePath(userid_t userId) {
|
|
return StringPrintf("%s/vendor_de/%u", BuildDataPath("").c_str(), userId);
|
|
}
|
|
|
|
std::string BuildDataPath(const std::string& volumeUuid) {
|
|
// TODO: unify with installd path generation logic
|
|
if (volumeUuid.empty()) {
|
|
return "/data";
|
|
} else {
|
|
CHECK(isValidFilename(volumeUuid));
|
|
return StringPrintf("/mnt/expand/%s", volumeUuid.c_str());
|
|
}
|
|
}
|
|
|
|
std::string BuildDataMediaCePath(const std::string& volumeUuid, userid_t userId) {
|
|
// TODO: unify with installd path generation logic
|
|
std::string data(BuildDataPath(volumeUuid));
|
|
return StringPrintf("%s/media/%u", data.c_str(), userId);
|
|
}
|
|
|
|
std::string BuildDataMiscCePath(const std::string& volumeUuid, userid_t userId) {
|
|
return StringPrintf("%s/misc_ce/%u", BuildDataPath(volumeUuid).c_str(), userId);
|
|
}
|
|
|
|
std::string BuildDataMiscDePath(const std::string& volumeUuid, userid_t userId) {
|
|
return StringPrintf("%s/misc_de/%u", BuildDataPath(volumeUuid).c_str(), userId);
|
|
}
|
|
|
|
std::string BuildDataUserCePath(const std::string& volumeUuid, userid_t userId) {
|
|
// TODO: unify with installd path generation logic
|
|
std::string data(BuildDataPath(volumeUuid));
|
|
return StringPrintf("%s/user/%u", data.c_str(), userId);
|
|
}
|
|
|
|
std::string BuildDataUserDePath(const std::string& volumeUuid, userid_t userId) {
|
|
// TODO: unify with installd path generation logic
|
|
std::string data(BuildDataPath(volumeUuid));
|
|
return StringPrintf("%s/user_de/%u", data.c_str(), userId);
|
|
}
|
|
|
|
dev_t GetDevice(const std::string& path) {
|
|
struct stat sb;
|
|
if (stat(path.c_str(), &sb)) {
|
|
PLOG(WARNING) << "Failed to stat " << path;
|
|
return 0;
|
|
} else {
|
|
return sb.st_dev;
|
|
}
|
|
}
|
|
|
|
// Returns true if |path| exists and is a symlink.
|
|
bool IsSymlink(const std::string& path) {
|
|
struct stat stbuf;
|
|
return lstat(path.c_str(), &stbuf) == 0 && S_ISLNK(stbuf.st_mode);
|
|
}
|
|
|
|
// Returns true if |path1| names the same existing file or directory as |path2|.
|
|
bool IsSameFile(const std::string& path1, const std::string& path2) {
|
|
struct stat stbuf1, stbuf2;
|
|
if (stat(path1.c_str(), &stbuf1) != 0 || stat(path2.c_str(), &stbuf2) != 0) return false;
|
|
return stbuf1.st_ino == stbuf2.st_ino && stbuf1.st_dev == stbuf2.st_dev;
|
|
}
|
|
|
|
status_t RestoreconRecursive(const std::string& path) {
|
|
LOG(DEBUG) << "Starting restorecon of " << path;
|
|
|
|
static constexpr const char* kRestoreconString = "selinux.restorecon_recursive";
|
|
|
|
android::base::SetProperty(kRestoreconString, "");
|
|
android::base::SetProperty(kRestoreconString, path);
|
|
|
|
android::base::WaitForProperty(kRestoreconString, path);
|
|
|
|
LOG(DEBUG) << "Finished restorecon of " << path;
|
|
return OK;
|
|
}
|
|
|
|
bool Readlinkat(int dirfd, const std::string& path, std::string* result) {
|
|
// Shamelessly borrowed from android::base::Readlink()
|
|
result->clear();
|
|
|
|
// Most Linux file systems (ext2 and ext4, say) limit symbolic links to
|
|
// 4095 bytes. Since we'll copy out into the string anyway, it doesn't
|
|
// waste memory to just start there. We add 1 so that we can recognize
|
|
// whether it actually fit (rather than being truncated to 4095).
|
|
std::vector<char> buf(4095 + 1);
|
|
while (true) {
|
|
ssize_t size = readlinkat(dirfd, path.c_str(), &buf[0], buf.size());
|
|
// Unrecoverable error?
|
|
if (size == -1) return false;
|
|
// It fit! (If size == buf.size(), it may have been truncated.)
|
|
if (static_cast<size_t>(size) < buf.size()) {
|
|
result->assign(&buf[0], size);
|
|
return true;
|
|
}
|
|
// Double our buffer and try again.
|
|
buf.resize(buf.size() * 2);
|
|
}
|
|
}
|
|
|
|
static unsigned int GetMajorBlockVirtioBlk() {
|
|
std::string devices;
|
|
if (!ReadFileToString(kProcDevices, &devices)) {
|
|
PLOG(ERROR) << "Unable to open /proc/devices";
|
|
return 0;
|
|
}
|
|
|
|
bool blockSection = false;
|
|
for (auto line : android::base::Split(devices, "\n")) {
|
|
if (line == "Block devices:") {
|
|
blockSection = true;
|
|
} else if (line == "Character devices:") {
|
|
blockSection = false;
|
|
} else if (blockSection) {
|
|
auto tokens = android::base::Split(line, " ");
|
|
if (tokens.size() == 2 && tokens[1] == "virtblk") {
|
|
return std::stoul(tokens[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool IsVirtioBlkDevice(unsigned int major) {
|
|
// Most virtualized platforms expose block devices with the virtio-blk
|
|
// block device driver. Unfortunately, this driver does not use a fixed
|
|
// major number, but relies on the kernel to assign one from a specific
|
|
// range of block majors, which are allocated for "LOCAL/EXPERIMENAL USE"
|
|
// per Documentation/devices.txt. This is true even for the latest Linux
|
|
// kernel (4.4; see init() in drivers/block/virtio_blk.c).
|
|
static unsigned int kMajorBlockVirtioBlk = GetMajorBlockVirtioBlk();
|
|
return kMajorBlockVirtioBlk && major == kMajorBlockVirtioBlk;
|
|
}
|
|
|
|
status_t UnmountTree(const std::string& mountPoint) {
|
|
if (TEMP_FAILURE_RETRY(umount2(mountPoint.c_str(), MNT_DETACH)) < 0 && errno != EINVAL &&
|
|
errno != ENOENT) {
|
|
PLOG(ERROR) << "Failed to unmount " << mountPoint;
|
|
return -errno;
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
bool IsDotOrDotDot(const struct dirent& ent) {
|
|
return strcmp(ent.d_name, ".") == 0 || strcmp(ent.d_name, "..") == 0;
|
|
}
|
|
|
|
static status_t delete_dir_contents(DIR* dir) {
|
|
// Shamelessly borrowed from android::installd
|
|
int dfd = dirfd(dir);
|
|
if (dfd < 0) {
|
|
return -errno;
|
|
}
|
|
|
|
status_t result = OK;
|
|
struct dirent* de;
|
|
while ((de = readdir(dir))) {
|
|
const char* name = de->d_name;
|
|
if (de->d_type == DT_DIR) {
|
|
/* always skip "." and ".." */
|
|
if (IsDotOrDotDot(*de)) continue;
|
|
|
|
android::base::unique_fd subfd(
|
|
openat(dfd, name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC));
|
|
if (subfd.get() == -1) {
|
|
PLOG(ERROR) << "Couldn't openat " << name;
|
|
result = -errno;
|
|
continue;
|
|
}
|
|
std::unique_ptr<DIR, decltype(&closedir)> subdirp(
|
|
android::base::Fdopendir(std::move(subfd)), closedir);
|
|
if (!subdirp) {
|
|
PLOG(ERROR) << "Couldn't fdopendir " << name;
|
|
result = -errno;
|
|
continue;
|
|
}
|
|
result = delete_dir_contents(subdirp.get());
|
|
if (unlinkat(dfd, name, AT_REMOVEDIR) < 0) {
|
|
PLOG(ERROR) << "Couldn't unlinkat " << name;
|
|
result = -errno;
|
|
}
|
|
} else {
|
|
if (unlinkat(dfd, name, 0) < 0) {
|
|
PLOG(ERROR) << "Couldn't unlinkat " << name;
|
|
result = -errno;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
status_t DeleteDirContentsAndDir(const std::string& pathname) {
|
|
status_t res = DeleteDirContents(pathname);
|
|
if (res < 0) {
|
|
return res;
|
|
}
|
|
if (TEMP_FAILURE_RETRY(rmdir(pathname.c_str())) < 0 && errno != ENOENT) {
|
|
PLOG(ERROR) << "rmdir failed on " << pathname;
|
|
return -errno;
|
|
}
|
|
LOG(VERBOSE) << "Success: rmdir on " << pathname;
|
|
return OK;
|
|
}
|
|
|
|
status_t DeleteDirContents(const std::string& pathname) {
|
|
// Shamelessly borrowed from android::installd
|
|
std::unique_ptr<DIR, decltype(&closedir)> dirp(opendir(pathname.c_str()), closedir);
|
|
if (!dirp) {
|
|
if (errno == ENOENT) {
|
|
return OK;
|
|
}
|
|
PLOG(ERROR) << "Failed to opendir " << pathname;
|
|
return -errno;
|
|
}
|
|
return delete_dir_contents(dirp.get());
|
|
}
|
|
|
|
// TODO(118708649): fix duplication with init/util.h
|
|
status_t WaitForFile(const char* filename, std::chrono::nanoseconds timeout) {
|
|
android::base::Timer t;
|
|
while (t.duration() < timeout) {
|
|
struct stat sb;
|
|
if (stat(filename, &sb) != -1) {
|
|
LOG(INFO) << "wait for '" << filename << "' took " << t;
|
|
return 0;
|
|
}
|
|
std::this_thread::sleep_for(10ms);
|
|
}
|
|
LOG(WARNING) << "wait for '" << filename << "' timed out and took " << t;
|
|
return -1;
|
|
}
|
|
|
|
bool pathExists(const std::string& path) {
|
|
return access(path.c_str(), F_OK) == 0;
|
|
}
|
|
|
|
bool FsyncDirectory(const std::string& dirname) {
|
|
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dirname.c_str(), O_RDONLY | O_CLOEXEC)));
|
|
if (fd == -1) {
|
|
PLOG(ERROR) << "Failed to open " << dirname;
|
|
return false;
|
|
}
|
|
if (fsync(fd) == -1) {
|
|
if (errno == EROFS || errno == EINVAL) {
|
|
PLOG(WARNING) << "Skip fsync " << dirname
|
|
<< " on a file system does not support synchronization";
|
|
} else {
|
|
PLOG(ERROR) << "Failed to fsync " << dirname;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FsyncParentDirectory(const std::string& path) {
|
|
return FsyncDirectory(android::base::Dirname(path));
|
|
}
|
|
|
|
// Creates all parent directories of |path| that don't already exist. Assigns
|
|
// the specified |mode| to any new directories, and also fsync()s their parent
|
|
// directories so that the new directories get written to disk right away.
|
|
bool MkdirsSync(const std::string& path, mode_t mode) {
|
|
if (path[0] != '/') {
|
|
LOG(ERROR) << "MkdirsSync() needs an absolute path, but got " << path;
|
|
return false;
|
|
}
|
|
std::vector<std::string> components = android::base::Split(android::base::Dirname(path), "/");
|
|
|
|
std::string current_dir = "/";
|
|
for (const std::string& component : components) {
|
|
if (component.empty()) continue;
|
|
|
|
std::string parent_dir = current_dir;
|
|
if (current_dir != "/") current_dir += "/";
|
|
current_dir += component;
|
|
|
|
if (!pathExists(current_dir)) {
|
|
if (mkdir(current_dir.c_str(), mode) != 0) {
|
|
PLOG(ERROR) << "Failed to create " << current_dir;
|
|
return false;
|
|
}
|
|
if (!FsyncDirectory(parent_dir)) return false;
|
|
LOG(DEBUG) << "Created directory " << current_dir;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool writeStringToFile(const std::string& payload, const std::string& filename) {
|
|
android::base::unique_fd fd(TEMP_FAILURE_RETRY(
|
|
open(filename.c_str(), O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC | O_CLOEXEC, 0666)));
|
|
if (fd == -1) {
|
|
PLOG(ERROR) << "Failed to open " << filename;
|
|
return false;
|
|
}
|
|
if (!android::base::WriteStringToFd(payload, fd)) {
|
|
PLOG(ERROR) << "Failed to write to " << filename;
|
|
unlink(filename.c_str());
|
|
return false;
|
|
}
|
|
// fsync as close won't guarantee flush data
|
|
// see close(2), fsync(2) and b/68901441
|
|
if (fsync(fd) == -1) {
|
|
if (errno == EROFS || errno == EINVAL) {
|
|
PLOG(WARNING) << "Skip fsync " << filename
|
|
<< " on a file system does not support synchronization";
|
|
} else {
|
|
PLOG(ERROR) << "Failed to fsync " << filename;
|
|
unlink(filename.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
status_t AbortFuseConnections() {
|
|
namespace fs = std::filesystem;
|
|
|
|
static constexpr const char* kFuseConnections = "/sys/fs/fuse/connections";
|
|
|
|
std::error_code ec;
|
|
for (const auto& itEntry : fs::directory_iterator(kFuseConnections, ec)) {
|
|
std::string fsPath = itEntry.path().string() + "/filesystem";
|
|
std::string fs;
|
|
|
|
// Virtiofs is on top of fuse and there isn't any user space daemon.
|
|
// Android user space doesn't manage it.
|
|
if (android::base::ReadFileToString(fsPath, &fs, false) &&
|
|
android::base::Trim(fs) == "virtiofs") {
|
|
LOG(INFO) << "Ignore virtiofs connection entry " << itEntry.path().string();
|
|
continue;
|
|
}
|
|
|
|
std::string abortPath = itEntry.path().string() + "/abort";
|
|
LOG(DEBUG) << "Aborting fuse connection entry " << abortPath;
|
|
bool ret = writeStringToFile("1", abortPath);
|
|
if (!ret) {
|
|
LOG(WARNING) << "Failed to write to " << abortPath;
|
|
}
|
|
}
|
|
|
|
if (ec) {
|
|
LOG(WARNING) << "Failed to iterate through " << kFuseConnections << ": " << ec.message();
|
|
return -ec.value();
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
status_t EnsureDirExists(const std::string& path, mode_t mode, uid_t uid, gid_t gid) {
|
|
if (access(path.c_str(), F_OK) != 0) {
|
|
PLOG(WARNING) << "Dir does not exist: " << path;
|
|
if (fs_prepare_dir(path.c_str(), mode, uid, gid) != 0) {
|
|
return -errno;
|
|
}
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
// Gets the sysfs path for parameters of the backing device info (bdi)
|
|
static std::string getBdiPathForMount(const std::string& mount) {
|
|
// First figure out MAJOR:MINOR of mount. Simplest way is to stat the path.
|
|
struct stat info;
|
|
if (stat(mount.c_str(), &info) != 0) {
|
|
PLOG(ERROR) << "Failed to stat " << mount;
|
|
return "";
|
|
}
|
|
unsigned int maj = major(info.st_dev);
|
|
unsigned int min = minor(info.st_dev);
|
|
|
|
return StringPrintf("/sys/class/bdi/%u:%u", maj, min);
|
|
}
|
|
|
|
// Configures max_ratio for the FUSE filesystem.
|
|
void ConfigureMaxDirtyRatioForFuse(const std::string& fuse_mount, unsigned int max_ratio) {
|
|
LOG(INFO) << "Configuring max_ratio of " << fuse_mount << " fuse filesystem to " << max_ratio;
|
|
if (max_ratio > 100) {
|
|
LOG(ERROR) << "Invalid max_ratio: " << max_ratio;
|
|
return;
|
|
}
|
|
std::string fuseBdiPath = getBdiPathForMount(fuse_mount);
|
|
if (fuseBdiPath == "") {
|
|
return;
|
|
}
|
|
std::string max_ratio_file = StringPrintf("%s/max_ratio", fuseBdiPath.c_str());
|
|
unique_fd fd(TEMP_FAILURE_RETRY(open(max_ratio_file.c_str(), O_WRONLY | O_CLOEXEC)));
|
|
if (fd.get() == -1) {
|
|
PLOG(ERROR) << "Failed to open " << max_ratio_file;
|
|
return;
|
|
}
|
|
LOG(INFO) << "Writing " << max_ratio << " to " << max_ratio_file;
|
|
if (!WriteStringToFd(std::to_string(max_ratio), fd)) {
|
|
PLOG(ERROR) << "Failed to write to " << max_ratio_file;
|
|
}
|
|
}
|
|
|
|
// Configures read ahead property of the fuse filesystem with the mount point |fuse_mount| by
|
|
// writing |read_ahead_kb| to the /sys/class/bdi/MAJOR:MINOR/read_ahead_kb.
|
|
void ConfigureReadAheadForFuse(const std::string& fuse_mount, size_t read_ahead_kb) {
|
|
LOG(INFO) << "Configuring read_ahead of " << fuse_mount << " fuse filesystem to "
|
|
<< read_ahead_kb << "kb";
|
|
std::string fuseBdiPath = getBdiPathForMount(fuse_mount);
|
|
if (fuseBdiPath == "") {
|
|
return;
|
|
}
|
|
// We found the bdi path for our filesystem, time to configure read ahead!
|
|
std::string read_ahead_file = StringPrintf("%s/read_ahead_kb", fuseBdiPath.c_str());
|
|
unique_fd fd(TEMP_FAILURE_RETRY(open(read_ahead_file.c_str(), O_WRONLY | O_CLOEXEC)));
|
|
if (fd.get() == -1) {
|
|
PLOG(ERROR) << "Failed to open " << read_ahead_file;
|
|
return;
|
|
}
|
|
LOG(INFO) << "Writing " << read_ahead_kb << " to " << read_ahead_file;
|
|
if (!WriteStringToFd(std::to_string(read_ahead_kb), fd)) {
|
|
PLOG(ERROR) << "Failed to write to " << read_ahead_file;
|
|
}
|
|
}
|
|
|
|
status_t MountUserFuse(userid_t user_id, const std::string& absolute_lower_path,
|
|
const std::string& relative_upper_path, android::base::unique_fd* fuse_fd) {
|
|
std::string pre_fuse_path(StringPrintf("/mnt/user/%d", user_id));
|
|
std::string fuse_path(
|
|
StringPrintf("%s/%s", pre_fuse_path.c_str(), relative_upper_path.c_str()));
|
|
|
|
std::string pre_pass_through_path(StringPrintf("/mnt/pass_through/%d", user_id));
|
|
std::string pass_through_path(
|
|
StringPrintf("%s/%s", pre_pass_through_path.c_str(), relative_upper_path.c_str()));
|
|
|
|
// Ensure that /mnt/user is 0700. With FUSE, apps don't need access to /mnt/user paths directly.
|
|
// Without FUSE however, apps need /mnt/user access so /mnt/user in init.rc is 0755 until here
|
|
auto result = PrepareDir("/mnt/user", 0750, AID_ROOT, AID_MEDIA_RW);
|
|
if (result != android::OK) {
|
|
PLOG(ERROR) << "Failed to prepare directory /mnt/user";
|
|
return -1;
|
|
}
|
|
|
|
// Shell is neither AID_ROOT nor AID_EVERYBODY. Since it equally needs 'execute' access to
|
|
// /mnt/user/0 to 'adb shell ls /sdcard' for instance, we set the uid bit of /mnt/user/0 to
|
|
// AID_SHELL. This gives shell access along with apps running as group everybody (user 0 apps)
|
|
// These bits should be consistent with what is set in zygote in
|
|
// com_android_internal_os_Zygote#MountEmulatedStorage on volume bind mount during app fork
|
|
result = PrepareDir(pre_fuse_path, 0710, user_id ? AID_ROOT : AID_SHELL,
|
|
multiuser_get_uid(user_id, AID_EVERYBODY));
|
|
if (result != android::OK) {
|
|
PLOG(ERROR) << "Failed to prepare directory " << pre_fuse_path;
|
|
return -1;
|
|
}
|
|
|
|
result = PrepareDir(fuse_path, 0700, AID_ROOT, AID_ROOT);
|
|
if (result != android::OK) {
|
|
PLOG(ERROR) << "Failed to prepare directory " << fuse_path;
|
|
return -1;
|
|
}
|
|
|
|
result = PrepareDir(pre_pass_through_path, 0710, AID_ROOT, AID_MEDIA_RW);
|
|
if (result != android::OK) {
|
|
PLOG(ERROR) << "Failed to prepare directory " << pre_pass_through_path;
|
|
return -1;
|
|
}
|
|
|
|
result = PrepareDir(pass_through_path, 0710, AID_ROOT, AID_MEDIA_RW);
|
|
if (result != android::OK) {
|
|
PLOG(ERROR) << "Failed to prepare directory " << pass_through_path;
|
|
return -1;
|
|
}
|
|
|
|
if (relative_upper_path == "emulated") {
|
|
std::string linkpath(StringPrintf("/mnt/user/%d/self", user_id));
|
|
result = PrepareDir(linkpath, 0755, AID_ROOT, AID_ROOT);
|
|
if (result != android::OK) {
|
|
PLOG(ERROR) << "Failed to prepare directory " << linkpath;
|
|
return -1;
|
|
}
|
|
linkpath += "/primary";
|
|
Symlink("/storage/emulated/" + std::to_string(user_id), linkpath);
|
|
|
|
std::string pass_through_linkpath(StringPrintf("/mnt/pass_through/%d/self", user_id));
|
|
result = PrepareDir(pass_through_linkpath, 0710, AID_ROOT, AID_MEDIA_RW);
|
|
if (result != android::OK) {
|
|
PLOG(ERROR) << "Failed to prepare directory " << pass_through_linkpath;
|
|
return -1;
|
|
}
|
|
pass_through_linkpath += "/primary";
|
|
Symlink("/storage/emulated/" + std::to_string(user_id), pass_through_linkpath);
|
|
}
|
|
|
|
// Open fuse fd.
|
|
fuse_fd->reset(open("/dev/fuse", O_RDWR | O_CLOEXEC));
|
|
if (fuse_fd->get() == -1) {
|
|
PLOG(ERROR) << "Failed to open /dev/fuse";
|
|
return -1;
|
|
}
|
|
|
|
// Note: leaving out default_permissions since we don't want kernel to do lower filesystem
|
|
// permission checks before routing to FUSE daemon.
|
|
const auto opts = StringPrintf(
|
|
"fd=%i,"
|
|
"rootmode=40000,"
|
|
"allow_other,"
|
|
"user_id=0,group_id=0,",
|
|
fuse_fd->get());
|
|
|
|
result = TEMP_FAILURE_RETRY(mount("/dev/fuse", fuse_path.c_str(), "fuse",
|
|
MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME | MS_LAZYTIME,
|
|
opts.c_str()));
|
|
if (result != 0) {
|
|
PLOG(ERROR) << "Failed to mount " << fuse_path;
|
|
return -errno;
|
|
}
|
|
|
|
if (IsSdcardfsUsed()) {
|
|
std::string sdcardfs_path(
|
|
StringPrintf("/mnt/runtime/full/%s", relative_upper_path.c_str()));
|
|
|
|
LOG(INFO) << "Bind mounting " << sdcardfs_path << " to " << pass_through_path;
|
|
return BindMount(sdcardfs_path, pass_through_path);
|
|
} else {
|
|
LOG(INFO) << "Bind mounting " << absolute_lower_path << " to " << pass_through_path;
|
|
return BindMount(absolute_lower_path, pass_through_path);
|
|
}
|
|
}
|
|
|
|
status_t UnmountUserFuse(userid_t user_id, const std::string& absolute_lower_path,
|
|
const std::string& relative_upper_path) {
|
|
std::string fuse_path(StringPrintf("/mnt/user/%d/%s", user_id, relative_upper_path.c_str()));
|
|
std::string pass_through_path(
|
|
StringPrintf("/mnt/pass_through/%d/%s", user_id, relative_upper_path.c_str()));
|
|
|
|
LOG(INFO) << "Unmounting fuse path " << fuse_path;
|
|
android::status_t result = ForceUnmount(fuse_path);
|
|
if (result != android::OK) {
|
|
// TODO(b/135341433): MNT_DETACH is needed for fuse because umount2 can fail with EBUSY.
|
|
// Figure out why we get EBUSY and remove this special casing if possible.
|
|
PLOG(ERROR) << "Failed to unmount. Trying MNT_DETACH " << fuse_path << " ...";
|
|
if (umount2(fuse_path.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH) && errno != EINVAL &&
|
|
errno != ENOENT) {
|
|
PLOG(ERROR) << "Failed to unmount with MNT_DETACH " << fuse_path;
|
|
return -errno;
|
|
}
|
|
result = android::OK;
|
|
}
|
|
rmdir(fuse_path.c_str());
|
|
|
|
LOG(INFO) << "Unmounting pass_through_path " << pass_through_path;
|
|
auto status = ForceUnmount(pass_through_path);
|
|
if (status != android::OK) {
|
|
LOG(ERROR) << "Failed to unmount " << pass_through_path;
|
|
}
|
|
rmdir(pass_through_path.c_str());
|
|
|
|
return result;
|
|
}
|
|
|
|
status_t PrepareAndroidDirs(const std::string& volumeRoot) {
|
|
std::string androidDir = volumeRoot + kAndroidDir;
|
|
std::string androidDataDir = volumeRoot + kAppDataDir;
|
|
std::string androidObbDir = volumeRoot + kAppObbDir;
|
|
std::string androidMediaDir = volumeRoot + kAppMediaDir;
|
|
|
|
bool useSdcardFs = IsSdcardfsUsed();
|
|
|
|
// mode 0771 + sticky bit for inheriting GIDs
|
|
mode_t mode = S_IRWXU | S_IRWXG | S_IXOTH | S_ISGID;
|
|
if (fs_prepare_dir(androidDir.c_str(), mode, AID_MEDIA_RW, AID_MEDIA_RW) != 0) {
|
|
PLOG(ERROR) << "Failed to create " << androidDir;
|
|
return -errno;
|
|
}
|
|
|
|
gid_t dataGid = useSdcardFs ? AID_MEDIA_RW : AID_EXT_DATA_RW;
|
|
if (fs_prepare_dir(androidDataDir.c_str(), mode, AID_MEDIA_RW, dataGid) != 0) {
|
|
PLOG(ERROR) << "Failed to create " << androidDataDir;
|
|
return -errno;
|
|
}
|
|
|
|
gid_t obbGid = useSdcardFs ? AID_MEDIA_RW : AID_EXT_OBB_RW;
|
|
if (fs_prepare_dir(androidObbDir.c_str(), mode, AID_MEDIA_RW, obbGid) != 0) {
|
|
PLOG(ERROR) << "Failed to create " << androidObbDir;
|
|
return -errno;
|
|
}
|
|
// Some other apps, like installers, have write access to the OBB directory
|
|
// to pre-download them. To make sure newly created folders in this directory
|
|
// have the right permissions, set a default ACL.
|
|
SetDefaultAcl(androidObbDir, mode, AID_MEDIA_RW, obbGid, {});
|
|
|
|
if (fs_prepare_dir(androidMediaDir.c_str(), mode, AID_MEDIA_RW, AID_MEDIA_RW) != 0) {
|
|
PLOG(ERROR) << "Failed to create " << androidMediaDir;
|
|
return -errno;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
namespace ab = android::base;
|
|
|
|
static ab::unique_fd openDirFd(int parentFd, const char* name) {
|
|
return ab::unique_fd{::openat(parentFd, name, O_CLOEXEC | O_DIRECTORY | O_PATH | O_NOFOLLOW)};
|
|
}
|
|
|
|
static ab::unique_fd openAbsolutePathFd(std::string_view path) {
|
|
if (path.empty() || path[0] != '/') {
|
|
errno = EINVAL;
|
|
return {};
|
|
}
|
|
if (path == "/") {
|
|
return openDirFd(-1, "/");
|
|
}
|
|
|
|
// first component is special - it includes the leading slash
|
|
auto next = path.find('/', 1);
|
|
auto component = std::string(path.substr(0, next));
|
|
if (component == "..") {
|
|
errno = EINVAL;
|
|
return {};
|
|
}
|
|
auto fd = openDirFd(-1, component.c_str());
|
|
if (!fd.ok()) {
|
|
return fd;
|
|
}
|
|
path.remove_prefix(std::min(next + 1, path.size()));
|
|
while (next != path.npos && !path.empty()) {
|
|
next = path.find('/');
|
|
component.assign(path.substr(0, next));
|
|
fd = openDirFd(fd, component.c_str());
|
|
if (!fd.ok()) {
|
|
return fd;
|
|
}
|
|
path.remove_prefix(std::min(next + 1, path.size()));
|
|
}
|
|
return fd;
|
|
}
|
|
|
|
std::pair<android::base::unique_fd, std::string> OpenDirInProcfs(std::string_view path) {
|
|
auto fd = openAbsolutePathFd(path);
|
|
if (!fd.ok()) {
|
|
return {};
|
|
}
|
|
|
|
auto linkPath = std::string("/proc/self/fd/") += std::to_string(fd.get());
|
|
return {std::move(fd), std::move(linkPath)};
|
|
}
|
|
|
|
bool IsFuseBpfEnabled() {
|
|
// TODO Once kernel supports flag, trigger off kernel flag unless
|
|
// ro.fuse.bpf.enabled is explicitly set to false
|
|
bool enabled;
|
|
if (base::GetProperty("ro.fuse.bpf.is_running", "") != "")
|
|
enabled = base::GetBoolProperty("ro.fuse.bpf.is_running", false);
|
|
else if (base::GetProperty("persist.sys.fuse.bpf.override", "") != "")
|
|
enabled = base::GetBoolProperty("persist.sys.fuse.bpf.override", false);
|
|
else
|
|
enabled = base::GetBoolProperty("ro.fuse.bpf.enabled", false);
|
|
|
|
if (enabled) {
|
|
base::SetProperty("ro.fuse.bpf.is_running", "true");
|
|
return true;
|
|
} else {
|
|
base::SetProperty("ro.fuse.bpf.is_running", "false");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
} // namespace vold
|
|
} // namespace android
|