|
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/. */ |
|
4 |
|
5 #include "VolumeManager.h" |
|
6 |
|
7 #include "Volume.h" |
|
8 #include "VolumeCommand.h" |
|
9 #include "VolumeManagerLog.h" |
|
10 #include "VolumeServiceTest.h" |
|
11 |
|
12 #include "nsWhitespaceTokenizer.h" |
|
13 #include "nsXULAppAPI.h" |
|
14 |
|
15 #include "base/message_loop.h" |
|
16 #include "mozilla/Scoped.h" |
|
17 #include "mozilla/StaticPtr.h" |
|
18 |
|
19 #include <android/log.h> |
|
20 #include <cutils/sockets.h> |
|
21 #include <fcntl.h> |
|
22 #include <sys/socket.h> |
|
23 |
|
24 namespace mozilla { |
|
25 namespace system { |
|
26 |
|
27 static StaticRefPtr<VolumeManager> sVolumeManager; |
|
28 |
|
29 VolumeManager::STATE VolumeManager::mState = VolumeManager::UNINITIALIZED; |
|
30 VolumeManager::StateObserverList VolumeManager::mStateObserverList; |
|
31 |
|
32 /***************************************************************************/ |
|
33 |
|
34 VolumeManager::VolumeManager() |
|
35 : LineWatcher('\0', kRcvBufSize), |
|
36 mSocket(-1), |
|
37 mCommandPending(false) |
|
38 { |
|
39 DBG("VolumeManager constructor called"); |
|
40 } |
|
41 |
|
42 VolumeManager::~VolumeManager() |
|
43 { |
|
44 } |
|
45 |
|
46 //static |
|
47 size_t |
|
48 VolumeManager::NumVolumes() |
|
49 { |
|
50 if (!sVolumeManager) { |
|
51 return 0; |
|
52 } |
|
53 return sVolumeManager->mVolumeArray.Length(); |
|
54 } |
|
55 |
|
56 //static |
|
57 TemporaryRef<Volume> |
|
58 VolumeManager::GetVolume(size_t aIndex) |
|
59 { |
|
60 MOZ_ASSERT(aIndex < NumVolumes()); |
|
61 return sVolumeManager->mVolumeArray[aIndex]; |
|
62 } |
|
63 |
|
64 //static |
|
65 VolumeManager::STATE |
|
66 VolumeManager::State() |
|
67 { |
|
68 return mState; |
|
69 } |
|
70 |
|
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 } |
|
82 |
|
83 |
|
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 } |
|
95 |
|
96 //static |
|
97 void |
|
98 VolumeManager::RegisterStateObserver(StateObserver* aObserver) |
|
99 { |
|
100 mStateObserverList.AddObserver(aObserver); |
|
101 } |
|
102 |
|
103 //static |
|
104 void VolumeManager::UnregisterStateObserver(StateObserver* aObserver) |
|
105 { |
|
106 mStateObserverList.RemoveObserver(aObserver); |
|
107 } |
|
108 |
|
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 } |
|
126 |
|
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 } |
|
140 |
|
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 } |
|
159 |
|
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 }; |
|
169 |
|
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 } |
|
201 |
|
202 LOG("Connected to vold"); |
|
203 PostCommand(new VolumeListCommand(new VolumeListCallback)); |
|
204 return true; |
|
205 } |
|
206 |
|
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); |
|
216 |
|
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 } |
|
228 |
|
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 } |
|
243 |
|
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 } |
|
273 |
|
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 } |
|
283 |
|
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()); |
|
287 |
|
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 } |
|
309 |
|
310 void |
|
311 VolumeManager::OnFileCanWriteWithoutBlocking(int aFd) |
|
312 { |
|
313 MOZ_ASSERT(aFd == mSocket.get()); |
|
314 WriteCommandData(); |
|
315 } |
|
316 |
|
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()); |
|
328 |
|
329 RefPtr<Volume> vol = FindVolumeByName(volName); |
|
330 if (!vol) { |
|
331 return; |
|
332 } |
|
333 vol->HandleVoldResponse(aResponseCode, tokenizer); |
|
334 } |
|
335 |
|
336 void |
|
337 VolumeManager::Restart() |
|
338 { |
|
339 mReadWatcher.StopWatchingFileDescriptor(); |
|
340 mWriteWatcher.StopWatchingFileDescriptor(); |
|
341 |
|
342 while (!mCommands.empty()) { |
|
343 mCommands.pop(); |
|
344 } |
|
345 mCommandPending = false; |
|
346 mSocket.dispose(); |
|
347 Start(); |
|
348 } |
|
349 |
|
350 //static |
|
351 void |
|
352 VolumeManager::Start() |
|
353 { |
|
354 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); |
|
355 |
|
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 } |
|
368 |
|
369 void |
|
370 VolumeManager::OnError() |
|
371 { |
|
372 Restart(); |
|
373 } |
|
374 |
|
375 /***************************************************************************/ |
|
376 |
|
377 static void |
|
378 InitVolumeManagerIOThread() |
|
379 { |
|
380 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); |
|
381 MOZ_ASSERT(!sVolumeManager); |
|
382 |
|
383 sVolumeManager = new VolumeManager(); |
|
384 VolumeManager::Start(); |
|
385 |
|
386 InitVolumeServiceTestIOThread(); |
|
387 } |
|
388 |
|
389 static void |
|
390 ShutdownVolumeManagerIOThread() |
|
391 { |
|
392 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); |
|
393 |
|
394 sVolumeManager = nullptr; |
|
395 } |
|
396 |
|
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 **************************************************************************/ |
|
405 |
|
406 void |
|
407 InitVolumeManager() |
|
408 { |
|
409 XRE_GetIOMessageLoop()->PostTask( |
|
410 FROM_HERE, |
|
411 NewRunnableFunction(InitVolumeManagerIOThread)); |
|
412 } |
|
413 |
|
414 void |
|
415 ShutdownVolumeManager() |
|
416 { |
|
417 ShutdownVolumeServiceTest(); |
|
418 |
|
419 XRE_GetIOMessageLoop()->PostTask( |
|
420 FROM_HERE, |
|
421 NewRunnableFunction(ShutdownVolumeManagerIOThread)); |
|
422 } |
|
423 |
|
424 } // system |
|
425 } // mozilla |