windows-nt/Source/XPSP1/NT/sdktools/symlink/common.c
2020-09-26 16:20:57 +08:00

825 lines
24 KiB
C

/*++
Copyright (c) 1994 Microsoft Corporation
Module Name:
common.c
Abstract:
This module contains common apis used by tlist & kill.
Author:
Wesley Witt (wesw) 20-May-1994
Environment:
User Mode
--*/
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include "common.h"
BOOLEAN
MassageLinkValue(
IN LPCWSTR lpLinkName,
IN LPCWSTR lpLinkValue,
OUT PUNICODE_STRING NtLinkName,
OUT PUNICODE_STRING NtLinkValue,
OUT PUNICODE_STRING DosLinkValue
)
{
PWSTR FilePart;
PWSTR s, sBegin, sBackupLimit;
NTSTATUS Status;
USHORT nSaveNtNameLength;
ULONG nLevels;
//
// Initialize output variables to NULL
//
RtlInitUnicodeString( NtLinkName, NULL );
RtlInitUnicodeString( NtLinkValue, NULL );
//
// Translate link name into full NT path.
//
if (!RtlDosPathNameToNtPathName_U( lpLinkName,
NtLinkName,
NULL,
NULL
)
) {
return FALSE;
}
//
// All done if no link value.
//
if (!ARGUMENT_PRESENT( lpLinkValue )) {
return TRUE;
}
//
// If the target is a device, do not allow the link.
//
if (RtlIsDosDeviceName_U( (PWSTR)lpLinkValue )) {
return FALSE;
}
//
// Convert to DOS path to full path, and get Nt representation
// of DOS path.
//
if (!RtlGetFullPathName_U( lpLinkValue,
DosLinkValue->MaximumLength,
DosLinkValue->Buffer,
NULL
)
) {
return FALSE;
}
DosLinkValue->Length = wcslen( DosLinkValue->Buffer ) * sizeof( WCHAR );
//
// Verify that the link value is a valid NT name.
//
if (!RtlDosPathNameToNtPathName_U( DosLinkValue->Buffer,
NtLinkValue,
NULL,
NULL
)
) {
return FALSE;
}
return TRUE;
}
BOOL
CreateSymbolicLinkW(
LPCWSTR lpLinkName,
LPCWSTR lpLinkValue,
BOOLEAN IsMountPoint,
LPSECURITY_ATTRIBUTES lpSecurityAttributes
)
/*++
Routine Description:
A symbolic link is established using CreateSymbolicLink.
Arguments:
lpLinkName - Supplies the DOS file name where the symbolic link is desired. This
name must not exists as a file/directory.
lpLinkValue - Points to an DOS name which is the value of the symbolic link. This
name may or may not exist.
lpSecurityAttributes - Points to a SECURITY_ATTRIBUTES structure that specifies
the security attributes for the directory to be created. The file system must
support this parameter for it to have an effect.
Return Value:
TRUE - The operation was successful.
FALSE/NULL - The operation failed. Extended error status is available
using GetLastError.
--*/
{
NTSTATUS Status;
BOOLEAN TranslationStatus;
UNICODE_STRING NtLinkName;
UNICODE_STRING NtLinkValue;
UNICODE_STRING DosLinkValue;
OBJECT_ATTRIBUTES ObjectAttributes;
IO_STATUS_BLOCK IoStatusBlock;
HANDLE FileHandle;
ULONG FileAttributes;
ULONG OpenOptions;
ULONG ReparseDataLength;
PREPARSE_DATA_BUFFER ReparseBufferHeader;
WCHAR FullPathLinkValue[ DOS_MAX_PATH_LENGTH+1 ];
//
// Ensure that both names were passed.
//
if (!ARGUMENT_PRESENT( lpLinkName ) || !ARGUMENT_PRESENT( lpLinkValue )) {
SetLastError( ERROR_INVALID_NAME );
return FALSE;
}
//
// Convert link name and value paths into NT versions
//
DosLinkValue.Buffer = FullPathLinkValue;
DosLinkValue.MaximumLength = sizeof( FullPathLinkValue );
DosLinkValue.Length = 0;
if (!MassageLinkValue( lpLinkName,
lpLinkValue,
&NtLinkName,
&NtLinkValue,
&DosLinkValue
)
) {
if (DosLinkValue.Length == 0) {
SetLastError( ERROR_INVALID_NAME );
}
else {
SetLastError( ERROR_PATH_NOT_FOUND );
}
RtlFreeUnicodeString( &NtLinkName );
RtlFreeUnicodeString( &NtLinkValue );
return FALSE;
}
InitializeObjectAttributes( &ObjectAttributes,
&NtLinkName,
OBJ_CASE_INSENSITIVE,
NULL,
NULL
);
if (ARGUMENT_PRESENT( lpSecurityAttributes )) {
ObjectAttributes.SecurityDescriptor = lpSecurityAttributes->lpSecurityDescriptor;
}
//
// Notice that FILE_OPEN_REPARSE_POINT inhibits the reparse behavior.
//
OpenOptions = FILE_OPEN_REPARSE_POINT | FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE;
//
// Open link name. Must NOT exist.
//
Status = NtCreateFile( &FileHandle,
FILE_LIST_DIRECTORY | FILE_WRITE_DATA | FILE_READ_ATTRIBUTES |
FILE_WRITE_ATTRIBUTES | SYNCHRONIZE,
&ObjectAttributes,
&IoStatusBlock,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_CREATE,
OpenOptions,
NULL,
0
);
//
// Free the buffer for the link name as we are done with it.
//
RtlFreeUnicodeString( &NtLinkName );
if (!NT_SUCCESS( Status )) {
SetLastError( ERROR_INVALID_NAME );
RtlFreeUnicodeString( &NtLinkValue );
return FALSE;
}
//
// Allocate a buffer to set the reparse point.
//
ReparseDataLength = (FIELD_OFFSET(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) -
REPARSE_DATA_BUFFER_HEADER_SIZE) +
NtLinkValue.Length + sizeof(UNICODE_NULL) +
DosLinkValue.Length + sizeof(UNICODE_NULL);
ReparseBufferHeader = (PREPARSE_DATA_BUFFER)
RtlAllocateHeap( RtlProcessHeap(),
HEAP_ZERO_MEMORY,
REPARSE_DATA_BUFFER_HEADER_SIZE + ReparseDataLength
);
if (ReparseBufferHeader == NULL) {
NtClose( FileHandle );
RtlFreeUnicodeString( &NtLinkValue );
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
return FALSE;
}
//
// Set the reparse point with symbolic link tag.
//
if (IsMountPoint) {
ReparseBufferHeader->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
}
else {
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// No support for symbolic links in NT 5.0 Beta 1
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// ReparseBufferHeader->ReparseTag = IO_REPARSE_TAG_SYMBOLIC_LINK;
//
SetLastError( ERROR_INVALID_PARAMETER );
return FALSE;
}
ReparseBufferHeader->ReparseDataLength = (USHORT)ReparseDataLength;
ReparseBufferHeader->Reserved = 0;
ReparseBufferHeader->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0;
ReparseBufferHeader->SymbolicLinkReparseBuffer.SubstituteNameLength = NtLinkValue.Length;
ReparseBufferHeader->SymbolicLinkReparseBuffer.PrintNameOffset = NtLinkValue.Length + sizeof( UNICODE_NULL );
ReparseBufferHeader->SymbolicLinkReparseBuffer.PrintNameLength = NtLinkValue.Length;
RtlCopyMemory( ReparseBufferHeader->SymbolicLinkReparseBuffer.PathBuffer,
NtLinkValue.Buffer,
NtLinkValue.Length
);
RtlCopyMemory( (PCHAR)(ReparseBufferHeader->SymbolicLinkReparseBuffer.PathBuffer)+
NtLinkValue.Length + sizeof(UNICODE_NULL),
DosLinkValue.Buffer,
DosLinkValue.Length
);
RtlFreeUnicodeString( &NtLinkValue );
Status = NtFsControlFile( FileHandle,
NULL,
NULL,
NULL,
&IoStatusBlock,
FSCTL_SET_REPARSE_POINT,
ReparseBufferHeader,
REPARSE_DATA_BUFFER_HEADER_SIZE + ReparseDataLength,
NULL,
0
);
RtlFreeHeap( RtlProcessHeap(), 0, ReparseBufferHeader );
NtClose( FileHandle );
if (!NT_SUCCESS( Status )) {
return FALSE;
}
return TRUE;
}
BOOL
SetSymbolicLinkW(
LPCWSTR lpLinkName,
LPCWSTR lpLinkValue
)
/*++
Routine Description:
A symbolic link is established using CreateSymbolicLink.
Arguments:
lpLinkName - Supplies the DOS file name where the symbolic link is located. This
name must exist as a symbolic link to a file/directory.
lpLinkValue - Points to an DOS name which is the value of the symbolic link. This
name may or may not exist.
Return Value:
TRUE - The operation was successful.
FALSE/NULL - The operation failed. Extended error status is available
using GetLastError.
--*/
{
NTSTATUS Status;
BOOLEAN TranslationStatus;
UNICODE_STRING NtLinkName;
UNICODE_STRING NtLinkValue;
UNICODE_STRING DosLinkValue;
OBJECT_ATTRIBUTES ObjectAttributes;
IO_STATUS_BLOCK IoStatusBlock;
HANDLE FileHandle;
ACCESS_MASK FileAccess;
ULONG OpenOptions;
ULONG ReparseDataLength;
PREPARSE_DATA_BUFFER ReparseBufferHeader;
WCHAR FullPathLinkValue[ DOS_MAX_PATH_LENGTH+1 ];
//
// Ensure that link name was passed.
//
if (!ARGUMENT_PRESENT( lpLinkName )) {
SetLastError( ERROR_INVALID_NAME );
return FALSE;
}
//
// Convert link name and value paths into NT versions
//
DosLinkValue.Buffer = FullPathLinkValue;
DosLinkValue.MaximumLength = sizeof( FullPathLinkValue );
DosLinkValue.Length = 0;
if (!MassageLinkValue( lpLinkName,
lpLinkValue,
&NtLinkName,
&NtLinkValue,
&DosLinkValue
)
) {
if (DosLinkValue.Length == 0) {
SetLastError( ERROR_INVALID_NAME );
}
else {
SetLastError( ERROR_PATH_NOT_FOUND );
}
RtlFreeUnicodeString( &NtLinkName );
RtlFreeUnicodeString( &NtLinkValue );
return FALSE;
}
InitializeObjectAttributes( &ObjectAttributes,
&NtLinkName,
OBJ_CASE_INSENSITIVE,
NULL,
NULL
);
//
// Notice that FILE_OPEN_REPARSE_POINT inhibits the reparse behavior.
//
OpenOptions = FILE_OPEN_FOR_BACKUP_INTENT |
FILE_OPEN_REPARSE_POINT |
FILE_SYNCHRONOUS_IO_NONALERT |
FILE_NON_DIRECTORY_FILE;
//
// If no link value specified, then deleting the link
//
if (!ARGUMENT_PRESENT( lpLinkValue )) {
FileAccess = DELETE | SYNCHRONIZE;
}
else {
FileAccess = FILE_WRITE_DATA | FILE_READ_ATTRIBUTES |
FILE_WRITE_ATTRIBUTES | SYNCHRONIZE;
}
//
// Open link name. Must exists.
//
Status = NtOpenFile( &FileHandle,
FileAccess,
&ObjectAttributes,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
OpenOptions
);
//
// Free the buffer for the link name as we are done with it.
//
RtlFreeUnicodeString( &NtLinkName );
if (!NT_SUCCESS( Status )) {
RtlFreeUnicodeString( &NtLinkValue );
SetLastError( ERROR_INVALID_NAME );
return FALSE;
}
if (!ARGUMENT_PRESENT( lpLinkValue )) {
FILE_DISPOSITION_INFORMATION Disposition;
//
// Delete the link
//
#undef DeleteFile
Disposition.DeleteFile = TRUE;
Status = NtSetInformationFile( FileHandle,
&IoStatusBlock,
&Disposition,
sizeof( Disposition ),
FileDispositionInformation
);
NtClose( FileHandle );
if (!NT_SUCCESS( Status )) {
return FALSE;
}
else {
return TRUE;
}
}
//
// Allocate a buffer to set the reparse point.
//
ReparseDataLength = (FIELD_OFFSET(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) -
REPARSE_DATA_BUFFER_HEADER_SIZE) +
NtLinkValue.Length + sizeof(UNICODE_NULL) +
DosLinkValue.Length + sizeof(UNICODE_NULL);
ReparseBufferHeader = (PREPARSE_DATA_BUFFER)
RtlAllocateHeap( RtlProcessHeap(),
HEAP_ZERO_MEMORY,
REPARSE_DATA_BUFFER_HEADER_SIZE + ReparseDataLength
);
if (ReparseBufferHeader == NULL) {
RtlFreeUnicodeString( &NtLinkValue );
NtClose( FileHandle );
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
return FALSE;
}
//
// Set the reparse point with symbolic link tag.
//
ReparseBufferHeader->ReparseTag = IO_REPARSE_TAG_SYMBOLIC_LINK;
ReparseBufferHeader->ReparseDataLength = (USHORT)ReparseDataLength;
ReparseBufferHeader->Reserved = 0;
ReparseBufferHeader->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0;
ReparseBufferHeader->SymbolicLinkReparseBuffer.SubstituteNameLength = NtLinkValue.Length;
ReparseBufferHeader->SymbolicLinkReparseBuffer.PrintNameOffset = NtLinkValue.Length + sizeof( UNICODE_NULL );
ReparseBufferHeader->SymbolicLinkReparseBuffer.PrintNameLength = NtLinkValue.Length;
RtlCopyMemory( ReparseBufferHeader->SymbolicLinkReparseBuffer.PathBuffer,
NtLinkValue.Buffer,
NtLinkValue.Length
);
RtlCopyMemory( (PCHAR)(ReparseBufferHeader->SymbolicLinkReparseBuffer.PathBuffer)+
NtLinkValue.Length + sizeof(UNICODE_NULL),
DosLinkValue.Buffer,
DosLinkValue.Length
);
RtlFreeUnicodeString( &NtLinkValue );
Status = NtFsControlFile( FileHandle,
NULL,
NULL,
NULL,
&IoStatusBlock,
FSCTL_SET_REPARSE_POINT,
ReparseBufferHeader,
REPARSE_DATA_BUFFER_HEADER_SIZE + ReparseDataLength,
NULL,
0
);
RtlFreeHeap( RtlProcessHeap(), 0, ReparseBufferHeader );
NtClose( FileHandle );
if (!NT_SUCCESS( Status )) {
return FALSE;
}
return TRUE;
}
DWORD
QuerySymbolicLinkW(
LPCWSTR lpLinkName,
LPWSTR lpBuffer,
DWORD nBufferLength
)
/*++
Routine Description:
An existing file can be queried for its symbolic link value using QuerySymbolicLink.
Arguments:
lpLinkName - Supplies the file name of the file to be queried.
lpBuffer - Points to a buffer where the symbolic link is to be returned.
nBufferSize - Length of the buffer being passed by the caller.
Return Value:
If the function suceeds, the return value is the length, in characters, of the
string copied to lpBuffer, not including the terminating null character. If the
lpBuffer is too small, the return value is the size of the buffer, in characters,
required to hold the name.
Zero is returned if the operation failed. Extended error status is available
using GetLastError.
--*/
{
NTSTATUS Status;
BOOLEAN TranslationStatus;
UNICODE_STRING FileName;
RTL_RELATIVE_NAME RelativeName;
OBJECT_ATTRIBUTES ObjectAttributes;
IO_STATUS_BLOCK IoStatusBlock;
HANDLE FileHandle;
ULONG OpenOptions;
PWSTR pathBuffer;
USHORT pathLength;
USHORT NtPathLength;
USHORT ReturnLength;
PVOID FreeBuffer;
REPARSE_DATA_BUFFER ReparseInfo;
PREPARSE_DATA_BUFFER ReparseBufferHeader;
if (!ARGUMENT_PRESENT( lpLinkName )) {
SetLastError( ERROR_INVALID_NAME );
return 0;
}
TranslationStatus = RtlDosPathNameToNtPathName_U( lpLinkName,
&FileName,
NULL,
&RelativeName
);
if (!TranslationStatus) {
SetLastError( ERROR_PATH_NOT_FOUND );
return 0;
}
FreeBuffer = FileName.Buffer;
if (RelativeName.RelativeName.Length) {
FileName = *(PUNICODE_STRING)&RelativeName.RelativeName;
}
else {
RelativeName.ContainingDirectory = NULL;
}
InitializeObjectAttributes( &ObjectAttributes,
&FileName,
OBJ_CASE_INSENSITIVE,
RelativeName.ContainingDirectory,
NULL
);
//
// Notice that FILE_OPEN_REPARSE_POINT inhibits the reparse behavior.
//
OpenOptions = FILE_OPEN_REPARSE_POINT | FILE_SYNCHRONOUS_IO_NONALERT;
//
// Open as file for read access.
//
Status = NtOpenFile( &FileHandle,
FILE_READ_DATA | SYNCHRONIZE,
&ObjectAttributes,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
OpenOptions | FILE_NON_DIRECTORY_FILE
);
//
// Free the buffer for the name as we are done with it.
//
RtlFreeHeap( RtlProcessHeap(), 0, FreeBuffer );
if (!NT_SUCCESS( Status )) {
SetLastError( ERROR_INVALID_NAME );
return 0;
}
//
// Query with zero length to get reparse point tag and required buffer length
//
Status = NtFsControlFile( FileHandle,
NULL,
NULL,
NULL,
&IoStatusBlock,
FSCTL_GET_REPARSE_POINT,
NULL,
0,
(PVOID)&ReparseInfo,
sizeof( ReparseInfo )
);
//
// Verify that the reparse point buffer brings back a symbolic link or a
// mount point, and that we got the required buffer length back via
// IoStatus.Information
//
ReparseBufferHeader = NULL;
if ((Status != STATUS_BUFFER_OVERFLOW) ||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// No support for symbolic links in NT 5.0 Beta 1
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// (ReparseInfo.ReparseTag != IO_REPARSE_TAG_SYMBOLIC_LINK) ||
//
(ReparseInfo.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)
) {
Status = STATUS_OBJECT_NAME_INVALID;
}
else {
//
// Allocate a buffer to hold reparse point information
//
ReparseBufferHeader = (PREPARSE_DATA_BUFFER)
RtlAllocateHeap( RtlProcessHeap(),
0,
REPARSE_DATA_BUFFER_HEADER_SIZE + ReparseInfo.ReparseDataLength
);
if (ReparseBufferHeader == NULL) {
//
// Not enough memory. Fail the call.
//
Status = STATUS_NO_MEMORY;
}
else {
//
// Now query the reparse point information into our allocated buffer.
// This should not fail.
//
Status = NtFsControlFile( FileHandle,
NULL,
NULL,
NULL,
&IoStatusBlock,
FSCTL_GET_REPARSE_POINT,
NULL,
0,
(PVOID)ReparseBufferHeader,
REPARSE_DATA_BUFFER_HEADER_SIZE + ReparseInfo.ReparseDataLength
);
}
}
//
// Done with file handle.
//
NtClose( FileHandle );
//
// Return any failure to caller
//
if (!NT_SUCCESS( Status )) {
if (ReparseBufferHeader != NULL) {
RtlFreeHeap( RtlProcessHeap(), 0, ReparseBufferHeader );
}
return 0;
}
//
// See if this is an old style symbolic link reparse point, which only stored the
// NT path name. If so, return an error, as we dont have a DOS path to return
//
pathBuffer = (PWSTR)(
(PCHAR)ReparseBufferHeader->SymbolicLinkReparseBuffer.PathBuffer +
ReparseBufferHeader->SymbolicLinkReparseBuffer.PrintNameOffset
);
pathLength = ReparseBufferHeader->SymbolicLinkReparseBuffer.PrintNameLength;
//
// Sanity check the length. As the tag is fine we do not zero the buffer.
//
ReturnLength = pathLength / sizeof( WCHAR );
//
// If amount to return is less than callers buffer length, copy the Dos path
// to the callers buffer
//
if (ReturnLength < nBufferLength) {
RtlMoveMemory( (PUCHAR)lpBuffer,
(PCHAR)pathBuffer,
pathLength
);
}
else {
//
// If we are failing for insufficient buffer length, tell them how much
// space they really need including the terminating null character.
//
ReturnLength += 1;
SetLastError( ERROR_INSUFFICIENT_BUFFER );
}
RtlFreeHeap( RtlProcessHeap(), 0, ReparseBufferHeader );
return ReturnLength;
}
VOID
GetCommandLineArgs(
LPDWORD NumberOfArguments,
LPWSTR Arguments[]
)
{
LPWSTR lpstrCmd;
WCHAR ch;
WCHAR ArgumentBuffer[ MAX_PATH ];
LPWSTR p;
lpstrCmd = GetCommandLine();
// skip over program name
do {
ch = *lpstrCmd++;
}
while (ch != L' ' && ch != L'\t' && ch != L'\0');
*NumberOfArguments = 0;
while (ch != '\0') {
// skip over any following white space
while (ch != L'\0' && _istspace(ch)) {
ch = *lpstrCmd++;
}
if (ch == L'\0') {
break;
}
p = ArgumentBuffer;
do {
*p++ = ch;
ch = *lpstrCmd++;
} while (ch != L' ' && ch != L'\t' && ch != L'\0');
*p = L'\0';
Arguments[ *NumberOfArguments ] = malloc( (_tcslen( ArgumentBuffer ) + 1) * sizeof( WCHAR ) );
if (Arguments[ *NumberOfArguments ]) {
_tcscpy( Arguments[ *NumberOfArguments ], ArgumentBuffer );
*NumberOfArguments += 1;
}
}
return;
}