toolkit/components/downloads/nsDownloadScanner.cpp

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

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 }

mercurial