1266 lines
32 KiB
C
1266 lines
32 KiB
C
/*++
|
|
|
|
Copyright (c) 2001 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
srvsnap.c
|
|
|
|
Abstract:
|
|
|
|
This module contains routines for supporting the SnapShot feature
|
|
that interfaces CIFS with SnapShots
|
|
|
|
Author:
|
|
|
|
David Kruse (dkruse) 22-March-2001
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "precomp.h"
|
|
#include "srvsupp.h"
|
|
#include "ntddsnap.h"
|
|
#include "stdarg.h"
|
|
#include "stdio.h"
|
|
#include "srvsnap.tmh"
|
|
#pragma hdrstop
|
|
|
|
// Function Declaractions
|
|
NTSTATUS
|
|
StartIoAndWait (
|
|
IN PIRP Irp,
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PKEVENT Event,
|
|
IN PIO_STATUS_BLOCK IoStatusBlock
|
|
);
|
|
|
|
PIRP
|
|
BuildCoreOfSyncIoRequest (
|
|
IN HANDLE FileHandle,
|
|
IN PFILE_OBJECT FileObject OPTIONAL,
|
|
IN PKEVENT Event,
|
|
IN PIO_STATUS_BLOCK IoStatusBlock,
|
|
IN OUT PDEVICE_OBJECT *DeviceObject
|
|
);
|
|
|
|
NTSTATUS
|
|
SrvSnapGetNames(
|
|
IN HANDLE hFile,
|
|
IN OUT PVOID pBuffer,
|
|
IN OUT LPDWORD lpdwBufSize
|
|
);
|
|
|
|
NTSTATUS
|
|
SrvSnapGetNamesForVolume(
|
|
IN HANDLE hFile,
|
|
OUT PVOLSNAP_NAMES* Names
|
|
);
|
|
|
|
NTSTATUS
|
|
SrvSnapFillConfigInfo(
|
|
IN PSHARE_SNAPSHOT SnapShare,
|
|
IN PUNICODE_STRING SnapShotPath
|
|
);
|
|
|
|
BOOLEAN
|
|
SrvParseMultiSZ(
|
|
IN OUT PUNICODE_STRING mszString
|
|
);
|
|
|
|
NTSTATUS
|
|
SrvSnapInsertSnapIntoShare(
|
|
IN PSHARE Share,
|
|
IN PSHARE_SNAPSHOT SnapShot
|
|
);
|
|
|
|
NTSTATUS
|
|
SrvSnapAddShare(
|
|
IN PSHARE Share,
|
|
IN PUNICODE_STRING SnapPath
|
|
);
|
|
|
|
NTSTATUS
|
|
SrvSnapRemoveShare(
|
|
IN PSHARE_SNAPSHOT SnapShare
|
|
);
|
|
|
|
NTSTATUS
|
|
SrvSnapCheckForAndCreateSnapShare(
|
|
IN PSHARE Share,
|
|
IN PUNICODE_STRING SnapShotName
|
|
);
|
|
|
|
NTSTATUS
|
|
SrvSnapRefreshSnapShotsForShare(
|
|
IN PSHARE Share
|
|
);
|
|
|
|
NTSTATUS
|
|
SrvSnapEnumerateSnapShots(
|
|
IN PWORK_CONTEXT WorkContext
|
|
);
|
|
|
|
NTSTATUS
|
|
SrvSnapGetRootHandle(
|
|
IN PWORK_CONTEXT WorkContext,
|
|
OUT HANDLE* RootHandle
|
|
);
|
|
|
|
NTSTATUS
|
|
SrvSnapGetNameString(
|
|
IN PWORK_CONTEXT WorkContext,
|
|
OUT PUNICODE_STRING* ShareName,
|
|
OUT PBOOLEAN AllocatedShareName
|
|
);
|
|
|
|
BOOLEAN
|
|
ExtractNumber(
|
|
IN PWSTR psz,
|
|
IN ULONG Count,
|
|
OUT PLONG value
|
|
);
|
|
|
|
BOOLEAN
|
|
SrvSnapParseToken(
|
|
IN PWSTR Source,
|
|
OUT PLARGE_INTEGER TimeStamp
|
|
);
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text( PAGE, SrvSnapGetNames )
|
|
#pragma alloc_text( PAGE, SrvSnapGetNamesForVolume )
|
|
#pragma alloc_text( PAGE, SrvSnapFillConfigInfo )
|
|
#pragma alloc_text( PAGE, SrvParseMultiSZ )
|
|
#pragma alloc_text( PAGE, SrvSnapInsertSnapIntoShare )
|
|
#pragma alloc_text( PAGE, SrvSnapAddShare )
|
|
#pragma alloc_text( PAGE, SrvSnapRemoveShare )
|
|
#pragma alloc_text( PAGE, SrvSnapCheckForAndCreateSnapShare )
|
|
#pragma alloc_text( PAGE, SrvSnapRefreshSnapShotsForShare )
|
|
#pragma alloc_text( PAGE, SrvSnapEnumerateSnapShots )
|
|
#pragma alloc_text( PAGE, SrvSnapGetRootHandle )
|
|
#pragma alloc_text( PAGE, SrvSnapGetNameString )
|
|
#pragma alloc_text( PAGE, ExtractNumber )
|
|
#pragma alloc_text( PAGE, SrvSnapParseToken )
|
|
#endif
|
|
|
|
//
|
|
// Helper Functions
|
|
//
|
|
|
|
NTSTATUS
|
|
SrvSnapGetNames(
|
|
IN HANDLE hFile,
|
|
IN OUT PVOID pBuffer,
|
|
IN OUT LPDWORD lpdwBufSize
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function takes a volume handle and attempts to enumerate all the
|
|
SnapShots on that volume into the given buffer
|
|
|
|
Arguments:
|
|
|
|
hFile - The handle to the volume
|
|
|
|
pBuffer - A pointer to the output buffer
|
|
|
|
lpdwBufSize - Pointer to the size of passed in buffer. Set to the
|
|
required size if STATUS_BUFFER_OVERFLOW is returned
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Expected return codes are STATUS_SUCCESS or STATUS_BUFFER_OVERFLOW.
|
|
Any other response is an unexpected error code and the request should be
|
|
failed
|
|
|
|
--*/
|
|
|
|
{
|
|
PIRP Irp = NULL;
|
|
NTSTATUS Status;
|
|
IO_STATUS_BLOCK IoStatus;
|
|
KEVENT CompletionEvent;
|
|
PDEVICE_OBJECT DeviceObject = NULL;
|
|
PIO_STACK_LOCATION IrpSp;
|
|
|
|
ASSERT( (*lpdwBufSize) > sizeof(VOLSNAP_NAMES) );
|
|
|
|
// Initialize the variables
|
|
KeInitializeEvent( &CompletionEvent, SynchronizationEvent, FALSE );
|
|
|
|
// Create the IRP
|
|
Irp = BuildCoreOfSyncIoRequest(
|
|
hFile,
|
|
NULL,
|
|
&CompletionEvent,
|
|
&IoStatus,
|
|
&DeviceObject );
|
|
if( !Irp )
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
// Initialize the other IRP fields
|
|
Irp->Flags = (LONG)IRP_BUFFERED_IO;
|
|
Irp->AssociatedIrp.SystemBuffer = pBuffer;
|
|
IrpSp = IoGetNextIrpStackLocation( Irp );
|
|
IrpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL;
|
|
IrpSp->MinorFunction = 0;
|
|
IrpSp->Parameters.DeviceIoControl.OutputBufferLength = *lpdwBufSize;
|
|
IrpSp->Parameters.DeviceIoControl.InputBufferLength = 0;
|
|
IrpSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_VOLSNAP_QUERY_NAMES_OF_SNAPSHOTS;
|
|
IrpSp->Parameters.DeviceIoControl.Type3InputBuffer = NULL;
|
|
|
|
// Issue the IO
|
|
Status = StartIoAndWait( Irp, DeviceObject, &CompletionEvent, &IoStatus );
|
|
|
|
// If this was a buffer overflow, update the value
|
|
if( (Status == STATUS_BUFFER_OVERFLOW) &&
|
|
(*lpdwBufSize >= sizeof(VOLSNAP_NAMES)) )
|
|
{
|
|
*lpdwBufSize = ((PVOLSNAP_NAMES)pBuffer)->MultiSzLength + sizeof(VOLSNAP_NAMES);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
SrvSnapGetNamesForVolume(
|
|
IN HANDLE hFile,
|
|
OUT PVOLSNAP_NAMES* Names
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function takes a volume handle and returns the list of SnapShots for that
|
|
volume. If an error occurs, or no SnapShots exist, the returned pointer is
|
|
NULL. NOTE: The caller is responsible for freeing the returned pointer via
|
|
DEALLOCATE_NONPAGED_POOL
|
|
|
|
Arguments:
|
|
|
|
hFile - The handle to the volume
|
|
|
|
Names - A pointer to the pointer where we will store the volume name list
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Expected return code is STATUS_SUCCESS . Any other response is an
|
|
unexpected error code and the request should be failed
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
VOLSNAP_NAMES VolNamesBase;
|
|
PVOLSNAP_NAMES pNames = NULL;
|
|
DWORD dwSize = sizeof(VOLSNAP_NAMES);
|
|
|
|
// Initialize the values
|
|
*Names = NULL;
|
|
|
|
// Find out how big it should be
|
|
Status = SrvSnapGetNames( hFile, &VolNamesBase, &dwSize );
|
|
|
|
if( Status != STATUS_BUFFER_OVERFLOW )
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
// Allocate the correct size block
|
|
pNames = (PVOLSNAP_NAMES)ALLOCATE_NONPAGED_POOL( dwSize, BlockTypeSnapShot );
|
|
if( !pNames )
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
// Build up the IRP to be used for the query
|
|
Status = SrvSnapGetNames( hFile, pNames, &dwSize );
|
|
|
|
// Save the output if we're successful, or else deallocate
|
|
if( !NT_SUCCESS(Status) )
|
|
{
|
|
if( pNames )
|
|
{
|
|
DEALLOCATE_NONPAGED_POOL( pNames );
|
|
pNames = NULL;
|
|
*Names = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ASSERT(pNames);
|
|
*Names = pNames;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
SrvSnapFillConfigInfo(
|
|
IN PSHARE_SNAPSHOT SnapShare,
|
|
IN PUNICODE_STRING SnapShotPath
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function takes a SnapShare with existing names filled in and
|
|
queries the extra config info (the timestamp) for that SnapShot
|
|
|
|
Arguments:
|
|
|
|
SnapShare - Pointer to the SnapShot share to be filled in
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Expected return code is STATUS_SUCCESS . Any other response is an
|
|
unexpected error code and the request should be failed
|
|
|
|
--*/
|
|
{
|
|
HANDLE hVolume;
|
|
DWORD dwBytes;
|
|
NTSTATUS Status;
|
|
PIRP Irp = NULL;
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
VOLSNAP_CONFIG_INFO ConfigInfo;
|
|
IO_STATUS_BLOCK IoStatus;
|
|
KEVENT CompletionEvent;
|
|
PDEVICE_OBJECT DeviceObject;
|
|
PIO_STACK_LOCATION IrpSp;
|
|
|
|
// Now open the SnapShot handle
|
|
KeInitializeEvent( &CompletionEvent, SynchronizationEvent, FALSE );
|
|
InitializeObjectAttributes(
|
|
&objectAttributes,
|
|
SnapShotPath,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
Status = NtOpenFile(
|
|
&hVolume,
|
|
FILE_TRAVERSE|FILE_GENERIC_READ,
|
|
&objectAttributes,
|
|
&IoStatus,
|
|
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
|
|
0
|
|
);
|
|
if( !NT_SUCCESS(Status) )
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
// Create the IRP
|
|
Irp = BuildCoreOfSyncIoRequest(
|
|
hVolume,
|
|
NULL,
|
|
&CompletionEvent,
|
|
&IoStatus,
|
|
&DeviceObject );
|
|
if( !Irp )
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
// Initialize the other IRP fields
|
|
Irp->Flags = (LONG)IRP_BUFFERED_IO;
|
|
Irp->AssociatedIrp.SystemBuffer = &ConfigInfo;
|
|
IrpSp = IoGetNextIrpStackLocation( Irp );
|
|
IrpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL;
|
|
IrpSp->MinorFunction = 0;
|
|
IrpSp->Parameters.DeviceIoControl.OutputBufferLength = sizeof(VOLSNAP_CONFIG_INFO);
|
|
IrpSp->Parameters.DeviceIoControl.InputBufferLength = 0;
|
|
IrpSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_VOLSNAP_QUERY_CONFIG_INFO;
|
|
IrpSp->Parameters.DeviceIoControl.Type3InputBuffer = NULL;
|
|
|
|
// Issue the IO
|
|
Status = StartIoAndWait( Irp, DeviceObject, &CompletionEvent, &IoStatus );
|
|
|
|
if( !NT_SUCCESS( Status ) )
|
|
{
|
|
NtClose( hVolume );
|
|
return Status;
|
|
}
|
|
|
|
// Fill in the info and return success
|
|
SnapShare->Timestamp = ConfigInfo.SnapshotCreationTime;
|
|
NtClose( hVolume );
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
BOOLEAN
|
|
SrvParseMultiSZ(
|
|
IN OUT PUNICODE_STRING mszString
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function takes a UNICODE_STRING that points to a MultiSZ value, and
|
|
parses it with each iteration (similar to strtok( psz, "\0" )). For every
|
|
iteration this returns TRUE, the string will point to the next SZ in the
|
|
MultiSZ
|
|
|
|
Arguments:
|
|
|
|
mszString - Pointer to the MultiSZ string. For the first iteration, set the
|
|
MaximumLength to the length of the MultiSZ, and the Length to 0. For all
|
|
other iterations, simply pass in the string from the previous iteration
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - If TRUE, this is a valid SZ. If FALSE, all have been parsed
|
|
|
|
--*/
|
|
{
|
|
USHORT Count;
|
|
|
|
ASSERT( mszString->Length <= mszString->MaximumLength );
|
|
|
|
if( mszString->Length > 0 )
|
|
{
|
|
// Move the pointer past the string and the NULL
|
|
mszString->Buffer += (mszString->Length/2)+1;
|
|
mszString->MaximumLength -= (mszString->Length+2);
|
|
}
|
|
|
|
for( Count=0; Count<mszString->MaximumLength; Count++ )
|
|
{
|
|
if( mszString->Buffer[Count] == (WCHAR)'\0' )
|
|
{
|
|
mszString->Length = Count*2;
|
|
if( Count > 0 )
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// The starting data was bad!
|
|
ASSERT(FALSE);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
NTSTATUS
|
|
SrvSnapInsertSnapIntoShare(
|
|
IN PSHARE Share,
|
|
IN PSHARE_SNAPSHOT SnapShot
|
|
)
|
|
{
|
|
PLIST_ENTRY ListEntry = Share->SnapShots.Flink;
|
|
|
|
// Walk the SnapShot share list and see if this is a duplicate
|
|
while( ListEntry != &Share->SnapShots )
|
|
{
|
|
PSHARE_SNAPSHOT snapShare = CONTAINING_RECORD( ListEntry, SHARE_SNAPSHOT, SnapShotList );
|
|
if( RtlEqualUnicodeString( &SnapShot->SnapShotName, &snapShare->SnapShotName, TRUE ) )
|
|
{
|
|
return STATUS_DUPLICATE_NAME;
|
|
}
|
|
ListEntry = ListEntry->Flink;
|
|
}
|
|
|
|
|
|
InsertTailList( &Share->SnapShots, &SnapShot->SnapShotList );
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
SrvSnapAddShare(
|
|
IN PSHARE Share,
|
|
IN PUNICODE_STRING SnapPath
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function allocates a SnapShot Share that is being added to the system and
|
|
initializes it
|
|
|
|
Arguments:
|
|
|
|
Share - Pointer to the parent share
|
|
|
|
SnapPath - UNICODE_STRING name of the SnapShot (\\Device\\HardDiskSnap1)
|
|
|
|
NOTE: Caller must have the SnapShotLock acquired
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Returns STATUS_SUCCESS or STATUS_INSUFFICIENT_RESOURCES
|
|
|
|
--*/
|
|
{
|
|
#define SHARE_DEVICE_HEADER 6
|
|
NTSTATUS Status;
|
|
PSHARE_SNAPSHOT snapShare = NULL;
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
IO_STATUS_BLOCK IoStatus;
|
|
ULONG AllocSize;
|
|
TIME_FIELDS rtlTime;
|
|
|
|
IF_DEBUG( SNAPSHOT) KdPrint(( "SrvSnapAddShare %p %wZ\n", Share, SnapPath ));
|
|
|
|
// Calculate the size to allocate
|
|
// sizeof(SNAP_STRUCT) + (Length of SnapShotName) + (Max Length of SnapShot Path)
|
|
AllocSize = sizeof(SHARE_SNAPSHOT) + SNAPSHOT_NAME_LENGTH + (SnapPath->Length + Share->NtPathName.Length);
|
|
|
|
snapShare = ALLOCATE_HEAP( AllocSize, BlockTypeSnapShot );
|
|
if( !snapShare )
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
// Initialize the SnapShot-share structure
|
|
RtlZeroMemory( snapShare, sizeof(SHARE_SNAPSHOT) );
|
|
snapShare->SnapShotName.MaximumLength = SNAPSHOT_NAME_LENGTH;
|
|
snapShare->SnapShotName.Length = 0;
|
|
snapShare->SnapShotName.Buffer = (PWCHAR)(snapShare+1);
|
|
|
|
// This is only valid on shares who's NT Path is \??\X:\ where X is a logical drive
|
|
// Don't allow any others
|
|
if( Share->NtPathName.Length < (SHARE_DEVICE_HEADER+1)*sizeof(WCHAR) )
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Build the new SnapShot-relative path
|
|
snapShare->SnapShotPath.MaximumLength = SnapPath->Length + Share->NtPathName.Length;
|
|
snapShare->SnapShotPath.Length = 0;
|
|
snapShare->SnapShotPath.Buffer = snapShare->SnapShotName.Buffer + (snapShare->SnapShotName.MaximumLength/sizeof(WCHAR));
|
|
|
|
// Create it by using the SnapShot device and the Share NtPath
|
|
RtlCopyUnicodeString( &snapShare->SnapShotPath, SnapPath );
|
|
RtlCopyMemory( snapShare->SnapShotPath.Buffer + (snapShare->SnapShotPath.Length/2), Share->NtPathName.Buffer + SHARE_DEVICE_HEADER, Share->NtPathName.Length-SHARE_DEVICE_HEADER*sizeof(WCHAR) );
|
|
snapShare->SnapShotPath.Length += (Share->NtPathName.Length-SHARE_DEVICE_HEADER*sizeof(WCHAR));
|
|
|
|
//IF_DEBUG( SNAPSHOT ) KdPrint(( "%wZ -> %wZ\n", &Share->NtPathName, &snapShare->SnapShotPath ));
|
|
|
|
// Now open the relative handle
|
|
InitializeObjectAttributes(
|
|
&objectAttributes,
|
|
&snapShare->SnapShotPath,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
Status = NtOpenFile(
|
|
&snapShare->SnapShotRootDirectoryHandle,
|
|
FILE_TRAVERSE,
|
|
&objectAttributes,
|
|
&IoStatus,
|
|
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
|
|
0
|
|
);
|
|
if( !NT_SUCCESS(Status) )
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Fill in the configuration information
|
|
Status = SrvSnapFillConfigInfo( snapShare, SnapPath );
|
|
if( !NT_SUCCESS(Status) )
|
|
{
|
|
NtClose( snapShare->SnapShotRootDirectoryHandle );
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Generate the SnapShot name
|
|
snapShare->SnapShotName.Length = SNAPSHOT_NAME_LENGTH-sizeof(WCHAR);
|
|
RtlTimeToTimeFields( &snapShare->Timestamp, &rtlTime );
|
|
rtlTime.Milliseconds = 0;
|
|
rtlTime.Weekday = 0;
|
|
_snwprintf( snapShare->SnapShotName.Buffer, SNAPSHOT_NAME_LENGTH, SNAPSHOT_NAME_FORMAT, rtlTime.Year, rtlTime.Month, rtlTime.Day, rtlTime.Hour, rtlTime.Minute, rtlTime.Second );
|
|
RtlTimeFieldsToTime( &rtlTime, &snapShare->Timestamp );
|
|
ASSERT( wcslen( snapShare->SnapShotName.Buffer ) == snapShare->SnapShotName.Length );
|
|
|
|
|
|
// Insert it into the list, and return Success
|
|
Status = SrvSnapInsertSnapIntoShare( Share, snapShare );
|
|
|
|
if( !NT_SUCCESS(Status) ) {
|
|
NtClose( snapShare->SnapShotRootDirectoryHandle );
|
|
goto Cleanup;
|
|
}
|
|
|
|
//IF_DEBUG( SNAPSHOT ) KdPrint(( "%wZ Handle=%p\n", &snapShare->SnapShotPath, snapShare->SnapShotRootDirectoryHandle ));
|
|
|
|
Cleanup:
|
|
// Cleanup
|
|
if( !NT_SUCCESS(Status) )
|
|
{
|
|
if( snapShare ) FREE_HEAP( snapShare );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
SrvSnapRemoveShare(
|
|
IN PSHARE_SNAPSHOT SnapShare
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function deallocates a SnapShot Share after it has been removed from
|
|
the underlying disk
|
|
|
|
NOTE: Caller must have the SnapShotLock acquired
|
|
|
|
Arguments:
|
|
|
|
SnapShare - Pointer to the SnapShot Share to remove. It will be removed and
|
|
deallocated.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Returns STATUS_SUCCESS
|
|
|
|
--*/
|
|
{
|
|
IF_DEBUG( SNAPSHOT ) KdPrint(( "SrvSnapRemoveShare %p %wZ\n", SnapShare, &SnapShare->SnapShotName ));
|
|
|
|
if( SnapShare->SnapShotRootDirectoryHandle )
|
|
{
|
|
NtClose( SnapShare->SnapShotRootDirectoryHandle );
|
|
SnapShare->SnapShotRootDirectoryHandle = NULL;
|
|
}
|
|
|
|
RemoveEntryList( &SnapShare->SnapShotList );
|
|
FREE_HEAP( SnapShare );
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
SrvSnapCheckForAndCreateSnapShare(
|
|
IN PSHARE Share,
|
|
IN PUNICODE_STRING SnapShotName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function checks to see if a given SnapShot share exists on the given
|
|
share, and if it does not it creates one. If it does, it removes the NOT_FOUND
|
|
flag to signify this share still exists
|
|
|
|
NOTE: Caller must have the SnapShotLock acquired
|
|
|
|
Arguments:
|
|
|
|
Share - The parent share of the SnapShot
|
|
SnapShotName - The UNICODE_STRING name of the SnapShot (\\Device\\HardDiskSnap1)
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Returns STATUS_SUCCESS or an unexpected error
|
|
|
|
--*/
|
|
{
|
|
PLIST_ENTRY snapList;
|
|
UNICODE_STRING SnapPartialName;
|
|
|
|
//IF_DEBUG( SNAPSHOT ) KdPrint(( "Share %x, Name %wZ\n", Share, SnapShotName ));
|
|
|
|
snapList = Share->SnapShots.Flink;
|
|
SnapPartialName.Length = SnapPartialName.MaximumLength = SnapShotName->Length;
|
|
|
|
while( snapList != &Share->SnapShots )
|
|
{
|
|
PSHARE_SNAPSHOT snapShare = CONTAINING_RECORD( snapList, SHARE_SNAPSHOT, SnapShotList );
|
|
snapList = snapList->Flink;
|
|
if( snapShare->SnapShotPath.Length < SnapPartialName.Length )
|
|
{
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
SnapPartialName.Buffer = snapShare->SnapShotPath.Buffer;
|
|
|
|
if( RtlEqualUnicodeString( SnapShotName, &SnapPartialName, TRUE ) )
|
|
{
|
|
ClearFlag( snapShare->Flags, SRV_SNAP_SHARE_NOT_FOUND );
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return SrvSnapAddShare( Share, SnapShotName );
|
|
}
|
|
|
|
NTSTATUS
|
|
SrvSnapRefreshSnapShotsForShare(
|
|
IN PSHARE Share
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function takes a share and refreshes the SnapShot views on the share
|
|
so that only currently existing SnapShots are listed
|
|
|
|
Arguments:
|
|
|
|
Share - The share we are examining
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - STATUS_SUCCESS if all went well, otherwise the appropriate error
|
|
code (and the request should be failed)
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_UNSUCCESSFUL;
|
|
OBJECT_HANDLE_INFORMATION HandleInfo;
|
|
PVOLSNAP_NAMES pNames = NULL;
|
|
UNICODE_STRING VolumeName;
|
|
UNICODE_STRING RootVolume;
|
|
HANDLE VolumeHandle;
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
IO_STATUS_BLOCK iosb;
|
|
PLIST_ENTRY shareList;
|
|
|
|
// Validate we can do SnapShots
|
|
if( Share->Removable )
|
|
{
|
|
Status = STATUS_NOT_SUPPORTED;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Get a handle to the root volume
|
|
RootVolume.MaximumLength = Share->NtPathName.Length;
|
|
RootVolume.Buffer = Share->NtPathName.Buffer;
|
|
RootVolume.Length = 12;
|
|
if( (Share->NtPathName.Length < 12) ||
|
|
(RootVolume.Buffer[5] != L':') )
|
|
{
|
|
IF_DEBUG( SNAPSHOT ) KdPrint(( "Bad NtPathName on Share (%wZ)\n", &Share->NtPathName ));
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
InitializeObjectAttributes(
|
|
&objectAttributes,
|
|
&RootVolume,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
Status = NtOpenFile(
|
|
&VolumeHandle,
|
|
FILE_TRAVERSE,
|
|
&objectAttributes,
|
|
&iosb,
|
|
FILE_SHARE_READ|FILE_SHARE_WRITE,
|
|
0
|
|
);
|
|
if( !NT_SUCCESS(Status) )
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Get the NamesArray for the volume
|
|
Status = SrvSnapGetNamesForVolume( VolumeHandle, &pNames );
|
|
|
|
NtClose( VolumeHandle );
|
|
|
|
if( !NT_SUCCESS(Status) )
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
else if( !pNames )
|
|
{
|
|
ACQUIRE_LOCK( Share->SnapShotLock );
|
|
|
|
// No SnapShots were found, so delete any that exist
|
|
shareList = Share->SnapShots.Flink;
|
|
while( shareList != &Share->SnapShots )
|
|
{
|
|
PSHARE_SNAPSHOT snapShare = CONTAINING_RECORD( shareList, SHARE_SNAPSHOT, SnapShotList );
|
|
shareList = shareList->Flink;
|
|
SrvSnapRemoveShare( snapShare );
|
|
}
|
|
|
|
RELEASE_LOCK( Share->SnapShotLock );
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
if( pNames->MultiSzLength > 0xFFFF )
|
|
{
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
goto Cleanup;
|
|
}
|
|
|
|
VolumeName.MaximumLength = (USHORT)pNames->MultiSzLength;
|
|
VolumeName.Length = 0;
|
|
VolumeName.Buffer = pNames->Names;
|
|
|
|
ACQUIRE_LOCK( Share->SnapShotLock );
|
|
|
|
// Mark all the snap-shares as "not-found"
|
|
shareList = Share->SnapShots.Flink;
|
|
while( shareList != &Share->SnapShots )
|
|
{
|
|
PSHARE_SNAPSHOT snapShare = CONTAINING_RECORD( shareList, SHARE_SNAPSHOT, SnapShotList );
|
|
snapShare->Flags |= SRV_SNAP_SHARE_NOT_FOUND;
|
|
shareList = shareList->Flink;
|
|
}
|
|
|
|
// Walk the name list and create a SnapShot for any volumes we don't currently have
|
|
while( SrvParseMultiSZ( &VolumeName ) )
|
|
{
|
|
Status = SrvSnapCheckForAndCreateSnapShare( Share, &VolumeName );
|
|
if( !NT_SUCCESS(Status) )
|
|
{
|
|
IF_DEBUG( SNAPSHOT ) KdPrint(( "Failed to Add share %wZ (%x). Continuing..\n", &VolumeName, Status ));
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
// Any shares that are still marked as "not-found" are no longer availibe,
|
|
// so we need to remove them
|
|
shareList = Share->SnapShots.Flink;
|
|
while( shareList != &Share->SnapShots )
|
|
{
|
|
PSHARE_SNAPSHOT snapShare = CONTAINING_RECORD( shareList, SHARE_SNAPSHOT, SnapShotList );
|
|
shareList = shareList->Flink;
|
|
|
|
if( snapShare->Flags & SRV_SNAP_SHARE_NOT_FOUND )
|
|
{
|
|
SrvSnapRemoveShare( snapShare );
|
|
}
|
|
}
|
|
|
|
RELEASE_LOCK( Share->SnapShotLock );
|
|
|
|
Cleanup:
|
|
|
|
// Release the memory associated with the enumeration
|
|
if( pNames )
|
|
{
|
|
DEALLOCATE_NONPAGED_POOL( pNames );
|
|
pNames = NULL;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
SrvSnapEnumerateSnapShots(
|
|
IN PWORK_CONTEXT WorkContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function handles a transaction that wants to enumerate the availible
|
|
SnapShots for a given share
|
|
|
|
Arguments:
|
|
|
|
WorkContext - The context for the transaction
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - STATUS_SUCCESS and STATUS_BUFFER_OVERFLOW are expected, and should
|
|
be returned with data. Any other status code should be returned without data
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
ULONG SnapShotCount;
|
|
PLIST_ENTRY listEntry;
|
|
PSHARE Share = WorkContext->TreeConnect->Share;
|
|
PTRANSACTION transaction = WorkContext->Parameters.Transaction;
|
|
PSRV_SNAPSHOT_ARRAY SnapShotArray = (PSRV_SNAPSHOT_ARRAY)transaction->OutData;
|
|
|
|
ASSERT(WorkContext->TreeConnect);
|
|
|
|
// Check the buffer
|
|
if( transaction->MaxDataCount < sizeof(SRV_SNAPSHOT_ARRAY) )
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
// Refresh the SnapShot share list
|
|
Status = SrvSnapRefreshSnapShotsForShare( Share );
|
|
if( !NT_SUCCESS(Status) )
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
// Lock the share
|
|
ACQUIRE_LOCK_SHARED( Share->SnapShotLock );
|
|
|
|
// Check the buffer size
|
|
SnapShotCount = 0;
|
|
listEntry = Share->SnapShots.Blink;
|
|
while( listEntry != &(Share->SnapShots) )
|
|
{
|
|
SnapShotCount++;
|
|
listEntry = listEntry->Blink;
|
|
}
|
|
|
|
// Set the value and check if we will overflow
|
|
SnapShotArray->NumberOfSnapShots = SnapShotCount;
|
|
SnapShotArray->SnapShotArraySize = SNAPSHOT_NAME_LENGTH*SnapShotArray->NumberOfSnapShots+sizeof(WCHAR);
|
|
if( (SnapShotCount == 0) || (transaction->MaxDataCount < SnapShotArray->SnapShotArraySize) )
|
|
{
|
|
// The buffer is not big enough. Return the required size
|
|
SnapShotArray->NumberOfSnapShotsReturned = 0;
|
|
transaction->DataCount = sizeof(SRV_SNAPSHOT_ARRAY);
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
// The buffer is big enough. Fill it in and return it
|
|
PBYTE nameLocation = (PBYTE)SnapShotArray->SnapShotMultiSZ;
|
|
|
|
SnapShotCount = 0;
|
|
listEntry = Share->SnapShots.Blink;
|
|
RtlZeroMemory( SnapShotArray->SnapShotMultiSZ, SnapShotArray->SnapShotArraySize );
|
|
while( listEntry != &(Share->SnapShots) )
|
|
{
|
|
PSHARE_SNAPSHOT SnapShot = CONTAINING_RECORD( listEntry, SHARE_SNAPSHOT, SnapShotList );
|
|
RtlCopyMemory( nameLocation, SnapShot->SnapShotName.Buffer, SNAPSHOT_NAME_LENGTH );
|
|
nameLocation += SNAPSHOT_NAME_LENGTH;
|
|
SnapShotCount++;
|
|
listEntry = listEntry->Blink;
|
|
}
|
|
|
|
SnapShotArray->NumberOfSnapShotsReturned = SnapShotArray->NumberOfSnapShots;
|
|
transaction->DataCount = sizeof(SRV_SNAPSHOT_ARRAY)+SnapShotArray->SnapShotArraySize;
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
// Release the lock
|
|
RELEASE_LOCK( Share->SnapShotLock );
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
SrvSnapGetRootHandle(
|
|
IN PWORK_CONTEXT WorkContext,
|
|
OUT HANDLE* RootHandle
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function retrieves the correct Root Handle for an operation given the
|
|
parsed SnapShot timestamp on the WORK_CONTEXT
|
|
|
|
Arguments:
|
|
|
|
WorkContext - The context for the transaction
|
|
RootHandle - Where to store the resulting handle
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - STATUS_SUCCESS or STATUS_NOT_FOUND if the SnapShot is not found
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
PSHARE Share;
|
|
PLIST_ENTRY listEntry;
|
|
PSHARE_SNAPSHOT SnapShare;
|
|
|
|
ASSERT( WorkContext );
|
|
ASSERT( WorkContext->TreeConnect );
|
|
ASSERT( WorkContext->TreeConnect->Share );
|
|
Share = WorkContext->TreeConnect->Share;
|
|
|
|
if( WorkContext->SnapShotTime.QuadPart != 0 )
|
|
{
|
|
//IF_DEBUG( SNAPSHOT ) KdPrint(( "Looking for %x%x\n", WorkContext->SnapShotTime.HighPart, WorkContext->SnapShotTime.LowPart ));
|
|
|
|
// Acquire the shared lock
|
|
ACQUIRE_LOCK_SHARED( Share->SnapShotLock );
|
|
|
|
// Walk the list and look for the entry
|
|
listEntry = Share->SnapShots.Flink;
|
|
while( listEntry != &Share->SnapShots )
|
|
{
|
|
SnapShare = CONTAINING_RECORD( listEntry, SHARE_SNAPSHOT, SnapShotList );
|
|
if( SnapShare->Timestamp.QuadPart == WorkContext->SnapShotTime.QuadPart )
|
|
{
|
|
//IF_DEBUG( SNAPSHOT ) KdPrint((" Found %wZ\n", &SnapShare->SnapShotName ));
|
|
*RootHandle = SnapShare->SnapShotRootDirectoryHandle;
|
|
Status = STATUS_SUCCESS;
|
|
break;
|
|
}
|
|
|
|
listEntry = listEntry->Flink;
|
|
}
|
|
|
|
RELEASE_LOCK( Share->SnapShotLock );
|
|
}
|
|
else
|
|
{
|
|
*RootHandle = Share->RootDirectoryHandle;
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
SrvSnapGetNameString(
|
|
IN PWORK_CONTEXT WorkContext,
|
|
OUT PUNICODE_STRING* ShareName,
|
|
OUT PBOOLEAN AllocatedShareName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function retrieves the correct Root Handle for an operation given the
|
|
parsed SnapShot timestamp on the WORK_CONTEXT
|
|
|
|
Arguments:
|
|
|
|
WorkContext - The context for the transaction
|
|
ShareName - The Name of the share
|
|
AllocatedShareName - Whether the ShareName should be freed after use (via FREE_HEAP)
|
|
|
|
Return Value:
|
|
|
|
PUNICODE_STRING - pointer to existing string or NULL
|
|
BUGBUG = Have to take a reference or copy at this point!
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
PSHARE Share;
|
|
PLIST_ENTRY listEntry;
|
|
PSHARE_SNAPSHOT SnapShare;
|
|
PUNICODE_STRING SnapShareName;
|
|
|
|
ASSERT( WorkContext );
|
|
ASSERT( WorkContext->TreeConnect );
|
|
ASSERT( WorkContext->TreeConnect->Share );
|
|
Share = WorkContext->TreeConnect->Share;
|
|
|
|
if( WorkContext->SnapShotTime.QuadPart != 0 )
|
|
{
|
|
//IF_DEBUG( SNAPSHOT ) KdPrint(( "Looking for %x%x\n", WorkContext->SnapShotTime.HighPart, WorkContext->SnapShotTime.LowPart ));
|
|
|
|
// Acquire the shared lock
|
|
ACQUIRE_LOCK_SHARED( Share->SnapShotLock );
|
|
|
|
// Walk the list and look for the entry
|
|
listEntry = Share->SnapShots.Flink;
|
|
while( listEntry != &Share->SnapShots )
|
|
{
|
|
SnapShare = CONTAINING_RECORD( listEntry, SHARE_SNAPSHOT, SnapShotList );
|
|
if( SnapShare->Timestamp.QuadPart == WorkContext->SnapShotTime.QuadPart )
|
|
{
|
|
SnapShareName = ALLOCATE_HEAP( sizeof(UNICODE_STRING)+SnapShare->SnapShotPath.Length, BlockTypeSnapShot );
|
|
if( !SnapShareName )
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
*AllocatedShareName = FALSE;
|
|
}
|
|
else
|
|
{
|
|
SnapShareName->Length = 0;
|
|
SnapShareName->MaximumLength = SnapShare->SnapShotPath.Length;
|
|
SnapShareName->Buffer = (PWCHAR)(SnapShareName+1);
|
|
RtlCopyUnicodeString( SnapShareName, &SnapShare->SnapShotPath );
|
|
*AllocatedShareName = TRUE;
|
|
*ShareName = SnapShareName;
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
RELEASE_LOCK( Share->SnapShotLock );
|
|
|
|
return Status;
|
|
}
|
|
|
|
listEntry = listEntry->Flink;
|
|
}
|
|
|
|
RELEASE_LOCK( Share->SnapShotLock );
|
|
|
|
return Status;
|
|
}
|
|
else
|
|
{
|
|
*ShareName = &WorkContext->TreeConnect->Share->NtPathName;
|
|
*AllocatedShareName = FALSE;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
BOOLEAN
|
|
ExtractNumber(
|
|
IN PWSTR psz,
|
|
IN ULONG Count,
|
|
OUT PLONG 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') ||
|
|
IS_UNICODE_PATH_SEPARATOR( *psz ) )
|
|
{
|
|
//IF_DEBUG( SNAPSHOT ) KdPrint(( "Path Seperator found %d\n", Count ));
|
|
return FALSE;
|
|
}
|
|
|
|
if( (*psz < '0') || (*psz > '9') )
|
|
{
|
|
//IF_DEBUG( SNAPSHOT ) KdPrint(( "Non-digit found %x\n", *psz ));
|
|
return FALSE;
|
|
}
|
|
|
|
*value = (*value)*10+(*psz-L'0');
|
|
Count--;
|
|
psz++;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
SrvSnapParseToken(
|
|
IN PWSTR Source,
|
|
OUT PLARGE_INTEGER TimeStamp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function parses a null-terminated UNICODE file path name string to see if the
|
|
current token is a Snap-designator
|
|
|
|
Arguments:
|
|
|
|
Source - Pointer to the string
|
|
TimeStamp - If this is a SnapShot, this is set to the time value designated by the string
|
|
|
|
Return Value:
|
|
|
|
PUNICODE_STRING - pointer to existing string or NULL
|
|
|
|
--*/
|
|
{
|
|
PWSTR psz = Source;
|
|
UNICODE_STRING NameString;
|
|
ULONG Count = 0;
|
|
#define SNAPSHOT_HEADER L"@GMT-"
|
|
PWSTR header = SNAPSHOT_HEADER;
|
|
TIME_FIELDS rtlTime;
|
|
LONG value;
|
|
|
|
// Check the SNAP. header
|
|
for( Count=0; Count<wcslen(SNAPSHOT_HEADER); Count++,psz++ )
|
|
{
|
|
if( (toupper(*psz) != header[Count]) ||
|
|
(*psz == L'\0') ||
|
|
IS_UNICODE_PATH_SEPARATOR( *psz ) )
|
|
{
|
|
//IF_DEBUG( SNAPSHOT ) KdPrint(("Count %d (%x != %x)\n", Count, *psz, header[Count] ));
|
|
goto NoMatch;
|
|
}
|
|
}
|
|
|
|
// Prepare to parse
|
|
RtlZeroMemory( &rtlTime, sizeof(TIME_FIELDS) );
|
|
|
|
// Extract the Year
|
|
if( !ExtractNumber( psz, 4, &value ) )
|
|
goto NoMatch;
|
|
if( psz[4] != L'.' )
|
|
goto NoMatch;
|
|
rtlTime.Year = (CSHORT)value;
|
|
psz += 5;
|
|
|
|
// Extract the Month
|
|
if( !ExtractNumber( psz, 2, &value ) )
|
|
goto NoMatch;
|
|
if( psz[2] != L'.' )
|
|
goto NoMatch;
|
|
rtlTime.Month = (CSHORT)value;
|
|
psz += 3;
|
|
|
|
// Extract the Day
|
|
if( !ExtractNumber( psz, 2, &value ) )
|
|
goto NoMatch;
|
|
if( psz[2] != L'-' )
|
|
goto NoMatch;
|
|
rtlTime.Day = (CSHORT)value;
|
|
psz += 3;
|
|
|
|
// Extract the Hour
|
|
if( !ExtractNumber( psz, 2, &value ) )
|
|
goto NoMatch;
|
|
if( psz[2] != L'.' )
|
|
goto NoMatch;
|
|
rtlTime.Hour = (CSHORT)value;
|
|
psz += 3;
|
|
|
|
// Extract the Minutes
|
|
if( !ExtractNumber( psz, 2, &value ) )
|
|
goto NoMatch;
|
|
if( psz[2] != L'.' )
|
|
goto NoMatch;
|
|
rtlTime.Minute = (CSHORT)value;
|
|
psz += 3;
|
|
|
|
// Extract the Seconds
|
|
if( !ExtractNumber( psz, 2, &value ) )
|
|
goto NoMatch;
|
|
if( !IS_UNICODE_PATH_SEPARATOR( psz[2] ) &&
|
|
(psz[2] != L'\0') )
|
|
goto NoMatch;
|
|
rtlTime.Second = (CSHORT)value;
|
|
psz += 3;
|
|
|
|
RtlTimeFieldsToTime( &rtlTime, TimeStamp );
|
|
|
|
return TRUE;
|
|
|
|
NoMatch:
|
|
return FALSE;
|
|
}
|
|
|
|
|