/*++ Module Name: ln.c Abstract: Program to create hardlink. build: "cl ln.c /Ox" Author: Ahdrey Shedel You may distribute under the terms of the GNU General Public License You can contact author at andreys@cr.cyco.com or http://www.chat.ru/~ashedel --*/ #define WIN32_LEAN_AND_MEAN #define _DLL #define _MT #include #include #if (_WIN32_WINNT < 0x0500) #undef _WIN32_WINNT #define _WIN32_WINNT 0x0500 #endif #include #ifndef DBG #define DBG 0 #endif #pragma warning(push, 4) #pragma warning(disable:4204) // nonstandard extension used : non-constant aggregate initializer #pragma warning(disable:4201) // nonstandard extension used : nameless struct/union #pragma comment(lib,"ntdll.lib") #pragma comment(lib,"kernel32.lib") #pragma comment(lib,"shell32.lib") #pragma comment(linker, "-opt:nowin98") #pragma comment(linker, "-entry:raw_main") #pragma comment(linker, "-opt:ref") #pragma comment(linker, "-merge:.rdata=.text") #pragma comment(linker, "-subsystem:console") #if DBG #pragma comment(lib,"msvcrt.lib") #endif DBG // // Some prototypes from nt.h & ntrtl.h // typedef ULONG NTSTATUS; typedef struct { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, *PUNICODE_STRING; typedef struct { NTSTATUS Status; ULONG Information; } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; typedef struct { ULONG Length; HANDLE RootDirectory; PUNICODE_STRING ObjectName; ULONG Attributes; PVOID SecurityDescriptor; PVOID SecurityQualityOfService; } OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES; NTSYSAPI BOOLEAN NTAPI RtlCreateUnicodeString( PUNICODE_STRING DestinationString, PCWSTR SourceString ); NTSYSAPI VOID NTAPI RtlFreeUnicodeString( PUNICODE_STRING String ); NTSYSAPI BOOLEAN NTAPI RtlDosPathNameToNtPathName_U( IN PCWSTR DosName, OUT PUNICODE_STRING NtName, OUT PCWSTR* DosFilePath OPTIONAL, OUT PUNICODE_STRING NtFilePath OPTIONAL ); NTSYSAPI NTSTATUS NTAPI NtClose( IN HANDLE Handle ); typedef enum _FILE_INFORMATION_CLASS { FileLinkInformation = 11, FileDispositionInformation = 13 }FILE_INFORMATION_CLASS; typedef struct _FILE_LINK_INFORMATION { BOOLEAN ReplaceIfExists; HANDLE RootDirectory; ULONG FileNameLength; WCHAR FileName[1]; } FILE_LINK_INFORMATION, *PFILE_LINK_INFORMATION; typedef struct _FILE_DISPOSITION_INFORMATION { BOOLEAN DeleteFile; } FILE_DISPOSITION_INFORMATION, *PFILE_DISPOSITION_INFORMATION; NTSYSAPI NTSTATUS NTAPI NtFsControlFile( IN HANDLE FileHandle, IN HANDLE Event OPTIONAL, IN /*PIO_APC_ROUTINE*/PVOID ApcRoutine OPTIONAL, IN PVOID ApcContext OPTIONAL, OUT PIO_STATUS_BLOCK IoStatusBlock, IN ULONG IoControlCode, IN PVOID InputBuffer OPTIONAL, IN ULONG InputBufferLength, OUT PVOID OutputBuffer OPTIONAL, IN ULONG OutputBufferLength ); NTSYSAPI NTSTATUS NTAPI NtSetInformationFile( IN HANDLE FileHandle, OUT PIO_STATUS_BLOCK IoStatusBlock, IN PVOID FileInformation, IN ULONG Length, IN FILE_INFORMATION_CLASS FileInformationClass ); typedef struct _FILE_BASIC_INFORMATION { LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; ULONG FileAttributes; } FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION; NTSYSAPI NTSTATUS NTAPI NtQueryAttributesFile( IN POBJECT_ATTRIBUTES ObjectAttributes, OUT PFILE_BASIC_INFORMATION FileInformation ); NTSYSAPI NTSTATUS NTAPI NtCreateFile( OUT PHANDLE FileHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, OUT PIO_STATUS_BLOCK IoStatusBlock, IN PLARGE_INTEGER AllocationSize OPTIONAL, IN ULONG FileAttributes, IN ULONG ShareAccess, IN ULONG CreateDisposition, IN ULONG CreateOptions, IN PVOID EaBuffer OPTIONAL, IN ULONG EaLength ); NTSYSAPI NTSTATUS NTAPI NtDeleteFile( IN POBJECT_ATTRIBUTES ObjectAttributes ); #define InitializeObjectAttributes( p, n, a, r, s ) { \ (p)->Length = sizeof( OBJECT_ATTRIBUTES );\ (p)->RootDirectory = r;\ (p)->Attributes = a;\ (p)->ObjectName = n;\ (p)->SecurityDescriptor = s;\ (p)->SecurityQualityOfService = NULL;\ } #define OBJ_CASE_INSENSITIVE 0x00000040L #define FILE_DIRECTORY_FILE 0x00000001 #define FILE_NON_DIRECTORY_FILE 0x00000040 #define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020 #define FILE_OPEN_REPARSE_POINT 0x00200000 #define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000 #define STATUS_SUCCESS 0 #define FILE_SUPERSEDE 0x00000000 #define FILE_OPEN 0x00000001 #define FILE_CREATE 0x00000002 #define FILE_OPEN_IF 0x00000003 #define FILE_CREATED 0x00000002 // // No words... // #ifndef FILE_SPECIAL_ACCESS #define FILE_SPECIAL_ACCESS (FILE_ANY_ACCESS) #undef FSCTL_SET_REPARSE_POINT #define FSCTL_SET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, METHOD_BUFFERED, FILE_SPECIAL_ACCESS) #undef FSCTL_DELETE_REPARSE_POINT #define FSCTL_DELETE_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 43, METHOD_BUFFERED, FILE_SPECIAL_ACCESS) // REPARSE_DATA_BUFFER, #endif // // They also have removed raperse data buffer from latest headers. // #ifndef REPARSE_DATA_BUFFER_HEADER_SIZE // // The reparse structure is used by layered drivers to store data in a // reparse point. The constraints on reparse tags are defined below. // This version of the reparse data buffer is only for Microsoft tags. // typedef struct _REPARSE_DATA_BUFFER { ULONG ReparseTag; USHORT ReparseDataLength; USHORT Reserved; union { struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; WCHAR PathBuffer[1]; } SymbolicLinkReparseBuffer; struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; WCHAR PathBuffer[1]; } MountPointReparseBuffer; struct { UCHAR DataBuffer[1]; } GenericReparseBuffer; }; } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; #define REPARSE_DATA_BUFFER_HEADER_SIZE FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer) #endif // // Strings. // static const wchar_t NTDLL_DLL[] = L"ntdll.dll"; static const char FAILED_PREFIX[] = " failed. "; static const wchar_t DOSDEV_PREFIX_1[] = L"\\??\\"; static const wchar_t DOSDEV_PREFIX_2[] = L"\\DosDevices\\"; static const char USAGE_STRING[] = "ln: create/delete links for NT.\nCopyright (c) 2000 Andrey Shedel ,\nhttp://www.chat.ru/~ashedel\n\nUsage: ln [-fis] [-u|]\n -f - force overwrite/delete\n -i - ignore case\n -s - symlink\n -u - unlink\n"; static const char OPEN_PREFIX[] = "Open"; static const char DOIT_PREFIX[] = "Create link"; static const char DOIT_DELETE_PREFIX[] = "Delete link"; // // Local support routines. // void __fastcall _puts( IN PCSTR msg ) { ULONG Written; WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), msg, strlen(msg), &Written, NULL); } PCSTR __fastcall GetErrorString( IN NTSTATUS error ) { PCSTR lpMsgBuf; FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE, GetModuleHandleW(NTDLL_DLL), error, 0, (char*)&lpMsgBuf, 0, NULL); return lpMsgBuf; } void __fastcall pstatus( IN NTSTATUS Status ) { if(STATUS_SUCCESS == Status) { ; } else { PCSTR s = GetErrorString(Status); _puts(FAILED_PREFIX); if(NULL != s) { _puts(s); LocalFree((HLOCAL)s); } } } // // Allocate NtName using Name either copying or converting. // NTSTATUS __fastcall _get_nt_name( IN PCWSTR Name, OUT PUNICODE_STRING NtName ) { if((0 == wcsncmp(Name, DOSDEV_PREFIX_1, 4)) || (0 == _wcsnicmp(Name, DOSDEV_PREFIX_2, 12)) || !RtlDosPathNameToNtPathName_U(Name, NtName, NULL, NULL)) { if(!RtlCreateUnicodeString(NtName, Name)) { return STATUS_NO_MEMORY; } } #if DBG printf("RealName: %ws, length = 0x%08lx\n", NtName->Buffer, NtName->Length); #endif DBG return STATUS_SUCCESS; } // // Open subject. // NTSTATUS __fastcall _open_source( IN PHANDLE Handle, IN PCWSTR Name, IN BOOLEAN IgnoreCase, IN BOOLEAN ForSymlink, IN BOOLEAN CreateAlways, IN BOOLEAN IsDir, OUT PBOOLEAN Created ) { UNICODE_STRING UnicodeString; OBJECT_ATTRIBUTES ObjectAttributes; IO_STATUS_BLOCK IoStatusBlock; NTSTATUS Status; // // Get full NT name. // Status = _get_nt_name(Name, &UnicodeString); if(STATUS_SUCCESS != Status) { return Status; } // // Create/open the file. // InitializeObjectAttributes(&ObjectAttributes, &UnicodeString, IgnoreCase ? OBJ_CASE_INSENSITIVE : 0, NULL, NULL ); Status = NtCreateFile(Handle, SYNCHRONIZE | DELETE | (ForSymlink ? FILE_WRITE_DATA : 0), &ObjectAttributes, &IoStatusBlock, NULL, (ForSymlink ? (FILE_ATTRIBUTE_REPARSE_POINT | (IsDir ? FILE_ATTRIBUTE_DIRECTORY : 0)) : 0), FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, (ForSymlink ? (CreateAlways ? FILE_OPEN_IF : FILE_CREATE) : FILE_OPEN), (ForSymlink ? FILE_OPEN_REPARSE_POINT : 0) | (IsDir ? FILE_DIRECTORY_FILE : FILE_NON_DIRECTORY_FILE) | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); RtlFreeUnicodeString(&UnicodeString); // // Fill in requested information. // if(NULL != Created) { *Created = (BOOLEAN)(FILE_CREATED == IoStatusBlock.Information); } return Status; } // // Fast check if object is a directory file. // static BOOLEAN __fastcall _is_dir( IN PUNICODE_STRING Name ) { OBJECT_ATTRIBUTES ObjectAttributes; NTSTATUS Status; FILE_BASIC_INFORMATION FileInformation; // // Create/open the file. // InitializeObjectAttributes(&ObjectAttributes, Name, OBJ_CASE_INSENSITIVE, NULL, NULL ); Status = NtQueryAttributesFile(&ObjectAttributes, &FileInformation); return (BOOLEAN)((STATUS_SUCCESS == Status) && (0 != (FILE_ATTRIBUTE_DIRECTORY & FileInformation.FileAttributes))); } // // Hardlink. // int __fastcall hardlink( IN PCWSTR source, IN PCWSTR target, IN BOOLEAN Force, IN BOOLEAN IgnoreCase ) { HANDLE Handle; NTSTATUS Status; IO_STATUS_BLOCK IoStatusBlock; UNICODE_STRING Target; PFILE_LINK_INFORMATION FileInformation; ULONG BufferSize; // // Open source handle. // Status = _open_source(&Handle, source, IgnoreCase, FALSE, FALSE, FALSE, NULL); if(STATUS_SUCCESS != Status) { _puts(OPEN_PREFIX); pstatus(Status); return 1; } // // Get NT target name. // Status = _get_nt_name(target, &Target); if(STATUS_SUCCESS != Status) { NtClose(Handle); _puts(DOIT_PREFIX); pstatus(Status); return 1; } // // Fill in the information buffer. // BufferSize = sizeof(FILE_LINK_INFORMATION) + Target.Length + sizeof(WCHAR); FileInformation = (PFILE_LINK_INFORMATION)_alloca(BufferSize); FileInformation->ReplaceIfExists = Force; FileInformation->RootDirectory = NULL; FileInformation->FileNameLength = Target.Length; RtlCopyMemory(&FileInformation->FileName[0], Target.Buffer, FileInformation->FileNameLength); // // Try to set it. // Status = NtSetInformationFile(Handle, &IoStatusBlock, FileInformation, BufferSize, FileLinkInformation); NtClose(Handle); RtlFreeUnicodeString(&Target); if(STATUS_SUCCESS == Status) { return 0; } _puts(DOIT_PREFIX); pstatus(Status); return 1; } int __fastcall unlink( IN PCWSTR Name, IN BOOLEAN IgnoreCase, IN BOOLEAN CompleteRemove, IN BOOLEAN Symbolic ) { UNICODE_STRING UnicodeString; OBJECT_ATTRIBUTES ObjectAttributes; NTSTATUS Status; const char* prefix = DOIT_DELETE_PREFIX; // // Get full NT name. // Status = _get_nt_name(Name, &UnicodeString); if(STATUS_SUCCESS != Status) { _puts(prefix); pstatus(Status); return 1; } // // Create/open the file. // InitializeObjectAttributes(&ObjectAttributes, &UnicodeString, IgnoreCase ? OBJ_CASE_INSENSITIVE : 0, NULL, NULL ); if(Symbolic) { IO_STATUS_BLOCK IoStatusBlock; HANDLE Handle; Status = NtCreateFile(&Handle, SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA | (CompleteRemove ? DELETE : 0), &ObjectAttributes, &IoStatusBlock, NULL, 0, FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, FILE_OPEN, FILE_OPEN_REPARSE_POINT | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); if(STATUS_SUCCESS == Status) { PREPARSE_DATA_BUFFER ReparseBuffer = (PREPARSE_DATA_BUFFER)_alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE); ReparseBuffer->Reserved = 0; ReparseBuffer->ReparseDataLength = 0; ReparseBuffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; Status = NtFsControlFile(Handle, NULL, NULL, NULL, &IoStatusBlock, FSCTL_GET_REPARSE_POINT, NULL, 0, ReparseBuffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE); if((STATUS_SUCCESS != Status) || (REPARSE_DATA_BUFFER_HEADER_SIZE > IoStatusBlock.Information)) { ReparseBuffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; } ReparseBuffer->Reserved = 0; ReparseBuffer->ReparseDataLength = 0; Status = NtFsControlFile(Handle, NULL, NULL, NULL, &IoStatusBlock, FSCTL_DELETE_REPARSE_POINT, ReparseBuffer, REPARSE_DATA_BUFFER_HEADER_SIZE, NULL, 0); if((STATUS_SUCCESS == Status) && CompleteRemove) { FILE_DISPOSITION_INFORMATION di = {TRUE}; Status = NtSetInformationFile(Handle, &IoStatusBlock, &di, sizeof(FILE_DISPOSITION_INFORMATION), FileDispositionInformation); } NtClose(Handle); } else { prefix = OPEN_PREFIX; } } else { Status = NtDeleteFile(&ObjectAttributes); } RtlFreeUnicodeString(&UnicodeString); if(STATUS_SUCCESS != Status) { _puts(prefix); pstatus(Status); return 1; } return 0; } // // Symdlink. // int __fastcall symlink( IN PCWSTR source, IN PCWSTR target, IN BOOLEAN Force, IN BOOLEAN IgnoreCase ) { HANDLE Handle; NTSTATUS Status; IO_STATUS_BLOCK IoStatusBlock; UNICODE_STRING Target; BOOLEAN Created; PREPARSE_DATA_BUFFER ReparseBuffer; BOOLEAN IsDir; // // Get full name of the file to be linked. // Status = _get_nt_name(target, &Target); if(STATUS_SUCCESS != Status) { _puts(DOIT_PREFIX); pstatus(Status); return 1; } // // Check if one is a directory. // IsDir = _is_dir(&Target); // // Open source. Source is a target I want to use. // Status = _open_source(&Handle, source, IgnoreCase, TRUE, Force, TRUE/*IsDir*/, &Created); if(STATUS_SUCCESS != Status) { _puts(OPEN_PREFIX); pstatus(Status); return 1; } ReparseBuffer = (PREPARSE_DATA_BUFFER)_alloca(REPARSE_DATA_BUFFER_HEADER_SIZE + (Target.Length + 1 + 1) * sizeof(WCHAR)); // // Fill in reparse buffer. // ReparseBuffer->ReparseTag = /*IsDir ? */IO_REPARSE_TAG_MOUNT_POINT/* : IO_REPARSE_TAG_SYMBOLIC_LINK*/; ReparseBuffer->Reserved = 0; ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0; ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameLength = Target.Length; RtlCopyMemory(&ReparseBuffer->SymbolicLinkReparseBuffer.PathBuffer[0], Target.Buffer, ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameLength); if(IsDir && (L'\\' != ReparseBuffer->SymbolicLinkReparseBuffer.PathBuffer[(ReparseBuffer->MountPointReparseBuffer.SubstituteNameLength - 1) / sizeof(WCHAR)])) { ReparseBuffer->SymbolicLinkReparseBuffer.PathBuffer[(ReparseBuffer->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR))] = L'\\'; ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameLength += sizeof(WCHAR); } ReparseBuffer->SymbolicLinkReparseBuffer.PathBuffer[(ReparseBuffer->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR))] = L'\0'; ReparseBuffer->SymbolicLinkReparseBuffer.PrintNameOffset = (USHORT)(ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameLength + sizeof(WCHAR)); ReparseBuffer->SymbolicLinkReparseBuffer.PrintNameLength = 0; ReparseBuffer->ReparseDataLength = (USHORT)(sizeof(ReparseBuffer->SymbolicLinkReparseBuffer) + ReparseBuffer->SymbolicLinkReparseBuffer.PrintNameOffset + ReparseBuffer->SymbolicLinkReparseBuffer.PrintNameLength); RtlFreeUnicodeString(&Target); Status = NtFsControlFile(Handle, NULL, NULL, NULL, &IoStatusBlock, FSCTL_SET_REPARSE_POINT, ReparseBuffer, REPARSE_DATA_BUFFER_HEADER_SIZE + ReparseBuffer->ReparseDataLength, NULL, 0); if((STATUS_SUCCESS != Status) && Created) { // // Remove created object. // FILE_DISPOSITION_INFORMATION di = {TRUE}; NtSetInformationFile(Handle, &IoStatusBlock, &di, sizeof(FILE_DISPOSITION_INFORMATION), FileDispositionInformation); } NtClose(Handle); if(STATUS_SUCCESS == Status) { return 0; } _puts(DOIT_PREFIX); pstatus(Status); return 1; } // // usage information. // void __fastcall usage(void) { _puts(USAGE_STRING); } // // Real main entry. // In debug build it is used as called from wmainCRTStartup // int __cdecl wmain( IN int argc, IN PCWSTR* argv ) { PCWSTR Source = NULL; PCWSTR Target = NULL; BOOLEAN Force = FALSE; BOOLEAN Symlink = FALSE; BOOLEAN IgnoreCase = FALSE; BOOLEAN Unlink = FALSE; // // Parse. // while (argc > 1) { PCWSTR arg = *(++argv); --argc; // // Skip empty. // if(L'\0' == arg[0]) continue; // // Interpret flags. // if((L'-' == *arg) || (L'?' == *arg)) { ++arg; do { wchar_t c = *arg; if(L'f' == c) { Force = TRUE; } else if(L'i' == c) { IgnoreCase = TRUE; } else if(L's' == c) { Symlink = TRUE; } else if(L'u' == c) { Unlink = TRUE; } else { usage(); return (L'?' == c) ? 0 : 1; } ++arg; }while(L'\0' != *arg); } else { // // Set source/target. // if(NULL == Source) { Source = arg; } else if(NULL == Target) { Target = arg; } else { usage(); return 1; } } } // // Check for requied parameter set. // if((NULL == Source) || (!Unlink && (NULL == Target)) || (Unlink && (NULL != Target))) { usage(); return 1; } // // Execute. // if(Unlink) return unlink(Source, IgnoreCase, Force, Symlink); else { if(Symlink) return symlink(Source, Target, Force, IgnoreCase); else return hardlink(Source, Target, Force, IgnoreCase); } } // // Raw main entry for release version. // void __cdecl raw_main(void) { int argc; LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); ExitProcess(wmain(argc, argv)); }