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.
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 958ae9a6937..f652af80c07 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 | 237 +++++++++++++++++++++++----------------- server/fd.c | 131 +++++++++++++++++----- 2 files changed, 240 insertions(+), 128 deletions(-)
diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index 1f35bd58edf..b75ddff30bf 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -6203,7 +6203,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() ); @@ -6212,49 +6212,42 @@ 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, &stat_info, sizeof(stat_info), FileStatInformation ); ok( !status, "got %#lx\n", status ); - ok( stat_info.FileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), + todo_wine 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 ); + todo_wine ok( stat_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", stat_info.ReparseTag ); ok( !stat_info.AllocationSize.QuadPart, "got size %#I64x\n", stat_info.AllocationSize.QuadPart ); ok( !stat_info.EndOfFile.QuadPart, "got eof %#I64x\n", stat_info.EndOfFile.QuadPart );
status = NtQueryInformationFile( handle2, &io, &stat_info, sizeof(stat_info), FileStatInformation ); ok( !status, "got %#lx\n", status ); - ok( stat_info.FileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), + todo_wine 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 ); + todo_wine 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 );
@@ -6262,7 +6255,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 ); @@ -6272,9 +6266,9 @@ static void test_reparse_points(void) ok( !status, "got %#lx\n", status ); status = NtQueryInformationFile( handle2, &io, &stat_info, sizeof(stat_info), FileStatInformation ); ok( !status, "got %#lx\n", status ); - ok( stat_info.FileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), + todo_wine 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 ); + todo_wine ok( stat_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", stat_info.ReparseTag ); ok( !stat_info.AllocationSize.QuadPart, "got size %#I64x\n", stat_info.AllocationSize.QuadPart ); ok( !stat_info.EndOfFile.QuadPart, "got eof %#I64x\n", stat_info.EndOfFile.QuadPart ); NtClose( handle2 ); @@ -6286,7 +6280,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 ); @@ -6307,7 +6302,7 @@ 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 ); + todo_wine ok( status == STATUS_PENDING, "got %#lx\n", status ); ret = WaitForSingleObject( handle, 0 ); ok( !ret, "got %#x\n", ret ); dir_info = (FILE_DIRECTORY_INFORMATION *)dir_buffer; @@ -6354,7 +6349,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 );
@@ -6367,9 +6362,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 );
/* Attempting to use the reparse point itself as a parent results in the @@ -6386,14 +6381,32 @@ static void test_reparse_points(void) 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 ); + todo_wine ok( status == STATUS_REPARSE_POINT_NOT_RESOLVED, "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_IF, FILE_OPEN_REPARSE_POINT, 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) + { + status = NtSetInformationFile( subdir, &io, &fdi, sizeof(fdi), FileDispositionInformation ); + ok( !status, "got %#lx\n", status ); + NtClose( subdir ); + } 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 ); @@ -6408,7 +6421,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 ); + todo_wine ok( status == STATUS_NOT_A_DIRECTORY, "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 ); @@ -6417,7 +6431,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 ); @@ -6426,47 +6441,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. @@ -6483,7 +6498,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 ); + todo_wine ok( status == STATUS_NOT_A_DIRECTORY, "got %#lx\n", status ); + if (!status) NtClose( handle2 );
swprintf( path, ARRAY_SIZE(path), L"%stestreparse_dir2", temp_path ); ret = DeleteFileW( path ); @@ -6493,18 +6509,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 ); @@ -6527,35 +6552,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" ); - - 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" ); + 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" );
- 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 ); @@ -6582,28 +6610,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. */ @@ -6614,28 +6642,31 @@ 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 ); + todo_wine ok( !status, "got %#lx\n", 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 ); @@ -6648,7 +6679,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; @@ -6657,7 +6688,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..e96280d757e 100644 --- a/server/fd.c +++ b/server/fd.c @@ -97,6 +97,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) @@ -1092,8 +1093,17 @@ static void unlink_closed_fd( struct inode *inode, struct closed_fd *fd ) struct stat st; if (!stat( fd->unix_name, &st ) && st.st_dev == inode->device->dev && st.st_ino == inode->ino) { + size_t len = strlen( fd->unix_name ); + if (S_ISDIR(st.st_mode)) rmdir( fd->unix_name ); else unlink( fd->unix_name ); + + if (fd->unix_name[len - 1] == '?') + { + fd->unix_name[len - 1] = '*'; + unlink( fd->unix_name ); + fd->unix_name[len - 1] = '?'; + } } }
@@ -2325,6 +2335,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 +2407,73 @@ static void unmount_device( struct fd *device_fd ) release_object( device ); }
+static void set_reparse_point( struct fd *fd, struct async *async ) +{ + char *reparse_name; + struct stat st; + int data_fd; + 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; + } + + len = strlen( fd->unix_name ); + if (fd->unix_name[len - 1] == '?') + --len; + if (!(reparse_name = mem_alloc( len + 2 ))) return; + memcpy( reparse_name, fd->unix_name, len ); + strcpy( reparse_name + len, "*" ); + + if ((data_fd = open( reparse_name, O_WRONLY | O_CREAT | O_TRUNC, 0666 )) < 0) + { + free( reparse_name ); + file_set_error(); + return; + } + if (write( data_fd, get_req_data(), get_req_data_size() ) != get_req_data_size()) + { + close( data_fd ); + unlink( reparse_name ); + free( reparse_name ); + file_set_error(); + return; + } + close( data_fd ); + + if (!fd->unix_name[len]) + { + /* we are adding a reparse point where there previously was not one; + * move the file out of the way so open attempts will fail */ + + 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; + } + else + { + free( reparse_name ); + } +} /* default read() routine */ void no_fd_read( struct fd *fd, struct async *async, file_pos_t pos ) { @@ -2490,6 +2592,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 +2616,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 | 61 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 12 deletions(-)
diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index b75ddff30bf..7743caf71de 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -6455,22 +6455,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 ); @@ -6481,7 +6481,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. @@ -6623,15 +6623,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. */ @@ -6667,6 +6667,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 e96280d757e..76272673f7b 100644 --- a/server/fd.c +++ b/server/fd.c @@ -2474,6 +2474,63 @@ static void set_reparse_point( struct fd *fd, struct async *async ) free( reparse_name ); } } + +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; + } + + fd->unix_name[len - 1] = '*'; + unlink( fd->unix_name ); + + free( fd->unix_name ); + fd->closed->unix_name = fd->unix_name = base_name; +} + /* default read() routine */ void no_fd_read( struct fd *fd, struct async *async, file_pos_t pos ) { @@ -2596,6 +2653,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 | 66 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 25 deletions(-)
diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index 7743caf71de..413cc55d511 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -6218,22 +6218,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, &stat_info, sizeof(stat_info), FileStatInformation ); ok( !status, "got %#lx\n", status ); @@ -6610,9 +6610,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 ); @@ -6642,9 +6642,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 f652af80c07..fec3dc97d28 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -6362,15 +6362,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 76272673f7b..50f97afe7f9 100644 --- a/server/fd.c +++ b/server/fd.c @@ -2475,6 +2475,68 @@ static void set_reparse_point( struct fd *fd, struct async *async ) } }
+static void get_reparse_point( struct fd *fd, struct async *async ) +{ + char *reparse_name; + void *buffer; + ssize_t ret; + int data_fd; + size_t len; + char dummy; + + if (!fd->unix_name) + { + set_error( STATUS_OBJECT_TYPE_MISMATCH ); + return; + } + + if (!get_reply_max_size()) + { + set_error( STATUS_INVALID_USER_BUFFER ); + return; + } + + len = strlen( fd->unix_name ); + if (fd->unix_name[len - 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 (!(reparse_name = mem_alloc( len + 1 ))) return; + memcpy( reparse_name, fd->unix_name, len + 1 ); + reparse_name[len - 1] = '*'; + + if ((data_fd = open( reparse_name, O_RDONLY )) < 0) + { + file_set_error(); + free( reparse_name ); + return; + } + free( reparse_name ); + + if (!(buffer = mem_alloc( get_reply_max_size() ))) + { + close( data_fd ); + return; + } + + if ((ret = read( data_fd, buffer, get_reply_max_size() )) >= 0) + { + if (ret == get_reply_max_size() && read( data_fd, &dummy, 1 ) == 1) + set_error( STATUS_BUFFER_OVERFLOW ); + set_reply_data_ptr( buffer, ret ); + } + else file_set_error(); + close( data_fd ); +} + static void delete_reparse_point( struct fd *fd, struct async *async ) { const REPARSE_DATA_BUFFER *data = get_req_data(); @@ -2653,6 +2715,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
--- dlls/ntdll/tests/file.c | 58 ++++++++------- dlls/ntdll/unix/file.c | 154 ++++++++++++++++++++++++++++++++++++++-- include/winnt.h | 1 + 3 files changed, 180 insertions(+), 33 deletions(-)
diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index 413cc55d511..009cc55d984 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -6260,18 +6260,21 @@ 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, &stat_info, sizeof(stat_info), FileStatInformation ); - ok( !status, "got %#lx\n", status ); - todo_wine ok( stat_info.FileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), - "got attributes %#lx\n", stat_info.FileAttributes ); - todo_wine ok( stat_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", stat_info.ReparseTag ); - ok( !stat_info.AllocationSize.QuadPart, "got size %#I64x\n", stat_info.AllocationSize.QuadPart ); - ok( !stat_info.EndOfFile.QuadPart, "got eof %#I64x\n", stat_info.EndOfFile.QuadPart ); - NtClose( handle2 ); + todo_wine ok( !status, "got %#lx\n", status ); + if (!status) + { + status = NtQueryInformationFile( handle2, &io, &stat_info, sizeof(stat_info), FileStatInformation ); + ok( !status, "got %#lx\n", status ); + todo_wine ok( stat_info.FileAttributes == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT), + "got attributes %#lx\n", stat_info.FileAttributes ); + todo_wine ok( stat_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "got tag %#lx\n", stat_info.ReparseTag ); + ok( !stat_info.AllocationSize.QuadPart, "got size %#I64x\n", stat_info.AllocationSize.QuadPart ); + ok( !stat_info.EndOfFile.QuadPart, "got eof %#I64x\n", stat_info.EndOfFile.QuadPart ); + NtClose( handle2 ); + }
/* alter the target in-place */ data_size = init_reparse_mount_point( &data, L"\??\C:\bogus" ); @@ -6280,8 +6283,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 ); @@ -6361,11 +6364,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 ); + }
/* Attempting to use the reparse point itself as a parent results in the * somewhat cryptic STATUS_REPARSE_POINT_NOT_RESOLVED. @@ -6431,8 +6437,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 ); @@ -6441,15 +6447,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 ); @@ -6583,7 +6589,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 ); @@ -6616,10 +6622,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 ); @@ -7192,7 +7198,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..86a2a8571c5 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" @@ -3455,14 +3456,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 +3529,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 +3549,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 +3598,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 +3610,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 +3704,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 +3738,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 +3758,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 +3858,118 @@ 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; + struct stat st; + + if (reparse_count++ >= MAXIMUM_REPARSE_COUNT) + { + WARN( "too many reparse points\n" ); + return STATUS_REPARSE_POINT_NOT_RESOLVED; + } + + fstat( fd, &st ); + + if (!(data = malloc( st.st_size ))) return STATUS_NO_MEMORY; + + if (read( fd, data, st.st_size ) != st.st_size) + { + ERR( "failed to read %llu bytes, errno %d\n", (unsigned long long)st.st_size, errno ); + free( data ); + return errno_to_status( errno ); + } + + TRACE( "size %llu tag %#x\n", (unsigned long long)st.st_size, data->ReparseTag ); + + if (st.st_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 */ diff --git a/include/winnt.h b/include/winnt.h index e112740e0e1..5d65ce42e4a 100644 --- a/include/winnt.h +++ b/include/winnt.h @@ -2569,6 +2569,7 @@ static FORCEINLINE struct _TEB * WINAPI NtCurrentTeb(void) #define IO_REPARSE_TAG_ONEDRIVE __MSABI_LONG(0x80000021) #define IO_REPARSE_TAG_GVFS_TOMBSTONE __MSABI_LONG(0xA0000022)
+#define IsReparseTagDirectory(x) ((x) & 0x10000000) #define IsReparseTagNameSurrogate(x) ((x) & 0x20000000)
/*
I suspect that xattr would be better in general, and would hopefully have fewer races. Note that since these are stored in the filesystem, you also have to consider races caused by other tools manipulating the files.
Also there are still quite a few test failures on Windows from the previous rounds of tests, it would be prudent to address that first.
I suspect that xattr would be better in general, and would hopefully have fewer races.
Thanks for the prompt review; it's much appreciated. I'll rework this to use xattr instead. I think I misinterpreted your initial direction as looking for something with more widely guaranteed support.
I hadn't checked before, but it's worth mentioning that one limitation of xattr, which could be potentially problematic, is the allowed size of xattr. From `man 7 xattr` on Linux:
In the current ext2, ext3, and ext4 filesystem implementations, the total bytes used by the names and values of all of a file's extended attributes must fit in a single filesystem block (1024, 2048 or 4096 bytes, depending on the block size specified when the filesystem was created).
That's the most restrictive limit given, but that's also by far the most common file system. By contrast, Windows places the limit for reparse data at 16 kB [1]. The Unix limit would also include xattr used for other purposes: DOS attributes (fortunately very small) and ACLs (which we don't implement yet but will need to later). Fortunately we don't have to worry about EAs since Microsoft forbids having EAs on a reparse point [not that a use case has been found for EAs yet.]
In practice this is probably going to be fine, but it's something to be aware of.
[1] https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-points
Note that since these are stored in the filesystem, you also have to consider races caused by other tools manipulating the files.
I regrettably don't understand what this actually implies. File system races are an odd concept because I can't imagine any case that would *not* be user error. Fortunately I suppose it doesn't matter because xattr should be just as atomic as reparse points are...
Also there are still quite a few test failures on Windows from the previous rounds of tests, it would be prudent to address that first.
Sorry, I didn't notice these. They don't show up in the CI and the testbot no longer warns about them anywhere visible :-/ I'll fix them first.
I regrettably don't understand what this actually implies. File system races are an odd concept because I can't imagine any case that would not be user error. Fortunately I suppose it doesn't matter because xattr should be just as atomic as reparse points are..
Yes, that's what I mean. Unix tools that manipulate files generally use (or can be made to use) atomic operations WRT individual files, using a temp file + rename etc. It would be more difficult to safely manipulate two files at once using standard tools.
Sorry, I didn't notice these. They don't show up in the CI and the testbot no longer warns about them anywhere visible :-/ I'll fix them first.
Thanks. They should show up in the CI now with 6f7f4074be4c5fa555abeba233be5a56aea49091.