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 */