As prescribed some time ago, this attempts to implement correct reparse behaviour simply without attempting at all to make reparse points resolvable Unix symlinks.
A reparse point consists two files. For a file whose NT name is "foobar", the contents of the file or directory (and note that a reparse point *can* have arbitrary contents) is stored in a file or directory "foobar?". The contents of the reparse point are stored in a file "foobar*". Both * and ? are valid in POSIX filenames but not NT, so no collision should be possible.
The actual file "foobar" does not exist in this case. This is to allow the case where there are no reparse points (which is most common) to work as it currently does, without any extra overhead to check for a reparse point. [Simply guarding this behind FILE_OPEN_REPARSE_POINT does not seem good enough to me; that flag is used for example by many builtin functions like MoveFile().]
When failing to locate a path segment "foobar", we check for "foobar*" and attempt to parse and resolve that reparse point.
Issues:
(1) Since we are manipulating two files, are there any race conditions?
I believe there are no meaningful races.
Read/write races:
- When setting a reparse point, we move the contents file out of the way last, so attempts to open the file before that will see a normal file. Attempts to read the reparse point might see a partial file, but we treat that as failure.
- When removing a reparse point from a file, we move the contents file back first, so attempts to open or read will see a normal file.
- When deleting a reparse point entirely, we delete the contents first, so attempts to open the file will see neither a normal file nor the contents of the symlink, and fail. Attempts to query attributes don't need to open the contents, only the data, which will either be present or not.
Write/write races:
- Races involving file deletion don't matter, because deletion doesn't take effect until all handles to an inode are closed.
- If we try to simultaneously set the reparse point from two different threads, one will fail with no effect since we create the reparse data file with O_EXCL.
- If we try to remove the reparse point while setting it, the contents won't be in the right place and we'll fail.
- If we try to remove the reparse point from two different threads, one will fail to move the contents back or delete them.
(2) How does this interact with hard links?
Creating a hard link to an existing reparse point is not too hard to do; we can just hard link both the data file and the contents file.
However, turning an existing hardlinked file into a reparse point cannot work. This is also a limitation of other approaches that have been proposed.
For this reason I've elected to forbid hard link interaction for now.
One alternative approach that would allow turning hardlinked files into reparse points would be to store the reparse data in xattr. This would be less portable though.
-- v4: ntdll: Resolve IO_REPARSE_TAG_MOUNT_POINT during path lookup.
From: Elizabeth Figura zfigura@codeweavers.com
* Avoid checking GetLastError() after a successful CopyFile(); it's not set consistently.
* Handle versions before 1709 which don't implement "directory" reparse points.
* Avoid using FileStatInformation which isn't implemented until 1809.
* Handle presumably broken versions < 1709 which don't validate file/directory status in some cases.
* Handle versions before 1607 which return a different error when trying to use a reparse point as a root directory.
* Account for some very weird and inexplicably intermittent behaviour on testbot machines when trying to use a reparse point as a root directory.
* Fix a memory error. --- dlls/ntdll/tests/file.c | 185 +++++++++++++++++++++++++--------------- 1 file changed, 114 insertions(+), 71 deletions(-)
diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index 1f35bd58edf..1cf1a393834 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -6103,9 +6103,11 @@ static void test_reparse_points(void) 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_INTERNAL_INFORMATION internal_info, internal_info2; REPARSE_GUID_DATA_BUFFER *guid_data, *guid_data2; - FILE_STAT_INFORMATION stat_info, stat_info2; + FILE_ATTRIBUTE_TAG_INFORMATION tag_info; FILE_DIRECTORY_INFORMATION *dir_info; + FILE_STANDARD_INFORMATION std_info; FILE_BASIC_INFORMATION basic_info; REPARSE_DATA_BUFFER *data, *data2; char dir_buffer[1024], buffer[9]; @@ -6242,21 +6244,29 @@ static void test_reparse_points(void) 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 ); + status = NtQueryInformationFile( handle, &io, &tag_info, sizeof(tag_info), FileAttributeTagInformation ); 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 ); + ok( tag_info.FileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), + "got attributes %#lx\n", tag_info.FileAttributes ); + ok( tag_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", tag_info.ReparseTag ); + status = NtQueryInformationFile( handle, &io, &std_info, sizeof(std_info), FileStandardInformation ); + ok( !status, "got %#lx\n", status ); + ok( !std_info.AllocationSize.QuadPart, "got size %#I64x\n", std_info.AllocationSize.QuadPart ); + ok( !std_info.EndOfFile.QuadPart, "got eof %#I64x\n", std_info.EndOfFile.QuadPart ); + ok( std_info.NumberOfLinks == 1, "got %lu links\n", std_info.NumberOfLinks ); + ok( std_info.Directory == TRUE, "got directory %u\n", std_info.Directory );
- status = NtQueryInformationFile( handle2, &io, &stat_info, sizeof(stat_info), FileStatInformation ); + status = NtQueryInformationFile( handle2, &io, &tag_info, sizeof(tag_info), FileAttributeTagInformation ); + ok( !status, "got %#lx\n", status ); + ok( tag_info.FileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), + "got attributes %#lx\n", tag_info.FileAttributes ); + ok( tag_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", tag_info.ReparseTag ); + status = NtQueryInformationFile( handle2, &io, &std_info, sizeof(std_info), FileStandardInformation ); 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 ); + ok( !std_info.AllocationSize.QuadPart, "got size %#I64x\n", std_info.AllocationSize.QuadPart ); + ok( !std_info.EndOfFile.QuadPart, "got eof %#I64x\n", std_info.EndOfFile.QuadPart ); + ok( std_info.NumberOfLinks == 1, "got %lu links\n", std_info.NumberOfLinks ); + ok( std_info.Directory == TRUE, "got directory %u\n", std_info.Directory );
NtClose( handle2 );
@@ -6270,13 +6280,17 @@ static void test_reparse_points(void) 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( handle2, &io, &stat_info, sizeof(stat_info), FileStatInformation ); + status = NtQueryInformationFile( handle2, &io, &tag_info, sizeof(tag_info), FileAttributeTagInformation ); + ok( !status, "got %#lx\n", status ); + ok( tag_info.FileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), + "got attributes %#lx\n", tag_info.FileAttributes ); + ok( tag_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", tag_info.ReparseTag ); + status = NtQueryInformationFile( handle2, &io, &std_info, sizeof(std_info), FileStandardInformation ); 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 ); + ok( !std_info.AllocationSize.QuadPart, "got size %#I64x\n", std_info.AllocationSize.QuadPart ); + ok( !std_info.EndOfFile.QuadPart, "got eof %#I64x\n", std_info.EndOfFile.QuadPart ); + ok( std_info.NumberOfLinks == 1, "got %lu links\n", std_info.NumberOfLinks ); + ok( std_info.Directory == TRUE, "got directory %u\n", std_info.Directory ); NtClose( handle2 );
/* alter the target in-place */ @@ -6307,8 +6321,8 @@ static void test_reparse_points(void)
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( status == STATUS_PENDING || !status, "got %#lx\n", status ); + ret = WaitForSingleObject( handle, 1000 ); 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 ), @@ -6372,11 +6386,22 @@ static void test_reparse_points(void) 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. + /* Test using the reparse point as a parent. + * On some machines this returns STATUS_REPARSE_POINT_NOT_RESOLVED (which + * is cryptic but at least makes a bit of sense). + * For some unfathomable reason, though, on all of the testbot machines, + * it instead returns either STATUS_OBJECT_NAME_INVALID or, rarely and + * apparently randomly, STATUS_OBJECT_NAME_NOT_FOUND. In the last case, + * if FILE_OPENIF is used, the open actually succeeds, but then somehow the + * file is kept open and can't properly be deleted later. + * It's not clear what accounts for the difference in behaviour, since both + * patterns are observed on the same OS version. + * Before Windows 10 v1607 only STATUS_DIRECTORY_IS_A_REPARSE_POINT has + * been observed. * * Attempting to open the same file, using an empty name, results in the - * even more cryptic STATUS_IO_REPARSE_DATA_INVALID. + * even more cryptic STATUS_IO_REPARSE_DATA_INVALID, and this is at least + * consistent across machines. * 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, @@ -6385,11 +6410,17 @@ static void test_reparse_points(void) 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 ); + FILE_OPEN, 0, NULL, 0 ); + ok( status == STATUS_REPARSE_POINT_NOT_RESOLVED + || status == STATUS_OBJECT_NAME_INVALID + || status == STATUS_OBJECT_NAME_NOT_FOUND + || status == STATUS_DIRECTORY_IS_A_REPARSE_POINT, "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 ); + FILE_OPEN, FILE_OPEN_REPARSE_POINT, NULL, 0 ); + ok( status == STATUS_REPARSE_POINT_NOT_RESOLVED + || status == STATUS_OBJECT_NAME_INVALID + || status == STATUS_OBJECT_NAME_NOT_FOUND + || status == STATUS_DIRECTORY_IS_A_REPARSE_POINT, "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 ); @@ -6408,7 +6439,8 @@ static void test_reparse_points(void) 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 ); + ok( status == STATUS_NOT_A_DIRECTORY || broken(!status) /* < Win10 1709 */, "got %#lx\n", status ); + if (!status) NtClose( handle2 );
swprintf( path, ARRAY_SIZE(path), L"\??\%stestreparse_dirlink", temp_path ); data_size = init_reparse_mount_point( &data, path ); @@ -6483,7 +6515,8 @@ static void test_reparse_points(void)
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 ); + ok( status == STATUS_NOT_A_DIRECTORY || broken(!status) /* < Win10 1709 */, "got %#lx\n", status ); + if (!status) NtClose( handle2 );
swprintf( path, ARRAY_SIZE(path), L"%stestreparse_dir2", temp_path ); ret = DeleteFileW( path ); @@ -6606,33 +6639,37 @@ static void test_reparse_points(void) ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); CloseHandle( handle2 );
- /* Set the "directory" bit on our custom tag. */ + /* Set the "directory" bit on our custom tag. + * This is only supported since Windows 10 v1607. */
data_size = init_reparse_custom( &guid_data, 0x1000beef ); status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, guid_data, data_size, NULL, 0 ); - ok( !status, "got %#lx\n", status ); + ok( !status || broken(status == STATUS_DIRECTORY_NOT_EMPTY), "got %#lx\n", status );
- io.Information = 1; - status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, guid_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( guid_data, guid_data2, data_size ), "buffers didn't match\n" ); + if (!status) + { + io.Information = 1; + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, guid_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( guid_data, guid_data2, data_size ), "buffers didn't match\n" );
- RtlInitUnicodeString( &nameW, L"testreparse_customdir" ); - status = NtOpenFile( &handle2, GENERIC_READ, &attr, &io, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0 ); - ok( !status, "got %#lx\n", status ); + RtlInitUnicodeString( &nameW, L"testreparse_customdir" ); + status = NtOpenFile( &handle2, GENERIC_READ, &attr, &io, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0 ); + ok( !status, "got %#lx\n", status );
- io.Information = 1; - status = NtFsControlFile( handle2, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, guid_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( guid_data, guid_data2, data_size ), "buffers didn't match\n" ); - NtClose( handle2 ); + io.Information = 1; + status = NtFsControlFile( handle2, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, guid_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( guid_data, guid_data2, data_size ), "buffers didn't match\n" ); + NtClose( handle2 );
- RtlInitUnicodeString( &nameW, L"testreparse_customdir\file" ); - status = NtOpenFile( &handle2, GENERIC_READ, &attr, &io, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0 ); - ok( !status, "got %#lx\n", status ); - NtClose( handle2 ); + RtlInitUnicodeString( &nameW, L"testreparse_customdir\file" ); + status = NtOpenFile( &handle2, GENERIC_READ, &attr, &io, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0 ); + ok( !status, "got %#lx\n", status ); + NtClose( handle2 ); + }
ret = DeleteFileW( path ); ok( ret == TRUE, "got error %lu\n", GetLastError() ); @@ -6682,7 +6719,8 @@ static void test_reparse_points(void)
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 ); + ok( status == STATUS_FILE_IS_A_DIRECTORY || broken(!status) /* < Win10 1709 */, "got %#lx\n", status ); + if (!status) NtClose( handle2 );
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 ); @@ -6698,8 +6736,8 @@ static void test_reparse_points(void)
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, "got %#lx\n", status ); - NtClose( handle2 ); + ok( !status || broken(status == STATUS_IO_REPARSE_DATA_INVALID) /* win10 1607 only */, "got %#lx\n", status ); + if (!status) NtClose( handle2 );
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 ); @@ -6757,8 +6795,7 @@ static void test_reparse_points(void) 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() ); + ok( ret == TRUE, "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() ); @@ -6776,8 +6813,7 @@ static void test_reparse_points(void)
/* 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() ); + ok( ret == TRUE, "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, @@ -6794,8 +6830,7 @@ static void test_reparse_points(void) 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() ); + ok( ret == TRUE, "got error %lu\n", GetLastError() );
handle2 = CreateFileW( path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, 0 ); @@ -6819,26 +6854,32 @@ static void test_reparse_points(void) 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, "got error %lu\n", GetLastError() ); - status = NtQueryInformationFile( handle, &io, &stat_info, sizeof(stat_info), FileStatInformation ); + status = NtQueryInformationFile( handle2, &io, &tag_info, sizeof(tag_info), FileAttributeTagInformation ); + ok( !status, "got %#lx\n", status ); + ok( tag_info.FileAttributes == (FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_REPARSE_POINT), + "got attributes %#lx\n", tag_info.FileAttributes ); + ok( tag_info.ReparseTag == IO_REPARSE_TAG_SYMLINK, "got tag %#lx\n", tag_info.ReparseTag ); + status = NtQueryInformationFile( handle2, &io, &std_info, sizeof(std_info), FileStandardInformation ); + ok( !status, "got %#lx\n", status ); + ok( !std_info.AllocationSize.QuadPart, "got size %#I64x\n", std_info.AllocationSize.QuadPart ); + ok( !std_info.EndOfFile.QuadPart, "got eof %#I64x\n", std_info.EndOfFile.QuadPart ); + ok( std_info.NumberOfLinks == 2, "got %lu links\n", std_info.NumberOfLinks ); + ok( !std_info.Directory, "got directory %u\n", std_info.Directory ); + status = NtQueryInformationFile( handle, &io, &internal_info, sizeof(internal_info), FileInternalInformation ); ok( !status, "got %#lx\n", status ); - status = NtQueryInformationFile( handle2, &io, &stat_info2, sizeof(stat_info2), FileStatInformation ); + status = NtQueryInformationFile( handle2, &io, &internal_info2, sizeof(internal_info2), FileInternalInformation ); 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 ); + ok( internal_info.IndexNumber.QuadPart == internal_info2.IndexNumber.QuadPart, "got ids %#I64x vs %#I64x\n", + internal_info.IndexNumber.QuadPart, internal_info2.IndexNumber.QuadPart ); CloseHandle( handle2 );
handle2 = CreateFileW( path2, 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 ); + status = NtQueryInformationFile( handle2, &io, &internal_info2, sizeof(internal_info2), FileInternalInformation ); 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 ); + ok( internal_info.IndexNumber.QuadPart != internal_info2.IndexNumber.QuadPart, "got ids %#I64x vs %#I64x\n", + internal_info.IndexNumber.QuadPart, internal_info2.IndexNumber.QuadPart ); CloseHandle( handle2 );
/* Clean up the hard link, and show in the process that DeleteFile() @@ -6979,6 +7020,7 @@ static void test_reparse_points(void) status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, data_size, NULL, 0 ); ok( !status, "got %#lx\n", status );
+ data2 = malloc( data_size + 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 ); @@ -7071,6 +7113,7 @@ static void test_reparse_points(void) ret = CreateDirectoryW( path, NULL ); ok( ret == TRUE, "got error %lu\n", GetLastError() );
+ data2 = malloc( data_size + 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 );
From: Elizabeth Figura zfigura@codeweavers.com
--- dlls/ntdll/tests/file.c | 217 ++++++++++++++++++++++------------------ server/fd.c | 138 ++++++++++++++++++++----- 2 files changed, 230 insertions(+), 125 deletions(-)
diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index 1cf1a393834..c9a9184ea55 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -6205,7 +6205,7 @@ static void test_reparse_points(void) 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 ); + ok( status == STATUS_DIRECTORY_NOT_EMPTY, "got %#lx\n", status );
ret = DeleteFileW( path ); ok( ret == TRUE, "got error %lu\n", GetLastError() ); @@ -6214,41 +6214,34 @@ static void test_reparse_points(void) ok( !status, "got %#lx\n", status );
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; - } + ok( !status, "got %#lx\n", status );
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_GUID_DATA_BUFFER) - 1 ); - ok( status == STATUS_BUFFER_TOO_SMALL, "got %#lx\n", status ); + todo_wine 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, sizeof(REPARSE_GUID_DATA_BUFFER) ); - ok( status == STATUS_BUFFER_OVERFLOW, "got %#lx\n", status ); - ok( io.Information == sizeof(REPARSE_GUID_DATA_BUFFER), "expected size %#Ix, got %#Ix\n", data_size, io.Information ); - ok( !memcmp( data, data2, sizeof(REPARSE_GUID_DATA_BUFFER) ), "buffers didn't match\n" ); + todo_wine ok( status == STATUS_BUFFER_OVERFLOW, "got %#lx\n", status ); + todo_wine ok( io.Information == sizeof(REPARSE_GUID_DATA_BUFFER), "expected size %#Ix, got %#Ix\n", data_size, io.Information ); + todo_wine ok( !memcmp( data, data2, sizeof(REPARSE_GUID_DATA_BUFFER) ), "buffers didn't match\n" ); 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" ); + todo_wine ok( status == STATUS_BUFFER_OVERFLOW, "got %#lx\n", status ); + todo_wine ok( io.Information == data_size - 1, "expected size %#Ix, got %#Ix\n", data_size, io.Information ); + todo_wine 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" ); + todo_wine ok( !status, "got %#lx\n", status ); + todo_wine ok( io.Information == data_size, "expected size %#Ix, got %#Ix\n", data_size, io.Information ); + todo_wine ok( !memcmp( data, data2, data_size ), "buffers didn't match\n" );
status = NtQueryInformationFile( handle, &io, &tag_info, sizeof(tag_info), FileAttributeTagInformation ); ok( !status, "got %#lx\n", status ); - ok( tag_info.FileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), + todo_wine ok( tag_info.FileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), "got attributes %#lx\n", tag_info.FileAttributes ); - ok( tag_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", tag_info.ReparseTag ); + todo_wine ok( tag_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", tag_info.ReparseTag ); status = NtQueryInformationFile( handle, &io, &std_info, sizeof(std_info), FileStandardInformation ); ok( !status, "got %#lx\n", status ); ok( !std_info.AllocationSize.QuadPart, "got size %#I64x\n", std_info.AllocationSize.QuadPart ); @@ -6258,9 +6251,9 @@ static void test_reparse_points(void)
status = NtQueryInformationFile( handle2, &io, &tag_info, sizeof(tag_info), FileAttributeTagInformation ); ok( !status, "got %#lx\n", status ); - ok( tag_info.FileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), + todo_wine ok( tag_info.FileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), "got attributes %#lx\n", tag_info.FileAttributes ); - ok( tag_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", tag_info.ReparseTag ); + todo_wine ok( tag_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", tag_info.ReparseTag ); status = NtQueryInformationFile( handle2, &io, &std_info, sizeof(std_info), FileStandardInformation ); ok( !status, "got %#lx\n", status ); ok( !std_info.AllocationSize.QuadPart, "got size %#I64x\n", std_info.AllocationSize.QuadPart ); @@ -6272,7 +6265,8 @@ static void test_reparse_points(void)
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 ); + todo_wine ok( status == STATUS_OBJECT_NAME_INVALID, "got %#lx\n", status ); + if (!status) NtClose( handle2 );
status = NtCreateFile( &handle2, GENERIC_ALL, &attr, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT, NULL, 0 ); @@ -6282,9 +6276,9 @@ static void test_reparse_points(void) ok( !status, "got %#lx\n", status ); status = NtQueryInformationFile( handle2, &io, &tag_info, sizeof(tag_info), FileAttributeTagInformation ); ok( !status, "got %#lx\n", status ); - ok( tag_info.FileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), + todo_wine ok( tag_info.FileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), "got attributes %#lx\n", tag_info.FileAttributes ); - ok( tag_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", tag_info.ReparseTag ); + todo_wine ok( tag_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", tag_info.ReparseTag ); status = NtQueryInformationFile( handle2, &io, &std_info, sizeof(std_info), FileStandardInformation ); ok( !status, "got %#lx\n", status ); ok( !std_info.AllocationSize.QuadPart, "got size %#I64x\n", std_info.AllocationSize.QuadPart ); @@ -6300,7 +6294,8 @@ static void test_reparse_points(void)
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 ); + todo_wine ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "got %#lx\n", status ); + if (!status) NtClose( handle2 );
swprintf( path, ARRAY_SIZE(path), L"\??\%stestreparse_dir\", temp_path ); data_size = init_reparse_mount_point( &data, path ); @@ -6368,7 +6363,7 @@ static void test_reparse_points(void) 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 ); + todo_wine ok( ret == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), "got %#x\n", ret );
SetCurrentDirectoryW( path2 );
@@ -6381,9 +6376,9 @@ static void test_reparse_points(void) 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), + todo_wine 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 ); + todo_wine ok( find_data.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", find_data.dwReserved0 ); FindClose( find_handle );
/* Test using the reparse point as a parent. @@ -6424,7 +6419,13 @@ static void test_reparse_points(void) 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 ); + todo_wine ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); + if (!status) + { + status = NtSetInformationFile( subdir, &io, &fdi, sizeof(fdi), FileDispositionInformation ); + ok( !status, "got %#lx\n", status ); + NtClose( subdir ); + } status = NtCreateFile( &subdir, GENERIC_ALL, &attr2, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_OPEN_REPARSE_POINT, NULL, 0 ); ok( !status, "got %#lx\n", status ); @@ -6439,7 +6440,7 @@ static void test_reparse_points(void) 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 || broken(!status) /* < Win10 1709 */, "got %#lx\n", status ); + todo_wine ok( status == STATUS_NOT_A_DIRECTORY || broken(!status) /* < Win10 1709 */, "got %#lx\n", status ); if (!status) NtClose( handle2 );
swprintf( path, ARRAY_SIZE(path), L"\??\%stestreparse_dirlink", temp_path ); @@ -6449,7 +6450,8 @@ static void test_reparse_points(void)
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 ); + todo_wine ok( status == STATUS_REPARSE_POINT_NOT_RESOLVED, "got %#lx\n", status ); + if (!status) NtClose( handle2 );
swprintf( path, ARRAY_SIZE(path), L"\??\%s", temp_path ); data_size = init_reparse_mount_point( &data, path ); @@ -6458,47 +6460,47 @@ static void test_reparse_points(void)
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() ); + todo_wine 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 | FILE_FLAG_BACKUP_SEMANTICS, 0 ); - ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + todo_wine 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() ); + todo_wine 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 )); + todo_wine 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 ); + todo_wine ok( status == STATUS_INVALID_BUFFER_SIZE, "got %#lx\n", status ); status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, data, 1, NULL, 0 ); - ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); + todo_wine ok( status == STATUS_IO_REPARSE_DATA_INVALID, "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 ); + todo_wine ok( status == STATUS_IO_REPARSE_DATA_INVALID, "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_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); + todo_wine ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); data->ReparseDataLength = 0; 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 ); + todo_wine ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, data, sizeof(REPARSE_GUID_DATA_BUFFER), NULL, 0 ); - ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); + todo_wine ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status );
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 ); + todo_wine 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 ); + todo_wine 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 ); + todo_wine 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 ); + todo_wine 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. @@ -6515,7 +6517,7 @@ static void test_reparse_points(void)
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 || broken(!status) /* < Win10 1709 */, "got %#lx\n", status ); + todo_wine ok( status == STATUS_NOT_A_DIRECTORY || broken(!status) /* < Win10 1709 */, "got %#lx\n", status ); if (!status) NtClose( handle2 );
swprintf( path, ARRAY_SIZE(path), L"%stestreparse_dir2", temp_path ); @@ -6526,18 +6528,27 @@ static void test_reparse_points(void) * 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 ); + todo_wine ok( ret == FALSE, "got %d\n", ret ); + todo_wine ok( GetLastError() == ERROR_ALREADY_EXISTS, "got error %lu\n", GetLastError() ); + if (ret) + { + ret = RemoveDirectoryW( path ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + }
/* 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 ); + handle2 = CreateFileW( path, DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0 ); + todo_wine ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + if (handle2 != INVALID_HANDLE_VALUE) + status = NtSetInformationFile( handle2, &io, &fdi, sizeof(fdi), FileDispositionInformation ); + else + status = NtSetInformationFile( handle, &io, &fdi, sizeof(fdi), FileDispositionInformation ); ok( !status, "got %#lx\n", status ); + CloseHandle( handle2 ); + CloseHandle( handle );
handle = CreateFileW( path, DELETE, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0 ); @@ -6560,35 +6571,38 @@ static void test_reparse_points(void) status = NtOpenFile( &handle2, GENERIC_READ | SYNCHRONIZE, &attr, &io, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN_REPARSE_POINT | FILE_SYNCHRONOUS_IO_NONALERT ); - ok( !status, "got %#lx\n", status ); + todo_wine ok( !status, "got %#lx\n", status );
guid_data2 = malloc( data_size + 1 ); - memset( guid_data2, 0xcc, data_size + 1 ); - io.Information = 1; - status = NtFsControlFile( handle2, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, guid_data2, sizeof(REPARSE_GUID_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( handle2, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, guid_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( guid_data, guid_data2, data_size - 1 ), "buffers didn't match\n" ); + if (!status) + { + memset( guid_data2, 0xcc, data_size + 1 ); + io.Information = 1; + status = NtFsControlFile( handle2, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, guid_data2, sizeof(REPARSE_GUID_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( handle2, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, guid_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( guid_data, guid_data2, data_size - 1 ), "buffers didn't match\n" );
- io.Information = 1; - status = NtFsControlFile( handle2, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, guid_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( guid_data, guid_data2, data_size ), "buffers didn't match\n" ); + io.Information = 1; + status = NtFsControlFile( handle2, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, guid_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( guid_data, guid_data2, data_size ), "buffers didn't match\n" );
- 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 )); + 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 ); + CloseHandle( handle2 ); + }
status = NtOpenFile( &handle2, GENERIC_READ, &attr, &io, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0 ); - ok( status == STATUS_IO_REPARSE_TAG_NOT_HANDLED, "got %#lx\n", status ); + todo_wine ok( status == STATUS_IO_REPARSE_TAG_NOT_HANDLED, "got %#lx\n", status );
status = NtSetInformationFile( handle, &io, &fdi, sizeof(fdi), FileDispositionInformation ); ok( !status, "got %#lx\n", status ); @@ -6615,28 +6629,28 @@ static void test_reparse_points(void)
io.Information = 1; status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, guid_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( guid_data, guid_data2, data_size ), "buffers didn't match\n" ); + todo_wine ok( !status, "got %#lx\n", status ); + todo_wine ok( io.Information == data_size, "expected size %#Ix, got %#Ix\n", data_size, io.Information ); + todo_wine ok( !memcmp( guid_data, guid_data2, data_size ), "buffers didn't match\n" );
RtlInitUnicodeString( &nameW, L"testreparse_customdir\file" ); status = NtCreateFile( &handle2, GENERIC_ALL, &attr, &io, NULL, 0, 0, FILE_OPEN, 0, NULL, 0 ); - ok( status == STATUS_IO_REPARSE_TAG_NOT_HANDLED, "got %#lx\n", status ); + todo_wine ok( status == STATUS_IO_REPARSE_TAG_NOT_HANDLED, "got %#lx\n", status );
status = NtCreateFile( &handle2, GENERIC_ALL, &attr, &io, NULL, 0, 0, FILE_OPEN, FILE_OPEN_REPARSE_POINT, NULL, 0 ); - ok( status == STATUS_IO_REPARSE_TAG_NOT_HANDLED, "got %#lx\n", status ); + todo_wine ok( status == STATUS_IO_REPARSE_TAG_NOT_HANDLED, "got %#lx\n", status );
guid_data->ReparseDataLength = 0; status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, guid_data, data_size, NULL, 0 ); - ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); + todo_wine ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, guid_data, sizeof(REPARSE_GUID_DATA_BUFFER), NULL, 0 ); - ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); + todo_wine ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, guid_data, sizeof(REPARSE_DATA_BUFFER), NULL, 0 ); - ok( !status, "got %#lx\n", status ); + todo_wine ok( !status, "got %#lx\n", status );
swprintf( path, ARRAY_SIZE(path), L"%s/testreparse_customdir/file", temp_path ); handle2 = CreateFileW( path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, 0 ); - ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + todo_wine ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); CloseHandle( handle2 );
/* Set the "directory" bit on our custom tag. @@ -6650,29 +6664,32 @@ static void test_reparse_points(void) { io.Information = 1; status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, guid_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( guid_data, guid_data2, data_size ), "buffers didn't match\n" ); + todo_wine ok( !status, "got %#lx\n", status ); + todo_wine ok( io.Information == data_size, "expected size %#Ix, got %#Ix\n", data_size, io.Information ); + todo_wine ok( !memcmp( guid_data, guid_data2, data_size ), "buffers didn't match\n" );
RtlInitUnicodeString( &nameW, L"testreparse_customdir" ); status = NtOpenFile( &handle2, GENERIC_READ, &attr, &io, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0 ); - ok( !status, "got %#lx\n", status ); + todo_wine ok( !status, "got %#lx\n", status );
- io.Information = 1; - status = NtFsControlFile( handle2, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, guid_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( guid_data, guid_data2, data_size ), "buffers didn't match\n" ); - NtClose( handle2 ); + if (!status) + { + io.Information = 1; + status = NtFsControlFile( handle2, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, guid_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( guid_data, guid_data2, data_size ), "buffers didn't match\n" ); + NtClose( handle2 ); + }
RtlInitUnicodeString( &nameW, L"testreparse_customdir\file" ); status = NtOpenFile( &handle2, GENERIC_READ, &attr, &io, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0 ); - ok( !status, "got %#lx\n", status ); - NtClose( handle2 ); + todo_wine ok( !status, "got %#lx\n", status ); + if (!status) NtClose( handle2 ); }
ret = DeleteFileW( path ); - ok( ret == TRUE, "got error %lu\n", GetLastError() ); + todo_wine ok( ret == TRUE, "got error %lu\n", GetLastError() );
status = NtSetInformationFile( handle, &io, &fdi, sizeof(fdi), FileDispositionInformation ); ok( !status, "got %#lx\n", status ); @@ -6685,7 +6702,7 @@ static void test_reparse_points(void) ret = OpenProcessToken( GetCurrentProcess(), TOKEN_ALL_ACCESS, &token ); ok( ret == TRUE, "got error %lu\n", GetLastError() ); ret = LookupPrivilegeValueA( NULL, "SeCreateSymbolicLinkPrivilege", &luid ); - ok( ret == TRUE, "got error %lu\n", GetLastError() ); + todo_wine ok( ret == TRUE, "got error %lu\n", GetLastError() );
privs.PrivilegeCount = 1; privs.Privileges[0].Luid = luid; @@ -6694,7 +6711,7 @@ static void test_reparse_points(void) ok( ret == TRUE, "got error %lu\n", GetLastError() ); if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) { - win_skip("Insufficient permissions to perform symlink tests.\n"); + todo_wine win_skip("Insufficient permissions to perform symlink tests.\n"); CloseHandle( token ); goto out; } diff --git a/server/fd.c b/server/fd.c index c4be028845f..183bc06ca6d 100644 --- a/server/fd.c +++ b/server/fd.c @@ -86,6 +86,13 @@ #ifdef HAVE_SYS_SYSCALL_H #include <sys/syscall.h> #endif +#ifdef HAVE_SYS_XATTR_H +#include <sys/xattr.h> +#endif +#ifdef HAVE_SYS_EXTATTR_H +#undef XATTR_ADDITIONAL_OPTIONS +#include <sys/extattr.h> +#endif
#include "ntstatus.h" #define WIN32_NO_STATUS @@ -97,6 +104,7 @@
#include "winternl.h" #include "winioctl.h" +#include "ddk/ntifs.h" #include "ddk/wdm.h"
#if defined(HAVE_SYS_EPOLL_H) && defined(HAVE_EPOLL_CREATE) @@ -2325,6 +2333,31 @@ void default_fd_reselect_async( struct fd *fd, struct async_queue *queue ) } }
+static int is_dir_empty( int fd ) +{ + DIR *dir; + int empty; + struct dirent *de; + + if ((fd = dup( fd )) == -1) + return -1; + + if (!(dir = fdopendir( fd ))) + { + close( fd ); + return -1; + } + + empty = 1; + while (empty && (de = readdir( dir ))) + { + if (!strcmp( de->d_name, "." ) || !strcmp( de->d_name, ".." )) continue; + empty = 0; + } + closedir( dir ); + return empty; +} + static inline int is_valid_mounted_device( struct stat *st ) { #if defined(linux) || defined(__sun__) @@ -2372,6 +2405,82 @@ static void unmount_device( struct fd *device_fd ) release_object( device ); }
+#ifndef XATTR_USER_PREFIX +# define XATTR_USER_PREFIX "user." +#endif +#ifndef XATTR_USER_PREFIX_LEN +# define XATTR_USER_PREFIX_LEN (sizeof(XATTR_USER_PREFIX) - 1) +#endif + +#define XATTR_REPARSE XATTR_USER_PREFIX "WINEREPARSE" + +static int xattr_fset( int filedes, const char *name, const void *value, size_t size ) +{ +#ifdef HAVE_SYS_XATTR_H +# ifdef XATTR_ADDITIONAL_OPTIONS + return fsetxattr( filedes, name, value, size, 0, 0 ); +# else + return fsetxattr( filedes, name, value, size, 0 ); +# endif +#elif defined(HAVE_SYS_EXTATTR_H) + return extattr_set_fd( filedes, EXTATTR_NAMESPACE_USER, &name[XATTR_USER_PREFIX_LEN], + value, size ); +#else + errno = ENOSYS; + return -1; +#endif +} + +static void set_reparse_point( struct fd *fd, struct async *async ) +{ + char *reparse_name; + struct stat st; + size_t len; + + if (!fd->unix_name) + { + set_error( STATUS_OBJECT_TYPE_MISMATCH ); + return; + } + + if (fstat( fd->unix_fd, &st ) == -1) + { + file_set_error(); + return; + } + + if (S_ISDIR(st.st_mode) && !is_dir_empty( fd->unix_fd )) + { + set_error( STATUS_DIRECTORY_NOT_EMPTY ); + return; + } + + if (xattr_fset( fd->unix_fd, XATTR_REPARSE, get_req_data(), get_req_data_size() ) < 0) + { + file_set_error(); + return; + } + + len = strlen( fd->unix_name ); + if (fd->unix_name[len - 1] != '?') + { + /* we are adding a reparse point where there previously was not one; + * move the file out of the way so open attempts will fail */ + + if (!(reparse_name = mem_alloc( len + 2 ))) return; + memcpy( reparse_name, fd->unix_name, len ); + strcpy( reparse_name + len, "?" ); + + if (rename( fd->unix_name, reparse_name )) + { + free( reparse_name ); + return; + } + free( fd->unix_name ); + fd->closed->unix_name = fd->unix_name = reparse_name; + } +} + /* default read() routine */ void no_fd_read( struct fd *fd, struct async *async, file_pos_t pos ) { @@ -2490,6 +2599,10 @@ void default_fd_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) unmount_device( fd ); break;
+ case FSCTL_SET_REPARSE_POINT: + set_reparse_point( fd, async ); + break; + default: set_error( STATUS_NOT_SUPPORTED ); } @@ -2510,31 +2623,6 @@ static struct fd *get_handle_fd_obj( struct process *process, obj_handle_t handl return fd; }
-static int is_dir_empty( int fd ) -{ - DIR *dir; - int empty; - struct dirent *de; - - if ((fd = dup( fd )) == -1) - return -1; - - if (!(dir = fdopendir( fd ))) - { - close( fd ); - return -1; - } - - empty = 1; - while (empty && (de = readdir( dir ))) - { - if (!strcmp( de->d_name, "." ) || !strcmp( de->d_name, ".." )) continue; - empty = 0; - } - closedir( dir ); - return empty; -} - /* set disposition for the fd */ static void set_fd_disposition( struct fd *fd, unsigned int flags ) {
From: Elizabeth Figura zfigura@codeweavers.com
--- dlls/ntdll/tests/file.c | 32 +++++++++++------- server/fd.c | 75 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 12 deletions(-)
diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index c9a9184ea55..4f2f964e081 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -6474,22 +6474,22 @@ static void test_reparse_points(void) CloseHandle( handle2 );
status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, NULL, 0, NULL, 0 ); - todo_wine ok( status == STATUS_INVALID_BUFFER_SIZE, "got %#lx\n", status ); + ok( status == STATUS_INVALID_BUFFER_SIZE, "got %#lx\n", status ); status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, data, 1, NULL, 0 ); - todo_wine ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); + ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, data, data_size, NULL, 0 ); - todo_wine ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); + ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, data, sizeof(REPARSE_DATA_BUFFER), NULL, 0 ); - todo_wine ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); + ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); data->ReparseDataLength = 0; status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, data, data_size, NULL, 0 ); - todo_wine ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); + ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, data, sizeof(REPARSE_GUID_DATA_BUFFER), NULL, 0 ); - todo_wine ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); + ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status );
data->ReparseTag = 0; status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, data, sizeof(REPARSE_DATA_BUFFER), NULL, 0 ); - todo_wine ok( status == STATUS_IO_REPARSE_TAG_INVALID, "got %#lx\n", status ); + 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 ); @@ -6500,7 +6500,7 @@ static void test_reparse_points(void) todo_wine ok( !status, "got %#lx\n", status );
status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, data, sizeof(REPARSE_DATA_BUFFER), NULL, 0 ); - todo_wine ok( status == STATUS_NOT_A_REPARSE_POINT, "got %#lx\n", status ); + 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. @@ -6642,15 +6642,15 @@ static void test_reparse_points(void)
guid_data->ReparseDataLength = 0; status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, guid_data, data_size, NULL, 0 ); - todo_wine ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); + ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, guid_data, sizeof(REPARSE_GUID_DATA_BUFFER), NULL, 0 ); - todo_wine ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); + ok( status == STATUS_IO_REPARSE_DATA_INVALID, "got %#lx\n", status ); status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, guid_data, sizeof(REPARSE_DATA_BUFFER), NULL, 0 ); - todo_wine ok( !status, "got %#lx\n", status ); + ok( !status, "got %#lx\n", status );
swprintf( path, ARRAY_SIZE(path), L"%s/testreparse_customdir/file", temp_path ); handle2 = CreateFileW( path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, 0 ); - todo_wine ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); CloseHandle( handle2 );
/* Set the "directory" bit on our custom tag. @@ -6690,6 +6690,14 @@ static void test_reparse_points(void)
ret = DeleteFileW( path ); todo_wine ok( ret == TRUE, "got error %lu\n", GetLastError() ); + if (!ret) + { + guid_data->ReparseDataLength = 0; + status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, guid_data, sizeof(REPARSE_DATA_BUFFER), NULL, 0 ); + ok( !status, "got %#lx\n", status ); + ret = DeleteFileW( path ); + ok( ret == TRUE, "got error %lu\n", GetLastError() ); + }
status = NtSetInformationFile( handle, &io, &fdi, sizeof(fdi), FileDispositionInformation ); ok( !status, "got %#lx\n", status ); diff --git a/server/fd.c b/server/fd.c index 183bc06ca6d..0b8fe21fd07 100644 --- a/server/fd.c +++ b/server/fd.c @@ -2431,6 +2431,22 @@ static int xattr_fset( int filedes, const char *name, const void *value, size_t #endif }
+static int xattr_fremove( int filedes, const char *name ) +{ +#ifdef HAVE_SYS_XATTR_H +# ifdef XATTR_ADDITIONAL_OPTIONS + return fremovexattr( filedes, name, 0 ); +# else + return fremovexattr( filedes, name ); +# endif +#elif defined(HAVE_SYS_EXTATTR_H) + return extattr_delete_fd( filedes, EXTATTR_NAMESPACE_USER, &name[XATTR_USER_PREFIX_LEN] ); +#else + errno = ENOSYS; + return -1; +#endif +} + static void set_reparse_point( struct fd *fd, struct async *async ) { char *reparse_name; @@ -2481,6 +2497,61 @@ static void set_reparse_point( struct fd *fd, struct async *async ) } }
+static void delete_reparse_point( struct fd *fd, struct async *async ) +{ + const REPARSE_DATA_BUFFER *data = get_req_data(); + char *base_name; + size_t len; + + if (!fd->unix_name) + { + set_error( STATUS_OBJECT_TYPE_MISMATCH ); + return; + } + + if (!get_req_data_size()) + { + set_error( STATUS_INVALID_BUFFER_SIZE ); + return; + } + + len = strlen( fd->unix_name ); + if (fd->unix_name[len - 1] != '?') + { + set_error( STATUS_NOT_A_REPARSE_POINT ); + return; + } + + if (get_req_data_size() != sizeof(REPARSE_DATA_BUFFER) || data->ReparseDataLength) + { + set_error( STATUS_IO_REPARSE_DATA_INVALID ); + return; + } + + if (!data->ReparseTag) + { + set_error( STATUS_IO_REPARSE_TAG_INVALID ); + return; + } + + if (!(base_name = mem_alloc( len ))) + return; + memcpy( base_name, fd->unix_name, len - 1 ); + base_name[len - 1] = 0; + + if (rename( fd->unix_name, base_name ) < 0) + { + file_set_error(); + free( base_name ); + return; + } + + free( fd->unix_name ); + fd->closed->unix_name = fd->unix_name = base_name; + + xattr_fremove( fd->unix_fd, XATTR_REPARSE ); +} + /* default read() routine */ void no_fd_read( struct fd *fd, struct async *async, file_pos_t pos ) { @@ -2603,6 +2674,10 @@ void default_fd_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) set_reparse_point( fd, async ); break;
+ case FSCTL_DELETE_REPARSE_POINT: + delete_reparse_point( fd, async ); + break; + default: set_error( STATUS_NOT_SUPPORTED ); }
From: Elizabeth Figura zfigura@codeweavers.com
--- dlls/ntdll/tests/file.c | 32 ++++++------ dlls/ntdll/unix/file.c | 9 ---- server/fd.c | 107 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 25 deletions(-)
diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index 4f2f964e081..43913062164 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -6220,22 +6220,22 @@ static void test_reparse_points(void) 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_GUID_DATA_BUFFER) - 1 ); - todo_wine ok( status == STATUS_BUFFER_TOO_SMALL, "got %#lx\n", status ); + 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, sizeof(REPARSE_GUID_DATA_BUFFER) ); - todo_wine ok( status == STATUS_BUFFER_OVERFLOW, "got %#lx\n", status ); - todo_wine ok( io.Information == sizeof(REPARSE_GUID_DATA_BUFFER), "expected size %#Ix, got %#Ix\n", data_size, io.Information ); - todo_wine ok( !memcmp( data, data2, sizeof(REPARSE_GUID_DATA_BUFFER) ), "buffers didn't match\n" ); + ok( status == STATUS_BUFFER_OVERFLOW, "got %#lx\n", status ); + ok( io.Information == sizeof(REPARSE_GUID_DATA_BUFFER), "expected size %#Ix, got %#Ix\n", data_size, io.Information ); + ok( !memcmp( data, data2, sizeof(REPARSE_GUID_DATA_BUFFER) ), "buffers didn't match\n" ); status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, data2, data_size - 1); - todo_wine ok( status == STATUS_BUFFER_OVERFLOW, "got %#lx\n", status ); - todo_wine ok( io.Information == data_size - 1, "expected size %#Ix, got %#Ix\n", data_size, io.Information ); - todo_wine ok( !memcmp( data, data2, data_size - 1 ), "buffers didn't match\n" ); + 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); - todo_wine ok( !status, "got %#lx\n", status ); - todo_wine ok( io.Information == data_size, "expected size %#Ix, got %#Ix\n", data_size, io.Information ); - todo_wine ok( !memcmp( data, data2, data_size ), "buffers didn't match\n" ); + 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, &tag_info, sizeof(tag_info), FileAttributeTagInformation ); ok( !status, "got %#lx\n", status ); @@ -6629,9 +6629,9 @@ static void test_reparse_points(void)
io.Information = 1; status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, guid_data2, data_size + 1); - todo_wine ok( !status, "got %#lx\n", status ); - todo_wine ok( io.Information == data_size, "expected size %#Ix, got %#Ix\n", data_size, io.Information ); - todo_wine ok( !memcmp( guid_data, guid_data2, data_size ), "buffers didn't match\n" ); + ok( !status, "got %#lx\n", status ); + ok( io.Information == data_size, "expected size %#Ix, got %#Ix\n", data_size, io.Information ); + ok( !memcmp( guid_data, guid_data2, data_size ), "buffers didn't match\n" );
RtlInitUnicodeString( &nameW, L"testreparse_customdir\file" ); status = NtCreateFile( &handle2, GENERIC_ALL, &attr, &io, NULL, 0, 0, FILE_OPEN, 0, NULL, 0 ); @@ -6664,9 +6664,9 @@ static void test_reparse_points(void) { io.Information = 1; status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT, NULL, 0, guid_data2, data_size + 1); - todo_wine ok( !status, "got %#lx\n", status ); - todo_wine ok( io.Information == data_size, "expected size %#Ix, got %#Ix\n", data_size, io.Information ); - todo_wine ok( !memcmp( guid_data, guid_data2, data_size ), "buffers didn't match\n" ); + ok( !status, "got %#lx\n", status ); + ok( io.Information == data_size, "expected size %#Ix, got %#Ix\n", data_size, io.Information ); + ok( !memcmp( guid_data, guid_data2, data_size ), "buffers didn't match\n" );
RtlInitUnicodeString( &nameW, L"testreparse_customdir" ); status = NtOpenFile( &handle2, GENERIC_READ, &attr, &io, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0 ); diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index 958ae9a6937..d2fe57e1d35 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -6359,15 +6359,6 @@ NTSTATUS WINAPI NtFsControlFile( HANDLE handle, HANDLE event, PIO_APC_ROUTINE ap break; }
- case FSCTL_GET_REPARSE_POINT: - if (out_buffer && out_size) - { - FIXME("FSCTL_GET_REPARSE_POINT semi-stub\n"); - status = STATUS_NOT_A_REPARSE_POINT; - } - else status = STATUS_INVALID_USER_BUFFER; - break; - case FSCTL_GET_OBJECT_ID: { FILE_OBJECTID_BUFFER *info = out_buffer; diff --git a/server/fd.c b/server/fd.c index 0b8fe21fd07..fa0b92f5017 100644 --- a/server/fd.c +++ b/server/fd.c @@ -2431,6 +2431,56 @@ static int xattr_fset( int filedes, const char *name, const void *value, size_t #endif }
+/* On macOS, getxattr() is significantly slower than listxattr() + * (even for files with no extended attributes). + */ +#ifdef __APPLE__ +static BOOL xattr_exists( const char **path, int *filedes, const char *name ) +{ + char xattrs[1024]; + ssize_t i = 0, ret; + + if (path) + ret = listxattr( *path, xattrs, sizeof(xattrs), 0 ); + else + ret = flistxattr( *filedes, xattrs, sizeof(xattrs), 0 ); + if (ret == -1) + return errno == ERANGE; + + while (i < ret) + { + if (!strcmp( name, &xattrs[i] )) + return TRUE; + i += strlen(&xattrs[i]) + 1; + } + + errno = ENOATTR; + return FALSE; +} +#endif + +static int xattr_fget( int filedes, const char *name, void *value, size_t size ) +{ +#ifdef __APPLE__ + if (!xattr_exists( NULL, &filedes, name )) + return -1; +#endif + +#ifdef HAVE_SYS_XATTR_H +# ifdef XATTR_ADDITIONAL_OPTIONS + return fgetxattr( filedes, name, value, size, 0, 0 ); +# else + return fgetxattr( filedes, name, value, size ); +# endif +#elif defined(HAVE_SYS_EXTATTR_H) + return extattr_get_fd( filedes, EXTATTR_NAMESPACE_USER, &name[XATTR_USER_PREFIX_LEN], + value, size ); +#else + errno = ENOSYS; + return -1; +#endif +} + static int xattr_fremove( int filedes, const char *name ) { #ifdef HAVE_SYS_XATTR_H @@ -2497,6 +2547,59 @@ static void set_reparse_point( struct fd *fd, struct async *async ) } }
+static void get_reparse_point( struct fd *fd, struct async *async ) +{ + void *buffer; + int ret; + + if (!fd->unix_name) + { + set_error( STATUS_OBJECT_TYPE_MISMATCH ); + return; + } + + if (!get_reply_max_size()) + { + set_error( STATUS_INVALID_USER_BUFFER ); + return; + } + + if (fd->unix_name[strlen( fd->unix_name ) - 1] != '?') + { + set_error( STATUS_NOT_A_REPARSE_POINT ); + return; + } + + if (get_reply_max_size() < sizeof(REPARSE_GUID_DATA_BUFFER)) + { + set_error( STATUS_BUFFER_TOO_SMALL ); + return; + } + + if ((ret = xattr_fget( fd->unix_fd, XATTR_REPARSE, NULL, 0 )) < 0) + { + file_set_error(); + return; + } + + if (!(buffer = mem_alloc( ret ))) return; + + if ((ret = xattr_fget( fd->unix_fd, XATTR_REPARSE, buffer, ret )) >= 0) + { + if (ret > get_reply_max_size()) + { + set_error( STATUS_BUFFER_OVERFLOW ); + ret = get_reply_max_size(); + } + set_reply_data_ptr( buffer, ret ); + } + else + { + file_set_error(); + free( buffer ); + } +} + static void delete_reparse_point( struct fd *fd, struct async *async ) { const REPARSE_DATA_BUFFER *data = get_req_data(); @@ -2674,6 +2777,10 @@ void default_fd_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) set_reparse_point( fd, async ); break;
+ case FSCTL_GET_REPARSE_POINT: + get_reparse_point( fd, async ); + break; + case FSCTL_DELETE_REPARSE_POINT: delete_reparse_point( fd, async ); break;
From: Elizabeth Figura zfigura@codeweavers.com
Querying the NT name from a file returns its resolved path, without any reparse points. Much like with wow64 redirection, then, we need to modify the NT name. It also may convert a handle-relative path to an absolute one, clearing the RootDirectory. --- dlls/ntdll/unix/file.c | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-)
diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index d2fe57e1d35..fec3dc97d28 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -3460,10 +3460,13 @@ done: * * Helper for nt_to_unix_file_name */ -static NTSTATUS lookup_unix_name( int root_fd, const WCHAR *name, int name_len, char **buffer, int unix_len, +static NTSTATUS lookup_unix_name( int root_fd, OBJECT_ATTRIBUTES *attr, UNICODE_STRING *nt_name, + unsigned int nt_pos, char **buffer, int unix_len, int pos, UINT disposition, BOOL is_unix ) { static const WCHAR invalid_charsW[] = { INVALID_NT_CHARS, '/', 0 }; + const WCHAR *name = attr->ObjectName->Buffer + nt_pos; + unsigned int name_len = (attr->ObjectName->Length / sizeof(WCHAR)) - nt_pos; NTSTATUS status; int ret; struct stat st; @@ -3579,13 +3582,14 @@ static NTSTATUS lookup_unix_name( int root_fd, const WCHAR *name, int name_len, /****************************************************************************** * nt_to_unix_file_name_no_root */ -static NTSTATUS nt_to_unix_file_name_no_root( const UNICODE_STRING *nameW, char **unix_name_ret, - UINT disposition ) +static NTSTATUS nt_to_unix_file_name_no_root( OBJECT_ATTRIBUTES *attr, UNICODE_STRING *nt_name, + char **unix_name_ret, UINT disposition ) { static const WCHAR unixW[] = {'u','n','i','x'}; static const WCHAR invalid_charsW[] = { INVALID_NT_CHARS, 0 };
NTSTATUS status = STATUS_SUCCESS; + unsigned int nt_pos; const WCHAR *name; struct stat st; char *unix_name; @@ -3593,16 +3597,16 @@ static NTSTATUS nt_to_unix_file_name_no_root( const UNICODE_STRING *nameW, char WCHAR prefix[MAX_DIR_ENTRY_LEN + 1]; BOOLEAN is_unix = FALSE;
- name = nameW->Buffer; - name_len = nameW->Length / sizeof(WCHAR); + name = attr->ObjectName->Buffer; + name_len = attr->ObjectName->Length / sizeof(WCHAR);
if (!name_len || name[0] != '\') return STATUS_OBJECT_PATH_SYNTAX_BAD;
- if (!(pos = get_dos_prefix_len( nameW ))) + if (!(nt_pos = get_dos_prefix_len( attr->ObjectName ))) return STATUS_BAD_DEVICE_TYPE; /* no DOS prefix, assume NT native name */
- name += pos; - name_len -= pos; + name += nt_pos; + name_len -= nt_pos;
if (!name_len) return STATUS_OBJECT_NAME_INVALID;
@@ -3671,18 +3675,16 @@ static NTSTATUS nt_to_unix_file_name_no_root( const UNICODE_STRING *nameW, char
prefix_len++; /* skip initial backslash */ if (name_len > prefix_len && name[prefix_len] == '\') prefix_len++; /* allow a second backslash */ - name += prefix_len; - name_len -= prefix_len; + nt_pos += prefix_len;
- status = lookup_unix_name( AT_FDCWD, name, name_len, &unix_name, unix_len, pos, disposition, is_unix ); + status = lookup_unix_name( AT_FDCWD, attr, nt_name, nt_pos, + &unix_name, unix_len, pos, disposition, is_unix ); if (status == STATUS_SUCCESS || status == STATUS_NO_SUCH_FILE) { - TRACE( "%s -> %s\n", debugstr_us(nameW), debugstr_a(unix_name) ); *unix_name_ret = unix_name; } else { - TRACE( "%s not found in %s\n", debugstr_w(name), debugstr_an(unix_name, pos) ); free( unix_name ); } return status; @@ -3698,7 +3700,8 @@ static NTSTATUS nt_to_unix_file_name_no_root( const UNICODE_STRING *nameW, char * element doesn't have to exist; in that case STATUS_NO_SUCH_FILE is * returned, but the unix name is still filled in properly. */ -static NTSTATUS nt_to_unix_file_name( const OBJECT_ATTRIBUTES *attr, char **name_ret, UINT disposition ) +static NTSTATUS nt_to_unix_file_name( OBJECT_ATTRIBUTES *attr, UNICODE_STRING *nt_name, + char **name_ret, UINT disposition ) { enum server_fd_type type; int root_fd, needs_close; @@ -3708,7 +3711,7 @@ static NTSTATUS nt_to_unix_file_name( const OBJECT_ATTRIBUTES *attr, char **name NTSTATUS status;
if (!attr->RootDirectory) /* without root dir fall back to normal lookup */ - return nt_to_unix_file_name_no_root( attr->ObjectName, name_ret, disposition ); + return nt_to_unix_file_name_no_root( attr, nt_name, name_ret, disposition );
name = attr->ObjectName->Buffer; name_len = attr->ObjectName->Length / sizeof(WCHAR); @@ -3728,7 +3731,7 @@ static NTSTATUS nt_to_unix_file_name( const OBJECT_ATTRIBUTES *attr, char **name } else { - status = lookup_unix_name( root_fd, name, name_len, &unix_name, unix_len, 1, disposition, FALSE ); + status = lookup_unix_name( root_fd, attr, nt_name, 0, &unix_name, unix_len, 1, disposition, FALSE ); if (needs_close) close( root_fd ); } } @@ -4004,7 +4007,7 @@ NTSTATUS get_nt_and_unix_names( OBJECT_ATTRIBUTES *attr, UNICODE_STRING *nt_name #ifndef _WIN64 get_redirect( attr, nt_name ); #endif - status = nt_to_unix_file_name( attr, unix_name_ret, disposition ); + status = nt_to_unix_file_name( attr, nt_name, unix_name_ret, disposition ); }
if (!status || status == STATUS_NO_SUCH_FILE)
From: Elizabeth Figura zfigura@codeweavers.com
--- dlls/ntdll/tests/file.c | 66 +++++++++-------- dlls/ntdll/unix/file.c | 160 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 189 insertions(+), 37 deletions(-)
diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index 43913062164..7016ca166f9 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -6270,22 +6270,25 @@ static void test_reparse_points(void)
status = NtCreateFile( &handle2, GENERIC_ALL, &attr, &io, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT, NULL, 0 ); - ok( status == STATUS_FILE_IS_A_DIRECTORY, "got %#lx\n", status ); + todo_wine ok( status == STATUS_FILE_IS_A_DIRECTORY, "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( handle2, &io, &tag_info, sizeof(tag_info), FileAttributeTagInformation ); - ok( !status, "got %#lx\n", status ); - todo_wine ok( tag_info.FileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), - "got attributes %#lx\n", tag_info.FileAttributes ); - todo_wine ok( tag_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", tag_info.ReparseTag ); - status = NtQueryInformationFile( handle2, &io, &std_info, sizeof(std_info), FileStandardInformation ); - ok( !status, "got %#lx\n", status ); - ok( !std_info.AllocationSize.QuadPart, "got size %#I64x\n", std_info.AllocationSize.QuadPart ); - ok( !std_info.EndOfFile.QuadPart, "got eof %#I64x\n", std_info.EndOfFile.QuadPart ); - ok( std_info.NumberOfLinks == 1, "got %lu links\n", std_info.NumberOfLinks ); - ok( std_info.Directory == TRUE, "got directory %u\n", std_info.Directory ); - NtClose( handle2 ); + todo_wine ok( !status, "got %#lx\n", status ); + if (!status) + { + status = NtQueryInformationFile( handle2, &io, &tag_info, sizeof(tag_info), FileAttributeTagInformation ); + ok( !status, "got %#lx\n", status ); + todo_wine ok( tag_info.FileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), + "got attributes %#lx\n", tag_info.FileAttributes ); + todo_wine ok( tag_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", tag_info.ReparseTag ); + status = NtQueryInformationFile( handle2, &io, &std_info, sizeof(std_info), FileStandardInformation ); + ok( !status, "got %#lx\n", status ); + ok( !std_info.AllocationSize.QuadPart, "got size %#I64x\n", std_info.AllocationSize.QuadPart ); + ok( !std_info.EndOfFile.QuadPart, "got eof %#I64x\n", std_info.EndOfFile.QuadPart ); + ok( std_info.NumberOfLinks == 1, "got %lu links\n", std_info.NumberOfLinks ); + ok( std_info.Directory == TRUE, "got directory %u\n", std_info.Directory ); + NtClose( handle2 ); + }
/* alter the target in-place */ data_size = init_reparse_mount_point( &data, L"\??\C:\bogus" ); @@ -6294,8 +6297,8 @@ static void test_reparse_points(void)
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 ); - todo_wine ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "got %#lx\n", status ); - if (!status) NtClose( handle2 ); + ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "got %#lx\n", status ); + NtClose( handle2 );
swprintf( path, ARRAY_SIZE(path), L"\??\%stestreparse_dir\", temp_path ); data_size = init_reparse_mount_point( &data, path ); @@ -6375,11 +6378,14 @@ static void test_reparse_points(void)
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() ); - todo_wine ok( find_data.dwFileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), - "got attributes %#lx\n", find_data.dwFileAttributes ); - todo_wine ok( find_data.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", find_data.dwReserved0 ); - FindClose( find_handle ); + todo_wine ok( find_handle != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + if (find_handle != INVALID_HANDLE_VALUE) + { + todo_wine ok( find_data.dwFileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), + "got attributes %#lx\n", find_data.dwFileAttributes ); + todo_wine ok( find_data.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", find_data.dwReserved0 ); + FindClose( find_handle ); + }
/* Test using the reparse point as a parent. * On some machines this returns STATUS_REPARSE_POINT_NOT_RESOLVED (which @@ -6450,8 +6456,8 @@ static void test_reparse_points(void)
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 ); - todo_wine ok( status == STATUS_REPARSE_POINT_NOT_RESOLVED, "got %#lx\n", status ); - if (!status) NtClose( handle2 ); + ok( status == STATUS_REPARSE_POINT_NOT_RESOLVED, "got %#lx\n", status ); + NtClose( handle2 );
swprintf( path, ARRAY_SIZE(path), L"\??\%s", temp_path ); data_size = init_reparse_mount_point( &data, path ); @@ -6460,15 +6466,15 @@ static void test_reparse_points(void)
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 ); - todo_wine ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + 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 | FILE_FLAG_BACKUP_SEMANTICS, 0 ); - todo_wine ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + ok( handle2 != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); ret = GetFinalPathNameByHandleW( handle2, ret_path, ARRAY_SIZE( ret_path ), VOLUME_NAME_DOS ); - todo_wine ok( ret > 0, "got error %lu\n", GetLastError() ); + ok( ret > 0, "got error %lu\n", GetLastError() ); swprintf( path, ARRAY_SIZE(path), L"\\?\%stestreparse_dirlink", temp_path ); todo_wine ok( !wcscmp( ret_path, path ), "expected path %s, got %s\n", debugstr_w( path ), debugstr_w( ret_path )); CloseHandle( handle2 ); @@ -6602,7 +6608,7 @@ static void test_reparse_points(void)
status = NtOpenFile( &handle2, GENERIC_READ, &attr, &io, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0 ); - todo_wine ok( status == STATUS_IO_REPARSE_TAG_NOT_HANDLED, "got %#lx\n", status ); + ok( status == STATUS_IO_REPARSE_TAG_NOT_HANDLED, "got %#lx\n", status );
status = NtSetInformationFile( handle, &io, &fdi, sizeof(fdi), FileDispositionInformation ); ok( !status, "got %#lx\n", status ); @@ -6635,10 +6641,10 @@ static void test_reparse_points(void)
RtlInitUnicodeString( &nameW, L"testreparse_customdir\file" ); status = NtCreateFile( &handle2, GENERIC_ALL, &attr, &io, NULL, 0, 0, FILE_OPEN, 0, NULL, 0 ); - todo_wine ok( status == STATUS_IO_REPARSE_TAG_NOT_HANDLED, "got %#lx\n", status ); + ok( status == STATUS_IO_REPARSE_TAG_NOT_HANDLED, "got %#lx\n", status );
status = NtCreateFile( &handle2, GENERIC_ALL, &attr, &io, NULL, 0, 0, FILE_OPEN, FILE_OPEN_REPARSE_POINT, NULL, 0 ); - todo_wine ok( status == STATUS_IO_REPARSE_TAG_NOT_HANDLED, "got %#lx\n", status ); + ok( status == STATUS_IO_REPARSE_TAG_NOT_HANDLED, "got %#lx\n", status );
guid_data->ReparseDataLength = 0; status = NtFsControlFile( handle, NULL, NULL, NULL, &io, FSCTL_DELETE_REPARSE_POINT, guid_data, data_size, NULL, 0 ); @@ -7221,7 +7227,7 @@ out:
swprintf( path, ARRAY_SIZE(path), L"%s/testreparse_dir", temp_path ); ret = RemoveDirectoryW( path ); - ok( ret == TRUE, "got error %lu\n", GetLastError() ); + todo_wine ok( ret == TRUE, "got error %lu\n", GetLastError() );
CloseHandle( temp_dir ); } diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index fec3dc97d28..161e4fc3be8 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -114,6 +114,7 @@ #include "winternl.h" #include "ddk/ntddk.h" #include "ddk/ntddser.h" +#include "ddk/ntifs.h" #include "ddk/wdm.h" #define WINE_MOUNTMGR_EXTENSIONS #include "ddk/mountmgr.h" @@ -181,6 +182,8 @@ typedef struct #define SAMBA_XATTR_DOS_ATTRIB XATTR_USER_PREFIX "DOSATTRIB" #define XATTR_ATTRIBS_MASK (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM)
+#define XATTR_REPARSE XATTR_USER_PREFIX "WINEREPARSE" + struct file_identity { dev_t dev; @@ -3455,14 +3458,19 @@ done: }
+static NTSTATUS resolve_reparse_point( int fd, int root_fd, OBJECT_ATTRIBUTES *attr, + UNICODE_STRING *nt_name, unsigned int nt_pos, unsigned int reparse_len, char **unix_name, + int unix_len, int pos, UINT disposition, BOOL is_unix, unsigned int reparse_count ); + + /****************************************************************************** * lookup_unix_name * * Helper for nt_to_unix_file_name */ static NTSTATUS lookup_unix_name( int root_fd, OBJECT_ATTRIBUTES *attr, UNICODE_STRING *nt_name, - unsigned int nt_pos, char **buffer, int unix_len, - int pos, UINT disposition, BOOL is_unix ) + unsigned int nt_pos, char **buffer, int unix_len, int pos, + UINT disposition, BOOL is_unix, unsigned int reparse_count ) { static const WCHAR invalid_charsW[] = { INVALID_NT_CHARS, '/', 0 }; const WCHAR *name = attr->ObjectName->Buffer + nt_pos; @@ -3523,6 +3531,7 @@ static NTSTATUS lookup_unix_name( int root_fd, OBJECT_ATTRIBUTES *attr, UNICODE_ while (name_len) { const WCHAR *end, *next; + WCHAR *reparse_name;
end = name; while (end < name + name_len && *end != '\') end++; @@ -3542,6 +3551,25 @@ static NTSTATUS lookup_unix_name( int root_fd, OBJECT_ATTRIBUTES *attr, UNICODE_
status = find_file_in_dir( root_fd, unix_name, pos, name, end - name, is_unix );
+ /* try to resolve it as a reparse point */ + if (status == STATUS_OBJECT_NAME_NOT_FOUND && (reparse_name = malloc( (end - name + 1) * sizeof(WCHAR) ))) + { + int reparse_fd; + + memcpy( reparse_name, name, (end - name) * sizeof(WCHAR) ); + reparse_name[end - name] = '?'; + + if (!find_file_in_dir( root_fd, unix_name, pos, reparse_name, end - name + 1, is_unix ) + && (reparse_fd = openat( root_fd, unix_name, O_RDONLY )) >= 0) + { + status = resolve_reparse_point( reparse_fd, root_fd, attr, nt_name, nt_pos, next - name, buffer, + unix_len, pos, disposition, is_unix, reparse_count ); + close( reparse_fd ); + free( reparse_name ); + return status; + } + } + /* if this is the last element, not finding it is not necessarily fatal */ if (!name_len) { @@ -3572,6 +3600,7 @@ static NTSTATUS lookup_unix_name( int root_fd, OBJECT_ATTRIBUTES *attr, UNICODE_ if (status != STATUS_SUCCESS) break;
pos += strlen( unix_name + pos ); + nt_pos += next - name; name = next; }
@@ -3583,7 +3612,7 @@ static NTSTATUS lookup_unix_name( int root_fd, OBJECT_ATTRIBUTES *attr, UNICODE_ * nt_to_unix_file_name_no_root */ static NTSTATUS nt_to_unix_file_name_no_root( OBJECT_ATTRIBUTES *attr, UNICODE_STRING *nt_name, - char **unix_name_ret, UINT disposition ) + char **unix_name_ret, UINT disposition, unsigned int reparse_count ) { static const WCHAR unixW[] = {'u','n','i','x'}; static const WCHAR invalid_charsW[] = { INVALID_NT_CHARS, 0 }; @@ -3677,8 +3706,8 @@ static NTSTATUS nt_to_unix_file_name_no_root( OBJECT_ATTRIBUTES *attr, UNICODE_S if (name_len > prefix_len && name[prefix_len] == '\') prefix_len++; /* allow a second backslash */ nt_pos += prefix_len;
- status = lookup_unix_name( AT_FDCWD, attr, nt_name, nt_pos, - &unix_name, unix_len, pos, disposition, is_unix ); + status = lookup_unix_name( AT_FDCWD, attr, nt_name, nt_pos, &unix_name, unix_len, + pos, disposition, is_unix, reparse_count ); if (status == STATUS_SUCCESS || status == STATUS_NO_SUCH_FILE) { *unix_name_ret = unix_name; @@ -3711,7 +3740,7 @@ static NTSTATUS nt_to_unix_file_name( OBJECT_ATTRIBUTES *attr, UNICODE_STRING *n NTSTATUS status;
if (!attr->RootDirectory) /* without root dir fall back to normal lookup */ - return nt_to_unix_file_name_no_root( attr, nt_name, name_ret, disposition ); + return nt_to_unix_file_name_no_root( attr, nt_name, name_ret, disposition, 0 );
name = attr->ObjectName->Buffer; name_len = attr->ObjectName->Length / sizeof(WCHAR); @@ -3731,7 +3760,8 @@ static NTSTATUS nt_to_unix_file_name( OBJECT_ATTRIBUTES *attr, UNICODE_STRING *n } else { - status = lookup_unix_name( root_fd, attr, nt_name, 0, &unix_name, unix_len, 1, disposition, FALSE ); + status = lookup_unix_name( root_fd, attr, nt_name, 0, &unix_name, unix_len, + 1, disposition, FALSE, 0 ); if (needs_close) close( root_fd ); } } @@ -3830,6 +3860,122 @@ static WCHAR *collapse_path( WCHAR *path ) }
+/* from MSDN */ +#define MAXIMUM_REPARSE_COUNT 63 + + +static NTSTATUS resolve_absolute_reparse_point( const WCHAR *target, unsigned int target_len, + OBJECT_ATTRIBUTES *attr, UNICODE_STRING *nt_name, const WCHAR *remainder, unsigned int remainder_len, + char **unix_name, UINT disposition, unsigned int reparse_count ) +{ + WCHAR *new_nt_name; + char *new_unix_name; + NTSTATUS status; + + TRACE( "target %s\n", debugstr_wn(target, target_len) ); + + /* glue together the target with the remainder of the path */ + + if (!(new_nt_name = malloc( (target_len + 1 + remainder_len + 1) * sizeof(WCHAR) ))) return STATUS_NO_MEMORY; + memcpy( new_nt_name, target, target_len * sizeof(WCHAR) ); + if (remainder_len) + { + if (new_nt_name[target_len - 1] != '\') + new_nt_name[target_len++] = '\'; + memcpy( new_nt_name + target_len, remainder, remainder_len * sizeof(WCHAR) ); + } + new_nt_name[target_len + remainder_len] = 0; + + free( nt_name->Buffer ); + nt_name->Buffer = new_nt_name; + nt_name->Length = (target_len + remainder_len) * sizeof(WCHAR); + nt_name->MaximumLength = nt_name->Length + sizeof(WCHAR); + attr->RootDirectory = 0; + attr->ObjectName = nt_name; + + status = nt_to_unix_file_name_no_root( attr, nt_name, &new_unix_name, disposition, reparse_count ); + if (!status || status == STATUS_NO_SUCH_FILE) + { + free( *unix_name ); + *unix_name = new_unix_name; + } + return status; +} + + +static NTSTATUS resolve_reparse_point( int fd, int root_fd, OBJECT_ATTRIBUTES *attr, UNICODE_STRING *nt_name, + unsigned int nt_pos, unsigned int reparse_len, char **unix_name, int unix_len, int pos, + UINT disposition, BOOL is_unix, unsigned int reparse_count ) +{ + const WCHAR *name = attr->ObjectName->Buffer; + unsigned int name_len = attr->ObjectName->Length / sizeof(WCHAR); + const WCHAR *remainder = name + nt_pos + reparse_len; + unsigned int remainder_len = name_len - (nt_pos + reparse_len); + REPARSE_DATA_BUFFER *data; + NTSTATUS status; + int size; + + if (reparse_count++ >= MAXIMUM_REPARSE_COUNT) + { + WARN( "too many reparse points\n" ); + return STATUS_REPARSE_POINT_NOT_RESOLVED; + } + + if ((size = xattr_fget( fd, XATTR_REPARSE, NULL, 0 )) < 0) + { + ERR( "failed to get xattr size, errno %d\n", errno ); + return errno_to_status( errno ); + } + + if (!(data = malloc( size ))) return STATUS_NO_MEMORY; + + if (xattr_fget( fd, XATTR_REPARSE, data, size ) != size) + { + ERR( "failed to read %d bytes, errno %d\n", size, errno ); + free( data ); + return errno_to_status( errno ); + } + + TRACE( "size %d tag %#x\n", size, data->ReparseTag ); + + if (size < sizeof(*data)) + { + free( data ); + return STATUS_IO_REPARSE_DATA_INVALID; + } + + switch (data->ReparseTag) + { + case IO_REPARSE_TAG_MOUNT_POINT: + { + const WCHAR *target = data->MountPointReparseBuffer.PathBuffer + + data->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR); + USHORT target_len = data->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR); + + status = resolve_absolute_reparse_point( target, target_len, attr, nt_name, remainder, remainder_len, + unix_name, disposition, reparse_count ); + break; + } + + default: + if (!IsReparseTagDirectory(data->ReparseTag)) + { + status = STATUS_IO_REPARSE_TAG_NOT_HANDLED; + break; + } + + /* Directory reparse tags can be opened as normal directories. + * This is doable, but tricky, and unlikely to be needed. */ + FIXME( "directory reparse tag %#x\n", (int)data->ReparseTag ); + status = STATUS_NOT_IMPLEMENTED; + break; + } + + free( data ); + return status; +} + + /*********************************************************************** * find_drive_nt_root */
I've put together a new implementation using xattr. Fortunately it wasn't too difficult, and it is indeed a bit simpler. It also is kinder to some edge cases related to querying attributes from a opened file.
The implementation still uses a ?-suffixed name for files with a reparse tag. Otherwise we'd have to force segment-by-segment resolution for all files so we can check whether a reparse point exists, which seems very bad. As such there's still a bit of potential for race conditions, but I don't see a way to avoid that. At least there are still no meaningful race conditions within a prefix.
Some interactions with hard links still can't quite work with this approach. Notably, because we move the file out of the way when adding a reparse point, adding a reparse point to a hardlinked file (or removing one) can't affect the other entries. I think that's the only thing that doesn't work, though.