From 3770d244330395ed3325d8832d5eebb43ebfe980 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 21 Jan 2018 13:47:09 -0800 Subject: [PATCH] Capture and apply extended attributes on Windows DISM recently started supporting capturing and applying xattrs on Windows (though, it is broken when applying multiple xattrs per file). Make wimlib support the same, using the same on-disk format. Unlike DISM it is on by default, not controlled by an option, since there doesn't seem to be a good reason to make it an option. Also deprecate the tagged item wimlib was using to store xattrs on Linux and switch over to the format used by WIMGAPI to store xattrs on Windows, so that new WIM images use the same xattr format on both platforms. One caveat is that on Linux XATTR_SIZE_MAX is 65536 whereas in the new WIM tagged item format we can only store up to 65535 bytes. That is unlikely to matter though. As future work, the NTFS-3G capture and apply backends should be updated to support xattrs too. --- doc/man1/wimapply.1 | 4 + doc/man1/wimcapture.1 | 6 +- doc/man1/wimmount.1 | 9 +- include/wimlib/apply.h | 2 +- include/wimlib/tagged_items.h | 8 +- include/wimlib/win32_common.h | 18 ++++ include/wimlib/xattr.h | 170 +++++++++++++++++++++++--------- src/extract.c | 21 ++-- src/test_support.c | 77 +++++++-------- src/unix_apply.c | 63 ++++++++---- src/unix_capture.c | 64 ++++++------ src/win32_apply.c | 128 +++++++++++++++++++++++- src/win32_capture.c | 165 +++++++++++++++++++++++++++---- tests/wims/README | 3 + tests/wims/linux_xattrs_old.wim | Bin 0 -> 1466 bytes tests/wlfuzz.c | 1 + 16 files changed, 548 insertions(+), 191 deletions(-) create mode 100644 tests/wims/linux_xattrs_old.wim diff --git a/doc/man1/wimapply.1 b/doc/man1/wimapply.1 index c59ad256..5c160d0e 100644 --- a/doc/man1/wimapply.1 +++ b/doc/man1/wimapply.1 @@ -88,6 +88,8 @@ Object IDs. .PP However, encrypted files will not be extracted. .PP +Restoring extended attributes (EAs) is also not yet supported in this mode. +.PP Regardless, since almost all information from the WIM image is restored in this mode, it is possible (and fully supported) to restore an image of an actual Windows installation using \fBwimapply\fR on a UNIX-like system as an @@ -141,6 +143,8 @@ Short filenames (also known as 8.3 names or DOS names). Hard links, if supported by the target filesystem. .IP \[bu] Object IDs, if supported by the target filesystem. +.IP \[bu] +Extended attributes (EAs), if supported by the target filesystem. .PP Additional notes about extracting files on Windows: .IP \[bu] 4 diff --git a/doc/man1/wimcapture.1 b/doc/man1/wimcapture.1 index 53bd6cf8..0830ffe2 100644 --- a/doc/man1/wimcapture.1 +++ b/doc/man1/wimcapture.1 @@ -90,6 +90,8 @@ However, the main limitations of this mode are: .IP \[bu] 4 Encrypted files are excluded. .IP \[bu] +Extended attributes (EAs) are not stored yet. +.IP \[bu] Sparse files will be flagged as such, but their full data will still be stored, subject to the usual compression. .IP \[bu] @@ -137,8 +139,8 @@ considered an error condition. Hard links, if supported by the source filesystem. .IP \[bu] Object IDs, if supported by the source filesystem. -.PP -There is no support yet for capturing NTFS extended attributes. +.IP \[bu] +Extended attributes (EAs), if supported by the source filesystem. .SH REPARSE POINTS AND SYMLINKS A "symbolic link" (or "symlink") is a special file which "points to" some other file or directory. On Windows, a "reparse point" is a generalization of a diff --git a/doc/man1/wimmount.1 b/doc/man1/wimmount.1 index b42b3491..9b33445e 100644 --- a/doc/man1/wimmount.1 +++ b/doc/man1/wimmount.1 @@ -57,11 +57,10 @@ Object IDs. New files are not given object IDs. EFS-encrypted files. The files themselves will be visible in mounted WIM images but their data will not be available. .IP \[bu] -Extended attributes. Although wimlib now supports creating a WIM image -containing Linux-style extended attributes, these are not yet exposed in mounted -WIM images. (This may be implemented in the future, though it would conflict -with the use of extended attributes to expose Windows concepts like named data -streams.) +Extended attributes. Although wimlib supports WIM images containing extended +attributes, these are not yet exposed in mounted WIM images. (This may be +implemented in the future, though it would conflict with the use of extended +attributes to expose Windows concepts like named data streams.) .SH SPLIT WIMS You may use \fBwimmount\fR to mount an image from a split WIM read-only. However, you may not mount an image from a split WIM read-write. diff --git a/include/wimlib/apply.h b/include/wimlib/apply.h index cfddd1ed..2592fae6 100644 --- a/include/wimlib/apply.h +++ b/include/wimlib/apply.h @@ -31,7 +31,7 @@ struct wim_features { unsigned long object_ids; unsigned long timestamps; unsigned long case_sensitive_filenames; - unsigned long linux_xattrs; + unsigned long xattrs; }; struct blob_descriptor; diff --git a/include/wimlib/tagged_items.h b/include/wimlib/tagged_items.h index c8ddb341..dfbae30b 100644 --- a/include/wimlib/tagged_items.h +++ b/include/wimlib/tagged_items.h @@ -8,10 +8,16 @@ struct wim_inode; /* Windows-style object ID */ #define TAG_OBJECT_ID 0x00000001 +/* Extended attributes */ +#define TAG_XATTRS 0x00000002 + /* [wimlib extension] Standard UNIX metadata: uid, gid, mode, and rdev */ #define TAG_WIMLIB_UNIX_DATA 0x337DD873 -/* [wimlib extension] Linux-style xattrs */ +/* + * [wimlib extension] Linux-style extended attributes + * (deprecated in favor of TAG_XATTRS) + */ #define TAG_WIMLIB_LINUX_XATTRS 0x337DD874 extern void * diff --git a/include/wimlib/win32_common.h b/include/wimlib/win32_common.h index cbdacd32..70114c8d 100644 --- a/include/wimlib/win32_common.h +++ b/include/wimlib/win32_common.h @@ -101,6 +101,24 @@ NtOpenSymbolicLinkObject(PHANDLE LinkHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes); +NTSTATUS +NTAPI +NtQueryEaFile(IN HANDLE FileHandle, + OUT PIO_STATUS_BLOCK IoStatusBlock, + OUT PVOID Buffer, + IN ULONG Length, + IN BOOLEAN ReturnSingleEntry, + IN PVOID EaList OPTIONAL, + IN ULONG EaListLength, + IN PULONG EaIndex OPTIONAL, + IN BOOLEAN RestartScan); + +NTSTATUS +NTAPI +NtSetEaFile(IN HANDLE FileHandle, + OUT PIO_STATUS_BLOCK IoStatusBlock, + OUT PVOID Buffer, + IN ULONG Length); /* Dynamically loaded ntdll functions */ diff --git a/include/wimlib/xattr.h b/include/wimlib/xattr.h index 2fc5f6dc..2d297245 100644 --- a/include/wimlib/xattr.h +++ b/include/wimlib/xattr.h @@ -7,24 +7,82 @@ #include "wimlib/tagged_items.h" #include "wimlib/util.h" -#undef HAVE_XATTR_SUPPORT +#undef HAVE_LINUX_XATTR_SUPPORT #if defined(HAVE_SYS_XATTR_H) && \ defined(HAVE_LLISTXATTR) && defined(HAVE_LGETXATTR) && \ defined(HAVE_FSETXATTR) && defined(HAVE_LSETXATTR) -# define HAVE_XATTR_SUPPORT 1 +# define HAVE_LINUX_XATTR_SUPPORT 1 #endif +#define WIM_XATTR_NAME_MAX 255 +#define WIM_XATTR_SIZE_MAX 65535 + +/* + * On-disk format of each extended attribute (xattr, or EA) entry in a metadata + * item tagged with TAG_XATTRS. This is the preferred xattr format, since it is + * also used by WIMGAPI and DISM starting in Windows 10 version 1607. + */ +struct wim_xattr_entry { + + /* length of xattr value in bytes */ + le16 value_len; + + /* length of xattr name in bytes, excluding the null terminator */ + u8 name_len; + + /* flags: 0 or 0x80 (FILE_NEED_EA) */ + u8 flags; + + /* followed by the xattr name *with* a null terminator */ + char name[0]; + + /* followed by the xattr value */ + /* u8 value[0]; */ + + /* no padding at end! */ +} _packed_attribute; + +static inline size_t +xattr_entry_size(const struct wim_xattr_entry *entry) +{ + STATIC_ASSERT(sizeof(*entry) == 4); + + return sizeof(*entry) + entry->name_len + 1 + + le16_to_cpu(entry->value_len); +} + +/* minimum is a 1-byte name (plus null terminator) and an empty value */ +#define XATTR_ENTRY_MIN_SIZE (sizeof(struct wim_xattr_entry) + 2) + +static inline struct wim_xattr_entry * +xattr_entry_next(const struct wim_xattr_entry *entry) +{ + return (void *)entry + xattr_entry_size(entry); +} + +static inline bool +valid_xattr_entry(const struct wim_xattr_entry *entry, size_t avail) +{ + if (avail < sizeof(*entry)) + return false; + + return entry->name_len > 0 && entry->name_len <= WIM_XATTR_NAME_MAX && + le16_to_cpu(entry->value_len) <= WIM_XATTR_SIZE_MAX && + avail >= xattr_entry_size(entry) && + memchr(entry->name, '\0', entry->name_len) == NULL && + entry->name[entry->name_len] == '\0'; +} + /* - * On-disk format of an entry in an extended attributes tagged item (wimlib - * extension). An xattr item consists of a series of variable-length xattr - * name/value pairs, each of which begins with this header. - * - * Currently this is only used for Linux-style xattrs, but in the future we may - * use this for Windows-style xattrs too. + * On-disk format of each extended attribute entry in a metadata item tagged + * with TAG_WIMLIB_LINUX_XATTRS. This is a deprecated format which wimlib + * v1.11-v1.12 used to store extended attributes on Linux (predating the Windows + * xattr support in both WIMGAPI and wimlib). Now we use TAG_XATTRS for both + * Windows and Linux xattrs. */ -struct wimlib_xattr_entry { +struct wimlib_xattr_entry_old { - /* length of xattr name in bytes */ + /* length of xattr name in bytes, excluding a null terminator */ le16 name_len; /* reserved, must be 0 */ @@ -33,17 +91,17 @@ struct wimlib_xattr_entry { /* length of xattr value in bytes */ le32 value_len; - /* followed by the xattr name with no terminating null */ + /* followed by the xattr name *without* a null terminator */ char name[0]; - /* followed by the xattr value with no terminating null */ + /* followed by the xattr value */ /* u8 value[0]; */ /* then zero-padded to a 4-byte boundary */ } _aligned_attribute(4); static inline size_t -xattr_entry_size(const struct wimlib_xattr_entry *entry) +old_xattr_entry_size(const struct wimlib_xattr_entry_old *entry) { STATIC_ASSERT(sizeof(*entry) == 8); @@ -51,44 +109,34 @@ xattr_entry_size(const struct wimlib_xattr_entry *entry) le32_to_cpu(entry->value_len), 4); } -static inline struct wimlib_xattr_entry * -xattr_entry_next(const struct wimlib_xattr_entry *entry) +/* minimum is a 1-byte name and an empty value */ +#define OLD_XATTR_ENTRY_MIN_SIZE \ + (ALIGN(sizeof(struct wimlib_xattr_entry_old) + 1, 4)) + +static inline struct wimlib_xattr_entry_old * +old_xattr_entry_next(const struct wimlib_xattr_entry_old *entry) { - return (void *)entry + xattr_entry_size(entry); + return (void *)entry + old_xattr_entry_size(entry); } -/* Currently we use the Linux limits when validating xattr names and values */ -#define XATTR_NAME_MAX 255 -#define XATTR_SIZE_MAX 65536 - static inline bool -valid_xattr_entry(const struct wimlib_xattr_entry *entry, size_t avail) +old_valid_xattr_entry(const struct wimlib_xattr_entry_old *entry, size_t avail) { - if (avail < sizeof(*entry)) - return false; - - if (entry->name_len == 0 || - le16_to_cpu(entry->name_len) > XATTR_NAME_MAX) - return false; - - if (entry->reserved != 0) - return false; + u16 name_len; - if (le32_to_cpu(entry->value_len) > XATTR_SIZE_MAX) - return false; - - if (avail < xattr_entry_size(entry)) - return false; - - if (memchr(entry->name, '\0', le16_to_cpu(entry->name_len))) + if (avail < sizeof(*entry)) return false; - return true; + name_len = le16_to_cpu(entry->name_len); + return name_len > 0 && name_len <= WIM_XATTR_NAME_MAX && + le32_to_cpu(entry->value_len) <= WIM_XATTR_SIZE_MAX && + avail >= old_xattr_entry_size(entry) && + memchr(entry->name, '\0', name_len) == NULL; } -/* Is the xattr of the specified name security-related? */ +/* Is the xattr of the specified name security-related on Linux? */ static inline bool -is_security_xattr(const char *name) +is_linux_security_xattr(const char *name) { #define XATTR_SECURITY_PREFIX "security." #define XATTR_SYSTEM_PREFIX "system." @@ -104,23 +152,49 @@ is_security_xattr(const char *name) } static inline const void * -inode_get_linux_xattrs(const struct wim_inode *inode, u32 *len_ret) +inode_get_xattrs(const struct wim_inode *inode, u32 *len_ret) +{ + return inode_get_tagged_item(inode, TAG_XATTRS, + XATTR_ENTRY_MIN_SIZE, len_ret); +} + +static inline const void * +inode_get_xattrs_old(const struct wim_inode *inode, u32 *len_ret) +{ + return inode_get_tagged_item(inode, TAG_WIMLIB_LINUX_XATTRS, + OLD_XATTR_ENTRY_MIN_SIZE, len_ret); +} + +static inline const void * +inode_get_linux_xattrs(const struct wim_inode *inode, u32 *len_ret, + bool *is_old_format_ret) { - return inode_get_tagged_item(inode, TAG_WIMLIB_LINUX_XATTRS, 0, - len_ret); + const void *entries; + + entries = inode_get_xattrs(inode, len_ret); + if (entries) { + *is_old_format_ret = false; + return entries; + } + entries = inode_get_xattrs_old(inode, len_ret); + if (entries) { + *is_old_format_ret = true; + return entries; + } + return NULL; } static inline bool -inode_has_linux_xattrs(const struct wim_inode *inode) +inode_has_xattrs(const struct wim_inode *inode) { - return inode_get_linux_xattrs(inode, NULL) != NULL; + return inode_get_xattrs(inode, NULL) != NULL || + inode_get_xattrs_old(inode, NULL) != NULL; } static inline bool -inode_set_linux_xattrs(struct wim_inode *inode, const void *entries, u32 len) +inode_set_xattrs(struct wim_inode *inode, const void *entries, u32 len) { - return inode_set_tagged_item(inode, TAG_WIMLIB_LINUX_XATTRS, - entries, len); + return inode_set_tagged_item(inode, TAG_XATTRS, entries, len); } #endif /* _WIMLIB_XATTR_H */ diff --git a/src/extract.c b/src/extract.c index f59cffb5..cd1dd2c1 100644 --- a/src/extract.c +++ b/src/extract.c @@ -6,7 +6,7 @@ */ /* - * Copyright (C) 2012-2017 Eric Biggers + * Copyright (C) 2012-2018 Eric Biggers * * This file is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -1225,8 +1225,8 @@ inode_tally_features(const struct wim_inode *inode, features->unix_data++; if (inode_has_object_id(inode)) features->object_ids++; - if (inode_has_linux_xattrs(inode)) - features->linux_xattrs++; + if (inode_has_xattrs(inode)) + features->xattrs++; } /* Tally features necessary to extract a dentry and the corresponding inode. */ @@ -1380,14 +1380,15 @@ do_feature_check(const struct wim_features *required_features, T("\n (use --unix-data mode to extract these)") : T(""))); } - /* Linux-style extended attributes */ - if (required_features->linux_xattrs && - (!supported_features->linux_xattrs || - !(extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA))) + /* Extended attributes */ + if (required_features->xattrs && + (!supported_features->xattrs || + (supported_features->unix_data && + !(extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA)))) { - WARNING("Ignoring Linux-style extended attributes of %lu files%"TS, - required_features->linux_xattrs, - (supported_features->linux_xattrs ? + WARNING("Ignoring extended attributes of %lu files%"TS, + required_features->xattrs, + (supported_features->xattrs ? T("\n (use --unix-data mode to extract these)") : T(""))); } diff --git a/src/test_support.c b/src/test_support.c index 4df34d4c..d2fecc82 100644 --- a/src/test_support.c +++ b/src/test_support.c @@ -3,7 +3,7 @@ */ /* - * Copyright (C) 2015-2017 Eric Biggers + * Copyright (C) 2015-2018 Eric Biggers * * This file is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -471,8 +471,8 @@ static noinline_for_stack int set_random_xattrs(struct wim_inode *inode) { int num_xattrs = 1 + rand32() % 16; - char entries[8192] _aligned_attribute(4); - struct wimlib_xattr_entry *entry = (void *)entries; + char entries[8192]; + struct wim_xattr_entry *entry = (void *)entries; size_t entries_size; struct wimlib_unix_data unix_data; const char *prefix = "user."; @@ -497,19 +497,19 @@ set_random_xattrs(struct wim_inode *inode) int value_len = rand32() % 64; u8 *p; - entry->reserved = 0; - entry->value_len = cpu_to_le32(value_len); + entry->value_len = cpu_to_le16(value_len); + entry->flags = 0; if (rand32() % 16 == 0 && am_root() && !generated_capability_xattr) { int name_len = sizeof(capability_name) - 1; - entry->name_len = cpu_to_le16(name_len); - p = mempcpy(entry->name, capability_name, name_len); + entry->name_len = name_len; + p = mempcpy(entry->name, capability_name, name_len + 1); generated_capability_xattr = true; } else { int name_len = 1 + rand32() % 64; - entry->name_len = cpu_to_le16(strlen(prefix) + name_len); + entry->name_len = strlen(prefix) + name_len; p = mempcpy(entry->name, prefix, strlen(prefix)); *p++ = 'a' + i; for (int j = 1; j < name_len; j++) { @@ -518,21 +518,18 @@ set_random_xattrs(struct wim_inode *inode) } while (*p == '\0'); p++; } + *p++ = '\0'; } for (int j = 0; j < value_len; j++) *p++ = rand8(); - while ((uintptr_t)p % 4) - *p++ = 0; - entry = (void *)p; } entries_size = (char *)entry - entries; - wimlib_assert(entries_size > 0 && entries_size % 4 == 0 && - entries_size <= sizeof(entries)); + wimlib_assert(entries_size > 0 && entries_size <= sizeof(entries)); - if (!inode_set_linux_xattrs(inode, entries, entries_size)) + if (!inode_set_xattrs(inode, entries, entries_size)) return WIMLIB_ERR_NOMEM; return 0; @@ -1393,28 +1390,26 @@ cmp_unix_metadata(const struct wim_inode *inode1, static int cmp_xattr_names(const void *p1, const void *p2) { - const struct wimlib_xattr_entry *entry1 = *(const struct wimlib_xattr_entry **)p1; - const struct wimlib_xattr_entry *entry2 = *(const struct wimlib_xattr_entry **)p2; - u16 name_len1 = le16_to_cpu(entry1->name_len); - u16 name_len2 = le16_to_cpu(entry2->name_len); + const struct wim_xattr_entry *entry1 = *(const struct wim_xattr_entry **)p1; + const struct wim_xattr_entry *entry2 = *(const struct wim_xattr_entry **)p2; int res; - res = cmp_u32(name_len1, name_len2); + res = entry1->name_len - entry2->name_len; if (res) return res; - return memcmp(entry1->name, entry2->name, name_len1); + return memcmp(entry1->name, entry2->name, entry1->name_len); } /* Validate and sort by name a list of extended attributes */ static int parse_xattrs(const void *xattrs, u32 len, - const struct wimlib_xattr_entry *entries[], + const struct wim_xattr_entry *entries[], u32 *num_entries_p) { u32 limit = *num_entries_p; u32 num_entries = 0; - const struct wimlib_xattr_entry *entry = xattrs; + const struct wim_xattr_entry *entry = xattrs; while ((void *)entry < xattrs + len) { if (!valid_xattr_entry(entry, xattrs + len - (void *)entry)) { @@ -1448,14 +1443,14 @@ parse_xattrs(const void *xattrs, u32 len, } static int -cmp_linux_xattrs(const struct wim_inode *inode1, - const struct wim_inode *inode2, int cmp_flags) +cmp_xattrs(const struct wim_inode *inode1, const struct wim_inode *inode2, + int cmp_flags) { const void *xattrs1, *xattrs2; u32 len1, len2; - xattrs1 = inode_get_linux_xattrs(inode1, &len1); - xattrs2 = inode_get_linux_xattrs(inode2, &len2); + xattrs1 = inode_get_xattrs(inode1, &len1); + xattrs2 = inode_get_xattrs(inode2, &len2); if (!xattrs1 && !xattrs2) { return 0; @@ -1472,8 +1467,8 @@ cmp_linux_xattrs(const struct wim_inode *inode1, return WIMLIB_ERR_IMAGES_ARE_DIFFERENT; } else { const int max_entries = 64; - const struct wimlib_xattr_entry *entries1[max_entries]; - const struct wimlib_xattr_entry *entries2[max_entries]; + const struct wim_xattr_entry *entries1[max_entries]; + const struct wim_xattr_entry *entries2[max_entries]; u32 xattr_count1 = max_entries; u32 xattr_count2 = max_entries; int ret; @@ -1496,21 +1491,21 @@ cmp_linux_xattrs(const struct wim_inode *inode1, xattr_count1, xattr_count2); } for (u32 i = 0; i < xattr_count1; i++) { - const struct wimlib_xattr_entry *entry1 = entries1[i]; - const struct wimlib_xattr_entry *entry2 = entries2[i]; + const struct wim_xattr_entry *entry1 = entries1[i]; + const struct wim_xattr_entry *entry2 = entries2[i]; - if (entry1->name_len != entry2->name_len || - entry1->value_len != entry2->value_len || - entry1->reserved != entry2->reserved || + if (entry1->value_len != entry2->value_len || + entry1->name_len != entry2->name_len || + entry1->flags != entry2->flags || memcmp(entry1->name, entry2->name, - le16_to_cpu(entry1->name_len)) || - memcmp(entry1->name + le16_to_cpu(entry1->name_len), - entry2->name + le16_to_cpu(entry1->name_len), - le32_to_cpu(entry1->value_len))) + entry1->name_len) || + memcmp(entry1->name + entry1->name_len + 1, + entry2->name + entry2->name_len + 1, + le16_to_cpu(entry1->value_len))) { ERROR("xattr %.*s of %"TS" differs", - le16_to_cpu(entry1->name_len), - entry1->name, inode_any_full_path(inode1)); + entry1->name_len, entry1->name, + inode_any_full_path(inode1)); return WIMLIB_ERR_IMAGES_ARE_DIFFERENT; } } @@ -1634,8 +1629,8 @@ cmp_inodes(const struct wim_inode *inode1, const struct wim_inode *inode2, if (ret) return ret; - /* Compare Linux-style xattrs */ - ret = cmp_linux_xattrs(inode1, inode2, cmp_flags); + /* Compare extended attributes */ + ret = cmp_xattrs(inode1, inode2, cmp_flags); if (ret) return ret; diff --git a/src/unix_apply.c b/src/unix_apply.c index 93e7b56b..43215e03 100644 --- a/src/unix_apply.c +++ b/src/unix_apply.c @@ -3,7 +3,7 @@ */ /* - * Copyright (C) 2012-2016 Eric Biggers + * Copyright (C) 2012-2018 Eric Biggers * * This file is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -64,8 +64,8 @@ unix_get_supported_features(const char *target, supported_features->unix_data = 1; supported_features->timestamps = 1; supported_features->case_sensitive_filenames = 1; -#ifdef HAVE_XATTR_SUPPORT - supported_features->linux_xattrs = 1; +#ifdef HAVE_LINUX_XATTR_SUPPORT + supported_features->xattrs = 1; #endif return 0; } @@ -268,38 +268,58 @@ unix_set_mode(int fd, const char *path, mode_t mode) return WIMLIB_ERR_SET_SECURITY; } -#ifdef HAVE_XATTR_SUPPORT +#ifdef HAVE_LINUX_XATTR_SUPPORT /* Apply extended attributes to a file */ static int apply_linux_xattrs(int fd, const struct wim_inode *inode, const char *path, struct unix_apply_ctx *ctx, - const void *entries, size_t entries_size) + const void *entries, size_t entries_size, bool is_old_format) { const void * const entries_end = entries + entries_size; - char name[XATTR_NAME_MAX + 1]; + char name[WIM_XATTR_NAME_MAX + 1]; - for (const struct wimlib_xattr_entry *entry = entries; - (void *)entry < entries_end; entry = xattr_entry_next(entry)) + for (const void *entry = entries; + entry < entries_end; + entry = is_old_format ? (const void *)old_xattr_entry_next(entry) : + (const void *)xattr_entry_next(entry)) { + bool valid; u16 name_len; const void *value; u32 value_len; int res; - if (!valid_xattr_entry(entry, entries_end - (void *)entry)) { + if (is_old_format) { + valid = old_valid_xattr_entry(entry, + entries_end - entry); + } else { + valid = valid_xattr_entry(entry, entries_end - entry); + } + if (!valid) { if (!path) { path = unix_build_inode_extraction_path(inode, ctx); } - ERROR("\"%s\": extended attribute is corrupt", path); + ERROR("\"%s\": extended attribute is corrupt or unsupported", + path); return WIMLIB_ERR_INVALID_XATTR; } - name_len = le16_to_cpu(entry->name_len); - memcpy(name, entry->name, name_len); - name[name_len] = '\0'; + if (is_old_format) { + const struct wimlib_xattr_entry_old *e = entry; - value = entry->name + name_len; - value_len = le32_to_cpu(entry->value_len); + name_len = le16_to_cpu(e->name_len); + memcpy(name, e->name, name_len); + value = e->name + name_len; + value_len = le32_to_cpu(e->value_len); + } else { + const struct wim_xattr_entry *e = entry; + + name_len = e->name_len; + memcpy(name, e->name, name_len); + value = e->name + name_len + 1; + value_len = le16_to_cpu(e->value_len); + } + name[name_len] = '\0'; if (fd >= 0) res = fsetxattr(fd, name, value, value_len, 0); @@ -311,7 +331,7 @@ apply_linux_xattrs(int fd, const struct wim_inode *inode, path = unix_build_inode_extraction_path(inode, ctx); } - if (is_security_xattr(name) && + if (is_linux_security_xattr(name) && (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS)) { @@ -325,7 +345,7 @@ apply_linux_xattrs(int fd, const struct wim_inode *inode, } return 0; } -#endif /* HAVE_XATTR_SUPPORT */ +#endif /* HAVE_LINUX_XATTR_SUPPORT */ /* * Apply UNIX-specific metadata to a file if available. This includes standard @@ -348,9 +368,10 @@ apply_unix_metadata(int fd, const struct wim_inode *inode, { bool have_dat; struct wimlib_unix_data dat; -#ifdef HAVE_XATTR_SUPPORT +#ifdef HAVE_LINUX_XATTR_SUPPORT const void *entries; u32 entries_size; + bool is_old_format; #endif int ret; @@ -373,11 +394,11 @@ apply_unix_metadata(int fd, const struct wim_inode *inode, } } -#ifdef HAVE_XATTR_SUPPORT - entries = inode_get_linux_xattrs(inode, &entries_size); +#ifdef HAVE_LINUX_XATTR_SUPPORT + entries = inode_get_linux_xattrs(inode, &entries_size, &is_old_format); if (entries) { ret = apply_linux_xattrs(fd, inode, path, ctx, - entries, entries_size); + entries, entries_size, is_old_format); if (ret) return ret; } diff --git a/src/unix_capture.c b/src/unix_capture.c index 5ae8209b..f9fad4a3 100644 --- a/src/unix_capture.c +++ b/src/unix_capture.c @@ -3,7 +3,7 @@ */ /* - * Copyright (C) 2012-2017 Eric Biggers + * Copyright (C) 2012-2018 Eric Biggers * * This file is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -103,7 +103,7 @@ my_fdopendir(int *dirfd_p) # define AT_SYMLINK_NOFOLLOW 0x100 #endif -#ifdef HAVE_XATTR_SUPPORT +#ifdef HAVE_LINUX_XATTR_SUPPORT /* * Retrieves the values of the xattrs named by the null-terminated @names of the * file at @path and serializes the xattr names and values into @entries. If @@ -117,35 +117,39 @@ gather_xattr_entries(const char *path, const char *names, size_t names_size, const char * const names_end = names + names_size; void * const entries_end = entries + entries_size; const char *name = names; - struct wimlib_xattr_entry *entry = entries; + struct wim_xattr_entry *entry = entries; - wimlib_assert((uintptr_t)entries % 4 == 0 && - entries_size % 4 == 0 && names_size != 0); do { size_t name_len = strnlen(name, names_end - name); void *value; ssize_t value_len; - if (name_len == 0 || name_len >= names_end - name || - (u16)name_len != name_len) { + if (name_len == 0 || name_len >= names_end - name) { ERROR("\"%s\": malformed extended attribute names list", path); errno = EINVAL; return -1; } + if (name_len > WIM_XATTR_NAME_MAX) { + WARNING("\"%s\": name of extended attribute \"%s\" is too long to store", + path, name); + goto next_name; + } + /* - * Note: we take care to always call lgetxattr() with a nonzero - * size, since zero size means to return the value length only. + * Take care to always call lgetxattr() with a nonzero size, + * since zero size means to return the value length only. */ - if (entries_end - (void *)entry <= sizeof(*entry) + name_len) { + if (entries_end - (void *)entry <= + sizeof(*entry) + name_len + 1) { errno = ERANGE; return -1; } - entry->name_len = cpu_to_le16(name_len); - entry->reserved = 0; - value = mempcpy(entry->name, name, name_len); + entry->name_len = name_len; + entry->flags = 0; + value = mempcpy(entry->name, name, name_len + 1); value_len = lgetxattr(path, name, value, entries_end - value); if (value_len < 0) { @@ -155,26 +159,14 @@ gather_xattr_entries(const char *path, const char *names, size_t names_size, } return -1; } - if ((u32)value_len != value_len) { - ERROR("\"%s\": value of extended attribute \"%s\" is too large", - path, name); - errno = EINVAL; - return -1; + if (value_len > WIM_XATTR_SIZE_MAX) { + WARNING("\"%s\": value of extended attribute \"%s\" is too large to store", + path, name); + goto next_name; } - entry->value_len = cpu_to_le32(value_len); - - /* - * Zero-pad the entry to the next 4-byte boundary. - * Note: because we've guaranteed that @entries_size is a - * multiple of 4, this cannot overflow the @entries buffer. - */ - value += value_len; - while ((uintptr_t)value & 3) { - *(u8 *)value = 0; - value++; - } - - entry = value; + entry->value_len = cpu_to_le16(value_len); + entry = value + value_len; + next_name: name += name_len + 1; } while (name < names_end); @@ -185,7 +177,7 @@ static int create_xattr_item(const char *path, struct wim_inode *inode, const char *names, size_t names_size) { - char _entries[1024] _aligned_attribute(4); + char _entries[1024]; char *entries = _entries; size_t entries_avail = ARRAY_LEN(_entries); ssize_t entries_size; @@ -217,7 +209,7 @@ retry: goto out; } ret = WIMLIB_ERR_NOMEM; - if (!inode_set_linux_xattrs(inode, entries, entries_size)) + if (!inode_set_xattrs(inode, entries, entries_size)) goto out; ret = 0; @@ -284,7 +276,7 @@ out: FREE(names); return ret; } -#endif /* HAVE_XATTR_SUPPORT */ +#endif /* HAVE_LINUX_XATTR_SUPPORT */ static int unix_scan_regular_file(const char *path, u64 blocks, u64 size, @@ -622,7 +614,7 @@ unix_build_dentry_tree_recursive(struct wim_dentry **tree_ret, ret = WIMLIB_ERR_NOMEM; goto out; } -#ifdef HAVE_XATTR_SUPPORT +#ifdef HAVE_LINUX_XATTR_SUPPORT ret = scan_linux_xattrs(params->cur_path, inode); if (ret) goto out; diff --git a/src/win32_apply.c b/src/win32_apply.c index 94285aca..1434bd22 100644 --- a/src/win32_apply.c +++ b/src/win32_apply.c @@ -3,7 +3,7 @@ */ /* - * Copyright (C) 2013-2016 Eric Biggers + * Copyright (C) 2013-2018 Eric Biggers * * This file is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -40,9 +40,10 @@ #include "wimlib/reparse.h" #include "wimlib/scan.h" /* for mangle_pat() and match_pattern_list() */ #include "wimlib/textfile.h" -#include "wimlib/xml.h" #include "wimlib/wimboot.h" #include "wimlib/wof.h" +#include "wimlib/xattr.h" +#include "wimlib/xml.h" struct win32_apply_ctx { @@ -169,6 +170,9 @@ struct win32_apply_ctx { /* Number of files for which we couldn't set the object ID. */ unsigned long num_object_id_failures; + /* Number of files for which we couldn't set extended attributes. */ + unsigned long num_xattr_failures; + /* The Windows build number of the image being applied, or 0 if unknown. */ u64 windows_build_number; @@ -225,9 +229,14 @@ get_vol_flags(const wchar_t *target, DWORD *vol_flags_ret, } if (wcsstr(filesystem_name, L"NTFS")) { - /* FILE_SUPPORTS_HARD_LINKS is only supported on Windows 7 and - * later. Force it on anyway if filesystem is NTFS. */ + /* + * FILE_SUPPORTS_HARD_LINKS and + * FILE_SUPPORTS_EXTENDED_ATTRIBUTES are only supported on + * Windows 7 and later. Force them on anyway if the filesystem + * is NTFS. + */ *vol_flags_ret |= FILE_SUPPORTS_HARD_LINKS; + *vol_flags_ret |= FILE_SUPPORTS_EXTENDED_ATTRIBUTES; /* There's no volume flag for short names, but according to the * MS documentation they are only user-settable on NTFS. */ @@ -339,6 +348,9 @@ win32_get_supported_features(const wchar_t *target, supported_features->case_sensitive_filenames = 1; } + if (vol_flags & FILE_SUPPORTS_EXTENDED_ATTRIBUTES) + supported_features->xattrs = 1; + return 0; } @@ -2805,6 +2817,105 @@ set_object_id(HANDLE h, const struct wim_inode *inode, } } +static int +set_xattrs(HANDLE h, const struct wim_inode *inode, struct win32_apply_ctx *ctx) +{ + const void *entries, *entries_end; + u32 len; + const struct wim_xattr_entry *entry; + size_t bufsize = 0; + u8 _buf[1024] _aligned_attribute(4); + u8 *buf = _buf; + FILE_FULL_EA_INFORMATION *ea, *ea_prev; + NTSTATUS status; + int ret; + + if (!ctx->common.supported_features.xattrs) + return 0; + + entries = inode_get_xattrs(inode, &len); + if (likely(entries == NULL || len == 0)) /* No extended attributes? */ + return 0; + entries_end = entries + len; + + entry = entries; + for (entry = entries; (void *)entry < entries_end; + entry = xattr_entry_next(entry)) { + if (!valid_xattr_entry(entry, entries_end - (void *)entry)) { + ERROR("\"%"TS"\": extended attribute is corrupt or unsupported", + inode_any_full_path(inode)); + return WIMLIB_ERR_INVALID_XATTR; + } + + bufsize += ALIGN(offsetof(FILE_FULL_EA_INFORMATION, EaName) + + entry->name_len + 1 + + le16_to_cpu(entry->value_len), 4); + } + + if (unlikely(bufsize != (u32)bufsize)) { + ERROR("\"%"TS"\": too many extended attributes to extract!", + inode_any_full_path(inode)); + return WIMLIB_ERR_INVALID_XATTR; + } + + if (unlikely(bufsize > sizeof(_buf))) { + buf = MALLOC(bufsize); + if (!buf) + return WIMLIB_ERR_NOMEM; + } + + ea_prev = NULL; + ea = (FILE_FULL_EA_INFORMATION *)buf; + for (entry = entries; (void *)entry < entries_end; + entry = xattr_entry_next(entry)) { + u8 *p; + + if (ea_prev) + ea_prev->NextEntryOffset = (u8 *)ea - (u8 *)ea_prev; + ea->Flags = entry->flags; + ea->EaNameLength = entry->name_len; + ea->EaValueLength = le16_to_cpu(entry->value_len); + p = mempcpy(ea->EaName, entry->name, + ea->EaNameLength + 1 + ea->EaValueLength); + while ((uintptr_t)p & 3) + *p++ = 0; + ea_prev = ea; + ea = (FILE_FULL_EA_INFORMATION *)p; + } + ea_prev->NextEntryOffset = 0; + wimlib_assert((u8 *)ea - buf == bufsize); + + status = NtSetEaFile(h, &ctx->iosb, buf, bufsize); + if (unlikely(!NT_SUCCESS(status))) { + if (status == STATUS_EAS_NOT_SUPPORTED) { + /* This happens with Samba. */ + WARNING("Filesystem advertised extended attribute (EA) support, but it doesn't\n" + " work. EAs will not be extracted."); + ctx->common.supported_features.xattrs = 0; + } else if (status == STATUS_INVALID_EA_NAME) { + ctx->num_xattr_failures++; + if (ctx->num_xattr_failures < 5) { + winnt_warning(status, + L"Can't set extended attributes on \"%ls\"", + current_path(ctx)); + } else if (ctx->num_xattr_failures == 5) { + WARNING("Suppressing further warnings about " + "failure to set extended attributes."); + } + } else { + winnt_error(status, L"Can't set extended attributes on \"%ls\"", + current_path(ctx)); + ret = WIMLIB_ERR_SET_XATTR; + goto out; + } + } + ret = 0; +out: + if (buf != _buf) + FREE(buf); + return ret; +} + /* Set the security descriptor @desc, of @desc_size bytes, on the file with open * handle @h. */ static NTSTATUS @@ -2948,11 +3059,18 @@ do_apply_metadata_to_file(HANDLE h, const struct wim_inode *inode, { FILE_BASIC_INFORMATION info; NTSTATUS status; + int ret; /* Set the file's object ID if present and object IDs are supported by * the filesystem. */ set_object_id(h, inode, ctx); + /* Set the file's extended attributes (EAs) if present and EAs are + * supported by the filesystem. */ + ret = set_xattrs(h, inode, ctx); + if (ret) + return ret; + /* Set the file's security descriptor if present and we're not in * NO_ACLS mode */ if (inode_has_security_descriptor(inode) && @@ -3017,7 +3135,7 @@ apply_metadata_to_file(const struct wim_dentry *dentry, NTSTATUS status; int ret; - perms = FILE_WRITE_ATTRIBUTES | WRITE_DAC | + perms = FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | WRITE_DAC | WRITE_OWNER | ACCESS_SYSTEM_SECURITY; build_extraction_path(dentry, ctx); diff --git a/src/win32_capture.c b/src/win32_capture.c index 75c5e66f..df908b02 100644 --- a/src/win32_capture.c +++ b/src/win32_capture.c @@ -5,7 +5,7 @@ */ /* - * Copyright (C) 2013-2017 Eric Biggers + * Copyright (C) 2013-2018 Eric Biggers * * This file is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -41,6 +41,7 @@ #include "wimlib/scan.h" #include "wimlib/win32_vss.h" #include "wimlib/wof.h" +#include "wimlib/xattr.h" struct winnt_scan_ctx { struct scan_params *params; @@ -695,6 +696,111 @@ winnt_load_object_id(HANDLE h, struct wim_inode *inode, return 0; } +/* Load a file's extended attributes into the corresponding WIM inode. */ +static noinline_for_stack int +winnt_load_xattrs(HANDLE h, struct wim_inode *inode, + struct winnt_scan_ctx *ctx, u32 ea_size) +{ + IO_STATUS_BLOCK iosb; + NTSTATUS status; + u8 _buf[1024] _aligned_attribute(4); + u8 *buf = _buf; + const FILE_FULL_EA_INFORMATION *ea; + struct wim_xattr_entry *entry; + int ret; + + + /* + * EaSize from FILE_EA_INFORMATION is apparently supposed to give the + * size of the buffer required for NtQueryEaFile(), but it doesn't + * actually work correctly; it can be off by about 4 bytes per xattr. + * + * So just start out by doubling the advertised size, and also handle + * STATUS_BUFFER_OVERFLOW just in case. + */ +retry: + if (unlikely(ea_size * 2 < ea_size)) + ea_size = UINT32_MAX; + else + ea_size *= 2; + if (unlikely(ea_size > sizeof(_buf))) { + buf = MALLOC(ea_size); + if (!buf) { + if (ea_size >= (1 << 20)) { + WARNING("\"%ls\": EaSize was extremely large (%u)", + printable_path(ctx), ea_size); + } + return WIMLIB_ERR_NOMEM; + } + } + + status = NtQueryEaFile(h, &iosb, buf, ea_size, + FALSE, NULL, 0, NULL, TRUE); + + if (unlikely(!NT_SUCCESS(status))) { + if (status == STATUS_BUFFER_OVERFLOW) { + if (buf != _buf) { + FREE(buf); + buf = NULL; + } + goto retry; + } + if (status == STATUS_NO_EAS_ON_FILE) { + /* + * FILE_EA_INFORMATION.EaSize was nonzero so this + * shouldn't happen, but just in case... + */ + ret = 0; + goto out; + } + winnt_error(status, L"\"%ls\": Can't read extended attributes", + printable_path(ctx)); + ret = WIMLIB_ERR_STAT; + goto out; + } + + ea = (const FILE_FULL_EA_INFORMATION *)buf; + entry = (struct wim_xattr_entry *)buf; + for (;;) { + /* + * wim_xattr_entry is not larger than FILE_FULL_EA_INFORMATION, + * so we can reuse the same buffer by overwriting the + * FILE_FULL_EA_INFORMATION with the wim_xattr_entry in-place. + */ + FILE_FULL_EA_INFORMATION _ea; + + STATIC_ASSERT(offsetof(struct wim_xattr_entry, name) <= + offsetof(FILE_FULL_EA_INFORMATION, EaName)); + wimlib_assert((u8 *)entry <= (const u8 *)ea); + + memcpy(&_ea, ea, sizeof(_ea)); + + entry->value_len = cpu_to_le16(_ea.EaValueLength); + entry->name_len = _ea.EaNameLength; + entry->flags = _ea.Flags; + memmove(entry->name, ea->EaName, _ea.EaNameLength); + entry->name[_ea.EaNameLength] = '\0'; + memmove(&entry->name[_ea.EaNameLength + 1], + &ea->EaName[_ea.EaNameLength + 1], _ea.EaValueLength); + entry = (struct wim_xattr_entry *) + &entry->name[_ea.EaNameLength + 1 + _ea.EaValueLength]; + if (_ea.NextEntryOffset == 0) + break; + ea = (const FILE_FULL_EA_INFORMATION *) + ((const u8 *)ea + _ea.NextEntryOffset); + } + wimlib_assert((u8 *)entry - buf <= ea_size); + + ret = WIMLIB_ERR_NOMEM; + if (!inode_set_xattrs(inode, buf, (u8 *)entry - buf)) + goto out; + ret = 0; +out: + if (unlikely(buf != _buf)) + FREE(buf); + return ret; +} + static int winnt_build_dentry_tree_recursive(struct wim_dentry **root_ret, HANDLE cur_dir, @@ -1518,6 +1624,7 @@ struct file_info { u64 last_access_time; u64 ino; u64 end_of_file; + u32 ea_size; }; static noinline_for_stack NTSTATUS @@ -1540,6 +1647,7 @@ get_file_info(HANDLE h, struct file_info *info) info->last_access_time = all_info.BasicInformation.LastAccessTime.QuadPart; info->ino = all_info.InternalInformation.IndexNumber.QuadPart; info->end_of_file = all_info.StandardInformation.EndOfFile.QuadPart; + info->ea_size = all_info.EaInformation.EaSize; return STATUS_SUCCESS; } @@ -1620,8 +1728,8 @@ winnt_build_dentry_tree_recursive(struct wim_dentry **root_ret, * this permission on all nondirectories. Perhaps it causes Windows to * start prefetching the file contents... */ status = winnt_openat(cur_dir, relative_path, relative_path_nchars, - FILE_READ_ATTRIBUTES | READ_CONTROL | - ACCESS_SYSTEM_SECURITY, + FILE_READ_ATTRIBUTES | FILE_READ_EA | + READ_CONTROL | ACCESS_SYSTEM_SECURITY, &h); if (unlikely(!NT_SUCCESS(status))) { if (status == STATUS_DELETE_PENDING) { @@ -1717,6 +1825,13 @@ winnt_build_dentry_tree_recursive(struct wim_dentry **root_ret, if (ret) goto out; + /* Get the file's extended attributes. */ + if (unlikely(file_info.ea_size != 0)) { + ret = winnt_load_xattrs(h, inode, ctx, file_info.ea_size); + if (ret) + goto out; + } + /* If this is a reparse point, load the reparse data. */ if (unlikely(inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)) { ret = winnt_load_reparse_data(h, inode, ctx); @@ -1961,6 +2076,10 @@ typedef struct { #define NTFS_IS_SPECIAL_FILE(ino) \ (NTFS_MFT_NO(ino) <= 15 && !NTFS_IS_ROOT_FILE(ino)) +#define NTFS_SPECIAL_STREAM_OBJECT_ID 0x00000001 +#define NTFS_SPECIAL_STREAM_EA 0x00000002 +#define NTFS_SPECIAL_STREAM_EA_INFORMATION 0x00000004 + /* Intermediate inode structure. This is used to temporarily save information * from FSCTL_QUERY_FILE_LAYOUT before creating the full 'struct wim_inode'. */ struct ntfs_inode { @@ -1973,8 +2092,8 @@ struct ntfs_inode { u32 attributes; u32 security_id; u32 num_aliases; - u32 num_streams : 31; - u32 have_object_id : 1; + u32 num_streams; + u32 special_streams; u32 first_stream_offset; struct ntfs_dentry *first_child; wchar_t short_name[13]; @@ -2145,13 +2264,10 @@ is_valid_stream_entry(const STREAM_LAYOUT_ENTRY *stream) stream->StreamIdentifierLength / 2); } -static bool -is_object_id_stream(const STREAM_LAYOUT_ENTRY *stream) -{ - return stream->StreamIdentifierLength == 24 && - !wmemcmp(stream->StreamIdentifier, L"::$OBJECT_ID", 12); -} - +/* assumes that 'id' is a wide string literal */ +#define stream_has_identifier(stream, id) \ + ((stream)->StreamIdentifierLength == sizeof(id) - 2 && \ + !memcmp((stream)->StreamIdentifier, id, sizeof(id) - 2)) /* * If the specified STREAM_LAYOUT_ENTRY represents a DATA stream as opposed to * some other type of NTFS stream such as a STANDARD_INFORMATION stream, return @@ -2188,16 +2304,18 @@ use_stream(const FILE_LAYOUT_ENTRY *file, const STREAM_LAYOUT_ENTRY *stream, /* Validate the STREAM_LAYOUT_ENTRYs of the specified file and compute the total * length in bytes of the ntfs_stream structures needed to hold the stream - * information. In addition, set *have_object_id_ret=true if the file has an - * object ID stream. */ + * information. In addition, set *special_streams_ret to a bitmask of special + * stream types that were found. */ static int validate_streams_and_compute_total_length(const FILE_LAYOUT_ENTRY *file, size_t *total_length_ret, - bool *have_object_id_ret) + u32 *special_streams_ret) { const STREAM_LAYOUT_ENTRY *stream = (const void *)file + file->FirstStreamOffset; size_t total = 0; + u32 special_streams = 0; + for (;;) { const wchar_t *name; size_t name_nchars; @@ -2217,8 +2335,12 @@ validate_streams_and_compute_total_length(const FILE_LAYOUT_ENTRY *file, if (use_stream(file, stream, &name, &name_nchars)) { total += ALIGN(sizeof(struct ntfs_stream) + (name_nchars + 1) * sizeof(wchar_t), 8); - } else if (is_object_id_stream(stream)) { - *have_object_id_ret = true; + } else if (stream_has_identifier(stream, L"::$OBJECT_ID")) { + special_streams |= NTFS_SPECIAL_STREAM_OBJECT_ID; + } else if (stream_has_identifier(stream, L"::$EA")) { + special_streams |= NTFS_SPECIAL_STREAM_EA; + } else if (stream_has_identifier(stream, L"::$EA_INFORMATION")) { + special_streams |= NTFS_SPECIAL_STREAM_EA_INFORMATION; } if (stream->NextStreamOffset == 0) break; @@ -2226,6 +2348,7 @@ validate_streams_and_compute_total_length(const FILE_LAYOUT_ENTRY *file, } *total_length_ret = total; + *special_streams_ret = special_streams; return 0; } @@ -2322,7 +2445,7 @@ load_one_file(const FILE_LAYOUT_ENTRY *file, struct ntfs_inode_map *inode_map) size_t n; int ret; void *p; - bool have_object_id = false; + u32 special_streams = 0; inode_size = ALIGN(sizeof(struct ntfs_inode), 8); @@ -2340,7 +2463,7 @@ load_one_file(const FILE_LAYOUT_ENTRY *file, struct ntfs_inode_map *inode_map) if (file_has_streams(file)) { ret = validate_streams_and_compute_total_length(file, &n, - &have_object_id); + &special_streams); if (ret) return ret; inode_size += n; @@ -2358,7 +2481,7 @@ load_one_file(const FILE_LAYOUT_ENTRY *file, struct ntfs_inode_map *inode_map) ni->last_write_time = info->BasicInformation.LastWriteTime; ni->last_access_time = info->BasicInformation.LastAccessTime; ni->security_id = info->SecurityId; - ni->have_object_id = have_object_id; + ni->special_streams = special_streams; p = FIRST_DENTRY(ni); @@ -2612,7 +2735,7 @@ generate_wim_structures_recursive(struct wim_dentry **root_ret, * but not from FSCTL_QUERY_FILE_LAYOUT. */ if (ni->attributes & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_ENCRYPTED) || - ni->have_object_id) + ni->special_streams != 0) { ret = winnt_build_dentry_tree_recursive(&root, NULL, diff --git a/tests/wims/README b/tests/wims/README index 790f5d41..07cfc6bb 100644 --- a/tests/wims/README +++ b/tests/wims/README @@ -18,3 +18,6 @@ This should still be extracted successfully. empty_dacl.wim: This WIM has an image containing file with a security descriptor having an empty DACL. This is valid and should be extracted successfully. + +linux_xattrs_old.wim: Includes Linux xattrs in old format +(TAG_WIMLIB_LINUX_XATTRS) diff --git a/tests/wims/linux_xattrs_old.wim b/tests/wims/linux_xattrs_old.wim new file mode 100644 index 0000000000000000000000000000000000000000..44e1e120a1a57e4817ccf4f4811c6cde54cad05c GIT binary patch literal 1466 zcmc&yJ!lhQ7=DrlO@)HN!9_s|1yL|bBZ=ZETw*kbxx^+)bj)E2MW@6D7vXBB(j^5c z9mGv=brJ+i&`#As$l~fIT_l6xWcs|{-C=UM1YGnxzI*?k_wTtgt2M&}@P!w1A=n~$ zUVV%0M^5gRUiLn%KI`|_LXZboR8fRGVMlRo{BrE@2iNb~4(K1gqi!$TmB%RuJRR5e zi-Y&)&cAus3;jHOA}-Ri{kTbX>+92$^!PsG+^#-~JS(K}*t{}_=t0Np>z6@Tzb}eq ztl$wE4EX1<*Bta?&bdSN2(OH^+G;c})Y~{ZV%OWk-=BD$2w#rnqy|o!qTaY?XC4IFJ5oP-aY^P_TyfA z@LNL-225OorlgLW$m0z460TFbhpU)Jl2;HV=)~r!KOk#Odg4PqjTr_i$f1n8^xq*a zsW9Vy4L67_veq~{D59}inOA2{7Wb*;QDyyvsvjKFP$0g-sfuc!TLGEV5E^Hzu#e7e z2D{5f!j