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.
862 lines
24 KiB
862 lines
24 KiB
#include <nt.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
#include <windows.h>
|
|
#include <srvfsctl.h> // FSCTL_SRV_ENUMERATE_SNAPSHOTS
|
|
#include <lm.h>
|
|
#include <lmdfs.h> // NetDfsGetClientInfo
|
|
#include <shlwapi.h> // 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<lNumberOfSnapshots; iCount++ )
|
|
{
|
|
BOOL bAcceptThisEntry = FALSE;
|
|
|
|
pPathCopyStart = pPathCopy;
|
|
|
|
// Copy the prefix
|
|
RtlCopyMemory( pPathCopy, pPrefix, lPrefix*sizeof(WCHAR) );
|
|
pPathCopy += lPrefix;
|
|
|
|
// Copy the front portion of the path
|
|
RtlCopyMemory( pPathCopy, lpszPath, lPathFront*sizeof(WCHAR) );
|
|
pPathCopy += lPathFront;
|
|
|
|
// Copy the seperator
|
|
*pPathCopy++ = L'\\';
|
|
|
|
// Copy the Snapshot name
|
|
if (lstrlenW(pSnapName) < SNAPSHOT_NAME_LENGTH)
|
|
{
|
|
LocalFree( pPathMultiSZ );
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
RtlCopyMemory( pPathCopy, pSnapName, SNAPSHOT_NAME_LENGTH*sizeof(WCHAR) );
|
|
pPathCopy += SNAPSHOT_NAME_LENGTH;
|
|
|
|
// Copy the tail
|
|
RtlCopyMemory( pPathCopy, pPathSplit, lPathBack*sizeof(WCHAR) );
|
|
pPathCopy += lPathBack;
|
|
|
|
// Copy the NULL
|
|
*pPathCopy++ = L'\0';
|
|
|
|
// A path is only included in the return result if it matches the users criteria
|
|
if( lFlags & (QUERY_SNAPSHOT_EXISTING|QUERY_SNAPSHOT_DIFFERENT) )
|
|
{
|
|
// If they just want Existing, we query the attributes to confirm that the file exists
|
|
if( GetFileAttributesEx( pPathCopyStart, GetFileExInfoStandard, &w32Attributes ) )
|
|
{
|
|
// If they want Different, we check the lastModifiedTime against the last iteration to
|
|
// determine acceptance
|
|
if( lFlags & QUERY_SNAPSHOT_DIFFERENT )
|
|
{
|
|
if( ((w32Attributes.ftLastWriteTime.dwHighDateTime != fModifiedTime.dwHighDateTime) ||
|
|
(w32Attributes.ftLastWriteTime.dwLowDateTime != fModifiedTime.dwLowDateTime)) &&
|
|
((w32Attributes.ftLastWriteTime.dwHighDateTime != fOriginalTime.dwHighDateTime) ||
|
|
(w32Attributes.ftLastWriteTime.dwLowDateTime != fOriginalTime.dwLowDateTime))
|
|
)
|
|
{
|
|
// When we accept this entry, we update the LastModifiedTime for the next iteration
|
|
fModifiedTime.dwLowDateTime = w32Attributes.ftLastWriteTime.dwLowDateTime;
|
|
fModifiedTime.dwHighDateTime = w32Attributes.ftLastWriteTime.dwHighDateTime;
|
|
bAcceptThisEntry = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bAcceptThisEntry = TRUE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bAcceptThisEntry = TRUE;
|
|
}
|
|
|
|
if (!bAcceptThisEntry)
|
|
{
|
|
// Skip this entry and remove its reference
|
|
pPathCopy = pPathCopyStart;
|
|
lNumberOfSnapshots--;
|
|
iCount--;
|
|
}
|
|
|
|
// Move the Snapshot Name forward
|
|
pSnapName += (SNAPSHOT_NAME_LENGTH + 1);
|
|
}
|
|
|
|
// Append the final NULL
|
|
*pPathCopy = L'\0';
|
|
|
|
*lplpszPathArray = pPathMultiSZ;
|
|
*lpdwPathCount = lNumberOfSnapshots;
|
|
dwError = ERROR_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
// The name was invalid, return the fact
|
|
dwError = ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
// If we're failing, free the buffer
|
|
if( ERROR_SUCCESS != dwError )
|
|
{
|
|
LocalFree( pPathMultiSZ );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Our allocation failed. Return the error
|
|
dwError = ERROR_OUTOFMEMORY;
|
|
}
|
|
|
|
return dwError;
|
|
}
|
|
|
|
DWORD
|
|
QuerySnapshotsForPath(
|
|
IN LPCWSTR lpszFilePath,
|
|
IN DWORD dwQueryFlags,
|
|
OUT LPWSTR* ppszPathMultiSZ,
|
|
OUT LPDWORD iNumberOfPaths )
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function takes a path and returns an array of snapshot-paths to the file.
|
|
(These are the paths to be passed to Win32 functions to obtain handles to the
|
|
previous versions of the file) This will be the only public export of the
|
|
TimeWarp API
|
|
|
|
Arguments:
|
|
|
|
lpszFilePath - The UNICODE path to the file or directory
|
|
dwQueryFlags - See Notes below
|
|
ppszPathMultiSZ - Upon successful return, the allocated array of the path
|
|
iNumberOfPaths - Upon successful return, the number of paths returned
|
|
|
|
|
|
Return Value:
|
|
|
|
Windows Error code
|
|
|
|
Notes:
|
|
|
|
- The user is responsible for freeing the returned buffer with LocalFree
|
|
- The possible flags are:
|
|
|
|
Return only the path names where the file exists
|
|
#define QUERY_SNAPSHOT_EXISTING 0x1
|
|
|
|
Return the minimum set of paths to the different versions of the
|
|
files. (Does LastModifiedTime checking)
|
|
#define QUERY_SNAPSHOT_DIFFERENT 0x2
|
|
|
|
|
|
--*/
|
|
|
|
{
|
|
HANDLE hFile;
|
|
DWORD dwError;
|
|
LPWSTR pSnapshotNameArray = NULL;
|
|
DWORD dwNumberOfSnapshots = 0;
|
|
LPWSTR pSnapshotPathArray = NULL;
|
|
DWORD dwSnapshotPathSize = 0;
|
|
DWORD dwFinalSnapshoutCount = 0;
|
|
|
|
ASSERT( lpszFilePath );
|
|
|
|
dwError = OpenFileForSnapshot( lpszFilePath, &hFile );
|
|
if( dwError == ERROR_SUCCESS )
|
|
{
|
|
// Get the array of names
|
|
dwError = QuerySnapshotNames( hFile, &pSnapshotNameArray, &dwNumberOfSnapshots );
|
|
if( dwError == ERROR_SUCCESS )
|
|
{
|
|
// Calculate the necessary string size
|
|
if (dwNumberOfSnapshots > 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 <Count> 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;
|
|
}
|
|
|