Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: se cin sw=2 ts=2 et : */
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
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsDownloadScanner.h"
8 #include <comcat.h>
9 #include <process.h>
10 #include "nsDownloadManager.h"
11 #include "nsIXULAppInfo.h"
12 #include "nsXULAppAPI.h"
13 #include "nsIPrefService.h"
14 #include "nsNetUtil.h"
15 #include "nsDeque.h"
16 #include "nsIFileURL.h"
17 #include "nsIPrefBranch.h"
18 #include "nsXPCOMCIDInternal.h"
20 /**
21 * Code overview
22 *
23 * Download scanner attempts to make use of one of two different virus
24 * scanning interfaces available on Windows - IOfficeAntiVirus (Windows
25 * 95/NT 4 and IE 5) and IAttachmentExecute (XPSP2 and up). The latter
26 * interface supports calling IOfficeAntiVirus internally, while also
27 * adding support for XPSP2+ ADS forks which define security related
28 * prompting on downloaded content.
29 *
30 * Both interfaces are synchronous and can take a while, so it is not a
31 * good idea to call either from the main thread. Some antivirus scanners can
32 * take a long time to scan or the call might block while the scanner shows
33 * its UI so if the user were to download many files that finished around the
34 * same time, they would have to wait a while if the scanning were done on
35 * exactly one other thread. Since the overhead of creating a thread is
36 * relatively small compared to the time it takes to download a file and scan
37 * it, a new thread is spawned for each download that is to be scanned. Since
38 * most of the mozilla codebase is not threadsafe, all the information needed
39 * for the scanner is gathered in the main thread in nsDownloadScanner::Scan::Start.
40 * The only function of nsDownloadScanner::Scan which is invoked on another
41 * thread is DoScan.
42 *
43 * Watchdog overview
44 *
45 * The watchdog is used internally by the scanner. It maintains a queue of
46 * current download scans. In a separate thread, it dequeues the oldest scan
47 * and waits on that scan's thread with a timeout given by WATCHDOG_TIMEOUT
48 * (default is 30 seconds). If the wait times out, then the watchdog notifies
49 * the Scan that it has timed out. If the scan really has timed out, then the
50 * Scan object will dispatch its run method to the main thread; this will
51 * release the watchdog thread's addref on the Scan. If it has not timed out
52 * (i.e. the Scan just finished in time), then the watchdog dispatches a
53 * ReleaseDispatcher to release its ref of the Scan on the main thread.
54 *
55 * In order to minimize execution time, there are two events used to notify the
56 * watchdog thread of a non-empty queue and a quit event. Every blocking wait
57 * that the watchdog thread does waits on the quit event; this lets the thread
58 * quickly exit when shutting down. Also, the download scan queue will be empty
59 * most of the time; rather than use a spin loop, a simple event is triggered
60 * by the main thread when a new scan is added to an empty queue. When the
61 * watchdog thread knows that it has run out of elements in the queue, it will
62 * wait on the new item event.
63 *
64 * Memory/resource leaks due to timeout:
65 * In the event of a timeout, the thread must remain alive; terminating it may
66 * very well cause the antivirus scanner to crash or be put into an
67 * inconsistent state; COM resources may also not be cleaned up. The downside
68 * is that we need to leave the thread running; suspending it may lead to a
69 * deadlock. Because the scan call may be ongoing, it may be dependent on the
70 * memory referenced by the MSOAVINFO structure, so we cannot free mName, mPath
71 * or mOrigin; this means that we cannot free the Scan object since doing so
72 * will deallocate that memory. Note that mDownload is set to null upon timeout
73 * or completion, so the download itself is never leaked. If the scan does
74 * eventually complete, then the all the memory and resources will be freed.
75 * It is possible, however extremely rare, that in the event of a timeout, the
76 * mStateSync critical section will leak its event; this will happen only if
77 * the scanning thread, watchdog thread or main thread try to enter the
78 * critical section when one of the others is already in it.
79 *
80 * Reasoning for CheckAndSetState - there exists a race condition between the time when
81 * either the timeout or normal scan sets the state and when Scan::Run is
82 * executed on the main thread. Ex: mStatus could be set by Scan::DoScan* which
83 * then queues a dispatch on the main thread. Before that dispatch is executed,
84 * the timeout code fires and sets mStatus to AVSCAN_TIMEDOUT which then queues
85 * its dispatch to the main thread (the same function as DoScan*). Both
86 * dispatches run and both try to set the download state to AVSCAN_TIMEDOUT
87 * which is incorrect.
88 *
89 * There are 5 possible outcomes of the virus scan:
90 * AVSCAN_GOOD => the file is clean
91 * AVSCAN_BAD => the file has a virus
92 * AVSCAN_UGLY => the file had a virus, but it was cleaned
93 * AVSCAN_FAILED => something else went wrong with the virus scanner.
94 * AVSCAN_TIMEDOUT => the scan (thread setup + execution) took too long
95 *
96 * Both the good and ugly states leave the user with a benign file, so they
97 * transition to the finished state. Bad files are sent to the blocked state.
98 * The failed and timedout states transition to finished downloads.
99 *
100 * Possible Future enhancements:
101 * * Create an interface for scanning files in general
102 * * Make this a service
103 * * Get antivirus scanner status via WMI/registry
104 */
106 // IAttachementExecute supports user definable settings for certain
107 // security related prompts. This defines a general GUID for use in
108 // all projects. Individual projects can define an individual guid
109 // if they want to.
110 #ifndef MOZ_VIRUS_SCANNER_PROMPT_GUID
111 #define MOZ_VIRUS_SCANNER_PROMPT_GUID \
112 { 0xb50563d1, 0x16b6, 0x43c2, { 0xa6, 0x6a, 0xfa, 0xe6, 0xd2, 0x11, 0xf2, \
113 0xea } }
114 #endif
115 static const GUID GUID_MozillaVirusScannerPromptGeneric =
116 MOZ_VIRUS_SCANNER_PROMPT_GUID;
118 // Initial timeout is 30 seconds
119 #define WATCHDOG_TIMEOUT (30*PR_USEC_PER_SEC)
121 // Maximum length for URI's passed into IAE
122 #define MAX_IAEURILENGTH 1683
124 class nsDownloadScannerWatchdog
125 {
126 typedef nsDownloadScanner::Scan Scan;
127 public:
128 nsDownloadScannerWatchdog();
129 ~nsDownloadScannerWatchdog();
131 nsresult Init();
132 nsresult Shutdown();
134 void Watch(Scan *scan);
135 private:
136 static unsigned int __stdcall WatchdogThread(void *p);
137 CRITICAL_SECTION mQueueSync;
138 nsDeque mScanQueue;
139 HANDLE mThread;
140 HANDLE mNewItemEvent;
141 HANDLE mQuitEvent;
142 };
144 nsDownloadScanner::nsDownloadScanner() :
145 mAESExists(false)
146 {
147 }
149 // This destructor appeases the compiler; it would otherwise complain about an
150 // incomplete type for nsDownloadWatchdog in the instantiation of
151 // nsAutoPtr::~nsAutoPtr
152 // Plus, it's a handy location to call nsDownloadScannerWatchdog::Shutdown from
153 nsDownloadScanner::~nsDownloadScanner() {
154 if (mWatchdog)
155 (void)mWatchdog->Shutdown();
156 }
158 nsresult
159 nsDownloadScanner::Init()
160 {
161 // This CoInitialize/CoUninitialize pattern seems to be common in the Mozilla
162 // codebase. All other COM calls/objects are made on different threads.
163 nsresult rv = NS_OK;
164 CoInitialize(nullptr);
166 if (!IsAESAvailable()) {
167 CoUninitialize();
168 return NS_ERROR_NOT_AVAILABLE;
169 }
171 mAESExists = true;
173 // Initialize scanning
174 mWatchdog = new nsDownloadScannerWatchdog();
175 if (mWatchdog) {
176 rv = mWatchdog->Init();
177 if (FAILED(rv))
178 mWatchdog = nullptr;
179 } else {
180 rv = NS_ERROR_OUT_OF_MEMORY;
181 }
183 if (NS_FAILED(rv))
184 return rv;
186 return rv;
187 }
189 bool
190 nsDownloadScanner::IsAESAvailable()
191 {
192 // Try to instantiate IAE to see if it's available.
193 nsRefPtr<IAttachmentExecute> ae;
194 HRESULT hr;
195 hr = CoCreateInstance(CLSID_AttachmentServices, nullptr, CLSCTX_INPROC,
196 IID_IAttachmentExecute, getter_AddRefs(ae));
197 if (FAILED(hr)) {
198 NS_WARNING("Could not instantiate attachment execution service\n");
199 return false;
200 }
201 return true;
202 }
204 // If IAttachementExecute is available, use the CheckPolicy call to find out
205 // if this download should be prevented due to Security Zone Policy settings.
206 AVCheckPolicyState
207 nsDownloadScanner::CheckPolicy(nsIURI *aSource, nsIURI *aTarget)
208 {
209 nsresult rv;
211 if (!mAESExists || !aSource || !aTarget)
212 return AVPOLICY_DOWNLOAD;
214 nsAutoCString source;
215 rv = aSource->GetSpec(source);
216 if (NS_FAILED(rv))
217 return AVPOLICY_DOWNLOAD;
219 nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(aTarget));
220 if (!fileUrl)
221 return AVPOLICY_DOWNLOAD;
223 nsCOMPtr<nsIFile> theFile;
224 nsAutoString aFileName;
225 if (NS_FAILED(fileUrl->GetFile(getter_AddRefs(theFile))) ||
226 NS_FAILED(theFile->GetLeafName(aFileName)))
227 return AVPOLICY_DOWNLOAD;
229 // IAttachementExecute prohibits src data: schemes by default but we
230 // support them. If this is a data src, skip off doing a policy check.
231 // (The file will still be scanned once it lands on the local system.)
232 bool isDataScheme(false);
233 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aSource);
234 if (innerURI)
235 (void)innerURI->SchemeIs("data", &isDataScheme);
236 if (isDataScheme)
237 return AVPOLICY_DOWNLOAD;
239 nsRefPtr<IAttachmentExecute> ae;
240 HRESULT hr;
241 hr = CoCreateInstance(CLSID_AttachmentServices, nullptr, CLSCTX_INPROC,
242 IID_IAttachmentExecute, getter_AddRefs(ae));
243 if (FAILED(hr))
244 return AVPOLICY_DOWNLOAD;
246 (void)ae->SetClientGuid(GUID_MozillaVirusScannerPromptGeneric);
247 (void)ae->SetSource(NS_ConvertUTF8toUTF16(source).get());
248 (void)ae->SetFileName(aFileName.get());
250 // Any failure means the file download/exec will be blocked by the system.
251 // S_OK or S_FALSE imply it's ok.
252 hr = ae->CheckPolicy();
254 if (hr == S_OK)
255 return AVPOLICY_DOWNLOAD;
257 if (hr == S_FALSE)
258 return AVPOLICY_PROMPT;
260 if (hr == E_INVALIDARG)
261 return AVPOLICY_PROMPT;
263 return AVPOLICY_BLOCKED;
264 }
266 #ifndef THREAD_MODE_BACKGROUND_BEGIN
267 #define THREAD_MODE_BACKGROUND_BEGIN 0x00010000
268 #endif
270 #ifndef THREAD_MODE_BACKGROUND_END
271 #define THREAD_MODE_BACKGROUND_END 0x00020000
272 #endif
274 unsigned int __stdcall
275 nsDownloadScanner::ScannerThreadFunction(void *p)
276 {
277 HANDLE currentThread = GetCurrentThread();
278 NS_ASSERTION(!NS_IsMainThread(), "Antivirus scan should not be run on the main thread");
279 nsDownloadScanner::Scan *scan = static_cast<nsDownloadScanner::Scan*>(p);
280 if (!SetThreadPriority(currentThread, THREAD_MODE_BACKGROUND_BEGIN))
281 (void)SetThreadPriority(currentThread, THREAD_PRIORITY_IDLE);
282 scan->DoScan();
283 (void)SetThreadPriority(currentThread, THREAD_MODE_BACKGROUND_END);
284 _endthreadex(0);
285 return 0;
286 }
288 // The sole purpose of this class is to release an object on the main thread
289 // It assumes that its creator will addref it and it will release itself on
290 // the main thread too
291 class ReleaseDispatcher : public nsRunnable {
292 public:
293 ReleaseDispatcher(nsISupports *ptr)
294 : mPtr(ptr) {}
295 NS_IMETHOD Run();
296 private:
297 nsISupports *mPtr;
298 };
300 nsresult ReleaseDispatcher::Run() {
301 NS_ASSERTION(NS_IsMainThread(), "Antivirus scan release dispatch should be run on the main thread");
302 NS_RELEASE(mPtr);
303 NS_RELEASE_THIS();
304 return NS_OK;
305 }
307 nsDownloadScanner::Scan::Scan(nsDownloadScanner *scanner, nsDownload *download)
308 : mDLScanner(scanner), mThread(nullptr),
309 mDownload(download), mStatus(AVSCAN_NOTSTARTED),
310 mSkipSource(false)
311 {
312 InitializeCriticalSection(&mStateSync);
313 }
315 nsDownloadScanner::Scan::~Scan() {
316 DeleteCriticalSection(&mStateSync);
317 }
319 nsresult
320 nsDownloadScanner::Scan::Start()
321 {
322 mStartTime = PR_Now();
324 mThread = (HANDLE)_beginthreadex(nullptr, 0, ScannerThreadFunction,
325 this, CREATE_SUSPENDED, nullptr);
326 if (!mThread)
327 return NS_ERROR_OUT_OF_MEMORY;
329 nsresult rv = NS_OK;
331 // Get the path to the file on disk
332 nsCOMPtr<nsIFile> file;
333 rv = mDownload->GetTargetFile(getter_AddRefs(file));
334 NS_ENSURE_SUCCESS(rv, rv);
335 rv = file->GetPath(mPath);
336 NS_ENSURE_SUCCESS(rv, rv);
338 // Grab the app name
339 nsCOMPtr<nsIXULAppInfo> appinfo =
340 do_GetService(XULAPPINFO_SERVICE_CONTRACTID, &rv);
341 NS_ENSURE_SUCCESS(rv, rv);
343 nsAutoCString name;
344 rv = appinfo->GetName(name);
345 NS_ENSURE_SUCCESS(rv, rv);
346 CopyUTF8toUTF16(name, mName);
348 // Get the origin
349 nsCOMPtr<nsIURI> uri;
350 rv = mDownload->GetSource(getter_AddRefs(uri));
351 NS_ENSURE_SUCCESS(rv, rv);
353 nsAutoCString origin;
354 rv = uri->GetSpec(origin);
355 NS_ENSURE_SUCCESS(rv, rv);
357 // Certain virus interfaces do not like extremely long uris.
358 // Chop off the path and cgi data and just pass the base domain.
359 if (origin.Length() > MAX_IAEURILENGTH) {
360 rv = uri->GetPrePath(origin);
361 NS_ENSURE_SUCCESS(rv, rv);
362 }
364 CopyUTF8toUTF16(origin, mOrigin);
366 // We count https/ftp/http as an http download
367 bool isHttp(false), isFtp(false), isHttps(false);
368 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri);
369 if (!innerURI) innerURI = uri;
370 (void)innerURI->SchemeIs("http", &isHttp);
371 (void)innerURI->SchemeIs("ftp", &isFtp);
372 (void)innerURI->SchemeIs("https", &isHttps);
373 mIsHttpDownload = isHttp || isFtp || isHttps;
375 // IAttachementExecute prohibits src data: schemes by default but we
376 // support them. Mark the download if it's a data scheme, so we
377 // can skip off supplying the src to IAttachementExecute when we scan
378 // the resulting file.
379 (void)innerURI->SchemeIs("data", &mSkipSource);
381 // ResumeThread returns the previous suspend count
382 if (1 != ::ResumeThread(mThread)) {
383 CloseHandle(mThread);
384 return NS_ERROR_UNEXPECTED;
385 }
386 return NS_OK;
387 }
389 nsresult
390 nsDownloadScanner::Scan::Run()
391 {
392 NS_ASSERTION(NS_IsMainThread(), "Antivirus scan dispatch should be run on the main thread");
394 // Cleanup our thread
395 if (mStatus != AVSCAN_TIMEDOUT)
396 WaitForSingleObject(mThread, INFINITE);
397 CloseHandle(mThread);
399 DownloadState downloadState = 0;
400 EnterCriticalSection(&mStateSync);
401 switch (mStatus) {
402 case AVSCAN_BAD:
403 downloadState = nsIDownloadManager::DOWNLOAD_DIRTY;
404 break;
405 default:
406 case AVSCAN_FAILED:
407 case AVSCAN_GOOD:
408 case AVSCAN_UGLY:
409 case AVSCAN_TIMEDOUT:
410 downloadState = nsIDownloadManager::DOWNLOAD_FINISHED;
411 break;
412 }
413 LeaveCriticalSection(&mStateSync);
414 // Download will be null if we already timed out
415 if (mDownload)
416 (void)mDownload->SetState(downloadState);
418 // Clean up some other variables
419 // In the event of a timeout, our destructor won't be called
420 mDownload = nullptr;
422 NS_RELEASE_THIS();
423 return NS_OK;
424 }
426 static DWORD
427 ExceptionFilterFunction(DWORD exceptionCode) {
428 switch(exceptionCode) {
429 case EXCEPTION_ACCESS_VIOLATION:
430 case EXCEPTION_ILLEGAL_INSTRUCTION:
431 case EXCEPTION_IN_PAGE_ERROR:
432 case EXCEPTION_PRIV_INSTRUCTION:
433 case EXCEPTION_STACK_OVERFLOW:
434 return EXCEPTION_EXECUTE_HANDLER;
435 default:
436 return EXCEPTION_CONTINUE_SEARCH;
437 }
438 }
440 bool
441 nsDownloadScanner::Scan::DoScanAES()
442 {
443 // This warning is for the destructor of ae which will not be invoked in the
444 // event of a win32 exception
445 #pragma warning(disable: 4509)
446 HRESULT hr;
447 nsRefPtr<IAttachmentExecute> ae;
448 MOZ_SEH_TRY {
449 hr = CoCreateInstance(CLSID_AttachmentServices, nullptr, CLSCTX_ALL,
450 IID_IAttachmentExecute, getter_AddRefs(ae));
451 } MOZ_SEH_EXCEPT(ExceptionFilterFunction(GetExceptionCode())) {
452 return CheckAndSetState(AVSCAN_NOTSTARTED,AVSCAN_FAILED);
453 }
455 // If we (somehow) already timed out, then don't bother scanning
456 if (CheckAndSetState(AVSCAN_SCANNING, AVSCAN_NOTSTARTED)) {
457 AVScanState newState;
458 if (SUCCEEDED(hr)) {
459 bool gotException = false;
460 MOZ_SEH_TRY {
461 (void)ae->SetClientGuid(GUID_MozillaVirusScannerPromptGeneric);
462 (void)ae->SetLocalPath(mPath.get());
463 // Provide the src for everything but data: schemes.
464 if (!mSkipSource)
465 (void)ae->SetSource(mOrigin.get());
467 // Save() will invoke the scanner
468 hr = ae->Save();
469 } MOZ_SEH_EXCEPT(ExceptionFilterFunction(GetExceptionCode())) {
470 gotException = true;
471 }
473 MOZ_SEH_TRY {
474 ae = nullptr;
475 } MOZ_SEH_EXCEPT(ExceptionFilterFunction(GetExceptionCode())) {
476 gotException = true;
477 }
479 if(gotException) {
480 newState = AVSCAN_FAILED;
481 }
482 else if (SUCCEEDED(hr)) { // Passed the scan
483 newState = AVSCAN_GOOD;
484 }
485 else if (HRESULT_CODE(hr) == ERROR_FILE_NOT_FOUND) {
486 NS_WARNING("Downloaded file disappeared before it could be scanned");
487 newState = AVSCAN_FAILED;
488 }
489 else if (hr == E_INVALIDARG) {
490 NS_WARNING("IAttachementExecute returned invalid argument error");
491 newState = AVSCAN_FAILED;
492 }
493 else {
494 newState = AVSCAN_UGLY;
495 }
496 }
497 else {
498 newState = AVSCAN_FAILED;
499 }
500 return CheckAndSetState(newState, AVSCAN_SCANNING);
501 }
502 return false;
503 }
504 #pragma warning(default: 4509)
506 void
507 nsDownloadScanner::Scan::DoScan()
508 {
509 CoInitialize(nullptr);
511 if (DoScanAES()) {
512 // We need to do a few more things on the main thread
513 NS_DispatchToMainThread(this);
514 } else {
515 // We timed out, so just release
516 ReleaseDispatcher* releaser = new ReleaseDispatcher(this);
517 if(releaser) {
518 NS_ADDREF(releaser);
519 NS_DispatchToMainThread(releaser);
520 }
521 }
523 MOZ_SEH_TRY {
524 CoUninitialize();
525 } MOZ_SEH_EXCEPT(ExceptionFilterFunction(GetExceptionCode())) {
526 // Not much we can do at this point...
527 }
528 }
530 HANDLE
531 nsDownloadScanner::Scan::GetWaitableThreadHandle() const
532 {
533 HANDLE targetHandle = INVALID_HANDLE_VALUE;
534 (void)DuplicateHandle(GetCurrentProcess(), mThread,
535 GetCurrentProcess(), &targetHandle,
536 SYNCHRONIZE, // Only allow clients to wait on this handle
537 FALSE, // cannot be inherited by child processes
538 0);
539 return targetHandle;
540 }
542 bool
543 nsDownloadScanner::Scan::NotifyTimeout()
544 {
545 bool didTimeout = CheckAndSetState(AVSCAN_TIMEDOUT, AVSCAN_SCANNING) ||
546 CheckAndSetState(AVSCAN_TIMEDOUT, AVSCAN_NOTSTARTED);
547 if (didTimeout) {
548 // We need to do a few more things on the main thread
549 NS_DispatchToMainThread(this);
550 }
551 return didTimeout;
552 }
554 bool
555 nsDownloadScanner::Scan::CheckAndSetState(AVScanState newState, AVScanState expectedState) {
556 bool gotExpectedState = false;
557 EnterCriticalSection(&mStateSync);
558 if(gotExpectedState = (mStatus == expectedState))
559 mStatus = newState;
560 LeaveCriticalSection(&mStateSync);
561 return gotExpectedState;
562 }
564 nsresult
565 nsDownloadScanner::ScanDownload(nsDownload *download)
566 {
567 if (!mAESExists)
568 return NS_ERROR_NOT_AVAILABLE;
570 // No ref ptr, see comment below
571 Scan *scan = new Scan(this, download);
572 if (!scan)
573 return NS_ERROR_OUT_OF_MEMORY;
575 NS_ADDREF(scan);
577 nsresult rv = scan->Start();
579 // Note that we only release upon error. On success, the scan is passed off
580 // to a new thread. It is eventually released in Scan::Run on the main thread.
581 if (NS_FAILED(rv))
582 NS_RELEASE(scan);
583 else
584 // Notify the watchdog
585 mWatchdog->Watch(scan);
587 return rv;
588 }
590 nsDownloadScannerWatchdog::nsDownloadScannerWatchdog()
591 : mNewItemEvent(nullptr), mQuitEvent(nullptr) {
592 InitializeCriticalSection(&mQueueSync);
593 }
594 nsDownloadScannerWatchdog::~nsDownloadScannerWatchdog() {
595 DeleteCriticalSection(&mQueueSync);
596 }
598 nsresult
599 nsDownloadScannerWatchdog::Init() {
600 // Both events are auto-reset
601 mNewItemEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
602 if (INVALID_HANDLE_VALUE == mNewItemEvent)
603 return NS_ERROR_OUT_OF_MEMORY;
604 mQuitEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
605 if (INVALID_HANDLE_VALUE == mQuitEvent) {
606 (void)CloseHandle(mNewItemEvent);
607 return NS_ERROR_OUT_OF_MEMORY;
608 }
610 // This thread is always running, however it will be asleep
611 // for most of the dlmgr's lifetime
612 mThread = (HANDLE)_beginthreadex(nullptr, 0, WatchdogThread,
613 this, 0, nullptr);
614 if (!mThread) {
615 (void)CloseHandle(mNewItemEvent);
616 (void)CloseHandle(mQuitEvent);
617 return NS_ERROR_OUT_OF_MEMORY;
618 }
620 return NS_OK;
621 }
623 nsresult
624 nsDownloadScannerWatchdog::Shutdown() {
625 // Tell the watchdog thread to quite
626 (void)SetEvent(mQuitEvent);
627 (void)WaitForSingleObject(mThread, INFINITE);
628 (void)CloseHandle(mThread);
629 // Manually clear and release the queued scans
630 while (mScanQueue.GetSize() != 0) {
631 Scan *scan = reinterpret_cast<Scan*>(mScanQueue.Pop());
632 NS_RELEASE(scan);
633 }
634 (void)CloseHandle(mNewItemEvent);
635 (void)CloseHandle(mQuitEvent);
636 return NS_OK;
637 }
639 void
640 nsDownloadScannerWatchdog::Watch(Scan *scan) {
641 bool wasEmpty;
642 // Note that there is no release in this method
643 // The scan will be released by the watchdog ALWAYS on the main thread
644 // when either the watchdog thread processes the scan or the watchdog
645 // is shut down
646 NS_ADDREF(scan);
647 EnterCriticalSection(&mQueueSync);
648 wasEmpty = mScanQueue.GetSize()==0;
649 mScanQueue.Push(scan);
650 LeaveCriticalSection(&mQueueSync);
651 // If the queue was empty, then the watchdog thread is/will be asleep
652 if (wasEmpty)
653 (void)SetEvent(mNewItemEvent);
654 }
656 unsigned int
657 __stdcall
658 nsDownloadScannerWatchdog::WatchdogThread(void *p) {
659 NS_ASSERTION(!NS_IsMainThread(), "Antivirus scan watchdog should not be run on the main thread");
660 nsDownloadScannerWatchdog *watchdog = (nsDownloadScannerWatchdog*)p;
661 HANDLE waitHandles[3] = {watchdog->mNewItemEvent, watchdog->mQuitEvent, INVALID_HANDLE_VALUE};
662 DWORD waitStatus;
663 DWORD queueItemsLeft = 0;
664 // Loop until quit event or error
665 while (0 != queueItemsLeft ||
666 (WAIT_OBJECT_0 + 1) !=
667 (waitStatus =
668 WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE)) &&
669 waitStatus != WAIT_FAILED) {
670 Scan *scan = nullptr;
671 PRTime startTime, expectedEndTime, now;
672 DWORD waitTime;
674 // Pop scan from queue
675 EnterCriticalSection(&watchdog->mQueueSync);
676 scan = reinterpret_cast<Scan*>(watchdog->mScanQueue.Pop());
677 queueItemsLeft = watchdog->mScanQueue.GetSize();
678 LeaveCriticalSection(&watchdog->mQueueSync);
680 // Calculate expected end time
681 startTime = scan->GetStartTime();
682 expectedEndTime = WATCHDOG_TIMEOUT + startTime;
683 now = PR_Now();
684 // PRTime is not guaranteed to be a signed integral type (afaik), but
685 // currently it is
686 if (now > expectedEndTime) {
687 waitTime = 0;
688 } else {
689 // This is a positive value, and we know that it will not overflow
690 // (bounded by WATCHDOG_TIMEOUT)
691 // waitTime is in milliseconds, nspr uses microseconds
692 waitTime = static_cast<DWORD>((expectedEndTime - now)/PR_USEC_PER_MSEC);
693 }
694 HANDLE hThread = waitHandles[2] = scan->GetWaitableThreadHandle();
696 // Wait for the thread (obj 1) or quit event (obj 0)
697 waitStatus = WaitForMultipleObjects(2, (waitHandles+1), FALSE, waitTime);
698 CloseHandle(hThread);
700 ReleaseDispatcher* releaser = new ReleaseDispatcher(scan);
701 if(!releaser)
702 continue;
703 NS_ADDREF(releaser);
704 // Got quit event or error
705 if (waitStatus == WAIT_FAILED || waitStatus == WAIT_OBJECT_0) {
706 NS_DispatchToMainThread(releaser);
707 break;
708 // Thread exited normally
709 } else if (waitStatus == (WAIT_OBJECT_0+1)) {
710 NS_DispatchToMainThread(releaser);
711 continue;
712 // Timeout case
713 } else {
714 NS_ASSERTION(waitStatus == WAIT_TIMEOUT, "Unexpected wait status in dlmgr watchdog thread");
715 if (!scan->NotifyTimeout()) {
716 // If we didn't time out, then release the thread
717 NS_DispatchToMainThread(releaser);
718 } else {
719 // NotifyTimeout did a dispatch which will release the scan, so we
720 // don't need to release the scan
721 NS_RELEASE(releaser);
722 }
723 }
724 }
725 _endthreadex(0);
726 return 0;
727 }