This patch set is based upon [patches from Wine Staging](https://github.com/wine-staging/wine-staging/tree/master/patches/ntdll-DOS_A...) by Erich E. Hoover (@ehoover), and implements support for the `SYSTEM`, `HIDDEN` and `READONLY` DOS file attributes. These can implemented in various ways depending upon the capabilities of the operating system and the file system. However, this initial patch-set focusses on just one method: Samba-formatted Extended File Attributes.
Modern filesystems generally support Extended File Attributes - auxiliary blobs of binary data that can be attached to a file. Samba uses the `user.DOSATTRIB` attribute to store DOS attribute information in the form of a hexadecimal value, and this patch-set implements a compatible mechanism.
Support for additional storage methods to increase operating system and filesystem compatibility is planned for later patch submissions.
This effort is part of a larger project I have been working on to get Msys2 and Cygwin working properly on Wine. The absence of DOS fule attribute support prevents one of the modes that Cygwin and Msys2 can use to emulate symbolic links from working correctly, which causes the Cygwin installer to fail: https://bugs.winehq.org/show_bug.cgi?id=9158
-- v4: ntdll: Implement storing DOS attributes in NtCreateFile. ntdll: Implement retrieving DOS attributes in NtQueryInformationFile ntdll: Implement storing DOS attributes in NtSetInformationFile. ntdll: Implement retrieving DOS attributes in [fd_]get_file_info().
From: "Erich E. Hoover" erich.e.hoover@gmail.com
Co-authored-by: Joel Holdsworth joel@airwebreathe.org.uk Signed-off-by: Joel Holdsworth joel@airwebreathe.org.uk --- configure | 11 +++++++++++ configure.ac | 2 ++ dlls/ntdll/unix/file.c | 38 +++++++++++++++++++++++++++++++++++++- include/config.h.in | 3 +++ 4 files changed, 53 insertions(+), 1 deletion(-)
diff --git a/configure b/configure index 3088184aade..604743e2394 100755 --- a/configure +++ b/configure @@ -9105,6 +9105,17 @@ then : fi
+ for ac_header in sys/xattr.h +do : + ac_fn_c_check_header_compile "$LINENO" "sys/xattr.h" "ac_cv_header_sys_xattr_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_xattr_h" = xyes +then : + printf "%s\n" "#define HAVE_SYS_XATTR_H 1" >>confdefs.h + HAVE_XATTR=1 +fi + +done +
DLLFLAGS=""
diff --git a/configure.ac b/configure.ac index 1e733962b75..be8a8e81684 100644 --- a/configure.ac +++ b/configure.ac @@ -635,6 +635,8 @@ AC_CHECK_HEADERS([libprocstat.h],,, #include <sys/queue.h> #endif])
+AC_CHECK_HEADERS(sys/xattr.h, [HAVE_XATTR=1]) + dnl **** Check for working dll ****
AC_SUBST(DLLFLAGS,"") diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index 7eb8dbe7ad4..4824a120cc0 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -98,6 +98,9 @@ #ifdef HAVE_SYS_STATFS_H #include <sys/statfs.h> #endif +#ifdef HAVE_SYS_XATTR_H +#include <sys/xattr.h> +#endif #include <time.h> #include <unistd.h>
@@ -167,6 +170,9 @@ typedef struct
#define MAX_IGNORED_FILES 4
+#define SAMBA_XATTR_DOS_ATTRIB "user.DOSATTRIB" +#define XATTR_ATTRIBS_MASK (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM) + struct file_identity { dev_t dev; @@ -355,6 +361,18 @@ NTSTATUS errno_to_status( int err ) } }
+ +static int xattr_get( const char *path, const char *name, void *value, size_t size ) +{ +#if defined(HAVE_SYS_XATTR_H) + return getxattr( path, name, value, size ); +#else + errno = ENOSYS; + return -1; +#endif +} + + /* get space from the current directory data buffer, allocating a new one if necessary */ static void *get_dir_data_space( struct dir_data *data, unsigned int size ) { @@ -1451,6 +1469,18 @@ static inline ULONG get_file_attributes( const struct stat *st ) }
+/* decode the xattr-stored DOS attributes */ +static int get_file_xattr( char *hexattr, int attrlen ) +{ + if (attrlen > 2 && hexattr[0] == '0' && hexattr[1] == 'x') + { + hexattr[attrlen] = 0; + return strtol( hexattr+2, NULL, 16 ) & XATTR_ATTRIBS_MASK; + } + return 0; +} + + static BOOL fd_is_mount_point( int fd, const struct stat *st ) { struct stat parent; @@ -1479,7 +1509,8 @@ static int fd_get_file_info( int fd, unsigned int options, struct stat *st, ULON static int get_file_info( const char *path, struct stat *st, ULONG *attr ) { char *parent_path; - int ret; + char hexattr[11]; + int len, ret;
*attr = 0; ret = lstat( path, st ); @@ -1505,6 +1536,11 @@ static int get_file_info( const char *path, struct stat *st, ULONG *attr ) free( parent_path ); } *attr |= get_file_attributes( st ); + + len = xattr_get( path, SAMBA_XATTR_DOS_ATTRIB, hexattr, sizeof(hexattr)-1 ); + if (len != -1) + *attr |= get_file_xattr( hexattr, len ); + return ret; }
diff --git a/include/config.h.in b/include/config.h.in index de1bf6c61eb..733e00bf953 100644 --- a/include/config.h.in +++ b/include/config.h.in @@ -679,6 +679,9 @@ /* Define to 1 if you have the <sys/vnode.h> header file. */ #undef HAVE_SYS_VNODE_H
+/* Define to 1 if you have the <sys/xattr.h> header file. */ +#undef HAVE_SYS_XATTR_H + /* Define to 1 if you have the `tcdrain' function. */ #undef HAVE_TCDRAIN
On 9/27/22 16:20, Erich E. Hoover wrote:
diff --git a/configure.ac b/configure.ac index 1e733962b75..be8a8e81684 100644 --- a/configure.ac +++ b/configure.ac @@ -635,6 +635,8 @@ AC_CHECK_HEADERS([libprocstat.h],,, #include <sys/queue.h> #endif])
+AC_CHECK_HEADERS(sys/xattr.h, [HAVE_XATTR=1])
dnl **** Check for working dll ****
AC_SUBST(DLLFLAGS,"")
You're not using HAVE_XATTR anywhere, so you can get rid of that (and fold this into the other AC_CHECK_HEADERS checks).
From: "Erich E. Hoover" erich.e.hoover@gmail.com
Co-authored-by: Joel Holdsworth joel@airwebreathe.org.uk Signed-off-by: Joel Holdsworth joel@airwebreathe.org.uk --- dlls/ntdll/unix/file.c | 103 +++++++++++++++++++++++++++++++++-------- 1 file changed, 83 insertions(+), 20 deletions(-)
diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index 4824a120cc0..4ca47de1444 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -362,6 +362,28 @@ NTSTATUS errno_to_status( int err ) }
+static int xattr_fremove( int filedes, const char *name ) +{ +#if defined(HAVE_SYS_XATTR_H) + return fremovexattr( filedes, name ); +#else + errno = ENOSYS; + return -1; +#endif +} + + +static int xattr_fset( int filedes, const char *name, void *value, size_t size ) +{ +#if defined(HAVE_SYS_XATTR_H) + return fsetxattr( filedes, name, value, size, 0 ); +#else + errno = ENOSYS; + return -1; +#endif +} + + static int xattr_get( const char *path, const char *name, void *value, size_t size ) { #if defined(HAVE_SYS_XATTR_H) @@ -1505,6 +1527,66 @@ static int fd_get_file_info( int fd, unsigned int options, struct stat *st, ULON }
+static int fd_set_dos_attrib( int fd, ULONG attr ) +{ + /* do not store everything, but keep everything Samba can use */ + attr &= ~FILE_ATTRIBUTE_NORMAL; + if (attr != 0) + { + char hexattr[11]; + int len = sprintf( hexattr, "0x%x", attr ); + int ret; + struct stat st; + + ret = xattr_fset( fd, SAMBA_XATTR_DOS_ATTRIB, hexattr, len ); + if (ret == 0) return 0; + if (errno != EACCES) return ret; + + /* if we don't have access, try temporarily modifying permissions. */ + if (fstat( fd, &st ) != 0 || + st.st_uid != getuid() || + fchmod( fd, st.st_mode | S_IRUSR ) != 0) + { + errno = EACCES; + return -1; + } + + ret = xattr_fset( fd, SAMBA_XATTR_DOS_ATTRIB, hexattr, len ); + if (ret != 0) return ret; + + return fchmod( fd, st.st_mode ); + } + else + return xattr_fremove( fd, SAMBA_XATTR_DOS_ATTRIB ); +} + + +/* set the stat info and file attributes for a file (by file descriptor) */ +NTSTATUS fd_set_file_info( int fd, ULONG attr ) +{ + struct stat st; + + if (fstat( fd, &st ) == -1) return errno_to_status( errno ); + if (attr & FILE_ATTRIBUTE_READONLY) + { + if (S_ISDIR( st.st_mode)) + WARN("FILE_ATTRIBUTE_READONLY ignored for directory.\n"); + else + st.st_mode &= ~0222; /* clear write permission bits */ + } + else + { + /* add write permission only where we already have read permission */ + st.st_mode |= (0600 | ((st.st_mode & 044) >> 1)) & (~start_umask); + } + if (fchmod( fd, st.st_mode ) == -1) return errno_to_status( errno ); + + fd_set_dos_attrib( fd, attr ); + + return STATUS_SUCCESS; +} + + /* get the stat info and file attributes for a file (by name) */ static int get_file_info( const char *path, struct stat *st, ULONG *attr ) { @@ -4399,7 +4481,6 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io, case FileBasicInformation: if (len >= sizeof(FILE_BASIC_INFORMATION)) { - struct stat st; const FILE_BASIC_INFORMATION *info = ptr; LARGE_INTEGER mtime, atime;
@@ -4413,25 +4494,7 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io, status = set_file_times( fd, &mtime, &atime );
if (status == STATUS_SUCCESS && info->FileAttributes) - { - if (fstat( fd, &st ) == -1) status = errno_to_status( errno ); - else - { - if (info->FileAttributes & FILE_ATTRIBUTE_READONLY) - { - if (S_ISDIR( st.st_mode)) - WARN("FILE_ATTRIBUTE_READONLY ignored for directory.\n"); - else - st.st_mode &= ~0222; /* clear write permission bits */ - } - else - { - /* add write permission only where we already have read permission */ - st.st_mode |= (0600 | ((st.st_mode & 044) >> 1)) & (~start_umask); - } - if (fchmod( fd, st.st_mode ) == -1) status = errno_to_status( errno ); - } - } + status = fd_set_file_info( fd, info->FileAttributes );
if (needs_close) close( fd ); }
From: Joel Holdsworth joel@airwebreathe.org.uk
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=9158 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=15679 Co-authored-by: Erich E. Hoover erich.e.hoover@gmail.com Signed-off-by: Joel Holdsworth joel@airwebreathe.org.uk --- dlls/ntdll/tests/file.c | 8 ++++---- dlls/ntdll/unix/file.c | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 5 deletions(-)
diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index dd0061b13d8..d49d4d5c26f 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -1392,7 +1392,7 @@ static void test_file_basic_information(void) memset(&fbi, 0, sizeof(fbi)); res = pNtQueryInformationFile(h, &io, &fbi, sizeof fbi, FileBasicInformation); ok ( res == STATUS_SUCCESS, "can't get attributes\n"); - todo_wine ok ( (fbi.FileAttributes & attrib_mask) == FILE_ATTRIBUTE_SYSTEM, "attribute %lx not FILE_ATTRIBUTE_SYSTEM\n", fbi.FileAttributes ); + ok ( (fbi.FileAttributes & attrib_mask) == FILE_ATTRIBUTE_SYSTEM, "attribute %lx not FILE_ATTRIBUTE_SYSTEM\n", fbi.FileAttributes );
/* Then HIDDEN */ memset(&fbi, 0, sizeof(fbi)); @@ -1405,7 +1405,7 @@ static void test_file_basic_information(void) memset(&fbi, 0, sizeof(fbi)); res = pNtQueryInformationFile(h, &io, &fbi, sizeof fbi, FileBasicInformation); ok ( res == STATUS_SUCCESS, "can't get attributes\n"); - todo_wine ok ( (fbi.FileAttributes & attrib_mask) == FILE_ATTRIBUTE_HIDDEN, "attribute %lx not FILE_ATTRIBUTE_HIDDEN\n", fbi.FileAttributes ); + ok ( (fbi.FileAttributes & attrib_mask) == FILE_ATTRIBUTE_HIDDEN, "attribute %lx not FILE_ATTRIBUTE_HIDDEN\n", fbi.FileAttributes );
/* Check NORMAL last of all (to make sure we can clear attributes) */ memset(&fbi, 0, sizeof(fbi)); @@ -1462,7 +1462,7 @@ static void test_file_all_information(void) memset(&fai_buf.fai, 0, sizeof(fai_buf.fai)); res = pNtQueryInformationFile(h, &io, &fai_buf.fai, sizeof fai_buf, FileAllInformation); ok ( res == STATUS_SUCCESS, "can't get attributes, res %x\n", res); - todo_wine ok ( (fai_buf.fai.BasicInformation.FileAttributes & attrib_mask) == FILE_ATTRIBUTE_SYSTEM, "attribute %lx not FILE_ATTRIBUTE_SYSTEM\n", fai_buf.fai.BasicInformation.FileAttributes ); + ok ( (fai_buf.fai.BasicInformation.FileAttributes & attrib_mask) == FILE_ATTRIBUTE_SYSTEM, "attribute %lx not FILE_ATTRIBUTE_SYSTEM\n", fai_buf.fai.BasicInformation.FileAttributes );
/* Then HIDDEN */ memset(&fai_buf.fai.BasicInformation, 0, sizeof(fai_buf.fai.BasicInformation)); @@ -1475,7 +1475,7 @@ static void test_file_all_information(void) memset(&fai_buf.fai, 0, sizeof(fai_buf.fai)); res = pNtQueryInformationFile(h, &io, &fai_buf.fai, sizeof fai_buf, FileAllInformation); ok ( res == STATUS_SUCCESS, "can't get attributes\n"); - todo_wine ok ( (fai_buf.fai.BasicInformation.FileAttributes & attrib_mask) == FILE_ATTRIBUTE_HIDDEN, "attribute %lx not FILE_ATTRIBUTE_HIDDEN\n", fai_buf.fai.BasicInformation.FileAttributes ); + ok ( (fai_buf.fai.BasicInformation.FileAttributes & attrib_mask) == FILE_ATTRIBUTE_HIDDEN, "attribute %lx not FILE_ATTRIBUTE_HIDDEN\n", fai_buf.fai.BasicInformation.FileAttributes );
/* Check NORMAL last of all (to make sure we can clear attributes) */ memset(&fai_buf.fai.BasicInformation, 0, sizeof(fai_buf.fai.BasicInformation)); diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index 4ca47de1444..d7dcdd9dca2 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -395,6 +395,17 @@ static int xattr_get( const char *path, const char *name, void *value, size_t si }
+static int xattr_fget( int filedes, const char *name, void *value, size_t size ) +{ +#if defined(HAVE_SYS_XATTR_H) + return fgetxattr( filedes, name, value, size ); +#else + errno = ENOSYS; + return -1; +#endif +} + + /* get space from the current directory data buffer, allocating a new one if necessary */ static void *get_dir_data_space( struct dir_data *data, unsigned int size ) { @@ -1514,7 +1525,8 @@ static BOOL fd_is_mount_point( int fd, const struct stat *st ) /* get the stat info and file attributes for a file (by file descriptor) */ static int fd_get_file_info( int fd, unsigned int options, struct stat *st, ULONG *attr ) { - int ret; + char hexattr[11]; + int len, ret;
*attr = 0; ret = fstat( fd, st ); @@ -1523,6 +1535,11 @@ static int fd_get_file_info( int fd, unsigned int options, struct stat *st, ULON /* consider mount points to be reparse points (IO_REPARSE_TAG_MOUNT_POINT) */ if ((options & FILE_OPEN_REPARSE_POINT) && fd_is_mount_point( fd, st )) *attr |= FILE_ATTRIBUTE_REPARSE_POINT; + + len = xattr_fget( fd, SAMBA_XATTR_DOS_ATTRIB, hexattr, sizeof(hexattr)-1 ); + if (len != -1) + *attr |= get_file_xattr( hexattr, len ); + return ret; }
From: "Erich E. Hoover" erich.e.hoover@gmail.com
Co-authored-by: Joel Holdsworth joel@airwebreathe.org.uk Signed-off-by: Joel Holdsworth joel@airwebreathe.org.uk --- dlls/ntdll/tests/directory.c | 25 ++++++++++++------------- dlls/ntdll/unix/file.c | 11 +++++++++++ 2 files changed, 23 insertions(+), 13 deletions(-)
diff --git a/dlls/ntdll/tests/directory.c b/dlls/ntdll/tests/directory.c index 2a5fedb4659..a5ea7900f8d 100644 --- a/dlls/ntdll/tests/directory.c +++ b/dlls/ntdll/tests/directory.c @@ -55,7 +55,6 @@ static NTSTATUS (WINAPI *pRtlWow64EnableFsRedirectionEx)( ULONG disable, ULONG *
/* The attribute sets to test */ static struct testfile_s { - BOOL todo; /* set if it doesn't work on wine yet */ BOOL attr_done; /* set if attributes were tested for this file already */ const DWORD attr; /* desired attribute */ WCHAR name[20]; /* filename to use */ @@ -63,16 +62,16 @@ static struct testfile_s { const char *description; /* for error messages */ int nfound; /* How many were found (expect 1) */ } testfiles[] = { - { 0, 0, FILE_ATTRIBUTE_NORMAL, {'l','o','n','g','f','i','l','e','n','a','m','e','.','t','m','p'}, "normal" }, - { 0, 0, FILE_ATTRIBUTE_NORMAL, {'n','.','t','m','p',}, "normal" }, - { 1, 0, FILE_ATTRIBUTE_HIDDEN, {'h','.','t','m','p',}, "hidden" }, - { 1, 0, FILE_ATTRIBUTE_SYSTEM, {'s','.','t','m','p',}, "system" }, - { 0, 0, FILE_ATTRIBUTE_DIRECTORY, {'d','.','t','m','p',}, "directory" }, - { 0, 0, FILE_ATTRIBUTE_NORMAL, {0xe9,'a','.','t','m','p'}, "normal" }, - { 0, 0, FILE_ATTRIBUTE_NORMAL, {0xc9,'b','.','t','m','p'}, "normal" }, - { 0, 0, FILE_ATTRIBUTE_NORMAL, {'e','a','.','t','m','p'}, "normal" }, - { 0, 0, FILE_ATTRIBUTE_DIRECTORY, {'.'}, ". directory" }, - { 0, 0, FILE_ATTRIBUTE_DIRECTORY, {'.','.'}, ".. directory" } + { 0, FILE_ATTRIBUTE_NORMAL, {'l','o','n','g','f','i','l','e','n','a','m','e','.','t','m','p'}, "normal" }, + { 0, FILE_ATTRIBUTE_NORMAL, {'n','.','t','m','p',}, "normal" }, + { 0, FILE_ATTRIBUTE_HIDDEN, {'h','.','t','m','p',}, "hidden" }, + { 0, FILE_ATTRIBUTE_SYSTEM, {'s','.','t','m','p',}, "system" }, + { 0, FILE_ATTRIBUTE_DIRECTORY, {'d','.','t','m','p',}, "directory" }, + { 0, FILE_ATTRIBUTE_NORMAL, {0xe9,'a','.','t','m','p'}, "normal" }, + { 0, FILE_ATTRIBUTE_NORMAL, {0xc9,'b','.','t','m','p'}, "normal" }, + { 0, FILE_ATTRIBUTE_NORMAL, {'e','a','.','t','m','p'}, "normal" }, + { 0, FILE_ATTRIBUTE_DIRECTORY, {'.'}, ". directory" }, + { 0, FILE_ATTRIBUTE_DIRECTORY, {'.','.'}, ".. directory" } }; static const int test_dir_count = ARRAY_SIZE(testfiles); static const int max_test_dir_size = ARRAY_SIZE(testfiles) + 5; /* size of above plus some for .. etc */ @@ -162,8 +161,8 @@ static void tally_test_file(FILE_BOTH_DIRECTORY_INFORMATION *dir_info) if (namelen != len || memcmp(nameW, testfiles[i].name, len*sizeof(WCHAR))) continue; if (!testfiles[i].attr_done) { - todo_wine_if (testfiles[i].todo) - ok (attrib == (testfiles[i].attr & attribmask), "file %s: expected %s (%lx), got %lx (is your linux new enough?)\n", wine_dbgstr_w(testfiles[i].name), testfiles[i].description, testfiles[i].attr, attrib); + ok (attrib == (testfiles[i].attr & attribmask), "file %s: expected %s (%lx), got %lx\n", + wine_dbgstr_w(testfiles[i].name), testfiles[i].description, testfiles[i].attr, attrib); testfiles[i].attr_done = TRUE; } testfiles[i].nfound++; diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index d7dcdd9dca2..b128e88d93c 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -3985,6 +3985,17 @@ NTSTATUS WINAPI NtCreateFile( HANDLE *handle, ACCESS_MASK access, OBJECT_ATTRIBU io->Information = FILE_OVERWRITTEN; break; } + + if (io->Information == FILE_CREATED) + { + int fd, needs_close; + + /* set any DOS extended attributes */ + if (!(status = server_get_unix_fd( *handle, 0, &fd, &needs_close, NULL, NULL ))) { + fd_set_dos_attrib( fd, attributes ); + if (needs_close) close( fd ); + } + } } else if (status == STATUS_TOO_MANY_OPENED_FILES) {
On Tue Sep 27 21:19:15 2022 +0000, Erich Hoover wrote:
Wait, why do these patches require libattr? As far as I can tell we're not actually using it. We could just as easily include sys/xattr.h instead.
That's probably the only way I could get the header 8-ish years ago, but it could be I just included the wrong one at the time.
(I also can't tell what libattr does, since there's almost no documentation attached to the project.)
It's just a compatibility library for using extended attributes on various systems.
You beat me to it! I just switched it over to using `<sys/xattr.h>` instead of `<attr/xattr.h>`