1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/system/gonk/VolumeManager.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,425 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#include "VolumeManager.h" 1.9 + 1.10 +#include "Volume.h" 1.11 +#include "VolumeCommand.h" 1.12 +#include "VolumeManagerLog.h" 1.13 +#include "VolumeServiceTest.h" 1.14 + 1.15 +#include "nsWhitespaceTokenizer.h" 1.16 +#include "nsXULAppAPI.h" 1.17 + 1.18 +#include "base/message_loop.h" 1.19 +#include "mozilla/Scoped.h" 1.20 +#include "mozilla/StaticPtr.h" 1.21 + 1.22 +#include <android/log.h> 1.23 +#include <cutils/sockets.h> 1.24 +#include <fcntl.h> 1.25 +#include <sys/socket.h> 1.26 + 1.27 +namespace mozilla { 1.28 +namespace system { 1.29 + 1.30 +static StaticRefPtr<VolumeManager> sVolumeManager; 1.31 + 1.32 +VolumeManager::STATE VolumeManager::mState = VolumeManager::UNINITIALIZED; 1.33 +VolumeManager::StateObserverList VolumeManager::mStateObserverList; 1.34 + 1.35 +/***************************************************************************/ 1.36 + 1.37 +VolumeManager::VolumeManager() 1.38 + : LineWatcher('\0', kRcvBufSize), 1.39 + mSocket(-1), 1.40 + mCommandPending(false) 1.41 +{ 1.42 + DBG("VolumeManager constructor called"); 1.43 +} 1.44 + 1.45 +VolumeManager::~VolumeManager() 1.46 +{ 1.47 +} 1.48 + 1.49 +//static 1.50 +size_t 1.51 +VolumeManager::NumVolumes() 1.52 +{ 1.53 + if (!sVolumeManager) { 1.54 + return 0; 1.55 + } 1.56 + return sVolumeManager->mVolumeArray.Length(); 1.57 +} 1.58 + 1.59 +//static 1.60 +TemporaryRef<Volume> 1.61 +VolumeManager::GetVolume(size_t aIndex) 1.62 +{ 1.63 + MOZ_ASSERT(aIndex < NumVolumes()); 1.64 + return sVolumeManager->mVolumeArray[aIndex]; 1.65 +} 1.66 + 1.67 +//static 1.68 +VolumeManager::STATE 1.69 +VolumeManager::State() 1.70 +{ 1.71 + return mState; 1.72 +} 1.73 + 1.74 +//static 1.75 +const char * 1.76 +VolumeManager::StateStr(VolumeManager::STATE aState) 1.77 +{ 1.78 + switch (aState) { 1.79 + case UNINITIALIZED: return "Uninitialized"; 1.80 + case STARTING: return "Starting"; 1.81 + case VOLUMES_READY: return "Volumes Ready"; 1.82 + } 1.83 + return "???"; 1.84 +} 1.85 + 1.86 + 1.87 +//static 1.88 +void 1.89 +VolumeManager::SetState(STATE aNewState) 1.90 +{ 1.91 + if (mState != aNewState) { 1.92 + LOG("changing state from '%s' to '%s'", 1.93 + StateStr(mState), StateStr(aNewState)); 1.94 + mState = aNewState; 1.95 + mStateObserverList.Broadcast(StateChangedEvent()); 1.96 + } 1.97 +} 1.98 + 1.99 +//static 1.100 +void 1.101 +VolumeManager::RegisterStateObserver(StateObserver* aObserver) 1.102 +{ 1.103 + mStateObserverList.AddObserver(aObserver); 1.104 +} 1.105 + 1.106 +//static 1.107 +void VolumeManager::UnregisterStateObserver(StateObserver* aObserver) 1.108 +{ 1.109 + mStateObserverList.RemoveObserver(aObserver); 1.110 +} 1.111 + 1.112 +//static 1.113 +TemporaryRef<Volume> 1.114 +VolumeManager::FindVolumeByName(const nsCSubstring& aName) 1.115 +{ 1.116 + if (!sVolumeManager) { 1.117 + return nullptr; 1.118 + } 1.119 + VolumeArray::size_type numVolumes = NumVolumes(); 1.120 + VolumeArray::index_type volIndex; 1.121 + for (volIndex = 0; volIndex < numVolumes; volIndex++) { 1.122 + RefPtr<Volume> vol = GetVolume(volIndex); 1.123 + if (vol->Name().Equals(aName)) { 1.124 + return vol; 1.125 + } 1.126 + } 1.127 + return nullptr; 1.128 +} 1.129 + 1.130 +//static 1.131 +TemporaryRef<Volume> 1.132 +VolumeManager::FindAddVolumeByName(const nsCSubstring& aName) 1.133 +{ 1.134 + RefPtr<Volume> vol = FindVolumeByName(aName); 1.135 + if (vol) { 1.136 + return vol; 1.137 + } 1.138 + // No volume found, create and add a new one. 1.139 + vol = new Volume(aName); 1.140 + sVolumeManager->mVolumeArray.AppendElement(vol); 1.141 + return vol; 1.142 +} 1.143 + 1.144 +class VolumeListCallback : public VolumeResponseCallback 1.145 +{ 1.146 + virtual void ResponseReceived(const VolumeCommand* aCommand) 1.147 + { 1.148 + switch (ResponseCode()) { 1.149 + case ResponseCode::VolumeListResult: { 1.150 + // Each line will look something like: 1.151 + // 1.152 + // sdcard /mnt/sdcard 1 1.153 + // 1.154 + // So for each volume that we get back, we update any volumes that 1.155 + // we have of the same name, or add new ones if they don't exist. 1.156 + nsCWhitespaceTokenizer tokenizer(ResponseStr()); 1.157 + nsDependentCSubstring volName(tokenizer.nextToken()); 1.158 + RefPtr<Volume> vol = VolumeManager::FindAddVolumeByName(volName); 1.159 + vol->HandleVoldResponse(ResponseCode(), tokenizer); 1.160 + break; 1.161 + } 1.162 + 1.163 + case ResponseCode::CommandOkay: { 1.164 + // We've received the list of volumes. Tell anybody who 1.165 + // is listening that we're open for business. 1.166 + VolumeManager::SetState(VolumeManager::VOLUMES_READY); 1.167 + break; 1.168 + } 1.169 + } 1.170 + } 1.171 +}; 1.172 + 1.173 +bool 1.174 +VolumeManager::OpenSocket() 1.175 +{ 1.176 + SetState(STARTING); 1.177 + if ((mSocket.rwget() = socket_local_client("vold", 1.178 + ANDROID_SOCKET_NAMESPACE_RESERVED, 1.179 + SOCK_STREAM)) < 0) { 1.180 + ERR("Error connecting to vold: (%s) - will retry", strerror(errno)); 1.181 + return false; 1.182 + } 1.183 + // add FD_CLOEXEC flag 1.184 + int flags = fcntl(mSocket.get(), F_GETFD); 1.185 + if (flags == -1) { 1.186 + return false; 1.187 + } 1.188 + flags |= FD_CLOEXEC; 1.189 + if (fcntl(mSocket.get(), F_SETFD, flags) == -1) { 1.190 + return false; 1.191 + } 1.192 + // set non-blocking 1.193 + if (fcntl(mSocket.get(), F_SETFL, O_NONBLOCK) == -1) { 1.194 + return false; 1.195 + } 1.196 + if (!MessageLoopForIO::current()-> 1.197 + WatchFileDescriptor(mSocket.get(), 1.198 + true, 1.199 + MessageLoopForIO::WATCH_READ, 1.200 + &mReadWatcher, 1.201 + this)) { 1.202 + return false; 1.203 + } 1.204 + 1.205 + LOG("Connected to vold"); 1.206 + PostCommand(new VolumeListCommand(new VolumeListCallback)); 1.207 + return true; 1.208 +} 1.209 + 1.210 +//static 1.211 +void 1.212 +VolumeManager::PostCommand(VolumeCommand* aCommand) 1.213 +{ 1.214 + if (!sVolumeManager) { 1.215 + ERR("VolumeManager not initialized. Dropping command '%s'", aCommand->Data()); 1.216 + return; 1.217 + } 1.218 + aCommand->SetPending(true); 1.219 + 1.220 + DBG("Sending command '%s'", aCommand->Data()); 1.221 + // vold can only process one command at a time, so add our command 1.222 + // to the end of the command queue. 1.223 + sVolumeManager->mCommands.push(aCommand); 1.224 + if (!sVolumeManager->mCommandPending) { 1.225 + // There aren't any commands currently being processed, so go 1.226 + // ahead and kick this one off. 1.227 + sVolumeManager->mCommandPending = true; 1.228 + sVolumeManager->WriteCommandData(); 1.229 + } 1.230 +} 1.231 + 1.232 +/*************************************************************************** 1.233 +* The WriteCommandData initiates sending of a command to vold. Since 1.234 +* we're running on the IOThread and not allowed to block, WriteCommandData 1.235 +* will write as much data as it can, and if not all of the data can be 1.236 +* written then it will setup a file descriptor watcher and 1.237 +* OnFileCanWriteWithoutBlocking will call WriteCommandData to write out 1.238 +* more of the command data. 1.239 +*/ 1.240 +void 1.241 +VolumeManager::WriteCommandData() 1.242 +{ 1.243 + if (mCommands.size() == 0) { 1.244 + return; 1.245 + } 1.246 + 1.247 + VolumeCommand* cmd = mCommands.front(); 1.248 + if (cmd->BytesRemaining() == 0) { 1.249 + // All bytes have been written. We're waiting for a response. 1.250 + return; 1.251 + } 1.252 + // There are more bytes left to write. Try to write them all. 1.253 + ssize_t bytesWritten = write(mSocket.get(), cmd->Data(), cmd->BytesRemaining()); 1.254 + if (bytesWritten < 0) { 1.255 + ERR("Failed to write %d bytes to vold socket", cmd->BytesRemaining()); 1.256 + Restart(); 1.257 + return; 1.258 + } 1.259 + DBG("Wrote %ld bytes (of %d)", bytesWritten, cmd->BytesRemaining()); 1.260 + cmd->ConsumeBytes(bytesWritten); 1.261 + if (cmd->BytesRemaining() == 0) { 1.262 + return; 1.263 + } 1.264 + // We were unable to write all of the command bytes. Setup a watcher 1.265 + // so we'll get called again when we can write without blocking. 1.266 + if (!MessageLoopForIO::current()-> 1.267 + WatchFileDescriptor(mSocket.get(), 1.268 + false, // one-shot 1.269 + MessageLoopForIO::WATCH_WRITE, 1.270 + &mWriteWatcher, 1.271 + this)) { 1.272 + ERR("Failed to setup write watcher for vold socket"); 1.273 + Restart(); 1.274 + } 1.275 +} 1.276 + 1.277 +void 1.278 +VolumeManager::OnLineRead(int aFd, nsDependentCSubstring& aMessage) 1.279 +{ 1.280 + MOZ_ASSERT(aFd == mSocket.get()); 1.281 + char* endPtr; 1.282 + int responseCode = strtol(aMessage.Data(), &endPtr, 10); 1.283 + if (*endPtr == ' ') { 1.284 + endPtr++; 1.285 + } 1.286 + 1.287 + // Now fish out the rest of the line after the response code 1.288 + nsDependentCString responseLine(endPtr, aMessage.Length() - (endPtr - aMessage.Data())); 1.289 + DBG("Rcvd: %d '%s'", responseCode, responseLine.Data()); 1.290 + 1.291 + if (responseCode >= ResponseCode::UnsolicitedInformational) { 1.292 + // These are unsolicited broadcasts. We intercept these and process 1.293 + // them ourselves 1.294 + HandleBroadcast(responseCode, responseLine); 1.295 + } else { 1.296 + // Everything else is considered to be part of the command response. 1.297 + if (mCommands.size() > 0) { 1.298 + VolumeCommand* cmd = mCommands.front(); 1.299 + cmd->HandleResponse(responseCode, responseLine); 1.300 + if (responseCode >= ResponseCode::CommandOkay) { 1.301 + // That's a terminating response. We can remove the command. 1.302 + mCommands.pop(); 1.303 + mCommandPending = false; 1.304 + // Start the next command, if there is one. 1.305 + WriteCommandData(); 1.306 + } 1.307 + } else { 1.308 + ERR("Response with no command"); 1.309 + } 1.310 + } 1.311 +} 1.312 + 1.313 +void 1.314 +VolumeManager::OnFileCanWriteWithoutBlocking(int aFd) 1.315 +{ 1.316 + MOZ_ASSERT(aFd == mSocket.get()); 1.317 + WriteCommandData(); 1.318 +} 1.319 + 1.320 +void 1.321 +VolumeManager::HandleBroadcast(int aResponseCode, nsCString& aResponseLine) 1.322 +{ 1.323 + // Format of the line is something like: 1.324 + // 1.325 + // Volume sdcard /mnt/sdcard state changed from 7 (Shared-Unmounted) to 1 (Idle-Unmounted) 1.326 + // 1.327 + // So we parse out the volume name and the state after the string " to " 1.328 + nsCWhitespaceTokenizer tokenizer(aResponseLine); 1.329 + tokenizer.nextToken(); // The word "Volume" 1.330 + nsDependentCSubstring volName(tokenizer.nextToken()); 1.331 + 1.332 + RefPtr<Volume> vol = FindVolumeByName(volName); 1.333 + if (!vol) { 1.334 + return; 1.335 + } 1.336 + vol->HandleVoldResponse(aResponseCode, tokenizer); 1.337 +} 1.338 + 1.339 +void 1.340 +VolumeManager::Restart() 1.341 +{ 1.342 + mReadWatcher.StopWatchingFileDescriptor(); 1.343 + mWriteWatcher.StopWatchingFileDescriptor(); 1.344 + 1.345 + while (!mCommands.empty()) { 1.346 + mCommands.pop(); 1.347 + } 1.348 + mCommandPending = false; 1.349 + mSocket.dispose(); 1.350 + Start(); 1.351 +} 1.352 + 1.353 +//static 1.354 +void 1.355 +VolumeManager::Start() 1.356 +{ 1.357 + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); 1.358 + 1.359 + if (!sVolumeManager) { 1.360 + return; 1.361 + } 1.362 + SetState(STARTING); 1.363 + if (!sVolumeManager->OpenSocket()) { 1.364 + // Socket open failed, try again in a second. 1.365 + MessageLoopForIO::current()-> 1.366 + PostDelayedTask(FROM_HERE, 1.367 + NewRunnableFunction(VolumeManager::Start), 1.368 + 1000); 1.369 + } 1.370 +} 1.371 + 1.372 +void 1.373 +VolumeManager::OnError() 1.374 +{ 1.375 + Restart(); 1.376 +} 1.377 + 1.378 +/***************************************************************************/ 1.379 + 1.380 +static void 1.381 +InitVolumeManagerIOThread() 1.382 +{ 1.383 + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); 1.384 + MOZ_ASSERT(!sVolumeManager); 1.385 + 1.386 + sVolumeManager = new VolumeManager(); 1.387 + VolumeManager::Start(); 1.388 + 1.389 + InitVolumeServiceTestIOThread(); 1.390 +} 1.391 + 1.392 +static void 1.393 +ShutdownVolumeManagerIOThread() 1.394 +{ 1.395 + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); 1.396 + 1.397 + sVolumeManager = nullptr; 1.398 +} 1.399 + 1.400 +/************************************************************************** 1.401 +* 1.402 +* Public API 1.403 +* 1.404 +* Since the VolumeManager runs in IO Thread context, we need to switch 1.405 +* to IOThread context before we can do anything. 1.406 +* 1.407 +**************************************************************************/ 1.408 + 1.409 +void 1.410 +InitVolumeManager() 1.411 +{ 1.412 + XRE_GetIOMessageLoop()->PostTask( 1.413 + FROM_HERE, 1.414 + NewRunnableFunction(InitVolumeManagerIOThread)); 1.415 +} 1.416 + 1.417 +void 1.418 +ShutdownVolumeManager() 1.419 +{ 1.420 + ShutdownVolumeServiceTest(); 1.421 + 1.422 + XRE_GetIOMessageLoop()->PostTask( 1.423 + FROM_HERE, 1.424 + NewRunnableFunction(ShutdownVolumeManagerIOThread)); 1.425 +} 1.426 + 1.427 +} // system 1.428 +} // mozilla