+
+ /* Not actually necessary, but to be safe don't retain the now-obsolete
+ * parent pointer. */
+ dentry->d_parent = dentry;
+}
+
+static int
+read_extra_data(const u8 *p, const u8 *end, struct wim_inode *inode)
+{
+ while (((uintptr_t)p & 7) && p < end)
+ p++;
+
+ if (unlikely(p < end)) {
+ inode->i_extra = memdup(p, end - p);
+ if (!inode->i_extra)
+ return WIMLIB_ERR_NOMEM;
+ inode->i_extra_size = end - p;
+ }
+ return 0;
+}
+
+/*
+ * Set the type of each stream for an encrypted file.
+ *
+ * All data streams of the encrypted file should have been packed into a single
+ * stream in the format provided by ReadEncryptedFileRaw() on Windows. We
+ * assign this stream type STREAM_TYPE_EFSRPC_RAW_DATA.
+ *
+ * Encrypted files can't have a reparse point stream. In the on-disk NTFS
+ * format they can, but as far as I know the reparse point stream of an
+ * encrypted file can't be stored in the WIM format in a way that's compatible
+ * with WIMGAPI, nor is there even any way for it to be read or written on
+ * Windows when the process does not have access to the file encryption key.
+ */
+static void
+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))
+ {
+ strm->stream_type = STREAM_TYPE_EFSRPC_RAW_DATA;
+ return;
+ }
+ }
+}
+
+/*
+ * Set the type of each stream for an unencrypted file.
+ *
+ * There will be an unnamed data stream, a reparse point stream, or both an
+ * unnamed data stream and a reparse point stream. In addition, there may be
+ * named data streams.
+ *
+ * NOTE: if the file has a reparse point stream or at least one named data
+ * stream, then WIMGAPI puts *all* streams in the extra stream entries and
+ * leaves the default stream hash zeroed. wimlib now does the same. However,
+ * for input we still support the default hash field being used, since wimlib
+ * used to use it and MS software is somewhat accepting of it as well.
+ */
+static void
+assign_stream_types_unencrypted(struct wim_inode *inode)
+{
+ bool found_reparse_point_stream = false;
+ bool found_unnamed_data_stream = false;
+ struct wim_inode_stream *unnamed_stream_with_zero_hash = NULL;
+
+ for (unsigned i = 0; i < inode->i_num_streams; i++) {
+ struct wim_inode_stream *strm = &inode->i_streams[i];
+
+ if (stream_is_named(strm)) {
+ /* Named data stream */
+ strm->stream_type = STREAM_TYPE_DATA;
+ } else if (i != 0 || !is_zero_hash(strm->_stream_hash)) {
+ /* Unnamed stream in the extra stream entries, OR the
+ * default stream in the dentry provided that it has a
+ * nonzero hash. */
+ if ((inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
+ !found_reparse_point_stream) {
+ found_reparse_point_stream = true;
+ strm->stream_type = STREAM_TYPE_REPARSE_POINT;
+ } else if (!found_unnamed_data_stream) {
+ found_unnamed_data_stream = true;
+ strm->stream_type = STREAM_TYPE_DATA;
+ }
+ } else if (!unnamed_stream_with_zero_hash) {
+ unnamed_stream_with_zero_hash = strm;
+ }
+ }
+
+ if (unnamed_stream_with_zero_hash) {
+ int type = STREAM_TYPE_UNKNOWN;
+ if ((inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
+ !found_reparse_point_stream) {
+ type = STREAM_TYPE_REPARSE_POINT;
+ } else if (!found_unnamed_data_stream) {
+ type = STREAM_TYPE_DATA;
+ }
+ unnamed_stream_with_zero_hash->stream_type = type;
+ }
+}
+
+/*
+ * Read and interpret the collection of streams for the specified inode.
+ */
+static int
+setup_inode_streams(const u8 *p, const u8 *end, struct wim_inode *inode,
+ unsigned num_extra_streams, const u8 *default_hash,
+ u64 *offset_p)
+{
+ const u8 *orig_p = p;
+
+ inode->i_num_streams = 1 + num_extra_streams;
+
+ if (unlikely(inode->i_num_streams > ARRAY_LEN(inode->i_embedded_streams))) {
+ inode->i_streams = CALLOC(inode->i_num_streams,
+ sizeof(inode->i_streams[0]));
+ if (!inode->i_streams)
+ return WIMLIB_ERR_NOMEM;
+ }
+
+ /* Use the default hash field for the first stream */
+ inode->i_streams[0].stream_name = (utf16lechar *)NO_STREAM_NAME;
+ copy_hash(inode->i_streams[0]._stream_hash, default_hash);
+ inode->i_streams[0].stream_type = STREAM_TYPE_UNKNOWN;
+ inode->i_streams[0].stream_id = 0;
+
+ /* Read the extra stream entries */
+ for (unsigned i = 1; i < inode->i_num_streams; i++) {
+ struct wim_inode_stream *strm;
+ const struct wim_extra_stream_entry_on_disk *disk_strm;
+ u64 length;
+ u16 name_nbytes;
+
+ strm = &inode->i_streams[i];
+
+ strm->stream_id = i;
+
+ /* Do we have at least the size of the fixed-length data we know
+ * need? */
+ if ((end - p) < sizeof(struct wim_extra_stream_entry_on_disk))
+ return WIMLIB_ERR_INVALID_METADATA_RESOURCE;
+
+ disk_strm = (const struct wim_extra_stream_entry_on_disk *)p;
+
+ /* Read the length field */
+ length = ALIGN(le64_to_cpu(disk_strm->length), 8);
+
+ /* Make sure the length field is neither so small it doesn't
+ * include all the fixed-length data nor so large it overflows
+ * the metadata resource buffer. */
+ if (length < sizeof(struct wim_extra_stream_entry_on_disk) ||
+ length > (end - p))
+ return WIMLIB_ERR_INVALID_METADATA_RESOURCE;
+
+ /* Read the rest of the fixed-length data. */
+
+ copy_hash(strm->_stream_hash, disk_strm->hash);
+ name_nbytes = le16_to_cpu(disk_strm->name_nbytes);
+
+ /* If stream_name_nbytes != 0, the stream is named. */
+ if (name_nbytes != 0) {
+ /* The name is encoded in UTF16-LE, which uses 2-byte
+ * coding units, so the length of the name had better be
+ * an even number of bytes. */
+ if (name_nbytes & 1)
+ return WIMLIB_ERR_INVALID_METADATA_RESOURCE;
+
+ /* Add the length of the stream name to get the length
+ * we actually need to read. Make sure this isn't more
+ * than the specified length of the entry. */
+ if (sizeof(struct wim_extra_stream_entry_on_disk) +
+ name_nbytes > length)
+ return WIMLIB_ERR_INVALID_METADATA_RESOURCE;
+
+ strm->stream_name = utf16le_dupz(disk_strm->name,
+ name_nbytes);
+ if (!strm->stream_name)
+ return WIMLIB_ERR_NOMEM;
+ } else {
+ strm->stream_name = (utf16lechar *)NO_STREAM_NAME;
+ }
+
+ strm->stream_type = STREAM_TYPE_UNKNOWN;
+
+ p += length;
+ }
+
+ inode->i_next_stream_id = inode->i_num_streams;
+
+ /* 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. */
+
+ if (unlikely(inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED))
+ assign_stream_types_encrypted(inode);
+ else
+ assign_stream_types_unencrypted(inode);
+
+ *offset_p += p - orig_p;
+ return 0;