From: Elizabeth Figura zfigura@codeweavers.com
Based on patches by Erich E. Hoover. --- dlls/ntdll/tests/file.c | 748 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 740 insertions(+), 8 deletions(-)
diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index c87038d5ab1..25381caf313 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -38,6 +38,7 @@ #include "winuser.h" #include "winioctl.h" #include "winnls.h" +#include "ddk/ntifs.h"
#ifndef IO_COMPLETION_ALL_ACCESS #define IO_COMPLETION_ALL_ACCESS 0x001F0003 @@ -5970,32 +5971,763 @@ static void test_mailslot_name(void) CloseHandle( device ); }
+static size_t init_reparse_mount_point( REPARSE_DATA_BUFFER **ret_buffer, const WCHAR *target ) +{ + size_t len = (wcslen( target ) + 1) + (5 + 1); + size_t size = offsetof( REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer[len] ); + REPARSE_DATA_BUFFER *buffer; + + /* Native is very picky about the contents of the reparse buffer. + * The substitute name *must* be at offset 0, and the print name *must* + * be directly after the substitute name. Both names must be null + * terminated. ReparseDataLength, as well as the ioctl length, must be + * exactly at the null-terminated print name, no longer or shorter. */ + buffer = malloc( size ); + buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; + buffer->ReparseDataLength = size - offsetof( REPARSE_DATA_BUFFER, MountPointReparseBuffer ); + buffer->Reserved = 0; + buffer->MountPointReparseBuffer.SubstituteNameOffset = 0; + buffer->MountPointReparseBuffer.SubstituteNameLength = wcslen( target ) * sizeof(WCHAR); + buffer->MountPointReparseBuffer.PrintNameOffset = (wcslen( target ) + 1) * sizeof(WCHAR); + buffer->MountPointReparseBuffer.PrintNameLength = 5 * sizeof(WCHAR); + wcscpy( buffer->MountPointReparseBuffer.PathBuffer, target ); + wcscpy( buffer->MountPointReparseBuffer.PathBuffer + wcslen( target ) + 1, L"bogus" ); + *ret_buffer = buffer; + return size; +} + +static size_t init_reparse_symlink( REPARSE_DATA_BUFFER **ret_buffer, const WCHAR *target, ULONG flags ) +{ + size_t len = (wcslen( target ) + 1) + (5 + 1); + size_t size = offsetof( REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer[len] ); + REPARSE_DATA_BUFFER *buffer; + + buffer = malloc( size ); + buffer->ReparseTag = IO_REPARSE_TAG_SYMLINK; + buffer->ReparseDataLength = size - offsetof( REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer ); + buffer->Reserved = 0; + buffer->SymbolicLinkReparseBuffer.Flags = flags; + buffer->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0; + buffer->SymbolicLinkReparseBuffer.SubstituteNameLength = wcslen( target ) * sizeof(WCHAR); + buffer->SymbolicLinkReparseBuffer.PrintNameOffset = (wcslen( target ) + 1) * sizeof(WCHAR); + buffer->SymbolicLinkReparseBuffer.PrintNameLength = 5 * sizeof(WCHAR); + wcscpy( buffer->SymbolicLinkReparseBuffer.PathBuffer, target ); + wcscpy( buffer->SymbolicLinkReparseBuffer.PathBuffer + wcslen( target ) + 1, L"bogus" ); + *ret_buffer = buffer; + return size; +} + static void test_reparse_points(void) { - OBJECT_ATTRIBUTES attr; - HANDLE handle; + WCHAR temp_path[MAX_PATH], path[MAX_PATH], path2[MAX_PATH], ret_path[MAX_PATH]; + HANDLE temp_dir, handle, handle2, subdir, token, find_handle; + FILE_DISPOSITION_INFORMATION fdi = {.DoDeleteFile = TRUE}; + FILE_STAT_INFORMATION stat_info, stat_info2; + FILE_DIRECTORY_INFORMATION *dir_info; + FILE_BASIC_INFORMATION basic_info; + REPARSE_DATA_BUFFER *data, *data2; + char dir_buffer[1024], buffer[9]; + OBJECT_ATTRIBUTES attr, attr2; + WIN32_FIND_DATAW find_data; + TOKEN_PRIVILEGES privs; + UNICODE_STRING nameW; IO_STATUS_BLOCK io; + DWORD flags, size; + size_t data_size; NTSTATUS status; - UNICODE_STRING nameW; - unsigned char reparse_data[1]; + LUID luid; + BOOL ret; + + GetTempPathW( MAX_PATH, temp_path );
- pRtlInitUnicodeString( &nameW, L"\??\C:\" ); + RtlInitUnicodeString( &nameW, path ); InitializeObjectAttributes( &attr, &nameW, 0, NULL, NULL );
- status = pNtOpenFile( &handle, READ_CONTROL, &attr, &io, 0, 0 ); + RtlInitUnicodeString( &nameW, L"\??\C:\" ); + status = NtOpenFile( &handle, READ_CONTROL, &attr, &io, 0, 0 ); ok( !status, "open %s failed %#lx\n", wine_dbgstr_w(nameW.Buffer), status );
status = pNtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, NULL, 0 ); ok( status == STATUS_INVALID_USER_BUFFER, "expected %#lx, got %#lx\n", STATUS_INVALID_USER_BUFFER, status );
- status = pNtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, reparse_data, 0 ); + status = pNtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, buffer, 0 ); ok( status == STATUS_INVALID_USER_BUFFER, "expected %#lx, got %#lx\n", STATUS_INVALID_USER_BUFFER, status );
/* a volume cannot be a reparse point by definition */ - status = pNtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, reparse_data, 1 ); + status = pNtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, buffer, 1 ); ok( status == STATUS_NOT_A_REPARSE_POINT, "expected %#lx, got %#lx\n", STATUS_NOT_A_REPARSE_POINT, status );
CloseHandle( handle ); + + status = NtOpenFile( &handle, READ_CONTROL, &attr, &io, 0, FILE_OPEN_REPARSE_POINT ); + ok( !status, "got %#lx\n", status ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, buffer, 1 ); + ok( status == STATUS_NOT_A_REPARSE_POINT, "got %#lx\n", status ); + CloseHandle( handle ); + + status = GetVolumeInformationW( L"C:\", NULL, 0, NULL, NULL, &flags, NULL, 0 ); + ok( status == TRUE, "got error %lu\n", GetLastError() ); + todo_wine ok( flags & FILE_SUPPORTS_REPARSE_POINTS, "C: drive does not support reparse points\n" ); + + swprintf( path, ARRAY_SIZE(path), L"\??\%s", temp_path ); + RtlInitUnicodeString( &nameW, path ); + status = NtOpenFile( &temp_dir, READ_CONTROL, &attr, &io, 0, FILE_OPEN_REPARSE_POINT ); + ok( !status, "got %#lx\n", status ); + + attr.RootDirectory = temp_dir; + RtlInitUnicodeString( &nameW, L"testreparse_file" ); + status = NtCreateFile( &handle, SYNCHRONIZE | GENERIC_WRITE, &attr, &io, NULL, 0, 0, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, buffer, 1 ); + ok( status == STATUS_NOT_A_REPARSE_POINT, "got %#lx\n", status ); + ret = WriteFile( handle, "file1", 5, &size, NULL ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + CloseHandle( handle ); + + attr.RootDirectory = temp_dir; + RtlInitUnicodeString( &nameW, L"testreparse_dir" ); + status = NtCreateFile( &handle, GENERIC_WRITE, &attr, &io, NULL, 0, 0, FILE_OPEN_IF, FILE_DIRECTORY_FILE, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, buffer, 1 ); + ok( status == STATUS_NOT_A_REPARSE_POINT, "got %#lx\n", status ); + CloseHandle( handle ); + + data_size = init_reparse_mount_point( &data, L"foobar" ); + + RtlInitUnicodeString( &nameW, L"testreparse_filelink" ); + status = NtCreateFile( &handle, GENERIC_ALL, &attr, &io, NULL, 0, 0, + FILE_OPEN_IF, FILE_OPEN_REPARSE_POINT, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, buffer, 1 ); + ok( status == STATUS_NOT_A_REPARSE_POINT, "got %#lx\n", status ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, data_size, NULL, 0 ); + todo_wine ok( status == STATUS_NOT_A_DIRECTORY, "got %#lx\n", status ); + + status = NtSetInformationFile( handle, &io, &fdi, sizeof(fdi), FileDispositionInformation ); + ok( !status, "got %#lx\n", status ); + CloseHandle( handle ); + + RtlInitUnicodeString( &nameW, L"testreparse_dirlink" ); + status = NtCreateFile( &handle, GENERIC_ALL, &attr, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN_IF, FILE_DIRECTORY_FILE, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, buffer, 1 ); + ok( status == STATUS_NOT_A_REPARSE_POINT, "got %#lx\n", status ); + + /* create a file under the directory before turning it into a reparse point */ + swprintf( path, ARRAY_SIZE(path), L"%s/testreparse_dirlink/file", temp_path ); + handle2 = CreateFileW( path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0 ); + ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + CloseHandle( handle2 ); + + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, data_size, NULL, 0 ); + todo_wine ok( status == STATUS_DIRECTORY_NOT_EMPTY, "got %#lx\n", status ); + + ret = DeleteFileW( path ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, data_size, NULL, 0 ); + todo_wine ok( !status, "got %#lx\n", status ); + if (status) + { + status = NtSetInformationFile( handle, &io, &fdi, sizeof(fdi), FileDispositionInformation ); + ok( !status, "got %#lx\n", status ); + NtClose( handle ); + goto out; + } + + data2 = malloc( data_size + 1 ); + memset( data2, 0xcc, data_size + 1 ); + io.Information = 1; + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, data2, sizeof(REPARSE_DATA_BUFFER) - 1 ); + ok( status == STATUS_BUFFER_TOO_SMALL, "got %#lx\n", status ); + ok( io.Information == 1, "got size %#Ix\n", io.Information ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, data2, data_size - 1); + ok( status == STATUS_BUFFER_OVERFLOW, "got %#lx\n", status ); + ok( io.Information == data_size - 1, "expected size %#Ix, got %#Ix\n", data_size, io.Information ); + ok( !memcmp( data, data2, data_size - 1 ), "buffers didn't match\n" ); + + io.Information = 1; + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, data2, data_size + 1); + ok( !status, "got %#lx\n", status ); + ok( io.Information == data_size, "expected size %#Ix, got %#Ix\n", data_size, io.Information ); + ok( !memcmp( data, data2, data_size ), "buffers didn't match\n" ); + + status = NtQueryInformationFile( handle, &io, &stat_info, sizeof(stat_info), FileStatInformation ); + ok( !status, "got %#lx\n", status ); + ok( stat_info.FileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), + "got attributes %#lx\n", stat_info.FileAttributes ); + ok( stat_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", stat_info.ReparseTag ); + ok( !stat_info.AllocationSize.QuadPart, "got size %#I64x\n", stat_info.AllocationSize.QuadPart ); + ok( !stat_info.EndOfFile.QuadPart, "got eof %#I64x\n", stat_info.EndOfFile.QuadPart ); + + status = NtCreateFile( &handle2, GENERIC_ALL, &attr, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN_IF, FILE_DIRECTORY_FILE, NULL, 0 ); + ok( status == STATUS_OBJECT_NAME_INVALID, "got %#lx\n", status ); + + status = NtCreateFile( &handle2, GENERIC_ALL, &attr, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN_IF, FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + status = NtQueryInformationFile( handle, &io, &stat_info, sizeof(stat_info), FileStatInformation ); + ok( !status, "got %#lx\n", status ); + ok( stat_info.FileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), + "got attributes %#lx\n", stat_info.FileAttributes ); + ok( stat_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", stat_info.ReparseTag ); + ok( !stat_info.AllocationSize.QuadPart, "got size %#I64x\n", stat_info.AllocationSize.QuadPart ); + ok( !stat_info.EndOfFile.QuadPart, "got eof %#I64x\n", stat_info.EndOfFile.QuadPart ); + NtClose( handle2 ); + + /* alter the target in-place */ + data_size = init_reparse_mount_point( &data, L"\??\C:\bogus" ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, data_size, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + + status = NtCreateFile( &handle2, GENERIC_READ, &attr, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, FILE_DIRECTORY_FILE, NULL, 0 ); + ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "got %#lx\n", status ); + + swprintf( path, ARRAY_SIZE(path), L"\??\%stestreparse_dir\", temp_path ); + data_size = init_reparse_mount_point( &data, path ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, data_size, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + + status = NtCreateFile( &handle2, GENERIC_READ, &attr, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, FILE_DIRECTORY_FILE, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + NtClose( handle2 ); + + /* Test NtQueryDirectoryFile and FindFirstFile() on the reparse point. */ + + swprintf( path, ARRAY_SIZE(path), L"%s/testreparse_dirlink/file", temp_path ); + handle2 = CreateFileW( path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0 ); + ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + CloseHandle( handle2 ); + + status = NtQueryDirectoryFile( handle, NULL, NULL, NULL, &io, dir_buffer, sizeof(dir_buffer), + FileDirectoryInformation, FALSE, NULL, TRUE ); + ok( status == STATUS_PENDING, "got %#lx\n", status ); + ret = WaitForSingleObject( handle, 0 ); + ok( !ret, "got %#x\n", ret ); + dir_info = (FILE_DIRECTORY_INFORMATION *)dir_buffer; + ok( dir_info->FileNameLength == sizeof(WCHAR) && !memcmp( dir_info->FileName, L".", dir_info->FileNameLength ), + "got name %s\n", debugstr_wn( dir_info->FileName, dir_info->FileNameLength / sizeof(WCHAR) )); + ok( !!dir_info->NextEntryOffset, "expected a next entry\n" ); + dir_info = (FILE_DIRECTORY_INFORMATION *)(dir_buffer + dir_info->NextEntryOffset); + ok( dir_info->FileNameLength == 2 * sizeof(WCHAR) && !memcmp( dir_info->FileName, L"..", dir_info->FileNameLength ), + "got name %s\n", debugstr_wn( dir_info->FileName, dir_info->FileNameLength / sizeof(WCHAR) )); + ok( !dir_info->NextEntryOffset, "expected no next entry\n" ); + + swprintf( path, ARRAY_SIZE(path), L"%s/testreparse_dirlink/*.*", temp_path ); + find_handle = FindFirstFileW( path, &find_data ); + ok( find_handle != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + ok( !wcscmp( find_data.cFileName, L"." ), "got file %s\n", debugstr_w( find_data.cFileName )); + ret = FindNextFileW( find_handle, &find_data ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + ok( !wcscmp( find_data.cFileName, L".." ), "got file %s\n", debugstr_w( find_data.cFileName )); + ret = FindNextFileW( find_handle, &find_data ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + ok( !wcscmp( find_data.cFileName, L"file" ), "got file %s\n", debugstr_w( find_data.cFileName )); + ret = FindNextFileW( find_handle, &find_data ); + ok( ret == FALSE, "got %d\n", ret ); + ok( GetLastError() == ERROR_NO_MORE_FILES, "got error %lu\n", GetLastError() ); + FindClose( find_handle ); + + /* Test GetFullPathNameW(). */ + + swprintf( path, ARRAY_SIZE(path), L"%stestreparse_dirlink\file", temp_path ); + GetFullPathNameW( path, ARRAY_SIZE( ret_path ), ret_path, NULL ); + ok( !wcscmp( ret_path, path ), "expected path %s, got %s\n", debugstr_w( path ), debugstr_w( ret_path )); + + /* Test changing directory. */ + + GetCurrentDirectoryW( ARRAY_SIZE( path2 ), path2 ); + + swprintf( path, ARRAY_SIZE(path), L"%stestreparse_dirlink", temp_path ); + ret = SetCurrentDirectoryW( path ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + ret = GetCurrentDirectoryW( ARRAY_SIZE( ret_path ), ret_path ); + ok( ret > 0, "got error %lu\n", GetLastError() ); + ok( !wcscmp( ret_path, path ), "expected path %s, got %s\n", debugstr_w( path ), debugstr_w( ret_path )); + + ret = GetFileAttributesW( L"file" ); + ok( ret == FILE_ATTRIBUTE_ARCHIVE, "got %#x\n", ret ); + ret = GetFileAttributesW( L"." ); + ok( ret == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), "got %#x\n", ret ); + + SetCurrentDirectoryW( path2 ); + + swprintf( path, ARRAY_SIZE(path), L"%stestreparse_dirlink\file", temp_path ); + ret = DeleteFileW( path ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + + /* FindFirstFile() puts the reparse tag in dwReserved0. */ + + swprintf( path, ARRAY_SIZE(path), L"%s/testreparse_dirlink", temp_path ); + find_handle = FindFirstFileW( path, &find_data ); + ok( find_handle != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + ok( find_data.dwFileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), + "got attributes %#lx\n", find_data.dwFileAttributes ); + ok( find_data.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", find_data.dwReserved0 ); + FindClose( find_handle ); + + /* Attempting to use the reparse point itself as a parent results in the + * somewhat cryptic STATUS_REPARSE_POINT_NOT_RESOLVED. + * + * Attempting to open the same file, using an empty name, results in the + * even more cryptic STATUS_IO_REPARSE_DATA_INVALID. + * It works with FILE_OPEN_REPARSE_POINT, though. */ + + status = NtCreateFile( &handle2, GENERIC_READ, &attr, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + InitializeObjectAttributes( &attr2, &nameW, 0, handle2, NULL ); + RtlInitUnicodeString( &nameW, L"subdir" ); + status = NtCreateFile( &subdir, GENERIC_ALL, &attr2, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN_IF, 0, NULL, 0 ); + ok( status == STATUS_REPARSE_POINT_NOT_RESOLVED, "got %#lx\n", status ); + status = NtCreateFile( &subdir, GENERIC_ALL, &attr2, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN_IF, FILE_OPEN_REPARSE_POINT, NULL, 0 ); + ok( status == STATUS_REPARSE_POINT_NOT_RESOLVED, "got %#lx\n", status ); + RtlInitUnicodeString( &nameW, L"" ); + status = NtCreateFile( &subdir, GENERIC_ALL, &attr2, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN_IF, 0, NULL, 0 ); + ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); + status = NtCreateFile( &subdir, GENERIC_ALL, &attr2, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN_IF, FILE_OPEN_REPARSE_POINT, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + NtClose( subdir ); + NtClose( handle2 ); + + swprintf( path, ARRAY_SIZE(path), L"\??\%stestreparse_file", temp_path ); + data_size = init_reparse_mount_point( &data, path ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, data_size, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + + RtlInitUnicodeString( &nameW, L"testreparse_dirlink" ); + status = NtCreateFile( &handle2, GENERIC_READ, &attr, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, 0, NULL, 0 ); + ok( status == STATUS_NOT_A_DIRECTORY, "got %#lx\n", status ); + + swprintf( path, ARRAY_SIZE(path), L"\??\%stestreparse_dirlink", temp_path ); + data_size = init_reparse_mount_point( &data, path ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, data_size, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + + status = NtCreateFile( &handle2, GENERIC_READ, &attr, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, FILE_DIRECTORY_FILE, NULL, 0 ); + ok( status == STATUS_REPARSE_POINT_NOT_RESOLVED, "got %#lx\n", status ); + + swprintf( path, ARRAY_SIZE(path), L"\??\%s", temp_path ); + data_size = init_reparse_mount_point( &data, path ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, data_size, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + + swprintf( path, ARRAY_SIZE(path), L"%s/testreparse_dirlink/testreparse_dirlink/testreparse_dirlink/testreparse_file", temp_path ); + handle2 = CreateFileW( path, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0 ); + ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + CloseHandle( handle2 ); + + swprintf( path, ARRAY_SIZE(path), L"%s/testreparse_dirlink/testreparse_dirlink", temp_path ); + handle2 = CreateFileW( path, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, 0 ); + ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + ret = GetFinalPathNameByHandleW( handle2, ret_path, ARRAY_SIZE( ret_path ), VOLUME_NAME_DOS ); + ok( ret > 0, "got error %lu\n", GetLastError() ); + swprintf( path, ARRAY_SIZE(path), L"%stestreparse_dirlink", temp_path ); + ok( !wcscmp( ret_path, path ), "expected path %s, got %s\n", debugstr_w( path ), debugstr_w( ret_path )); + CloseHandle( handle2 ); + + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, NULL, 0, NULL, 0 ); + ok( status == STATUS_INVALID_BUFFER_SIZE, "got %#lx\n", status ); + + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, data, data_size, NULL, 0 ); + ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); + + data->ReparseDataLength = 0; + data->ReparseTag = 0; + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, data, sizeof(REPARSE_DATA_BUFFER), NULL, 0 ); + ok( status == STATUS_IO_REPARSE_TAG_INVALID, "got %#lx\n", status ); + + data->ReparseTag = 3; + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, data, sizeof(REPARSE_DATA_BUFFER), NULL, 0 ); + ok( status == STATUS_IO_REPARSE_TAG_MISMATCH, "got %#lx\n", status ); + + data->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, data, sizeof(REPARSE_DATA_BUFFER), NULL, 0 ); + ok( !status, "got %#lx\n", status ); + + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, data, sizeof(REPARSE_DATA_BUFFER), NULL, 0 ); + ok( status == STATUS_NOT_A_REPARSE_POINT, "got %#lx\n", status ); + + /* Create a dangling symlink, and then open it without + * FILE_OPEN_REPARSE_POINT but with FILE_OPEN_IF. This creates the target. + * No validation is done regarding file/directory mismatch. */ + swprintf( path, ARRAY_SIZE(path), L"\??\%stestreparse_dir2", temp_path ); + data_size = init_reparse_mount_point( &data, path ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, data_size, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + + RtlInitUnicodeString( &nameW, L"testreparse_dir2" ); + status = NtCreateFile( &handle2, GENERIC_ALL, &attr, &io, NULL, 0, 0, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + NtClose( handle2 ); + + RtlInitUnicodeString( &nameW, L"testreparse_dirlink" ); + status = NtCreateFile( &handle2, GENERIC_ALL, &attr, &io, NULL, 0, 0, FILE_OPEN, 0, NULL, 0 ); + ok( status == STATUS_NOT_A_DIRECTORY, "got %#lx\n", status ); + + swprintf( path, ARRAY_SIZE(path), L"%stestreparse_dir2", temp_path ); + ret = DeleteFileW( path ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + + /* As above, but with CreateDirectory(). This apparently uses + * FILE_OPEN_REPARSE_POINT under the hood. */ + swprintf( path, ARRAY_SIZE(path), L"%stestreparse_dirlink", temp_path ); + ret = CreateDirectoryW( path, NULL ); + ok( ret == FALSE, "got %d\n", ret ); + ok( GetLastError() == ERROR_ALREADY_EXISTS, "got error %lu\n", GetLastError() ); + + CloseHandle( handle ); + + /* Test FindFirstFile(). */ + + swprintf( path, ARRAY_SIZE(path), L"%s/testreparse_dirlink", temp_path ); + handle = CreateFileW( path, DELETE, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0 ); + ok( handle != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + status = NtSetInformationFile( handle, &io, &fdi, sizeof(fdi), FileDispositionInformation ); + ok( !status, "got %#lx\n", status ); + CloseHandle( handle ); + + handle = CreateFileW( path, DELETE, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0 ); + ok( handle == INVALID_HANDLE_VALUE, "expected failure\n" ); + ok( GetLastError() == ERROR_FILE_NOT_FOUND, "got error %lu\n", GetLastError() ); + + /* Test IO_REPARSE_TAG_SYMLINK. + * Symlinks require administrator access, but we *also* need to + * raise the specific privilege. */ + + ret = OpenProcessToken( GetCurrentProcess(), TOKEN_ALL_ACCESS, &token ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + ret = LookupPrivilegeValueA( NULL, "SeCreateSymbolicLinkPrivilege", &luid ); + todo_wine ok( ret == TRUE, "got error %lu\n", GetLastError() ); + + privs.PrivilegeCount = 1; + privs.Privileges[0].Luid = luid; + privs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + ret = AdjustTokenPrivileges( token, FALSE, &privs, 0, NULL, NULL ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) + { + win_skip("Insufficient permissions to perform symlink tests.\n"); + CloseHandle( token ); + goto out; + } + CloseHandle( token ); + + RtlInitUnicodeString( &nameW, L"testreparse_filelink" ); + status = NtCreateFile( &handle, GENERIC_ALL, &attr, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN_IF, 0, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + + data_size = init_reparse_symlink( &data, L"testreparse_dir", 0 ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, data_size, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + + status = NtCreateFile( &handle2, GENERIC_READ, &attr, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, 0, NULL, 0 ); + ok( status == STATUS_OBJECT_NAME_INVALID, "got %#lx\n", status ); + + data_size = init_reparse_symlink( &data, L"testreparse_dir", SYMLINK_FLAG_RELATIVE ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, data_size, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + + status = NtCreateFile( &handle2, GENERIC_READ, &attr, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, 0, NULL, 0 ); + ok( status == STATUS_FILE_IS_A_DIRECTORY, "got %#lx\n", status ); + + data_size = init_reparse_symlink( &data, L"testreparse_file", SYMLINK_FLAG_RELATIVE ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, data_size, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + + /* Test how various functions interact with a file reparse point. */ + + swprintf( path, ARRAY_SIZE(path), L"%s/testreparse_filelink", temp_path ); + + handle2 = CreateFileW( path, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, 0 ); + ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + + /* Regular I/O. It turns out you can just blithely write data into the file, + * and it's stored. */ + ret = ReadFile( handle2, buffer, sizeof(buffer), &size, NULL ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + ok( !size, "got size %lu\n", size ); + ret = WriteFile( handle2, "data", 4, &size, NULL ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + ok( size == 4, "got size %lu\n", size ); + ret = GetFileSize( handle2, NULL ); + ok( ret == 4, "got size %lu\n", size ); + + CloseHandle( handle2 ); + + /* Deleting the symlink has no effect on the data. */ + data->ReparseDataLength = 0; + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, data, sizeof(REPARSE_DATA_BUFFER), NULL, 0 ); + ok( !status, "got %#lx\n", status ); + + handle2 = CreateFileW( path, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, 0, 0 ); + ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + ret = ReadFile( handle2, buffer, sizeof(buffer), &size, NULL ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + ok( size == 4, "got size %lu\n", size ); + ok( !memcmp( buffer, "data", size ), "got data %s\n", debugstr_an( buffer, size )); + CloseHandle( handle2 ); + + /* Adding the symlink, however, is illegal as long as there is data + * in the file. The status here is rather misleading, of course. */ + data->ReparseDataLength = data_size - offsetof( REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, data_size, NULL, 0 ); + ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); + + SetFilePointer( handle, 0, 0, FILE_BEGIN ); + ret = SetEndOfFile( handle ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, data_size, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + + /* CopyFile(). */ + + swprintf( path2, ARRAY_SIZE(path2), L"%s/testreparse_file2", temp_path ); + + ret = CopyFileW( path, path2, FALSE ); + ok( ret == TRUE, "got %d\n", ret ); + ok( !GetLastError(), "got error %lu\n", GetLastError() ); + + handle2 = CreateFileW( path2, GENERIC_ALL, 0, NULL, OPEN_EXISTING, FILE_OPEN_REPARSE_POINT, 0 ); + ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + ret = ReadFile( handle2, buffer, sizeof(buffer), &size, NULL ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + ok( size == 5, "got size %lu\n", size ); + ok( !memcmp( buffer, "file1", size ), "got data %s\n", debugstr_an( buffer, size )); + SetFilePointer( handle2, 0, NULL, FILE_BEGIN ); + WriteFile( handle2, "file2", 5, &size, NULL ); + CloseHandle( handle2 ); + + ret = CopyFileW( path2, path, TRUE ); + ok( !ret, "got %d\n", ret ); + ok( GetLastError() == ERROR_FILE_EXISTS, "got error %lu\n", GetLastError() ); + + /* Copying *to* the symlink replaces the target contents, not the symlink. */ + ret = CopyFileW( path2, path, FALSE ); + ok( ret == TRUE, "got %d\n", ret ); + ok( !GetLastError(), "got error %lu\n", GetLastError() ); + + swprintf( path, ARRAY_SIZE(path), L"%s/testreparse_file", temp_path ); + handle2 = CreateFileW( path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, 0, 0 ); + ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + ret = ReadFile( handle2, buffer, sizeof(buffer), &size, NULL ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + ok( size == 5, "got size %lu\n", size ); + ok( !memcmp( buffer, "file2", size ), "got data %s\n", debugstr_an( buffer, size )); + CloseHandle( handle2 ); + + /* It also works if the target doesn't exist. */ + ret = DeleteFileW( path ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + + ret = CopyFileW( path2, path, TRUE ); + ok( ret == TRUE, "got %d\n", ret ); + ok( !GetLastError(), "got error %lu\n", GetLastError() ); + + handle2 = CreateFileW( path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, 0, 0 ); + ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + ret = ReadFile( handle2, buffer, sizeof(buffer), &size, NULL ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + ok( size == 5, "got size %lu\n", size ); + ok( !memcmp( buffer, "file2", size ), "got data %s\n", debugstr_an( buffer, size )); + CloseHandle( handle2 ); + + /* Hard links. */ + + swprintf( path, ARRAY_SIZE(path), L"%stestreparse_filelink", temp_path ); + swprintf( path2, ARRAY_SIZE(path2), L"%stestreparse_filelink2", temp_path ); + ret = CreateHardLinkW( path2, path, NULL ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + + handle2 = CreateFileW( path, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, 0 ); + ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + status = NtQueryInformationFile( handle, &io, &stat_info, sizeof(stat_info), FileStatInformation ); + ok( !status, "got %#lx\n", status ); + status = NtQueryInformationFile( handle2, &io, &stat_info2, sizeof(stat_info2), FileStatInformation ); + ok( !status, "got %#lx\n", status ); + ok( stat_info2.FileAttributes == (FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_REPARSE_POINT), + "got attributes %#lx\n", stat_info2.FileAttributes ); + ok( stat_info2.ReparseTag == IO_REPARSE_TAG_SYMLINK, "got tag %#lx\n", stat_info2.ReparseTag ); + ok( !stat_info2.AllocationSize.QuadPart, "got size %#I64x\n", stat_info2.AllocationSize.QuadPart ); + ok( !stat_info2.EndOfFile.QuadPart, "got eof %#I64x\n", stat_info2.EndOfFile.QuadPart ); + ok( stat_info2.FileId.QuadPart == stat_info.FileId.QuadPart, "got ids %#I64x vs %#I64x\n", + stat_info.FileId.QuadPart, stat_info2.FileId.QuadPart ); + CloseHandle( handle2 ); + + handle2 = CreateFileW( path, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, 0, 0 ); + ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + status = NtQueryInformationFile( handle2, &io, &stat_info2, sizeof(stat_info2), FileStatInformation ); + ok( !status, "got %#lx\n", status ); + ok( stat_info2.FileId.QuadPart != stat_info.FileId.QuadPart, "got ids %#I64x vs %#I64x\n", + stat_info.FileId.QuadPart, stat_info2.FileId.QuadPart ); + CloseHandle( handle2 ); + + /* Clean up the hard link, and show in the process that DeleteFile() + * deletes the symlink, not the target. */ + + ret = DeleteFileW( path2 ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + + handle2 = CreateFileW( path2, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, 0 ); + ok( handle2 == INVALID_HANDLE_VALUE, "expected failure\n" ); + ok( GetLastError() == ERROR_FILE_NOT_FOUND, "got error %lu\n", GetLastError() ); + + /* MoveFile(). */ + + ret = MoveFileW( path, path2 ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + + handle2 = CreateFileW( path, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, 0 ); + ok( handle2 == INVALID_HANDLE_VALUE, "expected failure\n" ); + ok( GetLastError() == ERROR_FILE_NOT_FOUND, "got error %lu\n", GetLastError() ); + + ret = MoveFileW( path2, path ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + + /* Attributes. */ + + swprintf( path2, ARRAY_SIZE(path2), L"%s/testreparse_file", temp_path ); + + ret = SetFileAttributesW( path, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + + ret = GetFileAttributesW( path ); + ok( ret == (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_REPARSE_POINT), + "got attributes %#x\n", ret ); + + ret = GetFileAttributesW( path2 ); + ok( ret == FILE_ATTRIBUTE_ARCHIVE, "got attributes %#x\n", ret ); + + status = NtQueryAttributesFile( &attr, &basic_info ); + ok( !status, "got %#lx\n", status ); + ok( basic_info.FileAttributes == (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_REPARSE_POINT), + "got attributes %#lx\n", basic_info.FileAttributes ); + + /* Path names. */ + + GetFullPathNameW( path, ARRAY_SIZE( ret_path ), ret_path, NULL ); + ok( !wcscmp( ret_path, path ), "expected path %s, got %s\n", debugstr_w( path ), debugstr_w( ret_path )); + + GetLongPathNameW( path, ret_path, ARRAY_SIZE( ret_path )); + ok( !wcscmp( ret_path, path ), "expected path %s, got %s\n", debugstr_w( path ), debugstr_w( ret_path )); + + /* ReOpenFile(). */ + + handle2 = ReOpenFile( handle, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0 ); + ok( handle2 == INVALID_HANDLE_VALUE, "expected failure\n" ); + ok( GetLastError() == ERROR_INVALID_REPARSE_DATA, "got error %lu\n", GetLastError() ); + handle2 = ReOpenFile( handle, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_FLAG_OPEN_REPARSE_POINT ); + ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + CloseHandle( handle2 ); + + ret = DeleteFileW( path ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + + status = NtOpenFile( &handle, READ_CONTROL, &attr, &io, 0, FILE_OPEN_REPARSE_POINT ); + ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "got %#lx\n", status ); + + /* Create a directory symlink. */ + + RtlInitUnicodeString( &nameW, L"testreparse_dirlink" ); + status = NtCreateFile( &handle, GENERIC_ALL, &attr, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN_IF, FILE_DIRECTORY_FILE, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + + swprintf( path, ARRAY_SIZE(path), L"%s/testreparse_dirlink/file", temp_path ); + handle2 = CreateFileW( path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0 ); + ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + CloseHandle( handle2 ); + + data_size = init_reparse_symlink( &data, L"testreparse_dir", SYMLINK_FLAG_RELATIVE ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, data_size, NULL, 0 ); + ok( status == STATUS_DIRECTORY_NOT_EMPTY, "got %#lx\n", status ); + + ret = DeleteFileW( path ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, data_size, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + + swprintf( path, ARRAY_SIZE(path), L"%s/testreparse_dirlink/file", temp_path ); + handle2 = CreateFileW( path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0 ); + ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + CloseHandle( handle2 ); + + NtClose( handle ); + + swprintf( path, ARRAY_SIZE(path), L"%s/testreparse_dir/file", temp_path ); + ret = DeleteFileW( path ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + + /* RemoveDirectory() also deletes the link rather than the target. */ + swprintf( path, ARRAY_SIZE(path), L"%s/testreparse_dirlink", temp_path ); + ret = RemoveDirectoryW( path ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + + status = NtOpenFile( &handle, READ_CONTROL, &attr, &io, 0, FILE_OPEN_REPARSE_POINT ); + ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "got %#lx\n", status ); + + /* Create an absolute symlink. */ + + RtlInitUnicodeString( &nameW, L"testreparse_dirlink" ); + status = NtCreateFile( &handle, GENERIC_ALL, &attr, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN_IF, FILE_DIRECTORY_FILE, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + + swprintf( path, ARRAY_SIZE(path), L"\??\%stestreparse_dir", temp_path ); + data_size = init_reparse_symlink( &data, path, SYMLINK_FLAG_RELATIVE ); + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, data_size, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + + status = NtOpenFile( &handle2, READ_CONTROL, &attr, &io, 0, 0 ); + ok( status == STATUS_OBJECT_NAME_INVALID, "got %#lx\n", status ); + + data->SymbolicLinkReparseBuffer.Flags = 0; + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, data_size, NULL, 0 ); + ok( !status, "got %#lx\n", status ); + + status = NtOpenFile( &handle2, READ_CONTROL, &attr, &io, 0, 0 ); + ok( !status, "got %#lx\n", status ); + NtClose( handle2 ); + + NtClose( handle ); + + swprintf( path, ARRAY_SIZE(path), L"%s/testreparse_dirlink", temp_path ); + ret = RemoveDirectoryW( path ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + +out: + swprintf( path, ARRAY_SIZE(path), L"%s/testreparse_file", temp_path ); + ret = DeleteFileW( path ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + + swprintf( path, ARRAY_SIZE(path), L"%s/testreparse_dir", temp_path ); + ret = RemoveDirectoryW( path ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + + CloseHandle( temp_dir ); }
static void test_set_io_completion_ex(void)
From: Elizabeth Figura zfigura@codeweavers.com
--- dlls/kernel32/tests/file.c | 161 +++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+)
diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index db3ffc655ce..9de1eb3df90 100644 --- a/dlls/kernel32/tests/file.c +++ b/dlls/kernel32/tests/file.c @@ -31,9 +31,11 @@ #include "windef.h" #include "winbase.h" #include "winerror.h" +#include "winioctl.h" #include "winternl.h" #include "winnls.h" #include "fileapi.h" +#include "ddk/ntifs.h"
static HANDLE (WINAPI *pFindFirstFileExA)(LPCSTR,FINDEX_INFO_LEVELS,LPVOID,FINDEX_SEARCH_OPS,LPVOID,DWORD); static BOOL (WINAPI *pReplaceFileW)(LPCWSTR, LPCWSTR, LPCWSTR, DWORD, LPVOID, LPVOID); @@ -6317,6 +6319,164 @@ static void test_eof(void) ok(ret, "failed to delete %s, error %lu\n", debugstr_a(filename), GetLastError()); }
+static void test_symbolic_link(void) +{ + WCHAR temp_path[MAX_PATH], path[MAX_PATH], path2[MAX_PATH], expect_path[MAX_PATH]; + char buffer[1024]; + const REPARSE_DATA_BUFFER *data = (void *)buffer; + TOKEN_PRIVILEGES privs; + const WCHAR *ret_path; + IO_STATUS_BLOCK io; + HANDLE file, token; + LUID luid; + BOOL ret; + + ret = OpenProcessToken( GetCurrentProcess(), TOKEN_ALL_ACCESS, &token ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + ret = LookupPrivilegeValueA( NULL, "SeCreateSymbolicLinkPrivilege", &luid ); + todo_wine ok( ret == TRUE, "got error %lu\n", GetLastError() ); + + privs.PrivilegeCount = 1; + privs.Privileges[0].Luid = luid; + privs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + ret = AdjustTokenPrivileges( token, FALSE, &privs, 0, NULL, NULL ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) + { + todo_wine win_skip( "Insufficient permissions to perform symlink tests.\n" ); + CloseHandle( token ); + return; + } + CloseHandle( token ); + + GetTempPathW( ARRAY_SIZE( temp_path ), temp_path ); + + swprintf( path, ARRAY_SIZE(path), L"%s/testsymlink\", temp_path ); + swprintf( path2, ARRAY_SIZE(path2), L"%s/target\", temp_path ); + + SetLastError( 0xdeadbeef ); + ret = CreateSymbolicLinkW( path, path2, TRUE ); + todo_wine ok( ret == TRUE, "got %d\n", ret ); + todo_wine ok( !GetLastError(), "got error %lu\n", GetLastError() ); + if (!ret) + return; + + ret = GetFileAttributesW( path ); + ok( ret == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), "got attrs %#x\n", ret ); + + file = CreateFileW( path, FILE_READ_DATA, 0, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL ); + ok( file != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + + ret = NtFsControlFile( file, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, buffer, sizeof(buffer) ); + ok( !ret, "got %#x\n", ret ); + ok( data->ReparseTag == IO_REPARSE_TAG_SYMLINK, "got tag %#lx\n", data->ReparseTag ); + ok( !data->Reserved, "got reserved %#x\n", data->Reserved ); + ok( data->ReparseDataLength == io.Information - offsetof( REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer ), + "got information %Iu, length %u\n", io.Information, data->ReparseDataLength ); + ok( !data->SymbolicLinkReparseBuffer.Flags, "got flags %#lx\n", data->SymbolicLinkReparseBuffer.Flags ); + ret_path = data->SymbolicLinkReparseBuffer.PathBuffer + (data->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)); + swprintf( expect_path, ARRAY_SIZE(expect_path), L"\??\%starget\", temp_path ); + ok( data->SymbolicLinkReparseBuffer.SubstituteNameLength == wcslen( expect_path ) * 2, + "got length %u\n", data->SymbolicLinkReparseBuffer.SubstituteNameLength ); + ok( !memcmp( ret_path, expect_path, data->SymbolicLinkReparseBuffer.SubstituteNameLength ), + "expected %s, got %s\n", debugstr_w( expect_path ), + debugstr_wn( ret_path, data->SymbolicLinkReparseBuffer.SubstituteNameLength )); + ret_path = data->SymbolicLinkReparseBuffer.PathBuffer + (data->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)); + ok( data->SymbolicLinkReparseBuffer.PrintNameLength == wcslen( path2 ) * 2, + "got length %u\n", data->SymbolicLinkReparseBuffer.PrintNameLength ); + ok( !memcmp( ret_path, path2, data->SymbolicLinkReparseBuffer.PrintNameLength ), + "expected %s, got %s\n", debugstr_w( path2 ), + debugstr_wn( ret_path, data->SymbolicLinkReparseBuffer.PrintNameLength )); + CloseHandle( file ); + + SetLastError( 0xdeadbeef ); + ret = CreateSymbolicLinkW( path, L"target", TRUE ); + ok( ret == FALSE, "got %d\n", ret ); + ok( GetLastError() == ERROR_ALREADY_EXISTS, "got error %lu\n", GetLastError() ); + + ret = RemoveDirectoryW( path ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + + SetLastError( 0xdeadbeef ); + ret = CreateSymbolicLinkW( path, L".\target", TRUE ); + ok( ret == TRUE, "got %d\n", ret ); + ok( !GetLastError(), "got error %lu\n", GetLastError() ); + + ret = GetFileAttributesW( path ); + ok( ret == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), "got attrs %#x\n", ret ); + + file = CreateFileW( path, FILE_READ_DATA, 0, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL ); + ok( file != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + + ret = NtFsControlFile( file, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, buffer, sizeof(buffer) ); + ok( !ret, "got %#x\n", ret ); + ok( data->ReparseTag == IO_REPARSE_TAG_SYMLINK, "got tag %#lx\n", data->ReparseTag ); + ok( !data->Reserved, "got reserved %#x\n", data->Reserved ); + ok( data->ReparseDataLength == io.Information - offsetof( REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer ), + "got information %Iu, length %u\n", io.Information, data->ReparseDataLength ); + ok( data->SymbolicLinkReparseBuffer.Flags == SYMLINK_FLAG_RELATIVE, "got flags %#lx\n", data->SymbolicLinkReparseBuffer.Flags ); + ret_path = data->SymbolicLinkReparseBuffer.PathBuffer + (data->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)); + ok( data->SymbolicLinkReparseBuffer.SubstituteNameLength == wcslen( L".\target" ) * 2, + "got length %u\n", data->SymbolicLinkReparseBuffer.SubstituteNameLength ); + ok( !memcmp( ret_path, L".\target", data->SymbolicLinkReparseBuffer.SubstituteNameLength ), + "expected %s, got %s\n", debugstr_w( L".\target" ), + debugstr_wn( ret_path, data->SymbolicLinkReparseBuffer.SubstituteNameLength )); + ret_path = data->SymbolicLinkReparseBuffer.PathBuffer + (data->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)); + ok( data->SymbolicLinkReparseBuffer.PrintNameLength == wcslen( L".\target" ) * 2, + "got length %u\n", data->SymbolicLinkReparseBuffer.PrintNameLength ); + ok( !memcmp( ret_path, L".\target", data->SymbolicLinkReparseBuffer.PrintNameLength ), + "expected %s, got %s\n", debugstr_w( L".\target" ), + debugstr_wn( ret_path, data->SymbolicLinkReparseBuffer.PrintNameLength )); + CloseHandle( file ); + + ret = RemoveDirectoryW( path ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + + SetLastError( 0xdeadbeef ); + ret = CreateSymbolicLinkW( path, L".\target\", FALSE ); + ok( !ret, "got %d\n", ret ); + ok( GetLastError() == ERROR_INVALID_NAME, "got error %lu\n", GetLastError() ); + + SetLastError( 0xdeadbeef ); + swprintf( path, ARRAY_SIZE(path), L"%s/testsymlink", temp_path ); + ret = CreateSymbolicLinkW( path, L".\target\", FALSE ); + ok( ret == TRUE, "got %d\n", ret ); + ok( !GetLastError(), "got error %lu\n", GetLastError() ); + + ret = GetFileAttributesW( path ); + ok( ret == (FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_REPARSE_POINT), "got attrs %#x\n", ret ); + + file = CreateFileW( path, FILE_READ_DATA, 0, NULL, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT, NULL ); + ok( file != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + + ret = NtFsControlFile( file, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, buffer, sizeof(buffer) ); + ok( !ret, "got %#x\n", ret ); + ok( data->ReparseTag == IO_REPARSE_TAG_SYMLINK, "got tag %#lx\n", data->ReparseTag ); + ok( !data->Reserved, "got reserved %#x\n", data->Reserved ); + ok( data->ReparseDataLength == io.Information - offsetof( REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer ), + "got information %Iu, length %u\n", io.Information, data->ReparseDataLength ); + ok( data->SymbolicLinkReparseBuffer.Flags == SYMLINK_FLAG_RELATIVE, "got flags %#lx\n", data->SymbolicLinkReparseBuffer.Flags ); + ret_path = data->SymbolicLinkReparseBuffer.PathBuffer + (data->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)); + ok( data->SymbolicLinkReparseBuffer.SubstituteNameLength == wcslen( L".\target\" ) * 2, + "got length %u\n", data->SymbolicLinkReparseBuffer.SubstituteNameLength ); + ok( !memcmp( ret_path, L".\target\", data->SymbolicLinkReparseBuffer.SubstituteNameLength ), + "expected %s, got %s\n", debugstr_w( L".\target\" ), + debugstr_wn( ret_path, data->SymbolicLinkReparseBuffer.SubstituteNameLength )); + ret_path = data->SymbolicLinkReparseBuffer.PathBuffer + (data->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)); + ok( data->SymbolicLinkReparseBuffer.PrintNameLength == wcslen( L".\target\" ) * 2, + "got length %u\n", data->SymbolicLinkReparseBuffer.PrintNameLength ); + ok( !memcmp( ret_path, L".\target\", data->SymbolicLinkReparseBuffer.PrintNameLength ), + "expected %s, got %s\n", debugstr_w( L".\target\" ), + debugstr_wn( ret_path, data->SymbolicLinkReparseBuffer.PrintNameLength )); + CloseHandle( file ); + + ret = DeleteFileW( path ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); +} + START_TEST(file) { char temp_path[MAX_PATH]; @@ -6395,4 +6555,5 @@ START_TEST(file) test_hard_link(); test_move_file(); test_eof(); + test_symbolic_link(); }