+/*
+ * Write the stream references for a WIM dentry. To be compatible with DISM, we
+ * follow the below rules:
+ *
+ * 1. If the file has FILE_ATTRIBUTE_ENCRYPTED, then only the EFSRPC_RAW_DATA
+ * stream is stored. Otherwise, the streams that are stored are:
+ * - Reparse stream if the file has FILE_ATTRIBUTE_REPARSE_POINT
+ * - Unnamed data stream if the file doesn't have FILE_ATTRIBUTE_DIRECTORY
+ * - Named data streams
+ *
+ * 2. If only one stream is being stored and it is the EFSRPC_RAW_DATA, unnamed
+ * data, or reparse stream, then its hash goes in main_hash, and no extra
+ * stream entries are stored. Otherwise, *all* streams go in the extra
+ * stream entries, and main_hash is left zeroed!
+ *
+ * 3. If both the reparse stream and unnamed data stream are being stored, then
+ * the reparse stream comes first.
+ *
+ * 4. The unnamed stream(s) come before the named stream(s). (Actually, DISM
+ * puts the named streams between the first and second unnamed streams, but
+ * this is incompatible with itself... Tested with DISM 10.0.20348.681.)
+ *
+ * wimlib v1.14.1 and earlier behaved slightly differently for directories.
+ * First, wimlib always put the hash of the reparse stream in an extra stream
+ * entry, never in main_hash. This difference vs. DISM went unnoticed for a
+ * long time, but eventually it was found that it broke the Windows 8 setup
+ * wizard. Second, when a directory had any extra streams, wimlib created an
+ * extra stream entry to represent the (empty) unnamed data stream. However,
+ * DISM now rejects that (though I think it used to accept it). There isn't
+ * really any such thing as "unnamed data stream" for a directory.
+ *
+ * Keep this in sync with dentry_out_total_length()!
+ */
+static u8 *
+write_dentry_streams(const struct wim_inode *inode,
+ struct wim_dentry_on_disk *disk_dentry, u8 *p)
+{
+ const u8 *unnamed_data_stream_hash = zero_hash;
+ const u8 *reparse_stream_hash = zero_hash;
+ const u8 *efsrpc_stream_hash = zero_hash;
+ const u8 *unnamed_stream_hashes[2] = { zero_hash };
+ unsigned num_unnamed_streams = 0;
+ unsigned num_named_streams = 0;
+
+ for (unsigned i = 0; i < inode->i_num_streams; i++) {
+ const struct wim_inode_stream *strm = &inode->i_streams[i];
+
+ switch (strm->stream_type) {
+ case STREAM_TYPE_DATA:
+ if (stream_is_named(strm))
+ num_named_streams++;
+ else
+ unnamed_data_stream_hash = stream_hash(strm);
+ break;
+ case STREAM_TYPE_REPARSE_POINT:
+ reparse_stream_hash = stream_hash(strm);
+ break;
+ case STREAM_TYPE_EFSRPC_RAW_DATA:
+ efsrpc_stream_hash = stream_hash(strm);
+ break;
+ }
+ }
+
+ if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED) {
+ unnamed_stream_hashes[num_unnamed_streams++] = efsrpc_stream_hash;
+ num_named_streams = 0;
+ } else {
+ if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)
+ unnamed_stream_hashes[num_unnamed_streams++] = reparse_stream_hash;
+ if (!(inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY))
+ unnamed_stream_hashes[num_unnamed_streams++] = unnamed_data_stream_hash;
+ }
+
+ if (num_unnamed_streams <= 1 && num_named_streams == 0) {
+ /* No extra stream entries are needed. */
+ copy_hash(disk_dentry->main_hash, unnamed_stream_hashes[0]);
+ disk_dentry->num_extra_streams = 0;
+ return p;
+ }
+
+ /* Else, all streams go in extra stream entries. */
+ copy_hash(disk_dentry->main_hash, zero_hash);
+ wimlib_assert(num_unnamed_streams + num_named_streams <= 0xFFFF);
+ disk_dentry->num_extra_streams = cpu_to_le16(num_unnamed_streams +
+ num_named_streams);
+ for (unsigned i = 0; i < num_unnamed_streams; i++)
+ p = write_extra_stream_entry(p, NO_STREAM_NAME,
+ unnamed_stream_hashes[i]);
+ for (unsigned i = 0; i < inode->i_num_streams; i++) {
+ const struct wim_inode_stream *strm = &inode->i_streams[i];
+
+ if (stream_is_named_data_stream(strm)) {
+ p = write_extra_stream_entry(p, strm->stream_name,
+ stream_hash(strm));
+ }
+ }
+ return p;
+}
+