// Copyright (c) 1996-1999 Microsoft Corporation //+------------------------------------------------------------------------- // // Microsoft Windows // // File: fileops.cxx // // Contents: OBJID and file operations // // Classes: // // Functions: // // // // History: 18-Nov-96 BillMo Created. // // Notes: // // Codework: // //-------------------------------------------------------------------------- #include #pragma hdrstop #include "trklib.hxx" #include "mountmgr.h" //+------------------------------------------------------------------- // // Function: ConvertToNtPath, public // // Synopsis: Convert the path in the buffer to an Nt style path, in the // other buffer. // // Arguments: [pwszVolumePath] -- In. Buffer containing Dos style path. // [pwszNtPath] -- Out. Buffer for new path. // [cwcBuf] -- In. Size of buffer in wide chars. // // Returns: Return value is length in characters (not including nul) // of converted path. Zero if not completely converted. // //-------------------------------------------------------------------- unsigned ConvertToNtPath(const TCHAR *ptszVolumePath, WCHAR *pwszNtPath, ULONG cwcBuf) { unsigned i=12; // for \DosDevices\ . WCHAR *pwszWrite = pwszNtPath; ULONG cwcLeft = cwcBuf; BOOL fDone = FALSE; unsigned ret; if (ptszVolumePath[0] == TEXT('\\') && ptszVolumePath[1] == TEXT('\\')) { i+=3; ptszVolumePath++; // i = 15 // ptszVolumePath -----\ . // | // v // \\billmo2\rootd // } if (cwcLeft > i) { memcpy(pwszWrite, L"\\DosDevices\\UNC", i*sizeof(WCHAR)); pwszWrite += i; cwcLeft -= i; while (cwcLeft) { *pwszWrite = (WCHAR) *ptszVolumePath; cwcLeft --; if (*ptszVolumePath == 0) { // we just copied a null fDone = TRUE; break; } else { ptszVolumePath++; pwszWrite++; } } } ret = (fDone ? (unsigned)(pwszWrite - pwszNtPath) : 0); return(ret); } //+---------------------------------------------------------------------------- // // IsLocalObjectVolume // // Determine if the specified volume (specified as a mount manager volume // name) is capable of object IDs (i.e. NTFS5). The tracking service // currently only supports fixed volumes, so this routine really only // returns true for fixed NTFS5 volumes. // // The input is a "volume name", as opposed to a volume device name // (i.e. it has a trailing slash). E.g.: // // \\?\Volume{8baec120-078b-11d2-824b-000000000000}\ // // The proper way to implement this routine is to open the filesystem // on this device, query for its FS attributes, and check for the // supports-object-ids bit. But the ugly side-effect of this is that // we end up opening every volume on the system during system bootup, // including the floppies. So as a workaround, we look up the device // in the object directory first, and only bother to check for the // bit on "\Device\HarddiskVolume" type devices. // //+---------------------------------------------------------------------------- const TCHAR *s_tszHarddiskDevicePrefix = TEXT("\\Device\\HarddiskVolume"); BOOL IsLocalObjectVolume( const TCHAR *ptszVolumeName ) { TCHAR tszTargetSymLink[ MAX_PATH ]; TCHAR tszVolumeDeviceName[ MAX_PATH + 1 ]; ULONG cchVolumeName; DWORD dwFsFlags = 0; // Validate the volume name prefix. //TrkAssert( !_tcsnicmp( TEXT("\\\\?\\"), ptszVolumeName, 4 ) ); /* // For the QueryDosDevice call, we need to strip the "\\?\" // from the beginning of the name. cchVolumeName = _tcslen( &ptszVolumeName[4] ); memcpy( tszVolumeDeviceName, &ptszVolumeName[4], cchVolumeName * sizeof(TCHAR) ); // Also for the QueryDosDevice call, we nee to strip the // whack from the end of the name. TrkAssert( TEXT('\\') == tszVolumeDeviceName[cchVolumeName-1] ); tszVolumeDeviceName[ cchVolumeName - 1 ] = TEXT('\0'); // Query for this device's symlink. if( !QueryDosDevice( tszVolumeDeviceName, tszTargetSymLink, sizeof(tszTargetSymLink)/sizeof(TCHAR) )) { TrkLog(( TRKDBG_MISC, TEXT("Couldn't query %s for symlink in obj dir (%lu)"), tszVolumeDeviceName, GetLastError() )); return FALSE; } TrkLog(( TRKDBG_MISC, TEXT("Volume %s is %s"), ptszVolumeName, tszTargetSymLink )); // Is this a harddisk? I.e., does the symlink have the \Device\HarddiskVolume // prefix? if( _tcsnicmp( tszTargetSymLink, s_tszHarddiskDevicePrefix, _tcslen(s_tszHarddiskDevicePrefix) )) { // No, assume therefore that it's not an object (NTFS5) volume. return FALSE; } // Otherwise, is it a fixed harddisk? else if( DRIVE_FIXED != GetDriveType(ptszVolumeName) ) // No - we don't currently handle removeable media. return FALSE; */ if( DRIVE_FIXED != GetDriveType(ptszVolumeName) ) return FALSE; // Finally, check to see if it supports object IDs if( GetVolumeInformation(ptszVolumeName, NULL, 0, NULL, NULL, &dwFsFlags, NULL, 0 ) && (dwFsFlags & FILE_SUPPORTS_OBJECT_IDS) ) { // Yes, it's a fixed harddisk that supports OIDs return TRUE; } else // It's a fixed harddisk, but it doesn't supports // OIDs (it's probably FAT). return FALSE; } #if 0 BOOL IsLocalObjectVolume( const TCHAR *ptszVolumeDeviceName ) { BOOL fObjectIdCapable = FALSE; TCHAR tszVol[4]; TCHAR tszRootOfVolume[MAX_PATH+1]; DWORD dw; DWORD dwFsFlags; UINT DriveType; _tcscpy( tszRootOfVolume, ptszVolumeDeviceName ); _tcscat( tszRootOfVolume, TEXT("\\") ); // Get the drive type DriveType = GetDriveType(tszRootOfVolume); // Return TRUE if the drive type is fixed, and if // the filesystem attribute bit is set that indicates // that object IDs are supported. return ( ( DRIVE_FIXED == DriveType /*|| DRIVE_REMOVABLE == DriveType*/ ) && GetVolumeInformation(tszRootOfVolume, NULL, 0, NULL, NULL, &dwFsFlags, NULL, 0 ) && (dwFsFlags & FILE_SUPPORTS_OBJECT_IDS) ); } #endif //+------------------------------------------------------------------- // // Function: MapLocalPathToUNC // // Synopsis: Convert a volume-relative path to a UNC path. // Since there could be multiple UNC paths which cover // this local path, that which provides greatest // coverage & access will be returns (e.g., "C:\" // covers more than "C:\Docs". // // Arguments: [tszLocalPath] (in) // A local path, including the drive letter. // [ptszUNC] (out) // An equivalent UNC path which provides the // greatest access. // [cbMaxUnc] (in) // The size of the buffer pointed to be ptszUNC. // // Returns: [HRESULT] // // Exceptions: None // //-------------------------------------------------------------------- HRESULT MapLocalPathToUNC( RPC_BINDING_HANDLE IDL_handle, const TCHAR *ptszLocalPath, TCHAR *ptszUNC, ULONG cbMaxUnc ) { // ----- // Locals // ------ HRESULT hr = S_OK; // Return value CShareEnumerator cShareEnum; ULONG ulBestMerit; TCHAR tszBestShare[ MAX_PATH + 1 ]; TCHAR tszBestPath[ MAX_PATH + 1 ]; ULONG cchBestPath = 0; ULONG cch; // ---------- // Initialize // ---------- __try { // Open an enumeration of the disk shares on this machine. // If we early-exit due to an exception, it will clean itself up. // NOTE: The ptszLocalPath is only provided until we can fix // GetAccessLevel so that it doesn't have to do opens. cShareEnum.Initialize( IDL_handle ); ulBestMerit = cShareEnum.GetMinimumMerit() - 1; // -------------------- // Enumerate the shares // -------------------- while( cShareEnum.Next() ) { // If the name or path is too long, skip it now if( _tcslen(cShareEnum.GetShareName()) > MAX_PATH || _tcslen(cShareEnum.GetSharePath()) > MAX_PATH ) { continue; } // Does this share cover the local path? if( cShareEnum.CoversDrivePath( ptszLocalPath )) { // Is this a better share than anything we've seen so far? if( cShareEnum.GetMerit() > ulBestMerit ) { // We have a new best share. We'll hang on to both // the share and it's path. _tcscpy( tszBestShare, cShareEnum.GetShareName() ); _tcscpy( tszBestPath, cShareEnum.GetSharePath() ); ulBestMerit = cShareEnum.GetMerit(); cchBestPath = cShareEnum.QueryCCHSharePath(); } } // if( cenumShares.QueryCCHSharePath() < cchBestPath ) } // while( cenumShares.Next() ) // Did we find a share which encompasses the file? if( 0 == cchBestPath ) { hr = HRESULT_FROM_WIN32( ERROR_PATH_NOT_FOUND ); goto Exit; } // ------------------- // Create the UNC path // ------------------- // Leave room for a terminator cbMaxUnc--; // Get the machine name cch = _tcslen( cShareEnum.GetMachineName() ); if( cch * sizeof(WCHAR) > cbMaxUnc ) TrkRaiseWin32Error(ERROR_PATH_NOT_FOUND); _tcscpy( ptszUNC, cShareEnum.GetMachineName() ); cbMaxUnc -= cch * sizeof(WCHAR); // Add the share name cch = _tcslen(tszBestShare) + 1; if( cch * sizeof(WCHAR) > cbMaxUnc ) TrkRaiseWin32Error(ERROR_PATH_NOT_FOUND); _tcscat( ptszUNC, TEXT("\\") ); _tcscat( ptszUNC, tszBestShare ); cbMaxUnc -= cch * sizeof(WCHAR); // Add a whack if necessary before the path if ( ptszLocalPath[ cchBestPath ] != TEXT('\\') ) { if( cbMaxUnc/sizeof(WCHAR) < 2 ) TrkRaiseWin32Error(ERROR_PATH_NOT_FOUND); _tcscat( ptszUNC, TEXT("\\") ); cbMaxUnc -= sizeof(WCHAR); } // Finally, add on the local path cch = _tcslen( &ptszLocalPath[cchBestPath] ); if( cch * sizeof(WCHAR) > cbMaxUnc ) TrkRaiseWin32Error(ERROR_PATH_NOT_FOUND); _tcscat( ptszUNC, &ptszLocalPath[ cchBestPath ] ); hr = S_OK; } // __try __except( BreakOnDebuggableException() ) { hr = GetExceptionCode(); } // ---- // Exit // ---- Exit: if( FAILED(hr) ) { TrkLog(( TRKDBG_ERROR, TEXT("MapLocalPathToUNC returned hr=%08x"), hr )); } return( hr ); } // MapLocalPathToUNC //+------------------------------------------------------------------- // // Function: OpenVolume, public // // [ptszVolumeDeviceName] is a Win32 name for a volume in the NT // namespace, *without* the trailing whack. E.g. // // \\.\A: // // if you append a whack on the end of this, it opens the root // of the volume, not the volume itself. // //-------------------------------------------------------------------- NTSTATUS OpenVolume( const TCHAR *ptszVolumeDeviceName, HANDLE * phVolume ) { NTSTATUS status; FILE_FS_DEVICE_INFORMATION DeviceInfo; IO_STATUS_BLOCK Iosb; HANDLE hDirect = NULL; // First, open the file in direct mode (by only opening it for // file_read_attributes). This will open the volume but not cause // any filesystem to be loaded. This was done for the following scenario: // A volume gets dismounted and goes offline for some reason. Trkwks // gets the dismount notification and closes its handles. Something // attempt to open the volume, and IO loads the RAW filesystem (if no other // filesystem can be loaded, IO always loads the rawfs). This causes a // mount notification. Trkwks gets this mount notification and // tries to reopen its handles. It can open the volume handle, but it's // just to the rawfs. It tries to open the oid index, but IO returns an // invalid parameter error. Trkwks then closes all of its handles again, // including the volume handle. When all handles are closed on rawfs in this // way, it automatically dismounts (without sending a dismount notification). // The problem is, when trkwks opened the volume handle, it mounted the // volume (rawfs) and caused a new mount notification, which it now receives // and tries to open its handles again. In this way trkwks goes into an // infinite loop. // // The solution is to open the volume direct, and see if it's really mounted. // If not, don't try to open the volume. status = TrkCreateFile( ptszVolumeDeviceName, FILE_READ_ATTRIBUTES, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, &hDirect ); if( !NT_SUCCESS(status) ) goto Exit; // Check for the current mount status. status = NtQueryVolumeInformationFile( hDirect, &Iosb, &DeviceInfo, sizeof(DeviceInfo), FileFsDeviceInformation ); NtClose( hDirect ); if( !NT_SUCCESS(status) ) goto Exit; if( !(FILE_DEVICE_IS_MOUNTED & DeviceInfo.Characteristics) ) { // This volume isn't currently mounted, and we don't want to be // the ones to mount it. TrkLog(( TRKDBG_WARNING, TEXT("Attempted to open dismounted volume (%s)"), ptszVolumeDeviceName )); status = STATUS_VOLUME_DISMOUNTED; goto Exit; } // The filesystem is already mounted, so it's OK for us to // open our handle. status = TrkCreateFile( ptszVolumeDeviceName, FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, phVolume ); Exit: TrkAssert(NT_SUCCESS(status) || *phVolume == NULL); return(status); } //+---------------------------------------------------------------------------- // // Function: CheckVolumeWriteProtection // // Check the filesystem attributes on a volume to see if it's write- // protected. // //+---------------------------------------------------------------------------- NTSTATUS CheckVolumeWriteProtection( const TCHAR *ptszVolumeDeviceName, BOOL *pfWriteProtected ) { NTSTATUS status; HANDLE hVolume = NULL; IO_STATUS_BLOCK Iosb; FILE_FS_ATTRIBUTE_INFORMATION FsAttrs; status = OpenVolume( ptszVolumeDeviceName, &hVolume ); if( !NT_SUCCESS(status) ) { TrkLog(( TRKDBG_WARNING, TEXT("Couldn't open volume - 0x%08x (%s)"), status, ptszVolumeDeviceName )); goto Exit; } status = NtQueryVolumeInformationFile( hVolume, &Iosb, &FsAttrs, sizeof(FsAttrs), FileFsAttributeInformation ); if( !NT_SUCCESS(status) && STATUS_BUFFER_OVERFLOW != status) { TrkLog(( TRKDBG_WARNING, TEXT("Couldn't query fs attrs - 0x%08x (%s)"), status, ptszVolumeDeviceName )); goto Exit; } if( FILE_READ_ONLY_VOLUME & FsAttrs.FileSystemAttributes ) *pfWriteProtected = TRUE; else *pfWriteProtected = FALSE; status = STATUS_SUCCESS; Exit: if( NULL != hVolume ) NtClose( hVolume ); return status; } //+------------------------------------------------------------------- // // Function: MapVolumeDeviceNameToIndex // // Map a volume device name, as defined by the mount manager, to a // zero-relative index, where 0 represents 'A:'. A "volume device name" // is e.g. // // \\?\Volume{96765fc3-9c72-11d1-b93d-000000000000} // // a "volume name" is the volume device name post-pended with a // whack (it is in this form that the mount manager returns the name). // //+------------------------------------------------------------------- /* LONG MapVolumeDeviceNameToIndex( TCHAR *ptszVolumeDeviceName ) { // BUGBUG: Rewrite this to use IOCTL_MOUNTMGR_QUERY_POINTS TCHAR tszVolumeNameOfCaller[ CCH_MAX_VOLUME_NAME + 1 ]; TCHAR tszVolumeNameForRoot[ CCH_MAX_VOLUME_NAME + 1 ]; TCHAR tszRoot[] = TEXT("*:\\"); // Convert the caller's volume name to a volume device name. _tcscpy( tszVolumeNameOfCaller, ptszVolumeDeviceName ); _tcscat( tszVolumeNameOfCaller, TEXT("\\") ); // Loop through all the possible drive letters, trying to find the // caller's volume name. for( LONG iVol = 0; iVol < NUM_VOLUMES; iVol++ ) { tszRoot[0] = TEXT('A') + iVol; if( GetVolumeNameForVolumeMountPoint( tszRoot, tszVolumeNameForRoot, sizeof(tszVolumeNameForRoot) )) { // We have a real volume with a name. See if it's the name we seek. if( 0 == _tcscmp( tszVolumeNameForRoot, tszVolumeNameOfCaller )) return( iVol ); } } return( -1 ); } */ LONG MapVolumeDeviceNameToIndex( TCHAR *ptszVolumeDeviceName ) { HANDLE hMountManager = INVALID_HANDLE_VALUE; BYTE MountPointBuffer[ sizeof(MOUNTMGR_MOUNT_POINT) + MAX_PATH ]; PMOUNTMGR_MOUNT_POINT pMountPoint = (PMOUNTMGR_MOUNT_POINT) MountPointBuffer; BYTE MountPointsBuffer[ MAX_PATH ]; PMOUNTMGR_MOUNT_POINTS pMountPoints = (PMOUNTMGR_MOUNT_POINTS) MountPointsBuffer; BOOL fQuerySuccessful = FALSE; ULONG cbMountPoints = 0; ULONG cbVolumeDeviceName; LONG iVol = -1; __try { // Open the mount manager. hMountManager = CreateFileW( MOUNTMGR_DOS_DEVICE_NAME, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, INVALID_HANDLE_VALUE ); if( INVALID_HANDLE_VALUE == hMountManager ) { TrkLog(( TRKDBG_ERROR, TEXT("Couldn't open MountManager") )); TrkRaiseLastError(); } // Initialize the input (pMountPoint) pMountPoint = (PMOUNTMGR_MOUNT_POINT) MountPointBuffer; memset(pMountPoint, 0, sizeof(MountPointBuffer) ); cbVolumeDeviceName = sizeof(TCHAR) * _tcslen(ptszVolumeDeviceName); // Load the name of the device for which we wish to query. We convert // this from Win32 "\\?\" form to NT "\??\" form. pMountPoint->DeviceNameOffset = sizeof(MOUNTMGR_MOUNT_POINT); pMountPoint->DeviceNameLength = cbVolumeDeviceName; _tcscpy( (TCHAR*)( MountPointBuffer + pMountPoint->DeviceNameOffset ), TEXT("\\??") ); _tcscat( (TCHAR*)( MountPointBuffer + pMountPoint->DeviceNameOffset ), &ptszVolumeDeviceName[3] ); // Query the mount manager for info on this device. ULONG cQueryAttempts = 0; // Guarantee no infinite loop. fQuerySuccessful = FALSE; cbMountPoints = sizeof(MountPointsBuffer); while( !fQuerySuccessful ) { // Check for an infinite loop. if( cQueryAttempts > 100 ) { TrkLog(( TRKDBG_ERROR, TEXT("Failed IOCTL_MOUNTMGR_QUERY_POINTS (%lu, loop detect)"), GetLastError() )); TrkRaiseLastError(); } // Query the mount manager fQuerySuccessful = DeviceIoControl( hMountManager, IOCTL_MOUNTMGR_QUERY_POINTS, pMountPoint, sizeof(MountPointBuffer), pMountPoints, cbMountPoints, &cbMountPoints, NULL); // Did it work? if( fQuerySuccessful ) // Yes, we got the info. break; // Otherwise, do we need a bigger out-buf? else if( ERROR_MORE_DATA == GetLastError() ) { // Yes, the size of the necessary out-buf is at // the beginning of the out-buf we provided. cbMountPoints = pMountPoints->Size; // The initial guess buffer is on the stack, not heap. if( (PMOUNTMGR_MOUNT_POINTS) MountPointsBuffer != pMountPoints ) delete [] pMountPoints; pMountPoints = (PMOUNTMGR_MOUNT_POINTS) new BYTE[ cbMountPoints ]; if( NULL == pMountPoints ) { TrkLog(( TRKDBG_ERROR, TEXT("Couldn't alloc pMountPoints") )); TrkRaiseWin32Error( ERROR_NOT_ENOUGH_MEMORY ); } } // Of it's not a more-data error, then there's nothing more we can do. else break; } // while( !fQuerySuccessful ) // Raise if the query failed. if( !fQuerySuccessful ) { TrkLog(( TRKDBG_WARNING, TEXT("Failed IOCTL_MOUNTMGR_QUERY_POINTS (%lu)"), GetLastError() )); TrkRaiseLastError(); } // Loop through the returned mount points. There should be 2: one of // the form "\??\Volume{8baec120-078b-11d2-824b-000000000000}", // and one of the form "\DosDevices\C:" (for the C drive). static const WCHAR wszSymLinkPrefix[] = { L"\\DosDevices\\" }; ULONG cchSymLinkPrefix = sizeof(wszSymLinkPrefix)/sizeof(WCHAR) - 1; for( int i = 0; i < pMountPoints->NumberOfMountPoints; ++i ) { PMOUNTMGR_MOUNT_POINT pOutPoint = &pMountPoints->MountPoints[i]; WCHAR wc; const WCHAR *pwszSymLinkName = (PWCHAR)( (BYTE*)pMountPoints + pOutPoint->SymbolicLinkNameOffset ); if( pOutPoint->SymbolicLinkNameLength/sizeof(WCHAR) >= 14 && 0 == wcsncmp( pwszSymLinkName, wszSymLinkPrefix, cchSymLinkPrefix ) && pOutPoint->UniqueIdLength ) { wc = pwszSymLinkName[ cchSymLinkPrefix ]; if( TEXT('a') <= wc && wc <= TEXT('z') ) { iVol = wc - TEXT('a'); break; } else if( TEXT('A') <= wc && wc <= TEXT('Z') ) { iVol = wc - TEXT('A'); break; } } } } __finally { if( INVALID_HANDLE_VALUE != hMountManager ) CloseHandle( hMountManager ); if( (PMOUNTMGR_MOUNT_POINTS) MountPointsBuffer != pMountPoints && NULL != pMountPoints ) { delete [] pMountPoints; } } return iVol; } //+---------------------------------------------------------------------------- // // IsSystemVolumeInformation // // Is the given volume-relative path under the "System Volume Information" // directory? // // Note: This is hard-coded for now, but will be replaced by a forthcoming // Rtl export. // //+---------------------------------------------------------------------------- BOOL IsSystemVolumeInformation( const TCHAR *ptszPath ) { return 0 == _wcsnicmp( s_tszSystemVolumeInformation, ptszPath, wcslen(s_tszSystemVolumeInformation)/sizeof(WCHAR) ); } //+------------------------------------------------------------------- // // OpenFileById // // Given an NTFS5 object ID, open the file and returns its handle. // //-------------------------------------------------------------------- NTSTATUS OpenFileById( const TCHAR *ptszVolumeDeviceName, const CObjId &oid, ACCESS_MASK AccessMask, ULONG ShareAccess, ULONG AdditionalCreateOptions, HANDLE *ph) { NTSTATUS status; // A buffer for the id-based path. This path is the volume's // path followed by the 16 byte object ID TCHAR tszPath[ MAX_PATH + 1 ] = { TEXT('\0') }; // Init to prevent prefix error. // Parameters for NtCreateFile OBJECT_ATTRIBUTES ObjectAttr; IO_STATUS_BLOCK IoStatus; UNICODE_STRING uPath; PVOID pFreeBuffer = NULL; // Compose a buffer with a Win32-style name with a dummy value where // the object ID will go (use "12345678" for now). It's Win32-style // in that we will pass it to RtlDosPathNameToNtPathName below. TrkAssert( NULL != ptszVolumeDeviceName ); _tcscpy( tszPath, ptszVolumeDeviceName ); _tcscat( tszPath, TEXT("\\") ); _tcscat( tszPath, TEXT("12345678") ); // Convert to the NT path to this volume if( !RtlDosPathNameToNtPathName_U( tszPath, &uPath, NULL, NULL )) { status = STATUS_OBJECT_NAME_INVALID; goto Exit; } pFreeBuffer = uPath.Buffer; // Put in the real object ID in place of the "12345678" TrkAssert( oid.Size() == 16 ); oid.SerializeRaw( reinterpret_cast(&uPath.Buffer[ (uPath.Length-oid.Size()) / sizeof(uPath.Buffer[0]) ]) ); // And open the file InitializeObjectAttributes( &ObjectAttr, // Structure &uPath, // Name (identifier) OBJ_CASE_INSENSITIVE, // Attributes 0, // Root 0 ); // Security status = NtCreateFile( ph, AccessMask, &ObjectAttr, &IoStatus, NULL, FILE_ATTRIBUTE_NORMAL, ShareAccess, FILE_OPEN, FILE_OPEN_BY_FILE_ID | FILE_OPEN_NO_RECALL | FILE_SYNCHRONOUS_IO_NONALERT | AdditionalCreateOptions, NULL, 0 ); if( !NT_SUCCESS(status) ) { *ph = NULL; goto Exit; } // ---- // Exit // ---- Exit: if( !NT_SUCCESS(status) && status != STATUS_OBJECT_NAME_NOT_FOUND ) { TrkLog(( TRKDBG_MISC, TEXT("OpenFileById returned status=%08X"), status )); } if( NULL != pFreeBuffer ) RtlFreeHeap( RtlProcessHeap(), 0, pFreeBuffer ); return( status ); } // OpenFileById //+---------------------------------------------------------------------- // // Function: SetVolId // // Synopsis: Set the volume ID on a local volume. // // Returns: NTSTATUS // // Note: Setting the volid (and changing a volume's lable) triggers // a GUID_IO_VOLUME_CHANGE PNP device event. // //+---------------------------------------------------------------------------- NTSTATUS SetVolId( const TCHAR *ptszVolumeDeviceName, const CVolumeId &volid ) { NTSTATUS status = STATUS_SUCCESS; UNICODE_STRING usPath; PVOID pFreeBuffer = NULL; HANDLE hVol = NULL; FILE_FS_OBJECTID_INFORMATION file_fs_objectid_information; OBJECT_ATTRIBUTES ObjectAttr; IO_STATUS_BLOCK IoStatus; EnableRestorePrivilege(); // Generate the NT path name to this volume. The VolumeDeviceName has // no trailing whack, so we'll be opening the volume, not the root dir of // the volume. if( !RtlDosPathNameToNtPathName_U( ptszVolumeDeviceName, &usPath, NULL, NULL )) { status = STATUS_OBJECT_NAME_INVALID; goto Exit; } pFreeBuffer = usPath.Buffer; // Fill in an ObjectAttributes for the NtCreateFile request InitializeObjectAttributes( &ObjectAttr, // Structure &usPath, // Name (identifier) OBJ_CASE_INSENSITIVE, // Attributes 0, // Root 0 ); // Security // Open the volume. // You wouldn't think that FILE_SHARE_WRITE would be necessary, but // without it we encounter a STATUS_UNABLE_TO_DELETE_SECTION error. // Also, we must use NtOpenFile; if we use NtCreateFile(...,FILE_OPEN,...), // we get a STATUS_ACCESS_DENIED error on the NtSetVolumeInformationFile // call. status = NtOpenFile( &hVol, SYNCHRONIZE | FILE_GENERIC_READ | FILE_GENERIC_WRITE, &ObjectAttr, &IoStatus, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_SYNCHRONOUS_IO_NONALERT ); if( !NT_SUCCESS(status) ) { hVol = NULL; TrkLog(( TRKDBG_ERROR, TEXT("SetVolId couldn't open the volume %s (status=%08x)"), ptszVolumeDeviceName, status )); goto Exit; } // Set the Volume ID file_fs_objectid_information = volid; status = NtSetVolumeInformationFile( hVol, &IoStatus, &file_fs_objectid_information, sizeof(file_fs_objectid_information), FileFsObjectIdInformation ); if( !NT_SUCCESS(status) ) { TrkLog(( TRKDBG_ERROR, TEXT("SetVolId couldn't set volume ID on volume %s (status=%08x)"), ptszVolumeDeviceName, status )); goto Exit; } // ---- // Exit // ---- Exit: if( NULL != hVol ) NtClose( hVol ); if( NULL != pFreeBuffer ) RtlFreeHeap( RtlProcessHeap(), 0, pFreeBuffer ); return( status ); } //+---------------------------------------------------------------------------- // // Function: TrkCreateFile // // Synopsis: Creates a file using NtCreateFile. // // Arguments: [pwszCompleteDosPath] (in) // The path to open, in 'dos' format as opposed to // NT format (e.g. "\\m\s\f" rather than "\DosDevices\..." // [AccessMask] (in) // Required access. SYNCRHONIZE is requested automatically. // [Attributes] (in) // [ShareAccess] (in) // [CreationDisposition] (in) // [CreateOptions] (in) // [lpSecurityAttributes] (in) // [ph] (out) // The resulting file handle. Always set to NULL when function // is entered. // // Returns: NTSTATUS // //+---------------------------------------------------------------------------- NTSTATUS TrkCreateFile( const WCHAR *pwszCompleteDosPath, ACCESS_MASK AccessMask, ULONG Attributes, ULONG ShareAccess, ULONG CreationDisposition, ULONG CreateOptions, LPSECURITY_ATTRIBUTES lpSecurityAttributes, HANDLE *ph) { NTSTATUS status; // Parameters for NtCreateFile OBJECT_ATTRIBUTES ObjectAttr; IO_STATUS_BLOCK IoStatus; // E.g. "\??\D:\..." UNICODE_STRING uPath; PVOID pFreeBuffer = NULL; *ph = NULL; // ------------- // Open the File // ------------- // Generate the NT path name if( !RtlDosPathNameToNtPathName_U( pwszCompleteDosPath, &uPath, NULL, NULL )) { status = STATUS_OBJECT_NAME_INVALID; goto Exit; } pFreeBuffer = uPath.Buffer; // Set up the ObjectAttributes InitializeObjectAttributes( &ObjectAttr, // Structure &uPath, // Name (identifier) OBJ_CASE_INSENSITIVE, // Attributes 0, // Root 0 ); // Security if( NULL != lpSecurityAttributes ) { ObjectAttr.SecurityDescriptor = lpSecurityAttributes->lpSecurityDescriptor; if ( lpSecurityAttributes->bInheritHandle ) { ObjectAttr.Attributes |= OBJ_INHERIT; } } // Create/Open the file status = NtCreateFile( ph, AccessMask | SYNCHRONIZE, &ObjectAttr, &IoStatus, NULL, Attributes, ShareAccess, CreationDisposition, CreateOptions, // | FILE_SYNCHRONOUS_IO_NONALERT, NULL, // No EA buffer 0 ); if( !NT_SUCCESS(status) ) { *ph = NULL; goto Exit; } // ---- // Exit // ---- Exit: if( NULL != pFreeBuffer ) RtlFreeHeap( RtlProcessHeap(), 0, pFreeBuffer ); return( status ); } // TrkCreateFile //+---------------------------------------------------------------------------- // // Function: FindLocalPath // // Synopsis: Given a volume index and a file ObjectId, return a volume-relative // path to the file, and return the file's Birth ID. The returned // path does not have the drive letter prefix. // // Inputs: [ptszVolumeDeviceName] (in) // The volume on which to search. // [objid] (in) // The ObjectID to search for on this volume. // [pdroidBirth] (out) // If the function is successful, returns the found file's // birth ID. // [ptszLocalPath] (out) // If the function is successful, returns the volume-relative // path (includes the drive letter). // // Returns: [NTSTATUS] // // Exceptions: None. // //+---------------------------------------------------------------------------- NTSTATUS FindLocalPath( IN const TCHAR *ptszVolumeDeviceName, IN const CObjId &objid, OUT CDomainRelativeObjId *pdroidBirth, OUT TCHAR *ptszLocalPath ) { // ------ // Locals // ------ NTSTATUS status; IO_STATUS_BLOCK Iosb; HANDLE hFile = NULL; // Open the file status = OpenFileById( ptszVolumeDeviceName, objid, SYNCHRONIZE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, &hFile); if( !NT_SUCCESS(status) ) { hFile = NULL; goto Exit; } // Get the file's birth ID status = GetBirthId( hFile, pdroidBirth ); if( !NT_SUCCESS(status) ) goto Exit; // Get the volume-relative path status = QueryVolRelativePath( hFile, ptszLocalPath ); if( !NT_SUCCESS(status) ) goto Exit; // When ptszLocalPath is a root directory, no relative path // will be found. i.e. "d:" will remain "d:", instead of "d:\", which is // what we want. We have to put the '\' in here. if(TEXT('\0') == ptszLocalPath[0]) { ptszLocalPath[0] = TEXT('\\'); ptszLocalPath[1] = TEXT('\0'); } // ---- // Exit // ---- Exit: if( NULL != hFile ) NtClose( hFile ); if( !NT_SUCCESS(status) && STATUS_OBJECT_PATH_NOT_FOUND != status && STATUS_OBJECT_NAME_NOT_FOUND != status && STATUS_INVALID_PARAMETER != status // Happens when the objid is really the volid ) { TrkLog(( TRKDBG_MISC, TEXT("FindLocalPath returned status=%08X"), status )); } return( status ); } //+---------------------------------------------------------------------------- // // Function: GetDroids // // Synopsis: Get the current and birth domain-relative object IDs from // a file. If rgoEnum is RGO_GET_OBJECT_ID, then an object ID // will be generated if necessary. If RGO_READ_OBJECT_ID is // specified, and the file doesn't already have an object ID, // then STATUS_OBJECT_NAME_NOT_FOUND will be returned. // // Inputs: [tszFile] (in) // The file who's object ID is to be retrieved. // [pdroidCurrent] (out) // The file's CDomainRelativeObjId. // [pdroidBirth] (out) // The file's birth CDomainRelativeObjId // [rgoEnum] (in) // RGO_READ_OBJECTID => Read the object IDs, return // STATUS_OBJECT_NAME_NOT_FOUND if none exist. // RGO_GET_OBJECTID => Get the object IDs, generating // and setting if necessary. // // Returns: NTSTATUS, outputs zero on error // // Exceptions: None // //+---------------------------------------------------------------------------- NTSTATUS GetDroids( HANDLE hFile, CDomainRelativeObjId *pdroidCurrent, CDomainRelativeObjId *pdroidBirth, RGO_ENUM rgoEnum ) { NTSTATUS status = STATUS_SUCCESS; // Filled by NtQueryInformationFile FILE_OBJECTID_BUFFER fobOID; IO_STATUS_BLOCK Iosb; CDomainRelativeObjId droidCurrent; CDomainRelativeObjId droidBirth; pdroidCurrent->Init(); pdroidBirth->Init(); // ----------------- // Get the Object ID // ----------------- // Use the file handle to get the file's Object ID memset( &fobOID, 0, sizeof(fobOID) ); status = NtFsControlFile( hFile, NULL, NULL, NULL, &Iosb, RGO_READ_OBJECTID == rgoEnum ? FSCTL_GET_OBJECT_ID : FSCTL_CREATE_OR_GET_OBJECT_ID, NULL, 0, &fobOID, // Out buffer sizeof(fobOID) ); // Out buffer size if( !NT_SUCCESS(status) ) goto Exit; // --------------- // Load the Droids // --------------- droidBirth.InitFromFOB( fobOID ); droidBirth.GetVolumeId().Normalize(); status = droidCurrent.InitFromFile( hFile, fobOID ); if( !NT_SUCCESS(status) ) goto Exit; *pdroidCurrent = droidCurrent; *pdroidBirth = droidBirth; // ---- // Exit // ---- Exit: if( !NT_SUCCESS(status) && STATUS_OBJECT_NAME_NOT_FOUND != status // Ignore non-link source && STATUS_INVALID_DEVICE_REQUEST != status // Ignore e.g. FAT && STATUS_VOLUME_NOT_UPGRADED != status // Ignore NTFS4 ) { TrkLog(( TRKDBG_ERROR, TEXT("GetDroids returned ntstatus=%08X"), status )); } return( status ); } // Following is a wrapper that takes a filename, opens the file, // and then calls the above GetDroids with the file handle. NTSTATUS GetDroids( const TCHAR *ptszFile, CDomainRelativeObjId *pdroidCurrent, CDomainRelativeObjId *pdroidBirth, RGO_ENUM rgoEnum ) { HANDLE hFile = NULL; NTSTATUS status = STATUS_SUCCESS; __try { status = TrkCreateFile( ptszFile, SYNCHRONIZE | FILE_READ_ATTRIBUTES, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_OPEN_NO_RECALL | FILE_SYNCHRONOUS_IO_NONALERT, NULL, &hFile ); if( !NT_SUCCESS(status )) { hFile = NULL; goto Exit; } status = GetDroids( hFile, pdroidCurrent, pdroidBirth, rgoEnum ); } __finally { if( NULL != hFile ) NtClose( hFile ); } Exit: return( status ); } //+---------------------------------------------------------------------------- // // Function: SetObjId // // Synopsis: Sets an Object ID (GUID) on a file. // // Inputs: [ptszFile] (in) // The file to be indexed. // [objid] (in) // The ID to put on the file. // [droidBirth] (in) // The BirthId to put on the file. // // Returns: NTSTATUS // // Exceptions: None // //+---------------------------------------------------------------------------- NTSTATUS SetObjId( const HANDLE hFile, CObjId objid, const CDomainRelativeObjId &droidBirth ) { // -------------- // Initialization // -------------- NTSTATUS status = STATUS_SUCCESS; FILE_OBJECTID_BUFFER fobOID; IO_STATUS_BLOCK IoStatus; // Initialize the request buffer memset( &fobOID, 0, sizeof(fobOID) ); droidBirth.SerializeRaw( fobOID.ExtendedInfo ); objid.SerializeRaw( fobOID.ObjectId ); // Send the FSCTL status = NtFsControlFile( hFile, NULL, NULL, NULL, &IoStatus, FSCTL_SET_OBJECT_ID, &fobOID, sizeof(fobOID), NULL, // Out buffer 0); // Out buffer size if( !NT_SUCCESS(status) ) goto Exit; // ---- // Exit // ---- Exit: if( !NT_SUCCESS(status) ) { TrkLog(( TRKDBG_ERROR, TEXT("SetObjId returned ntstatus=%08X"), status )); } return( status ); } NTSTATUS SetObjId( const TCHAR *ptszFile, CObjId objid, const CDomainRelativeObjId &droidBirth ) { NTSTATUS status = STATUS_SUCCESS; HANDLE hFile = NULL; __try { // Setting the object ID requires restore privelege, but no // file access. EnableRestorePrivilege(); status = TrkCreateFile( ptszFile, FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_NO_RECALL | FILE_SYNCHRONOUS_IO_NONALERT, NULL, &hFile ); if( !NT_SUCCESS(status )) { hFile = NULL; goto Exit; } status = SetObjId( hFile, objid, droidBirth ); } __finally { if( NULL != hFile ) NtClose( hFile ); } Exit: return( status ); } //+---------------------------------------------------------------------------- // // Function: MakeObjIdReborn // // Synopsis: Resets the birth ID on a file to it's current location. // // Inputs: [hFile] // The handle of the file to delete the object id of. // // Returns: NTSTATUS // // Exceptions: None // //+---------------------------------------------------------------------------- NTSTATUS MakeObjIdReborn(const TCHAR *ptszVolumeDeviceName, const CObjId &objid) { // -------------- // Initialization // -------------- NTSTATUS status = STATUS_SUCCESS; HANDLE hFile = NULL; IO_STATUS_BLOCK IoStatus; // Open the file EnableRestorePrivilege(); status = OpenFileById(ptszVolumeDeviceName, objid, SYNCHRONIZE | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN_FOR_BACKUP_INTENT, // for FSCTL_DELETE_OBJECT_ID &hFile); if( !NT_SUCCESS(status) ) { hFile = NULL; TrkLog(( (STATUS_SHARING_VIOLATION == status || STATUS_ACCESS_DENIED == status) ? TRKDBG_MISC : TRKDBG_ERROR, TEXT("Couldn't make born again objid (%s) on %s, failed open (%08x)"), (const TCHAR*)CDebugString(objid), ptszVolumeDeviceName, status )); goto Exit; } // Clear the file's birth ID status = MakeObjIdReborn( hFile ); if( !NT_SUCCESS(status) && STATUS_OBJECT_NAME_NOT_FOUND != status ) { TrkLog(( TRKDBG_ERROR, TEXT("Couldn't make born again Object ID (%s) on %s:, failed delete (%08x)"), (const TCHAR*)CDebugString(objid), ptszVolumeDeviceName, status )); goto Exit; } // ---- // Exit // ---- Exit: if( NULL != hFile ) NtClose( hFile ); #if DBG if( !NT_SUCCESS(status) && STATUS_SHARING_VIOLATION != status && STATUS_ACCESS_DENIED != status ) { TCHAR tszPath[ MAX_PATH + 1 ]; NTSTATUS statusDebug; hFile = NULL; statusDebug = OpenFileById( ptszVolumeDeviceName, objid, SYNCHRONIZE | FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_ATTRIBUTE_NORMAL, &hFile ); if( !NT_SUCCESS(statusDebug) ) hFile = NULL; if( NT_SUCCESS(statusDebug) ) statusDebug = QueryVolRelativePath( hFile, tszPath ); else TrkLog(( TRKDBG_ERROR, TEXT("Couldn't OpenFileById (%08x)"), statusDebug )); if( NT_SUCCESS(statusDebug) ) TrkLog(( TRKDBG_ERROR, TEXT("Failed to make born again objid on %s:%s"), ptszVolumeDeviceName, tszPath )); else TrkLog(( TRKDBG_ERROR, TEXT("Couldn't QueryVolRelativePath (%08x)"), statusDebug )); if( NULL != hFile ) NtClose( hFile ); } #endif return( status ); } NTSTATUS MakeObjIdReborn(HANDLE hFile ) { NTSTATUS status = STATUS_SUCCESS; status = SetBirthId( hFile, CDomainRelativeObjId( )); if( !NT_SUCCESS(status) ) goto Exit; Exit: return( status ); } //+---------------------------------------------------------------------------- // // SetBirthId // // The the birth ID on a file. The object ID isn't altered. // //+---------------------------------------------------------------------------- NTSTATUS SetBirthId( HANDLE hFile, const CDomainRelativeObjId &droidBirth ) { // -------------- // Initialization // -------------- NTSTATUS status = STATUS_SUCCESS; BOOL fOpen = FALSE; CObjId objidNull; FILE_OBJECTID_BUFFER fobOID; UNICODE_STRING uPath; IO_STATUS_BLOCK IoStatus; // Initialize the request buffer memset( &fobOID, 0, sizeof(fobOID) ); droidBirth.SerializeRaw( fobOID.ExtendedInfo ); objidNull.SerializeRaw( fobOID.ObjectId ); // Send the FSCTL status = NtFsControlFile( hFile, NULL, NULL, NULL, &IoStatus, FSCTL_SET_OBJECT_ID_EXTENDED, &fobOID.ExtendedInfo, sizeof(fobOID.ExtendedInfo), NULL, // Out buffer 0); // Out buffer size if( !NT_SUCCESS(status) ) goto Exit; // ---- // Exit // ---- Exit: if( !NT_SUCCESS(status) ) { TrkLog(( TRKDBG_ERROR, TEXT("SetBirthId returned ntstatus=%08X"), status )); } return( status ); } // Set the birth ID given a path, rather than a handle. NTSTATUS SetBirthId( const TCHAR *ptszFile, const CDomainRelativeObjId &droidBirth ) { NTSTATUS status = STATUS_SUCCESS; HANDLE hFile = NULL; __try { EnableRestorePrivilege(); status = TrkCreateFile( ptszFile, SYNCHRONIZE | FILE_WRITE_ATTRIBUTES, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_NO_RECALL | FILE_SYNCHRONOUS_IO_NONALERT, NULL, &hFile ); if( !NT_SUCCESS(status )) { hFile = NULL; goto Exit; } status = SetBirthId( hFile, droidBirth ); } __finally { if( NULL != hFile ) NtClose( hFile ); } Exit: return( status ); } //+---------------------------------------------------------------------------- // // Function: GetBirthId // // Synopsis: Get the birth ID from a given file. // // Parameters: [hFile] (in) // The file to query. // [pdroidBirth] (out) // The file's birth ID. // // Returns: [NTSTATUS] // //+---------------------------------------------------------------------------- NTSTATUS GetBirthId( IN HANDLE hFile, OUT CDomainRelativeObjId *pdroidBirth ) { NTSTATUS status = STATUS_SUCCESS; FILE_OBJECTID_BUFFER fobOID; IO_STATUS_BLOCK IoStatus; TrkAssert( NULL != pdroidBirth ); TrkAssert( INVALID_HANDLE_VALUE != hFile && NULL != hFile ); status = NtFsControlFile( hFile, NULL, NULL, NULL, &IoStatus, FSCTL_GET_OBJECT_ID, NULL, 0, &fobOID, sizeof(fobOID)); if( !NT_SUCCESS(status) ) goto Exit; // Load the droid from the objid buffer. pdroidBirth->InitFromFOB( fobOID ); // Clear the bit xvol-move bit, which is not considered part of the ID. pdroidBirth->GetVolumeId().Normalize(); Exit: return( status ); }