Fixes bug [53826](https://bugs.winehq.org/show_bug.cgi?id=53826).
-- v26: ntdll: Set xattr in NtCreateFile if inferred and requested attributes don't match. ntdll: Only infer hidden attribute from file name if xattr is not present. ntdll: Handle hidden file names inside get_file_info instead of after it. ntdll/tests: Add test for file attributes of files with names beginning with a dot.
From: Torge Matthies tmatthies@codeweavers.com
--- dlls/ntdll/tests/file.c | 92 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+)
diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index 6186afdfb63..beaa4734931 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -4051,6 +4051,97 @@ static void test_file_attribute_tag_information(void) CloseHandle( h ); }
+static void rename_file( HANDLE h, const WCHAR *filename ) +{ + FILE_RENAME_INFORMATION *fri; + UNICODE_STRING ntpath; + IO_STATUS_BLOCK io; + NTSTATUS status; + BOOLEAN ret; + ULONG size; + + ret = pRtlDosPathNameToNtPathName_U( filename, &ntpath, NULL, NULL ); + ok( ret, "RtlDosPathNameToNtPathName_U failed\n" ); + + size = offsetof( FILE_RENAME_INFORMATION, FileName ) + ntpath.Length; + fri = HeapAlloc( GetProcessHeap(), 0, size ); + ok( fri != NULL, "HeapAlloc failed\n" ); + fri->ReplaceIfExists = TRUE; + fri->RootDirectory = NULL; + fri->FileNameLength = ntpath.Length; + memcpy( fri->FileName, ntpath.Buffer, ntpath.Length ); + pRtlFreeUnicodeString( &ntpath ); + + status = pNtSetInformationFile( h, &io, fri, size, FileRenameInformation ); + HeapFree( GetProcessHeap(), 0, fri ); + ok( status == STATUS_SUCCESS, "got %#lx\n", status ); +} + +static void test_dotfile_file_attributes(void) +{ + char temppath[MAX_PATH], filename[MAX_PATH]; + WCHAR temppathW[MAX_PATH], filenameW[MAX_PATH]; + FILE_BASIC_INFORMATION info = {}; + IO_STATUS_BLOCK io; + NTSTATUS status; + DWORD attrs; + HANDLE h; + + GetTempPathA( MAX_PATH, temppath ); + GetTempFileNameA( temppath, ".foo", 0, filename ); + h = CreateFileA( filename, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, 0 ); + ok( h != INVALID_HANDLE_VALUE, "failed to create temp file\n" ); + + status = nt_get_file_attrs(filename, &attrs); + ok( status == STATUS_SUCCESS, "got %#lx\n", status ); + todo_wine ok( !(attrs & FILE_ATTRIBUTE_HIDDEN), "got attributes %#lx\n", attrs ); + + status = pNtQueryInformationFile( h, &io, &info, sizeof(info), FileBasicInformation ); + ok( status == STATUS_SUCCESS, "got %#lx\n", status ); + ok( !(info.FileAttributes & FILE_ATTRIBUTE_HIDDEN), "got attributes %#lx\n", info.FileAttributes ); + + info.FileAttributes = FILE_ATTRIBUTE_SYSTEM; + status = pNtSetInformationFile( h, &io, &info, sizeof(info), FileBasicInformation ); + ok( status == STATUS_SUCCESS, "got %#lx\n", status ); + + status = nt_get_file_attrs(filename, &attrs); + ok( status == STATUS_SUCCESS, "got %#lx\n", status ); + ok( attrs & FILE_ATTRIBUTE_SYSTEM, "got attributes %#lx\n", attrs ); + todo_wine ok( !(attrs & FILE_ATTRIBUTE_HIDDEN), "got attributes %#lx\n", attrs ); + + status = pNtQueryInformationFile( h, &io, &info, sizeof(info), FileBasicInformation ); + ok( status == STATUS_SUCCESS, "got %#lx\n", status ); + ok( info.FileAttributes & FILE_ATTRIBUTE_SYSTEM, "got attributes %#lx\n", info.FileAttributes ); + ok( !(info.FileAttributes & FILE_ATTRIBUTE_HIDDEN), "got attributes %#lx\n", info.FileAttributes ); + + CloseHandle( h ); + + GetTempPathW( MAX_PATH, temppathW ); + GetTempFileNameW( temppathW, L"foo", 0, filenameW ); + h = CreateFileW( filenameW, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, 0 ); + ok( h != INVALID_HANDLE_VALUE, "failed to create temp file\n" ); + + GetTempFileNameW( temppathW, L".foo", 0, filenameW ); + winetest_push_context("foo -> .foo"); + rename_file( h, filenameW ); + winetest_pop_context(); + + status = pNtQueryInformationFile( h, &io, &info, sizeof(info), FileBasicInformation ); + ok( status == STATUS_SUCCESS, "got %#lx\n", status ); + ok( !(info.FileAttributes & FILE_ATTRIBUTE_HIDDEN), "got attributes %#lx\n", info.FileAttributes ); + + GetTempFileNameW( temppathW, L"foo", 0, filenameW ); + winetest_push_context(".foo -> foo"); + rename_file( h, filenameW ); + winetest_pop_context(); + + status = pNtQueryInformationFile( h, &io, &info, sizeof(info), FileBasicInformation ); + ok( status == STATUS_SUCCESS, "got %#lx\n", status ); + ok( !(info.FileAttributes & FILE_ATTRIBUTE_HIDDEN), "got attributes %#lx\n", info.FileAttributes ); + + CloseHandle( h ); +} + static void test_file_mode(void) { UNICODE_STRING file_name, pipe_dev_name, mountmgr_dev_name, mailslot_dev_name; @@ -5499,6 +5590,7 @@ START_TEST(file) test_file_id_information(); test_file_access_information(); test_file_attribute_tag_information(); + test_dotfile_file_attributes(); test_file_mode(); test_file_readonly_access(); test_query_volume_information_file();
From: Torge Matthies tmatthies@codeweavers.com
Signed-off-by: Torge Matthies tmatthies@codeweavers.com --- dlls/ntdll/unix/file.c | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-)
diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index eca75b2d4fb..811132c1bde 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -1292,18 +1292,30 @@ static BOOLEAN get_dir_case_sensitivity( const char *dir ) /*********************************************************************** * is_hidden_file * - * Check if the specified file should be hidden based on its name and the show dot files option. + * Check if the specified file should be hidden based on its unix path and the show dot files option. */ -static BOOL is_hidden_file( const UNICODE_STRING *name ) +static BOOL is_hidden_file( const char *name ) { - WCHAR *p, *end; + const char *p, *end;
if (show_dot_files) return FALSE;
- end = p = name->Buffer + name->Length/sizeof(WCHAR); - while (p > name->Buffer && p[-1] == '\') p--; - while (p > name->Buffer && p[-1] != '\') p--; - return (p < end && *p == '.'); + end = p = name + strlen( name ); + while (p > name && p[-1] == '/') p--; + while (p > name && p[-1] != '/') p--; + if (p >= end || *p != '.') + return FALSE; + /* . and .. must not be hidden */ + p++; + if (p >= end || *p == '/') + return FALSE; + else if (*p == '.') + { + p++; + if (p >= end || *p == '/') + return FALSE; + } + return TRUE; }
@@ -1677,6 +1689,9 @@ static int get_file_info( const char *path, struct stat *st, ULONG *attr ) } *attr |= get_file_attributes( st );
+ if (is_hidden_file( path )) + *attr |= FILE_ATTRIBUTE_HIDDEN; + attr_len = xattr_get( path, SAMBA_XATTR_DOS_ATTRIB, attr_data, sizeof(attr_data)-1 ); if (attr_len != -1) *attr |= parse_samba_dos_attrib_data( attr_data, attr_len ); @@ -2252,11 +2267,6 @@ static NTSTATUS get_dir_data_entry( struct dir_data *dir_data, void *info_ptr, I if (class != FileNamesInformation) { if (st.st_dev != dir_data->id.dev) st.st_ino = 0; /* ignore inode if on a different device */ - - if (!show_dot_files && names->long_name[0] == '.' && names->long_name[1] && - (names->long_name[1] != '.' || names->long_name[2])) - attributes |= FILE_ATTRIBUTE_HIDDEN; - fill_file_info( &st, attributes, info, class ); }
@@ -4218,7 +4228,6 @@ NTSTATUS WINAPI NtQueryFullAttributesFile( const OBJECT_ATTRIBUTES *attr, info->AllocationSize = std.AllocationSize; info->EndOfFile = std.EndOfFile; info->FileAttributes = basic.FileAttributes; - if (is_hidden_file( attr->ObjectName )) info->FileAttributes |= FILE_ATTRIBUTE_HIDDEN; } free( unix_name ); } @@ -4249,10 +4258,7 @@ NTSTATUS WINAPI NtQueryAttributesFile( const OBJECT_ATTRIBUTES *attr, FILE_BASIC else if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) status = STATUS_INVALID_INFO_CLASS; else - { status = fill_file_info( &st, attributes, info, FileBasicInformation ); - if (is_hidden_file( attr->ObjectName )) info->FileAttributes |= FILE_ATTRIBUTE_HIDDEN; - } free( unix_name ); } else WARN( "%s not found (%x)\n", debugstr_us(attr->ObjectName), status );
From: Torge Matthies tmatthies@codeweavers.com
Signed-off-by: Torge Matthies tmatthies@codeweavers.com --- dlls/ntdll/tests/file.c | 2 +- dlls/ntdll/unix/file.c | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index beaa4734931..cdd924a7226 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -4107,7 +4107,7 @@ static void test_dotfile_file_attributes(void) status = nt_get_file_attrs(filename, &attrs); ok( status == STATUS_SUCCESS, "got %#lx\n", status ); ok( attrs & FILE_ATTRIBUTE_SYSTEM, "got attributes %#lx\n", attrs ); - todo_wine ok( !(attrs & FILE_ATTRIBUTE_HIDDEN), "got attributes %#lx\n", attrs ); + ok( !(attrs & FILE_ATTRIBUTE_HIDDEN), "got attributes %#lx\n", attrs );
status = pNtQueryInformationFile( h, &io, &info, sizeof(info), FileBasicInformation ); ok( status == STATUS_SUCCESS, "got %#lx\n", status ); diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index 811132c1bde..0490a984295 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -1689,14 +1689,13 @@ static int get_file_info( const char *path, struct stat *st, ULONG *attr ) } *attr |= get_file_attributes( st );
- if (is_hidden_file( path )) - *attr |= FILE_ATTRIBUTE_HIDDEN; - attr_len = xattr_get( path, SAMBA_XATTR_DOS_ATTRIB, attr_data, sizeof(attr_data)-1 ); if (attr_len != -1) *attr |= parse_samba_dos_attrib_data( attr_data, attr_len ); else { + if (is_hidden_file( path )) + *attr |= FILE_ATTRIBUTE_HIDDEN; if (errno == ENOTSUP) return ret; #ifdef ENODATA if (errno == ENODATA) return ret;
From: Torge Matthies tmatthies@codeweavers.com
And make sure it doesn't get deleted.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=53826 Signed-off-by: Torge Matthies tmatthies@codeweavers.com --- dlls/ntdll/tests/file.c | 2 +- dlls/ntdll/unix/file.c | 29 ++++++++++++++++++++++------- 2 files changed, 23 insertions(+), 8 deletions(-)
diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index cdd924a7226..b96b2e5b072 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -4094,7 +4094,7 @@ static void test_dotfile_file_attributes(void)
status = nt_get_file_attrs(filename, &attrs); ok( status == STATUS_SUCCESS, "got %#lx\n", status ); - todo_wine ok( !(attrs & FILE_ATTRIBUTE_HIDDEN), "got attributes %#lx\n", attrs ); + ok( !(attrs & FILE_ATTRIBUTE_HIDDEN), "got attributes %#lx\n", attrs );
status = pNtQueryInformationFile( h, &io, &info, sizeof(info), FileBasicInformation ); ok( status == STATUS_SUCCESS, "got %#lx\n", status ); diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index 0490a984295..4de6dfd0b8f 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -1612,11 +1612,11 @@ static int fd_get_file_info( int fd, unsigned int options, struct stat *st, ULON }
-static int fd_set_dos_attrib( int fd, UINT attr ) +static int fd_set_dos_attrib( int fd, UINT attr, BOOL force_set ) { /* we only store the HIDDEN and SYSTEM attributes */ attr &= XATTR_ATTRIBS_MASK; - if (attr != 0) + if (force_set || attr != 0) { /* encode the attributes in Samba 3 ASCII format. Samba 4 has extended * this format with more features, but retains compatibility with the @@ -1630,7 +1630,7 @@ static int fd_set_dos_attrib( int fd, UINT attr )
/* set the stat info and file attributes for a file (by file descriptor) */ -static NTSTATUS fd_set_file_info( int fd, UINT attr ) +static NTSTATUS fd_set_file_info( int fd, UINT attr, BOOL force_set_xattr ) { struct stat st;
@@ -1649,7 +1649,11 @@ static NTSTATUS fd_set_file_info( int fd, UINT attr ) } if (fchmod( fd, st.st_mode ) == -1) return errno_to_status( errno );
- if (fd_set_dos_attrib( fd, attr ) == -1 && errno != ENOTSUP) + /* if the file has multiple names, we can't be sure that it is safe to not + set the extended attribute, since any of the names could start with a dot */ + force_set_xattr = force_set_xattr || st.st_nlink > 1; + + if (fd_set_dos_attrib( fd, attr, force_set_xattr ) == -1 && errno != ENOTSUP) WARN( "Failed to set extended attribute " SAMBA_XATTR_DOS_ATTRIB ". errno %d (%s)\n", errno, strerror( errno ) );
@@ -3975,6 +3979,7 @@ NTSTATUS WINAPI NtCreateFile( HANDLE *handle, ACCESS_MASK access, OBJECT_ATTRIBU OBJECT_ATTRIBUTES new_attr; UNICODE_STRING nt_name; char *unix_name; + BOOL name_hidden = FALSE; BOOL created = FALSE; unsigned int status;
@@ -4017,6 +4022,7 @@ NTSTATUS WINAPI NtCreateFile( HANDLE *handle, ACCESS_MASK access, OBJECT_ATTRIBU
if (status == STATUS_SUCCESS) { + name_hidden = is_hidden_file( unix_name ); status = open_unix_file( handle, unix_name, access, &new_attr, attributes, sharing, disposition, options, ea_buffer, ea_length ); free( unix_name ); @@ -4044,14 +4050,15 @@ NTSTATUS WINAPI NtCreateFile( HANDLE *handle, ACCESS_MASK access, OBJECT_ATTRIBU break; }
- if (io->Information == FILE_CREATED && (attributes & XATTR_ATTRIBS_MASK)) + if (io->Information == FILE_CREATED && + ((attributes & XATTR_ATTRIBS_MASK) || name_hidden)) { int fd, needs_close;
/* set any DOS extended attributes */ if (!server_get_unix_fd( *handle, 0, &fd, &needs_close, NULL, NULL )) { - if (fd_set_dos_attrib( fd, attributes ) == -1 && errno != ENOTSUP) + if (fd_set_dos_attrib( fd, attributes, TRUE ) == -1 && errno != ENOTSUP) WARN( "Failed to set extended attribute " SAMBA_XATTR_DOS_ATTRIB ". errno %d (%s)", errno, strerror( errno ) ); if (needs_close) close( fd ); @@ -4573,10 +4580,14 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io, { const FILE_BASIC_INFORMATION *info = ptr; LARGE_INTEGER mtime, atime; + char *unix_name;
if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL ))) return io->u.Status = status;
+ if ((status = server_get_unix_name( handle, &unix_name ))) + unix_name = NULL; + mtime.QuadPart = info->LastWriteTime.QuadPart == -1 ? 0 : info->LastWriteTime.QuadPart; atime.QuadPart = info->LastAccessTime.QuadPart == -1 ? 0 : info->LastAccessTime.QuadPart;
@@ -4584,9 +4595,13 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io, status = set_file_times( fd, &mtime, &atime );
if (status == STATUS_SUCCESS && info->FileAttributes) - status = fd_set_file_info( fd, info->FileAttributes ); + { + BOOL force_xattr = unix_name && is_hidden_file( unix_name ); + status = fd_set_file_info( fd, info->FileAttributes, force_xattr ); + }
if (needs_close) close( fd ); + free( unix_name ); } else status = STATUS_INVALID_PARAMETER_3; break;
On Wed Apr 12 11:49:47 2023 +0000, Torge Matthies wrote:
Yup, this isn't necessary anymore and I forgot or accidentally dropped the change of the backslashes to forward slashes. The latest version of this MR now contains just a straightforward conversion of that function from working with nt paths to working with unix paths. The reason this conversion is necessary at all is because I'm moving the calls to this function to the `get_file_info` function, so that more places in the code use the correct file attributes (everything that uses `get_file_info` now automatically returns attributes of hidden files correctly), and so that the extended attribute can take precedence over the file name.
Ok @julliard I had to re-add the check for "." and ".." because get_dir_data_entry now uses this function (indirectly through get_file_info) and tests are not happy when those two special directories are hidden. I cleaned the code up a little though.
btw this is once again ready for review/merging. It's just 4 commits now, of which 1 is tests, and the other commits are pretty small.