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