2 * test_support.c - Supporting code for tests
6 * Copyright (C) 2015-2016 Eric Biggers
8 * This file is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU Lesser General Public License as published by the Free
10 * Software Foundation; either version 3 of the License, or (at your option) any
13 * This file is distributed in the hope that it will be useful, but WITHOUT
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this file; if not, see http://www.gnu.org/licenses/.
23 * This file contains specialized test code which is only compiled when the
24 * library is configured with --enable-test-support. The major features are:
26 * - Random directory tree generation
27 * - Directory tree comparison
34 #ifdef ENABLE_TEST_SUPPORT
40 #include "wimlib/endianness.h"
41 #include "wimlib/encoding.h"
42 #include "wimlib/metadata.h"
43 #include "wimlib/dentry.h"
44 #include "wimlib/inode.h"
45 #include "wimlib/reparse.h"
46 #include "wimlib/scan.h"
47 #include "wimlib/security_descriptor.h"
48 #include "wimlib/test_support.h"
50 /*----------------------------------------------------------------------------*
51 * File tree generation *
52 *----------------------------------------------------------------------------*/
54 struct generation_context {
55 struct scan_params *params;
56 struct wim_dentry *used_short_names[256];
63 static u64 state = 0x55DB93D0AB838771;
65 /* A simple linear congruential generator */
66 state = (state * 25214903917 + 11) & ((1ULL << 48) - 1);
73 return (rand32() & 1) != 0;
91 return ((u64)rand32() << 32) | rand32();
95 generate_random_timestamp(void)
97 /* When setting timestamps on Windows:
98 * - 0 is a special value meaning "not specified"
99 * - if the high bit is set you get STATUS_INVALID_PARAMETER */
100 return (1 + rand64()) & ~(1ULL << 63);
103 static const struct {
104 u8 num_subauthorities;
105 u64 identifier_authority;
106 u32 subauthorities[6];
108 { 1, 0, {0}}, /* NULL_SID */
109 { 1, 1, {0}}, /* WORLD_SID */
110 { 1, 2, {0}}, /* LOCAL_SID */
111 { 1, 3, {0}}, /* CREATOR_OWNER_SID */
112 { 1, 3, {1}}, /* CREATOR_GROUP_SID */
113 { 1, 3, {2}}, /* CREATOR_OWNER_SERVER_SID */
114 { 1, 3, {3}}, /* CREATOR_GROUP_SERVER_SID */
115 // { 0, 5, {}}, /* NT_AUTHORITY_SID */
116 { 1, 5, {1}}, /* DIALUP_SID */
117 { 1, 5, {2}}, /* NETWORK_SID */
118 { 1, 5, {3}}, /* BATCH_SID */
119 { 1, 5, {4}}, /* INTERACTIVE_SID */
120 { 1, 5, {6}}, /* SERVICE_SID */
121 { 1, 5, {7}}, /* ANONYMOUS_LOGON_SID */
122 { 1, 5, {8}}, /* PROXY_SID */
123 { 1, 5, {9}}, /* SERVER_LOGON_SID */
124 { 1, 5, {10}}, /* SELF_SID */
125 { 1, 5, {11}}, /* AUTHENTICATED_USER_SID */
126 { 1, 5, {12}}, /* RESTRICTED_CODE_SID */
127 { 1, 5, {13}}, /* TERMINAL_SERVER_SID */
128 { 1, 5, {18}}, /* NT AUTHORITY\SYSTEM */
129 { 1, 5, {19}}, /* NT AUTHORITY\LOCAL SERVICE */
130 { 1, 5, {20}}, /* NT AUTHORITY\NETWORK SERVICE */
131 { 5 ,80, {956008885, 3418522649, 1831038044, 1853292631, 2271478464}}, /* trusted installer */
132 { 2 ,5, {32, 544} } /* BUILTIN\ADMINISTRATORS */
135 /* Generate a SID and return its size in bytes. */
137 generate_random_sid(wimlib_SID *sid, struct generation_context *ctx)
145 r = (r >> 1) % ARRAY_LEN(common_sids);
147 sid->sub_authority_count = common_sids[r].num_subauthorities;
148 for (int i = 0; i < 6; i++) {
149 sid->identifier_authority[i] =
150 common_sids[r].identifier_authority >> (40 - i * 8);
152 for (int i = 0; i < common_sids[r].num_subauthorities; i++)
153 sid->sub_authority[i] = cpu_to_le32(common_sids[r].subauthorities[i]);
157 sid->sub_authority_count = 1 + ((r >> 1) % 15);
159 for (int i = 0; i < 6; i++)
160 sid->identifier_authority[i] = rand8();
162 for (int i = 0; i < sid->sub_authority_count; i++)
163 sid->sub_authority[i] = cpu_to_le32(rand32());
165 return (u8 *)&sid->sub_authority[sid->sub_authority_count] - (u8 *)sid;
168 /* Generate an ACL and return its size in bytes. */
170 generate_random_acl(wimlib_ACL *acl, bool dacl, struct generation_context *ctx)
175 ace_count = rand32() % 16;
179 acl->ace_count = cpu_to_le16(ace_count);
184 for (int i = 0; i < ace_count; i++) {
185 wimlib_ACCESS_ALLOWED_ACE *ace = (wimlib_ACCESS_ALLOWED_ACE *)p;
187 /* ACCESS_ALLOWED, ACCESS_DENIED, or SYSTEM_AUDIT; format is the
190 ace->hdr.type = rand32() % 2;
193 ace->hdr.flags = rand8();
194 ace->mask = cpu_to_le32(rand32() & 0x001F01FF);
196 p += offsetof(wimlib_ACCESS_ALLOWED_ACE, sid) +
197 generate_random_sid(&ace->sid, ctx);
198 ace->hdr.size = cpu_to_le16(p - (u8 *)ace);
201 acl->acl_size = cpu_to_le16(p - (u8 *)acl);
202 return p - (u8 *)acl;
205 /* Generate a security descriptor and return its size in bytes. */
207 generate_random_security_descriptor(void *_desc, struct generation_context *ctx)
209 wimlib_SECURITY_DESCRIPTOR_RELATIVE *desc = _desc;
215 control &= (wimlib_SE_DACL_AUTO_INHERITED |
216 wimlib_SE_SACL_AUTO_INHERITED);
218 control |= wimlib_SE_SELF_RELATIVE |
219 wimlib_SE_DACL_PRESENT |
220 wimlib_SE_SACL_PRESENT;
224 desc->control = cpu_to_le16(control);
226 p = (u8 *)(desc + 1);
228 desc->owner_offset = cpu_to_le32(p - (u8 *)desc);
229 p += generate_random_sid((wimlib_SID *)p, ctx);
231 desc->group_offset = cpu_to_le32(p - (u8 *)desc);
232 p += generate_random_sid((wimlib_SID *)p, ctx);
234 if ((control & wimlib_SE_DACL_PRESENT) && randbool()) {
235 desc->dacl_offset = cpu_to_le32(p - (u8 *)desc);
236 p += generate_random_acl((wimlib_ACL *)p, true, ctx);
238 desc->dacl_offset = cpu_to_le32(0);
241 if ((control & wimlib_SE_SACL_PRESENT) && randbool()) {
242 desc->sacl_offset = cpu_to_le32(p - (u8 *)desc);
243 p += generate_random_acl((wimlib_ACL *)p, false, ctx);
245 desc->sacl_offset = cpu_to_le32(0);
248 return p - (u8 *)desc;
252 set_random_metadata(struct wim_inode *inode, struct generation_context *ctx)
255 u32 attrib = (v & (FILE_ATTRIBUTE_READONLY |
256 FILE_ATTRIBUTE_HIDDEN |
257 FILE_ATTRIBUTE_SYSTEM |
258 FILE_ATTRIBUTE_ARCHIVE |
259 FILE_ATTRIBUTE_NOT_CONTENT_INDEXED |
260 FILE_ATTRIBUTE_COMPRESSED));
262 /* File attributes */
263 inode->i_attributes |= attrib;
266 inode->i_creation_time = generate_random_timestamp();
267 inode->i_last_access_time = generate_random_timestamp();
268 inode->i_last_write_time = generate_random_timestamp();
270 /* Security descriptor */
272 char desc[8192] _aligned_attribute(8);
275 size = generate_random_security_descriptor(desc, ctx);
277 wimlib_assert(size <= sizeof(desc));
279 inode->i_security_id = sd_set_add_sd(ctx->params->sd_set,
281 if (unlikely(inode->i_security_id < 0))
282 return WIMLIB_ERR_NOMEM;
289 /* Choose a random size for generated file data. We want to usually generate
290 * empty, small, or medium files, but occasionally generate large files. */
292 select_stream_size(struct generation_context *ctx)
294 if (ctx->metadata_only)
297 switch (rand32() % 2048) {
303 return rand32() % 64;
306 return rand32() % 4096;
309 return rand32() % 32768;
312 return rand32() % 262144;
315 return rand32() % 134217728;
319 /* Fill 'buffer' with 'size' bytes of "interesting" file data. */
321 generate_data(u8 *buffer, size_t size, struct generation_context *ctx)
324 size_t num_byte_fills = rand32() % 256;
326 memset(buffer, rand32() % 256, size);
328 for (size_t i = 0; i < num_byte_fills; i++) {
331 size_t count = ((double)size / (double)num_byte_fills) *
332 ((double)rand32() / 2e9);
333 size_t offset = rand32() & ~mask;
337 ((rand32()) & mask)) % size] = b;
341 if (rand32() % 4 == 0)
342 mask = (size_t)-1 << rand32() % 4;
345 if (rand32() % 8 == 0) {
346 double magnitude = rand32() % 128;
347 double scale = 1.0 / (1 + (rand32() % 256));
349 for (size_t i = 0; i < size; i++)
350 buffer[i] += (int)(magnitude * cos(i * scale));
355 add_stream(struct wim_inode *inode, struct generation_context *ctx,
356 int stream_type, const utf16lechar *stream_name,
357 void *buffer, size_t size)
359 struct blob_descriptor *blob = NULL;
360 struct wim_inode_stream *strm;
363 blob = new_blob_descriptor();
366 blob->attached_buffer = buffer;
367 blob->blob_location = BLOB_IN_ATTACHED_BUFFER;
371 strm = inode_add_stream(inode, stream_type, stream_name, blob);
375 prepare_unhashed_blob(blob, inode, strm->stream_id,
376 ctx->params->unhashed_blobs);
380 free_blob_descriptor(blob);
381 return WIMLIB_ERR_NOMEM;
385 set_random_reparse_point(struct wim_inode *inode, struct generation_context *ctx)
388 size_t rpdatalen = select_stream_size(ctx) % (REPARSE_DATA_MAX_SIZE + 1);
391 buffer = MALLOC(rpdatalen);
393 return WIMLIB_ERR_NOMEM;
394 generate_data(buffer, rpdatalen, ctx);
397 inode->i_attributes |= FILE_ATTRIBUTE_REPARSE_POINT;
398 inode->i_rp_reserved = rand16();
400 if (rpdatalen >= GUID_SIZE && randbool()) {
401 /* Non-Microsoft reparse tag (16-byte GUID required) */
403 guid[6] = (guid[6] & 0x0F) | 0x40;
404 guid[8] = (guid[8] & 0x3F) | 0x80;
405 inode->i_reparse_tag = 0x00000100;
407 /* Microsoft reparse tag */
408 inode->i_reparse_tag = 0x80000000;
411 return add_stream(inode, ctx, STREAM_TYPE_REPARSE_POINT, NO_STREAM_NAME,
416 add_random_data_stream(struct wim_inode *inode, struct generation_context *ctx,
417 const utf16lechar *stream_name)
422 size = select_stream_size(ctx);
424 buffer = MALLOC(size);
426 return WIMLIB_ERR_NOMEM;
427 generate_data(buffer, size, ctx);
430 return add_stream(inode, ctx, STREAM_TYPE_DATA, stream_name,
435 set_random_streams(struct wim_inode *inode, struct generation_context *ctx,
441 /* Reparse point (sometimes) */
442 if (reparse_ok && rand32() % 8 == 0) {
443 ret = set_random_reparse_point(inode, ctx);
448 /* Unnamed data stream (nondirectories only) */
449 if (!(inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY)) {
450 ret = add_random_data_stream(inode, ctx, NO_STREAM_NAME);
455 /* Named data streams (sometimes) */
458 utf16lechar stream_name[2] = {cpu_to_le16('a'), '\0'};
461 ret = add_random_data_stream(inode, ctx, stream_name);
464 stream_name[0] += cpu_to_le16(1);
472 generate_random_file_name(tchar name[], int max_len,
473 struct generation_context *ctx)
476 switch (rand32() % 8) {
479 length = 1 + (rand32() % 6);
484 /* medium-length name */
485 length = 7 + (rand32() % 8);
490 length = 15 + (rand32() % 15);
494 length = 30 + (rand32() % 90);
497 length = min(length, max_len);
498 for (int i = 0; i < length; i++)
499 name[i] = 'a' + (rand32() % 26);
505 select_inode_number(struct generation_context *ctx)
507 const struct wim_inode_table *table = ctx->params->inode_table;
508 const struct hlist_head *head;
509 const struct wim_inode *inode;
511 head = &table->array[rand32() % table->capacity];
512 hlist_for_each_entry(inode, head, i_hlist_node)
520 select_num_children(u32 depth, struct generation_context *ctx)
522 const double b = 1.01230;
523 u32 r = rand32() % 500;
524 return ((pow(b, pow(b, r)) - 1) / pow(depth, 1.5)) +
525 (2 - exp(0.04/depth));
529 is_name_forbidden_in_win32_namespace(const utf16lechar *name)
531 static const utf16lechar forbidden_names[][5] = {
532 { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('N'), },
533 { cpu_to_le16('P'), cpu_to_le16('R'), cpu_to_le16('N'), },
534 { cpu_to_le16('A'), cpu_to_le16('U'), cpu_to_le16('X'), },
535 { cpu_to_le16('N'), cpu_to_le16('U'), cpu_to_le16('L'), },
536 { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M'), cpu_to_le16('1'), },
537 { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M'), cpu_to_le16('2'), },
538 { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M'), cpu_to_le16('3'), },
539 { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M'), cpu_to_le16('4'), },
540 { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M'), cpu_to_le16('5'), },
541 { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M'), cpu_to_le16('6'), },
542 { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M'), cpu_to_le16('7'), },
543 { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M'), cpu_to_le16('8'), },
544 { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M'), cpu_to_le16('9'), },
545 { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), cpu_to_le16('1'), },
546 { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), cpu_to_le16('2'), },
547 { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), cpu_to_le16('3'), },
548 { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), cpu_to_le16('4'), },
549 { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), cpu_to_le16('5'), },
550 { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), cpu_to_le16('6'), },
551 { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), cpu_to_le16('7'), },
552 { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), cpu_to_le16('8'), },
553 { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), cpu_to_le16('9'), },
559 for (size_t i = 0; i < ARRAY_LEN(forbidden_names); i++)
560 if (!cmp_utf16le_strings_z(forbidden_names[i], name, true))
567 set_random_short_name(struct wim_dentry *dir, struct wim_dentry *child,
568 struct generation_context *ctx)
572 const utf16lechar *short_name;
574 struct wim_dentry **bucket;
576 /* If the long name is not allowed in the Win32 namespace, then it
577 * cannot be assigned a corresponding short name. */
578 if (is_name_forbidden_in_win32_namespace(child->d_name))
582 /* Don't select a short name that is already used by a long name within
583 * the same directory. */
585 int len = generate_random_file_name(name, 12, ctx);
587 /* Legal short names on Windows take one of the following forms:
589 * - 1 to 8 characters
590 * - 1 to 8 characters, then a dot, then 1 to 3 characters */
598 } while (get_dentry_child_with_name(dir, name,
599 WIMLIB_CASE_PLATFORM_DEFAULT));
602 /* Don't select a short name that is already used by another short name
603 * within the same directory. */
605 for (const tchar *p = name; *p; p++)
606 hash = (hash * 31) + totlower(*p);
607 ret = tstr_get_utf16le(name, &short_name);
610 FREE(child->d_short_name);
611 child->d_short_name = utf16le_dup(short_name);
612 child->d_short_name_nbytes = utf16le_len_bytes(short_name);
613 tstr_put_utf16le(short_name);
615 if (!child->d_short_name)
616 return WIMLIB_ERR_NOMEM;
618 bucket = &ctx->used_short_names[hash % ARRAY_LEN(ctx->used_short_names)];
620 for (struct wim_dentry *d = *bucket; d != NULL;
621 d = d->d_next_extraction_alias)
622 if (!cmp_utf16le_strings_z(child->d_short_name,
623 d->d_short_name, true))
626 if (is_name_forbidden_in_win32_namespace(child->d_short_name))
629 child->d_next_extraction_alias = *bucket;
635 inode_has_short_name(const struct wim_inode *inode)
637 const struct wim_dentry *dentry;
639 inode_for_each_dentry(dentry, inode)
640 if (dentry_has_short_name(dentry))
647 generate_dentry_tree_recursive(struct wim_dentry *dir, u32 depth,
648 struct generation_context *ctx)
650 u32 num_children = select_num_children(depth, ctx);
651 struct wim_dentry *child;
654 memset(ctx->used_short_names, 0, sizeof(ctx->used_short_names));
656 /* Generate 'num_children' dentries within 'dir'. Some may be
657 * directories themselves. */
659 for (u32 i = 0; i < num_children; i++) {
661 /* Generate the next child dentry. */
664 struct wim_dentry *duplicate;
665 struct wim_inode *inode;
669 /* Choose a long filename that is unique within the directory.*/
671 generate_random_file_name(name, 128, ctx);
672 } while (get_dentry_child_with_name(dir, name,
673 WIMLIB_CASE_PLATFORM_DEFAULT));
675 /* Decide whether to create a directory or not.
676 * If not a directory, also decide on the inode number (i.e. we
677 * may generate a "hard link" to an existing file). */
678 is_directory = ((rand32() % 16) <= 6);
682 ino = select_inode_number(ctx);
684 /* Create the dentry and add it to the directory. */
685 ret = inode_table_new_dentry(ctx->params->inode_table, name,
686 ino, 0, is_directory, &child);
690 duplicate = dentry_add_child(dir, child);
691 wimlib_assert(!duplicate);
693 inode = child->d_inode;
695 if (inode->i_nlink > 1) /* Existing inode? */
698 /* New inode; set attributes, metadata, and data. */
701 inode->i_attributes |= FILE_ATTRIBUTE_DIRECTORY;
703 ret = set_random_metadata(inode, ctx);
707 ret = set_random_streams(inode, ctx, true);
711 /* Recurse if it's a directory. */
713 !(inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT))
715 ret = generate_dentry_tree_recursive(child, depth + 1,
722 for_dentry_child(child, dir) {
723 /* sometimes generate a unique short name */
724 if (randbool() && !inode_has_short_name(child->d_inode)) {
725 ret = set_random_short_name(dir, child, ctx);
735 generate_dentry_tree(struct wim_dentry **root_ret, const tchar *_ignored,
736 struct scan_params *params)
739 struct wim_dentry *root = NULL;
740 struct generation_context ctx = {
744 ctx.metadata_only = ((rand32() % 8) != 0); /* usually metadata only */
746 ret = inode_table_new_dentry(params->inode_table, NULL, 0, 0, true, &root);
748 root->d_inode->i_attributes = FILE_ATTRIBUTE_DIRECTORY;
749 ret = set_random_metadata(root->d_inode, &ctx);
752 ret = set_random_streams(root->d_inode, &ctx, false);
754 ret = generate_dentry_tree_recursive(root, 1, &ctx);
758 free_dentry_tree(root, params->blob_table);
762 /*----------------------------------------------------------------------------*
763 * File tree comparison *
764 *----------------------------------------------------------------------------*/
766 #define INDEX_NODE_TO_DENTRY(node) \
767 ((node) ? avl_tree_entry((node), struct wim_dentry, d_index_node) : NULL)
769 static struct wim_dentry *
770 dentry_first_child(struct wim_dentry *dentry)
772 return INDEX_NODE_TO_DENTRY(
773 avl_tree_first_in_order(dentry->d_inode->i_children));
776 static struct wim_dentry *
777 dentry_next_sibling(struct wim_dentry *dentry)
779 return INDEX_NODE_TO_DENTRY(
780 avl_tree_next_in_order(&dentry->d_index_node));
784 * Verify that the dentries in the tree 'd1' exactly match the dentries in the
785 * tree 'd2', considering long and short filenames. In addition, set
786 * 'd_corresponding' of each dentry to point to the corresponding dentry in the
787 * other tree, and set 'i_corresponding' of each inode to point to the
788 * unverified corresponding inode in the other tree.
791 calc_corresponding_files_recursive(struct wim_dentry *d1, struct wim_dentry *d2,
794 struct wim_dentry *child1;
795 struct wim_dentry *child2;
798 /* Compare long filenames, case sensitively. */
799 if (cmp_utf16le_strings(d1->d_name, d1->d_name_nbytes / 2,
800 d2->d_name, d2->d_name_nbytes / 2,
803 ERROR("Filename mismatch; path1=\"%"TS"\", path2=\"%"TS"\"",
804 dentry_full_path(d1), dentry_full_path(d2));
805 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
808 /* Compare short filenames, case insensitively. */
809 if (!(d2->d_short_name_nbytes == 0 &&
810 (cmp_flags & WIMLIB_CMP_FLAG_SHORT_NAMES_NOT_PRESERVED)) &&
811 cmp_utf16le_strings(d1->d_short_name, d1->d_short_name_nbytes / 2,
812 d2->d_short_name, d2->d_short_name_nbytes / 2,
815 ERROR("Short name mismatch; path=\"%"TS"\"",
816 dentry_full_path(d1));
817 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
820 /* Match up the dentries */
821 d1->d_corresponding = d2;
822 d2->d_corresponding = d1;
824 /* Match up the inodes (may overwrite previous value) */
825 d1->d_inode->i_corresponding = d2->d_inode;
826 d2->d_inode->i_corresponding = d1->d_inode;
828 /* Process children */
829 child1 = dentry_first_child(d1);
830 child2 = dentry_first_child(d2);
831 while (child1 || child2) {
833 if (!child1 || !child2) {
834 ERROR("Child count mismatch; "
835 "path1=\"%"TS"\", path2=\"%"TS"\"",
836 dentry_full_path(d1), dentry_full_path(d2));
837 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
840 /* Recurse on this pair of children. */
841 ret = calc_corresponding_files_recursive(child1, child2,
846 /* Continue to the next pair of children. */
847 child1 = dentry_next_sibling(child1);
848 child2 = dentry_next_sibling(child2);
853 /* Perform sanity checks on an image's inodes. All assertions here should pass,
854 * even if the images being compared are different. */
856 assert_inodes_sane(const struct wim_image_metadata *imd)
858 const struct wim_inode *inode;
859 const struct wim_dentry *dentry;
862 image_for_each_inode(inode, imd) {
864 inode_for_each_dentry(dentry, inode) {
865 wimlib_assert(dentry->d_inode == inode);
868 wimlib_assert(link_count > 0);
869 wimlib_assert(link_count == inode->i_nlink);
870 wimlib_assert(inode->i_corresponding != NULL);
875 check_hard_link(struct wim_dentry *dentry, void *_ignore)
877 /* My inode is my corresponding dentry's inode's corresponding inode,
878 * and my inode's corresponding inode is my corresponding dentry's
880 const struct wim_inode *a = dentry->d_inode;
881 const struct wim_inode *b = dentry->d_corresponding->d_inode;
882 if (a == b->i_corresponding && a->i_corresponding == b)
884 ERROR("Hard link difference; path=%"TS"", dentry_full_path(dentry));
885 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
889 cmp_inodes(const struct wim_inode *inode1, const struct wim_inode *inode2,
890 const struct wim_image_metadata *imd1,
891 const struct wim_image_metadata *imd2, int cmp_flags)
893 const u32 attrib_diff = inode1->i_attributes ^ inode2->i_attributes;
894 bool reparse_point_should_preserved = true;
896 /* Compare attributes */
897 if (cmp_flags & WIMLIB_CMP_FLAG_ATTRIBUTES_NOT_PRESERVED) {
899 /* In this mode, we expect that most attributes are not
900 * preserved. However, FILE_ATTRIBUTE_DIRECTORY should always
902 if (attrib_diff & FILE_ATTRIBUTE_DIRECTORY)
903 goto attrib_mismatch;
905 /* We may also expect FILE_ATTRIBUTE_REPARSE_POINT to be
906 * preserved for symlinks. It also shouldn't be set if it
907 * wasn't set before. */
909 if ((cmp_flags & WIMLIB_CMP_FLAG_IMAGE2_SHOULD_HAVE_SYMLINKS) &&
910 inode_is_symlink(inode1))
911 reparse_point_should_preserved = true;
913 reparse_point_should_preserved = false;
915 if ((attrib_diff & FILE_ATTRIBUTE_REPARSE_POINT) &&
916 (reparse_point_should_preserved ||
917 (inode2->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)))
918 goto attrib_mismatch;
921 /* Most attributes should be preserved. */
923 /* Nothing other than COMPRESSED and NORMAL should have changed.
925 if (attrib_diff & ~(FILE_ATTRIBUTE_COMPRESSED |
926 FILE_ATTRIBUTE_NORMAL))
927 goto attrib_mismatch;
929 /* COMPRESSED shouldn't have changed unless specifically
931 if ((attrib_diff & FILE_ATTRIBUTE_COMPRESSED) &&
932 !(cmp_flags & WIMLIB_CMP_FLAG_COMPRESSION_NOT_PRESERVED))
933 goto attrib_mismatch;
935 /* We allow NORMAL to change, but not if the file ended up with
936 * other attributes set as well. */
937 if ((attrib_diff & FILE_ATTRIBUTE_NORMAL) &&
938 (inode2->i_attributes & ~FILE_ATTRIBUTE_NORMAL))
939 goto attrib_mismatch;
942 /* Compare security descriptors */
943 if (inode_has_security_descriptor(inode1)) {
944 if (inode_has_security_descriptor(inode2)) {
945 const void *desc1 = imd1->security_data->descriptors[inode1->i_security_id];
946 const void *desc2 = imd2->security_data->descriptors[inode2->i_security_id];
947 size_t size1 = imd1->security_data->sizes[inode1->i_security_id];
948 size_t size2 = imd2->security_data->sizes[inode2->i_security_id];
950 if (size1 != size2 || memcmp(desc1, desc2, size1)) {
951 ERROR("Security descriptor of %"TS" differs!",
952 inode_any_full_path(inode1));
953 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
955 } else if (!(cmp_flags & WIMLIB_CMP_FLAG_SECURITY_NOT_PRESERVED)) {
956 ERROR("%"TS" has a security descriptor in the first image but "
957 "not in the second image!", inode_any_full_path(inode1));
958 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
960 } else if (inode_has_security_descriptor(inode2)) {
961 /* okay --- consider it acceptable if a default security
962 * descriptor was assigned */
963 /*ERROR("%"TS" has a security descriptor in the second image but "*/
964 /*"not in the first image!", inode_any_full_path(inode1));*/
965 /*return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;*/
968 /* Compare streams */
969 for (unsigned i = 0; i < inode1->i_num_streams; i++) {
970 const struct wim_inode_stream *strm1 = &inode1->i_streams[i];
971 const struct wim_inode_stream *strm2;
973 if (strm1->stream_type == STREAM_TYPE_REPARSE_POINT &&
974 !reparse_point_should_preserved)
977 if (strm1->stream_type == STREAM_TYPE_UNKNOWN)
980 /* Get the corresponding stream from the second file */
981 strm2 = inode_get_stream(inode2, strm1->stream_type, strm1->stream_name);
984 /* Corresponding stream not found */
985 if (stream_is_named(strm1) &&
986 (cmp_flags & WIMLIB_CMP_FLAG_ADS_NOT_PRESERVED))
988 ERROR("Stream of %"TS" is missing in second image; "
989 "type %d, named=%d, empty=%d",
990 inode_any_full_path(inode1),
992 stream_is_named(strm1),
993 is_zero_hash(stream_hash(strm1)));
994 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
997 if (!hashes_equal(stream_hash(strm1), stream_hash(strm2))) {
998 ERROR("Stream of %"TS" differs; type %d",
999 inode_any_full_path(inode1), strm1->stream_type);
1000 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1007 ERROR("Attribute mismatch; %"TS" has attributes 0x%08"PRIx32" "
1008 "in first image but attributes 0x%08"PRIx32" in second image",
1009 inode_any_full_path(inode1), inode1->i_attributes,
1010 inode2->i_attributes);
1011 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1015 cmp_images(const struct wim_image_metadata *imd1,
1016 const struct wim_image_metadata *imd2, int cmp_flags)
1018 struct wim_dentry *root1 = imd1->root_dentry;
1019 struct wim_dentry *root2 = imd2->root_dentry;
1020 const struct wim_inode *inode;
1023 ret = calc_corresponding_files_recursive(root1, root2, cmp_flags);
1027 /* Verify that the hard links match up between the two images. */
1028 assert_inodes_sane(imd1);
1029 assert_inodes_sane(imd2);
1030 ret = for_dentry_in_tree(root1, check_hard_link, NULL);
1034 /* Compare corresponding inodes. */
1035 image_for_each_inode(inode, imd1) {
1036 ret = cmp_inodes(inode, inode->i_corresponding,
1037 imd1, imd2, cmp_flags);
1046 load_image(WIMStruct *wim, int image, struct wim_image_metadata **imd_ret)
1048 int ret = select_wim_image(wim, image);
1050 *imd_ret = wim_get_current_image_metadata(wim);
1051 mark_image_dirty(*imd_ret);
1057 wimlib_compare_images(WIMStruct *wim1, int image1,
1058 WIMStruct *wim2, int image2, int cmp_flags)
1061 struct wim_image_metadata *imd1, *imd2;
1063 ret = load_image(wim1, image1, &imd1);
1065 ret = load_image(wim2, image2, &imd2);
1067 ret = cmp_images(imd1, imd2, cmp_flags);
1071 #endif /* ENABLE_TEST_SUPPORT */