|
|
//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1995 - 2000.
//
// File: CIOPLOCK.CXX
//
// Contents: Oplock support for filtering documents
//
// Classes: CFilterOplock
//
// History: 03-Jul-95 DwightKr Created.
//
//----------------------------------------------------------------------------
#include <pch.cxx>
#pragma hdrstop
#include <ntopen.hxx>
#include <cioplock.hxx>
const WCHAR * g_aOplockException[] = { L"asf", L"asx", L"avi", L"m1v", L"mov", L"mp2", L"mp3", L"mpa", L"mpe", L"mpeg", L"mpg", L"mpv2", L"qt", L"wma", L"wmv", L"wvx", };
const unsigned g_cOplockException = sizeof g_aOplockException / sizeof g_aOplockException[0];
//+---------------------------------------------------------------------------
//
// Function: IsOplockException
//
// Synopsis: Checks if the extension on a file makes us want to not take
// the oplock because filtering properties will open the file
// in an incompatible mode.
//
// Arguments: [pwcPath] -- The path of the file to check
//
// History: 1-Feb-01 dlee Created
//
//----------------------------------------------------------------------------
BOOL IsOplockException( WCHAR const * pwcPath ) { WCHAR const * pwc = wcsrchr( pwcPath, L'.' );
if ( 0 == pwc ) return FALSE;
pwc++;
for ( unsigned i = 0; i < g_cOplockException; i++ ) if ( !wcscmp( pwc, g_aOplockException[i] ) ) return TRUE;
return FALSE; } //IsOplockException
//+---------------------------------------------------------------------------
//
// Member: CFilterOplock::CFilterOplock
//
// Synopsis: Takes an oplock on the file object specified
//
// Arguments: [wcsFileName] -- name of file to take oplock on
//
// History: 03-Jul-95 DwightKr Created
// 21-Feb-96 DwightKr Add support for OPLocks on NTFS
// directories
//
// Notes: NTFS doesn't support oplocks on directories. Change
// this routine's expectations when directory oplocks
// are supported.
//
//----------------------------------------------------------------------------
CFilterOplock::CFilterOplock( const CFunnyPath & funnyFileName, BOOL fTakeOplock ) : _hFileOplock(INVALID_HANDLE_VALUE), _hFileNormal(INVALID_HANDLE_VALUE), _hLockEvent(INVALID_HANDLE_VALUE), _funnyFileName( funnyFileName ), _fWriteAccess( TRUE ) { const BOOL fDrivePath = ( !funnyFileName.IsRemote() && funnyFileName.GetActualLength() > 2 );
HANDLE handle = INVALID_HANDLE_VALUE;
NTSTATUS Status; SHandle xLockHandle; // save handle in a smart pointer
fTakeOplock = fTakeOplock && fDrivePath;
BOOL fAppendBackSlash = FALSE;
// For volume \\?\D:, NtQueryInformationFile fails with the following
// error: 0xC0000010L - STATUS_INVALID_DEVICE_REQUEST. Need to append a \,
// to make it work. We need to append the '\'only in case of volume path.
if ( !funnyFileName.IsRemote() && 2 == funnyFileName.GetActualLength() ) { Win4Assert( L':' == (funnyFileName.GetActualPath())[1] ); ((CFunnyPath&)funnyFileName).AppendBackSlash(); // override const
fAppendBackSlash = TRUE; }
//
// Major work-around here. The shell IPropertySetStorage routines open
// these filetypes through wmi which open the file GENERIC_WRITE. This
// will deadlock the filter thread with itself since it's incompatible
// with the oplock.
//
if ( fTakeOplock && IsOplockException( funnyFileName.GetPath() ) ) fTakeOplock = FALSE;
//
// Make this case work (for SPS): \\.\backofficestorage...
// Funyypath treats it as a remote path. Detect this and change
// \\?\UNC\.\backoffice
// into
// \\?\UN\\.\backoffice
// then pass this into Rtl: \\.\backoffice
//
WCHAR *pwcPath = (WCHAR *) funnyFileName.GetPath();
BOOL fCtoBack = FALSE;
if ( pwcPath[8] == L'.' && pwcPath[6] == L'C' ) { pwcPath[6] = L'\\'; pwcPath = pwcPath + 6; fCtoBack = TRUE; }
UNICODE_STRING uScope;
if ( !RtlDosPathNameToNtPathName_U( pwcPath, &uScope, 0, 0 ) ) { ciDebugOut(( DEB_ERROR, "Error converting %ws to Nt path\n", funnyFileName.GetPath() )); THROW( CException(STATUS_INSUFFICIENT_RESOURCES) ); }
if ( fAppendBackSlash ) { ((CFunnyPath&)funnyFileName).RemoveBackSlash(); // override const
}
if ( fCtoBack ) pwcPath[6] = L'C';
IO_STATUS_BLOCK IoStatus; OBJECT_ATTRIBUTES ObjectAttr;
InitializeObjectAttributes( &ObjectAttr, // Structure
&uScope, // Name
OBJ_CASE_INSENSITIVE, // Attributes
0, // Root
0 ); // Security
//
// Don't try to take oplocks on UNC shares. Gibraltar doesn't support
// redirected network drives. So, don't worry about redirected network
// drives. Testing if a drive letter is a network drive is not very
// cheap.
//
BOOL fOplockNotSupported = !fTakeOplock; SHandle xOplockHandle(INVALID_HANDLE_VALUE);
if ( fTakeOplock ) { ULONG modeAccess = FILE_READ_ATTRIBUTES; ULONG modeShare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; ULONG modeCreate = FILE_OPEN; ULONG modeAttribute = FILE_ATTRIBUTE_NORMAL; ULONG modeCreateOptions = FILE_RESERVE_OPFILTER;
Status = NtCreateFile( &handle, // Handle
modeAccess, // Access
&ObjectAttr, // Object Attributes
&IoStatus, // I/O Status block
0, // Allocation Size
modeAttribute, // File Attributes
modeShare, // File Sharing
modeCreate, // Create Disposition
modeCreateOptions, // Create Options
0, // EA Buffer
0 ); // EA Buffer Length
if ( NT_SUCCESS(Status) ) Status = IoStatus.Status;
//
// Note: Keep uScope.Buffer around for the other open which occurs
// below.
//
if ( !NT_SUCCESS(Status) ) { //
// Failed to get oplock. Continue on though; this may have been
// due to a failure to get an oplock on a directory.
//
ciDebugOut(( DEB_IWARN, "CFilterOplock failed to NtCreateFile(%ws) status = 0x%x\n", funnyFileName.GetPath(), Status )); } else { //
// Oplock open succeeded. Try to actually get the oplock.
//
xOplockHandle.Set(handle); // Save in the smart pointer
//
// Create event (signalled on oplock break)
//
Status = NtCreateEvent( &handle, EVENT_ALL_ACCESS, 0, NotificationEvent, TRUE );
if (! NT_SUCCESS(Status) ) { ciDebugOut(( DEB_ERROR, "Error creating oplock event\n" )); THROW( CException(Status) ); }
xLockHandle.Set( handle );
_IoStatus.Status = STATUS_SUCCESS; _IoStatus.Information = 0;
Status = NtFsControlFile( xOplockHandle.Get(), xLockHandle.Get(), 0, 0, &_IoStatus, FSCTL_REQUEST_FILTER_OPLOCK, 0, 0, 0, 0 );
if ( !NT_SUCCESS(Status) ) { if ( STATUS_INVALID_DEVICE_REQUEST == Status ) fOplockNotSupported = TRUE;
ciDebugOut(( DEB_IWARN, "CFilterOplock failed to NtFsControlFile(%ws) status = 0x%x\n", funnyFileName.GetPath(), Status )); NtClose( xOplockHandle.Acquire() ); NtClose( xLockHandle.Acquire() ); } }
#if CIDBG == 1
if ( ( ! NT_SUCCESS(Status) ) && fDrivePath ) { //
// If we failed to take a filter oplock this is okay as long
// as either:
// 1. the file type is a directory and the FS is NTFS.
// 2. the file system does not support filter oplocks.
//
WCHAR wcsDrive[4]; wcsncpy( wcsDrive, funnyFileName.GetActualPath(), 3 ); wcsDrive[3] = L'\0'; Win4Assert( wcsDrive[1] == L':' && wcsDrive[2] == L'\\' );
WCHAR wcsFileSystemName[10]; wcsFileSystemName[0] = 0;
if ( !GetVolumeInformation( wcsDrive, 0,0,0,0,0, wcsFileSystemName, sizeof wcsFileSystemName / sizeof (WCHAR) ) ) { THROW( CException() ); }
if ( _wcsicmp(wcsFileSystemName, L"NTFS") == 0 ) { Win4Assert( !fOplockNotSupported && _wcsicmp(wcsFileSystemName, L"NTFS") == 0 ); }
}
#endif // CIDBG == 1
}
//
// Now open the file handle for normal access to the file.
// The oplock handle should only be used for the oplock.
//
InitializeObjectAttributes( &ObjectAttr, // Structure
&uScope, // Name
OBJ_CASE_INSENSITIVE, // Attributes
0, // Root
0 ); // Security
ULONG modeAccess = READ_CONTROL | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES; ULONG modeShare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; ULONG modeCreate = FILE_OPEN; ULONG modeAttribute = FILE_ATTRIBUTE_NORMAL; ULONG modeCreateOptions = 0;
Status = NtCreateFile( &handle, // Handle
modeAccess, // Access
&ObjectAttr, // Object Attributes
&IoStatus, // I/O Status block
0, // Allocation Size
modeAttribute, // File Attributes
modeShare, // File Sharing
modeCreate, // Create Disposition
modeCreateOptions, // Create Options
0, // EA Buffer
0 ); // EA Buffer Length
if ( NT_SUCCESS(Status) ) Status = IoStatus.Status;
if ( STATUS_ACCESS_DENIED == Status ) { // The open failed. Try again without requesting FILE_WRITE_ATTRIBUTE
// access.
_fWriteAccess = FALSE; modeAccess &= ~FILE_WRITE_ATTRIBUTES;
Status = NtCreateFile( &handle, // Handle
modeAccess, // Access
&ObjectAttr, // Object Attributes
&IoStatus, // I/O Status block
0, // Allocation Size
modeAttribute, // File Attributes
modeShare, // File Sharing
modeCreate, // Create Disposition
modeCreateOptions, // Create Options
0, // EA Buffer
0 ); // EA Buffer Length
if ( NT_SUCCESS(Status) ) Status = IoStatus.Status; }
RtlFreeHeap( RtlProcessHeap(), 0, uScope.Buffer );
if ( !NT_SUCCESS( Status ) ) { ciDebugOut(( DEB_IERROR, "CFilterOplock - Error opening %ws as normal file\n", funnyFileName.GetPath() )); QUIETTHROW( CException(Status) ); }
SHandle xNormalHandle( handle );
Status = NtQueryInformationFile( xNormalHandle.Get(), // File handle
&IoStatus, // I/O Status
&_BasicInfo, // Buffer
sizeof _BasicInfo, // Buffer size
FileBasicInformation );
if ( NT_SUCCESS(Status) ) Status = IoStatus.Status;
if ( !NT_SUCCESS(Status) ) { ciDebugOut(( DEB_IERROR, "CFilterOplock - Error 0x%x querying file info for %ws.\n", Status, funnyFileName.GetPath() )); QUIETTHROW( CException(Status) ); }
_fDirectory = (_BasicInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
if ( INVALID_HANDLE_VALUE == xOplockHandle.Get() && ! ( _fDirectory || fOplockNotSupported ) ) { ciDebugOut(( DEB_ITRACE, "CFilterOplock failed to aquire oplock (%ws) %d %d\n", funnyFileName.GetPath(), _fDirectory, fOplockNotSupported ));
QUIETTHROW( CException(STATUS_OPLOCK_BREAK_IN_PROGRESS) ); }
_hLockEvent = xLockHandle.Acquire(); _hFileOplock = xOplockHandle.Acquire(); _hFileNormal = xNormalHandle.Acquire(); }
//+---------------------------------------------------------------------------
//
// Member: CFilterOplock::~CFilterOplock, public
//
// Synopsis: Destructor
//
// History: 03-Jul-95 DwightKr Created
//
//----------------------------------------------------------------------------
CFilterOplock::~CFilterOplock() { if ( INVALID_HANDLE_VALUE != _hFileNormal ) NtClose(_hFileNormal);
//
// Make sure this is the last file handle closed.
//
if ( INVALID_HANDLE_VALUE != _hFileOplock ) NtClose(_hFileOplock);
if ( INVALID_HANDLE_VALUE != _hLockEvent ) {
//
// We MUST wait until the lock event has been completed to
// prevent a race between APC call and the cleanup.
//
NTSTATUS Status = NtWaitForSingleObject( _hLockEvent, FALSE, 0 // Infinite
); NtClose( _hLockEvent ); } }
//+---------------------------------------------------------------------------
//
// Member: CFilterOplock::IsOplockBroken, public
//
// Synopsis: Tests for a broken oplock
//
// History: 03-Jul-95 DwightKr Created
//
//----------------------------------------------------------------------------
BOOL CFilterOplock::IsOplockBroken() const { if ( INVALID_HANDLE_VALUE != _hLockEvent ) { static LARGE_INTEGER li = {0,0};
NTSTATUS Status = NtWaitForSingleObject( _hLockEvent, FALSE, &li );
if ( STATUS_SUCCESS == Status ) { if ( STATUS_NOT_IMPLEMENTED == _IoStatus.Status ) { return FALSE; } else if ( STATUS_OPLOCK_NOT_GRANTED != _IoStatus.Status ) { return TRUE; } } }
return FALSE; }
//+---------------------------------------------------------------------------
//
// Member: CFilterOplock::MaybeSetLastAccessTime, public
//
// Synopsis: Restores the last access time to value on oplock open
//
// Arguments: [ulDelay] -- If file is < [ulDelay] days old, time not written
//
// History: 01-Jul-98 KyleP Created
//
//----------------------------------------------------------------------------
void CFilterOplock::MaybeSetLastAccessTime( ULONG ulDelay ) { ULONGLONG const OneDay = 24i64 * 60i64 * 60i64 * 10000000i64;
if ( _fWriteAccess && !IsOplockBroken() && !_funnyFileName.IsRemote() ) { FILETIME ft;
GetSystemTimeAsFileTime( &ft );
ULONGLONG ftLastAccess = (ULONGLONG) _BasicInfo.LastAccessTime.QuadPart;
if ( ( *(ULONGLONG *)&ft - ftLastAccess ) > (OneDay * ulDelay) ) { do { //
// The normal file handle may have been closed and will need reopening...
//
NTSTATUS Status = STATUS_SUCCESS;
if ( INVALID_HANDLE_VALUE == _hFileNormal ) { Status = CiNtOpenNoThrow( _hFileNormal, _funnyFileName.GetActualPath(), READ_CONTROL | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0 ); }
if ( !NT_SUCCESS(Status) ) { ciDebugOut(( DEB_WARN, "CFilterOplock::MaybeSetLastAccessTime -- Error 0x%x re-opening %ws\n", Status, _funnyFileName.GetActualPath() ));
break; }
//
// Open volume. Needed to mark USN Journal entry.
//
WCHAR wszVolumePath[] = L"\\\\.\\a:"; wszVolumePath[4] = _funnyFileName.GetActualPath()[0];
HANDLE hVolume = CreateFile( wszVolumePath, FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL );
if ( hVolume == INVALID_HANDLE_VALUE ) { ciDebugOut(( DEB_ERROR, "CFilterOplock::MaybeSetLastAccessTime -- Error %u opening volume\n", GetLastError() ));
break; }
SWin32Handle xHandleVolume( hVolume );
IO_STATUS_BLOCK IoStatus; MARK_HANDLE_INFO hiCiIgnore = { USN_SOURCE_AUXILIARY_DATA, hVolume, 0 };
Status = NtFsControlFile( _hFileNormal, NULL, NULL, NULL, &IoStatus, FSCTL_MARK_HANDLE, &hiCiIgnore, sizeof( hiCiIgnore), 0, 0 );
Win4Assert( STATUS_PENDING != Status );
if ( !NT_SUCCESS( Status ) && STATUS_INVALID_DEVICE_REQUEST != Status ) { ciDebugOut(( DEB_ERROR, "CFilterOplock::MaybeSetLastAccessTime -- Error 0x%x marking handle\n", Status ));
break; }
//
// We only want to update last access time.
//
_BasicInfo.CreationTime.QuadPart = -1; _BasicInfo.LastWriteTime.QuadPart = -1; _BasicInfo.ChangeTime.QuadPart = -1; _BasicInfo.FileAttributes = 0;
Status = NtSetInformationFile( _hFileNormal, // File handle
&IoStatus, // I/O Status
&_BasicInfo, // Buffer
sizeof _BasicInfo, // Buffer size
FileBasicInformation );
Win4Assert( STATUS_PENDING != Status );
if ( !NT_SUCCESS( Status ) ) { ciDebugOut(( DEB_ERROR, "CFilterOplock::MaybeSetLastAccessTime -- Error 0x%x resetting last-access time.\n", Status )); break; }
} while( FALSE ); // Polish loop
} // if access time sufficiently stale
} // if oplock broken
}
|