466 lines
12 KiB
C++
466 lines
12 KiB
C++
/*
|
|
* Copyright (c) 2013 The CyanogenMod 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <pthread.h>
|
|
|
|
#include <string>
|
|
#include <sstream>
|
|
|
|
#include <cutils/properties.h>
|
|
#include <cutils/sockets.h>
|
|
|
|
#include "common.h"
|
|
#include "roots.h"
|
|
#include "voldclient.h"
|
|
|
|
#include "VolumeBase.h"
|
|
#include "ResponseCode.h"
|
|
|
|
using namespace android::vold;
|
|
|
|
VoldClient* vdc = NULL;
|
|
|
|
static void* threadfunc(void* arg)
|
|
{
|
|
VoldClient* self = (VoldClient*)arg;
|
|
self->run();
|
|
return NULL;
|
|
}
|
|
|
|
VoldClient::VoldClient(VoldWatcher* watcher /* = nullptr */) :
|
|
mRunning(false),
|
|
mSock(-1),
|
|
mSockMutex(PTHREAD_MUTEX_INITIALIZER),
|
|
mSockCond(PTHREAD_COND_INITIALIZER),
|
|
mInFlight(0),
|
|
mResult(0),
|
|
mWatcher(watcher),
|
|
mVolumeLock(PTHREAD_RWLOCK_INITIALIZER),
|
|
mVolumeChanged(false),
|
|
mEmulatedStorage(true)
|
|
{
|
|
}
|
|
|
|
void VoldClient::start(void)
|
|
{
|
|
mRunning = true;
|
|
pthread_create(&mThread, NULL, threadfunc, this);
|
|
while (mSock == -1) {
|
|
sleep(1);
|
|
}
|
|
while (mInFlight != 0) {
|
|
sleep(1);
|
|
}
|
|
LOGI("VoldClient initialized, storage is %s\n",
|
|
vdc->isEmulatedStorage() ? "emulated" : "physical");
|
|
}
|
|
|
|
void VoldClient::stop(void)
|
|
{
|
|
if (mRunning) {
|
|
mRunning = false;
|
|
close(mSock);
|
|
mSock = -1;
|
|
void* retval;
|
|
pthread_join(mThread, &retval);
|
|
}
|
|
}
|
|
|
|
VolumeInfo VoldClient::getVolume(const std::string& id)
|
|
{
|
|
pthread_rwlock_wrlock(&mVolumeLock);
|
|
VolumeInfo* info = getVolumeLocked(id);
|
|
pthread_rwlock_unlock(&mVolumeLock);
|
|
return *info;
|
|
}
|
|
|
|
bool VoldClient::reset(void)
|
|
{
|
|
const char *cmd[2] = { "volume", "reset" };
|
|
return sendCommand(2, cmd);
|
|
}
|
|
|
|
bool VoldClient::mountAll(void)
|
|
{
|
|
bool ret = true;
|
|
pthread_rwlock_rdlock(&mVolumeLock);
|
|
for (auto& info : mVolumes) {
|
|
if (info.mState == (int)VolumeBase::State::kUnmounted) {
|
|
if (!volumeMount(info.mId)) {
|
|
ret = false;
|
|
}
|
|
}
|
|
}
|
|
pthread_rwlock_unlock(&mVolumeLock);
|
|
return ret;
|
|
}
|
|
|
|
bool VoldClient::unmountAll(void)
|
|
{
|
|
bool ret = true;
|
|
pthread_rwlock_rdlock(&mVolumeLock);
|
|
for (auto& info : mVolumes) {
|
|
if (info.mState == (int)VolumeBase::State::kMounted) {
|
|
if (!volumeUnmount(info.mId)) {
|
|
ret = false;
|
|
}
|
|
}
|
|
}
|
|
pthread_rwlock_unlock(&mVolumeLock);
|
|
return ret;
|
|
}
|
|
|
|
bool VoldClient::volumeMount(const std::string& id)
|
|
{
|
|
// Special case for emulated storage
|
|
if (id == "emulated") {
|
|
pthread_rwlock_wrlock(&mVolumeLock);
|
|
VolumeInfo* info = getVolumeLocked(id);
|
|
if (!info) {
|
|
pthread_rwlock_unlock(&mVolumeLock);
|
|
return false;
|
|
}
|
|
info->mPath = "/storage/emulated";
|
|
info->mInternalPath = "/data/media";
|
|
pthread_rwlock_unlock(&mVolumeLock);
|
|
return ensure_path_mounted("/data") == 0;
|
|
}
|
|
const char *cmd[3] = { "volume", "mount", id.c_str() };
|
|
return sendCommand(3, cmd);
|
|
}
|
|
|
|
// NB: can only force or detach, not both
|
|
bool VoldClient::volumeUnmount(const std::string& id, bool detach /* = false */)
|
|
{
|
|
// Special case for emulated storage
|
|
if (id == "emulated") {
|
|
if (ensure_path_unmounted("/data", detach) != 0) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
const char *cmd[4] = { "volume", "unmount", id.c_str(), NULL };
|
|
int cmdlen = 3;
|
|
if (detach) {
|
|
cmd[3] = "detach";
|
|
cmdlen = 4;
|
|
}
|
|
return sendCommand(cmdlen, cmd);
|
|
}
|
|
|
|
bool VoldClient::volumeFormat(const std::string& id)
|
|
{
|
|
const char* cmd[3] = { "volume", "format", id.c_str() };
|
|
return sendCommand(3, cmd);
|
|
}
|
|
|
|
void VoldClient::resetVolumeState(void)
|
|
{
|
|
pthread_rwlock_wrlock(&mVolumeLock);
|
|
mVolumes.clear();
|
|
mVolumeChanged = false;
|
|
mEmulatedStorage = true;
|
|
pthread_rwlock_unlock(&mVolumeLock);
|
|
if (mWatcher) {
|
|
mWatcher->onVolumeChanged();
|
|
}
|
|
const char *cmd[2] = { "volume", "reset" };
|
|
sendCommand(2, cmd, false);
|
|
}
|
|
|
|
VolumeInfo* VoldClient::getVolumeLocked(const std::string& id)
|
|
{
|
|
for (auto iter = mVolumes.begin(); iter != mVolumes.end(); ++iter) {
|
|
if (iter->mId == id) {
|
|
return &(*iter);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool VoldClient::sendCommand(unsigned int len, const char** command, bool wait /* = true */)
|
|
{
|
|
char line[4096];
|
|
char* p;
|
|
unsigned int i;
|
|
size_t sz;
|
|
bool ret = true;
|
|
|
|
p = line;
|
|
p += sprintf(p, "0 "); /* 0 is a (now required) sequence number */
|
|
for (i = 0; i < len; i++) {
|
|
const char* cmd = command[i];
|
|
if (!cmd[0] || !strchr(cmd, ' '))
|
|
p += sprintf(p, "%s", cmd);
|
|
else
|
|
p += sprintf(p, "\"%s\"", cmd);
|
|
if (i < len - 1)
|
|
*p++ = ' ';
|
|
if (p >= line + sizeof(line)) {
|
|
LOGE("vold command line too long\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
// only one writer at a time
|
|
pthread_mutex_lock(&mSockMutex);
|
|
if (write(mSock, line, (p - line) + 1) < 0) {
|
|
LOGE("Unable to send command to vold!\n");
|
|
pthread_mutex_unlock(&mSockMutex);
|
|
return false;
|
|
}
|
|
++mInFlight;
|
|
|
|
if (wait) {
|
|
while (mInFlight) {
|
|
// wait for completion
|
|
pthread_cond_wait(&mSockCond, &mSockMutex);
|
|
}
|
|
ret = (mResult >= 200 && mResult < 300);
|
|
}
|
|
pthread_mutex_unlock(&mSockMutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void VoldClient::handleCommandOkay(void)
|
|
{
|
|
bool changed = false;
|
|
pthread_rwlock_wrlock(&mVolumeLock);
|
|
if (mVolumeChanged) {
|
|
mVolumeChanged = false;
|
|
changed = true;
|
|
}
|
|
pthread_rwlock_unlock(&mVolumeLock);
|
|
if (changed) {
|
|
mWatcher->onVolumeChanged();
|
|
}
|
|
}
|
|
|
|
void VoldClient::handleVolumeCreated(const std::string& id, const std::string& type,
|
|
const std::string& disk, const std::string& guid)
|
|
{
|
|
pthread_rwlock_wrlock(&mVolumeLock);
|
|
// Ignore emulated storage if primary storage is physical
|
|
if (id == "emulated") {
|
|
char value[PROPERTY_VALUE_MAX];
|
|
property_get("ro.vold.primary_physical", value, "0");
|
|
if (value[0] == '1' || value[0] == 'y' || !strcmp(value, "true")) {
|
|
mEmulatedStorage = false;
|
|
return;
|
|
}
|
|
mEmulatedStorage = true;
|
|
}
|
|
VolumeInfo info;
|
|
info.mId = id;
|
|
mVolumes.push_back(info);
|
|
pthread_rwlock_unlock(&mVolumeLock);
|
|
}
|
|
|
|
void VoldClient::handleVolumeStateChanged(const std::string& id, const std::string& state)
|
|
{
|
|
pthread_rwlock_wrlock(&mVolumeLock);
|
|
auto info = getVolumeLocked(id);
|
|
if (info) {
|
|
info->mState = atoi(state.c_str());
|
|
}
|
|
pthread_rwlock_unlock(&mVolumeLock);
|
|
}
|
|
|
|
void VoldClient::handleVolumeFsLabelChanged(const std::string& id, const std::string& label)
|
|
{
|
|
pthread_rwlock_wrlock(&mVolumeLock);
|
|
auto info = getVolumeLocked(id);
|
|
if (info) {
|
|
info->mLabel = label;
|
|
}
|
|
pthread_rwlock_unlock(&mVolumeLock);
|
|
}
|
|
|
|
void VoldClient::handleVolumePathChanged(const std::string& id, const std::string& path)
|
|
{
|
|
pthread_rwlock_wrlock(&mVolumeLock);
|
|
auto info = getVolumeLocked(id);
|
|
if (info) {
|
|
info->mPath = path;
|
|
}
|
|
pthread_rwlock_unlock(&mVolumeLock);
|
|
}
|
|
|
|
void VoldClient::handleVolumeInternalPathChanged(const std::string& id, const std::string& path)
|
|
{
|
|
pthread_rwlock_wrlock(&mVolumeLock);
|
|
auto info = getVolumeLocked(id);
|
|
if (info) {
|
|
info->mInternalPath = path;
|
|
}
|
|
pthread_rwlock_unlock(&mVolumeLock);
|
|
}
|
|
|
|
void VoldClient::handleVolumeDestroyed(const std::string& id)
|
|
{
|
|
pthread_rwlock_wrlock(&mVolumeLock);
|
|
for (auto iter = mVolumes.begin(); iter != mVolumes.end(); ++iter) {
|
|
if (iter->mId == id) {
|
|
mVolumes.erase(iter);
|
|
break;
|
|
}
|
|
}
|
|
pthread_rwlock_unlock(&mVolumeLock);
|
|
}
|
|
|
|
static std::vector<std::string> split(const std::string& line)
|
|
{
|
|
std::vector<std::string> tokens;
|
|
const char* tok = line.c_str();
|
|
|
|
while (*tok) {
|
|
unsigned int toklen;
|
|
const char* next;
|
|
if (*tok == '"') {
|
|
++tok;
|
|
const char* q = strchr(tok, '"');
|
|
if (!q) {
|
|
LOGE("vold line <%s> malformed\n", line.c_str());
|
|
exit(1);
|
|
}
|
|
toklen = q - tok;
|
|
next = q + 1;
|
|
if (*next) {
|
|
if (*next != ' ') {
|
|
LOGE("vold line <%s> malformed\n", line.c_str());
|
|
exit(0);
|
|
}
|
|
++next;
|
|
}
|
|
}
|
|
else {
|
|
next = strchr(tok, ' ');
|
|
if (next) {
|
|
toklen = next - tok;
|
|
++next;
|
|
}
|
|
else {
|
|
toklen = strlen(tok);
|
|
next = tok + toklen;
|
|
}
|
|
}
|
|
tokens.push_back(std::string(tok, toklen));
|
|
tok = next;
|
|
}
|
|
|
|
return tokens;
|
|
}
|
|
|
|
void VoldClient::dispatch(const std::string& line)
|
|
{
|
|
std::vector<std::string> tokens = split(line);
|
|
|
|
switch (mResult) {
|
|
case ResponseCode::CommandOkay:
|
|
handleCommandOkay();
|
|
break;
|
|
case ResponseCode::VolumeCreated:
|
|
handleVolumeCreated(tokens[1], tokens[2], tokens[3], tokens[4]);
|
|
break;
|
|
case ResponseCode::VolumeStateChanged:
|
|
handleVolumeStateChanged(tokens[1], tokens[2]);
|
|
break;
|
|
case ResponseCode::VolumeFsLabelChanged:
|
|
handleVolumeFsLabelChanged(tokens[1], tokens[2]);
|
|
break;
|
|
case ResponseCode::VolumePathChanged:
|
|
handleVolumePathChanged(tokens[1], tokens[2]);
|
|
break;
|
|
case ResponseCode::VolumeInternalPathChanged:
|
|
handleVolumeInternalPathChanged(tokens[1], tokens[2]);
|
|
break;
|
|
case ResponseCode::VolumeDestroyed:
|
|
handleVolumeDestroyed(tokens[1]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VoldClient::run(void)
|
|
{
|
|
LOGI("VoldClient thread starting\n");
|
|
while (mRunning) {
|
|
if (mSock == -1) {
|
|
LOGI("Connecting to Vold...\n");
|
|
mSock = socket_local_client("vold", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM);
|
|
if (mSock == -1) {
|
|
sleep(1);
|
|
continue;
|
|
}
|
|
resetVolumeState();
|
|
}
|
|
|
|
int rc;
|
|
|
|
struct timeval tv;
|
|
fd_set rfds;
|
|
|
|
memset(&tv, 0, sizeof(tv));
|
|
tv.tv_usec = 100 * 1000;
|
|
FD_ZERO(&rfds);
|
|
FD_SET(mSock, &rfds);
|
|
|
|
rc = select(mSock + 1, &rfds, NULL, NULL, &tv);
|
|
if (rc <= 0) {
|
|
if (rc < 0 && errno != EINTR) {
|
|
LOGE("vdc: error in select (%s)\n", strerror(errno));
|
|
close(mSock);
|
|
mSock = -1;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
char buf[4096];
|
|
memset(buf, 0, sizeof(buf));
|
|
rc = read(mSock, buf, sizeof(buf) - 1);
|
|
if (rc <= 0) {
|
|
LOGE("vdc: read failed: %s\n", (rc == 0 ? "EOF" : strerror(errno)));
|
|
close(mSock);
|
|
mSock = -1;
|
|
continue;
|
|
}
|
|
|
|
// dispatch each line of the response
|
|
int nread = rc;
|
|
int off = 0;
|
|
while (off < nread) {
|
|
char* eol = (char*)memchr(buf + off, 0, nread - off);
|
|
if (!eol) {
|
|
break;
|
|
}
|
|
mResult = atoi(buf + off);
|
|
dispatch(std::string(buf + off));
|
|
if (mResult >= 200 && mResult < 600) {
|
|
pthread_mutex_lock(&mSockMutex);
|
|
--mInFlight;
|
|
pthread_cond_signal(&mSockCond);
|
|
pthread_mutex_unlock(&mSockMutex);
|
|
}
|
|
off = (eol - buf) + 1;
|
|
}
|
|
}
|
|
}
|