summaryrefslogtreecommitdiffstats
path: root/voldclient.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'voldclient.cpp')
-rw-r--r--voldclient.cpp466
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;
+ }
+ }
+}