Let's reinvent storage, yet again!
Now that we're treating storage as a runtime permission, we need to grant read/write access without killing the app. This is really tricky, since we had been using GIDs for access control, and they're set in stone once Zygote drops privileges. The only thing left that can change dynamically is the filesystem itself, so let's do that. This means changing the FUSE daemon to present itself as three different views: /mnt/runtime_default/foo - view for apps with no access /mnt/runtime_read/foo - view for apps with read access /mnt/runtime_write/foo - view for apps with write access There is still a single location for all the backing files, and filesystem permissions are derived the same way for each view, but the file modes are masked off differently for each mountpoint. During Zygote fork, it wires up the appropriate storage access into an isolated mount namespace based on the current app permissions. When the app is granted permissions dynamically at runtime, the system asks vold to jump into the existing mount namespace and bind mount the newly granted access model into place. Bug: 21858077 Change-Id: Iade538e4bc7af979fe20095f74416e8a0f165a4a
This commit is contained in:
parent
5d268fdac4
commit
66270a21df
10 changed files with 200 additions and 53 deletions
|
@ -263,6 +263,12 @@ int CommandListener::VolumeCmd::runCommand(SocketClient *cli,
|
||||||
// forget_partition [partGuid]
|
// forget_partition [partGuid]
|
||||||
std::string partGuid(argv[2]);
|
std::string partGuid(argv[2]);
|
||||||
return sendGenericOkFail(cli, vm->forgetPartition(partGuid));
|
return sendGenericOkFail(cli, vm->forgetPartition(partGuid));
|
||||||
|
|
||||||
|
} else if (cmd == "remount_uid" && argc > 3) {
|
||||||
|
// remount_uid [uid] [none|default|read|write]
|
||||||
|
uid_t uid = atoi(argv[2]);
|
||||||
|
std::string mode(argv[3]);
|
||||||
|
return sendGenericOkFail(cli, vm->remountUid(uid, mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
return cli->sendMsg(ResponseCode::CommandSyntaxError, nullptr, false);
|
return cli->sendMsg(ResponseCode::CommandSyntaxError, nullptr, false);
|
||||||
|
|
|
@ -39,37 +39,45 @@ static const char* kFusePath = "/system/bin/sdcard";
|
||||||
EmulatedVolume::EmulatedVolume(const std::string& rawPath) :
|
EmulatedVolume::EmulatedVolume(const std::string& rawPath) :
|
||||||
VolumeBase(Type::kEmulated), mFusePid(0) {
|
VolumeBase(Type::kEmulated), mFusePid(0) {
|
||||||
setId("emulated");
|
setId("emulated");
|
||||||
mFusePath = "/storage/emulated";
|
|
||||||
mRawPath = rawPath;
|
mRawPath = rawPath;
|
||||||
|
mLabel = "emulated";
|
||||||
}
|
}
|
||||||
|
|
||||||
EmulatedVolume::EmulatedVolume(const std::string& rawPath, dev_t device,
|
EmulatedVolume::EmulatedVolume(const std::string& rawPath, dev_t device,
|
||||||
const std::string& fsUuid) : VolumeBase(Type::kEmulated), mFusePid(0) {
|
const std::string& fsUuid) : VolumeBase(Type::kEmulated), mFusePid(0) {
|
||||||
setId(StringPrintf("emulated:%u,%u", major(device), minor(device)));
|
setId(StringPrintf("emulated:%u,%u", major(device), minor(device)));
|
||||||
mFusePath = StringPrintf("/storage/%s", fsUuid.c_str());
|
|
||||||
mRawPath = rawPath;
|
mRawPath = rawPath;
|
||||||
|
mLabel = fsUuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
EmulatedVolume::~EmulatedVolume() {
|
EmulatedVolume::~EmulatedVolume() {
|
||||||
}
|
}
|
||||||
|
|
||||||
status_t EmulatedVolume::doMount() {
|
status_t EmulatedVolume::doMount() {
|
||||||
if (fs_prepare_dir(mFusePath.c_str(), 0700, AID_ROOT, AID_ROOT)) {
|
mFuseDefault = StringPrintf("/mnt/runtime_default/%s", mLabel.c_str());
|
||||||
PLOG(ERROR) << getId() << " failed to create mount point " << mFusePath;
|
mFuseRead = StringPrintf("/mnt/runtime_read/%s", mLabel.c_str());
|
||||||
|
mFuseWrite = StringPrintf("/mnt/runtime_write/%s", mLabel.c_str());
|
||||||
|
|
||||||
|
setInternalPath(mRawPath);
|
||||||
|
setPath(StringPrintf("/storage/%s", mLabel.c_str()));
|
||||||
|
|
||||||
|
if (fs_prepare_dir(mFuseDefault.c_str(), 0700, AID_ROOT, AID_ROOT) ||
|
||||||
|
fs_prepare_dir(mFuseRead.c_str(), 0700, AID_ROOT, AID_ROOT) ||
|
||||||
|
fs_prepare_dir(mFuseWrite.c_str(), 0700, AID_ROOT, AID_ROOT)) {
|
||||||
|
PLOG(ERROR) << getId() << " failed to create mount points";
|
||||||
return -errno;
|
return -errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
setInternalPath(mRawPath);
|
dev_t before = GetDevice(mFuseWrite);
|
||||||
setPath(mFusePath);
|
|
||||||
|
|
||||||
if (!(mFusePid = fork())) {
|
if (!(mFusePid = fork())) {
|
||||||
// TODO: protect when not mounted as visible
|
|
||||||
if (execl(kFusePath, kFusePath,
|
if (execl(kFusePath, kFusePath,
|
||||||
"-u", "1023", // AID_MEDIA_RW
|
"-u", "1023", // AID_MEDIA_RW
|
||||||
"-g", "1023", // AID_MEDIA_RW
|
"-g", "1023", // AID_MEDIA_RW
|
||||||
"-l",
|
"-m",
|
||||||
|
"-w",
|
||||||
mRawPath.c_str(),
|
mRawPath.c_str(),
|
||||||
mFusePath.c_str(),
|
mLabel.c_str(),
|
||||||
NULL)) {
|
NULL)) {
|
||||||
PLOG(ERROR) << "Failed to exec";
|
PLOG(ERROR) << "Failed to exec";
|
||||||
}
|
}
|
||||||
|
@ -83,6 +91,11 @@ status_t EmulatedVolume::doMount() {
|
||||||
return -errno;
|
return -errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while (before == GetDevice(mFuseWrite)) {
|
||||||
|
LOG(VERBOSE) << "Waiting for FUSE to spin up...";
|
||||||
|
usleep(50000); // 50ms
|
||||||
|
}
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,13 +106,17 @@ status_t EmulatedVolume::doUnmount() {
|
||||||
mFusePid = 0;
|
mFusePid = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ForceUnmount(mFusePath);
|
ForceUnmount(mFuseDefault);
|
||||||
ForceUnmount(mRawPath);
|
ForceUnmount(mFuseRead);
|
||||||
|
ForceUnmount(mFuseWrite);
|
||||||
|
|
||||||
if (TEMP_FAILURE_RETRY(rmdir(mFusePath.c_str()))) {
|
rmdir(mFuseDefault.c_str());
|
||||||
PLOG(ERROR) << getId() << " failed to rmdir mount point " << mFusePath;
|
rmdir(mFuseRead.c_str());
|
||||||
return -errno;
|
rmdir(mFuseWrite.c_str());
|
||||||
}
|
|
||||||
|
mFuseDefault.clear();
|
||||||
|
mFuseRead.clear();
|
||||||
|
mFuseWrite.clear();
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,10 +46,13 @@ protected:
|
||||||
status_t doUnmount() override;
|
status_t doUnmount() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/* Mount point of raw storage */
|
|
||||||
std::string mRawPath;
|
std::string mRawPath;
|
||||||
/* Mount point of visible storage */
|
std::string mLabel;
|
||||||
std::string mFusePath;
|
|
||||||
|
std::string mFuseDefault;
|
||||||
|
std::string mFuseRead;
|
||||||
|
std::string mFuseWrite;
|
||||||
|
|
||||||
/* PID of FUSE wrapper */
|
/* PID of FUSE wrapper */
|
||||||
pid_t mFusePid;
|
pid_t mFusePid;
|
||||||
|
|
||||||
|
|
|
@ -111,16 +111,19 @@ status_t PublicVolume::doMount() {
|
||||||
}
|
}
|
||||||
|
|
||||||
mRawPath = StringPrintf("/mnt/media_rw/%s", stableName.c_str());
|
mRawPath = StringPrintf("/mnt/media_rw/%s", stableName.c_str());
|
||||||
mFusePath = StringPrintf("/storage/%s", stableName.c_str());
|
|
||||||
setInternalPath(mRawPath);
|
|
||||||
setPath(mFusePath);
|
|
||||||
|
|
||||||
if (fs_prepare_dir(mRawPath.c_str(), 0700, AID_ROOT, AID_ROOT)) {
|
mFuseDefault = StringPrintf("/mnt/runtime_default/%s", stableName.c_str());
|
||||||
PLOG(ERROR) << getId() << " failed to create mount point " << mRawPath;
|
mFuseRead = StringPrintf("/mnt/runtime_read/%s", stableName.c_str());
|
||||||
return -errno;
|
mFuseWrite = StringPrintf("/mnt/runtime_write/%s", stableName.c_str());
|
||||||
}
|
|
||||||
if (fs_prepare_dir(mFusePath.c_str(), 0700, AID_ROOT, AID_ROOT)) {
|
setInternalPath(mRawPath);
|
||||||
PLOG(ERROR) << getId() << " failed to create mount point " << mFusePath;
|
setPath(StringPrintf("/storage/%s", stableName.c_str()));
|
||||||
|
|
||||||
|
if (fs_prepare_dir(mRawPath.c_str(), 0700, AID_ROOT, AID_ROOT) ||
|
||||||
|
fs_prepare_dir(mFuseDefault.c_str(), 0700, AID_ROOT, AID_ROOT) ||
|
||||||
|
fs_prepare_dir(mFuseRead.c_str(), 0700, AID_ROOT, AID_ROOT) ||
|
||||||
|
fs_prepare_dir(mFuseWrite.c_str(), 0700, AID_ROOT, AID_ROOT)) {
|
||||||
|
PLOG(ERROR) << getId() << " failed to create mount points";
|
||||||
return -errno;
|
return -errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,25 +137,18 @@ status_t PublicVolume::doMount() {
|
||||||
initAsecStage();
|
initAsecStage();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: teach FUSE daemon to protect itself with user-specific GID
|
dev_t before = GetDevice(mFuseWrite);
|
||||||
|
|
||||||
if (!(mFusePid = fork())) {
|
if (!(mFusePid = fork())) {
|
||||||
if (!(getMountFlags() & MountFlags::kVisible)) {
|
if (!(getMountFlags() & MountFlags::kVisible)) {
|
||||||
// TODO: mount so that only system apps can access
|
// TODO: do we need to wrap this device?
|
||||||
if (execl(kFusePath, kFusePath,
|
|
||||||
"-u", "1023", // AID_MEDIA_RW
|
|
||||||
"-g", "1023", // AID_MEDIA_RW
|
|
||||||
mRawPath.c_str(),
|
|
||||||
mFusePath.c_str(),
|
|
||||||
NULL)) {
|
|
||||||
PLOG(ERROR) << "Failed to exec";
|
|
||||||
}
|
|
||||||
} else if (getMountFlags() & MountFlags::kPrimary) {
|
} else if (getMountFlags() & MountFlags::kPrimary) {
|
||||||
if (execl(kFusePath, kFusePath,
|
if (execl(kFusePath, kFusePath,
|
||||||
"-u", "1023", // AID_MEDIA_RW
|
"-u", "1023", // AID_MEDIA_RW
|
||||||
"-g", "1023", // AID_MEDIA_RW
|
"-g", "1023", // AID_MEDIA_RW
|
||||||
"-d",
|
"-w",
|
||||||
mRawPath.c_str(),
|
mRawPath.c_str(),
|
||||||
mFusePath.c_str(),
|
stableName.c_str(),
|
||||||
NULL)) {
|
NULL)) {
|
||||||
PLOG(ERROR) << "Failed to exec";
|
PLOG(ERROR) << "Failed to exec";
|
||||||
}
|
}
|
||||||
|
@ -160,10 +156,8 @@ status_t PublicVolume::doMount() {
|
||||||
if (execl(kFusePath, kFusePath,
|
if (execl(kFusePath, kFusePath,
|
||||||
"-u", "1023", // AID_MEDIA_RW
|
"-u", "1023", // AID_MEDIA_RW
|
||||||
"-g", "1023", // AID_MEDIA_RW
|
"-g", "1023", // AID_MEDIA_RW
|
||||||
"-w", "1023", // AID_MEDIA_RW
|
|
||||||
"-d",
|
|
||||||
mRawPath.c_str(),
|
mRawPath.c_str(),
|
||||||
mFusePath.c_str(),
|
stableName.c_str(),
|
||||||
NULL)) {
|
NULL)) {
|
||||||
PLOG(ERROR) << "Failed to exec";
|
PLOG(ERROR) << "Failed to exec";
|
||||||
}
|
}
|
||||||
|
@ -178,6 +172,11 @@ status_t PublicVolume::doMount() {
|
||||||
return -errno;
|
return -errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while (before == GetDevice(mFuseWrite)) {
|
||||||
|
LOG(VERBOSE) << "Waiting for FUSE to spin up...";
|
||||||
|
usleep(50000); // 50ms
|
||||||
|
}
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,17 +188,20 @@ status_t PublicVolume::doUnmount() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ForceUnmount(kAsecPath);
|
ForceUnmount(kAsecPath);
|
||||||
ForceUnmount(mFusePath);
|
|
||||||
|
ForceUnmount(mFuseDefault);
|
||||||
|
ForceUnmount(mFuseRead);
|
||||||
|
ForceUnmount(mFuseWrite);
|
||||||
ForceUnmount(mRawPath);
|
ForceUnmount(mRawPath);
|
||||||
|
|
||||||
if (TEMP_FAILURE_RETRY(rmdir(mRawPath.c_str()))) {
|
rmdir(mFuseDefault.c_str());
|
||||||
PLOG(ERROR) << getId() << " failed to rmdir mount point " << mRawPath;
|
rmdir(mFuseRead.c_str());
|
||||||
}
|
rmdir(mFuseWrite.c_str());
|
||||||
if (TEMP_FAILURE_RETRY(rmdir(mFusePath.c_str()))) {
|
rmdir(mRawPath.c_str());
|
||||||
PLOG(ERROR) << getId() << " failed to rmdir mount point " << mFusePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
mFusePath.clear();
|
mFuseDefault.clear();
|
||||||
|
mFuseRead.clear();
|
||||||
|
mFuseWrite.clear();
|
||||||
mRawPath.clear();
|
mRawPath.clear();
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
|
|
|
@ -59,8 +59,11 @@ private:
|
||||||
std::string mDevPath;
|
std::string mDevPath;
|
||||||
/* Mount point of raw partition */
|
/* Mount point of raw partition */
|
||||||
std::string mRawPath;
|
std::string mRawPath;
|
||||||
/* Mount point of FUSE wrapper */
|
|
||||||
std::string mFusePath;
|
std::string mFuseDefault;
|
||||||
|
std::string mFuseRead;
|
||||||
|
std::string mFuseWrite;
|
||||||
|
|
||||||
/* PID of FUSE wrapper */
|
/* PID of FUSE wrapper */
|
||||||
pid_t mFusePid;
|
pid_t mFusePid;
|
||||||
|
|
||||||
|
|
10
Utils.cpp
10
Utils.cpp
|
@ -522,5 +522,15 @@ std::string BuildKeyPath(const std::string& partGuid) {
|
||||||
return StringPrintf("%s/expand_%s.key", kKeyPath, partGuid.c_str());
|
return StringPrintf("%s/expand_%s.key", kKeyPath, partGuid.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace vold
|
} // namespace vold
|
||||||
} // namespace android
|
} // namespace android
|
||||||
|
|
2
Utils.h
2
Utils.h
|
@ -90,6 +90,8 @@ status_t WipeBlockDevice(const std::string& path);
|
||||||
|
|
||||||
std::string BuildKeyPath(const std::string& partGuid);
|
std::string BuildKeyPath(const std::string& partGuid);
|
||||||
|
|
||||||
|
dev_t GetDevice(const std::string& path);
|
||||||
|
|
||||||
} // namespace vold
|
} // namespace vold
|
||||||
} // namespace android
|
} // namespace android
|
||||||
|
|
||||||
|
|
|
@ -219,7 +219,6 @@ status_t VolumeBase::unmount() {
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(State::kEjecting);
|
setState(State::kEjecting);
|
||||||
|
|
||||||
for (auto vol : mVolumes) {
|
for (auto vol : mVolumes) {
|
||||||
if (vol->destroy()) {
|
if (vol->destroy()) {
|
||||||
LOG(WARNING) << getId() << " failed to destroy " << vol->getId()
|
LOG(WARNING) << getId() << " failed to destroy " << vol->getId()
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include <sys/mount.h>
|
#include <sys/mount.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <linux/kdev_t.h>
|
#include <linux/kdev_t.h>
|
||||||
|
@ -480,6 +481,108 @@ int VolumeManager::setPrimary(const std::shared_ptr<android::vold::VolumeBase>&
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int VolumeManager::remountUid(uid_t uid, const std::string& mode) {
|
||||||
|
LOG(DEBUG) << "Remounting " << uid << " as mode " << mode;
|
||||||
|
|
||||||
|
DIR* dir;
|
||||||
|
struct dirent* de;
|
||||||
|
char rootName[PATH_MAX];
|
||||||
|
char pidName[PATH_MAX];
|
||||||
|
int pidFd;
|
||||||
|
int nsFd;
|
||||||
|
struct stat sb;
|
||||||
|
pid_t child;
|
||||||
|
|
||||||
|
if (!(dir = opendir("/proc"))) {
|
||||||
|
PLOG(ERROR) << "Failed to opendir";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure out root namespace to compare against below
|
||||||
|
if (readlinkat(dirfd(dir), "1/ns/mnt", rootName, PATH_MAX) == -1) {
|
||||||
|
PLOG(ERROR) << "Failed to readlink";
|
||||||
|
closedir(dir);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poke through all running PIDs look for apps running as UID
|
||||||
|
while ((de = readdir(dir))) {
|
||||||
|
pidFd = -1;
|
||||||
|
nsFd = -1;
|
||||||
|
|
||||||
|
pidFd = openat(dirfd(dir), de->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||||||
|
if (pidFd < 0) {
|
||||||
|
goto next;
|
||||||
|
}
|
||||||
|
if (fstat(pidFd, &sb) != 0) {
|
||||||
|
PLOG(WARNING) << "Failed to stat " << de->d_name;
|
||||||
|
goto next;
|
||||||
|
}
|
||||||
|
if (sb.st_uid != uid) {
|
||||||
|
goto next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches so far, but refuse to touch if in root namespace
|
||||||
|
LOG(DEBUG) << "Found matching PID " << de->d_name;
|
||||||
|
if (readlinkat(pidFd, "ns/mnt", pidName, PATH_MAX) == -1) {
|
||||||
|
PLOG(WARNING) << "Failed to read namespace for " << de->d_name;
|
||||||
|
goto next;
|
||||||
|
}
|
||||||
|
if (!strcmp(rootName, pidName)) {
|
||||||
|
LOG(WARNING) << "Skipping due to root namespace";
|
||||||
|
goto next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We purposefully leave the namespace open across the fork
|
||||||
|
nsFd = openat(pidFd, "ns/mnt", O_RDONLY);
|
||||||
|
if (nsFd < 0) {
|
||||||
|
PLOG(WARNING) << "Failed to open namespace";
|
||||||
|
goto next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(child = fork())) {
|
||||||
|
if (setns(nsFd, CLONE_NEWNS) != 0) {
|
||||||
|
PLOG(ERROR) << "Failed to setns";
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmount current view and replace with requested view
|
||||||
|
umount2("/storage", MNT_FORCE);
|
||||||
|
|
||||||
|
std::string storageSource;
|
||||||
|
if (mode == "default") {
|
||||||
|
storageSource = "/mnt/runtime_default";
|
||||||
|
} else if (mode == "read") {
|
||||||
|
storageSource = "/mnt/runtime_read";
|
||||||
|
} else if (mode == "write") {
|
||||||
|
storageSource = "/mnt/runtime_write";
|
||||||
|
} else {
|
||||||
|
// Sane default of no storage visible
|
||||||
|
_exit(0);
|
||||||
|
}
|
||||||
|
if (TEMP_FAILURE_RETRY(mount(storageSource.c_str(), "/storage",
|
||||||
|
NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
|
||||||
|
PLOG(WARNING) << "Failed to mount " << storageSource;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child == -1) {
|
||||||
|
PLOG(ERROR) << "Failed to fork";
|
||||||
|
goto next;
|
||||||
|
} else {
|
||||||
|
TEMP_FAILURE_RETRY(waitpid(child, nullptr, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
next:
|
||||||
|
close(nsFd);
|
||||||
|
close(pidFd);
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int VolumeManager::reset() {
|
int VolumeManager::reset() {
|
||||||
// Tear down all existing disks/volumes and start from a blank slate so
|
// Tear down all existing disks/volumes and start from a blank slate so
|
||||||
// newly connected framework hears all events.
|
// newly connected framework hears all events.
|
||||||
|
|
|
@ -129,6 +129,8 @@ public:
|
||||||
|
|
||||||
int setPrimary(const std::shared_ptr<android::vold::VolumeBase>& vol);
|
int setPrimary(const std::shared_ptr<android::vold::VolumeBase>& vol);
|
||||||
|
|
||||||
|
int remountUid(uid_t uid, const std::string& mode);
|
||||||
|
|
||||||
/* Reset all internal state, typically during framework boot */
|
/* Reset all internal state, typically during framework boot */
|
||||||
int reset();
|
int reset();
|
||||||
/* Prepare for device shutdown, safely unmounting all devices */
|
/* Prepare for device shutdown, safely unmounting all devices */
|
||||||
|
|
Loading…
Reference in a new issue