Migrate primary external storage.

When requested, kick off a thread that will migrate storage contents
between two locations.  This is performed in several steps that
also interact with the framework:

1. Take old and new volumes offline during migration
2. Wipe new location clean (10% of progress)
3. Copy files from old to new (60% of progress)
4. Inform framework that move was successful so it can persist
5. Wipe old location clean (15% of progress)

Derives a hacky progress estimate by using a rough proxy of free
disk space changes while a cp/rm is taking place.

Add new internal path for direct access to volumes to bypass any
FUSE emulation overhead, and send it to framework.  Remove mutex
around various exec calls since setexeccon() is already per-thread.

Bug: 19993667
Change-Id: Ibcb4f6fe0126d05b2365f316f53e71dc3e79a2b8
This commit is contained in:
Jeff Sharkey 2015-04-24 16:00:03 -07:00
parent c8e04c5a82
commit 1d6fbcc389
11 changed files with 435 additions and 29 deletions

View file

@ -22,6 +22,7 @@ common_src_files := \
PrivateVolume.cpp \
EmulatedVolume.cpp \
Utils.cpp \
MoveTask.cpp \
common_c_includes := \
system/extras/ext4_utils \

View file

@ -41,6 +41,7 @@
#include "Devmapper.h"
#include "cryptfs.h"
#include "fstrim.h"
#include "MoveTask.h"
#define DUMP_ARGS 0
@ -226,6 +227,17 @@ int CommandListener::VolumeCmd::runCommand(SocketClient *cli,
}
return sendGenericOkFail(cli, vol->format());
} else if (cmd == "move_storage" && argc > 3) {
// move_storage [fromVolId] [toVolId]
auto fromVol = vm->findVolume(std::string(argv[2]));
auto toVol = vm->findVolume(std::string(argv[3]));
if (fromVol == nullptr || toVol == nullptr) {
return cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown volume", false);
}
(new android::vold::MoveTask(fromVol, toVol))->start();
return sendGenericOkFail(cli, 0);
}
return cli->sendMsg(ResponseCode::CommandSyntaxError, nullptr, false);

View file

@ -60,8 +60,10 @@ status_t EmulatedVolume::doMount() {
}
setPath(mFusePath);
setInternalPath(mRawPath);
if (!(mFusePid = fork())) {
// TODO: protect when not mounted as visible
if (execl(kFusePath, kFusePath,
"-u", "1023", // AID_MEDIA_RW
"-g", "1023", // AID_MEDIA_RW

217
MoveTask.cpp Normal file
View file

@ -0,0 +1,217 @@
/*
* 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 "MoveTask.h"
#include "Utils.h"
#include "VolumeManager.h"
#include "ResponseCode.h"
#include <base/stringprintf.h>
#include <base/logging.h>
#include <private/android_filesystem_config.h>
#include <dirent.h>
#include <sys/wait.h>
#define CONSTRAIN(amount, low, high) (amount < low ? low : (amount > high ? high : amount))
using android::base::StringPrintf;
namespace android {
namespace vold {
// TODO: keep in sync with PackageManager
static const int kMoveSucceeded = -100;
static const int kMoveFailedInternalError = -6;
static const char* kCpPath = "/system/bin/cp";
static const char* kRmPath = "/system/bin/rm";
MoveTask::MoveTask(const std::shared_ptr<VolumeBase>& from,
const std::shared_ptr<VolumeBase>& to) :
mFrom(from), mTo(to) {
}
MoveTask::~MoveTask() {
}
void MoveTask::start() {
mThread = std::thread(&MoveTask::run, this);
}
static void notifyProgress(int progress) {
VolumeManager::Instance()->getBroadcaster()->sendBroadcast(ResponseCode::MoveStatus,
StringPrintf("%d", progress).c_str(), false);
}
static status_t pushBackContents(const std::string& path, std::vector<std::string>& cmd) {
DIR* dir = opendir(path.c_str());
if (dir == NULL) {
return -1;
}
bool found = false;
struct dirent* ent;
while ((ent = readdir(dir)) != NULL) {
if ((!strcmp(ent->d_name, ".")) || (!strcmp(ent->d_name, ".."))) {
continue;
}
cmd.push_back(StringPrintf("%s/%s", path.c_str(), ent->d_name));
found = true;
}
closedir(dir);
return found ? OK : -1;
}
static status_t execRm(const std::string& path, int startProgress, int stepProgress) {
notifyProgress(startProgress);
uint64_t expectedBytes = GetTreeBytes(path);
uint64_t startFreeBytes = GetFreeBytes(path);
std::vector<std::string> cmd;
cmd.push_back(kRmPath);
cmd.push_back("-f"); /* force: remove without confirmation, no error if it doesn't exist */
cmd.push_back("-R"); /* recursive: remove directory contents */
if (pushBackContents(path, cmd) != OK) {
LOG(WARNING) << "No contents in " << path;
return OK;
}
pid_t pid = ForkExecvpAsync(cmd);
if (pid == -1) return -1;
int status;
while (true) {
if (waitpid(pid, &status, WNOHANG) == pid) {
if (WIFEXITED(status)) {
LOG(DEBUG) << "Finished rm with status " << WEXITSTATUS(status);
return (WEXITSTATUS(status) == 0) ? OK : -1;
} else {
break;
}
}
sleep(1);
uint64_t deltaFreeBytes = GetFreeBytes(path) - startFreeBytes;
notifyProgress(startProgress + CONSTRAIN((int)
((deltaFreeBytes * stepProgress) / expectedBytes), 0, stepProgress));
}
return -1;
}
static status_t execCp(const std::string& fromPath, const std::string& toPath,
int startProgress, int stepProgress) {
notifyProgress(startProgress);
uint64_t expectedBytes = GetTreeBytes(fromPath);
uint64_t startFreeBytes = GetFreeBytes(toPath);
std::vector<std::string> cmd;
cmd.push_back(kCpPath);
cmd.push_back("-p"); /* preserve timestamps, ownership, and permissions */
cmd.push_back("-R"); /* recurse into subdirectories (DEST must be a directory) */
cmd.push_back("-P"); /* Do not follow symlinks [default] */
cmd.push_back("-d"); /* don't dereference symlinks */
if (pushBackContents(fromPath, cmd) != OK) {
LOG(WARNING) << "No contents in " << fromPath;
return OK;
}
cmd.push_back(toPath.c_str());
pid_t pid = ForkExecvpAsync(cmd);
if (pid == -1) return -1;
int status;
while (true) {
if (waitpid(pid, &status, WNOHANG) == pid) {
if (WIFEXITED(status)) {
LOG(DEBUG) << "Finished cp with status " << WEXITSTATUS(status);
return (WEXITSTATUS(status) == 0) ? OK : -1;
} else {
break;
}
}
sleep(1);
uint64_t deltaFreeBytes = startFreeBytes - GetFreeBytes(toPath);
notifyProgress(startProgress + CONSTRAIN((int)
((deltaFreeBytes * stepProgress) / expectedBytes), 0, stepProgress));
}
return -1;
}
static void bringOffline(const std::shared_ptr<VolumeBase>& vol) {
vol->destroy();
vol->setSilent(true);
vol->create();
vol->setMountFlags(0);
vol->mount();
}
static void bringOnline(const std::shared_ptr<VolumeBase>& vol) {
vol->destroy();
vol->setSilent(false);
vol->create();
}
void MoveTask::run() {
std::string fromPath;
std::string toPath;
// TODO: add support for public volumes
if (mFrom->getType() != VolumeBase::Type::kEmulated) goto fail;
if (mTo->getType() != VolumeBase::Type::kEmulated) goto fail;
// Step 1: tear down volumes and mount silently without making
// visible to userspace apps
bringOffline(mFrom);
bringOffline(mTo);
fromPath = mFrom->getInternalPath();
toPath = mTo->getInternalPath();
// Step 2: clean up any stale data
if (execRm(toPath, 10, 10) != OK) {
goto fail;
}
// Step 3: perform actual copy
if (execCp(fromPath, toPath, 20, 60) != OK) {
goto fail;
}
// NOTE: MountService watches for this magic value to know
// that move was successful
notifyProgress(82);
bringOnline(mFrom);
bringOnline(mTo);
// Step 4: clean up old data
if (execRm(fromPath, 85, 15) != OK) {
goto fail;
}
notifyProgress(kMoveSucceeded);
return;
fail:
bringOnline(mFrom);
bringOnline(mTo);
notifyProgress(kMoveFailedInternalError);
return;
}
} // namespace vold
} // namespace android

48
MoveTask.h Normal file
View file

@ -0,0 +1,48 @@
/*
* 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.
*/
#ifndef ANDROID_VOLD_MOVE_TASK_H
#define ANDROID_VOLD_MOVE_TASK_H
#include "Utils.h"
#include "VolumeBase.h"
#include <thread>
namespace android {
namespace vold {
class MoveTask {
public:
MoveTask(const std::shared_ptr<VolumeBase>& from, const std::shared_ptr<VolumeBase>& to);
virtual ~MoveTask();
void start();
private:
std::shared_ptr<VolumeBase> mFrom;
std::shared_ptr<VolumeBase> mTo;
std::thread mThread;
void run();
DISALLOW_COPY_AND_ASSIGN(MoveTask);
};
} // namespace vold
} // namespace android
#endif

View file

@ -101,7 +101,10 @@ status_t PrivateVolume::doMount() {
return -EIO;
}
if (Ext4::check(mDmDevPath.c_str(), mPath.c_str())) {
int res = Ext4::check(mDmDevPath.c_str(), mPath.c_str());
if (res == 0 || res == 1) {
LOG(DEBUG) << getId() << " passed filesystem check";
} else {
PLOG(ERROR) << getId() << " failed filesystem check";
return -EIO;
}

View file

@ -78,8 +78,11 @@ public:
static const int VolumeFsUuidChanged = 653;
static const int VolumeFsLabelChanged = 654;
static const int VolumePathChanged = 655;
static const int VolumeInternalPathChanged = 656;
static const int VolumeDestroyed = 659;
static const int MoveStatus = 660;
static int convertFromErrno();
};
#endif

128
Utils.cpp
View file

@ -25,6 +25,7 @@
#include <logwrap/logwrap.h>
#include <mutex>
#include <dirent.h>
#include <fcntl.h>
#include <linux/fs.h>
#include <stdlib.h>
@ -32,6 +33,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/statvfs.h>
#ifndef UMOUNT_NOFOLLOW
#define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */
@ -42,10 +44,6 @@ using android::base::StringPrintf;
namespace android {
namespace vold {
/* Since we use setexeccon(), we need to carefully lock around any
* code that calls exec() to avoid crossing the streams. */
static std::mutex sExecLock;
security_context_t sBlkidContext = nullptr;
security_context_t sBlkidUntrustedContext = nullptr;
security_context_t sFsckContext = nullptr;
@ -224,19 +222,16 @@ status_t ForkExecvp(const std::vector<std::string>& args, security_context_t con
}
}
status_t res = OK;
{
std::lock_guard<std::mutex> lock(sExecLock);
if (setexeccon(context)) {
LOG(ERROR) << "Failed to setexeccon";
abort();
}
res = android_fork_execvp(argc, argv, NULL, false, true);
status_t res = android_fork_execvp(argc, argv, NULL, false, true);
if (setexeccon(nullptr)) {
LOG(ERROR) << "Failed to setexeccon";
abort();
}
}
free(argv);
return res;
}
@ -259,19 +254,15 @@ status_t ForkExecvp(const std::vector<std::string>& args,
}
output.clear();
FILE* fp = nullptr;
{
std::lock_guard<std::mutex> lock(sExecLock);
if (setexeccon(context)) {
LOG(ERROR) << "Failed to setexeccon";
abort();
}
fp = popen(cmd.c_str(), "r");
FILE* fp = popen(cmd.c_str(), "r");
if (setexeccon(nullptr)) {
LOG(ERROR) << "Failed to setexeccon";
abort();
}
}
if (!fp) {
PLOG(ERROR) << "Failed to popen " << cmd;
@ -290,6 +281,39 @@ status_t ForkExecvp(const std::vector<std::string>& args,
return OK;
}
pid_t ForkExecvpAsync(const std::vector<std::string>& args) {
size_t argc = args.size();
char** argv = (char**) calloc(argc + 1, sizeof(char*));
for (size_t i = 0; i < argc; i++) {
argv[i] = (char*) args[i].c_str();
if (i == 0) {
LOG(VERBOSE) << args[i];
} else {
LOG(VERBOSE) << " " << args[i];
}
}
pid_t pid = fork();
if (pid == 0) {
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
if (execvp(argv[0], argv)) {
PLOG(ERROR) << "Failed to exec";
}
_exit(1);
}
if (pid == -1) {
PLOG(ERROR) << "Failed to exec";
}
free(argv);
return pid;
}
status_t ReadRandomBytes(size_t bytes, std::string& out) {
out.clear();
@ -363,5 +387,81 @@ status_t StrToHex(const std::string& str, std::string& hex) {
return OK;
}
uint64_t GetFreeBytes(const std::string& path) {
struct statvfs sb;
if (statvfs(path.c_str(), &sb) == 0) {
return sb.f_bfree * sb.f_bsize;
} 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 (name[0] == '.') {
if (name[1] == 0)
continue;
if ((name[1] == '.') && (name[2] == 0))
continue;
}
subfd = openat(dfd, name, O_RDONLY | O_DIRECTORY);
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_DIRECTORY, O_RDONLY);
if (dirfd < 0) {
PLOG(WARNING) << "Failed to open " << path;
return -1;
} else {
uint64_t res = calculate_dir_size(dirfd);
close(dirfd);
return res;
}
}
} // namespace vold
} // namespace android

View file

@ -69,6 +69,8 @@ status_t ForkExecvp(const std::vector<std::string>& args,
status_t ForkExecvp(const std::vector<std::string>& args,
std::vector<std::string>& output, security_context_t context);
pid_t ForkExecvpAsync(const std::vector<std::string>& args);
status_t ReadRandomBytes(size_t bytes, std::string& out);
/* Converts hex string to raw bytes, ignoring [ :-] */
@ -76,6 +78,9 @@ status_t HexToStr(const std::string& hex, std::string& str);
/* Converts raw bytes to hex string */
status_t StrToHex(const std::string& str, std::string& hex);
uint64_t GetFreeBytes(const std::string& path);
uint64_t GetTreeBytes(const std::string& path);
} // namespace vold
} // namespace android

View file

@ -110,6 +110,17 @@ status_t VolumeBase::setPath(const std::string& path) {
return OK;
}
status_t VolumeBase::setInternalPath(const std::string& internalPath) {
if (mState != State::kChecking) {
LOG(WARNING) << getId() << " internal path change requires state checking";
return -EBUSY;
}
mInternalPath = internalPath;
notifyEvent(ResponseCode::VolumeInternalPathChanged, mInternalPath);
return OK;
}
void VolumeBase::notifyEvent(int event) {
if (mSilent) return;
VolumeManager::Instance()->getBroadcaster()->sendBroadcast(event,

View file

@ -81,6 +81,7 @@ public:
userid_t getMountUserId() { return mMountUserId; }
State getState() { return mState; }
const std::string& getPath() { return mPath; }
const std::string& getInternalPath() { return mInternalPath; }
status_t setDiskId(const std::string& diskId);
status_t setMountFlags(int mountFlags);
@ -109,6 +110,7 @@ protected:
status_t setId(const std::string& id);
status_t setPath(const std::string& path);
status_t setInternalPath(const std::string& internalPath);
void notifyEvent(int msg);
void notifyEvent(int msg, const std::string& value);
@ -130,6 +132,8 @@ private:
State mState;
/* Path to mounted volume */
std::string mPath;
/* Path to internal backing storage */
std::string mInternalPath;
/* Flag indicating that volume should emit no events */
bool mSilent;