1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "mozilla/dom/FileSystemTaskBase.h"
8 
9 #include "nsNetCID.h"
10 #include "mozilla/dom/File.h"
11 #include "mozilla/dom/FileSystemBase.h"
12 #include "mozilla/dom/FileSystemRequestParent.h"
13 #include "mozilla/dom/FileSystemUtils.h"
14 #include "mozilla/dom/Promise.h"
15 #include "mozilla/dom/ipc/BlobParent.h"
16 #include "mozilla/ipc/BackgroundChild.h"
17 #include "mozilla/ipc/BackgroundParent.h"
18 #include "mozilla/ipc/PBackgroundChild.h"
19 #include "mozilla/Unused.h"
20 #include "nsProxyRelease.h"
21 
22 namespace mozilla {
23 namespace dom {
24 
25 namespace {
26 
27 nsresult
FileSystemErrorFromNsError(const nsresult & aErrorValue)28 FileSystemErrorFromNsError(const nsresult& aErrorValue)
29 {
30   uint16_t module = NS_ERROR_GET_MODULE(aErrorValue);
31   if (module == NS_ERROR_MODULE_DOM_FILESYSTEM ||
32       module == NS_ERROR_MODULE_DOM_FILE ||
33       module == NS_ERROR_MODULE_DOM) {
34     return aErrorValue;
35   }
36 
37   switch (aErrorValue) {
38     case NS_OK:
39       return NS_OK;
40 
41     case NS_ERROR_FILE_INVALID_PATH:
42     case NS_ERROR_FILE_UNRECOGNIZED_PATH:
43       return NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR;
44 
45     case NS_ERROR_FILE_DESTINATION_NOT_DIR:
46       return NS_ERROR_DOM_FILESYSTEM_INVALID_MODIFICATION_ERR;
47 
48     case NS_ERROR_FILE_ACCESS_DENIED:
49     case NS_ERROR_FILE_DIR_NOT_EMPTY:
50       return NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR;
51 
52     case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
53     case NS_ERROR_NOT_AVAILABLE:
54       return NS_ERROR_DOM_FILE_NOT_FOUND_ERR;
55 
56     case NS_ERROR_FILE_ALREADY_EXISTS:
57       return NS_ERROR_DOM_FILESYSTEM_PATH_EXISTS_ERR;
58 
59     case NS_ERROR_FILE_NOT_DIRECTORY:
60       return NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR;
61 
62     case NS_ERROR_UNEXPECTED:
63     default:
64       return NS_ERROR_DOM_FILESYSTEM_UNKNOWN_ERR;
65   }
66 }
67 
68 nsresult
DispatchToIOThread(nsIRunnable * aRunnable)69 DispatchToIOThread(nsIRunnable* aRunnable)
70 {
71   MOZ_ASSERT(aRunnable);
72 
73   nsCOMPtr<nsIEventTarget> target
74     = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
75   MOZ_ASSERT(target);
76 
77   return target->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
78 }
79 
80 // This runnable is used when an error value is set before doing any real
81 // operation on the I/O thread. In this case we skip all and we directly
82 // communicate the error.
83 class ErrorRunnable final : public CancelableRunnable
84 {
85 public:
ErrorRunnable(FileSystemTaskChildBase * aTask)86   explicit ErrorRunnable(FileSystemTaskChildBase* aTask)
87     : mTask(aTask)
88   {
89     MOZ_ASSERT(aTask);
90   }
91 
92   NS_IMETHOD
Run()93   Run() override
94   {
95     MOZ_ASSERT(NS_IsMainThread());
96     MOZ_ASSERT(mTask->HasError());
97 
98     mTask->HandlerCallback();
99     return NS_OK;
100   }
101 
102 private:
103   RefPtr<FileSystemTaskChildBase> mTask;
104 };
105 
106 } // anonymous namespace
107 
NS_IMPL_ISUPPORTS(FileSystemTaskChildBase,nsIIPCBackgroundChildCreateCallback)108 NS_IMPL_ISUPPORTS(FileSystemTaskChildBase, nsIIPCBackgroundChildCreateCallback)
109 
110 /**
111  * FileSystemTaskBase class
112  */
113 
114 FileSystemTaskChildBase::FileSystemTaskChildBase(FileSystemBase* aFileSystem)
115   : mErrorValue(NS_OK)
116   , mFileSystem(aFileSystem)
117 {
118   MOZ_ASSERT(aFileSystem, "aFileSystem should not be null.");
119   aFileSystem->AssertIsOnOwningThread();
120 }
121 
~FileSystemTaskChildBase()122 FileSystemTaskChildBase::~FileSystemTaskChildBase()
123 {
124   mFileSystem->AssertIsOnOwningThread();
125 }
126 
127 FileSystemBase*
GetFileSystem() const128 FileSystemTaskChildBase::GetFileSystem() const
129 {
130   mFileSystem->AssertIsOnOwningThread();
131   return mFileSystem.get();
132 }
133 
134 void
Start()135 FileSystemTaskChildBase::Start()
136 {
137   mFileSystem->AssertIsOnOwningThread();
138 
139   mozilla::ipc::PBackgroundChild* actor =
140     mozilla::ipc::BackgroundChild::GetForCurrentThread();
141   if (actor) {
142     ActorCreated(actor);
143   } else {
144     if (NS_WARN_IF(
145         !mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(this))) {
146       MOZ_CRASH();
147     }
148   }
149 }
150 
151 void
ActorFailed()152 FileSystemTaskChildBase::ActorFailed()
153 {
154   MOZ_CRASH("Failed to create a PBackgroundChild actor!");
155 }
156 
157 void
ActorCreated(mozilla::ipc::PBackgroundChild * aActor)158 FileSystemTaskChildBase::ActorCreated(mozilla::ipc::PBackgroundChild* aActor)
159 {
160   if (HasError()) {
161     // In this case we don't want to use IPC at all.
162     RefPtr<ErrorRunnable> runnable = new ErrorRunnable(this);
163     DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
164     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
165     return;
166   }
167 
168   if (mFileSystem->IsShutdown()) {
169     return;
170   }
171 
172   nsAutoString serialization;
173   mFileSystem->SerializeDOMPath(serialization);
174 
175   ErrorResult rv;
176   FileSystemParams params = GetRequestParams(serialization, rv);
177   if (NS_WARN_IF(rv.Failed())) {
178     rv.SuppressException();
179     return;
180   }
181 
182   // Retain a reference so the task object isn't deleted without IPDL's
183   // knowledge. The reference will be released by
184   // mozilla::ipc::BackgroundChildImpl::DeallocPFileSystemRequestChild.
185   NS_ADDREF_THIS();
186 
187   mozilla::ipc::PBackgroundChild* actor =
188     mozilla::ipc::BackgroundChild::GetForCurrentThread();
189   MOZ_ASSERT(actor);
190 
191   actor->SendPFileSystemRequestConstructor(this, params);
192 }
193 
194 void
SetRequestResult(const FileSystemResponseValue & aValue)195 FileSystemTaskChildBase::SetRequestResult(const FileSystemResponseValue& aValue)
196 {
197   mFileSystem->AssertIsOnOwningThread();
198 
199   if (aValue.type() == FileSystemResponseValue::TFileSystemErrorResponse) {
200     FileSystemErrorResponse r = aValue;
201     mErrorValue = r.error();
202   } else {
203     ErrorResult rv;
204     SetSuccessRequestResult(aValue, rv);
205     mErrorValue = rv.StealNSResult();
206   }
207 }
208 
209 bool
Recv__delete__(const FileSystemResponseValue & aValue)210 FileSystemTaskChildBase::Recv__delete__(const FileSystemResponseValue& aValue)
211 {
212   mFileSystem->AssertIsOnOwningThread();
213 
214   SetRequestResult(aValue);
215   HandlerCallback();
216   return true;
217 }
218 
219 void
SetError(const nsresult & aErrorValue)220 FileSystemTaskChildBase::SetError(const nsresult& aErrorValue)
221 {
222   mErrorValue = FileSystemErrorFromNsError(aErrorValue);
223 }
224 
225 /**
226  * FileSystemTaskParentBase class
227  */
228 
FileSystemTaskParentBase(FileSystemBase * aFileSystem,const FileSystemParams & aParam,FileSystemRequestParent * aParent)229 FileSystemTaskParentBase::FileSystemTaskParentBase(FileSystemBase* aFileSystem,
230                                                    const FileSystemParams& aParam,
231                                                    FileSystemRequestParent* aParent)
232   : mErrorValue(NS_OK)
233   , mFileSystem(aFileSystem)
234   , mRequestParent(aParent)
235   , mBackgroundEventTarget(NS_GetCurrentThread())
236 {
237   MOZ_ASSERT(XRE_IsParentProcess(),
238              "Only call from parent process!");
239   MOZ_ASSERT(aFileSystem, "aFileSystem should not be null.");
240   MOZ_ASSERT(aParent);
241   MOZ_ASSERT(mBackgroundEventTarget);
242   AssertIsOnBackgroundThread();
243 }
244 
~FileSystemTaskParentBase()245 FileSystemTaskParentBase::~FileSystemTaskParentBase()
246 {
247   // This task can be released on different threads because we dispatch it (as
248   // runnable) to main-thread, I/O and then back to the PBackground thread.
249   NS_ProxyRelease(mBackgroundEventTarget, mFileSystem.forget());
250   NS_ProxyRelease(mBackgroundEventTarget, mRequestParent.forget());
251 }
252 
253 void
Start()254 FileSystemTaskParentBase::Start()
255 {
256   AssertIsOnBackgroundThread();
257   mFileSystem->AssertIsOnOwningThread();
258 
259   if (NeedToGoToMainThread()) {
260     DebugOnly<nsresult> rv = NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
261     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
262     return;
263   }
264 
265   DebugOnly<nsresult> rv = DispatchToIOThread(this);
266   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchToIOThread failed");
267 }
268 
269 void
HandleResult()270 FileSystemTaskParentBase::HandleResult()
271 {
272   AssertIsOnBackgroundThread();
273   mFileSystem->AssertIsOnOwningThread();
274 
275   if (mFileSystem->IsShutdown()) {
276     return;
277   }
278 
279   MOZ_ASSERT(mRequestParent);
280   Unused << mRequestParent->Send__delete__(mRequestParent, GetRequestResult());
281 }
282 
283 FileSystemResponseValue
GetRequestResult() const284 FileSystemTaskParentBase::GetRequestResult() const
285 {
286   AssertIsOnBackgroundThread();
287   mFileSystem->AssertIsOnOwningThread();
288 
289   if (HasError()) {
290     return FileSystemErrorResponse(mErrorValue);
291   }
292 
293   ErrorResult rv;
294   FileSystemResponseValue value = GetSuccessRequestResult(rv);
295   if (NS_WARN_IF(rv.Failed())) {
296     return FileSystemErrorResponse(rv.StealNSResult());
297   }
298 
299   return value;
300 }
301 
302 void
SetError(const nsresult & aErrorValue)303 FileSystemTaskParentBase::SetError(const nsresult& aErrorValue)
304 {
305   mErrorValue = FileSystemErrorFromNsError(aErrorValue);
306 }
307 
308 bool
NeedToGoToMainThread() const309 FileSystemTaskParentBase::NeedToGoToMainThread() const
310 {
311   return mFileSystem->NeedToGoToMainThread();
312 }
313 
314 nsresult
MainThreadWork()315 FileSystemTaskParentBase::MainThreadWork()
316 {
317   MOZ_ASSERT(NS_IsMainThread());
318   return mFileSystem->MainThreadWork();
319 }
320 
321 NS_IMETHODIMP
Run()322 FileSystemTaskParentBase::Run()
323 {
324   // This method can run in 3 different threads. Here why:
325   // 1. if we are on the main-thread it's because the task must do something
326   //    here. If no errors are returned we go the step 2.
327   // 2. We can be here directly if the task doesn't have nothing to do on the
328   //    main-thread. We are are on the I/O thread and we call IOWork().
329   // 3. Both step 1 (in case of error) and step 2 end up here where return the
330   //    value back to the PBackground thread.
331   if (NS_IsMainThread()) {
332     MOZ_ASSERT(NeedToGoToMainThread());
333 
334     nsresult rv = MainThreadWork();
335     if (NS_WARN_IF(NS_FAILED(rv))) {
336       SetError(rv);
337 
338       // Something when wrong. Let's go to the Background thread directly
339       // skipping the I/O thread step.
340       rv = mBackgroundEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
341       if (NS_WARN_IF(NS_FAILED(rv))) {
342         return rv;
343       }
344     }
345 
346     // Next step must happen on the I/O thread.
347     rv = DispatchToIOThread(this);
348     if (NS_WARN_IF(NS_FAILED(rv))) {
349       return rv;
350     }
351 
352     return NS_OK;
353   }
354 
355   // Run I/O thread tasks
356   if (!IsOnBackgroundThread()) {
357     nsresult rv = IOWork();
358     if (NS_WARN_IF(NS_FAILED(rv))) {
359       SetError(rv);
360     }
361 
362     // Let's go back to PBackground thread to finish the work.
363     rv = mBackgroundEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
364     if (NS_WARN_IF(NS_FAILED(rv))) {
365       return rv;
366     }
367 
368     return NS_OK;
369   }
370 
371   // If we are here, it's because the I/O work has been done and we have to
372   // handle the result back via IPC.
373   AssertIsOnBackgroundThread();
374   HandleResult();
375   return NS_OK;
376 }
377 
378 } // namespace dom
379 } // namespace mozilla
380