diff options
Diffstat (limited to 'voldclient.cpp')
-rw-r--r-- | voldclient.cpp | 466 |
1 files changed, 466 insertions, 0 deletions
diff --git a/voldclient.cpp b/voldclient.cpp new file mode 100644 index 0000000..0c8462a --- /dev/null +++ b/voldclient.cpp @@ -0,0 +1,466 @@ +/* + * 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; + } + } +} |