/* * 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 #include #include #include #include #include #include #include #include #include #include #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 split(const std::string& line) { std::vector 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 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; } } }