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.
614 lines
22 KiB
614 lines
22 KiB
//+---------------------------------------------------------------------------
|
|
//
|
|
// 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"asx",
|
|
L"mp2",
|
|
L"mp3",
|
|
};
|
|
|
|
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
|
|
}
|