Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | #include "VolumeManager.h" |
michael@0 | 6 | |
michael@0 | 7 | #include "Volume.h" |
michael@0 | 8 | #include "VolumeCommand.h" |
michael@0 | 9 | #include "VolumeManagerLog.h" |
michael@0 | 10 | #include "VolumeServiceTest.h" |
michael@0 | 11 | |
michael@0 | 12 | #include "nsWhitespaceTokenizer.h" |
michael@0 | 13 | #include "nsXULAppAPI.h" |
michael@0 | 14 | |
michael@0 | 15 | #include "base/message_loop.h" |
michael@0 | 16 | #include "mozilla/Scoped.h" |
michael@0 | 17 | #include "mozilla/StaticPtr.h" |
michael@0 | 18 | |
michael@0 | 19 | #include <android/log.h> |
michael@0 | 20 | #include <cutils/sockets.h> |
michael@0 | 21 | #include <fcntl.h> |
michael@0 | 22 | #include <sys/socket.h> |
michael@0 | 23 | |
michael@0 | 24 | namespace mozilla { |
michael@0 | 25 | namespace system { |
michael@0 | 26 | |
michael@0 | 27 | static StaticRefPtr<VolumeManager> sVolumeManager; |
michael@0 | 28 | |
michael@0 | 29 | VolumeManager::STATE VolumeManager::mState = VolumeManager::UNINITIALIZED; |
michael@0 | 30 | VolumeManager::StateObserverList VolumeManager::mStateObserverList; |
michael@0 | 31 | |
michael@0 | 32 | /***************************************************************************/ |
michael@0 | 33 | |
michael@0 | 34 | VolumeManager::VolumeManager() |
michael@0 | 35 | : LineWatcher('\0', kRcvBufSize), |
michael@0 | 36 | mSocket(-1), |
michael@0 | 37 | mCommandPending(false) |
michael@0 | 38 | { |
michael@0 | 39 | DBG("VolumeManager constructor called"); |
michael@0 | 40 | } |
michael@0 | 41 | |
michael@0 | 42 | VolumeManager::~VolumeManager() |
michael@0 | 43 | { |
michael@0 | 44 | } |
michael@0 | 45 | |
michael@0 | 46 | //static |
michael@0 | 47 | size_t |
michael@0 | 48 | VolumeManager::NumVolumes() |
michael@0 | 49 | { |
michael@0 | 50 | if (!sVolumeManager) { |
michael@0 | 51 | return 0; |
michael@0 | 52 | } |
michael@0 | 53 | return sVolumeManager->mVolumeArray.Length(); |
michael@0 | 54 | } |
michael@0 | 55 | |
michael@0 | 56 | //static |
michael@0 | 57 | TemporaryRef<Volume> |
michael@0 | 58 | VolumeManager::GetVolume(size_t aIndex) |
michael@0 | 59 | { |
michael@0 | 60 | MOZ_ASSERT(aIndex < NumVolumes()); |
michael@0 | 61 | return sVolumeManager->mVolumeArray[aIndex]; |
michael@0 | 62 | } |
michael@0 | 63 | |
michael@0 | 64 | //static |
michael@0 | 65 | VolumeManager::STATE |
michael@0 | 66 | VolumeManager::State() |
michael@0 | 67 | { |
michael@0 | 68 | return mState; |
michael@0 | 69 | } |
michael@0 | 70 | |
michael@0 | 71 | //static |
michael@0 | 72 | const char * |
michael@0 | 73 | VolumeManager::StateStr(VolumeManager::STATE aState) |
michael@0 | 74 | { |
michael@0 | 75 | switch (aState) { |
michael@0 | 76 | case UNINITIALIZED: return "Uninitialized"; |
michael@0 | 77 | case STARTING: return "Starting"; |
michael@0 | 78 | case VOLUMES_READY: return "Volumes Ready"; |
michael@0 | 79 | } |
michael@0 | 80 | return "???"; |
michael@0 | 81 | } |
michael@0 | 82 | |
michael@0 | 83 | |
michael@0 | 84 | //static |
michael@0 | 85 | void |
michael@0 | 86 | VolumeManager::SetState(STATE aNewState) |
michael@0 | 87 | { |
michael@0 | 88 | if (mState != aNewState) { |
michael@0 | 89 | LOG("changing state from '%s' to '%s'", |
michael@0 | 90 | StateStr(mState), StateStr(aNewState)); |
michael@0 | 91 | mState = aNewState; |
michael@0 | 92 | mStateObserverList.Broadcast(StateChangedEvent()); |
michael@0 | 93 | } |
michael@0 | 94 | } |
michael@0 | 95 | |
michael@0 | 96 | //static |
michael@0 | 97 | void |
michael@0 | 98 | VolumeManager::RegisterStateObserver(StateObserver* aObserver) |
michael@0 | 99 | { |
michael@0 | 100 | mStateObserverList.AddObserver(aObserver); |
michael@0 | 101 | } |
michael@0 | 102 | |
michael@0 | 103 | //static |
michael@0 | 104 | void VolumeManager::UnregisterStateObserver(StateObserver* aObserver) |
michael@0 | 105 | { |
michael@0 | 106 | mStateObserverList.RemoveObserver(aObserver); |
michael@0 | 107 | } |
michael@0 | 108 | |
michael@0 | 109 | //static |
michael@0 | 110 | TemporaryRef<Volume> |
michael@0 | 111 | VolumeManager::FindVolumeByName(const nsCSubstring& aName) |
michael@0 | 112 | { |
michael@0 | 113 | if (!sVolumeManager) { |
michael@0 | 114 | return nullptr; |
michael@0 | 115 | } |
michael@0 | 116 | VolumeArray::size_type numVolumes = NumVolumes(); |
michael@0 | 117 | VolumeArray::index_type volIndex; |
michael@0 | 118 | for (volIndex = 0; volIndex < numVolumes; volIndex++) { |
michael@0 | 119 | RefPtr<Volume> vol = GetVolume(volIndex); |
michael@0 | 120 | if (vol->Name().Equals(aName)) { |
michael@0 | 121 | return vol; |
michael@0 | 122 | } |
michael@0 | 123 | } |
michael@0 | 124 | return nullptr; |
michael@0 | 125 | } |
michael@0 | 126 | |
michael@0 | 127 | //static |
michael@0 | 128 | TemporaryRef<Volume> |
michael@0 | 129 | VolumeManager::FindAddVolumeByName(const nsCSubstring& aName) |
michael@0 | 130 | { |
michael@0 | 131 | RefPtr<Volume> vol = FindVolumeByName(aName); |
michael@0 | 132 | if (vol) { |
michael@0 | 133 | return vol; |
michael@0 | 134 | } |
michael@0 | 135 | // No volume found, create and add a new one. |
michael@0 | 136 | vol = new Volume(aName); |
michael@0 | 137 | sVolumeManager->mVolumeArray.AppendElement(vol); |
michael@0 | 138 | return vol; |
michael@0 | 139 | } |
michael@0 | 140 | |
michael@0 | 141 | class VolumeListCallback : public VolumeResponseCallback |
michael@0 | 142 | { |
michael@0 | 143 | virtual void ResponseReceived(const VolumeCommand* aCommand) |
michael@0 | 144 | { |
michael@0 | 145 | switch (ResponseCode()) { |
michael@0 | 146 | case ResponseCode::VolumeListResult: { |
michael@0 | 147 | // Each line will look something like: |
michael@0 | 148 | // |
michael@0 | 149 | // sdcard /mnt/sdcard 1 |
michael@0 | 150 | // |
michael@0 | 151 | // So for each volume that we get back, we update any volumes that |
michael@0 | 152 | // we have of the same name, or add new ones if they don't exist. |
michael@0 | 153 | nsCWhitespaceTokenizer tokenizer(ResponseStr()); |
michael@0 | 154 | nsDependentCSubstring volName(tokenizer.nextToken()); |
michael@0 | 155 | RefPtr<Volume> vol = VolumeManager::FindAddVolumeByName(volName); |
michael@0 | 156 | vol->HandleVoldResponse(ResponseCode(), tokenizer); |
michael@0 | 157 | break; |
michael@0 | 158 | } |
michael@0 | 159 | |
michael@0 | 160 | case ResponseCode::CommandOkay: { |
michael@0 | 161 | // We've received the list of volumes. Tell anybody who |
michael@0 | 162 | // is listening that we're open for business. |
michael@0 | 163 | VolumeManager::SetState(VolumeManager::VOLUMES_READY); |
michael@0 | 164 | break; |
michael@0 | 165 | } |
michael@0 | 166 | } |
michael@0 | 167 | } |
michael@0 | 168 | }; |
michael@0 | 169 | |
michael@0 | 170 | bool |
michael@0 | 171 | VolumeManager::OpenSocket() |
michael@0 | 172 | { |
michael@0 | 173 | SetState(STARTING); |
michael@0 | 174 | if ((mSocket.rwget() = socket_local_client("vold", |
michael@0 | 175 | ANDROID_SOCKET_NAMESPACE_RESERVED, |
michael@0 | 176 | SOCK_STREAM)) < 0) { |
michael@0 | 177 | ERR("Error connecting to vold: (%s) - will retry", strerror(errno)); |
michael@0 | 178 | return false; |
michael@0 | 179 | } |
michael@0 | 180 | // add FD_CLOEXEC flag |
michael@0 | 181 | int flags = fcntl(mSocket.get(), F_GETFD); |
michael@0 | 182 | if (flags == -1) { |
michael@0 | 183 | return false; |
michael@0 | 184 | } |
michael@0 | 185 | flags |= FD_CLOEXEC; |
michael@0 | 186 | if (fcntl(mSocket.get(), F_SETFD, flags) == -1) { |
michael@0 | 187 | return false; |
michael@0 | 188 | } |
michael@0 | 189 | // set non-blocking |
michael@0 | 190 | if (fcntl(mSocket.get(), F_SETFL, O_NONBLOCK) == -1) { |
michael@0 | 191 | return false; |
michael@0 | 192 | } |
michael@0 | 193 | if (!MessageLoopForIO::current()-> |
michael@0 | 194 | WatchFileDescriptor(mSocket.get(), |
michael@0 | 195 | true, |
michael@0 | 196 | MessageLoopForIO::WATCH_READ, |
michael@0 | 197 | &mReadWatcher, |
michael@0 | 198 | this)) { |
michael@0 | 199 | return false; |
michael@0 | 200 | } |
michael@0 | 201 | |
michael@0 | 202 | LOG("Connected to vold"); |
michael@0 | 203 | PostCommand(new VolumeListCommand(new VolumeListCallback)); |
michael@0 | 204 | return true; |
michael@0 | 205 | } |
michael@0 | 206 | |
michael@0 | 207 | //static |
michael@0 | 208 | void |
michael@0 | 209 | VolumeManager::PostCommand(VolumeCommand* aCommand) |
michael@0 | 210 | { |
michael@0 | 211 | if (!sVolumeManager) { |
michael@0 | 212 | ERR("VolumeManager not initialized. Dropping command '%s'", aCommand->Data()); |
michael@0 | 213 | return; |
michael@0 | 214 | } |
michael@0 | 215 | aCommand->SetPending(true); |
michael@0 | 216 | |
michael@0 | 217 | DBG("Sending command '%s'", aCommand->Data()); |
michael@0 | 218 | // vold can only process one command at a time, so add our command |
michael@0 | 219 | // to the end of the command queue. |
michael@0 | 220 | sVolumeManager->mCommands.push(aCommand); |
michael@0 | 221 | if (!sVolumeManager->mCommandPending) { |
michael@0 | 222 | // There aren't any commands currently being processed, so go |
michael@0 | 223 | // ahead and kick this one off. |
michael@0 | 224 | sVolumeManager->mCommandPending = true; |
michael@0 | 225 | sVolumeManager->WriteCommandData(); |
michael@0 | 226 | } |
michael@0 | 227 | } |
michael@0 | 228 | |
michael@0 | 229 | /*************************************************************************** |
michael@0 | 230 | * The WriteCommandData initiates sending of a command to vold. Since |
michael@0 | 231 | * we're running on the IOThread and not allowed to block, WriteCommandData |
michael@0 | 232 | * will write as much data as it can, and if not all of the data can be |
michael@0 | 233 | * written then it will setup a file descriptor watcher and |
michael@0 | 234 | * OnFileCanWriteWithoutBlocking will call WriteCommandData to write out |
michael@0 | 235 | * more of the command data. |
michael@0 | 236 | */ |
michael@0 | 237 | void |
michael@0 | 238 | VolumeManager::WriteCommandData() |
michael@0 | 239 | { |
michael@0 | 240 | if (mCommands.size() == 0) { |
michael@0 | 241 | return; |
michael@0 | 242 | } |
michael@0 | 243 | |
michael@0 | 244 | VolumeCommand* cmd = mCommands.front(); |
michael@0 | 245 | if (cmd->BytesRemaining() == 0) { |
michael@0 | 246 | // All bytes have been written. We're waiting for a response. |
michael@0 | 247 | return; |
michael@0 | 248 | } |
michael@0 | 249 | // There are more bytes left to write. Try to write them all. |
michael@0 | 250 | ssize_t bytesWritten = write(mSocket.get(), cmd->Data(), cmd->BytesRemaining()); |
michael@0 | 251 | if (bytesWritten < 0) { |
michael@0 | 252 | ERR("Failed to write %d bytes to vold socket", cmd->BytesRemaining()); |
michael@0 | 253 | Restart(); |
michael@0 | 254 | return; |
michael@0 | 255 | } |
michael@0 | 256 | DBG("Wrote %ld bytes (of %d)", bytesWritten, cmd->BytesRemaining()); |
michael@0 | 257 | cmd->ConsumeBytes(bytesWritten); |
michael@0 | 258 | if (cmd->BytesRemaining() == 0) { |
michael@0 | 259 | return; |
michael@0 | 260 | } |
michael@0 | 261 | // We were unable to write all of the command bytes. Setup a watcher |
michael@0 | 262 | // so we'll get called again when we can write without blocking. |
michael@0 | 263 | if (!MessageLoopForIO::current()-> |
michael@0 | 264 | WatchFileDescriptor(mSocket.get(), |
michael@0 | 265 | false, // one-shot |
michael@0 | 266 | MessageLoopForIO::WATCH_WRITE, |
michael@0 | 267 | &mWriteWatcher, |
michael@0 | 268 | this)) { |
michael@0 | 269 | ERR("Failed to setup write watcher for vold socket"); |
michael@0 | 270 | Restart(); |
michael@0 | 271 | } |
michael@0 | 272 | } |
michael@0 | 273 | |
michael@0 | 274 | void |
michael@0 | 275 | VolumeManager::OnLineRead(int aFd, nsDependentCSubstring& aMessage) |
michael@0 | 276 | { |
michael@0 | 277 | MOZ_ASSERT(aFd == mSocket.get()); |
michael@0 | 278 | char* endPtr; |
michael@0 | 279 | int responseCode = strtol(aMessage.Data(), &endPtr, 10); |
michael@0 | 280 | if (*endPtr == ' ') { |
michael@0 | 281 | endPtr++; |
michael@0 | 282 | } |
michael@0 | 283 | |
michael@0 | 284 | // Now fish out the rest of the line after the response code |
michael@0 | 285 | nsDependentCString responseLine(endPtr, aMessage.Length() - (endPtr - aMessage.Data())); |
michael@0 | 286 | DBG("Rcvd: %d '%s'", responseCode, responseLine.Data()); |
michael@0 | 287 | |
michael@0 | 288 | if (responseCode >= ResponseCode::UnsolicitedInformational) { |
michael@0 | 289 | // These are unsolicited broadcasts. We intercept these and process |
michael@0 | 290 | // them ourselves |
michael@0 | 291 | HandleBroadcast(responseCode, responseLine); |
michael@0 | 292 | } else { |
michael@0 | 293 | // Everything else is considered to be part of the command response. |
michael@0 | 294 | if (mCommands.size() > 0) { |
michael@0 | 295 | VolumeCommand* cmd = mCommands.front(); |
michael@0 | 296 | cmd->HandleResponse(responseCode, responseLine); |
michael@0 | 297 | if (responseCode >= ResponseCode::CommandOkay) { |
michael@0 | 298 | // That's a terminating response. We can remove the command. |
michael@0 | 299 | mCommands.pop(); |
michael@0 | 300 | mCommandPending = false; |
michael@0 | 301 | // Start the next command, if there is one. |
michael@0 | 302 | WriteCommandData(); |
michael@0 | 303 | } |
michael@0 | 304 | } else { |
michael@0 | 305 | ERR("Response with no command"); |
michael@0 | 306 | } |
michael@0 | 307 | } |
michael@0 | 308 | } |
michael@0 | 309 | |
michael@0 | 310 | void |
michael@0 | 311 | VolumeManager::OnFileCanWriteWithoutBlocking(int aFd) |
michael@0 | 312 | { |
michael@0 | 313 | MOZ_ASSERT(aFd == mSocket.get()); |
michael@0 | 314 | WriteCommandData(); |
michael@0 | 315 | } |
michael@0 | 316 | |
michael@0 | 317 | void |
michael@0 | 318 | VolumeManager::HandleBroadcast(int aResponseCode, nsCString& aResponseLine) |
michael@0 | 319 | { |
michael@0 | 320 | // Format of the line is something like: |
michael@0 | 321 | // |
michael@0 | 322 | // Volume sdcard /mnt/sdcard state changed from 7 (Shared-Unmounted) to 1 (Idle-Unmounted) |
michael@0 | 323 | // |
michael@0 | 324 | // So we parse out the volume name and the state after the string " to " |
michael@0 | 325 | nsCWhitespaceTokenizer tokenizer(aResponseLine); |
michael@0 | 326 | tokenizer.nextToken(); // The word "Volume" |
michael@0 | 327 | nsDependentCSubstring volName(tokenizer.nextToken()); |
michael@0 | 328 | |
michael@0 | 329 | RefPtr<Volume> vol = FindVolumeByName(volName); |
michael@0 | 330 | if (!vol) { |
michael@0 | 331 | return; |
michael@0 | 332 | } |
michael@0 | 333 | vol->HandleVoldResponse(aResponseCode, tokenizer); |
michael@0 | 334 | } |
michael@0 | 335 | |
michael@0 | 336 | void |
michael@0 | 337 | VolumeManager::Restart() |
michael@0 | 338 | { |
michael@0 | 339 | mReadWatcher.StopWatchingFileDescriptor(); |
michael@0 | 340 | mWriteWatcher.StopWatchingFileDescriptor(); |
michael@0 | 341 | |
michael@0 | 342 | while (!mCommands.empty()) { |
michael@0 | 343 | mCommands.pop(); |
michael@0 | 344 | } |
michael@0 | 345 | mCommandPending = false; |
michael@0 | 346 | mSocket.dispose(); |
michael@0 | 347 | Start(); |
michael@0 | 348 | } |
michael@0 | 349 | |
michael@0 | 350 | //static |
michael@0 | 351 | void |
michael@0 | 352 | VolumeManager::Start() |
michael@0 | 353 | { |
michael@0 | 354 | MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); |
michael@0 | 355 | |
michael@0 | 356 | if (!sVolumeManager) { |
michael@0 | 357 | return; |
michael@0 | 358 | } |
michael@0 | 359 | SetState(STARTING); |
michael@0 | 360 | if (!sVolumeManager->OpenSocket()) { |
michael@0 | 361 | // Socket open failed, try again in a second. |
michael@0 | 362 | MessageLoopForIO::current()-> |
michael@0 | 363 | PostDelayedTask(FROM_HERE, |
michael@0 | 364 | NewRunnableFunction(VolumeManager::Start), |
michael@0 | 365 | 1000); |
michael@0 | 366 | } |
michael@0 | 367 | } |
michael@0 | 368 | |
michael@0 | 369 | void |
michael@0 | 370 | VolumeManager::OnError() |
michael@0 | 371 | { |
michael@0 | 372 | Restart(); |
michael@0 | 373 | } |
michael@0 | 374 | |
michael@0 | 375 | /***************************************************************************/ |
michael@0 | 376 | |
michael@0 | 377 | static void |
michael@0 | 378 | InitVolumeManagerIOThread() |
michael@0 | 379 | { |
michael@0 | 380 | MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); |
michael@0 | 381 | MOZ_ASSERT(!sVolumeManager); |
michael@0 | 382 | |
michael@0 | 383 | sVolumeManager = new VolumeManager(); |
michael@0 | 384 | VolumeManager::Start(); |
michael@0 | 385 | |
michael@0 | 386 | InitVolumeServiceTestIOThread(); |
michael@0 | 387 | } |
michael@0 | 388 | |
michael@0 | 389 | static void |
michael@0 | 390 | ShutdownVolumeManagerIOThread() |
michael@0 | 391 | { |
michael@0 | 392 | MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); |
michael@0 | 393 | |
michael@0 | 394 | sVolumeManager = nullptr; |
michael@0 | 395 | } |
michael@0 | 396 | |
michael@0 | 397 | /************************************************************************** |
michael@0 | 398 | * |
michael@0 | 399 | * Public API |
michael@0 | 400 | * |
michael@0 | 401 | * Since the VolumeManager runs in IO Thread context, we need to switch |
michael@0 | 402 | * to IOThread context before we can do anything. |
michael@0 | 403 | * |
michael@0 | 404 | **************************************************************************/ |
michael@0 | 405 | |
michael@0 | 406 | void |
michael@0 | 407 | InitVolumeManager() |
michael@0 | 408 | { |
michael@0 | 409 | XRE_GetIOMessageLoop()->PostTask( |
michael@0 | 410 | FROM_HERE, |
michael@0 | 411 | NewRunnableFunction(InitVolumeManagerIOThread)); |
michael@0 | 412 | } |
michael@0 | 413 | |
michael@0 | 414 | void |
michael@0 | 415 | ShutdownVolumeManager() |
michael@0 | 416 | { |
michael@0 | 417 | ShutdownVolumeServiceTest(); |
michael@0 | 418 | |
michael@0 | 419 | XRE_GetIOMessageLoop()->PostTask( |
michael@0 | 420 | FROM_HERE, |
michael@0 | 421 | NewRunnableFunction(ShutdownVolumeManagerIOThread)); |
michael@0 | 422 | } |
michael@0 | 423 | |
michael@0 | 424 | } // system |
michael@0 | 425 | } // mozilla |