From 4ef81cbb80143f81a6bf72a3b908d81a2bb6d2e6 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Mon, 26 Dec 2016 15:14:41 -0600 Subject: [PATCH] Implement basic handling of xattr streams Add a new stream type to represent a list of Linux-style extended attributes, and add a new per-file tagged item type to represent the hash of the xattr stream. --- Makefile.am | 1 + configure.ac | 3 +- include/wimlib/apply.h | 1 + include/wimlib/inode.h | 6 ++ include/wimlib/tagged_items.h | 3 + include/wimlib/xattr.h | 100 ++++++++++++++++++++++++++++++++++ src/dentry.c | 28 ++++++++-- src/extract.c | 26 +++++++-- 8 files changed, 158 insertions(+), 10 deletions(-) create mode 100644 include/wimlib/xattr.h diff --git a/Makefile.am b/Makefile.am index fbaecee8..f59f8411 100644 --- a/Makefile.am +++ b/Makefile.am @@ -153,6 +153,7 @@ libwim_la_SOURCES = \ include/wimlib/wim.h \ include/wimlib/write.h \ include/wimlib/x86_cpu_features.h \ + include/wimlib/xattr.h \ include/wimlib/xml.h \ include/wimlib/xml_windows.h \ include/wimlib/xpress_constants.h diff --git a/configure.ac b/configure.ac index d7de0b08..84d35c4e 100644 --- a/configure.ac +++ b/configure.ac @@ -59,7 +59,8 @@ AM_CONDITIONAL([WINDOWS_NATIVE_BUILD], [test "$WINDOWS_NATIVE_BUILD" = "yes"]) # Useful functions which we can do without. AC_CHECK_FUNCS([futimens utimensat flock mempcpy \ - openat fstatat readlinkat fdopendir posix_fallocate]) + openat fstatat readlinkat fdopendir posix_fallocate \ + llistxattr lgetxattr fsetxattr]) # Header checks, most of which are only here to satisfy conditional includes # made by the libntfs-3g headers. diff --git a/include/wimlib/apply.h b/include/wimlib/apply.h index 9d9c3c97..cfddd1ed 100644 --- a/include/wimlib/apply.h +++ b/include/wimlib/apply.h @@ -31,6 +31,7 @@ struct wim_features { unsigned long object_ids; unsigned long timestamps; unsigned long case_sensitive_filenames; + unsigned long linux_xattrs; }; struct blob_descriptor; diff --git a/include/wimlib/inode.h b/include/wimlib/inode.h index 9fc917b5..7e71d18c 100644 --- a/include/wimlib/inode.h +++ b/include/wimlib/inode.h @@ -33,6 +33,9 @@ enum wim_inode_stream_type { * as well as the encrypted data of all the file's data streams. */ STREAM_TYPE_EFSRPC_RAW_DATA, + /* Extended attributes originating from Linux (wimlib extension) */ + STREAM_TYPE_LINUX_XATTR, + /* Stream type could not be determined */ STREAM_TYPE_UNKNOWN, }; @@ -54,6 +57,9 @@ extern const utf16lechar NO_STREAM_NAME[1]; * one such stream, and it should be unnamed. However, it is possible for an * inode to have both a reparse point stream and an unnamed data stream, and * even named data streams as well. + * + * On Linux, wimlib now also supports storing and restoring extended attributes. + * For this an unnamed stream of type STREAM_TYPE_LINUX_XATTR is used. */ struct wim_inode_stream { diff --git a/include/wimlib/tagged_items.h b/include/wimlib/tagged_items.h index 64aae86f..9e4ed7cc 100644 --- a/include/wimlib/tagged_items.h +++ b/include/wimlib/tagged_items.h @@ -11,6 +11,9 @@ struct wim_inode; /* [wimlib extension] Standard UNIX metadata: uid, gid, mode, and rdev */ #define TAG_WIMLIB_UNIX_DATA 0x337DD873 +/* [wimlib extension] Hash of stream containing Linux-style xattrs */ +#define TAG_WIMLIB_LINUX_XATTR_HASH 0x337DD874 + extern bool inode_set_tagged_data(struct wim_inode *inode, u32 tag, const void *data, u32 len); diff --git a/include/wimlib/xattr.h b/include/wimlib/xattr.h new file mode 100644 index 00000000..3a024346 --- /dev/null +++ b/include/wimlib/xattr.h @@ -0,0 +1,100 @@ +#ifndef _WIMLIB_XATTR_H +#define _WIMLIB_XATTR_H + +#include "wimlib/endianness.h" +#include "wimlib/sha1.h" +#include "wimlib/tagged_items.h" +#include "wimlib/util.h" + +#undef HAVE_XATTR_SUPPORT +#if defined(HAVE_SYS_XATTR_H) && defined(HAVE_LLISTXATTR) && \ + defined(HAVE_LGETXATTR) && defined(HAVE_FSETXATTR) +# define HAVE_XATTR_SUPPORT 1 +#endif + +/* + * On-disk format of an entry in an extended attribute stream (wimlib + * extension). An xattr stream consists of a series of variable-length xattr + * entries, each of which begins with this entry header. + * + * Currently this is only used for Linux-style xattrs, but in the future we may + * use this for Windows-style xattrs too. + */ +struct wimlib_xattr_entry { + + /* length of xattr name in bytes */ + le16 name_len; + + /* reserved, must be 0 */ + le16 reserved; + + /* length of xattr value in bytes, not counting padding */ + le32 value_len; + + /* followed by the name with no terminating null */ + char name[0]; + + /* + * directly followed by the value, zero-padded to the next 4-byte + * boundary if not already aligned + */ + /* u8 value[0]; */ +}; + +static inline size_t +xattr_entry_size(const struct wimlib_xattr_entry *entry) +{ + return ALIGN(sizeof(*entry) + le16_to_cpu(entry->name_len) + + le32_to_cpu(entry->value_len), 4); +} + +static inline struct wimlib_xattr_entry * +xattr_entry_next(const struct wimlib_xattr_entry *entry) +{ + return (void *)entry + 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) +{ + 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; + + if (le32_to_cpu(entry->value_len) > XATTR_SIZE_MAX) + return false; + + return avail >= xattr_entry_size(entry); +} + +static inline const u8 * +inode_get_linux_xattr_hash(const struct wim_inode *inode) +{ + return inode_get_tagged_item(inode, TAG_WIMLIB_LINUX_XATTR_HASH, + SHA1_HASH_SIZE, NULL); +} + +static inline bool +inode_has_linux_xattr_hash(const struct wim_inode *inode) +{ + return inode_get_linux_xattr_hash(inode) != NULL; +} + +static inline bool +inode_set_linux_xattr_hash(struct wim_inode *inode, const u8 *hash) +{ + return inode_set_tagged_data(inode, TAG_WIMLIB_LINUX_XATTR_HASH, + hash, SHA1_HASH_SIZE); +} + +#endif /* _WIMLIB_XATTR_H */ diff --git a/src/dentry.c b/src/dentry.c index 9e175f7e..0bcc8685 100644 --- a/src/dentry.c +++ b/src/dentry.c @@ -68,6 +68,7 @@ #include "wimlib/endianness.h" #include "wimlib/metadata.h" #include "wimlib/paths.h" +#include "wimlib/xattr.h" /* On-disk format of a WIM dentry (directory entry), located in the metadata * resource for a WIM image. */ @@ -1151,7 +1152,8 @@ assign_stream_types_encrypted(struct wim_inode *inode) { for (unsigned i = 0; i < inode->i_num_streams; i++) { struct wim_inode_stream *strm = &inode->i_streams[i]; - if (!stream_is_named(strm) && !is_zero_hash(strm->_stream_hash)) + if (strm->stream_type == STREAM_TYPE_UNKNOWN && + !stream_is_named(strm) && !is_zero_hash(strm->_stream_hash)) { strm->stream_type = STREAM_TYPE_EFSRPC_RAW_DATA; return; @@ -1182,6 +1184,8 @@ assign_stream_types_unencrypted(struct wim_inode *inode) for (unsigned i = 0; i < inode->i_num_streams; i++) { struct wim_inode_stream *strm = &inode->i_streams[i]; + if (strm->stream_type != STREAM_TYPE_UNKNOWN) + continue; if (stream_is_named(strm)) { /* Named data stream */ strm->stream_type = STREAM_TYPE_DATA; @@ -1223,8 +1227,11 @@ setup_inode_streams(const u8 *p, const u8 *end, struct wim_inode *inode, u64 *offset_p) { const u8 *orig_p = p; + const u8 *linux_xattr_hash = inode_get_linux_xattr_hash(inode); + unsigned i; - inode->i_num_streams = 1 + num_extra_streams; + inode->i_num_streams = 1 + num_extra_streams + + (linux_xattr_hash != NULL); if (unlikely(inode->i_num_streams > ARRAY_LEN(inode->i_embedded_streams))) { inode->i_streams = CALLOC(inode->i_num_streams, @@ -1240,7 +1247,7 @@ setup_inode_streams(const u8 *p, const u8 *end, struct wim_inode *inode, inode->i_streams[0].stream_id = 0; /* Read the extra stream entries */ - for (unsigned i = 1; i < inode->i_num_streams; i++) { + for (i = 1; i < 1 + num_extra_streams; i++) { struct wim_inode_stream *strm; const struct wim_extra_stream_entry_on_disk *disk_strm; u64 length; @@ -1300,11 +1307,22 @@ setup_inode_streams(const u8 *p, const u8 *end, struct wim_inode *inode, p += length; } - inode->i_next_stream_id = inode->i_num_streams; + /* Set up the xattr stream if there is one (wimlib extension) */ + if (linux_xattr_hash != NULL) { + struct wim_inode_stream *strm = &inode->i_streams[i]; + + strm->stream_id = i; + strm->stream_type = STREAM_TYPE_LINUX_XATTR; + strm->stream_name = (utf16lechar *)NO_STREAM_NAME; + copy_hash(strm->_stream_hash, linux_xattr_hash); + i++; + } + + inode->i_next_stream_id = i; /* Now, assign a type to each stream. Unfortunately this requires * various hacks because stream types aren't explicitly provided in the - * WIM on-disk format. */ + * WIM on-disk format (except for the wimlib-specific xattr stream) */ if (unlikely(inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)) assign_stream_types_encrypted(inode); diff --git a/src/extract.c b/src/extract.c index a39c1c02..e30e10de 100644 --- a/src/extract.c +++ b/src/extract.c @@ -63,6 +63,7 @@ #include "wimlib/unix_data.h" #include "wimlib/wim.h" #include "wimlib/win32.h" /* for realpath() equivalent */ +#include "wimlib/xattr.h" #include "wimlib/xml.h" #define WIMLIB_EXTRACT_FLAG_FROM_PIPE 0x80000000 @@ -1125,6 +1126,11 @@ ref_stream_if_needed(struct wim_dentry *dentry, struct wim_inode *inode, need_stream = true; } break; + case STREAM_TYPE_LINUX_XATTR: + if ((ctx->extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) && + ctx->supported_features.linux_xattrs) + need_stream = true; + break; } if (need_stream) return ref_stream(strm, dentry, ctx); @@ -1224,6 +1230,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_xattr_hash(inode)) + features->linux_xattrs++; } /* Tally features necessary to extract a dentry and the corresponding inode. */ @@ -1361,19 +1369,29 @@ do_feature_check(const struct wim_features *required_features, WARNING("Ignoring Windows NT security descriptors of %lu files", required_features->security_descriptors); - /* UNIX data. */ + /* Standard UNIX metadata */ if ((extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) && required_features->unix_data && !supported_features->unix_data) { - ERROR("Extraction backend does not support UNIX data!"); + ERROR("Extraction backend does not support standard UNIX " + "metadata (uid/gid/mode/rdev)!"); return WIMLIB_ERR_UNSUPPORTED; } if (required_features->unix_data && !(extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA)) { - WARNING("Ignoring UNIX metadata of %lu files", - required_features->unix_data); + WARNING("Ignoring standard UNIX metadata (uid/gid/mode/rdev) " + "of %lu files", required_features->unix_data); + } + + /* Linux-style extended attributes */ + if (required_features->linux_xattrs && + (!supported_features->linux_xattrs || + !(extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA))) + { + WARNING("Ignoring Linux-style extended attributes of %lu files", + required_features->linux_xattrs); } /* Object IDs. */ -- 2.43.0