windows-nt/Source/XPSP1/NT/admin/services/drizzle/newjob/cjob.cpp

4881 lines
110 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
/************************************************************************
Copyright (c) 2000 - 2000 Microsoft Corporation
Module Name :
cjob.cpp
Abstract :
Main code file for handling jobs and files.
Author :
Revision History :
***********************************************************************/
#include "stdafx.h"
#include <malloc.h>
#include <numeric>
#include <functional>
#include <algorithm>
#include <sddl.h>
#if !defined(BITS_V12_ON_NT4)
#include "cjob.tmh"
#endif
// infinite retry wait time
//
#define INFINITE_RETRY_DELAY UINT64(-1)
//
// This is the number of seconds to keep trying to cancel an upload session in progress.
//
#define UPLOAD_CANCEL_TIMEOUT (24 * 60 * 60)
#define DEFAULT_JOB_TIMEOUT_TIME (90 * 24 * 60 * 60)
#define PROGRESS_SERIALIZE_INTERVAL (30 * NanoSec100PerSec)
// largest reply blob that can be returned via GetReplyData
//
#define MAX_EASY_REPLY_DATA (1024 * 1024)
void CJob::OnNetworkDisconnect()
{
if (m_state == BG_JOB_STATE_QUEUED ||
m_state == BG_JOB_STATE_TRANSIENT_ERROR)
{
QMErrInfo err;
err.Set( SOURCE_HTTP_CLIENT_CONN, ERROR_STYLE_HRESULT, BG_E_NETWORK_DISCONNECTED, NULL );
err.result = QM_FILE_TRANSIENT_ERROR;
FileTransientError( &err );
}
}
void CJob::OnNetworkConnect()
{
if (m_state == BG_JOB_STATE_TRANSIENT_ERROR)
{
SetState( BG_JOB_STATE_QUEUED );
ScheduleModificationCallback();
}
}
//------------------------------------------------------------------------
CJob::CJob()
:
m_ExternalInterface( new CJobExternal),
m_state( BG_JOB_STATE_SUSPENDED ),
m_NotifyPointer( NULL ),
m_sd( NULL ),
m_CurrentFile( 0 ),
m_OldExternalJobInterface( NULL ),
m_OldExternalGroupInterface( NULL )
{
//
// constructor has succeeded; allow CJobExternal to manage our lifetime.
//
GetExternalInterface()->SetInterfaceClass(this);
}
CJob::CJob(
LPCWSTR DisplayName,
BG_JOB_TYPE Type,
REFGUID JobId,
SidHandle NotifySid
) :
m_ExternalInterface( new CJobExternal),
m_id( JobId ),
m_name( DisplayName ),
m_type( Type ),
m_priority( BG_JOB_PRIORITY_NORMAL ),
m_state( BG_JOB_STATE_SUSPENDED ),
m_retries( 0 ),
m_NotifySid( NotifySid ),
m_NotifyPointer( NULL ),
m_sd( NULL ),
m_CurrentFile( 0 ),
m_MinimumRetryDelay( g_GlobalInfo->m_DefaultMinimumRetryDelay ),
m_NoProgressTimeout( g_GlobalInfo->m_DefaultNoProgressTimeout ),
m_OldExternalJobInterface( NULL ),
m_OldExternalGroupInterface( NULL ),
m_TransferCompletionTime( UINT64ToFILETIME( 0 )),
m_SerializeTime( UINT64ToFILETIME( 0 )),
m_NotifyFlags( BG_NOTIFY_JOB_TRANSFERRED | BG_NOTIFY_JOB_ERROR ),
m_fGroupNotifySid( FALSE )
{
LogInfo( "new job %p : ID is %!guid!, external %p", this, &m_id, m_ExternalInterface );
GetSystemTimeAsFileTime( &m_CreationTime );
m_ModificationTime = m_CreationTime;
m_LastAccessTime = m_CreationTime;
// we don't support group SIDs yet.
// THROW_HRESULT( IsGroupSid( m_NotifySid, &m_fGroupNotifySid ))
m_sd = new CJobSecurityDescriptor( NotifySid );
//
// constructor has succeeded; allow CJobExternal to manage our lifetime.
//
GetExternalInterface()->SetInterfaceClass(this);
}
CJob::~CJob()
{
//
// This should be redundant, but let's be safe.
//
g_Manager->m_TaskScheduler.CancelWorkItem( static_cast<CJobModificationItem *> (this) );
CancelWorkitems();
delete m_sd;
for (CFileList::iterator iter = m_files.begin(); iter != m_files.end(); ++iter)
{
delete (*iter);
}
m_files.clear();
if (g_LastServiceControl != SERVICE_CONTROL_SHUTDOWN)
{
SafeRelease( m_NotifyPointer );
}
}
void CJob::UnlinkFromExternalInterfaces()
{
//
// These objects np longer control the CJob's lifetime...
//
if (m_ExternalInterface)
{
m_ExternalInterface->SetInterfaceClass( NULL );
}
if (m_OldExternalJobInterface)
{
m_OldExternalJobInterface->SetInterfaceClass( NULL );
}
if (m_OldExternalGroupInterface)
{
m_OldExternalGroupInterface->SetInterfaceClass( NULL );
}
//
// ...and the CJob no longer holds a reference to them.
//
SafeRelease( m_ExternalInterface );
SafeRelease( m_OldExternalJobInterface );
SafeRelease( m_OldExternalGroupInterface );
}
void
CJob::HandleAddFile()
{
if ( m_state == BG_JOB_STATE_TRANSFERRED )
{
SetState( BG_JOB_STATE_QUEUED );
m_TransferCompletionTime = UINT64ToFILETIME( 0 );
g_Manager->m_TaskScheduler.CancelWorkItem( (CJobRetryItem *) this );
g_Manager->m_TaskScheduler.CancelWorkItem( (CJobCallbackItem *) this );
g_Manager->m_TaskScheduler.CancelWorkItem( (CJobNoProgressItem *) this );
}
UpdateModificationTime();
// restart the downloader if its running.
g_Manager->RetaskJob( this );
}
//
// Returns E_INVALIDARG if one of the filesets has
// - local name is blank
// - local name contains invalid characters
// - remote name is blank
// - remote name has invalid format
//
// Returns CO_E_NOT_SUPPORTED if
// - remote URL contains unsupported protocol
//
HRESULT
CJob::AddFileSet(
IN ULONG cFileCount,
IN BG_FILE_INFO *pFileSet
)
{
ULONG FirstNewIndex = m_files.size();
try
{
ULONG i;
g_Manager->ExtendMetadata( ( METADATA_FOR_FILE * cFileCount ) + METADATA_PADDING );
for (i=0; i < cFileCount; ++i)
{
THROW_HRESULT( AddFile( pFileSet[i].RemoteName,
pFileSet[i].LocalName,
false
));
}
HandleAddFile();
return S_OK;
}
catch ( ComError exception )
{
// remove all the files that were successful
// This assumes that new files are added at the back of the sequence.
//
m_files.Delete( m_files.begin() + FirstNewIndex, m_files.end() );
g_Manager->ShrinkMetadata();
return exception.Error();
}
}
HRESULT
CJob::AddFile(
IN LPCWSTR RemoteName,
IN LPCWSTR LocalName,
IN bool SingleAdd
)
{
HRESULT hr = S_OK;
CFile * file = NULL;
//
// This check must be completed outside the try..except; otherwise
// the attempt to add a 2nd file would delete the generated reply file
// for the 1st file.
//
if (m_type != BG_JOB_TYPE_DOWNLOAD && m_files.size() > 0)
{
return E_INVALIDARG;
}
try
{
if ( !RemoteName || !LocalName )
THROW_HRESULT( E_INVALIDARG );
LogInfo("job %p addfile( %S, %S )", this, RemoteName, LocalName );
if ( ( _GetState() == BG_JOB_STATE_CANCELLED ) ||
( _GetState() == BG_JOB_STATE_ACKNOWLEDGED ) )
throw ComError( BG_E_INVALID_STATE );
if ( SingleAdd )
g_Manager->ExtendMetadata( METADATA_FOR_FILE + METADATA_PADDING );
//
// Impersonate the user while checking file access.
//
CNestedImpersonation imp;
imp.SwitchToLogonToken();
file = new CFile( this, m_type, RemoteName, LocalName );
// WARNING: if you change this, also update the cleanup logic in AddFileSet.
//
m_files.push_back( file );
//
// Try to create the default reply file. Ignore error, because the app
// may be planning to set the reply file somewhere else.
//
if (m_type == BG_JOB_TYPE_UPLOAD_REPLY)
{
((CUploadJob *) this)->GenerateReplyFile( false );
}
}
catch ( ComError exception )
{
delete file;
file = NULL;
if (m_type == BG_JOB_TYPE_UPLOAD_REPLY)
{
((CUploadJob *) this)->DeleteGeneratedReplyFile();
((CUploadJob *) this)->ClearOwnFileNameBit();
}
if ( SingleAdd )
g_Manager->ShrinkMetadata();
hr = exception.Error();
}
if ( SUCCEEDED(hr) && SingleAdd )
{
HandleAddFile();
}
return hr;
}
HRESULT
CJob::SetDisplayName(
LPCWSTR Val
)
{
return SetLimitedString( m_name, Val, MAX_DISPLAYNAME );
}
HRESULT
CJob::GetDisplayName(
LPWSTR * pVal
) const
{
*pVal = MidlCopyString( m_name );
return (*pVal) ? S_OK : E_OUTOFMEMORY;
}
HRESULT
CJob::SetDescription(
LPCWSTR Val
)
{
return SetLimitedString( m_description, Val, MAX_DESCRIPTION );
}
HRESULT
CJob::GetDescription(
LPWSTR *pVal
) const
{
*pVal = MidlCopyString( m_description );
return (*pVal) ? S_OK : E_OUTOFMEMORY;
}
HRESULT
CJob::SetNotifyCmdLine(
LPCWSTR Val
)
{
return SetLimitedString( m_NotifyCmdLine, Val, MAX_NOTIFY_CMD_LINE );
}
HRESULT
CJob::GetNotifyCmdLine(
LPWSTR *pVal
) const
{
*pVal = MidlCopyString( m_NotifyCmdLine );
return (*pVal) ? S_OK : E_OUTOFMEMORY;
}
HRESULT
CJob::SetProxySettings(
BG_JOB_PROXY_USAGE ProxyUsage,
LPCWSTR ProxyList,
LPCWSTR ProxyBypassList
)
{
HRESULT hr = S_OK;
if ( ProxyUsage != BG_JOB_PROXY_USAGE_PRECONFIG &&
ProxyUsage != BG_JOB_PROXY_USAGE_NO_PROXY &&
ProxyUsage != BG_JOB_PROXY_USAGE_OVERRIDE )
{
return E_INVALIDARG;
}
if ( BG_JOB_PROXY_USAGE_PRECONFIG == ProxyUsage ||
BG_JOB_PROXY_USAGE_NO_PROXY == ProxyUsage )
{
if ( NULL != ProxyList ||
NULL != ProxyBypassList )
return E_INVALIDARG;
}
else
{
// BG_PROXY_USAGE_OVERRIDE == ProxyUsage
if ( NULL == ProxyList )
return E_INVALIDARG;
}
try
{
//
// Allocate space for the new proxy settings.
//
CAutoString ProxyListTemp(NULL);
CAutoString ProxyBypassListTemp(NULL);
g_Manager->ExtendMetadata();
if ( ProxyList )
{
if ( wcslen( ProxyList ) > MAX_PROXYLIST )
throw ComError( BG_E_PROXY_LIST_TOO_LARGE );
ProxyListTemp = CAutoString( CopyString( ProxyList ));
}
if ( ProxyBypassList )
{
if ( wcslen( ProxyBypassList ) > MAX_PROXYBYPASSLIST )
throw ComError( BG_E_PROXY_BYPASS_LIST_TOO_LARGE );
ProxyBypassListTemp = CAutoString( CopyString( ProxyBypassList ));
}
//
// Swap the old proxy settings for the new ones.
//
delete[] m_ProxySettings.ProxyList;
delete[] m_ProxySettings.ProxyBypassList;
m_ProxySettings.ProxyUsage = ProxyUsage;
m_ProxySettings.ProxyList = ProxyListTemp.release();
m_ProxySettings.ProxyBypassList = ProxyBypassListTemp.release();
//
// Interrupt the download so that the settings are in force immediately.
//
g_Manager->RetaskJob( this );
UpdateModificationTime();
return S_OK;
}
catch( ComError error )
{
g_Manager->ShrinkMetadata();
return error.Error();
}
}
HRESULT
CJob::GetProxySettings(
BG_JOB_PROXY_USAGE *pProxyUsage,
LPWSTR *pProxyList,
LPWSTR *pProxyBypassList
) const
{
HRESULT Hr = S_OK;
*pProxyUsage = m_ProxySettings.ProxyUsage;
*pProxyList = NULL;
*pProxyBypassList = NULL;
try
{
if ( m_ProxySettings.ProxyList )
{
*pProxyList = MidlCopyString( m_ProxySettings.ProxyList );
if (!*pProxyList)
throw ComError( E_OUTOFMEMORY );
}
if ( m_ProxySettings.ProxyBypassList )
{
*pProxyBypassList = MidlCopyString( m_ProxySettings.ProxyBypassList );
if (!*pProxyBypassList)
throw ComError( E_OUTOFMEMORY );
}
}
catch( ComError exception )
{
Hr = exception.Error();
CoTaskMemFree( *pProxyList );
CoTaskMemFree( *pProxyBypassList );
*pProxyList = *pProxyBypassList = NULL;
}
return Hr;
}
void
CJob::GetTimes(
BG_JOB_TIMES * s
) const
{
s->CreationTime = m_CreationTime;
s->ModificationTime = m_ModificationTime;
s->TransferCompletionTime = m_TransferCompletionTime;
}
void
CJob::GetProgress(
BG_JOB_PROGRESS * s
) const
{
s->BytesTransferred = 0;
s->BytesTotal = 0;
CFileList::const_iterator iter;
for (iter = m_files.begin(); iter != m_files.end(); ++iter)
{
BG_FILE_PROGRESS s2;
(*iter)->GetProgress( &s2 );
s->BytesTransferred += s2.BytesTransferred;
if (s2.BytesTotal != BG_SIZE_UNKNOWN &&
s->BytesTotal != BG_SIZE_UNKNOWN )
{
s->BytesTotal += s2.BytesTotal;
}
else
{
s->BytesTotal = BG_SIZE_UNKNOWN;
}
}
s->FilesTransferred = m_CurrentFile;
s->FilesTotal = m_files.size();
}
HRESULT
CJob::GetOwner(
LPWSTR * pVal
) const
{
wchar_t * buf;
wchar_t * str;
if (!ConvertSidToStringSid( m_NotifySid.get(), &str))
{
return HRESULT_FROM_WIN32( GetLastError());
}
*pVal = MidlCopyString( str );
LocalFree( str );
return (*pVal) ? S_OK : E_OUTOFMEMORY;
}
HRESULT
CJob::SetPriority(
BG_JOB_PRIORITY Val
)
{
if (Val > BG_JOB_PRIORITY_LOW ||
Val < BG_JOB_PRIORITY_FOREGROUND)
{
return E_NOTIMPL;
}
if (Val == m_priority)
{
return S_OK;
}
m_priority = Val;
g_Manager->RetaskJob( this );
UpdateModificationTime();
return S_OK;
}
HRESULT
CJob::SetNotifyFlags(
ULONG Val
)
{
// Note, this flag will have no affect on a callback already in progress.
if ( Val & ~(BG_NOTIFY_JOB_TRANSFERRED | BG_NOTIFY_JOB_ERROR | BG_NOTIFY_DISABLE | BG_NOTIFY_JOB_MODIFICATION ) )
{
return E_NOTIMPL;
}
m_NotifyFlags = Val;
UpdateModificationTime();
return S_OK;
}
HRESULT
CJob::SetNotifyInterface(
IUnknown * Val
)
{
// Note, this flag may not have any affect on a callback already in progress.
IBackgroundCopyCallback *pICB = NULL;
if ( Val )
{
try
{
#if !defined( BITS_V12_ON_NT4 )
CNestedImpersonation imp;
imp.SwitchToLogonToken();
THROW_HRESULT( SetStaticCloaking( Val ) );
#endif
THROW_HRESULT( Val->QueryInterface( __uuidof(IBackgroundCopyCallback),
(void **) &pICB ) );
#if !defined( BITS_V12_ON_NT4 )
// All callbacks should happen in the context of the
// person who set the interface pointer.
HRESULT Hr = SetStaticCloaking( pICB );
if ( FAILED( Hr ) )
{
SafeRelease( pICB );
throw ComError( Hr );
}
#endif
}
catch( ComError Error )
{
return Error.Error();
}
}
// Release the old pointer if it exists
SafeRelease( m_NotifyPointer );
m_NotifyPointer = pICB;
return S_OK;
}
HRESULT
CJob::GetNotifyInterface(
IUnknown ** ppVal
) const
{
try
{
CNestedImpersonation imp;
if (m_NotifyPointer)
{
m_NotifyPointer->AddRef();
}
*ppVal = m_NotifyPointer;
return S_OK;
}
catch ( ComError err )
{
*ppVal = NULL;
return err.Error();
}
}
// CJob::TestNotifyInterface()
//
// See if a notification interface is provide, if so, test it to see if it is
// valid. If so, then return TRUE, else return FALSE.
BOOL
CJob::TestNotifyInterface()
{
BOOL fValidNotifyInterface = TRUE;
try
{
CNestedImpersonation imp;
IUnknown *pPrevIntf = NULL;
// Ok, see if there was a previously registered interface, and if
// there is, see if it's still valid.
if (m_NotifyPointer)
{
m_NotifyPointer->AddRef();
if ( (FAILED(m_NotifyPointer->QueryInterface(IID_IUnknown,(void**)&pPrevIntf)))
||(pPrevIntf == NULL) )
{
fValidNotifyInterface = FALSE;
}
else
{
fValidNotifyInterface = TRUE;
pPrevIntf->Release();
}
m_NotifyPointer->Release();
}
else
{
fValidNotifyInterface = FALSE;
}
}
catch( ComError err )
{
fValidNotifyInterface = FALSE;
}
return fValidNotifyInterface;
}
HRESULT
CJob::GetMinimumRetryDelay(
ULONG * pVal
) const
{
*pVal = m_MinimumRetryDelay;
return S_OK;
}
HRESULT
CJob::SetMinimumRetryDelay(
ULONG Val
)
{
m_MinimumRetryDelay = Val;
g_Manager->m_TaskScheduler.RescheduleDelayedTask(
(CJobRetryItem *)this,
(UINT64)m_MinimumRetryDelay * (UINT64) NanoSec100PerSec);
UpdateModificationTime();
return S_OK;
}
HRESULT
CJob::GetNoProgressTimeout(
ULONG * pVal
) const
{
*pVal = m_NoProgressTimeout;
return S_OK;
}
HRESULT
CJob::SetNoProgressTimeout(
ULONG Val
)
{
m_NoProgressTimeout = Val;
g_Manager->m_TaskScheduler.RescheduleDelayedTask(
(CJobNoProgressItem *)this,
(UINT64)m_NoProgressTimeout * (UINT64) NanoSec100PerSec);
UpdateModificationTime();
return S_OK;
}
HRESULT
CJob::GetErrorCount(
ULONG * pVal
) const
{
*pVal = m_retries;
return S_OK;
}
HRESULT
CJob::IsVisible()
{
HRESULT hr;
hr = CheckClientAccess( BG_JOB_READ );
if (hr == S_OK)
{
return S_OK;
}
if (hr == E_ACCESSDENIED)
{
return S_FALSE;
}
return hr;
}
bool
CJob::IsOwner(
SidHandle sid
)
{
return (sid == m_NotifySid);
}
void CJob::SetState( BG_JOB_STATE state )
{
if (m_state == state)
{
return;
}
LogInfo("job %p state %d -> %d", this, m_state, state);
m_state = state;
bool ShouldClearError = false;
switch( state )
{
case BG_JOB_STATE_QUEUED:
case BG_JOB_STATE_CONNECTING:
ShouldClearError = false;
break;
case BG_JOB_STATE_TRANSFERRING:
case BG_JOB_STATE_SUSPENDED:
ShouldClearError = true;
break;
case BG_JOB_STATE_ERROR:
case BG_JOB_STATE_TRANSIENT_ERROR:
ShouldClearError = false;
break;
case BG_JOB_STATE_TRANSFERRED:
case BG_JOB_STATE_ACKNOWLEDGED:
case BG_JOB_STATE_CANCELLED:
ShouldClearError = true;
break;
default:
ASSERT(0);
break;
}
if (ShouldClearError)
m_error.ClearError();
if (state != BG_JOB_STATE_TRANSIENT_ERROR)
{
g_Manager->m_TaskScheduler.CancelWorkItem( (CJobRetryItem *) this );
}
UpdateModificationTime( false );
}
GENERIC_MAPPING CJob::s_AccessMapping =
{
STANDARD_RIGHTS_READ,
STANDARD_RIGHTS_WRITE,
STANDARD_RIGHTS_EXECUTE,
STANDARD_RIGHTS_ALL
};
HRESULT
CJob::CheckClientAccess(
IN DWORD RequestedAccess
) const
/*
Checks the current thread's access to this group. The token must allow impersonation.
RequestedAccess lists the standard access bits that the client needs.
*/
{
HRESULT hr = S_OK;
BOOL fSuccess = FALSE;
DWORD AllowedAccess = 0;
HANDLE hToken = 0;
//
// Convert generic bits into specific bits.
//
MapGenericMask( &RequestedAccess, &s_AccessMapping );
try
{
if ( ( RequestedAccess & ~BG_JOB_READ ) &&
( ( m_state == BG_JOB_STATE_CANCELLED ) || ( m_state == BG_JOB_STATE_ACKNOWLEDGED ) ) )
{
LogError("Denying non-read access since job/file is cancelled or acknowledged");
throw ComError(BG_E_INVALID_STATE);
}
CNestedImpersonation imp;
hr = IsRemoteUser();
if (FAILED(hr) )
throw ComError( hr );
if ( S_OK == hr )
throw ComError( BG_E_REMOTE_NOT_SUPPORTED );
THROW_HRESULT(
m_sd->CheckTokenAccess( imp.QueryToken(),
RequestedAccess,
&AllowedAccess,
&fSuccess
));
if (!fSuccess || AllowedAccess != RequestedAccess)
{
LogWarning( "denied access %s 0x%x", fSuccess ? "TRUE" : "FALSE", AllowedAccess );
throw ComError( E_ACCESSDENIED );
}
hr = S_OK;
}
catch (ComError exception)
{
hr = exception.Error();
}
if (hToken)
{
CloseHandle( hToken );
}
return hr;
}
bool
CJob::IsCallbackEnabled(
DWORD bit
)
{
//
// Only one bit, please.
//
ASSERT( 0 == (bit & (bit-1)) );
if ((m_NotifyFlags & bit) == 0 ||
(m_NotifyFlags & BG_NOTIFY_DISABLE))
{
return false;
}
if (m_OldExternalGroupInterface)
{
IBackgroundCopyCallback1 * pif = m_OldExternalGroupInterface->GetNotificationPointer();
if (pif == NULL)
{
return false;
}
pif->Release();
}
else
{
if (m_NotifyPointer == NULL && m_NotifyCmdLine.Size() == 0)
{
return false;
}
}
return true;
}
void
CJob::ScheduleCompletionCallback(
DWORD Seconds
)
{
//
// See whether any notification regime has been established.
// The callback procedure will check this again, in case something has changed
// between queuing the workitem and dispatching it.
//
if (!IsCallbackEnabled( BG_NOTIFY_JOB_TRANSFERRED ))
{
LogInfo("completion callback is not enabled");
return;
}
if (g_Manager->m_TaskScheduler.IsWorkItemInScheduler( static_cast<CJobCallbackItem *>(this) ))
{
LogInfo("callback is already scheduled");
return;
}
g_Manager->ScheduleDelayedTask( (CJobCallbackItem *) this, Seconds );
}
void
CJob::ScheduleErrorCallback(
DWORD Seconds
)
{
//
// See whether any notification regime has been established.
// The callback procedure will check this again, in case something has changed
// between queuing the workitem and dispatching it.
//
if (!IsCallbackEnabled( BG_NOTIFY_JOB_ERROR ))
{
LogInfo("error callback is not enabled");
return;
}
if (g_Manager->m_TaskScheduler.IsWorkItemInScheduler( static_cast<CJobCallbackItem *>(this) ))
{
LogInfo("callback is already scheduled");
return;
}
g_Manager->ScheduleDelayedTask( (CJobCallbackItem *) this, Seconds );
}
HRESULT
CJob::DeleteTemporaryFiles()
{
return S_OK;
}
void
CJob::JobTransferred()
{
// the file list is done
SetState( BG_JOB_STATE_TRANSFERRED );
g_Manager->m_TaskScheduler.CancelWorkItem( static_cast<CJobNoProgressItem *>( this ));
SetCompletionTime();
ScheduleCompletionCallback();
}
void
CJob::Transfer()
{
HRESULT hr;
auto_HANDLE<NULL> AutoToken;
#if !defined( BITS_V12_ON_NT4 )
if( LogLevelEnabled( LogFlagInfo ) )
{
LogDl( "current job: %!guid!", &m_id );
}
#endif
//
// Get a copy of the user's token.
//
HANDLE hToken = NULL;
hr = g_Manager->CloneUserToken( GetOwnerSid(), ANY_SESSION, &hToken );
if (FAILED(hr))
{
if (hr == HRESULT_FROM_WIN32( ERROR_NOT_LOGGED_ON ))
{
LogDl( "job owner is not logged on");
// move the group off the main list.
g_Manager->MoveJobOffline( this );
MoveToInactiveState();
ScheduleModificationCallback();
}
else
{
QMErrInfo ErrInfo;
ErrInfo.Set( SOURCE_QMGR_QUEUE, ERROR_STYLE_HRESULT, hr, "CloneUserToken" );
LogError( "download : unable to get token %!winerr!", hr);
FileTransientError( &ErrInfo );
}
g_Manager->m_TaskScheduler.CompleteWorkItem();
return;
}
AutoToken = hToken;
//
// Download the current file.
//
QMErrInfo ErrInfo;
long tries = 0;
bool bThrottle = ShouldThrottle();
LogDl( "Throttling %s", bThrottle ? "enabled" : "disabled" );
if (bThrottle)
{
// ignore errors
//
(void) SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_IDLE );
}
if (m_state != BG_JOB_STATE_TRANSFERRING)
{
SetState( BG_JOB_STATE_CONNECTING );
ScheduleModificationCallback();
}
if (!VerifyFileSizes( hToken ))
{
goto restore_thread;
}
ASSERT( GetCurrentFile() ); // if no more files, it shouldn't be the current job
retry:
ErrInfo.Clear();
if (!GetCurrentFile()->Transfer( hToken,
m_priority,
m_ProxySettings,
&m_Credentials,
ErrInfo ))
{
goto restore_thread;
}
//
// Interpret the download result.
//
switch (ErrInfo.result)
{
case QM_FILE_TRANSIENT_ERROR: FileTransientError( &ErrInfo ); break;
case QM_FILE_DONE: FileComplete(); break;
case QM_FILE_FATAL_ERROR: FileFatalError( &ErrInfo ); break;
case QM_FILE_ABORTED: break;
default: ASSERT( 0 && "unhandled download result" ); break;
case QM_SERVER_FILE_CHANGED:
{
FileChangedOnServer();
if (++tries < 3)
{
goto retry;
}
g_Manager->AppendOnline( this );
break;
}
}
restore_thread:
if (bThrottle)
{
while (!SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_NORMAL ))
{
Sleep(100);
}
}
}
void
CJob::FileComplete()
{
if ( GetOldExternalJobInterface() )
{
// Need to rename the files as they are completed for Mars.
HRESULT Hr = GetCurrentFile()->MoveTempFile();
if (FAILED(Hr))
{
QMErrInfo ErrorInfo;
ErrorInfo.Set( SOURCE_QMGR_FILE, ERROR_STYLE_HRESULT, Hr, "Unable to rename file" );
FileFatalError( &ErrorInfo );
return;
}
}
++m_CurrentFile;
if (m_CurrentFile == m_files.size())
{
JobTransferred();
g_Manager->Serialize();
}
else
{
// more files to download
UpdateModificationTime();
}
}
bool CJob::VerifyFileSizes(
HANDLE hToken
)
{
if ( AreRemoteSizesKnown() )
{
return true;
}
try
{
// retrieve file infomation on the file list.
// Ignore any errors.
LogDl("Need to retrieve file sizes before download can start");
auto_ptr<CUnknownFileSizeList> pFileSizeList = auto_ptr<CUnknownFileSizeList>( GetUnknownFileSizeList() );
QMErrInfo ErrInfo;
//
// Release the global lock while the download is in progress.
//
g_Manager->m_TaskScheduler.UnlockWriter();
LogDl( "UpdateRemoteSizes starting..." );
g_Manager->UpdateRemoteSizes( pFileSizeList.get(),
hToken,
&ErrInfo,
&m_ProxySettings,
&m_Credentials
);
LogDl( "UpdateRemoteSizes complete." );
ErrInfo.Log();
ASSERT( ErrInfo.result != QM_IN_PROGRESS );
bool fSuccessful = (ErrInfo.result != QM_FILE_ABORTED);
//
// Take the writer lock, since the caller expects it to be taken
// upon return.
//
while (g_Manager->m_TaskScheduler.LockWriter() )
{
g_Manager->m_TaskScheduler.AcknowledgeWorkItemCancel();
fSuccessful = false;
}
return fSuccessful;
}
catch (ComError err)
{
LogWarning("caught exception %u", err.Error() );
return false;
}
}
bool CJob::IsRunning()
{
if (m_state == BG_JOB_STATE_TRANSFERRING ||
m_state == BG_JOB_STATE_CONNECTING)
{
return true;
}
return false;
}
bool CJob::IsRunnable()
{
if (m_state == BG_JOB_STATE_TRANSFERRING ||
m_state == BG_JOB_STATE_CONNECTING ||
m_state == BG_JOB_STATE_QUEUED )
{
return true;
}
return false;
}
void
CJob::FileTransientError(
QMErrInfo * ErrInfo
)
{
LogWarning( "job %p transient failure, interrupt count = %d", this, m_retries );
if (_GetState() == BG_JOB_STATE_TRANSFERRING)
{
++m_retries;
}
SetState( BG_JOB_STATE_TRANSIENT_ERROR );
RecordError( ErrInfo );
#if !defined( BITS_V12_ON_NT4 )
if (g_Manager->m_NetworkMonitor.GetAddressCount() > 0)
{
#endif
g_Manager->ScheduleDelayedTask( (CJobRetryItem *) this, m_MinimumRetryDelay );
#if !defined( BITS_V12_ON_NT4 )
}
#endif
if ( m_NoProgressTimeout != INFINITE &&
!g_Manager->m_TaskScheduler.IsWorkItemInScheduler((CJobNoProgressItem *) this))
{
g_Manager->ScheduleDelayedTask( (CJobNoProgressItem *) this, m_NoProgressTimeout );
}
UpdateModificationTime();
}
bool
CJob::RecordError(
QMErrInfo * ErrInfo
)
{
m_error.Set( this, m_CurrentFile, ErrInfo );
return true;
}
void
CJob::FileFatalError(
QMErrInfo * ErrInfo
)
{
// If ErrInfo is NULL, use the current error.
if ( BG_JOB_STATE_TRANSFERRING == m_state )
{
++m_retries;
}
g_Manager->m_TaskScheduler.CancelWorkItem( static_cast<CJobNoProgressItem *>(this) );
g_Manager->m_TaskScheduler.CancelWorkItem( static_cast<CJobCallbackItem *>(this) );
SetState( BG_JOB_STATE_ERROR );
if ( ErrInfo )
{
RecordError( ErrInfo );
}
ScheduleErrorCallback();
g_Manager->Serialize();
}
void CJob::OnRetryJob()
{
if (g_Manager->m_TaskScheduler.LockWriter() )
{
g_Manager->m_TaskScheduler.AcknowledgeWorkItemCancel();
return;
}
g_Manager->m_TaskScheduler.CompleteWorkItem();
ASSERT( m_state == BG_JOB_STATE_TRANSIENT_ERROR );
SetState( BG_JOB_STATE_QUEUED );
UpdateModificationTime();
g_Manager->ScheduleAnotherGroup();
g_Manager->m_TaskScheduler.UnlockWriter();
}
void CJob::RetryNow()
{
MoveToInactiveState();
UpdateModificationTime( false );
//
// Normally UpdateModificationTime() would do these things for us,
// but we chose not to serialize.
//
if (g_Manager->m_TaskScheduler.IsWorkItemInScheduler( (CJobInactivityTimeout *) this))
{
g_Manager->m_TaskScheduler.CancelWorkItem( (CJobInactivityTimeout *) this );
g_Manager->m_TaskScheduler.InsertDelayedWorkItem( (CJobInactivityTimeout *) this, g_GlobalInfo->m_JobInactivityTimeout );
}
ScheduleModificationCallback();
}
void CJob::OnNoProgress()
{
LogInfo("job %p no-progress timeout", this);
if (g_Manager->m_TaskScheduler.LockWriter() )
{
g_Manager->m_TaskScheduler.AcknowledgeWorkItemCancel();
return;
}
//
// Make sure the downloader thread isn't using the job.
// Otherwise MoveActiveJobToListEnd may get confused.
//
switch (m_state)
{
case BG_JOB_STATE_TRANSFERRING:
{
// The job is making progress, after all.
//
g_Manager->m_TaskScheduler.CompleteWorkItem();
g_Manager->m_TaskScheduler.UnlockWriter();
return;
}
case BG_JOB_STATE_CONNECTING:
{
g_Manager->InterruptDownload();
break;
}
}
g_Manager->m_TaskScheduler.CompleteWorkItem();
FileFatalError( NULL );
g_Manager->ScheduleAnotherGroup();
g_Manager->m_TaskScheduler.UnlockWriter();
}
void CJob::UpdateProgress(
UINT64 BytesTransferred,
UINT64 BytesTotal
)
{
SetState( BG_JOB_STATE_TRANSFERRING );
g_Manager->m_TaskScheduler.CancelWorkItem( (CJobNoProgressItem *) this );
ScheduleModificationCallback();
//
// To avoid hammering the disk,
// don't serialize every interim progress notification.
//
FILETIME time;
GetSystemTimeAsFileTime( &time );
if (FILETIMEToUINT64(time) - FILETIMEToUINT64(m_SerializeTime) > PROGRESS_SERIALIZE_INTERVAL )
{
UpdateModificationTime();
}
}
void CJob::OnInactivityTimeout()
{
if (g_Manager->m_TaskScheduler.LockWriter() )
{
g_Manager->m_TaskScheduler.AcknowledgeWorkItemCancel();
return;
}
g_Manager->m_TaskScheduler.CompleteWorkItem();
Cancel();
g_Manager->m_TaskScheduler.UnlockWriter();
}
BOOL IsInterfacePointerDead(
IUnknown * punk,
HRESULT hr
)
{
if (hr == MAKE_HRESULT( SEVERITY_ERROR, FACILITY_WIN32, RPC_S_SERVER_UNAVAILABLE ))
{
return TRUE;
}
return FALSE;
}
void CJob::OnMakeCallback()
/*++
Description:
Used to notify the client app of job completion or a non-recoverable error.
Impersonates the user, CoCreates a notification object, and calls the method.
If the call fails, the fn posts a delayed task to retry.
At entry:
m_method: the method to call
m_notifysid: the user to impersonate
m_error: (if m_method is CM_ERROR) the error that halted the job
(if m_method is CM_COMPLETE) zero
m_RetryTime: sleep time before retrying after a failed notification attempt
At exit:
--*/
{
//
// check for cancel, and take a reference so the job cannot be deleted
// while this precedure is using it.
//
if (g_Manager->m_TaskScheduler.LockReader())
{
g_Manager->m_TaskScheduler.AcknowledgeWorkItemCancel();
return;
}
bool OldInterface = (m_OldExternalGroupInterface != NULL);
GetExternalInterface()->AddRef();
g_Manager->m_TaskScheduler.UnlockReader();
//
// Need to have this item out of the queue before the call,
// otherwise an incoming CompleteJob() call may block trying to remove it
// from the task scheduler queue.
// Also prevents CancelWorkItem calls from interfering with our mutex access.
//
g_Manager->m_TaskScheduler.CompleteWorkItem();
if (OldInterface)
{
if (FAILED(OldInterfaceCallback()))
{
RescheduleCallback();
}
}
else
{
if (FAILED(InterfaceCallback()) &&
FAILED(CmdLineCallback()))
{
RescheduleCallback();
}
}
GetExternalInterface()->Release();
}
HRESULT
CJob::RescheduleCallback()
{
if (g_Manager->m_TaskScheduler.LockWriter() )
{
LogInfo( "callback was cancelled" );
g_Manager->m_TaskScheduler.AcknowledgeWorkItemCancel();
return S_FALSE;
}
switch (m_state)
{
case BG_JOB_STATE_TRANSFERRED:
{
ScheduleCompletionCallback( m_MinimumRetryDelay );
break;
}
case BG_JOB_STATE_ERROR:
{
ScheduleErrorCallback( m_MinimumRetryDelay );
break;
}
default:
{
LogInfo("callback failed; job state is %d so no retry is planned", m_state );
}
}
g_Manager->m_TaskScheduler.UnlockWriter();
return S_OK;
}
void
CJob::OnModificationCallback()
{
if (g_Manager->m_TaskScheduler.LockWriter() )
{
LogInfo( "Modification call cancelled, ack cancel" );
g_Manager->m_TaskScheduler.AcknowledgeWorkItemCancel();
return;
}
if (!IsCallbackEnabled( BG_NOTIFY_JOB_MODIFICATION ))
{
LogInfo( "Modification call cancelled via flag/interface change" );
m_ModificationsPending = 0;
g_Manager->m_TaskScheduler.CancelWorkItem(
g_Manager->m_TaskScheduler.GetCurrentWorkItem());
GetExternalInterface()->Release();
g_Manager->m_TaskScheduler.UnlockWriter();
return;
}
IBackgroundCopyCallback *pICB = m_NotifyPointer;
pICB->AddRef();
g_Manager->m_TaskScheduler.UnlockWriter();
HRESULT Hr = pICB->JobModification( GetExternalInterface(), 0 );
LogInfo( "JobModification call complete, result %!winerr!", Hr );
SafeRelease( pICB );
if (g_Manager->m_TaskScheduler.LockWriter() )
{
LogInfo( "Modification work item canceled before lock reaquire" );
g_Manager->m_TaskScheduler.AcknowledgeWorkItemCancel();
return;
}
g_Manager->m_TaskScheduler.CompleteWorkItem();
m_ModificationsPending--;
if ( FAILED(Hr) && IsInterfacePointerDead( m_NotifyPointer, Hr ) )
{
LogInfo( "Modification interface pointer is dead, no more modifications" );
m_ModificationsPending = 0;
}
if ( m_ModificationsPending )
{
LogInfo( "%u more modification callbacks pending, reinsert work item", m_ModificationsPending );
g_Manager->m_TaskScheduler.InsertWorkItem( static_cast<CJobModificationItem*>(this) );
}
else
{
LogInfo( "no more modification callbacks pending, release interface ref" );
GetExternalInterface()->Release();
}
g_Manager->m_TaskScheduler.UnlockWriter();
}
void
CJob::ScheduleModificationCallback()
{
// Requires writer lock
//
// The old interface doesn't support this.
//
if (m_OldExternalGroupInterface)
{
return;
}
if (!IsCallbackEnabled( BG_NOTIFY_JOB_MODIFICATION ))
{
return;
}
if ( !m_ModificationsPending )
{
LogInfo( "New modification callback, adding work item for job %p", this );
GetExternalInterface()->AddRef();
g_Manager->m_TaskScheduler.InsertWorkItem( static_cast<CJobModificationItem*>(this) );
}
m_ModificationsPending++;
min( m_ModificationsPending, 0x7FFFFFFE );
LogInfo( "Added modification callback, new count of %u for job %p", m_ModificationsPending, this );
}
HRESULT
CJob::InterfaceCallback()
{
bool bLocked = true;
HRESULT hr;
IBackgroundCopyCallback * pICB = 0;
IBackgroundCopyError * pJobErrorExternal = 0;
try
{
CallbackMethod method;
IBackgroundCopyJob * pJobExternal = 0;
{
HoldReaderLock lock ( g_Manager->m_TaskScheduler );
pJobExternal = GetExternalInterface();
//
// It is possible that the job state changed after the callback was queued.
// Make the callback based on the current job state.
//
if (!m_NotifyPointer)
{
LogInfo( "Notification pointer for job %p is NULL, skipping callback", this );
return E_FAIL;
}
switch (m_state)
{
case BG_JOB_STATE_TRANSFERRED:
{
if (!IsCallbackEnabled( BG_NOTIFY_JOB_TRANSFERRED ))
{
LogInfo("error callback is not enabled");
return S_OK;
}
method = CM_COMPLETE;
break;
}
case BG_JOB_STATE_ERROR:
{
ASSERT( m_error.IsErrorSet() );
if (!IsCallbackEnabled( BG_NOTIFY_JOB_ERROR ))
{
LogInfo("error callback is not enabled");
return S_OK;
}
method = CM_ERROR;
pJobErrorExternal = new CJobErrorExternal( &m_error );
break;
}
default:
{
LogInfo("callback has become irrelevant, job state is %d", m_state );
return S_OK;
}
}
pICB = m_NotifyPointer;
pICB->AddRef();
}
//
// Free from the mutex, make the call.
//
switch (method)
{
case CM_COMPLETE:
LogInfo( "callback : job %p completion", this );
hr = pICB->JobTransferred( pJobExternal );
break;
case CM_ERROR:
LogInfo( "callback : job %p error", this );
hr = pICB->JobError( pJobExternal, pJobErrorExternal );
break;
default:
LogError( "job %p: invalid callback type 0x%x", this, method );
hr = S_OK;
break;
}
LogInfo("callback completed with 0x%x", hr);
//
// Clear the notification pointer if it is unusable.
//
if (FAILED(hr))
{
HoldWriterLock lock ( g_Manager->m_TaskScheduler );
if (m_NotifyPointer && IsInterfacePointerDead( m_NotifyPointer, hr ))
{
m_NotifyPointer->Release();
m_NotifyPointer = NULL;
}
throw ComError( hr );
}
hr = S_OK;
}
catch ( ComError exception )
{
LogWarning( "exception %x while dispatching callback", exception.Error() );
hr = exception.Error();
}
SafeRelease( pJobErrorExternal );
SafeRelease( pICB );
return hr;
}
HRESULT
CJob::CmdLineCallback()
{
ASSERT( GetOldExternalGroupInterface() == 0 );
HRESULT hr;
CUser * user = 0;
try
{
StringHandle CmdLine;
{
HoldReaderLock lock ( g_Manager->m_TaskScheduler );
switch (m_state)
{
case BG_JOB_STATE_TRANSFERRED:
{
if (!IsCallbackEnabled( BG_NOTIFY_JOB_TRANSFERRED ))
{
LogInfo("error callback is not enabled");
return S_OK;
}
break;
}
case BG_JOB_STATE_ERROR:
{
ASSERT( m_error.IsErrorSet() );
if (!IsCallbackEnabled( BG_NOTIFY_JOB_ERROR ))
{
LogInfo("error callback is not enabled");
return S_OK;
}
break;
}
default:
{
LogInfo("callback has become irrelevant, job state is %d", m_state );
return S_OK;
}
}
CmdLine = m_NotifyCmdLine;
}
//
// Free from the mutex, launch the application.
//
user = g_Manager->m_Users.FindUser( GetOwnerSid(), ANY_SESSION );
if (!user)
{
throw ComError( HRESULT_FROM_WIN32( ERROR_NOT_LOGGED_ON ));
}
THROW_HRESULT( user->LaunchProcess( CmdLine ) );
hr = S_OK;
}
catch ( ComError err )
{
LogWarning( "exception %x while launching callback process", err.Error() );
hr = err.Error();
}
if (user)
{
user->DecrementRefCount();
}
return hr;
}
HRESULT
CJob::OldInterfaceCallback()
{
HRESULT Hr = S_OK;
IBackgroundCopyCallback1 *pICB = NULL;
IBackgroundCopyGroup *pGroup = NULL;
IBackgroundCopyJob1 *pJob = NULL;
try
{
CallbackMethod method;
DWORD dwCurrentFile = 0;
DWORD dwRetries = 0;
DWORD dwWin32Result = 0;
DWORD dwTransportResult = 0;
{
CLockedJobReadPointer LockedJob(this);
pGroup = GetOldExternalGroupInterface();
ASSERT( pGroup );
pGroup->AddRef();
//
// It is possible that the job state changed after the callback was queued.
// Make the callback based on the current job state.
//
pICB = GetOldExternalGroupInterface()->GetNotificationPointer();
if (!pICB)
{
return S_FALSE;
}
switch (m_state)
{
case BG_JOB_STATE_TRANSFERRED:
{
if (!IsCallbackEnabled( BG_NOTIFY_JOB_TRANSFERRED ))
{
LogInfo("error callback is not enabled");
return S_OK;
}
method = CM_COMPLETE;
break;
}
case BG_JOB_STATE_ERROR:
{
ASSERT( m_error.IsErrorSet() );
if (!IsCallbackEnabled( BG_NOTIFY_JOB_ERROR ))
{
LogInfo("error callback is not enabled");
return S_OK;
}
method = CM_ERROR;
pJob = GetOldExternalJobInterface();
pJob->AddRef();
dwCurrentFile = m_error.GetFileIndex();
m_error.GetOldInterfaceErrors( &dwWin32Result, &dwTransportResult );
THROW_HRESULT( GetErrorCount(&dwRetries) );
break;
}
default:
{
LogInfo("callback has become irrelevant, job state is %d", m_state );
return S_OK;
}
}
}
// Outside of lock, do the callback
switch( method )
{
case CM_ERROR:
THROW_HRESULT( pICB->OnStatus(pGroup, pJob, dwCurrentFile,
QM_STATUS_GROUP_ERROR | QM_STATUS_GROUP_SUSPENDED,
dwRetries,
dwWin32Result,
dwTransportResult) );
break;
case CM_COMPLETE:
THROW_HRESULT( pICB->OnStatus(pGroup, NULL, -1, QM_STATUS_GROUP_COMPLETE, 0, 0, 0));
GetOldExternalGroupInterface()->SetNotificationPointer( __uuidof(IBackgroundCopyCallback1),
NULL );
break;
default:
ASSERT(0);
throw ComError( E_FAIL );
}
Hr = S_OK;
}
catch ( ComError exception )
{
LogWarning( "exception %x while dispatching callback", exception.Error() );
Hr = exception.Error();
}
SafeRelease( pICB );
SafeRelease( pGroup );
SafeRelease( pJob );
return Hr;
}
//
// Pause all activity on the job. The service will take no action until one of
// Resume(), Cancel(), Complete() is called.
//
// if already suspended, just returns S_OK.
//
HRESULT
CJob::Suspend()
{
return g_Manager->SuspendJob( this );
}
//
// Enable downloading for this job.
//
// if already running, just returns S_OK.
//
HRESULT
CJob::Resume()
{
if (IsEmpty())
{
return BG_E_EMPTY;
}
switch (m_state)
{
case BG_JOB_STATE_SUSPENDED:
{
CFile * file = GetCurrentFile();
if (!file)
{
// job was already transferred when it was suspended
JobTransferred();
return S_OK;
}
}
// fall through here
case BG_JOB_STATE_TRANSIENT_ERROR:
case BG_JOB_STATE_ERROR:
MoveToInactiveState();
if (IsRunnable())
{
g_Manager->AppendOnline( this );
}
g_Manager->ScheduleAnotherGroup();
UpdateModificationTime();
return S_OK;
case BG_JOB_STATE_CONNECTING:
case BG_JOB_STATE_TRANSFERRING:
case BG_JOB_STATE_QUEUED:
case BG_JOB_STATE_TRANSFERRED: // no-op
{
return S_OK;
}
case BG_JOB_STATE_CANCELLED:
case BG_JOB_STATE_ACKNOWLEDGED:
{
return BG_E_INVALID_STATE;
}
default:
{
ASSERT( 0 );
return S_OK;
}
}
ASSERT( 0 );
return S_OK;
}
//
// Permanently stop the job. The service will delete the job metadata and downloaded files.
//
HRESULT
CJob::Cancel()
{
HRESULT Hr = S_OK;
switch (m_state)
{
case BG_JOB_STATE_CONNECTING:
case BG_JOB_STATE_TRANSFERRING:
{
g_Manager->InterruptDownload();
// OK to fall through here
}
case BG_JOB_STATE_SUSPENDED:
case BG_JOB_STATE_ERROR:
case BG_JOB_STATE_QUEUED:
case BG_JOB_STATE_TRANSIENT_ERROR:
case BG_JOB_STATE_TRANSFERRED:
{
// abandon temporary files
RETURN_HRESULT( Hr = RemoveTemporaryFiles() );
SetState( BG_JOB_STATE_CANCELLED );
RemoveFromManager();
return Hr;
}
case BG_JOB_STATE_CANCELLED:
case BG_JOB_STATE_ACKNOWLEDGED:
{
return BG_E_INVALID_STATE;
}
default:
{
ASSERT( 0 );
return Hr;
}
}
ASSERT( 0 );
return Hr;
}
//
// Acknowledges receipt of the job-complete notification. The service will delete
// the job metadata and leave the downloaded files.
//
HRESULT
CJob::Complete( )
{
HRESULT hr;
switch (m_state)
{
case BG_JOB_STATE_CONNECTING:
case BG_JOB_STATE_TRANSFERRING:
case BG_JOB_STATE_QUEUED:
case BG_JOB_STATE_TRANSIENT_ERROR:
Suspend();
// OK to fall through here
case BG_JOB_STATE_SUSPENDED:
case BG_JOB_STATE_ERROR:
case BG_JOB_STATE_TRANSFERRED:
hr = S_OK;
// move downloaded files to final destination(skip for Mars)
if ( !GetOldExternalJobInterface() )
{
RETURN_HRESULT( hr = CommitTemporaryFiles() );
}
// hr may be S_OK, or BG_S_PARTIAL_COMPLETE.
SetState( BG_JOB_STATE_ACKNOWLEDGED );
RemoveFromManager();
return hr;
case BG_JOB_STATE_CANCELLED:
case BG_JOB_STATE_ACKNOWLEDGED:
{
return BG_E_INVALID_STATE;
}
default:
{
ASSERT( 0 );
return BG_E_INVALID_STATE;
}
}
ASSERT(0);
return BG_E_INVALID_STATE;
}
HRESULT
CJob::CommitTemporaryFiles()
{
HRESULT Hr = S_OK;
try
{
bool fPartial = false;
CNestedImpersonation imp( GetOwnerSid() );
CFileList::iterator iter;
LogInfo("commit job %p", this );
//
// First loop, rename completed temp files.
//
SIZE_T FilesMoved = 0;
for (iter = m_files.begin(); iter != m_files.end(); ++iter, FilesMoved++)
{
if (false == (*iter)->IsCompleted())
{
if ((*iter)->ReceivedAllData())
{
//
// Retain the first error encountered.
//
HRESULT LastResult = (*iter)->MoveTempFile();
if (FAILED(LastResult))
{
LogError( "commit: failed 0x%x", LastResult );
if (Hr == S_OK)
{
Hr = LastResult;
}
}
}
else
{
fPartial = true;
LogInfo("commit: skipping partial file '%S'", (const WCHAR*)(*iter)->GetLocalName());
}
}
else
{
LogInfo("commit: skipping previously completed file '%S'", (const WCHAR*)(*iter)->GetLocalName());
}
}
if (SUCCEEDED(Hr))
{
bool fErrorOnDelete = false;
//
// Second loop, delete incomplete temp files
//
for( iter = m_files.begin(); iter != m_files.end(); ++iter )
{
if (false == (*iter)->IsCompleted())
{
HRESULT HrDelete = (*iter)->DeleteTempFile();
if (FAILED(HrDelete))
fErrorOnDelete = true;
}
}
if ( fErrorOnDelete )
Hr = BG_S_UNABLE_TO_DELETE_FILES;
//
// Return S_OK if all files are returned, otherwise BG_S_PARTIAL_COMPLETE.
//
if (fPartial)
{
Hr = BG_S_PARTIAL_COMPLETE;
}
}
}
catch ( ComError exception )
{
Hr = exception.Error();
LogError( "commit: exception 0x%x", Hr );
}
//
// If commitment failed, the job will not be deleted.
// Update its modification time, and schedule the modification callback.
//
if (FAILED(Hr))
{
UpdateModificationTime();
}
return Hr;
}
HRESULT
CJob::RemoveTemporaryFilesPart2()
{
bool bErrorOnDelete = false;
CFileList::iterator iter;
for (iter = m_files.begin(); iter != m_files.end(); ++iter)
{
HRESULT Hr = (*iter)->DeleteTempFile();
if ( FAILED( Hr ) )
bErrorOnDelete = true;
}
return bErrorOnDelete ? BG_S_UNABLE_TO_DELETE_FILES : S_OK;
}
HRESULT
CJob::RemoveTemporaryFiles()
{
// Since the temporary files may be on a network drive
// we need to impersonate the user before deleteing the file.
// Unfortunatly, this itsn't always possible since the user
// may also be be logged on and we still want to allow
// adminstrators to cancel the job.
try
{
CNestedImpersonation imp( GetOwnerSid() );
return RemoveTemporaryFilesPart2();
}
catch( ComError Error )
{
return RemoveTemporaryFilesPart2();
}
}
void
CJob::SetCompletionTime( const FILETIME *pftCompletionTime )
{
FILETIME ftCurrentTime;
if ( !pftCompletionTime )
{
GetSystemTimeAsFileTime( &ftCurrentTime );
pftCompletionTime = &ftCurrentTime;
}
m_TransferCompletionTime = *pftCompletionTime;
SetModificationTime( pftCompletionTime );
}
void
CJob::SetModificationTime( const FILETIME *pftModificationTime )
{
FILETIME ftCurrentTime;
if ( !pftModificationTime )
{
GetSystemTimeAsFileTime( &ftCurrentTime );
pftModificationTime = &ftCurrentTime;
}
m_ModificationTime = *pftModificationTime;
}
void
CJob::SetLastAccessTime( const FILETIME *pftModificationTime )
{
FILETIME ftCurrentTime;
if ( !pftModificationTime )
{
GetSystemTimeAsFileTime( &ftCurrentTime );
pftModificationTime = &ftCurrentTime;
}
m_LastAccessTime = *pftModificationTime;
}
void
CJob::OnDiskChange(
const WCHAR *CanonicalVolume,
DWORD VolumeSerialNumber )
{
switch(m_state)
{
case BG_JOB_STATE_QUEUED:
case BG_JOB_STATE_CONNECTING:
case BG_JOB_STATE_TRANSFERRING:
break;
case BG_JOB_STATE_SUSPENDED:
case BG_JOB_STATE_ERROR:
return;
case BG_JOB_STATE_TRANSIENT_ERROR:
break;
case BG_JOB_STATE_TRANSFERRED:
case BG_JOB_STATE_ACKNOWLEDGED:
case BG_JOB_STATE_CANCELLED:
return;
default:
ASSERTMSG("Invalid job state", 0);
}
for (CFileList::iterator iter = m_files.begin(); iter != m_files.end(); ++iter)
{
if (!(*iter)->OnDiskChange( CanonicalVolume, VolumeSerialNumber ))
{
// If one file fails, the whole job fails.
return;
}
}
}
void
CJob::OnDismount(
const WCHAR *CanonicalVolume )
{
switch(m_state)
{
case BG_JOB_STATE_QUEUED:
case BG_JOB_STATE_CONNECTING:
case BG_JOB_STATE_TRANSFERRING:
break;
case BG_JOB_STATE_SUSPENDED:
case BG_JOB_STATE_ERROR:
return;
case BG_JOB_STATE_TRANSIENT_ERROR:
break;
case BG_JOB_STATE_TRANSFERRED:
case BG_JOB_STATE_ACKNOWLEDGED:
case BG_JOB_STATE_CANCELLED:
return;
default:
ASSERTMSG("Invalid job state", 0);
}
for (CFileList::iterator iter = m_files.begin(); iter != m_files.end(); ++iter)
{
if (!(*iter)->OnDismount( CanonicalVolume ))
{
// If one file fails, the whole job fails.
return;
}
}
}
bool
CJob::OnDeviceLock(
const WCHAR * CanonicalVolume
)
{
if ( IsRunnable() )
{
if ( IsTransferringToDrive( CanonicalVolume ) )
{
if (IsRunning() )
{
g_Manager->InterruptDownload();
}
QMErrInfo ErrorInfo;
ErrorInfo.Set( SOURCE_QMGR_FILE, ERROR_STYLE_HRESULT , BG_E_DESTINATION_LOCKED, "Destination is locked");
FileTransientError( &ErrorInfo );
return true;
}
}
return false;
}
bool
CJob::OnDeviceUnlock(
const WCHAR * CanonicalVolume
)
{
if ( BG_JOB_STATE_TRANSIENT_ERROR == m_state )
{
const CJobError *Error = GetError();
ASSERT( Error );
if ( ( Error->GetCode() == BG_E_DESTINATION_LOCKED ) &&
( Error->GetStyle() == ERROR_STYLE_HRESULT ) )
{
if ( IsTransferringToDrive( CanonicalVolume ) )
{
RetryNow();
return true;
}
}
}
return false;
}
HRESULT
CJob::AssignOwnership(
SidHandle sid
)
{
// If we are being called by the current
// owner, then we have nothing to do.
if ( sid == m_NotifySid )
return S_OK;
if ( IsRunning() )
{
g_Manager->InterruptDownload();
}
// revalidate access to all the local files
HRESULT Hr = S_OK;
for (CFileList::iterator iter = m_files.begin(); iter != m_files.end(); ++iter)
{
Hr = (*iter)->ValidateAccessForUser( sid,
(m_type == BG_JOB_TYPE_DOWNLOAD) ? true : false );
if (FAILED(Hr))
{
g_Manager->ScheduleAnotherGroup();
return Hr;
}
}
// actually reassign ownership
CJobSecurityDescriptor *newsd = NULL;
try
{
g_Manager->ExtendMetadata();
newsd = new CJobSecurityDescriptor( sid );
// replace the old notify sid and SECURITY_DESCRIPTOR
delete m_sd;
m_sd = newsd;
m_NotifySid = sid;
m_Credentials.Clear();
//
// Move the job to the online list if necessary.
//
g_Manager->ResetOnlineStatus( this, sid );
//
// Serialize and notify the client app of changes.
//
UpdateModificationTime();
g_Manager->ScheduleAnotherGroup();
return Hr;
}
catch( ComError Error )
{
Hr = Error.Error();
delete newsd;
g_Manager->ScheduleAnotherGroup();
g_Manager->ShrinkMetadata();
return Hr;
}
}
void
CJob::MoveToInactiveState()
{
#if !defined( BITS_V12_ON_NT4 )
if (g_Manager->m_NetworkMonitor.GetAddressCount() > 0)
{
#endif
SetState( BG_JOB_STATE_QUEUED );
#if !defined( BITS_V12_ON_NT4 )
}
else
{
QMErrInfo err;
err.Set( SOURCE_HTTP_CLIENT_CONN, ERROR_STYLE_HRESULT, BG_E_NETWORK_DISCONNECTED, NULL );
err.result = QM_FILE_TRANSIENT_ERROR;
if (_GetState() == BG_JOB_STATE_TRANSFERRING)
{
++m_retries;
}
SetState( BG_JOB_STATE_TRANSIENT_ERROR );
RecordError( &err );
if ( m_NoProgressTimeout != INFINITE &&
!g_Manager->m_TaskScheduler.IsWorkItemInScheduler((CJobNoProgressItem *) this))
{
g_Manager->ScheduleDelayedTask( (CJobNoProgressItem *) this, m_NoProgressTimeout );
}
}
#endif
}
CUnknownFileSizeList*
CJob::GetUnknownFileSizeList()
{
auto_ptr<CUnknownFileSizeList> pList( new CUnknownFileSizeList );
if (m_type == BG_JOB_TYPE_DOWNLOAD)
{
for(CFileList::iterator iter = m_files.begin(); iter != m_files.end(); iter++ )
{
if ( (*iter)->_GetBytesTotal() == -1 )
{
if (!pList->Add( (*iter), (*iter)->GetRemoteName() ) )
{
throw ComError( E_OUTOFMEMORY );
}
}
}
}
return pList.release();
}
void
CJob::UpdateModificationTime(
bool fNotify
)
{
FILETIME ftCurrentTime;
GetSystemTimeAsFileTime( &ftCurrentTime );
SetModificationTime( &ftCurrentTime );
UpdateLastAccessTime( );
if (fNotify)
{
if (g_Manager->m_TaskScheduler.IsWorkItemInScheduler( (CJobInactivityTimeout *) this))
{
g_Manager->m_TaskScheduler.CancelWorkItem( (CJobInactivityTimeout *) this );
g_Manager->m_TaskScheduler.InsertDelayedWorkItem( (CJobInactivityTimeout *) this, g_GlobalInfo->m_JobInactivityTimeout );
}
ScheduleModificationCallback();
g_Manager->Serialize();
}
}
void
CJob::UpdateLastAccessTime(
)
{
FILETIME ftCurrentTime;
GetSystemTimeAsFileTime( &ftCurrentTime );
SetLastAccessTime( &ftCurrentTime );
}
void CJob::CancelWorkitems()
{
ASSERT( g_Manager );
//
// While the job-modification item is pending, it keeps a separate ref to the job.
// The other work items share a single ref.
//
// g_Manager->m_TaskScheduler.CancelWorkItem( static_cast<CJobModificationItem *> (this) );
g_Manager->m_TaskScheduler.CancelWorkItem( static_cast<CJobInactivityTimeout *> (this) );
g_Manager->m_TaskScheduler.CancelWorkItem( static_cast<CJobNoProgressItem *> (this) );
g_Manager->m_TaskScheduler.CancelWorkItem( static_cast<CJobCallbackItem *> (this) );
g_Manager->m_TaskScheduler.CancelWorkItem( static_cast<CJobRetryItem *> (this) );
}
void
CJob::RemoveFromManager()
{
//
// The job is dead, except perhaps for a few references held by app threads.
// Ensure that no more action is taken on this job.
//
CancelWorkitems();
//
// If the job is not already removed from the job list, remove it
// and remove the refcount for the job's membership in the list.
//
if (g_Manager->RemoveJob( this ))
{
g_Manager->ScheduleAnotherGroup();
g_Manager->Serialize();
NotifyInternalDelete();
}
}
HRESULT
CJob::SetLimitedString(
StringHandle & destination,
const LPCWSTR Val,
SIZE_T limit
)
{
try
{
StringHandle name = Val;
name.Truncate( limit );
UpdateString( destination, name );
return S_OK;
}
catch ( ComError err )
{
return err.Error();
}
}
HRESULT
CJob::UpdateString(
StringHandle & destination,
const StringHandle & Val
)
{
try
{
if ( destination.Size() < Val.Size() )
g_Manager->ExtendMetadata( sizeof(wchar_t) * (Val.Size() - destination.Size()) );
destination = Val;
UpdateModificationTime();
return S_OK;
}
catch ( ComError err )
{
g_Manager->ShrinkMetadata();
return err.Error();
}
}
//------------------------------------------------------------------------
// Change the GUID when an incompatible Serialize change is made.
GUID JobGuid_v1_5 = { /* 85e5c459-ef86-4fcd-8ea0-5b4f00d27e35 */
0x85e5c459,
0xef86,
0x4fcd,
{0x8e, 0xa0, 0x5b, 0x4f, 0x00, 0xd2, 0x7e, 0x35}
};
GUID JobGuid_v1_0 = { /* 5770fca4-cf9f-4513-8737-972b4ea1265d */
0x5770fca4,
0xcf9f,
0x4513,
{0x87, 0x37, 0x97, 0x2b, 0x4e, 0xa1, 0x26, 0x5d}
};
GUID UploadJobGuid_v1_5 = { /* ebc54f55-23b0-4b1a-aa3f-936c0b0fd5b3 */
0xebc54f55,
0x23b0,
0x4b1a,
{0xaa, 0x3f, 0x93, 0x6c, 0x0b, 0x0f, 0xd5, 0xb3}
};
/* static */
CJob *
CJob::UnserializeJob(
HANDLE hFile
)
{
#define JOB_DOWNLOAD_V1_5 0
#define JOB_UPLOAD_V1_5 1
#define JOB_DOWNLOAD_V1 2
const GUID * JobGuids[] = { &JobGuid_v1_5, &UploadJobGuid_v1_5, &JobGuid_v1_0, NULL };
CJob * job = NULL;
try
{
int Type = SafeReadGuidChoice( hFile, JobGuids );
switch (Type)
{
case JOB_DOWNLOAD_V1: job = new CJob; break;
case JOB_DOWNLOAD_V1_5: job = new CJob; break;
case JOB_UPLOAD_V1_5: job = new CUploadJob; break;
default: THROW_HRESULT( E_FAIL );
}
// rewind to the front of the GUID
//
SetFilePointer( hFile, -1 * LONG(sizeof(GUID)), NULL, FILE_CURRENT );
job->Unserialize( hFile, Type );
}
catch( ComError err )
{
if (job)
{
job->UnlinkFromExternalInterfaces();
delete job;
}
throw;
}
return job;
}
HRESULT
CJob::Serialize(
HANDLE hFile
)
{
//
// If this function changes, be sure that the metadata extension
// constants are adequate.
//
SafeWriteBlockBegin( hFile, JobGuid_v1_5 );
long Was_m_refs = 0;
SafeWriteFile( hFile, Was_m_refs );
SafeWriteFile( hFile, m_priority );
SafeWriteFile( hFile, IsRunning() ? BG_JOB_STATE_QUEUED : m_state );
SafeWriteFile( hFile, m_type );
SafeWriteFile( hFile, m_id );
SafeWriteStringHandle( hFile, m_name );
SafeWriteStringHandle( hFile, m_description );
SafeWriteStringHandle( hFile, m_NotifyCmdLine );
SafeWriteSid( hFile, m_NotifySid );
SafeWriteFile( hFile, m_NotifyFlags );
SafeWriteFile( hFile, m_fGroupNotifySid );
SafeWriteFile( hFile, m_CurrentFile );
m_sd->Serialize( hFile );
m_files.Serialize( hFile );
m_error.Serialize( hFile );
SafeWriteFile( hFile, m_retries );
SafeWriteFile( hFile, m_MinimumRetryDelay );
SafeWriteFile( hFile, m_NoProgressTimeout );
SafeWriteFile( hFile, m_CreationTime );
SafeWriteFile( hFile, m_LastAccessTime );
SafeWriteFile( hFile, m_ModificationTime );
SafeWriteFile( hFile, m_TransferCompletionTime );
if ( GetOldExternalGroupInterface() )
{
SafeWriteFile( hFile, (bool)true );
GetOldExternalGroupInterface()->Serialize( hFile );
}
else
{
SafeWriteFile( hFile, (bool)false );
}
SafeWriteFile( hFile, m_method );
((CJobInactivityTimeout *) this)->Serialize( hFile );
((CJobNoProgressItem *) this)->Serialize( hFile );
((CJobCallbackItem *) this)->Serialize( hFile );
((CJobRetryItem *) this)->Serialize( hFile );
SafeWriteFile( hFile, m_ProxySettings.ProxyUsage );
SafeWriteFile( hFile, m_ProxySettings.ProxyList );
SafeWriteFile( hFile, m_ProxySettings.ProxyBypassList );
m_Credentials.Serialize( hFile );
SafeWriteBlockEnd( hFile, JobGuid_v1_5 );
GetSystemTimeAsFileTime( &m_SerializeTime );
return S_OK;
}
void
CJob::Unserialize(
HANDLE hFile,
int Type
)
{
try
{
LogInfo("job : unserializing %p", this);
SafeReadBlockBegin( hFile, (Type != JOB_DOWNLOAD_V1) ? JobGuid_v1_5 : JobGuid_v1_0 );
long Was_m_refs = 0;
SafeReadFile( hFile, &Was_m_refs );
SafeReadFile( hFile, &m_priority );
SafeReadFile( hFile, &m_state );
SafeReadFile( hFile, &m_type );
SafeReadFile( hFile, &m_id );
m_name = SafeReadStringHandle( hFile );
m_description = SafeReadStringHandle( hFile );
if (Type != JOB_DOWNLOAD_V1)
{
m_NotifyCmdLine = SafeReadStringHandle( hFile );
}
SafeReadSid( hFile, m_NotifySid );
SafeReadFile( hFile, &m_NotifyFlags );
SafeReadFile( hFile, &m_fGroupNotifySid );
SafeReadFile( hFile, &m_CurrentFile );
m_sd = CJobSecurityDescriptor::Unserialize( hFile );
m_files.Unserialize( hFile, this );
m_error.Unserialize( hFile, this );
SafeReadFile( hFile, &m_retries );
SafeReadFile( hFile, &m_MinimumRetryDelay );
SafeReadFile( hFile, &m_NoProgressTimeout );
SafeReadFile( hFile, &m_CreationTime );
SafeReadFile( hFile, &m_LastAccessTime );
SafeReadFile( hFile, &m_ModificationTime );
SafeReadFile( hFile, &m_TransferCompletionTime );
bool bHasOldExternalGroupInterface = false;
SafeReadFile( hFile, &bHasOldExternalGroupInterface );
if (bHasOldExternalGroupInterface)
{
COldGroupInterface *OldGroup = COldGroupInterface::UnSerialize( hFile, this );
SetOldExternalGroupInterface( OldGroup );
}
SafeReadFile( hFile, &m_method );
((CJobInactivityTimeout *) this)->Unserialize( hFile );
((CJobNoProgressItem *) this)->Unserialize( hFile );
((CJobCallbackItem *) this)->Unserialize( hFile );
((CJobRetryItem *) this)->Unserialize( hFile );
SafeReadFile( hFile, &m_ProxySettings.ProxyUsage );
SafeReadFile( hFile, &m_ProxySettings.ProxyList );
SafeReadFile( hFile, &m_ProxySettings.ProxyBypassList );
if (Type != JOB_DOWNLOAD_V1)
{
m_Credentials.Unserialize( hFile );
}
SafeReadBlockEnd( hFile, (Type != JOB_DOWNLOAD_V1) ? JobGuid_v1_5 : JobGuid_v1_0 );
}
catch( ComError Error )
{
LogError("invalid job data");
throw;
}
}
CUploadJob::CUploadJob(
LPCWSTR DisplayName,
BG_JOB_TYPE Type,
REFGUID JobId,
SidHandle NotifySid
)
: CJob( DisplayName, Type, JobId, NotifySid ),
m_ReplyFile( 0 )
{
}
CUploadJob::~CUploadJob()
{
delete m_ReplyFile;
}
HRESULT
CUploadJob::Serialize(
HANDLE hFile
)
{
LogInfo("serializing upload job %p", this);
SafeWriteBlockBegin( hFile, UploadJobGuid_v1_5 );
CJob::Serialize( hFile );
// additional data not in a download job
//
m_UploadData.Serialize( hFile );
SafeWriteFile( hFile, m_fOwnReplyFileName );
SafeWriteStringHandle( hFile, m_ReplyFileName );
if (m_ReplyFile)
{
SafeWriteFile( hFile, true );
m_ReplyFile->Serialize( hFile );
}
else
{
SafeWriteFile( hFile, false );
}
SafeWriteBlockEnd( hFile, UploadJobGuid_v1_5 );
return S_OK;
}
void
CUploadJob::Unserialize(
HANDLE hFile,
int Type
)
{
ASSERT( Type == JOB_UPLOAD_V1_5 );
LogInfo("unserializing upload job %p", this);
SafeReadBlockBegin( hFile, UploadJobGuid_v1_5 );
CJob::Unserialize( hFile, Type );
// additional data not in a download job
//
m_UploadData.Unserialize( hFile );
SafeReadFile( hFile, &m_fOwnReplyFileName );
m_ReplyFileName = SafeReadStringHandle( hFile );
bool fReplyFile;
SafeReadFile( hFile, &fReplyFile );
if (fReplyFile)
{
m_ReplyFile = CFile::Unserialize( hFile, this );
}
SafeReadBlockEnd( hFile, UploadJobGuid_v1_5 );
if (m_state == BG_JOB_STATE_CANCELLED ||
m_state == BG_JOB_STATE_ACKNOWLEDGED)
{
if (g_Manager->m_TaskScheduler.IsWorkItemInScheduler(static_cast<CJobRetryItem *>(this)))
{
m_UploadData.fSchedulable = false;
}
}
}
UPLOAD_DATA::UPLOAD_DATA()
{
State = UPLOAD_STATE_CREATE_SESSION;
fSchedulable = true;
memset( &SessionId, 0, sizeof( GUID ));
memset( &Protocol, 0, sizeof( GUID ));
HostId = NULL;
HostIdFallbackTimeout = 0xFFFFFFFF;
memset( &HostIdNoProgressStartTime, 0, sizeof(HostIdNoProgressStartTime) );
}
UPLOAD_DATA::~UPLOAD_DATA()
{
}
void
UPLOAD_DATA::SetUploadState(
UPLOAD_STATE NewState
)
{
if (State != NewState)
{
LogInfo( "upload state: %d -> %d", State, NewState );
State = NewState;
}
}
void
UPLOAD_DATA::Serialize(
HANDLE hFile
)
{
SafeWriteFile( hFile, State );
SafeWriteFile( hFile, SessionId );
SafeWriteFile( hFile, Protocol );
SafeWriteStringHandle( hFile, ReplyUrl );
SafeWriteStringHandle( hFile, HostId );
SafeWriteFile( hFile, HostIdFallbackTimeout );
SafeWriteFile( hFile, HostIdNoProgressStartTime );
}
void
UPLOAD_DATA::Unserialize(
HANDLE hFile
)
{
SafeReadFile( hFile, &State );
SafeReadFile( hFile, &SessionId );
SafeReadFile( hFile, &Protocol );
ReplyUrl = SafeReadStringHandle( hFile );
HostId = SafeReadStringHandle( hFile );
SafeReadFile( hFile, &HostIdFallbackTimeout );
SafeReadFile( hFile, &HostIdNoProgressStartTime );
fSchedulable = true;
}
void CUploadJob::Transfer()
{
}
HRESULT
CUploadJob::Complete()
{
HRESULT hr;
switch (m_state)
{
case BG_JOB_STATE_TRANSFERRED:
hr = S_OK;
RETURN_HRESULT( hr = CommitReplyFile() );
// hr may be S_OK, or BG_S_PARTIAL_COMPLETE.
SetState( BG_JOB_STATE_ACKNOWLEDGED );
RemoveFromManager();
return hr;
default:
{
return BG_E_INVALID_STATE;
}
}
ASSERT(0);
return BG_E_INVALID_STATE;
}
HRESULT
CUploadJob::Cancel()
{
HRESULT Hr = S_OK;
switch (m_state)
{
case BG_JOB_STATE_CONNECTING:
case BG_JOB_STATE_TRANSFERRING:
{
g_Manager->InterruptDownload();
// OK to fall through here
}
case BG_JOB_STATE_SUSPENDED:
case BG_JOB_STATE_ERROR:
case BG_JOB_STATE_QUEUED:
case BG_JOB_STATE_TRANSIENT_ERROR:
case BG_JOB_STATE_TRANSFERRED:
{
RETURN_HRESULT( Hr = RemoveReplyFile() );
// Hr may be BG_S_UNABLE_TO_REMOVE_FILES
SetState( BG_JOB_STATE_CANCELLED );
//
// If the close-session exchange has not happened yet,
// begin a cancel-session exchange.
//
if (SessionInProgress())
{
LogInfo("job %p: upload session in state %d, cancelling", this, m_UploadData.State );
g_Manager->m_TaskScheduler.CancelWorkItem( (CJobCallbackItem *) this );
SetNoProgressTimeout( UPLOAD_CANCEL_TIMEOUT );
m_UploadData.SetUploadState( UPLOAD_STATE_CANCEL_SESSION );
g_Manager->ScheduleAnotherGroup();
g_Manager->Serialize();
}
else
{
RemoveFromManager();
}
return Hr;
}
case BG_JOB_STATE_CANCELLED:
case BG_JOB_STATE_ACKNOWLEDGED:
{
return BG_E_INVALID_STATE;
}
default:
{
ASSERT( 0 );
return Hr;
}
}
ASSERT( 0 );
return Hr;
}
void
CUploadJob::FileComplete()
{
//
// The downloader successfully completed one of three things:
//
// 1. job type is UPLOAD. The file was uploaded and the session closed.
// 2. job type is UPLOAD_REPLY. The file was uploaded, reply downloaded, and session closed.
// 3. either job type; an early Cancel required the job to cancel the session.
//
switch (m_state)
{
case BG_JOB_STATE_CANCELLED:
case BG_JOB_STATE_ACKNOWLEDGED:
{
ASSERT (m_UploadData.State == UPLOAD_STATE_CLOSED || m_UploadData.State == UPLOAD_STATE_CANCELLED);
RemoveFromManager();
break;
}
default:
{
++m_CurrentFile;
JobTransferred();
g_Manager->Serialize();
}
}
}
void
CUploadJob::UpdateProgress(
UINT64 BytesTransferred,
UINT64 BytesTotal
)
{
memset( &GetUploadData().HostIdNoProgressStartTime, 0,
sizeof( GetUploadData().HostIdNoProgressStartTime ) );
CJob::UpdateProgress( BytesTransferred, BytesTotal );
}
bool
CUploadJob::CheckHostIdFallbackTimeout()
{
if ( GetUploadData().HostIdFallbackTimeout != 0xFFFFFFFF )
{
UINT64 HostIdNoProgressStartTime = FILETIMEToUINT64( GetUploadData().HostIdNoProgressStartTime );
if ( HostIdNoProgressStartTime )
{
UINT64 TimeoutTime = HostIdNoProgressStartTime +
GetUploadData().HostIdFallbackTimeout * NanoSec100PerSec;
if ( TimeoutTime < HostIdNoProgressStartTime )
return true; //wraparound
FILETIME CurrentTimeAsFileTime;
GetSystemTimeAsFileTime( &CurrentTimeAsFileTime );
UINT64 CurrentTime = FILETIMEToUINT64( CurrentTimeAsFileTime );
if ( CurrentTime > TimeoutTime )
return true;
}
}
return false;
}
void
CUploadJob::FileFatalError(
QMErrInfo * ErrInfo
)
{
switch (m_state)
{
case BG_JOB_STATE_CANCELLED:
case BG_JOB_STATE_ACKNOWLEDGED:
{
ASSERT (m_UploadData.State == UPLOAD_STATE_CLOSE_SESSION || m_UploadData.State == UPLOAD_STATE_CANCEL_SESSION);
RemoveFromManager();
break;
}
default:
{
if ( CheckHostIdFallbackTimeout() )
{
LogError( "Reverting back to main URL since the timeout has expired" );
FileTransientError( ErrInfo );
return;
}
CJob::FileFatalError( ErrInfo );
}
}
}
void
CUploadJob::FileTransientError(
QMErrInfo * ErrInfo
)
{
bool ShouldRevertToOriginalURL = CheckHostIdFallbackTimeout();
if ( ShouldRevertToOriginalURL )
{
LogError( "Reverting back to main URL since the timeout has expired" );
GetUploadData().HostId = StringHandle();
GetUploadData().HostIdFallbackTimeout = 0xFFFFFFFF;
}
switch (m_state)
{
case BG_JOB_STATE_CANCELLED:
case BG_JOB_STATE_ACKNOWLEDGED:
{
LogWarning( "job %p transient failure in state %d", this, m_state );
#if !defined( BITS_V12_ON_NT4 )
if (g_Manager->m_NetworkMonitor.GetAddressCount() > 0)
{
#endif
g_Manager->ScheduleDelayedTask( (CJobRetryItem *) this, m_MinimumRetryDelay );
#if !defined( BITS_V12_ON_NT4 )
}
#endif
m_UploadData.fSchedulable = false;
break;
}
default:
{
CJob::FileTransientError( ErrInfo );
}
}
if ( ShouldRevertToOriginalURL )
{
if ( g_Manager->m_TaskScheduler.IsWorkItemInScheduler((CJobNoProgressItem *) this))
{
g_Manager->m_TaskScheduler.RescheduleDelayedTask( (CJobNoProgressItem *) this, 0 );
}
}
else if ( GetUploadData().HostIdFallbackTimeout != 0xFFFFFFFF &&
!FILETIMEToUINT64( GetUploadData().HostIdNoProgressStartTime ) )
{
GetSystemTimeAsFileTime( &GetUploadData().HostIdNoProgressStartTime );
}
}
void CUploadJob::OnRetryJob()
{
if (g_Manager->m_TaskScheduler.LockWriter() )
{
g_Manager->m_TaskScheduler.AcknowledgeWorkItemCancel();
return;
}
if (m_state == BG_JOB_STATE_TRANSIENT_ERROR)
{
SetState( BG_JOB_STATE_QUEUED );
UpdateModificationTime();
}
else if (m_state == BG_JOB_STATE_CANCELLED ||
m_state == BG_JOB_STATE_ACKNOWLEDGED)
{
m_UploadData.fSchedulable = true;
g_Manager->AppendOnline( this );
}
g_Manager->m_TaskScheduler.CompleteWorkItem();
g_Manager->ScheduleAnotherGroup();
g_Manager->m_TaskScheduler.UnlockWriter();
}
void CUploadJob::OnInactivityTimeout()
{
if (g_Manager->m_TaskScheduler.LockWriter() )
{
g_Manager->m_TaskScheduler.AcknowledgeWorkItemCancel();
return;
}
g_Manager->m_TaskScheduler.CompleteWorkItem();
RemoveFromManager();
g_Manager->m_TaskScheduler.UnlockWriter();
}
void CUploadJob::OnNetworkDisconnect()
{
switch (m_state)
{
case BG_JOB_STATE_QUEUED:
case BG_JOB_STATE_TRANSIENT_ERROR:
{
QMErrInfo err;
err.Set( SOURCE_HTTP_CLIENT_CONN, ERROR_STYLE_HRESULT, BG_E_NETWORK_DISCONNECTED, NULL );
err.result = QM_FILE_TRANSIENT_ERROR;
FileTransientError( &err );
break;
}
case BG_JOB_STATE_CANCELLED:
case BG_JOB_STATE_ACKNOWLEDGED:
{
m_UploadData.fSchedulable = false;
break;
}
}
}
void CUploadJob::OnNetworkConnect()
{
if (m_state == BG_JOB_STATE_TRANSIENT_ERROR)
{
SetState( BG_JOB_STATE_QUEUED );
ScheduleModificationCallback();
}
else if (m_state == BG_JOB_STATE_ACKNOWLEDGED ||
m_state == BG_JOB_STATE_CANCELLED)
{
m_UploadData.fSchedulable = true;
}
}
bool CUploadJob::IsRunnable()
{
switch (m_state)
{
case BG_JOB_STATE_SUSPENDED:
case BG_JOB_STATE_ERROR:
case BG_JOB_STATE_TRANSIENT_ERROR:
return false;
default:
if (m_UploadData.fSchedulable &&
m_UploadData.State != UPLOAD_STATE_CLOSED &&
m_UploadData.State != UPLOAD_STATE_CANCELLED )
{
return true;
}
return false;
}
}
HRESULT
CUploadJob::RemoveReplyFile()
{
//
// Since the temporary files may be on a network drive
// we need to impersonate the user before deleting the file.
// Unfortunately, this isn't always possible since the user
// may also be be logged on and we still want to allow
// administrators to cancel the job.
//
CSaveThreadToken save;
auto_HANDLE<NULL> token;
if (S_OK == g_Manager->CloneUserToken( GetOwnerSid(), ANY_SESSION, token.GetWritePointer() ))
{
ImpersonateLoggedOnUser( token.get() );
}
//
// Delete the reply file, if it was created by BITS.
// Delete the temporary reply file
//
HRESULT Hr;
HRESULT FinalHr = S_OK;
if (FAILED( DeleteGeneratedReplyFile() ))
{
FinalHr = BG_S_UNABLE_TO_DELETE_FILES;
}
m_fOwnReplyFileName = false;
if (m_ReplyFile)
{
Hr = m_ReplyFile->DeleteTempFile();
if (FAILED(Hr))
{
FinalHr = BG_S_UNABLE_TO_DELETE_FILES;
}
}
return FinalHr;
}
HRESULT
CUploadJob::CommitReplyFile()
{
// Since the temporary files may be on a network drive
// we need to impersonate the user before deleting the file.
// Unfortunately, this isn't always possible since the user
// may also be be logged on and we still want to allow
// administrators to complete the job.
CSaveThreadToken save;
auto_HANDLE<NULL> token;
if (S_OK == g_Manager->CloneUserToken( GetOwnerSid(), ANY_SESSION, token.GetWritePointer() ))
{
ImpersonateLoggedOnUser( token.get() );
}
//
// Commit the reply file if it is complete.
// Otherwise, clean it up.
//
if (m_ReplyFile && m_ReplyFile->ReceivedAllData())
{
RETURN_HRESULT( m_ReplyFile->MoveTempFile() );
}
else
{
LogInfo("commit reply: skipping partial file '%S'",
m_ReplyFile ? (const WCHAR*) m_ReplyFile->GetLocalName() : L"(null)");
RemoveReplyFile();
return BG_S_PARTIAL_COMPLETE;
}
return S_OK;
}
//------------------------------------------------------------------------
GUID FileListStorageGuid = { /* 7756da36-516f-435a-acac-44a248fff34d */
0x7756da36,
0x516f,
0x435a,
{0xac, 0xac, 0x44, 0xa2, 0x48, 0xff, 0xf3, 0x4d}
};
HRESULT
CJob::CFileList::Serialize(
HANDLE hFile
)
{
//
// If this function changes, be sure that the metadata extension
// constants are adequate.
//
iterator iter;
SafeWriteBlockBegin( hFile, FileListStorageGuid );
DWORD count = size();
SafeWriteFile( hFile, count );
for (iter=begin(); iter != end(); ++iter)
{
(*iter)->Serialize( hFile );
}
SafeWriteBlockEnd( hFile, FileListStorageGuid );
return S_OK;
}
void
CJob::CFileList::Unserialize(
HANDLE hFile,
CJob* Job
)
{
DWORD i, count;
SafeReadBlockBegin( hFile, FileListStorageGuid );
SafeReadFile( hFile, &count );
for (i=0; i < count; ++i)
{
CFile * file = CFile::Unserialize( hFile, Job );
push_back( file );
}
SafeReadBlockEnd( hFile, FileListStorageGuid );
}
void
CJob::CFileList::Delete(
CFileList::iterator Initial,
CFileList::iterator Terminal
)
{
//
// delete the CFile objects
//
iterator iter = Initial;
while (iter != Terminal)
{
CFile * file = (*iter);
++iter;
delete file;
}
//
// erase them from the dictionary
//
erase( Initial, Terminal );
}
//------------------------------------------------------------------------
HRESULT CLockedJobWritePointer::ValidateAccess()
{
HRESULT hr = CLockedWritePointer<CJob, BG_JOB_WRITE>::ValidateAccess();
if (SUCCEEDED(hr))
{
m_Pointer->UpdateLastAccessTime();
}
return hr;
}
HRESULT CLockedJobReadPointer::ValidateAccess()
{
HRESULT hr = CLockedReadPointer<CJob, BG_JOB_READ>::ValidateAccess();
if (SUCCEEDED(hr))
{
((CJob *) m_Pointer)->UpdateLastAccessTime();
}
return hr;
}
CJobExternal::CJobExternal()
: m_ServiceInstance( g_ServiceInstance ),
pJob( NULL ),
m_refs(1)
{
}
CJobExternal::~CJobExternal()
{
//
// Delete the underlying job object, unless it was already deleted when the service stopped.
//
if (g_ServiceInstance != m_ServiceInstance ||
g_ServiceState != MANAGER_ACTIVE)
{
return;
}
delete pJob;
}
STDMETHODIMP
CJobExternal::QueryInterface(
REFIID iid,
void** ppvObject
)
{
BEGIN_EXTERNAL_FUNC
HRESULT Hr = S_OK;
*ppvObject = NULL;
if (iid == __uuidof(IUnknown)
|| iid == __uuidof(IBackgroundCopyJob)
#if !defined( BITS_V12 )
|| iid == __uuidof(IBackgroundCopyJob2)
#endif
)
{
*ppvObject = (IBackgroundCopyJob2 *) this;
((IUnknown *)(*ppvObject))->AddRef();
}
else
{
Hr = E_NOINTERFACE;
}
LogRef( "job %p, iid %!guid!, Hr %x", pJob, &iid, Hr );
return Hr;
END_EXTERNAL_FUNC
}
ULONG
CJobExternal::AddRef()
{
BEGIN_EXTERNAL_FUNC
ULONG newrefs = InterlockedIncrement(&m_refs);
LogRef( "job %p, refs = %d", pJob, newrefs );
return newrefs;
END_EXTERNAL_FUNC
}
ULONG
CJobExternal::Release()
{
BEGIN_EXTERNAL_FUNC
ULONG newrefs = InterlockedDecrement(&m_refs);
LogRef( "job %p, refs = %d", pJob, newrefs );
if (newrefs == 0)
{
delete this;
}
return newrefs;
END_EXTERNAL_FUNC
}
STDMETHODIMP
CJobExternal::AddFileSetInternal(
/* [in] */ ULONG cFileCount,
/* [size_is][in] */ BG_FILE_INFO *pFileSet
)
{
CLockedJobWritePointer LockedJob(pJob);
LogPublicApiBegin( "cFileCount %u, pFileSet %p", cFileCount, pFileSet );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->AddFileSet( cFileCount, pFileSet );
}
LogPublicApiEnd( "cFileCount %u, pFileSet %p", cFileCount, pFileSet );
return Hr;
}
STDMETHODIMP
CJobExternal::AddFileInternal(
/* [in] */ LPCWSTR RemoteUrl,
/* [in] */ LPCWSTR LocalName
)
{
CLockedJobWritePointer LockedJob(pJob);
LogPublicApiBegin( "RemoteUrl %S, LocalName %S", RemoteUrl, LocalName );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->AddFile( RemoteUrl, LocalName, true );
}
LogPublicApiEnd( "RemoteUrl %S, LocalName %S", RemoteUrl, LocalName );
return Hr;
}
STDMETHODIMP
CJobExternal::EnumFilesInternal(
/* [out] */ IEnumBackgroundCopyFiles **ppEnum
)
{
CLockedJobReadPointer LockedJob(pJob);
LogPublicApiBegin( "ppEnum %p", ppEnum );
HRESULT Hr = S_OK;
CEnumFiles *pEnum = NULL;
try
{
*ppEnum = NULL;
THROW_HRESULT( LockedJob.ValidateAccess());
pEnum = new CEnumFiles;
for (CJob::CFileList::const_iterator iter = LockedJob->m_files.begin();
iter != LockedJob->m_files.end(); ++iter)
{
CFileExternal * file = (*iter)->CreateExternalInterface();
pEnum->Add( file );
file->Release();
}
*ppEnum = pEnum;
Hr = S_OK;
}
catch ( ComError exception )
{
Hr = exception.Error();
SafeRelease( pEnum );
}
LogPublicApiEnd( "ppEnum %p(%p)", ppEnum, *ppEnum );
return Hr;
}
STDMETHODIMP
CJobExternal::SuspendInternal(
void
)
{
CLockedJobWritePointer LockedJob(pJob);
LogPublicApiBegin( " " );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->Suspend();
}
LogPublicApiEnd( " " );
return Hr;
}
STDMETHODIMP
CJobExternal::ResumeInternal(
void
)
{
CLockedJobWritePointer LockedJob(pJob);
LogPublicApiBegin( " " );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->Resume();
}
LogPublicApiEnd( " " );
return Hr;
}
STDMETHODIMP
CJobExternal::CancelInternal(
void
)
{
CLockedJobWritePointer LockedJob(pJob);
LogPublicApiBegin( " " );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->Cancel();
}
LogPublicApiEnd( " " );
return Hr;
}
STDMETHODIMP
CJobExternal::CompleteInternal(
void
)
{
CLockedJobWritePointer LockedJob(pJob);
LogPublicApiBegin( " " );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->Complete();
}
LogPublicApiEnd( " " );
return Hr;
}
STDMETHODIMP
CJobExternal::GetIdInternal(
/* [out] */ GUID *pVal
)
{
CLockedJobReadPointer LockedJob(pJob);
LogPublicApiBegin( "GetId pVal %p", pVal );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
*pVal = LockedJob->GetId();
}
LogPublicApiEnd( "pVal %p(%!guid!)", pVal, pVal );
return Hr;
}
STDMETHODIMP
CJobExternal::GetTypeInternal(
/* [out] */ BG_JOB_TYPE *pVal
)
{
CLockedJobReadPointer LockedJob(pJob);
LogPublicApiBegin( "pVal %p", pVal );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
*pVal = LockedJob->GetType();
}
LogPublicApiEnd( "pVal %p(%u)", pVal, *pVal );
return Hr;
}
STDMETHODIMP
CJobExternal::GetProgressInternal(
/* [out] */ BG_JOB_PROGRESS *pVal
)
{
CLockedJobReadPointer LockedJob(pJob);
LogPublicApiBegin( "pVal %p", pVal );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
LockedJob->GetProgress( pVal );
}
LogPublicApiEnd( "pVal %p", pVal );
return Hr;
}
STDMETHODIMP
CJobExternal::GetTimesInternal(
/* [out] */ BG_JOB_TIMES *pVal
)
{
CLockedJobReadPointer LockedJob(pJob);
LogPublicApiBegin( "pVal %p", pVal );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
LockedJob->GetTimes( pVal );
}
LogPublicApiEnd( "pVal %p", pVal );
return Hr;
}
STDMETHODIMP
CJobExternal::GetStateInternal(
/* [out] */ BG_JOB_STATE *pVal
)
{
CLockedJobReadPointer LockedJob(pJob);
LogPublicApiBegin( "pVal %p", pVal );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
*pVal = LockedJob->_GetState();
}
LogPublicApiEnd( "state %d", *pVal );
return Hr;
}
STDMETHODIMP
CJobExternal::GetErrorInternal(
/* [out] */ IBackgroundCopyError **ppError
)
{
CLockedJobReadPointer LockedJob(pJob);
LogPublicApiBegin( "ppError %p", ppError );
*ppError = NULL;
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
const CJobError *Error = LockedJob->GetError();
if ( !Error )
{
Hr = BG_E_ERROR_INFORMATION_UNAVAILABLE;
}
else
{
try
{
*ppError = new CJobErrorExternal( Error );
Hr = S_OK;
}
catch ( ComError err )
{
Hr = err.Error();
}
}
}
LogPublicApiEnd( "pError %p", *ppError );
return Hr;
}
STDMETHODIMP
CJobExternal::SetDisplayNameInternal(
/* [in] */ LPCWSTR Val
)
{
CLockedJobWritePointer LockedJob(pJob);
LogPublicApiBegin( "Val %S", Val );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->SetDisplayName( Val );
}
LogPublicApiEnd( "Val %S", Val );
return Hr;
}
STDMETHODIMP
CJobExternal::GetDisplayNameInternal(
/* [out] */ LPWSTR *pVal
)
{
CLockedJobReadPointer LockedJob(pJob);
LogPublicApiBegin( "pVal %p", pVal );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->GetDisplayName( pVal );
}
LogPublicApiEnd( "pVal %p(%S)", pVal, SUCCEEDED(Hr) ? *pVal : L"?" );
return Hr;
}
STDMETHODIMP
CJobExternal::SetDescriptionInternal(
/* [in] */ LPCWSTR Val
)
{
CLockedJobWritePointer LockedJob(pJob);
LogPublicApiBegin( "Val %S", Val );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->SetDescription( Val );
}
LogPublicApiEnd( "Val %S", Val );
return Hr;
}
STDMETHODIMP
CJobExternal::GetDescriptionInternal(
/* [out] */ LPWSTR *pVal
)
{
CLockedJobReadPointer LockedJob(pJob);
LogPublicApiBegin("pVal %p", pVal );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->GetDescription( pVal );
}
LogPublicApiEnd("pVal %p(%S)", pVal, SUCCEEDED(Hr) ? *pVal : L"?" );
return Hr;
}
STDMETHODIMP
CJobExternal::SetPriorityInternal(
/* [in] */ BG_JOB_PRIORITY Val
)
{
CLockedJobWritePointer LockedJob(pJob);
LogPublicApiBegin("Val %u", Val);
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->SetPriority( Val );
}
LogPublicApiEnd("Val %u", Val );
return Hr;
}
STDMETHODIMP
CJobExternal::GetPriorityInternal(
/* [out] */ BG_JOB_PRIORITY *pVal
)
{
CLockedJobReadPointer LockedJob(pJob);
LogPublicApiBegin( "pVal %p", pVal );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
*pVal = LockedJob->_GetPriority();
}
LogPublicApiEnd( "pVal %p(%u)", pVal, *pVal );
return Hr;
}
STDMETHODIMP
CJobExternal::GetOwnerInternal(
/* [out] */ LPWSTR *pVal
)
{
CLockedJobReadPointer LockedJob(pJob);
LogPublicApiBegin( "pVal %p", pVal );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->GetOwner( pVal );
}
LogPublicApiEnd( "pVal %p(%S)", pVal, SUCCEEDED(Hr) ? *pVal : L"?" );
return Hr;
}
STDMETHODIMP
CJobExternal::SetNotifyFlagsInternal(
/* [in] */ ULONG Val
)
{
CLockedJobWritePointer LockedJob(pJob);
LogPublicApiBegin( "Val %u", Val );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->SetNotifyFlags( Val );
}
LogPublicApiEnd( "Val %u", Val );
return Hr;
}
STDMETHODIMP
CJobExternal::GetNotifyFlagsInternal(
/* [out] */ ULONG *pVal
)
{
CLockedJobReadPointer LockedJob(pJob);
LogPublicApiBegin( "pVal %p", pVal );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
*pVal = LockedJob->GetNotifyFlags();
}
LogPublicApiEnd( "pVal %p(%u)", pVal, *pVal );
return Hr;
}
STDMETHODIMP
CJobExternal::SetNotifyInterfaceInternal(
/* [in] */ IUnknown * Val
)
{
CLockedJobWritePointer LockedJob(pJob);
LogPublicApiBegin( "Val %p", Val );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
BOOL fValidNotifyInterface = pJob->TestNotifyInterface();
Hr = pJob->SetNotifyInterface( Val );
// If there was no previous notification interface (or it's
// no longer valid) and the job is already in the Transferred
// state or fatal error state then go ahead and do the callback:
if ((SUCCEEDED(Hr))&&(Val)&&(!fValidNotifyInterface))
{
if (pJob->_GetState() == BG_JOB_STATE_TRANSFERRED)
{
pJob->ScheduleCompletionCallback();
}
else if (pJob->_GetState() == BG_JOB_STATE_ERROR)
{
pJob->ScheduleErrorCallback();
}
}
}
LogPublicApiEnd( "Val %p", Val );
return Hr;
}
STDMETHODIMP
CJobExternal::GetNotifyInterfaceInternal(
/* [out] */ IUnknown ** pVal
)
{
CLockedJobReadPointer LockedJob(pJob);
LogPublicApiBegin( "pVal %p", pVal );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->GetNotifyInterface( pVal );
}
LogPublicApiEnd( "pVal %p", pVal );
return Hr;
}
STDMETHODIMP
CJobExternal::SetMinimumRetryDelayInternal(
/* [in] */ ULONG Seconds
)
{
CLockedJobWritePointer LockedJob(pJob);
LogPublicApiBegin( "Seconds %u", Seconds );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->SetMinimumRetryDelay( Seconds );
}
LogPublicApiEnd( "Seconds %u", Seconds );
return Hr;
}
STDMETHODIMP
CJobExternal::GetMinimumRetryDelayInternal(
/* [out] */ ULONG *Seconds
)
{
CLockedJobReadPointer LockedJob(pJob);
LogPublicApiBegin( "Seconds %p", Seconds );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->GetMinimumRetryDelay( Seconds );
}
LogPublicApiEnd( "Seconds %p(%u)", Seconds, *Seconds );
return Hr;
}
STDMETHODIMP
CJobExternal::SetNoProgressTimeoutInternal(
/* [in] */ ULONG Seconds
)
{
CLockedJobWritePointer LockedJob(pJob);
LogPublicApiBegin( "Seconds %u", Seconds );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->SetNoProgressTimeout( Seconds );
}
LogPublicApiEnd( "Seconds %u", Seconds );
return Hr;
}
STDMETHODIMP
CJobExternal::GetNoProgressTimeoutInternal(
/* [out] */ ULONG *Seconds
)
{
CLockedJobReadPointer LockedJob(pJob);
LogPublicApiBegin( "Seconds %p", Seconds );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->GetNoProgressTimeout( Seconds );
}
LogPublicApiEnd( "Seconds %p(%u)", Seconds, *Seconds );
return Hr;
}
STDMETHODIMP
CJobExternal::GetErrorCountInternal(
/* [out] */ ULONG * Retries
)
{
CLockedJobReadPointer LockedJob(pJob);
LogPublicApiBegin( "retries %p", Retries );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->GetErrorCount( Retries );
}
LogPublicApiEnd( "retries %p(%u)", Retries, *Retries );
return Hr;
}
STDMETHODIMP
CJobExternal::SetProxySettingsInternal(
BG_JOB_PROXY_USAGE ProxyUsage,
LPCWSTR ProxyList,
LPCWSTR ProxyBypassList
)
{
CLockedJobWritePointer LockedJob(pJob);
LogPublicApiBegin( "ProxyUsage %u, ProxyList %S, ProxyBypassList %S",
ProxyUsage,
ProxyList ? ProxyList : L"NULL",
ProxyBypassList ? ProxyBypassList : L"NULL" );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->SetProxySettings( ProxyUsage, ProxyList, ProxyBypassList );
}
LogPublicApiEnd( "ProxyUsage %u, ProxyList %S, ProxyBypassList %S",
ProxyUsage,
ProxyList ? ProxyList : L"NULL",
ProxyBypassList ? ProxyBypassList : L"NULL" );
return Hr;
}
STDMETHODIMP
CJobExternal::GetProxySettingsInternal(
BG_JOB_PROXY_USAGE *pProxyUsage,
LPWSTR *pProxyList,
LPWSTR *pProxyBypassList
)
{
CLockedJobReadPointer LockedJob(pJob);
LogPublicApiBegin( "pProxyUsage %p, pProxyList %p, pProxyBypassList %p",
pProxyUsage, pProxyList, pProxyBypassList );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->GetProxySettings( pProxyUsage, pProxyList, pProxyBypassList );
}
LogPublicApiEnd( "pProxyUsage %p, pProxyList %p, pProxyBypassList %p",
pProxyUsage, pProxyList, pProxyBypassList );
return Hr;
}
STDMETHODIMP
CJobExternal::TakeOwnershipInternal()
{
LogPublicApiBegin( " " );
CLockedJobWritePointer LockedJob(pJob);
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->AssignOwnership( GetThreadClientSid() );
}
LogPublicApiEnd( " " );
return Hr;
}
HRESULT STDMETHODCALLTYPE
CJobExternal::SetNotifyCmdLineInternal(
LPCWSTR Val
)
{
CLockedJobWritePointer LockedJob(pJob);
LogPublicApiBegin( "Val %S", Val );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->SetNotifyCmdLine( Val );
}
LogPublicApiEnd( "Val %S", Val );
return Hr;
}
HRESULT STDMETHODCALLTYPE
CJobExternal::GetNotifyCmdLineInternal(
LPWSTR *pVal
)
{
CLockedJobReadPointer LockedJob(pJob);
LogPublicApiBegin("pVal %p", pVal );
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->GetNotifyCmdLine( pVal );
}
LogPublicApiEnd("pVal %p(%S)", pVal, SUCCEEDED(Hr) ? *pVal : L"?" );
return Hr;
}
HRESULT STDMETHODCALLTYPE
CJobExternal::GetReplyProgressInternal(
BG_JOB_REPLY_PROGRESS *pProgress
)
{
LogPublicApiBegin( " " );
CLockedJobReadPointer LockedJob(pJob);
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->GetReplyProgress( pProgress );
}
LogPublicApiEnd( "%I64d of %I64d transferred", pProgress->BytesTransferred, pProgress->BytesTotal );
return Hr;
}
HRESULT STDMETHODCALLTYPE
CJobExternal::GetReplyDataInternal(
byte **ppBuffer,
ULONG *pLength
)
{
LogPublicApiBegin( " " );
CLockedJobReadPointer LockedJob(pJob);
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->GetReplyData( ppBuffer, pLength );
}
return Hr;
}
HRESULT STDMETHODCALLTYPE
CJobExternal::SetReplyFileNameInternal(
LPCWSTR Val
)
{
LogPublicApiBegin( "file '%S'", Val ? Val : L"(null)");
CLockedJobWritePointer LockedJob(pJob);
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->SetReplyFileName( Val );
}
LogPublicApiEnd( " " );
return Hr;
}
HRESULT STDMETHODCALLTYPE
CJobExternal::GetReplyFileNameInternal(
LPWSTR *pReplyFileName
)
{
LogPublicApiBegin( " " );
//
// This can modify the job, if the reply file name is not yet created.
//
CLockedJobReadPointer LockedJob(pJob);
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->GetReplyFileName( pReplyFileName );
}
LogPublicApiEnd( "file '%S'", *pReplyFileName ? *pReplyFileName : L"(null)" );
return Hr;
}
HRESULT STDMETHODCALLTYPE
CJobExternal::SetCredentialsInternal(
BG_AUTH_CREDENTIALS * Credentials
)
{
LogPublicApiBegin( "cred %p, target %d, scheme %d", Credentials, Credentials->Target, Credentials->Scheme );
CLockedJobWritePointer LockedJob(pJob);
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->SetCredentials( Credentials );
}
LogPublicApiEnd( " " );
return Hr;
}
HRESULT STDMETHODCALLTYPE
CJobExternal::RemoveCredentialsInternal(
BG_AUTH_TARGET Target,
BG_AUTH_SCHEME Scheme
)
{
LogPublicApiBegin( "target %d, scheme %d", Target, Scheme );
CLockedJobWritePointer LockedJob(pJob);
HRESULT Hr = LockedJob.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedJob->RemoveCredentials( Target, Scheme );
}
LogPublicApiEnd( " " );
return Hr;
}
HRESULT
CJob::SetReplyFileName(
LPCWSTR Val
)
{
return E_NOTIMPL;
}
HRESULT
CUploadJob::SetReplyFileName(
LPCWSTR Val
)
{
if (m_type != BG_JOB_TYPE_UPLOAD_REPLY)
{
return E_NOTIMPL;
}
if (m_ReplyFile)
{
return BG_E_INVALID_STATE;
}
if (Val)
{
RETURN_HRESULT( CFile::VerifyLocalFileName( Val, BG_JOB_TYPE_DOWNLOAD ));
}
try
{
StringHandle name = Val;
//
// Impersonate the user while checking file access.
//
CNestedImpersonation imp;
imp.SwitchToLogonToken();
//
// Four cases:
//
// 1. new name NULL, old name NULL:
// no change
//
// 2. new name NULL, old name non-NULL:
// overwrite the file name, set ownership correctly. No need to
// delete the old file because it wasn't created yet.
//
// 3. new name non-NULL, old name NULL:
// overwrite the file name, set ownership correctly. Delete the
// temporary old file name.
//
// 4. new name non-NULL, old name non-NULL:
// overwrite the file name. no file to delete.
//
if (name.Size() > 0)
{
THROW_HRESULT( BITSCheckFileWritability( name ));
DeleteGeneratedReplyFile();
THROW_HRESULT( UpdateString( m_ReplyFileName, name));
m_fOwnReplyFileName = false;
}
else
{
THROW_HRESULT( UpdateString( m_ReplyFileName, name));
(void) GenerateReplyFile( false );
}
g_Manager->Serialize();
return S_OK;
}
catch ( ComError err )
{
return err.Error();
}
}
HRESULT
CJob::GetReplyFileName(
LPWSTR * pVal
) const
{
return E_NOTIMPL;
}
HRESULT
CUploadJob::GetReplyFileName(
LPWSTR * pVal
) const
{
if (m_ReplyFileName.Size() == 0)
{
*pVal = NULL;
return S_OK;
}
*pVal = MidlCopyString( m_ReplyFileName );
return (*pVal) ? S_OK : E_OUTOFMEMORY;
}
HRESULT
CJob::GetReplyProgress(
BG_JOB_REPLY_PROGRESS *pProgress
) const
{
return E_NOTIMPL;
}
HRESULT
CUploadJob::GetReplyProgress(
BG_JOB_REPLY_PROGRESS *pProgress
) const
{
if (m_type != BG_JOB_TYPE_UPLOAD_REPLY)
{
return E_NOTIMPL;
}
if (m_ReplyFile)
{
pProgress->BytesTotal = m_ReplyFile->_GetBytesTotal();
pProgress->BytesTransferred = m_ReplyFile->_GetBytesTransferred();
}
else
{
pProgress->BytesTotal = BG_SIZE_UNKNOWN;
pProgress->BytesTransferred = 0;
}
return S_OK;
}
HRESULT
CUploadJob::Resume()
{
if (m_type == BG_JOB_TYPE_UPLOAD_REPLY)
{
RETURN_HRESULT( GenerateReplyFile(true ) );
}
return CJob::Resume();
}
HRESULT
CUploadJob::GenerateReplyFile(
bool fSerialize
)
{
if (0 != wcscmp( m_ReplyFileName, L"" ))
{
return S_OK;
}
//
// Gotta create a reply file name.
//
try
{
if (IsEmpty())
{
return BG_E_EMPTY;
}
g_Manager->ExtendMetadata();
//
// Impersonate the user while checking file access.
//
CNestedImpersonation imp;
imp.SwitchToLogonToken();
StringHandle Ignore;
StringHandle Directory = BITSCrackFileName( GetUploadFile()->GetLocalName(),
Ignore );
m_ReplyFileName = BITSCreateTempFile( Directory );
m_fOwnReplyFileName = true;
if (fSerialize)
{
g_Manager->Serialize();
}
return S_OK;
}
catch ( ComError err )
{
g_Manager->ShrinkMetadata();
return err.Error();
}
}
HRESULT
CUploadJob::DeleteGeneratedReplyFile()
{
if (m_fOwnReplyFileName)
{
if (!DeleteFile( m_ReplyFileName ))
{
DWORD s = GetLastError();
LogWarning("unable to delete generated reply file '%S', %!winerr!", LPCWSTR(m_ReplyFileName), s);
return HRESULT_FROM_WIN32( s );
}
}
return S_OK;
}
void
CUploadJob::SetReplyFile(
CFile * file
)
{
try
{
g_Manager->ExtendMetadata( file->GetSizeEstimate() );
m_ReplyFile = file;
g_Manager->Serialize();
}
catch ( ComError err )
{
g_Manager->ShrinkMetadata();
throw;
}
}
HRESULT
CJob::GetReplyData(
byte **ppBuffer,
ULONG *pLength
) const
{
return E_NOTIMPL;
}
HRESULT
CUploadJob::GetReplyData(
byte **ppBuffer,
ULONG *pLength
) const
{
return E_NOTIMPL;
}
HRESULT
CJob::SetCredentials(
BG_AUTH_CREDENTIALS * Credentials
)
{
try
{
CNestedImpersonation imp;
imp.SwitchToLogonToken();
g_Manager->ExtendMetadata( m_Credentials.GetSizeEstimate( Credentials ));
THROW_HRESULT( m_Credentials.Update( Credentials ));
g_Manager->Serialize();
return S_OK;
}
catch ( ComError err )
{
g_Manager->ShrinkMetadata();
return err.Error();
}
}
HRESULT
CJob::RemoveCredentials(
BG_AUTH_TARGET Target,
BG_AUTH_SCHEME Scheme
)
{
try
{
CNestedImpersonation imp;
imp.SwitchToLogonToken();
HRESULT hr = m_Credentials.Remove( Target, Scheme );
THROW_HRESULT( hr );
g_Manager->Serialize();
return hr; // may be S_FALSE if the credential was never in the collection
}
catch ( ComError err )
{
return err.Error();
}
}