windows-nt/Source/XPSP1/NT/enduser/windows.com/iuengine/download.cpp
2020-09-26 16:20:57 +08:00

1918 lines
76 KiB
C++

//=======================================================================
//
// Copyright (c) 1998-2000 Microsoft Corporation. All Rights Reserved.
//
// File: downld.cpp
//
// Description:
//
// Implementation for the Download() function
//
//=======================================================================
#include "iuengine.h" // PCH - must include first
#include <iu.h>
#include <iucommon.h>
#include <download.h>
#include <trust.h>
#include <wininet.h>
#include <fileutil.h>
#include <shlwapi.h>
#include "iuxml.h"
#include "history.h"
#include <schemakeys.h>
//#include <serverPing.h> changed to use urllogging.h.
#include <intshcut.h>
#include <schemamisc.h>
#include <WaitUtil.h>
#include <urllogging.h>
#define MAX_CORPORATE_PATH 100
// named mutex used to update historical speed/time information in the registry.
const TCHAR IU_MUTEX_HISTORICALSPEED_REGUPDATE[] = _T("{5f3255a9-9051-49b1-80b9-aac31c092af4}");
const TCHAR IU_READMORE_LINK_NAME[] = _T("ReadMore.url");
const CHAR SZ_DOWNLOAD_FINISHED[] = "Download finished";
const LONG UPDATE_COMMAND = 0x0000000F;
typedef struct IUDOWNLOADSTARTUPINFO
{
BSTR bstrClientName;
BSTR bstrXmlCatalog;
BSTR bstrDestinationFolder;
LONG lMode;
IUnknown *punkProgressListener;
HWND hwnd;
BSTR bstrUuidOperation;
CEngUpdate* pEngUpdate;
} IUDOWNLOADSTARTUPINFO, *PIUDOWNLOADSTARTUPINFO;
// --------------------------------------------------------------------
// function forward declarations
// --------------------------------------------------------------------
//
// Callback function to provide status from the downloader
//
BOOL WINAPI DownloadCallback(VOID* pCallbackData, DWORD dwStatus, DWORD dwBytesTotal, DWORD dwBytesComplete, BSTR bstrXmlData, LONG* plCommandRequest);
//
// thread function used by DownloadAsync
//
DWORD WINAPI DownloadThreadProc(LPVOID lpv);
/////////////////////////////////////////////////////////////////////////////
// CreateReadMoreLink()
//
// If the item contains a "description/descriptionText/details" node, suck
// out the URL and create a shortcut for it in the destination folder
//
// Input:
// pxmlCatalog - CXmlCatalog containing downloaded items
// hItem - Handle to current download item in the catalog
// pszDestinationFolder - Folder where item is downloaded
//
// Return:
// S_OK - Wrote the ReadMore.htm link
// S_FALSE - details node didn't exist in item
// <other> - HRESULT returned from calling other functions
/////////////////////////////////////////////////////////////////////////////
HRESULT CreateReadMoreLink(CXmlCatalog* pxmlCatalog, HANDLE_NODE hItem, LPCTSTR pszDestinationFolder)
{
USES_IU_CONVERSION;
LOG_Block("CreateReadMoreLink");
IXMLDOMNode* pItemNode = NULL;
IXMLDOMNode* pReadMoreNode = NULL;
IUniformResourceLocator* purl = NULL;
IPersistFile* ppf = NULL;
HRESULT hr;
TCHAR szShortcut[MAX_PATH];
BSTR bstrURL = NULL;
if (NULL == pxmlCatalog || HANDLE_NODE_INVALID == hItem || NULL == pszDestinationFolder)
{
CleanUpIfFailedAndSetHrMsg(E_INVALIDARG);
}
//
// Get <item> node in catalog
//
if (NULL == (pItemNode = pxmlCatalog->GetDOMNodebyHandle(hItem)))
{
CleanUpIfFailedAndSetHrMsg(E_INVALIDARG);
}
//
// Get node containing ReadMore URL, or S_FALSE if it doesn't exist
//
hr = pItemNode->selectSingleNode(KEY_READMORE, &pReadMoreNode);
if (S_OK != hr)
{
if (S_FALSE != hr)
{
LOG_ErrorMsg(hr);
}
goto CleanUp;
}
//
// suck out the href attribute
//
CleanUpIfFailedAndSetHrMsg(GetAttribute(pReadMoreNode, KEY_HREF, &bstrURL));
// Get pointers to the IID_IUniformResourceLocator and IID_IPersistFile interfaces
// on the CLSID_InternetShortcut object
CleanUpIfFailedAndSetHrMsg(CoCreateInstance(CLSID_InternetShortcut, \
NULL, \
CLSCTX_INPROC_SERVER, \
IID_IUniformResourceLocator, \
(LPVOID*)&purl));
CleanUpIfFailedAndSetHrMsg(purl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf));
// We want to check the URL we got from the Manifest Data to make sure it is a HTTP URL, not some local file spec
URL_COMPONENTS UrlComponents;
// Break down the URL to get the Protocol Used
// Specifically we need the server name, object to download, username and
// password information.
TCHAR szScheme[32];
szScheme[0] = _T('\0');
ZeroMemory(&UrlComponents, sizeof(UrlComponents));
UrlComponents.dwStructSize = sizeof(UrlComponents);
UrlComponents.lpszScheme = szScheme;
UrlComponents.dwSchemeLength = ARRAYSIZE(szScheme);
if (!InternetCrackUrl(OLE2T(bstrURL), 0, 0, &UrlComponents))
{
LOG_ErrorMsg(HRESULT_FROM_WIN32(GetLastError()));
goto CleanUp;
}
if (szScheme[0] == _T('\0') || (0 != lstrcmpi(szScheme, _T("http")) && 0 != lstrcmpi(szScheme, _T("https"))))
{
// If the Scheme was undeterminable, or the scheme is not HTTP then we shouldn't trust this URL.
LOG_ErrorMsg(E_UNEXPECTED);
goto CleanUp;
}
//
// Set the URL, form the shortcut path, and write the link
//
CleanUpIfFailedAndSetHrMsg(purl->SetURL(OLE2T(bstrURL), 0));
hr = StringCchCopyEx(szShortcut, ARRAYSIZE(szShortcut), pszDestinationFolder,
NULL, NULL, MISTSAFE_STRING_FLAGS);
CleanUpIfFailedAndSetHrMsg(hr);
hr = PathCchAppend(szShortcut, ARRAYSIZE(szShortcut), IU_READMORE_LINK_NAME);
CleanUpIfFailedAndSetHrMsg(hr);
CleanUpIfFailedAndSetHrMsg(ppf->Save(T2OLE(szShortcut), FALSE));
CleanUp:
SysFreeString(bstrURL);
// pItemNode is owned by CXmlCatalog, don't release
SafeReleaseNULL(pReadMoreNode);
SafeReleaseNULL(ppf);
SafeReleaseNULL(purl);
return hr;
}
/////////////////////////////////////////////////////////////////////////////
// CreateItemDependencyList()
//
// If the item contains a "dependencies" node, we want to walk
// the dependendant Item List and list the proper order that installs should
// be done in. If a dependant Item is not available in the current catalog it
// will be ignored.
//
// Input:
// pxmlCatalog - CXmlCatalog containing downloaded items
// hItem - Handle to current download item in the catalog
// pszDestinationFolder - Folder where item is downloaded
//
// Return:
// S_OK - Dependency List Written
// S_FALSE - No Dependencies Available
// <other> - HRESULT returned from calling other functions
/////////////////////////////////////////////////////////////////////////////
HRESULT CreateItemDependencyList(CXmlCatalog* pxmlCatalog, HANDLE_NODE hItem, LPCTSTR pszDestinationFolder)
{
HRESULT hr = S_FALSE;
HANDLE_NODE hDependentItemList = HANDLE_NODELIST_INVALID;
HANDLE_NODE hDependentItem = HANDLE_NODE_INVALID;
int iDependentItemOrder = 1;
HANDLE hFile = INVALID_HANDLE_VALUE;
DWORD dwBytesWritten;
TCHAR szFileName[MAX_PATH];
char szWriteBuffer[MAX_PATH + 12]; // max_path is the safe length for the identitystr plus room for the order information
BSTR bstrIdentityStr = NULL;
BOOL fWroteItem = FALSE;
USES_IU_CONVERSION;
hDependentItemList = pxmlCatalog->GetFirstItemDependency(hItem, &hDependentItem);
if (HANDLE_NODELIST_INVALID != hDependentItemList)
{
hr = PathCchCombine(szFileName, ARRAYSIZE(szFileName), pszDestinationFolder, _T("order.txt"));
if (FAILED(hr))
{
pxmlCatalog->CloseItem(hDependentItem);
pxmlCatalog->CloseItemList(hDependentItemList);
return hr;
}
hFile = CreateFile(szFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hFile)
{
hr = HRESULT_FROM_WIN32(GetLastError());
pxmlCatalog->CloseItem(hDependentItem);
pxmlCatalog->CloseItemList(hDependentItemList);
return hr;
}
hr = S_OK;
while (hr == S_OK)
{
if (HANDLE_NODELIST_INVALID != hDependentItem)
{
pxmlCatalog->GetIdentityStr(hDependentItem, &bstrIdentityStr);
hr = StringCchPrintfExA(szWriteBuffer, ARRAYSIZE(szWriteBuffer), NULL, NULL, MISTSAFE_STRING_FLAGS,
"%d = %s\r\n", iDependentItemOrder, OLE2A(bstrIdentityStr));
if (FAILED(hr))
{
SafeSysFreeString(bstrIdentityStr);
pxmlCatalog->CloseItem(hDependentItem);
pxmlCatalog->CloseItemList(hDependentItemList);
return hr;
}
WriteFile(hFile, szWriteBuffer, lstrlenA(szWriteBuffer), &dwBytesWritten, NULL);
iDependentItemOrder++;
SafeSysFreeString(bstrIdentityStr);
pxmlCatalog->CloseItem(hDependentItem);
fWroteItem = TRUE;
}
hr = pxmlCatalog->GetNextItemDependency(hDependentItemList, &hDependentItem);
}
pxmlCatalog->CloseItemList(hDependentItemList);
CloseHandle(hFile);
if (!fWroteItem)
DeleteFile(szFileName); // no dependencies written
if (SUCCEEDED(hr))
hr = S_OK; // convert S_FALSE to S_OK, we successfully wrote the dependencylist
}
return hr;
}
/////////////////////////////////////////////////////////////////////////////
// _Download()
//
// Do synchronous downloading.
// Input:
// bstrClientName - the name of the client, for history logging use
// bstrXmlCatalog - the xml catalog portion containing items to be downloaded
// bstrDestinationFolder - the destination folder. Null will use the default IU folder
// lMode - bitmask indicates throttled/foreground and notification options
// punkProgressListener - the callback function pointer for reporting download progress
// hWnd - the event msg window handler passed from the stub
// Output:
// pbstrXmlItems - the items with download status in xml format
// e.g.
// <id guid="2560AD4D-3ED3-49C6-A937-4368C0B0E06D" downloaded="1"/>
/////////////////////////////////////////////////////////////////////////////
HRESULT _Download(BSTR bstrClientName, BSTR bstrXmlCatalog, BSTR bstrDestinationFolder, LONG lMode,
IUnknown *punkProgressListener, HWND hWnd, BSTR bstrUuidOperation, BSTR *pbstrXmlItems,
CEngUpdate* pEngUpdate)
{
LOG_Block("Download()");
HRESULT hr = S_OK;
HRESULT hrGlobalItemFailure = S_OK;
LPTSTR lpszClientInfo = NULL;
TCHAR szBaseDestinationFolder[MAX_PATH];
TCHAR szDestinationFolder[MAX_PATH];
TCHAR szItemPath[MAX_PATH];
LPTSTR pszCabUrl = NULL;
HANDLE_NODE hCatalogItemList = HANDLE_NODELIST_INVALID;
HANDLE_NODE hProviderList = HANDLE_NODELIST_INVALID;
HANDLE_NODE hItem = HANDLE_NODE_INVALID;
HANDLE_NODE hProvider = HANDLE_NODE_INVALID;
HANDLE_NODE hXmlItem = HANDLE_NODE_INVALID;
HANDLE_NODE hItemCabList = HANDLE_NODELIST_INVALID;
BSTR bstrCabUrl = NULL;
BSTR bstrLocalFileName = NULL;
BSTR bstrProviderName = NULL;
BSTR bstrProviderPublisher = NULL;
BSTR bstrProviderUUID = NULL;
BSTR bstrProviderIdentityStr = NULL;
BSTR bstrItemPath = NULL;
BSTR bstrInstallerType = NULL;
BSTR bstrLanguage = NULL;
BSTR bstrPlatformDir = NULL;
BSTR bstrTemp = NULL;
BSTR bstrCRC = NULL;
BOOL fCabPatchAvail;
BOOL fReboot;
BOOL fExclusive;
LONG lCommandCount;
LONG lCabSize = 0;
LPTSTR pszLocalFileName = NULL;
LPTSTR pszAllocatedFileName = NULL;
BOOL fNTFSDriveAvailable = FALSE;
TCHAR szFileSystemType[12];
TCHAR szLargestFATDrive[4];
int iMaxNTFSDriveFreeSpace = 0;
int iMaxDriveFreeSpace = 0;
BOOL fCorpCase = FALSE;
BOOL fContinue = TRUE; // for async mode
BOOL fUseSuppliedPath = FALSE;
long n;
DWORD dwBytesDownloaded = 0;
DWORD dwCount1, dwCount2, dwElapsedTime;
DWORD dwTotalElapsedTime = 0;
DWORD dwTotalBytesDownloaded = 0;
DWORD dwWaitResult;
DWORD dwHistoricalSpeed = 0;
DWORD dwHistoricalTime = 0;
DWORD dwSize;
DWORD dwRet;
HKEY hkeyIU = NULL;
HANDLE hMutex = NULL;
DCB_DATA CallbackData;
{
CXmlCatalog xmlCatalog;
CXmlItems xmlItemList;
LPTSTR ptszLivePingServerUrl = NULL;
LPTSTR ptszCorpPingServerUrl = NULL;
DWORD dwFlags = 0;
// clear any previous cancel event
ResetEvent(pEngUpdate->m_evtNeedToQuit);
ZeroMemory(&CallbackData, sizeof(CallbackData));
CallbackData.pOperationMgr = &pEngUpdate->m_OperationMgr;
USES_IU_CONVERSION;
CIUHistory history;
lpszClientInfo = OLE2T(bstrClientName);
if (NULL != (ptszLivePingServerUrl = (LPTSTR)HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
INTERNET_MAX_URL_LENGTH * sizeof(TCHAR))))
{
if (FAILED(g_pUrlAgent->GetLivePingServer(ptszLivePingServerUrl, INTERNET_MAX_URL_LENGTH)))
{
LOG_Out(_T("failed to get live ping server URL"));
SafeHeapFree(ptszLivePingServerUrl);
}
}
else
{
LOG_Out(_T("failed to allocate memory for ptszLivePingServerUrl"));
}
if (NULL != (ptszCorpPingServerUrl = (LPTSTR)HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
INTERNET_MAX_URL_LENGTH * sizeof(TCHAR))))
{
if (FAILED(g_pUrlAgent->GetCorpPingServer(ptszCorpPingServerUrl, INTERNET_MAX_URL_LENGTH)))
{
LOG_Out(_T("failed to get corp WU ping server URL"));
SafeHeapFree(ptszCorpPingServerUrl);
}
}
else
{
LOG_Out(_T("failed to allocate memory for ptszCorpPingServerUrl"));
}
CUrlLog pingSvr(lpszClientInfo, ptszLivePingServerUrl, ptszCorpPingServerUrl);
SafeHeapFree(ptszLivePingServerUrl);
SafeHeapFree(ptszCorpPingServerUrl);
if (FAILED(hr = g_pUrlAgent->IsClientSpecifiedByPolicy(lpszClientInfo)))
{
LOG_ErrorMsg(hr);
goto CleanUp;
}
//
// Set the flags for use by DownloadFile
//
if (S_FALSE == hr)
{
dwFlags = 0;
hr = S_OK;
}
else // S_OK
{
dwFlags = WUDF_DONTALLOWPROXY;
LOG_Internet(_T("WUDF_DONTALLOWPROXY set"));
}
pszCabUrl = (LPTSTR) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, INTERNET_MAX_URL_LENGTH * sizeof(TCHAR));
if (NULL == pszCabUrl)
{
dwRet = GetLastError();
hr = HRESULT_FROM_WIN32(dwRet);
LOG_ErrorMsg(hr);
goto CleanUp;
}
CallbackData.bstrOperationUuid = (NULL == bstrUuidOperation) ? NULL : SysAllocString(bstrUuidOperation);
CallbackData.hEventFiringWnd = hWnd;
if (NULL != punkProgressListener)
{
// get the IProgressListener interface pointer from the IUnknown pointer.. If the
// interface is not supported the pProgressListener is set to NULL
punkProgressListener->QueryInterface(IID_IProgressListener, (void**)&CallbackData.pProgressListener);
}
else
{
CallbackData.pProgressListener = NULL;
}
// Check for Corporate Download Handling Mode
if ((DWORD) lMode & (DWORD) UPDATE_CORPORATE_MODE)
{
fCorpCase = TRUE;
}
// Check for Progress Notification Requested Mode
if ((DWORD) lMode & (DWORD) UPDATE_NOTIFICATION_10PCT)
{
CallbackData.flProgressPercentage = (float).10;
}
else if ((DWORD) lMode & (DWORD) UPDATE_NOTIFICATION_5PCT)
{
CallbackData.flProgressPercentage = (float).05;
}
else if ((DWORD) lMode & (DWORD) UPDATE_NOTIFICATION_1PCT)
{
CallbackData.flProgressPercentage = (float).01;
}
else if ((DWORD) lMode & (DWORD) UPDATE_NOTIFICATION_COMPLETEONLY)
{
CallbackData.flProgressPercentage = (float) 1;
}
else
{
CallbackData.flProgressPercentage = 0;
}
if (NULL != bstrDestinationFolder && 0 < SysStringLen(bstrDestinationFolder))
{
if (SysStringLen(bstrDestinationFolder) > MAX_CORPORATE_PATH)
{
hr = E_INVALIDARG;
LOG_ErrorMsg(hr);
LogMessage("Catalog Download Path Greater Than (%d)", MAX_CORPORATE_PATH);
goto CleanUp;
}
// Caller specified a Base Path - Set this Flag so we don't create our temp folder
// structure under this path.
fUseSuppliedPath = TRUE;
//
// user passed in a designated path, this is to signal the
// download-no-install case, usually for corporate site
//
hr = StringCchCopyEx(szBaseDestinationFolder,
ARRAYSIZE(szBaseDestinationFolder),
OLE2T(bstrDestinationFolder),
NULL, NULL, MISTSAFE_STRING_FLAGS);
if (FAILED(hr))
{
LOG_ErrorMsg(hr);
goto CleanUp;
}
//
// verify that we have write access to this folder
// --- most likely it's a UNC path
//
DWORD dwErr = ValidateFolder(szBaseDestinationFolder, TRUE);
if (ERROR_SUCCESS != dwErr)
{
LOG_ErrorMsg(dwErr);
goto CleanUp;
}
//
// Find out if this Path is a UNC
//
if ('\\' == szBaseDestinationFolder[0] && '\\' == szBaseDestinationFolder[1])
{
// correct the path to the UNC to get the available space
hr = StringCchCopyEx(szDestinationFolder, ARRAYSIZE(szDestinationFolder),
szBaseDestinationFolder,
NULL, NULL, MISTSAFE_STRING_FLAGS);
if (FAILED(hr))
{
LOG_ErrorMsg(hr);
goto CleanUp;
}
LPTSTR pszWalk = szDestinationFolder;
pszWalk += 2; // skip the double slash
pszWalk = StrChr(pszWalk, '\\'); // find the next slash (separate machine and share name)
pszWalk += 1;
pszWalk = StrChr(pszWalk, '\\'); // try to find the next slash (end of share name)
if (NULL == pszWalk)
{
// no trailing slash and no further path information
hr = PathCchAddBackslash(szDestinationFolder, ARRAYSIZE(szBaseDestinationFolder));
if (FAILED(hr))
{
LOG_ErrorMsg(hr);
goto CleanUp;
}
}
else
{
// this path has a trailing slash (may have more path information, truncate after the slash)
pszWalk += 1;
*pszWalk = '\0';
}
GetFreeDiskSpace(szDestinationFolder, &iMaxDriveFreeSpace);
}
else
{
// path must be a local drive
GetFreeDiskSpace(szBaseDestinationFolder[0], &iMaxDriveFreeSpace);
}
}
else
{
//
// user passed in NULL as the destination folder,
// it means this is the normal case to download and install
// updates for this machine. we will try to find the
// drive with the most free space
//
TCHAR szDriveList[MAX_PATH];
GetLogicalDriveStrings(MAX_PATH, szDriveList);
LPTSTR pszCurrent = szDriveList;
int iSize;
//
// find the local fixed drive with the 'most' free space
//
while (NULL != pszCurrent && *pszCurrent != _T('\0'))
{
fContinue = (WaitForSingleObject(pEngUpdate->m_evtNeedToQuit, 0) != WAIT_OBJECT_0);
if (DRIVE_FIXED == GetDriveType(pszCurrent))
{
hr = GetFreeDiskSpace(*pszCurrent, &iSize);
if (FAILED(hr))
{
LOG_Error(_T("Error Reading Drive Space %c, hr = 0x%08x"), *pszCurrent, hr);
pszCurrent += (lstrlen(pszCurrent) + 1); // skip current and null terminater
continue;
}
if (!GetVolumeInformation(pszCurrent, NULL, 0, NULL, NULL, NULL, szFileSystemType, ARRAYSIZE(szFileSystemType)))
{
DWORD dwErr = GetLastError();
LOG_Error(_T("Error Reading VolumeInfo for Drive %c, GLE = %d"), *pszCurrent, dwErr);
pszCurrent += (lstrlen(pszCurrent) + 1); // skip current and null terminater
continue;
}
if (CSTR_EQUAL == CompareString(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), NORM_IGNORECASE,
szFileSystemType, -1, _T("NTFS"), -1))
{
fNTFSDriveAvailable = TRUE;
if (iSize > iMaxNTFSDriveFreeSpace)
{
iMaxNTFSDriveFreeSpace = iSize;
hr = StringCchCopyEx(szBaseDestinationFolder, ARRAYSIZE(szBaseDestinationFolder), pszCurrent,
NULL, NULL, MISTSAFE_STRING_FLAGS);
if (FAILED(hr))
{
LOG_ErrorMsg(hr);
continue;
}
}
}
else
{
// we want to keep track of non NTFS drive sizes in case there the largest
// NTFS drive size is too small, but a FAT partition has enough space. In this
// case we want to fall back to the FAT partition. Note: this is a behavior change
// from the initial design where we treated NTFS and FAT as mutually exclusive with
// NTFS always winning.
if (iSize > iMaxDriveFreeSpace)
{
iMaxDriveFreeSpace = iSize;
if (!fNTFSDriveAvailable)
{
// if no NTFS drive is available save this drive letter as the preferred
hr = StringCchCopyEx(szBaseDestinationFolder, ARRAYSIZE(szBaseDestinationFolder), pszCurrent,
NULL, NULL, MISTSAFE_STRING_FLAGS);
if (FAILED(hr))
{
LOG_ErrorMsg(hr);
continue;
}
hr = StringCchCopyEx(szLargestFATDrive, ARRAYSIZE(szLargestFATDrive), pszCurrent,
NULL, NULL, MISTSAFE_STRING_FLAGS);
if (FAILED(hr))
{
LOG_ErrorMsg(hr);
continue;
}
}
else
{
// NTFS drive exists, save this drive as a back up choice for the size check.
hr = StringCchCopyEx(szLargestFATDrive, ARRAYSIZE(szLargestFATDrive), pszCurrent,
NULL, NULL, MISTSAFE_STRING_FLAGS);
if (FAILED(hr))
{
LOG_ErrorMsg(hr);
continue;
}
}
}
}
}
pszCurrent += (lstrlen(pszCurrent) + 1); // skip current and null terminater
}
if (!fContinue)
{
hr = E_UNEXPECTED;
goto CleanUp;
}
if ((0 == iMaxDriveFreeSpace) && (0 == iMaxNTFSDriveFreeSpace))
{
//
// running on a system with no local drives?
//
hr = E_FAIL;
LOG_ErrorMsg(hr);
goto CleanUp;
}
}
//
// load the XML document into the XmlCatalog Class
//
if (WaitForSingleObject(pEngUpdate->m_evtNeedToQuit, 0) == WAIT_OBJECT_0)
{
hr = E_ABORT;
goto CleanUp;
}
hr = xmlCatalog.LoadXMLDocument(bstrXmlCatalog, pEngUpdate->m_fOfflineMode);
if (FAILED(hr))
{
LOG_ErrorMsg(hr);
goto CleanUp;
}
// We need to find the total estimated size of the download we're about to do.
// We'll walk the XML Catalog getting Size Info for each item.
hr = xmlCatalog.GetTotalEstimatedSize(&CallbackData.lTotalDownloadSize);
if (FAILED(hr))
{
LOG_ErrorMsg(hr);
goto CleanUp;
}
CallbackData.lTotalDownloaded = 0;
//
// added by JHou - bug#314: download does not detect available free space on local hard drive
//
// The lTotalDownloadSize is the size of the download in Bytes, the MaxDriveSpace is in KBytes
if ((CallbackData.lTotalDownloadSize / 1024) > ((fNTFSDriveAvailable) ? iMaxNTFSDriveFreeSpace : iMaxDriveFreeSpace))
{
// Before we bail out of the download we need to look to see if we excluded a chose a NTFS drive
// over a FAT drive. If the NTFS drive doesn't have enough space, but a FAT drive does we want to
// go ahead and allow the use of the FAT drive. This is a change in spec'd behavior per bug: 413079
if ((CallbackData.lTotalDownloadSize / 1024) < iMaxDriveFreeSpace)
{
// no error.. a FAT partition has enough free space, use it instead
hr = StringCchCopyEx(szBaseDestinationFolder, ARRAYSIZE(szBaseDestinationFolder), szLargestFATDrive,
NULL, NULL, MISTSAFE_STRING_FLAGS);
if (FAILED(hr))
{
LOG_ErrorMsg(hr);
goto CleanUp;
}
}
else
{
// tried both NTFS and FAT partitions.. none have enough space.. bail out.
dwRet = ERROR_DISK_FULL;
LOG_ErrorMsg(dwRet);
hr = HRESULT_FROM_WIN32(dwRet);
// need to write items result information for each item indicating it failed because of diskspace
hrGlobalItemFailure = HRESULT_FROM_WIN32(dwRet);
}
}
if (SUCCEEDED(hrGlobalItemFailure))
{
if (!fUseSuppliedPath)
{
// When a destination folder is specified, we don't need to add anything to it. If no path
// is specified we pick a drive letter, so we need to add the WUTemp directory
// to that base path.
hr = StringCchCatEx(szBaseDestinationFolder, ARRAYSIZE(szBaseDestinationFolder), IU_WUTEMP,
NULL, NULL, MISTSAFE_STRING_FLAGS);
if (FAILED(hr))
{
LOG_ErrorMsg(hr);
goto CleanUp;
}
}
//
// 500953 Allow Power Users to access WUTEMP
//
DWORD dwAttr = GetFileAttributes(szBaseDestinationFolder);
if (INVALID_FILE_ATTRIBUTES == dwAttr || 0 == (FILE_ATTRIBUTE_DIRECTORY & dwAttr))
{
//
// Only create directory if it doesn't already exist (Power Users can't
// SetFileAttributes if an administrator created the directory originally).
//
if (FAILED(hr = CreateDirectoryAndSetACLs(szBaseDestinationFolder, TRUE)))
{
LOG_ErrorMsg(hr);
hrGlobalItemFailure = hr;
}
if (!fUseSuppliedPath &&
!SetFileAttributes(szBaseDestinationFolder, FILE_ATTRIBUTE_HIDDEN))
{
DWORD dwErr = GetLastError();
LOG_ErrorMsg(dwErr);
hr = HRESULT_FROM_WIN32(dwErr);
hrGlobalItemFailure = HRESULT_FROM_WIN32(dwRet);
}
}
#if defined(UNICODE) || defined(_UNICODE)
LogMessage("Download destination root folder is: %ls", szBaseDestinationFolder);
#else
LogMessage("Download destination root folder is: %s", szBaseDestinationFolder);
#endif
if (fCorpCase)
{
history.SetDownloadBasePath(szBaseDestinationFolder);
}
}
//
// loop through each provider in the catalog, then each item in the provider
//
if (WaitForSingleObject(pEngUpdate->m_evtNeedToQuit, 0) == WAIT_OBJECT_0)
{
hr = E_ABORT;
goto CleanUp;
}
hProviderList = xmlCatalog.GetFirstProvider(&hProvider);
while (fContinue && HANDLE_NODE_INVALID != hProvider)
{
xmlCatalog.GetIdentity(hProvider, &bstrProviderName, &bstrProviderPublisher, &bstrProviderUUID);
xmlCatalog.GetIdentityStr(hProvider, &bstrProviderIdentityStr);
//
// Get the Enumerator List of Items in this Provider, and get the first item
//
hCatalogItemList = xmlCatalog.GetFirstItem(hProvider, &hItem);
if ((HANDLE_NODELIST_INVALID == hCatalogItemList) || (HANDLE_NODE_INVALID == hItem))
{
// No Items under this Provider
xmlCatalog.GetNextProvider(hProviderList, &hProvider);
continue;
}
while (fContinue && HANDLE_NODE_INVALID != hItem)
{
if (FAILED(hrGlobalItemFailure))
{
xmlItemList.AddItem(&xmlCatalog, hItem, &hXmlItem);
bstrTemp = T2BSTR(_T(""));
xmlItemList.AddDownloadPath(hXmlItem, bstrTemp);
SafeSysFreeString(bstrTemp);
history.AddHistoryItemDownloadStatus(&xmlCatalog, hItem, HISTORY_STATUS_FAILED, /*no download path*/_T(""), lpszClientInfo, hrGlobalItemFailure);
xmlItemList.AddDownloadStatus(hXmlItem, KEY_STATUS_FAILED, hrGlobalItemFailure);
xmlCatalog.CloseItem(hItem);
xmlCatalog.GetNextItem(hCatalogItemList, &hItem);
continue;
}
LONG lCallbackRequest = 0; // check if user set something in callback
xmlCatalog.GetIdentityStr(hItem, &bstrItemPath);
if (NULL == bstrItemPath)
{
LOG_Download(_T("Failed to Get Identity String for an Item"));
xmlCatalog.CloseItem(hItem);
xmlCatalog.GetNextItem(hCatalogItemList, &hItem);
continue;
}
//
// send out status to caller to tell which item we are about to download
//
BSTR bstrXmlItemForCallback = NULL;
if (SUCCEEDED(xmlCatalog.GetBSTRItemForCallback(hItem, &bstrXmlItemForCallback)))
{
CallbackData.lCurrentItemSize = 0;
DownloadCallback(&CallbackData,
DOWNLOAD_STATUS_ITEMSTART,
0,
0,
bstrXmlItemForCallback,
&lCallbackRequest);
SafeSysFreeString(bstrXmlItemForCallback);
bstrXmlItemForCallback = NULL;
if (UPDATE_COMMAND_CANCEL == lCallbackRequest)
{
LOG_Out(_T("Download Callback received UPDATE_COMMAND_CANCEL"));
SetEvent(pEngUpdate->m_evtNeedToQuit); // asked to quit
fContinue = FALSE;
}
else
{
//
// check the global quit event. If quit, then server ping treat it as a cancel.
//
fContinue = (WaitForSingleObject(pEngUpdate->m_evtNeedToQuit, 0) != WAIT_OBJECT_0);
}
if (!fContinue)
{
continue; // or break, same effect.
}
}
else
{
//
// something wrong with this item, so we should skip it
//
continue;
}
if (fCorpCase)
{
LPCTSTR szName = NULL;
// Corporate Folder Path is Constructed from Several Item Elements
// Software | Driver\<Locale>\<ProviderIdentity>\<Platform>\<ItemIdentity>.<version>
xmlCatalog.GetItemInstallInfo(hItem, &bstrInstallerType, &fExclusive, &fReboot, &lCommandCount);
if (NULL == bstrInstallerType)
{
LOG_Download(_T("Missing InstallerType Info for Item %ls"), bstrItemPath);
goto doneCorpCase;
}
if (CSTR_EQUAL == CompareStringW(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), NORM_IGNORECASE,
(LPCWSTR)bstrInstallerType, -1, L"CDM", -1))
{
szName = _T("Driver");
}
else
{
szName = _T("Software");
}
hr = StringCchCopyEx(szItemPath, ARRAYSIZE(szItemPath), szName, NULL, NULL, MISTSAFE_STRING_FLAGS);
if (FAILED(hr))
goto doneCorpCase;
xmlCatalog.GetItemLanguage(hItem, &bstrLanguage);
xmlCatalog.GetCorpItemPlatformStr(hItem, &bstrPlatformDir);
if (NULL == bstrLanguage || NULL == bstrPlatformDir)
{
LOG_Download(_T("Missing Language or Platform Info for Item %ls"), bstrItemPath);
goto doneCorpCase;
}
hr = PathCchCombine(szDestinationFolder, ARRAYSIZE(szDestinationFolder), szBaseDestinationFolder, szItemPath);
if (FAILED(hr))
goto doneCorpCase;
hr = PathCchAppend(szDestinationFolder, ARRAYSIZE(szDestinationFolder), OLE2T(bstrLanguage));
if (FAILED(hr))
goto doneCorpCase;
hr = PathCchAppend(szDestinationFolder, ARRAYSIZE(szDestinationFolder), OLE2T(bstrProviderIdentityStr));
if (FAILED(hr))
goto doneCorpCase;
hr = PathCchAppend(szDestinationFolder, ARRAYSIZE(szDestinationFolder), OLE2T(bstrPlatformDir));
if (FAILED(hr))
goto doneCorpCase;
hr = PathCchAppend(szDestinationFolder, ARRAYSIZE(szDestinationFolder), OLE2T(bstrItemPath));
if (FAILED(hr))
goto doneCorpCase;
doneCorpCase:
SafeSysFreeString(bstrInstallerType);
SafeSysFreeString(bstrLanguage);
SafeSysFreeString(bstrPlatformDir);
if (FAILED(hr))
{
LOG_ErrorMsg(hr);
SafeSysFreeString(bstrItemPath);
xmlCatalog.CloseItem(hItem);
xmlCatalog.GetNextItem(hCatalogItemList, &hItem);
continue;
}
}
else
{
hr = PathCchCombine(szDestinationFolder, ARRAYSIZE(szDestinationFolder), szBaseDestinationFolder, OLE2T(bstrItemPath));
if (FAILED(hr))
{
LOG_ErrorMsg(hr);
SafeSysFreeString(bstrItemPath);
xmlCatalog.CloseItem(hItem);
xmlCatalog.GetNextItem(hCatalogItemList, &hItem);
continue;
}
}
if (FAILED(hr = CreateDirectoryAndSetACLs(szDestinationFolder, TRUE)))
{
LOG_ErrorMsg(hr);
xmlCatalog.CloseItem(hItem);
xmlCatalog.GetNextItem(hCatalogItemList, &hItem);
SafeSysFreeString(bstrItemPath);
continue;
}
//
// Now get the collection of CodeBases for this Item
//
hItemCabList = xmlCatalog.GetItemFirstCodeBase(hItem, &bstrCabUrl, &bstrLocalFileName, &bstrCRC, &fCabPatchAvail, &lCabSize);
if ((HANDLE_NODELIST_INVALID == hItemCabList) || (NULL == bstrCabUrl))
{
// No Cabs for this Item?? skip it.
LOG_Download(_T("Item: %ls has no cabs, Skipping"), bstrItemPath);
SafeSysFreeString(bstrItemPath);
xmlCatalog.CloseItem(hItem);
xmlCatalog.GetNextItem(hCatalogItemList, &hItem);
continue;
}
while (fContinue && NULL != bstrCabUrl)
{
LPTSTR pszTempCabUrl = OLE2T(bstrCabUrl);
// pszCabUrl is allocated to be INTERNET_MAX_URL_LENGTH above.
hr = StringCchCopyEx(pszCabUrl, INTERNET_MAX_URL_LENGTH, pszTempCabUrl,
NULL, NULL, MISTSAFE_STRING_FLAGS);
if (FAILED(hr))
{
LOG_ErrorMsg(hr);
break;
}
SafeMemFree(pszTempCabUrl);
if (NULL != bstrLocalFileName && SysStringLen(bstrLocalFileName) > 0)
{
if (NULL != pszAllocatedFileName)
{
MemFree(pszAllocatedFileName);
}
pszAllocatedFileName = OLE2T(bstrLocalFileName);
}
else
{
//
// has not specified file name, use the same file name in URL
//
// search for the last forward slash (will separate the URL from the filename)
LPTSTR lpszLastSlash = StrRChr(pszCabUrl, NULL, _T('/'));
if (NULL != lpszLastSlash)
{
// last slash was found, skip to next character (will be the beginning of the filename)
lpszLastSlash++;
}
pszLocalFileName = lpszLastSlash;
}
// Download the Cab - Store Information for Progress Callbacks
dwBytesDownloaded = 0;
CallbackData.lCurrentItemSize = lCabSize;
dwCount1 = GetTickCount();
hr = DownloadFile(pszCabUrl, // fileurl to download
szDestinationFolder, // destination folder for file
(NULL != pszAllocatedFileName) ? pszAllocatedFileName : pszLocalFileName, // use AllocatedFileName if possible, else use localfilename
&dwBytesDownloaded, // bytes downloaded for this file
&pEngUpdate->m_evtNeedToQuit, // quit event array
1, // number of events
DownloadCallback, // callback function
&CallbackData, // data structure for callback function
dwFlags);
if (FAILED(hr))
{
//
// added by JHou: bug335292 - Temporary folder not deleted when network plug removed
//
// only empty folder can be deleted successfully so if RemoveDirectory() failed that
// may because it's not empty which means it's ok
if (RemoveDirectory(szDestinationFolder) && fCorpCase)
{
HRESULT hrCopy;
// If this Directory was successfully removed and this is the Corp Case we should
// try to remove its parents up to the base directory.
TCHAR szCorpDestinationFolderRemove[MAX_PATH];
hrCopy = StringCchCopyEx(szCorpDestinationFolderRemove,
ARRAYSIZE(szCorpDestinationFolderRemove),
szDestinationFolder,
NULL, NULL, MISTSAFE_STRING_FLAGS);
if (FAILED(hrCopy))
{
LOG_ErrorMsg(hrCopy);
break;
}
LPTSTR pszBackslash = NULL;
PathRemoveBackslash(szBaseDestinationFolder); // strip any trailing backslashes - need to normalize this to compare when we're done walking the folder tree
for (;;)
{
pszBackslash = StrRChr(szCorpDestinationFolderRemove, NULL, '\\');
if (NULL == pszBackslash)
break; // unexpected
*pszBackslash = '\0';
if (0 == StrCmp(szCorpDestinationFolderRemove, szBaseDestinationFolder))
break; // reached the base directory, done removing directories;
if (!RemoveDirectory(szCorpDestinationFolderRemove))
break; // couldn't remove folder at this level, assume folder not empty, leave the rest of the structure intact.
}
}
if (E_ABORT == hr)
{
LOG_Download(_T("DownloadFile function returns E_ABORT while downloading %s."), pszCabUrl);
#if defined(UNICODE) || defined(_UNICODE)
LogError(hr, "Download cancelled while processing file %ls", pszCabUrl);
#else
LogError(hr, "Download cancelled while processing file %s", pszCabUrl);
#endif
}
else
{
LOG_Download(_T("Download Failed for URL: %s, Skipping remaining files for this Item"), pszCabUrl);
#if defined(UNICODE) || defined(_UNICODE)
LogError(hr, "Downloading file %ls, skipping remaining files for this Item", pszCabUrl);
#else
LogError(hr, "Downloading file %s, skipping remaining files for this Item", pszCabUrl);
#endif
}
SafeSysFreeString(bstrCabUrl);
//
// since one file got error, we can exit the file loop for the current item
// because missing one file will make this item not usable.
//
break;
}
dwCount2 = GetTickCount();
if (0 != dwBytesDownloaded)
{
if (dwCount1 < dwCount2) // normal case, no roll-over
{
dwElapsedTime = dwCount2 - dwCount1;
}
else
{
// roll-over case, should almost never happen
dwElapsedTime = (0xFFFFFFFF - dwCount1) + dwCount2;
}
dwTotalBytesDownloaded += dwBytesDownloaded;
dwTotalElapsedTime += dwElapsedTime;
}
// Form the full Path and Filename of the file we just downloaded
hr = PathCchCombine(szItemPath, ARRAYSIZE(szItemPath), szDestinationFolder,
(NULL != pszAllocatedFileName) ? pszAllocatedFileName : pszLocalFileName);
if (FAILED(hr))
{
DeleteFile(szItemPath);
break;
}
// Verify CRC
//---------------
if (NULL != bstrCRC)
{
TCHAR szCRCHash[CRC_HASH_STRING_LENGTH] = {'\0'};
hr = StringCchCopyEx(szCRCHash, ARRAYSIZE(szCRCHash), OLE2T(bstrCRC), NULL, NULL, MISTSAFE_STRING_FLAGS);
if (FAILED(hr))
{
// Something was wrong with the BSTR we got back from XML. Fail Safely, delete the file.
// The Failed HR will fail the item
DeleteFile(szItemPath);
break;
}
hr = VerifyFileCRC(szItemPath, szCRCHash);
if (HRESULT_FROM_WIN32(ERROR_CRC) == hr || FAILED(hr))
{
// The File CRC's Did Not Match, or we had a problem Calculating the CRC. Fail Safely, delete the file.
// The Failed HR will fail the item
DeleteFile(szItemPath);
break;
}
}
// Check Trust
//---------------
hr = VerifyFileTrust(szItemPath,
NULL,
ReadWUPolicyShowTrustUI()
);
if (FAILED(hr))
{
// File Was Not Trusted - Need to Delete it and fail the item
DeleteFile(szItemPath);
break;
}
#if defined(UNICODE) || defined(_UNICODE)
LogMessage("Downloaded file %ls", pszCabUrl);
LogMessage("Local path %ls", szItemPath);
#else
LogMessage("Downloaded file %s", pszCabUrl);
LogMessage("Local path %s", szItemPath);
#endif
SafeSysFreeString(bstrCabUrl);
SafeSysFreeString(bstrLocalFileName);
SafeSysFreeString(bstrCRC);
bstrCabUrl = bstrLocalFileName = NULL;
fContinue = SUCCEEDED(xmlCatalog.GetItemNextCodeBase(hItemCabList, &bstrCabUrl, &bstrLocalFileName, &bstrCRC, &fCabPatchAvail, &lCabSize)) &&
(WaitForSingleObject(pEngUpdate->m_evtNeedToQuit, 0) != WAIT_OBJECT_0);
}
// Write XMLItems entry for this download result
xmlItemList.AddItem(&xmlCatalog, hItem, &hXmlItem);
bstrTemp = T2BSTR(szDestinationFolder);
xmlItemList.AddDownloadPath(hXmlItem, bstrTemp);
SafeSysFreeString(bstrTemp);
//
// For "corporate" download write ReadMore Link before writing history (in case we fail
//
if (TRUE == fCorpCase)
{
//
// Ignore errors as we want to keep downloaded cab anyway
//
(void) CreateReadMoreLink(&xmlCatalog, hItem, szDestinationFolder);
(void) CreateItemDependencyList(&xmlCatalog, hItem, szDestinationFolder);
}
//
// Also add download history for this item
//
if (SUCCEEDED(hr))
{
history.AddHistoryItemDownloadStatus(&xmlCatalog, hItem, HISTORY_STATUS_COMPLETE, szDestinationFolder, lpszClientInfo);
xmlItemList.AddDownloadStatus(hXmlItem, KEY_STATUS_COMPLETE);
}
else
{
history.AddHistoryItemDownloadStatus(&xmlCatalog, hItem, HISTORY_STATUS_FAILED, szDestinationFolder, lpszClientInfo, hr);
xmlItemList.AddDownloadStatus(hXmlItem, KEY_STATUS_FAILED, hr);
}
//
// ping server to report the download status for this item
//
{
BSTR bstrIdentityPing = NULL;
if (SUCCEEDED(xmlCatalog.GetIdentityStrForPing(hItem, &bstrIdentityPing)))
{
URLLOGSTATUS status = SUCCEEDED(hr) ? URLLOGSTATUS_Success : URLLOGSTATUS_Failed;
if (E_ABORT == hr)
{
//
// user/system cancelled the current process
//
status = URLLOGSTATUS_Cancelled;
}
pingSvr.Ping(
TRUE, // on-line
URLLOGDESTINATION_DEFAULT, // going live or corp WU ping server
&pEngUpdate->m_evtNeedToQuit, // pt to cancel events
1, // number of events
URLLOGACTIVITY_Download, // activity
status, // status code
hr, // error code, can be 0 or 1
OLE2T(bstrIdentityPing), // itemID
NULL // no device data can be given during dld phase
);
}
SafeSysFreeString(bstrIdentityPing);
//SafeSysFreeString(bstrPlatformPing);
//SafeSysFreeString(bstrLanguagePing);
}
xmlCatalog.CloseItemList(hItemCabList);
//
// done with this item, fire itemcomplete event
//
DownloadCallback(&CallbackData, DOWNLOAD_STATUS_ITEMCOMPLETE, CallbackData.lCurrentItemSize, 0, NULL, &lCallbackRequest);
SafeSysFreeString(bstrItemPath);
// get the next item. hItem will be HANDLE_NODE_INVALID when there are no
// remaining items.
xmlCatalog.CloseItem(hItem);
xmlCatalog.GetNextItem(hCatalogItemList, &hItem);
if (UPDATE_COMMAND_CANCEL == lCallbackRequest)
{
LOG_Out(_T("Download Callback received UPDATE_COMMAND_CANCEL"));
SetEvent(pEngUpdate->m_evtNeedToQuit); // asked to quit
fContinue = FALSE;
}
else
{
//
// check the global quit event. If quit, then server ping treat it as a cancel.
// TODO: also need to check the operation quit event!
//
fContinue = (WaitForSingleObject(pEngUpdate->m_evtNeedToQuit, 0) != WAIT_OBJECT_0);
}
}
xmlCatalog.CloseItemList(hCatalogItemList);
SafeSysFreeString(bstrProviderName);
SafeSysFreeString(bstrProviderPublisher);
SafeSysFreeString(bstrProviderUUID);
SafeSysFreeString(bstrProviderIdentityStr);
xmlCatalog.CloseItem(hProvider);
xmlCatalog.GetNextProvider(hProviderList, &hProvider);
fContinue = (WaitForSingleObject(pEngUpdate->m_evtNeedToQuit, 0) != WAIT_OBJECT_0);
}
xmlCatalog.CloseItemList(hProviderList);
RegOpenKey(HKEY_LOCAL_MACHINE, REGKEY_IUCTL, &hkeyIU);
hMutex = CreateMutex(NULL, FALSE, IU_MUTEX_HISTORICALSPEED_REGUPDATE);
if ((0 != dwTotalBytesDownloaded) && (0 != dwTotalElapsedTime) && (NULL != hkeyIU) && (NULL != hMutex))
{
HANDLE aHandles[2];
aHandles[0] = hMutex;
aHandles[1] = pEngUpdate->m_evtNeedToQuit;
dwWaitResult = MyMsgWaitForMultipleObjects(ARRAYSIZE(aHandles), aHandles, FALSE, /*30 seconds*/30000, QS_ALLINPUT);
if (WAIT_OBJECT_0 == dwWaitResult)
{
// convert elapsed time from milliseconds to seconds
dwTotalElapsedTime = dwTotalElapsedTime / 1000;
if (0 == dwTotalElapsedTime)
dwTotalElapsedTime = 1; // minimum one second
// we have the mutex, go ahead and read/write the reg information.
dwSize = sizeof(dwHistoricalSpeed);
RegQueryValueEx(hkeyIU, REGVAL_HISTORICALSPEED, NULL, NULL, (LPBYTE)&dwHistoricalSpeed, &dwSize);
dwSize = sizeof(dwHistoricalTime);
RegQueryValueEx(hkeyIU, REGVAL_TIMEELAPSED, NULL, NULL, (LPBYTE)&dwHistoricalTime, &dwSize);
// We need to get the Bytes Downloaded to add the bytes just downloaded
DWORD dwHistoricalBytes = dwHistoricalSpeed * dwHistoricalTime; // could be 0 if no previous history was recorded
dwHistoricalBytes += dwTotalBytesDownloaded; // new byte count
dwHistoricalTime += dwTotalElapsedTime; // new time count
dwHistoricalSpeed = dwHistoricalBytes / dwHistoricalTime; // calculate new speed bytes/second
RegSetValueEx(hkeyIU, REGVAL_HISTORICALSPEED, NULL, REG_DWORD, (LPBYTE)&dwHistoricalSpeed, sizeof(dwHistoricalSpeed));
RegSetValueEx(hkeyIU, REGVAL_TIMEELAPSED, NULL, REG_DWORD, (LPBYTE)&dwHistoricalTime, sizeof(dwHistoricalTime));
ReleaseMutex(hMutex);
CloseHandle(hMutex);
hMutex = NULL;
}
}
//
// We pass in pEngUpdate->m_evtNeedToQuit to MyMsgWaitForMultipleObjects above so it will exit immediately
// but don't bother to handle the WAIT_OBJECT_0 + 1 case there since the if statement may not
// execute and even if this is the case we still need to check pEngUpdate->m_evtNeedToQuit below anyway.
//
if (WaitForSingleObject(pEngUpdate->m_evtNeedToQuit, 0) == WAIT_OBJECT_0)
{
hr = E_ABORT;
}
CleanUp:
//
// add HRESULT in case the download failed before the download loop
//
if (S_OK != hr)
{
xmlItemList.AddGlobalErrorCodeIfNoItems(hr);
}
//
// generate result
//
xmlItemList.GetItemsBSTR(pbstrXmlItems);
SafeSysFreeString(CallbackData.bstrOperationUuid);
SafeHeapFree(pszCabUrl);
SafeSysFreeString(bstrCabUrl);
SafeSysFreeString(bstrLocalFileName);
SafeSysFreeString(bstrProviderName);
SafeSysFreeString(bstrProviderPublisher);
SafeSysFreeString(bstrProviderUUID);
SafeSysFreeString(bstrProviderIdentityStr);
SafeSysFreeString(bstrItemPath);
SafeSysFreeString(bstrInstallerType);
SafeSysFreeString(bstrLanguage);
SafeSysFreeString(bstrPlatformDir);
SafeSysFreeString(bstrTemp);
if (NULL != hkeyIU)
{
RegCloseKey(hkeyIU);
hkeyIU = NULL;
}
if (NULL != hMutex)
{
// shouldn't need to release, if hmutex is still valid at this point we were unable to
// get the mutex
CloseHandle(hMutex);
hMutex = NULL;
}
}
//
// notify that we are completed
//
if (NULL != punkProgressListener || NULL != hWnd)
{
DownloadCallback(&CallbackData, DOWNLOAD_STATUS_OPERATIONCOMPLETE, 0, 0, *pbstrXmlItems, NULL);
}
if (SUCCEEDED(hr))
{
LogMessage("%s %s", SZ_SEE_IUHIST, SZ_DOWNLOAD_FINISHED);
}
else
{
LogError(hr, "%s %s", SZ_SEE_IUHIST, SZ_DOWNLOAD_FINISHED);
}
return hr;
}
/////////////////////////////////////////////////////////////////////////////
// Download()
//
// Do synchronous downloading.
// Input:
// bstrXmlClientInfo - the credentials of the client in xml format
// bstrXmlCatalog - the xml catalog portion containing items to be downloaded
// bstrDestinationFolder - the destination folder. Null will use the default IU folder
// lMode - bitmask indicates throttled/foreground and notification options
// punkProgressListener - the callback function pointer for reporting download progress
// hWnd - the event msg window handler passed from the stub
// Output:
// pbstrXmlItems - the items with download status in xml format
// e.g.
// <id guid="2560AD4D-3ED3-49C6-A937-4368C0B0E06D" downloaded="1"/>
/////////////////////////////////////////////////////////////////////////////
HRESULT WINAPI CEngUpdate::Download(BSTR bstrXmlClientInfo, BSTR bstrXmlCatalog, BSTR bstrDestinationFolder, LONG lMode,
IUnknown *punkProgressListener, HWND hWnd, BSTR *pbstrXmlItems)
{
CXmlClientInfo clientInfo;
BSTR bstrClientName = NULL;
HRESULT hr;
LOG_Block("Download()");
LogMessage("Download started");
hr = clientInfo.LoadXMLDocument(bstrXmlClientInfo, m_fOfflineMode);
CleanUpIfFailedAndMsg(hr);
hr = clientInfo.GetClientName(&bstrClientName);
CleanUpIfFailedAndMsg(hr);
hr = _Download(
bstrClientName,
bstrXmlCatalog,
bstrDestinationFolder,
lMode,
punkProgressListener,
hWnd,
NULL, // no op id needed for sync download
pbstrXmlItems,
this);
CleanUp:
SysFreeString(bstrClientName);
return hr;
}
/////////////////////////////////////////////////////////////////////////////
// DownloadAsync()
//
// Download asynchronously - the method will return before completion.
// Input:
// bstrXmlClientInfo - the credentials of the client in xml format
// bstrXmlCatalog - the xml catalog portion containing items to be downloaded
// bstrDestinationFolder - the destination folder. Null will use the default IU folder
// lMode - indicates throttled or fore-ground downloading mode
// punkProgressListener - the callback function pointer for reporting download progress
// hWnd - the event msg window handler passed from the stub
// bstrUuidOperation - an id provided by the client to provide further
// identification to the operation as indexes may be reused.
// Output:
// pbstrUuidOperation - the operation ID. If it is not provided by the in bstrUuidOperation
// parameter (an empty string is passed), it will generate a new UUID,
// in which case, the caller will be responsible to free the memory of
// the string buffer that holds the generated UUID using SysFreeString().
// Otherwise, it returns the value passed by bstrUuidOperation.
/////////////////////////////////////////////////////////////////////////////
HRESULT WINAPI CEngUpdate::DownloadAsync(BSTR bstrXmlClientInfo, BSTR bstrXmlCatalog, BSTR bstrDestinationFolder, LONG lMode,
IUnknown *punkProgressListener, HWND hWnd, BSTR bstrUuidOperation, BSTR *pbstrUuidOperation)
{
HRESULT hr = S_OK;
BSTR bstrClientName = NULL;
DWORD dwThreadId = 0x0;
DWORD dwErr = 0x0;
HANDLE hThread = NULL;
GUID guid;
LPWSTR lpswClientInfo = NULL;
LPOLESTR pwszUuidOperation = NULL;
PIUDOWNLOADSTARTUPINFO pStartupInfo = NULL;
HANDLE hHeap = GetProcessHeap();
CXmlClientInfo clientInfo;
LOG_Block("DownloadAsync()");
LogMessage("Asynchronous Download started");
USES_IU_CONVERSION;
//
// validate parameters:
// if no catalog, or no return var, or no client info, this function can do nothing.
//
if ((NULL == bstrXmlCatalog) ||
(NULL == bstrXmlClientInfo) ||
(SysStringLen(bstrXmlCatalog) == 0) ||
(SysStringLen(bstrXmlClientInfo) == 0))
{
hr = E_INVALIDARG;
CleanUpIfFailedAndMsg(hr);
}
//
// validate the client info
//
hr = clientInfo.LoadXMLDocument(bstrXmlClientInfo, m_fOfflineMode);
CleanUpIfFailedAndMsg(hr);
hr = clientInfo.GetClientName(&bstrClientName);
CleanUpIfFailedAndMsg(hr);
if (NULL == (pStartupInfo = (PIUDOWNLOADSTARTUPINFO) HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(IUDOWNLOADSTARTUPINFO))))
{
hr = E_OUTOFMEMORY;
LOG_ErrorMsg(hr);
goto CleanUp;
}
pStartupInfo->bstrClientName = SysAllocString(bstrClientName);
pStartupInfo->bstrXmlCatalog = SysAllocString(bstrXmlCatalog);
pStartupInfo->hwnd = hWnd;
pStartupInfo->lMode = lMode;
pStartupInfo->punkProgressListener = punkProgressListener;
pStartupInfo->pEngUpdate = this;
if (NULL != bstrDestinationFolder && SysStringLen(bstrDestinationFolder) > 0)
{
LOG_Download(_T("Caller specified destination folder=%s"), OLE2T(bstrDestinationFolder));
pStartupInfo->bstrDestinationFolder = SysAllocString(bstrDestinationFolder);
}
//
// session id is required for download operation
//
if (NULL != bstrUuidOperation && SysStringLen(bstrUuidOperation) > 0)
{
LOG_Download(_T("User passed in UUID %s"), OLE2T(bstrUuidOperation));
pStartupInfo->bstrUuidOperation = SysAllocString(bstrUuidOperation);
if (NULL != pbstrUuidOperation)
{
*pbstrUuidOperation = SysAllocString(bstrUuidOperation);
}
}
else
{
//
// if user doesn't have an operation id, we generate one
//
hr = CoCreateGuid(&guid);
if (FAILED(hr))
{
LOG_ErrorMsg(hr);
goto CleanUp;
}
hr = StringFromCLSID(guid, &pwszUuidOperation);
if (FAILED(hr))
{
LOG_ErrorMsg(hr);
goto CleanUp;
}
pStartupInfo->bstrUuidOperation = SysAllocString(pwszUuidOperation);
if (NULL != pbstrUuidOperation)
{
*pbstrUuidOperation = SysAllocString(pwszUuidOperation);
}
LOG_Download(_T("UUID generated %s"), OLE2T(pwszUuidOperation));
CoTaskMemFree(pwszUuidOperation);
}
InterlockedIncrement(&m_lThreadCounter);
if (NULL != pStartupInfo->punkProgressListener)
{
//
// since this is an async operation, to prevent caller free this object after
// this call returns, we pump up ref count here. The thread proc will
// release refcount after it finishes the work
//
pStartupInfo->punkProgressListener->AddRef();
}
hThread = CreateThread(NULL, 0, DownloadThreadProc, (LPVOID)pStartupInfo, 0, &dwThreadId);
if (NULL == hThread)
{
//
// clean up allocated strings in pStartupInfo.
//
dwErr = GetLastError();
hr = HRESULT_FROM_WIN32(dwErr);
LOG_ErrorMsg(hr);
SafeRelease(pStartupInfo->punkProgressListener);
InterlockedDecrement(&m_lThreadCounter);
}
else
{
LOG_Download(_T("Download thread generated successfully"));
}
CleanUp:
if (FAILED(hr))
{
LogError(hr, "Asynchronous Download failed during startup");
if (NULL != pStartupInfo)
{
SysFreeString(pStartupInfo->bstrDestinationFolder);
SysFreeString(pStartupInfo->bstrXmlCatalog);
SysFreeString(pStartupInfo->bstrClientName);
SysFreeString(pStartupInfo->bstrUuidOperation);
HeapFree(hHeap, 0, pStartupInfo);
}
}
SysFreeString(bstrClientName);
return hr;
}
/////////////////////////////////////////////////////////////////////////////
// DownloadCallback()
//
// Callback Function to recieve progress from IU Downloader.
//
// Input:
// pCallbackData - void pointer to DCB_DATA structure
// dwStatus - Current Download Status
// dwBytesTotal - Total Bytes of File being Downloaded
// dwBytesComplete - Bytes Downloaded so far
// bstrCompleteResult - Contains Item Result XML
//
// Output:
// plCommandRequest - Used to Instruct the Downloader to continue, abort, suspend...
//
// Return:
// 0 - always, exit code is irrelevant since calling thread doesn't check the
// status of this thread after creation.
/////////////////////////////////////////////////////////////////////////////
BOOL WINAPI DownloadCallback(VOID* pCallbackData, DWORD dwStatus, DWORD dwBytesTotal, DWORD dwBlockSizeDownloaded, BSTR bstrXmlData, LONG* plCommandRequest)
{
LOG_Block("DownloadCallback()");
HRESULT hr;
LONG lUpdateMask = 0;
float flNewPercentage;
EventData evtData;
char szProgressSize[64] = {'\0'};
BOOL fPostWaitSuccess = TRUE;
ZeroMemory((LPVOID) &evtData, sizeof(evtData));
USES_IU_CONVERSION;
P_DCB_DATA pCallbackParam = (P_DCB_DATA) pCallbackData;
if (NULL != pCallbackParam->bstrOperationUuid)
{
evtData.bstrUuidOperation = SysAllocString(pCallbackParam->bstrOperationUuid);
LOG_Download(_T("Found UUID=%s"), OLE2T(evtData.bstrUuidOperation));
}
if (dwBytesTotal != pCallbackParam->lCurrentItemSize && DOWNLOAD_STATUS_ITEMCOMPLETE != dwStatus)
{
pCallbackParam->lTotalDownloadSize = (pCallbackParam->lTotalDownloadSize - pCallbackParam->lCurrentItemSize) + dwBytesTotal;
pCallbackParam->lCurrentItemSize = dwBytesTotal;
}
// Keep the Total Downloaded Bytes Counter
if (0 != dwBlockSizeDownloaded && DOWNLOAD_STATUS_ITEMCOMPLETE != dwStatus)
pCallbackParam->lTotalDownloaded += dwBlockSizeDownloaded;
LOG_Download(_T("dwStatus=0x%08x"), dwStatus);
//
// if the Status is DOWNLOAD_STATUS_FILECOMPLETE we are done with this File
//
evtData.fItemCompleted = (dwStatus == DOWNLOAD_STATUS_ITEMCOMPLETE);
switch (dwStatus)
{
case DOWNLOAD_STATUS_ITEMSTART:
if (NULL != pCallbackParam->pProgressListener)
{
pCallbackParam->pProgressListener->OnItemStart(pCallbackParam->bstrOperationUuid,
bstrXmlData, &evtData.lCommandRequest);
}
else
{
// only use event window if no progresslistener interface was given
if (NULL != pCallbackParam->hEventFiringWnd)
{
evtData.bstrXmlData = bstrXmlData;
SendMessage(pCallbackParam->hEventFiringWnd, UM_EVENT_ITEMSTART, 0, LPARAM(&evtData));
evtData.bstrXmlData = NULL;
}
}
break;
case DOWNLOAD_STATUS_OK: // simple progress update
case DOWNLOAD_STATUS_ITEMCOMPLETE:
if (0 != pCallbackParam->flProgressPercentage)
{
// we need to give progress callbacks on given percentage increments
flNewPercentage = ((float)pCallbackParam->lTotalDownloaded / pCallbackParam->lTotalDownloadSize);
if (((flNewPercentage - pCallbackParam->flLastPercentage) >= pCallbackParam->flProgressPercentage) ||
((1.0 - flNewPercentage) < 0.0001 && 1 != pCallbackParam->flProgressPercentage))
{
// The Difference between LastPercentComplete and CurrentPercentComplete complies with the
// Progress Percentage granuluarity OR the percentage is 100 (complete)
if (evtData.fItemCompleted)
{
//wsprintfA(szProgressSize, "%d", (int)flNewPercentage); // float should be 1.0, cast as int will be 1
//
// when we notify the user this "item", not file, completed, we don't need
// to pass out any percentage info.
//
szProgressSize[0] = _T('\0');
}
else
{
if ((1.0 - flNewPercentage) < 0.0001)
{
if (ARRAYSIZE(szProgressSize) >= 2)
{
szProgressSize[0] = _T('1');
szProgressSize[1] = _T('\0');
}
else
{
break;
}
}
else
{
hr = StringCchPrintfExA(szProgressSize, ARRAYSIZE(szProgressSize),
NULL, NULL, MISTSAFE_STRING_FLAGS,
".%02d", (int)(flNewPercentage*100)); // string equivilant of a float
if (FAILED(hr))
{
LOG_ErrorMsg(hr);
break;
}
}
}
pCallbackParam->flLastPercentage = flNewPercentage;
}
else
{
// don't make a callback
break;
}
}
else
{
// No percentage callback was requested.. just give the byte values.
if (dwStatus == DOWNLOAD_STATUS_ITEMCOMPLETE)
{
szProgressSize[0] = _T('\0');
}
else
{
hr = StringCchPrintfExA(szProgressSize, ARRAYSIZE(szProgressSize),
NULL, NULL, MISTSAFE_STRING_FLAGS,
"%lu:%lu", (ULONG)pCallbackParam->lTotalDownloadSize, (ULONG)pCallbackParam->lTotalDownloaded);
if (FAILED(hr))
{
LOG_ErrorMsg(hr);
break;
}
}
}
evtData.bstrProgress = SysAllocString(A2OLE(szProgressSize));
if (NULL != pCallbackParam->pProgressListener)
{
pCallbackParam->pProgressListener->OnProgress(pCallbackParam->bstrOperationUuid,
evtData.fItemCompleted, evtData.bstrProgress, &evtData.lCommandRequest);
}
else
{
// only use event window if no progresslistener interface was given
if (NULL != pCallbackParam->hEventFiringWnd)
{
SendMessage(pCallbackParam->hEventFiringWnd, UM_EVENT_PROGRESS, 0, LPARAM(&evtData));
}
}
break;
case DOWNLOAD_STATUS_OPERATIONCOMPLETE:
if (NULL != pCallbackParam->pProgressListener)
{
pCallbackParam->pProgressListener->OnOperationComplete(pCallbackParam->bstrOperationUuid,
bstrXmlData);
}
else
{
// only use event window if no progresslistener interface was given
if (NULL != pCallbackParam->hEventFiringWnd)
{
evtData.bstrXmlData = bstrXmlData;
fPostWaitSuccess = WUPostEventAndBlock(pCallbackParam->hEventFiringWnd,
UM_EVENT_COMPLETE,
&evtData);
}
}
// Look for an existing Operation in the Mgr, and update the Complete Result if available.
if (pCallbackParam->pOperationMgr->FindOperation(OLE2T(pCallbackParam->bstrOperationUuid), &lUpdateMask, NULL))
{
pCallbackParam->pOperationMgr->UpdateOperation(OLE2T(pCallbackParam->bstrOperationUuid), lUpdateMask, bstrXmlData);
}
break;
case DOWNLOAD_STATUS_ABORTED:
case DOWNLOAD_STATUS_ERROR:
//
// abort case: user should know. nothing to report
// error case: progress callback doesn't give us any way of telling the caller that its an error, no reason to send the callback
// the itemcomplete callback will have the error status for the item.
//
break;
}
if (NULL != plCommandRequest) // we made a callback and the command request value was retrieved
{
*plCommandRequest = (LONG)((DWORD) evtData.lCommandRequest & (DWORD) UPDATE_COMMAND);
LOG_Download(_T("Command returned: 0x%08x"), *plCommandRequest);
}
// don't free up the strings below unless the wait succeeded in
// WUPostEventAndBlock. If we do free the strings up and the wait didn't
// succeed, then we run the risk of AVing ourselves. Note that fPostWaitSuccess
// is initialized to TRUE so if we will free these BSTRs if WUPostEventAndBlock
// is not called.
if (fPostWaitSuccess)
{
SysFreeString(evtData.bstrProgress);
SysFreeString(evtData.bstrUuidOperation);
}
return TRUE;
}
/////////////////////////////////////////////////////////////////////////////
// DownloadThreadProc()
//
// Thread Proc for Async Download. Retrieves the startup information from
// the input param and calls Download() from this seperate thread. The calling
// thread returns immediately.
//
// Input:
// lpv - void pointer to IUDOWNLOADSTARTINFO struct containing all information
// needed to call Download()
//
// Return:
// 0 - always, exit code is irrelevant since calling thread doesn't check the
// status of this thread after creation.
/////////////////////////////////////////////////////////////////////////////
DWORD WINAPI DownloadThreadProc(LPVOID lpv)
{
LOG_Block("DownloadThreadProc()");
//
// in this new thread need to call CoInitialize again
// but since we don't know who the caller is, what threading they
// are using, so we just use single apartment
//
HRESULT hr = CoInitialize(NULL);
if (FAILED(hr))
{
LogError(hr, "Asynchronous Download thread exiting");
LOG_ErrorMsg(hr);
return 0;
}
LOG_Download(_T("CoInitialize called successfully"));
PIUDOWNLOADSTARTUPINFO pStartupInfo = (PIUDOWNLOADSTARTUPINFO)lpv;
BSTR bstrXmlItems = NULL;
LOG_Download(_T("Download thread started, now the thread count=%d"), pStartupInfo->pEngUpdate->m_lThreadCounter);
//
// call synchronized download function in this thread
//
_Download(
pStartupInfo->bstrClientName,
pStartupInfo->bstrXmlCatalog,
pStartupInfo->bstrDestinationFolder,
pStartupInfo->lMode,
pStartupInfo->punkProgressListener,
pStartupInfo->hwnd,
pStartupInfo->bstrUuidOperation,
&bstrXmlItems,
pStartupInfo->pEngUpdate);
//
// pStartupInfo is a buffer allocated by calling thread, when we are done, we need to
// free it here
//
SysFreeString(pStartupInfo->bstrDestinationFolder);
SysFreeString(pStartupInfo->bstrXmlCatalog);
SysFreeString(pStartupInfo->bstrClientName);
SysFreeString(pStartupInfo->bstrUuidOperation);
SysFreeString(bstrXmlItems);
SafeRelease(pStartupInfo->punkProgressListener); // so the caller can free this object
CoUninitialize();
LOG_Download(_T("CoUninitialize called"));
InterlockedDecrement(&(pStartupInfo->pEngUpdate->m_lThreadCounter));
HeapFree(GetProcessHeap(), 0, pStartupInfo);
return 0;
}