Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1496 lines
37 KiB

/************************************************************************
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"
#if !defined(BITS_V12_ON_NT4)
#include "cfile.tmh"
#endif
//------------------------------------------------------------------------
StringHandle
BITSGetVolumePathName(
const WCHAR * FileName )
{
#if defined( BITS_V12_ON_NT4 )
WCHAR VolumePath[4];
if ( !(
( FileName[0] >= L'A' && FileName[0] <= L'Z') ||
( FileName[0] >= L'a' && FileName[0] <= L'z')
) ||
( FileName[1] != L':' ) ||
( FileName[2] != L'\\' ) )
{
LogError( "%s doesn't appear to start with a drive letter", FileName );
throw ComError( HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ) );
}
VolumePath[0] = FileName[0];
VolumePath[1] = FileName[1];
VolumePath[2] = FileName[2];
VolumePath[3] = '\0';
return StringHandle( VolumePath );
#else
// Call get full path name to get the
// required buffer size
DWORD dwBufferLength =
GetFullPathName(
FileName,
0,
NULL,
NULL );
if ( !dwBufferLength )
{
DWORD dwError = GetLastError();
LogError( "GetFullPathName failed, error %!winerr!", dwError );
throw ComError( HRESULT_FROM_WIN32( dwError ) );
}
auto_ptr<WCHAR> VolumePathName( new WCHAR[ dwBufferLength + 1 ] );
BOOL bResult =
GetVolumePathName(
FileName,
VolumePathName.get(),
dwBufferLength + 1 );
if ( !bResult )
{
DWORD dwError = GetLastError();
LogError( "GetVolumePathName failed, error %!winerr!", dwError );
throw ComError( HRESULT_FROM_WIN32( dwError ) );
}
return StringHandle( VolumePathName.get() );
#endif
}
StringHandle
BITSGetVolumeNameForVolumeMountPoint(
const WCHAR * VolumeMountPoint )
{
#if defined( BITS_V12_ON_NT4 )
return BITSGetVolumePathName( VolumeMountPoint );
#else
WCHAR VolumeName[50]; // 50 is listed in MSDN
BOOL bResult =
GetVolumeNameForVolumeMountPoint(
VolumeMountPoint,
VolumeName,
50 );
if ( !bResult )
{
DWORD dwError = GetLastError();
LogError( "GetVolumeNameForVolumeMountPoint failed, error %!winerr!", dwError );
throw ComError( HRESULT_FROM_WIN32( dwError ) );
}
return StringHandle( VolumeName );
#endif
}
DWORD
BITSGetVolumeSerialNumber(
const WCHAR * VolumePath )
{
DWORD VolumeSerialNumber;
BOOL bResult =
GetVolumeInformation(
VolumePath, // root directory
NULL, // volume name buffer
0, // length of name buffer
&VolumeSerialNumber, // volume serial number
NULL, // maximum file name length
NULL, // file system options
NULL, // file system name buffer
0 // length of file system name buffer
);
if ( !bResult )
{
DWORD dwError = GetLastError();
LogError( "GetVolumeInformation failed, error %!winerr!", dwError );
throw ComError( HRESULT_FROM_WIN32( dwError ) );
}
return VolumeSerialNumber;
}
StringHandle
BITSGetTempFileName(
const WCHAR *PathName,
const WCHAR *Prefix,
UINT Unique )
{
auto_ptr<WCHAR> TempBuffer( new WCHAR[MAX_PATH+1] );
UINT Result =
GetTempFileName(
PathName,
Prefix,
Unique,
TempBuffer.get() );
if ( !Result )
{
DWORD dwError = GetLastError();
LogError( "GetTempFileName( '%S', '%S' ) failed, error %!winerr!", PathName, Prefix, dwError );
throw ComError( HRESULT_FROM_WIN32( dwError ) );
}
StringHandle ReturnVal;
try
{
ReturnVal = TempBuffer.get();
}
catch( ComError Error )
{
LogError( "Out of memory returning temp filename, deleting temp file" );
DeleteFile( TempBuffer.get() );
throw;
}
return ReturnVal;
}
StringHandle
BITSCrackFileName(
const WCHAR * RawFileName,
StringHandle & ReturnFileName )
{
DWORD FullPathSize =
GetFullPathName( RawFileName,
0,
NULL,
NULL );
DWORD dwAllocSize = FullPathSize;
if ( !FullPathSize )
{
DWORD dwError = GetLastError();
LogError( "GetFullPathName failed, error %!winerr!", dwError );
throw ComError( HRESULT_FROM_WIN32( dwError ) );
}
if ( FullPathSize > MAX_PATH )
{
// Fail large paths until the code can be cleanup up
LogError( "Path larger then MAX_PATH, failing" );
throw ComError( E_INVALIDARG );
}
auto_ptr<WCHAR> FullPath( new WCHAR[ dwAllocSize ] );
WCHAR *FilePointer = NULL;
FullPathSize =
GetFullPathName( RawFileName,
dwAllocSize,
FullPath.get(),
&FilePointer
);
if (FullPathSize == 0 ||
FullPathSize > dwAllocSize)
{
LogError( "GetFullPathName failed " );
throw ComError( E_INVALIDARG );
}
if ( !FilePointer ||
(*FilePointer == L'\0') ||
(FilePointer == FullPath.get()))
{
throw ComError( E_INVALIDARG );
}
auto_ptr<WCHAR> DirectoryName( new WCHAR[ dwAllocSize ] );
auto_ptr<WCHAR> FileName( new WCHAR[ dwAllocSize ] );
memcpy( DirectoryName.get(), FullPath.get(), (char*)FilePointer - (char*)FullPath.get() );
(DirectoryName.get())[ ((char*)FilePointer - (char*)FullPath.get()) / sizeof(WCHAR) ] = L'\0';
THROW_HRESULT( StringCchCopy( FileName.get(), dwAllocSize, FilePointer ));
FilePointer = NULL;
ReturnFileName = FileName.get();
return StringHandle( DirectoryName.get() );
}
StringHandle
BITSCreateTempFile(
StringHandle Directory
)
{
StringHandle TempFileName;
TempFileName = BITSGetTempFileName( Directory, L"BITS", 0 ); //throw ComError
//
// Make sure the client can create the temp file.
//
HANDLE hFile;
hFile = CreateFile( TempFileName,
GENERIC_WRITE,
0, // no file sharing
NULL, // generic security descriptor
CREATE_ALWAYS,
FILE_FLAG_WRITE_THROUGH | FILE_ATTRIBUTE_HIDDEN,
NULL // no template file
);
if (hFile == INVALID_HANDLE_VALUE)
{
ThrowLastError();
}
CloseHandle( hFile );
return TempFileName;
}
HRESULT
BITSCheckFileWritability(
LPCWSTR name
)
{
HANDLE hFile;
hFile = CreateFile( name,
GENERIC_WRITE,
0, // no file sharing
NULL, // generic security descriptor
OPEN_EXISTING,
0,
NULL // no template file
);
if (hFile == INVALID_HANDLE_VALUE && GetLastError() != ERROR_FILE_NOT_FOUND)
{
return HRESULT_FROM_WIN32( GetLastError() );
}
if (hFile != INVALID_HANDLE_VALUE)
{
if (GetFileType( hFile ) != FILE_TYPE_DISK)
{
CloseHandle( hFile );
return E_INVALIDARG;
}
CloseHandle( hFile );
}
return S_OK;
}
HRESULT
BITSCheckFileReadability(
LPCWSTR name
)
{
HANDLE hFile;
hFile = CreateFile( name,
GENERIC_READ,
FILE_SHARE_READ, // no file sharing
NULL, // generic security descriptor
OPEN_EXISTING,
0,
NULL // no template file
);
if (hFile == INVALID_HANDLE_VALUE)
{
return HRESULT_FROM_WIN32( GetLastError() );
}
if (GetFileType( hFile ) != FILE_TYPE_DISK)
{
CloseHandle( hFile );
return E_INVALIDARG;
}
CloseHandle( hFile );
return S_OK;
}
CFile::CFile(
CJob* Job,
BG_JOB_TYPE FileType,
StringHandle RemoteName,
StringHandle LocalName
)
{
m_Job = Job;
m_RemoteName = RemoteName;
m_LocalName = LocalName;
m_BytesTransferred = 0;
m_BytesTotal = BG_SIZE_UNKNOWN;
m_Completed = false;
if (!VerifyRemoteName( RemoteName ))
{
throw ComError( E_INVALIDARG );
}
HRESULT hr = VerifyLocalName( LocalName, FileType );
if (FAILED(hr))
{
throw ComError( hr );
}
}
// private constructor used during unserialization
// It initializes only the transient data.
CFile::CFile(
CJob* Job
)
{
m_Job = Job;
m_BytesTotal = 0;
m_BytesTransferred = 0;
m_Completed = false;
}
CFile::~CFile()
{
}
//----------------------------------------------
CFileExternal *
CFile::CreateExternalInterface()
{
return new CFileExternal( this, m_Job->GetExternalInterface() );
}
HRESULT
CFile::GetLocalName(
LPWSTR *pVal
) const
{
*pVal = MidlCopyString( m_LocalName );
return (*pVal) ? S_OK : E_OUTOFMEMORY;
}
HRESULT
CFile::GetRemoteName(
LPWSTR *pVal
) const
{
*pVal = MidlCopyString( m_RemoteName );
return (*pVal) ? S_OK : E_OUTOFMEMORY;
}
void
CFile::GetProgress(
BG_FILE_PROGRESS * s
) const
{
s->BytesTransferred = m_BytesTransferred;
s->BytesTotal = m_BytesTotal;
s->Completed = m_Completed;
}
HRESULT
CFile::Serialize(
HANDLE hFile
)
{
//
// If this function changes, be sure that the metadata extension
// constants are adequate.
//
// not needed for download jobs, and serializing it would be incompatible
// with BITS 1.0.
//
if (m_Job->GetType() != BG_JOB_TYPE_DOWNLOAD)
{
SafeWriteFile( hFile, m_LocalFileTime );
}
SafeWriteStringHandle( hFile, m_LocalName );
SafeWriteStringHandle( hFile, m_RemoteName );
SafeWriteStringHandle( hFile, m_TemporaryName );
SafeWriteFile( hFile, m_BytesTransferred );
SafeWriteFile( hFile, m_BytesTotal );
SafeWriteFile( hFile, m_Completed );
// Drive info
SafeWriteStringHandle( hFile, m_VolumePath );
SafeWriteStringHandle( hFile, m_CanonicalVolumePath );
SafeWriteFile( hFile, m_DriveType );
SafeWriteFile( hFile, m_VolumeSerialNumber );
return S_OK;
}
CFile *
CFile::Unserialize(
HANDLE hFile,
CJob* Job
)
{
CFile * file = NULL;
try
{
file = new CFile(Job);
if (Job->GetType() != BG_JOB_TYPE_DOWNLOAD)
{
SafeReadFile( hFile, &file->m_LocalFileTime );
}
file->m_LocalName = SafeReadStringHandle( hFile );
file->m_RemoteName = SafeReadStringHandle( hFile );
file->m_TemporaryName = SafeReadStringHandle( hFile );
SafeReadFile( hFile, &file->m_BytesTransferred );
SafeReadFile( hFile, &file->m_BytesTotal );
SafeReadFile( hFile, &file->m_Completed );
file->m_VolumePath = SafeReadStringHandle( hFile );
file->m_CanonicalVolumePath = SafeReadStringHandle( hFile );
SafeReadFile( hFile, &file->m_DriveType );
SafeReadFile( hFile, &file->m_VolumeSerialNumber );
}
catch ( ComError Error )
{
delete file;
throw;
}
return file;
}
inline HRESULT
CFile::CheckClientAccess(
DWORD RequestedAccess
) const
{
return m_Job->CheckClientAccess( RequestedAccess );
}
HRESULT
CFile::VerifyLocalFileName(
LPCWSTR name,
BG_JOB_TYPE FileType
)
{
if (name == NULL)
{
return E_INVALIDARG;
}
//
// Make sure the client can create a file there.
//
HRESULT Hr = S_OK;
try
{
DWORD s;
BOOL bResult;
if (!IsAbsolutePath( name ))
throw ComError( E_INVALIDARG );
if ( wcsncmp( name, L"\\\\?\\", 4 ) == 0 )
throw ComError( E_INVALIDARG );
if (FileType == BG_JOB_TYPE_DOWNLOAD)
{
HRESULT hr;
hr = BITSCheckFileWritability( name );
if (FAILED(hr))
{
throw ComError( hr );
}
}
else
{
//
// See if the client can read the destination file.
//
auto_HANDLE<NULL> hFile;
hFile = CreateFile( name,
GENERIC_READ,
FILE_SHARE_READ,
NULL, // gneeric security descriptor
OPEN_EXISTING,
0,
NULL // no template file
);
if (hFile.get() == INVALID_HANDLE_VALUE)
{
ThrowLastError();
}
if (GetFileType( hFile.get() ) != FILE_TYPE_DISK)
{
throw ComError( E_INVALIDARG );
}
}
//
// Success.
//
Hr = S_OK;
}
catch ( ComError exception )
{
Hr = exception.Error();
}
return Hr;
}
HRESULT
CFile::VerifyLocalName(
LPCWSTR name,
BG_JOB_TYPE FileType
)
{
if (name == NULL)
{
return E_INVALIDARG;
}
//
// Make sure the client can create a file there.
//
HRESULT Hr = S_OK;
try
{
DWORD s;
BOOL bResult;
if (!IsAbsolutePath( name ))
throw ComError( E_INVALIDARG );
if ( wcsncmp( name, L"\\\\?\\", 4 ) == 0 )
throw ComError( E_INVALIDARG );
StringHandle FileName;
StringHandle DirectoryName =
BITSCrackFileName(
name,
FileName ); // throws ComError
StringHandle VolumePath =
BITSGetVolumePathName(
DirectoryName ); // throws ComError
UINT DriveType = GetDriveType( VolumePath );
BOOL bIsRemote = IsDriveTypeRemote( DriveType );
StringHandle CanonicalPath;
DWORD VolumeSerialNumber = 0;
if ( !bIsRemote )
{
CanonicalPath =
BITSGetVolumeNameForVolumeMountPoint(
VolumePath ); // throw ComError
VolumeSerialNumber =
BITSGetVolumeSerialNumber( CanonicalPath ); //throws ComError
}
m_VolumePath = VolumePath;
m_CanonicalVolumePath = CanonicalPath;
m_DriveType = DriveType;
m_VolumeSerialNumber = VolumeSerialNumber;
if (FileType == BG_JOB_TYPE_DOWNLOAD)
{
HRESULT hr;
m_TemporaryName = BITSCreateTempFile( DirectoryName );
hr = BITSCheckFileWritability( name );
if (FAILED(hr))
{
DeleteFile( m_TemporaryName );
throw ComError( hr );
}
}
else
{
//
// See if the client can read the destination file.
//
auto_HANDLE<NULL> hFile;
hFile = CreateFile( name,
GENERIC_READ,
FILE_SHARE_READ, // no file sharing
NULL, // generic security descriptor
OPEN_EXISTING,
0,
NULL // no template file
);
if (hFile.get() == INVALID_HANDLE_VALUE)
{
ThrowLastError();
}
LARGE_INTEGER size;
if (!GetFileSizeEx( hFile.get(), &size ))
{
ThrowLastError();
}
m_BytesTotal = size.QuadPart;
BY_HANDLE_FILE_INFORMATION info;
if (!GetFileInformationByHandle( hFile.get(), &info ))
{
ThrowLastError();
}
m_LocalFileTime = info.ftLastWriteTime;
}
//
// Success.
//
Hr = S_OK;
}
catch ( ComError exception )
{
Hr = exception.Error();
}
return Hr;
}
HRESULT
CFile::ValidateAccessForUser(
SidHandle sid,
bool fWrite
)
{
try
{
StringHandle CanonicalPath;
DWORD VolumeSerialNumber = 0;
UINT DriveType = 0;
CNestedImpersonation imp( sid );
StringHandle VolumePath =
BITSGetVolumePathName( m_LocalName );
DriveType = GetDriveType( VolumePath );
bool bIsRemote = IsDriveTypeRemote( DriveType );
if ( !bIsRemote )
{
CanonicalPath =
BITSGetVolumeNameForVolumeMountPoint( VolumePath );
// Need to stop impersonating at this point since registration
// for notifications doesn't seem to tolerate impersonating callers
imp.Revert();
#if !defined( BITS_V12_ON_NT4 )
THROW_HRESULT( g_Manager->IsVolumeLocked( CanonicalPath ));
#endif
VolumeSerialNumber =
BITSGetVolumeSerialNumber( CanonicalPath );
}
bool bValid =
( _wcsicmp( VolumePath, m_VolumePath ) == 0 ) &&
( _wcsicmp( CanonicalPath, m_CanonicalVolumePath ) == 0 ) &&
( m_DriveType == DriveType ) &&
( m_VolumeSerialNumber == VolumeSerialNumber );
if ( !bValid )
return BG_E_NEW_OWNER_DIFF_MAPPING;
// Revalidate access to the file. There are three cases:
//
// 1.file is not renamed: test the temporary file and local file.
// 2. Mars job, file is renamed: test the local file
// 3. new job, file is renamed: no test; the app owns the local file
HANDLE hFile;
HRESULT hr;
imp.Impersonate();
if (IsCompleted())
{
if (m_Job->GetOldExternalGroupInterface())
{
//
// case 2
//
hr = BITSCheckFileWritability( m_LocalName );
if (hr == E_ACCESSDENIED)
{
hr = BG_E_NEW_OWNER_NO_FILE_ACCESS;
}
THROW_HRESULT( hr );
}
else
{
//
// case 3
//
}
}
else
{
//
// case 1
//
if (fWrite)
{
hr = BITSCheckFileWritability( m_TemporaryName );
if (hr == E_ACCESSDENIED)
{
hr = BG_E_NEW_OWNER_NO_FILE_ACCESS;
}
THROW_HRESULT( hr );
hr = BITSCheckFileWritability( m_LocalName );
if (hr == E_ACCESSDENIED)
{
hr = BG_E_NEW_OWNER_NO_FILE_ACCESS;
}
THROW_HRESULT( hr );
}
else
{
hr = BITSCheckFileReadability( m_LocalName );
if (hr == E_ACCESSDENIED)
{
hr = BG_E_NEW_OWNER_NO_FILE_ACCESS;
}
THROW_HRESULT( hr );
}
}
return S_OK;
}
catch( ComError Error )
{
return Error.Error();
}
}
bool
CFile::ValidateDriveInfo(
HANDLE hToken,
QMErrInfo & ErrInfo
)
{
try
{
StringHandle CanonicalPath;
DWORD VolumeSerialNumber = 0;
UINT DriveType = 0;
CNestedImpersonation imp( hToken );
StringHandle VolumePath =
BITSGetVolumePathName( m_LocalName ); // throws ComError
DriveType = GetDriveType( VolumePath );
bool bIsRemote = IsDriveTypeRemote( DriveType );
if ( !bIsRemote )
{
CanonicalPath =
BITSGetVolumeNameForVolumeMountPoint(
VolumePath ); // throws ComError
// Need to stop impersonating at this point since registration
// for notifications doesn't seem to tolerate impersonating callers
imp.Revert();
#if !defined( BITS_V12_ON_NT4 )
THROW_HRESULT( g_Manager->IsVolumeLocked( CanonicalPath ));
#endif
VolumeSerialNumber =
BITSGetVolumeSerialNumber( CanonicalPath ); //throws ComError
}
bool bValid =
( _wcsicmp( VolumePath, m_VolumePath ) == 0 ) &&
( _wcsicmp( CanonicalPath, m_CanonicalVolumePath ) == 0 ) &&
( m_DriveType == DriveType ) &&
( m_VolumeSerialNumber == VolumeSerialNumber );
if ( !bValid )
{
imp.Revert();
g_Manager->OnDiskChange( m_CanonicalVolumePath, m_VolumeSerialNumber );
ErrInfo.Set( SOURCE_QMGR_FILE, ERROR_STYLE_HRESULT, BG_E_VOLUME_CHANGED, "Volume has changed");
ErrInfo.result = QM_FILE_FATAL_ERROR;
return false;
}
}
catch( ComError Error )
{
HRESULT Hr = Error.Error();
LogWarning( "Transient error while reading volume info for %ls, hr %!winerr!",
(const WCHAR*)m_LocalName, Hr );
if ( Hr == HRESULT_FROM_WIN32( ERROR_ACCESS_DENIED ) )
{
LogWarning( "Volume info APIs returned access denied, assume locked volume." );
Hr = BG_E_DESTINATION_LOCKED;
}
ErrInfo.Set( SOURCE_QMGR_FILE, ERROR_STYLE_HRESULT, Hr, "Volume has changed");
ErrInfo.result = QM_FILE_TRANSIENT_ERROR;
return false;
}
return true;
}
bool
CFile::OnDiskChange(
const WCHAR *CanonicalVolume,
DWORD VolumeSerialNumber )
{
bool bFail =
( _wcsicmp( m_CanonicalVolumePath, CanonicalVolume ) == 0 ) &&
( VolumeSerialNumber == m_VolumeSerialNumber );
if (!bFail)
return true;
LogWarning( "Failing job %p, to do disk change affecting file %ls",
m_Job, (const WCHAR*)m_LocalName );
QMErrInfo ErrInfo;
ErrInfo.Set( SOURCE_QMGR_FILE, ERROR_STYLE_HRESULT, BG_E_VOLUME_CHANGED, "Volume has changed");
m_Job->FileFatalError( &ErrInfo );
return false;
}
bool
CFile::OnDismount(
const WCHAR *CanonicalVolume )
{
bool bFail =
( _wcsicmp( m_CanonicalVolumePath, CanonicalVolume ) == 0 );
if (!bFail)
return true;
LogWarning( "Failing job %p, to do dismount affecting file %ls",
m_Job, (const WCHAR*)m_LocalName );
QMErrInfo ErrInfo;
ErrInfo.Set( SOURCE_QMGR_FILE, ERROR_STYLE_HRESULT, BG_E_VOLUME_CHANGED, "Volume has changed");
m_Job->FileFatalError( &ErrInfo );
return false;
}
bool CFile::Transfer(
HANDLE hToken,
BG_JOB_PRIORITY priority,
const PROXY_SETTINGS & ProxySettings,
const CCredentialsContainer * Credentials,
QMErrInfo & ErrInfo
)
{
// Check if the destination is locked or changed.
if (!ValidateDriveInfo( hToken, ErrInfo ))
{
return true;
}
if (m_BytesTransferred == m_BytesTotal)
{
ErrInfo.result = QM_FILE_DONE;
return true;
}
//
// Release the global lock while the download is in progress.
//
g_Manager->m_TaskScheduler.UnlockWriter();
LogDl( "Download starting." );
g_Manager->m_pPD->Download( m_RemoteName,
m_TemporaryName,
m_BytesTransferred,
this,
&ErrInfo,
hToken,
priority,
&ProxySettings,
Credentials,
m_Job->GetHostId()
);
LogDl( "Download Ended." );
ErrInfo.Log();
switch (ErrInfo.result)
{
case QM_FILE_ABORTED:
//
// If the abort was due to quantum timeout, don't poke the workitem.
//
if (g_Manager->m_TaskScheduler.PollAbort())
{
g_Manager->m_TaskScheduler.AcknowledgeWorkItemCancel();
}
break;
case QM_IN_PROGRESS: ASSERT( ErrInfo.result != QM_IN_PROGRESS ); break;
case QM_SERVER_FILE_CHANGED: ChangedOnServer(); break;
case QM_FILE_TRANSIENT_ERROR:
#if !defined( BITS_V12_ON_NT4 )
//
// Map any connection failure to BG_E_NETWORK_DISCONNECTED, if no nets are active.
//
if (g_Manager->m_NetworkMonitor.GetAddressCount() == 0)
{
ErrInfo.Set( SOURCE_HTTP_CLIENT_CONN, ERROR_STYLE_HRESULT, BG_E_NETWORK_DISCONNECTED, NULL );
}
#else
break;
#endif
}
//
// 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();
ErrInfo.result = QM_FILE_ABORTED;
}
if (ErrInfo.result == QM_FILE_ABORTED)
{
return false;
}
return true;
}
bool
CFile::UploaderProgress(
UINT64 BytesTransferred
)
{
ASSERT( g_Manager->m_TaskScheduler.IsWriter() );
//
// Skip the progress update if the server is rewinding the file. Thus the no-progress
// timer is cleared only after we actually see progress.
//
if (BytesTransferred > m_BytesTransferred)
{
m_BytesTransferred = BytesTransferred;
m_Job->UpdateProgress( BytesTransferred, m_BytesTotal );
}
else
{
m_BytesTransferred = BytesTransferred;
}
bool bAbortQuantumExpired = g_Manager->CheckForQuantumTimeout();
return bAbortQuantumExpired;
}
bool
CFile::DownloaderProgress(
UINT64 BytesTransferred,
UINT64 BytesTotal
)
{
if ( g_Manager->m_TaskScheduler.LockWriter() )
{
// Cancel was requested, notify downloader.
return true;
}
m_BytesTransferred = BytesTransferred;
m_BytesTotal = BytesTotal;
m_Job->UpdateProgress( BytesTransferred, BytesTotal );
bool bAbortQuantumExpired = g_Manager->CheckForQuantumTimeout();
g_Manager->m_TaskScheduler.UnlockWriter();
return bAbortQuantumExpired;
}
bool
CFile::PollAbort()
{
if (g_Manager->m_TaskScheduler.PollAbort() ||
g_Manager->CheckForQuantumTimeout())
{
return true;
}
return false;
}
BOOL
CFile::VerifyRemoteName(
LPCWSTR name
)
{
if (name == NULL)
{
return FALSE;
}
if ( ( 0 != wcsncmp(name, L"http://", 7)) &&
( 0 != wcsncmp(name, L"https://", 8)) )
{
return FALSE;
}
if (( wcslen(name) > INTERNET_MAX_URL_LENGTH))
return FALSE;
return TRUE;
}
HRESULT
CFile::MoveTempFile()
{
LogInfo( "commit: moving '%S' to '%S'", (const WCHAR*)m_TemporaryName, (const WCHAR*)m_LocalName );
ASSERT( !m_Completed );
DWORD dwFileAttributes =
GetFileAttributes( (const WCHAR*)m_TemporaryName );
if ( (DWORD)-1 == dwFileAttributes )
{
DWORD dwError = GetLastError();
LogError( "GetFileAttributes error %!winerr!%", dwError );
return HRESULT_FROM_WIN32( dwError );
}
if (!SetFileAttributes( m_TemporaryName, FILE_ATTRIBUTE_NORMAL ))
{
DWORD dwError = GetLastError();
LogError( "SetFileAttributes error %d", dwError );
return HRESULT_FROM_WIN32( dwError );
}
const DWORD dwMaxRetries = 10;
DWORD dwError;
DWORD dwSleepMSec = 50;
for (int i=0; i<dwMaxRetries; i++)
{
if ( MoveFileEx( m_TemporaryName, m_LocalName, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED ) )
{
LogInfo( "file moved ok.");
m_Completed = true;
return S_OK;
}
dwError = GetLastError();
LogError( "Unable to move file '%S' to '%S' due to %!winerr!, sleeping",
(const WCHAR*) m_TemporaryName, (const WCHAR*)m_LocalName, dwError );
// Only try three times if this isn't a file sharing violation.
if ((i>3) && (dwError != ERROR_SHARING_VIOLATION))
{
break;
}
if (i<dwMaxRetries-1) // Don't hang the thread for the last sleep...
{
Sleep( dwSleepMSec );
}
dwSleepMSec *= 2;
}
LogError( "Timed out renaming temp file" );
// Attemp to reset the attributes on the file.
SetFileAttributes( (const WCHAR*)m_TemporaryName, dwFileAttributes );
return HRESULT_FROM_WIN32( dwError );
}
HRESULT
CFile::DeleteTempFile()
{
if (!DeleteFile( m_TemporaryName ))
{
DWORD s = GetLastError();
LogWarning("error %!winerr! deleting %S", s, LPCWSTR( m_TemporaryName ));
return HRESULT_FROM_WIN32( s );
}
return S_OK;
}
void
CFile::ChangedOnServer()
{
LogError( "deleting '%S' since it was changed on the server", (const WCHAR*)m_TemporaryName );
DeleteTempFile();
m_BytesTransferred = 0;
m_Completed = false;
m_BytesTotal = BG_SIZE_UNKNOWN;
}
void
CFile::DiscoverBytesTotal(
HANDLE Token,
const PROXY_SETTINGS & ProxySettings,
const CCredentialsContainer * Credentials,
QMErrInfo & ErrorInfo
)
{
UINT64 FileSize;
FILETIME FileTime;
LogDl( "Retrieving remote infomation for %ls", m_RemoteName );
g_Manager->m_TaskScheduler.UnlockWriter();
g_Manager->m_pPD->GetRemoteFileInformation(
Token,
m_RemoteName,
&FileSize,
&FileTime,
&ErrorInfo,
&ProxySettings,
Credentials,
m_Job->GetHostId()
);
{
bool fCancelled = false;
while (g_Manager->m_TaskScheduler.LockWriter() )
{
g_Manager->m_TaskScheduler.AcknowledgeWorkItemCancel();
fCancelled = true;
}
if (fCancelled)
{
ErrorInfo.result = QM_FILE_ABORTED;
}
}
LogDl("result was %d", ErrorInfo.result );
switch (ErrorInfo.result)
{
case QM_FILE_DONE: m_BytesTotal = FileSize; break;
case QM_IN_PROGRESS: ASSERT( 0 ); break;
case QM_SERVER_FILE_CHANGED: ChangedOnServer(); break;
}
}
HANDLE
CFile::OpenLocalFileForUpload()
{
auto_HANDLE<NULL> hFile = CreateFile( m_LocalName,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
0,
NULL );
if ( hFile.get() == INVALID_HANDLE_VALUE )
{
THROW_HRESULT( HRESULT_FROM_WIN32( GetLastError() ));
}
BY_HANDLE_FILE_INFORMATION info;
if (!GetFileInformationByHandle( hFile.get(), &info ))
{
THROW_HRESULT( HRESULT_FROM_WIN32( GetLastError() ));
}
if (m_LocalFileTime != info.ftLastWriteTime)
{
THROW_HRESULT( BG_E_LOCAL_FILE_CHANGED );
}
LARGE_INTEGER Offset;
Offset.QuadPart = m_BytesTransferred;
if (!SetFilePointerEx( hFile.get(), Offset, NULL, FILE_BEGIN ))
{
THROW_HRESULT( HRESULT_FROM_WIN32( GetLastError() ));
}
return hFile.release();
}
HRESULT
CFile::SetLocalFileTime( FILETIME Time )
/*
This is used as a special case to set the file time of a zero-length file, since
the normal download path is skipped.
*/
{
try
{
auto_HANDLE<NULL> hFile = CreateFile( m_TemporaryName,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_HIDDEN,
NULL );
if ( hFile.get() == INVALID_HANDLE_VALUE )
{
ThrowLastError();
}
if (!SetFileTime( hFile.get(), &Time, &Time, &Time ) )
{
ThrowLastError();
}
return S_OK;
}
catch ( ComError err )
{
LogError( "error %x setting creation time", err.Error() );
return err.Error();
}
}
//------------------------------------------------------------------------
typedef CLockedReadPointer<CFile, BG_JOB_READ> CLockedFileReadPointer;
typedef CLockedWritePointer<CFile, BG_JOB_WRITE> CLockedFileWritePointer;
CFileExternal::CFileExternal(
CFile * file,
CJobExternal * JobExternal
)
: m_ServiceInstance( g_ServiceInstance ),
m_refs(1),
m_file( file ),
m_JobExternal( JobExternal )
{
m_JobExternal->AddRef();
}
CFileExternal::~CFileExternal()
{
SafeRelease( m_JobExternal );
}
STDMETHODIMP
CFileExternal::QueryInterface(
REFIID iid,
void** ppvObject
)
{
BEGIN_EXTERNAL_FUNC
HRESULT Hr = S_OK;
*ppvObject = NULL;
if ((iid == IID_IUnknown) || (iid == IID_IBackgroundCopyFile))
{
*ppvObject = (IBackgroundCopyFile *)this;
AddRef();
}
else
{
Hr = E_NOINTERFACE;
}
LogRef( "iid %!guid!, hr %x", &iid, Hr );
return Hr;
END_EXTERNAL_FUNC
}
ULONG
CFileExternal::AddRef()
{
BEGIN_EXTERNAL_FUNC
ULONG newrefs = InterlockedIncrement(&m_refs);
LogRef("refs %d", newrefs);
return newrefs;
END_EXTERNAL_FUNC
}
ULONG
CFileExternal::Release()
{
BEGIN_EXTERNAL_FUNC
ULONG newrefs = InterlockedDecrement(&m_refs);
LogRef("refs %d", newrefs);
if (0 == newrefs)
{
delete this;
}
return newrefs;
END_EXTERNAL_FUNC
}
STDMETHODIMP
CFileExternal::GetRemoteNameInternal(
/* [out] */ LPWSTR *pVal
)
{
CLockedFileReadPointer LockedPointer(m_file);
LogPublicApiBegin( "pVal %p", pVal );
HRESULT Hr = LockedPointer.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedPointer->GetRemoteName( pVal );
}
LogPublicApiEnd( "pVal %p(%S) ", pVal, SUCCEEDED(Hr) ? *pVal : L"?" );
return Hr;
}
STDMETHODIMP
CFileExternal::GetLocalNameInternal(
/* [out] */ LPWSTR *pVal
)
{
CLockedFileReadPointer LockedPointer(m_file);
LogPublicApiBegin( "pVal %p", pVal );
HRESULT Hr = LockedPointer.ValidateAccess();
if (SUCCEEDED(Hr))
{
Hr = LockedPointer->GetLocalName( pVal );
}
LogPublicApiEnd( "pVal %p(%S) ", pVal, SUCCEEDED(Hr) ? *pVal : L"?" );
return Hr;
}
STDMETHODIMP
CFileExternal::GetProgressInternal(
/* [out] */ BG_FILE_PROGRESS *pVal
)
{
CLockedFileReadPointer LockedPointer(m_file);
LogPublicApiBegin( "pVal %p", pVal );
HRESULT Hr = LockedPointer.ValidateAccess();
if (SUCCEEDED(Hr))
{
LockedPointer->GetProgress( pVal );
}
LogPublicApiEnd( "pVal %p", pVal );
return Hr;
}