#include #include #include #include #include // FSCTL_SRV_ENUMERATE_SNAPSHOTS #include #include // NetDfsGetClientInfo #include // PathIsUNC #include "timewarp.h" typedef struct _SRV_SNAPSHOT_ARRAY { ULONG NumberOfSnapshots; // The number of snapshots for the volume ULONG NumberOfSnapshotsReturned; // The number of snapshots we can fit into this buffer ULONG SnapshotArraySize; // The size (in bytes) needed for the array WCHAR SnapShotMultiSZ[1]; // The multiSZ array of snapshot names } SRV_SNAPSHOT_ARRAY, *PSRV_SNAPSHOT_ARRAY; DWORD OpenFileForSnapshot( IN LPCWSTR lpszFilePath, OUT HANDLE* pHandle ) /*++ Routine Description: This routine opens a file with the access needed to query its snapshot information Arguments: lpszFilePath - network path to the file pHandle - Upon return, the handle to the opened file Return Value: Win32 Error Notes: None --*/ { NTSTATUS Status; UNICODE_STRING uPathName; OBJECT_ATTRIBUTES objectAttributes; IO_STATUS_BLOCK ioStatusBlock; *pHandle = CreateFile( lpszFilePath, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL ); if( *pHandle == INVALID_HANDLE_VALUE ) { return GetLastError(); } else { return ERROR_SUCCESS; } } NTSTATUS IssueSnapshotControl( IN HANDLE hFile, IN PVOID pData, IN ULONG outDataSize ) /*++ Routine Description: This routine issues the snapshot enumeration FSCTL against the provided handle Arguments: hFile - The handle to the file in question pData - A pointer to the output buffer outDataSize - The size of the given output buffer Return Value: NTSTATUS Notes: None --*/ { NTSTATUS Status; HANDLE hEvent; IO_STATUS_BLOCK ioStatusBlock; PSRV_SNAPSHOT_ARRAY pArray; RtlZeroMemory( pData, outDataSize ); // Create an event to synchronize with the driver Status = NtCreateEvent( &hEvent, FILE_ALL_ACCESS, NULL, NotificationEvent, FALSE ); if( NT_SUCCESS(Status) ) { Status = NtFsControlFile( hFile, hEvent, NULL, NULL, &ioStatusBlock, FSCTL_SRV_ENUMERATE_SNAPSHOTS, NULL, 0, pData, outDataSize); if( Status == STATUS_PENDING ) { NtWaitForSingleObject( hEvent, FALSE, NULL ); Status = ioStatusBlock.Status; } NtClose( hEvent ); } // Check the return value if( NT_SUCCESS(Status) ) { pArray = (PSRV_SNAPSHOT_ARRAY)pData; if( pArray->NumberOfSnapshots != pArray->NumberOfSnapshotsReturned ) { Status = STATUS_BUFFER_OVERFLOW; } } return Status; } DWORD QuerySnapshotNames( IN HANDLE hFile, OUT LPWSTR* ppszSnapshotNameArray, OUT LPDWORD pdwNumberOfSnapshots ) /*++ Routine Description: This routine takes a handle to a file and returns a MultiSZ list of the snapshots availible on the volume the handle resides. Arguments: hFile - Handle to the file in question ppszSnapshotNameArray - Upon return, an allocated MultiSZ array of names pdwNumberOfSnapshots - the number of snapshots in the above array Return Value: Win32 Error Notes: The returned list of snapshots is complete for the volume. It is not guaranteed that every returned entry will actually have the file existing in that snapshot. The caller should check that themselves. --*/ { NTSTATUS Status; SRV_SNAPSHOT_ARRAY sArray; PSRV_SNAPSHOT_ARRAY psAllocatedArray = NULL; LPWSTR pszNameArray = NULL; // Query the size needed for the snapshots Status = IssueSnapshotControl( hFile, &sArray, sizeof(SRV_SNAPSHOT_ARRAY) ); if( NT_SUCCESS(Status) || (Status == STATUS_BUFFER_OVERFLOW) ) { ULONG AllocSize = sizeof(SRV_SNAPSHOT_ARRAY)+sArray.SnapshotArraySize; if( sArray.NumberOfSnapshots == 0 ) { *pdwNumberOfSnapshots = 0; *ppszSnapshotNameArray = NULL; } else { // Allocate the array to the necessary size psAllocatedArray = (PSRV_SNAPSHOT_ARRAY)LocalAlloc( LPTR, AllocSize ); if( psAllocatedArray ) { // Call again with the proper size array Status = IssueSnapshotControl( hFile, psAllocatedArray, AllocSize ); if( NT_SUCCESS(Status) ) { // Allocate the string needed pszNameArray = (LPWSTR)LocalAlloc( LPTR, psAllocatedArray->SnapshotArraySize ); if( pszNameArray ) { // Copy the string and succeed RtlCopyMemory( pszNameArray, psAllocatedArray->SnapShotMultiSZ, psAllocatedArray->SnapshotArraySize ); *ppszSnapshotNameArray = pszNameArray; *pdwNumberOfSnapshots = psAllocatedArray->NumberOfSnapshots; Status = STATUS_SUCCESS; } else { Status = STATUS_INSUFFICIENT_RESOURCES; } } else if( Status == STATUS_BUFFER_OVERFLOW ) { Status = STATUS_RETRY; } LocalFree( psAllocatedArray ); } else { Status = STATUS_INSUFFICIENT_RESOURCES; } } } return RtlNtStatusToDosError(Status); } LPCWSTR FindVolumePathSplit( IN LPCWSTR lpszPath ) { LPCWSTR pszTail = NULL; WCHAR szVolumeName[MAX_PATH]; if (GetVolumePathNameW(lpszPath, szVolumeName, MAX_PATH)) { ULONG cchVolumeName = lstrlenW(szVolumeName); ASSERT(cchVolumeName > 0); ASSERT(szVolumeName[cchVolumeName-1] == L'\\'); pszTail = lpszPath + (cchVolumeName - 1); ASSERT(pszTail <= lpszPath + lstrlenW(lpszPath)); ASSERT(*pszTail == L'\\' || *pszTail == L'\0'); } return pszTail; } LPCWSTR FindDfsUncSplit( IN LPCWSTR lpszPath ) { LPCWSTR pszTail = NULL; PDFS_INFO_1 pDI1; DWORD dwErr; ASSERT(PathIsUNCW(lpszPath)); // Check for DFS dwErr = NetDfsGetClientInfo((LPWSTR)lpszPath, NULL, NULL, 1, (LPBYTE*)&pDI1); if (NERR_Success == dwErr) { // Note that EntryPath has only a single leading backslash, hence +1 pszTail = lpszPath + lstrlenW(pDI1->EntryPath) + 1; ASSERT(pszTail <= lpszPath + lstrlenW(lpszPath)); ASSERT(*pszTail == L'\\' || *pszTail == L'\0'); NetApiBufferFree(pDI1); } return pszTail; } LPCWSTR FindDfsPathSplit( IN LPCWSTR lpszPath ) { LPCWSTR pszTail = NULL; DWORD cbBuffer; DWORD dwErr; REMOTE_NAME_INFOW *pUncInfo; if (PathIsUNCW(lpszPath)) return FindDfsUncSplit(lpszPath); ASSERT(PathIsNetworkPathW(lpszPath)); // Get the UNC path. // // Note that WNetGetUniversalName returns ERROR_INVALID_PARAMETER if you // specify NULL for the buffer and 0 length (asking for size). cbBuffer = sizeof(REMOTE_NAME_INFOW) + MAX_PATH*sizeof(WCHAR); // initial guess pUncInfo = (REMOTE_NAME_INFOW*)LocalAlloc(LPTR, cbBuffer); if (pUncInfo) { dwErr = WNetGetUniversalNameW(lpszPath, REMOTE_NAME_INFO_LEVEL, pUncInfo, &cbBuffer); if (ERROR_MORE_DATA == dwErr) { // Alloc a new buffer and try again LocalFree(pUncInfo); pUncInfo = (REMOTE_NAME_INFOW*)LocalAlloc(LPTR, cbBuffer); if (pUncInfo) { dwErr = WNetGetUniversalNameW(lpszPath, REMOTE_NAME_INFO_LEVEL, pUncInfo, &cbBuffer); } } if (ERROR_SUCCESS == dwErr) { // Find the tail LPCWSTR pszUncTail = FindDfsUncSplit(pUncInfo->lpUniversalName); if (pszUncTail) { UINT cchJunction; UINT cchConnectionName; // It's a DFS path, so we'll at least return a // pointer to the drive root. ASSERT(lpszPath[0] != L'\0' && lpszPath[1] == L':'); pszTail = lpszPath + 2; // skip "X:" // If the DFS junction is deeper than the drive mapping, // move the pointer to after the junction point. cchJunction = (UINT)(pszUncTail - pUncInfo->lpUniversalName); cchConnectionName = lstrlenW(pUncInfo->lpConnectionName); if (cchJunction > cchConnectionName) { pszTail += cchJunction - cchConnectionName; } ASSERT(pszTail <= lpszPath + lstrlenW(lpszPath)); ASSERT(*pszTail == L'\\' || *pszTail == L'\0'); } } LocalFree(pUncInfo); } return pszTail; } LPCWSTR FindSnapshotPathSplit( IN LPCWSTR lpszPath ) /*++ Routine Description: This routine looks at a path and determines where the snapshot token will be inserted Arguments: lpszPath - The path we're examining Return Value: LPWSTR (pointer to the insertion point within lpszPath) Notes: None --*/ { LPCWSTR pszVolumeTail; LPCWSTR pszDfsTail; // FindVolumePathSplit accounts for mounted volumes, but not DFS. // FindDfsPathSplit accounts for DFS, but not mounted volumes. // Try both methods and pick the larger of the 2, if both succeed. pszVolumeTail = FindVolumePathSplit(lpszPath); pszDfsTail = FindDfsPathSplit(lpszPath); // Note that this comparison automatically handles the cases // where either or both pointers are NULL. if (pszDfsTail > pszVolumeTail) { pszVolumeTail = pszDfsTail; } return pszVolumeTail; } #define PREFIX_DRIVE L"\\\\?\\" #define PREFIX_UNC L"\\\\?\\UNC\\" #define MAX_PREFIX_LENGTH 8 // strlen(PREFIX_UNC) DWORD BuildSnapshotPathArray( IN ULONG lNumberOfSnapshots, IN LPWSTR pszSnapshotNameMultiSZ, IN LPCWSTR lpszPath, IN ULONG lFlags, OUT LPWSTR* lplpszPathArray, OUT LPDWORD lpdwPathCount ) /*++ Routine Description: This routine has the fun task of assembling an array of paths based on the snapshot name array, the path to the file, and the flags the user passed in. Arguments: lNumberOfSnapshots - The number of snapshots in the array pszSnapshotNameMultiSZ - A multi-SZ list of the snapshot names lpszPath - The path to the file lFlags - The query flags to determine what the user desires to be returned lplpszPathArray - Upon return, the allocated array of path names lpdwPathCount - Upon return, the number of paths in the array Return Value: Win32 Error Notes: None --*/ { DWORD dwError; LPWSTR pPathMultiSZ; ULONG lPathSize; LPCWSTR pPathSplit; LPWSTR pPathCopy; LPWSTR pPathCopyStart; ULONG lPathFront, lPathBack; LPWSTR pSnapName = pszSnapshotNameMultiSZ; ULONG iCount; WIN32_FILE_ATTRIBUTE_DATA w32Attributes; FILETIME fModifiedTime; FILETIME fOriginalTime; // If the user only wants files that are different, we use the ModifiedTime field // to keep track of the last modified time so we can remove duplicates. This field // is initialized to the current last-modified time of the file. Since the snapshot // name array is passed back in newest-to-oldest format, we can simply compare the current // iteration to the previous one to determine if the file changed in the current snapshot // as we build the list if( lFlags & QUERY_SNAPSHOT_DIFFERENT ) { fModifiedTime.dwHighDateTime = fModifiedTime.dwLowDateTime = 0; if( !GetFileAttributesEx( lpszPath, GetFileExInfoStandard, &w32Attributes ) ) { return GetLastError(); } fOriginalTime.dwHighDateTime = w32Attributes.ftLastWriteTime.dwHighDateTime; fOriginalTime.dwLowDateTime = w32Attributes.ftLastWriteTime.dwLowDateTime; } // Allocate the buffer to the maximum size we will need lPathSize = ((MAX_PREFIX_LENGTH+lstrlenW(lpszPath)+1+SNAPSHOT_NAME_LENGTH+1)*sizeof(WCHAR))*lNumberOfSnapshots + 2*sizeof(WCHAR); pPathMultiSZ = LocalAlloc( LPTR, lPathSize ); if( pPathMultiSZ ) { // For the path, we need to determine where the snapshot token will be inserted. It will be // placed as far left in the name as possible, at the volume junction point. pPathSplit = FindSnapshotPathSplit( lpszPath ); if( pPathSplit ) { // Because we are inserting an extra segment into the path, it is // easy to start with a valid path that is less than MAX_PATH, but // end up with something greater than MAX_PATH. Therefore, we add // the "\\?\" or "\\?\UNC\" prefix to override the maximum length. LPCWSTR pPrefix = PREFIX_DRIVE; ULONG lPrefix; if (PathIsUNCW(lpszPath)) { pPrefix = PREFIX_UNC; lpszPath += 2; // skip backslashes } lPrefix = lstrlenW(pPrefix); pPathCopy = pPathMultiSZ; lPathBack = lstrlenW( pPathSplit ); lPathFront = lstrlenW( lpszPath ) - lPathBack; // We now iterate through the snapshots and create the paths for( iCount=0; iCount 0) { dwError = BuildSnapshotPathArray( dwNumberOfSnapshots, pSnapshotNameArray, lpszFilePath, dwQueryFlags, &pSnapshotPathArray, &dwFinalSnapshoutCount ); } else { dwError = ERROR_NOT_FOUND; } if( dwError == ERROR_SUCCESS ) { *ppszPathMultiSZ = pSnapshotPathArray; *iNumberOfPaths = dwFinalSnapshoutCount; } // Release the name array buffer LocalFree( pSnapshotNameArray ); pSnapshotNameArray = NULL; } CloseHandle( hFile ); } return dwError; } BOOLEAN ExtractNumber( IN PCWSTR psz, IN ULONG Count, OUT CSHORT* value ) /*++ Routine Description: This function takes a string of characters and parses out a length decimal number. If it returns TRUE, value has been set and the string was parsed correctly. FALSE indicates an error in parsing. Arguments: psz - String pointer Count - Number of characters to pull off value - pointer to output parameter where value is stored Return Value: BOOLEAN - See description --*/ { *value = 0; while( Count ) { if( (*psz == L'\0') || (*psz == L'\\') ) { return FALSE; } if( (*psz < '0') || (*psz > '9') ) { return FALSE; } *value = (*value)*10+(*psz-L'0'); Count--; psz++; } return TRUE; } DWORD GetSnapshotTimeFromPath( IN LPCWSTR lpszFilePath, IN OUT FILETIME* pUTCTime ) { PCWSTR pszPath = lpszFilePath; TIME_FIELDS LocalTimeFields; CSHORT lValue; // Find the token pszPath = wcsstr( lpszFilePath, SNAPSHOT_MARKER ); if( !pszPath ) { return ERROR_INVALID_PARAMETER; } // Skip the GMT header pszPath += 5; // Pull the Year if( !ExtractNumber( pszPath, 4, &lValue ) ) { return ERROR_INVALID_PARAMETER; } LocalTimeFields.Year = lValue; pszPath += 4; // Skip the seperator if( *pszPath != L'.' ) { return ERROR_INVALID_PARAMETER; } pszPath++; // Pull the Month if( !ExtractNumber( pszPath, 2, &lValue ) ) { return ERROR_INVALID_PARAMETER; } LocalTimeFields.Month = lValue; pszPath += 2; // Skip the seperator if( *pszPath != L'.' ) { return ERROR_INVALID_PARAMETER; } pszPath++; // Pull the Day if( !ExtractNumber( pszPath, 2, &lValue ) ) { return ERROR_INVALID_PARAMETER; } LocalTimeFields.Day = lValue; pszPath += 2; // Skip the seperator if( *pszPath != L'-' ) { return ERROR_INVALID_PARAMETER; } pszPath++; // Pull the Hour if( !ExtractNumber( pszPath, 2, &lValue ) ) { return ERROR_INVALID_PARAMETER; } LocalTimeFields.Hour = lValue; pszPath += 2; // Skip the seperator if( *pszPath != L'.' ) { return ERROR_INVALID_PARAMETER; } pszPath++; // Pull the Minute if( !ExtractNumber( pszPath, 2, &lValue ) ) { return ERROR_INVALID_PARAMETER; } LocalTimeFields.Minute = lValue; pszPath += 2; // Skip the seperator if( *pszPath != L'.' ) { return ERROR_INVALID_PARAMETER; } pszPath++; // Pull the Seconds if( !ExtractNumber( pszPath, 2, &lValue ) ) { return ERROR_INVALID_PARAMETER; } LocalTimeFields.Second = lValue; pszPath += 2; // Make sure the seperator is there if( (*pszPath != L'\\') && (*pszPath != L'\0') ) { return ERROR_INVALID_PARAMETER; } LocalTimeFields.Milliseconds = 0; LocalTimeFields.Weekday = 0; RtlTimeFieldsToTime( &LocalTimeFields, (LARGE_INTEGER*)pUTCTime ); return ERROR_SUCCESS; }