2 * test_support.c - Supporting code for tests
6 * Copyright (C) 2015-2018 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
43 #include "wimlib/endianness.h"
44 #include "wimlib/encoding.h"
45 #include "wimlib/metadata.h"
46 #include "wimlib/dentry.h"
47 #include "wimlib/inode.h"
48 #include "wimlib/object_id.h"
49 #include "wimlib/reparse.h"
50 #include "wimlib/scan.h"
51 #include "wimlib/security_descriptor.h"
52 #include "wimlib/test_support.h"
53 #include "wimlib/unix_data.h"
54 #include "wimlib/xattr.h"
56 /*----------------------------------------------------------------------------*
57 * File tree generation *
58 *----------------------------------------------------------------------------*/
60 struct generation_context {
61 struct scan_params *params;
62 struct wim_dentry *used_short_names[256];
69 static u64 state = 0x55DB93D0AB838771;
71 /* A simple linear congruential generator */
72 state = (state * 25214903917 + 11) & ((1ULL << 48) - 1);
79 return (rand32() & 1) != 0;
97 return ((u64)rand32() << 32) | rand32();
101 generate_random_timestamp(void)
103 /* When setting timestamps on Windows:
104 * - 0 is a special value meaning "not specified"
105 * - if the high bit is set you get STATUS_INVALID_PARAMETER */
106 return (1 + rand64()) & ~(1ULL << 63);
110 is_valid_windows_filename_char(utf16lechar c)
112 return le16_to_cpu(c) > 31 &&
113 c != cpu_to_le16('/') &&
114 c != cpu_to_le16('<') &&
115 c != cpu_to_le16('>') &&
116 c != cpu_to_le16(':') &&
117 c != cpu_to_le16('"') &&
118 c != cpu_to_le16('/' ) &&
119 c != cpu_to_le16('\\') &&
120 c != cpu_to_le16('|') &&
121 c != cpu_to_le16('?') &&
122 c != cpu_to_le16('*');
125 /* Is the character valid in a filename on the current platform? */
127 is_valid_filename_char(utf16lechar c)
130 return is_valid_windows_filename_char(c);
132 return c != cpu_to_le16('\0') && c != cpu_to_le16('/');
136 /* Generate a random filename and return its length. */
138 generate_random_filename(utf16lechar name[], int max_len,
139 struct generation_context *ctx)
143 /* Choose the length of the name. */
144 switch (rand32() % 8) {
147 len = 1 + (rand32() % 6);
152 /* medium-length name */
153 len = 7 + (rand32() % 8);
158 len = 15 + (rand32() % 15);
162 len = 30 + (rand32() % 90);
165 len = min(len, max_len);
168 /* Generate the characters in the name. */
169 for (int i = 0; i < len; i++) {
172 } while (!is_valid_filename_char(name[i]));
175 /* Add a null terminator. */
176 name[len] = cpu_to_le16('\0');
178 /* Don't generate . and .. */
179 if (name[0] == cpu_to_le16('.') &&
180 (len == 1 || (len == 2 && name[1] == cpu_to_le16('.'))))
186 /* The set of characters which are valid in short filenames. */
187 static const char valid_short_name_chars[] = {
188 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
189 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
190 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
191 '!', '#', '$', '%', '&', '\'', '(', ')', '-', '@', '^', '_', '`', '{',
193 /* Note: Windows does not allow space and 128-255 in short filenames
194 * (tested on both NTFS and FAT). */
198 generate_short_name_component(utf16lechar p[], int len)
200 for (int i = 0; i < len; i++) {
201 char c = valid_short_name_chars[rand32() %
202 ARRAY_LEN(valid_short_name_chars)];
203 p[i] = cpu_to_le16(c);
208 /* Generate a random short (8.3) filename and return its length.
209 * The @name array must have length >= 13 (8 + 1 + 3 + 1). */
211 generate_random_short_name(utf16lechar name[], struct generation_context *ctx)
214 * Legal short names on Windows consist of 1 to 8 characters, optionally
215 * followed by a dot then 1 to 3 more characters. Only certain
216 * characters are allowed.
218 int base_len = 1 + (rand32() % 8);
219 int ext_len = rand32() % 4;
222 base_len = generate_short_name_component(name, base_len);
225 name[base_len] = cpu_to_le16('.');
226 ext_len = generate_short_name_component(&name[base_len + 1],
228 total_len = base_len + 1 + ext_len;
230 total_len = base_len;
232 name[total_len] = cpu_to_le16('\0');
237 static const struct {
238 u8 num_subauthorities;
239 u64 identifier_authority;
240 u32 subauthorities[6];
242 { 1, 0, {0}}, /* NULL_SID */
243 { 1, 1, {0}}, /* WORLD_SID */
244 { 1, 2, {0}}, /* LOCAL_SID */
245 { 1, 3, {0}}, /* CREATOR_OWNER_SID */
246 { 1, 3, {1}}, /* CREATOR_GROUP_SID */
247 { 1, 3, {2}}, /* CREATOR_OWNER_SERVER_SID */
248 { 1, 3, {3}}, /* CREATOR_GROUP_SERVER_SID */
249 // { 0, 5, {}}, /* NT_AUTHORITY_SID */
250 { 1, 5, {1}}, /* DIALUP_SID */
251 { 1, 5, {2}}, /* NETWORK_SID */
252 { 1, 5, {3}}, /* BATCH_SID */
253 { 1, 5, {4}}, /* INTERACTIVE_SID */
254 { 1, 5, {6}}, /* SERVICE_SID */
255 { 1, 5, {7}}, /* ANONYMOUS_LOGON_SID */
256 { 1, 5, {8}}, /* PROXY_SID */
257 { 1, 5, {9}}, /* SERVER_LOGON_SID */
258 { 1, 5, {10}}, /* SELF_SID */
259 { 1, 5, {11}}, /* AUTHENTICATED_USER_SID */
260 { 1, 5, {12}}, /* RESTRICTED_CODE_SID */
261 { 1, 5, {13}}, /* TERMINAL_SERVER_SID */
262 { 1, 5, {18}}, /* NT AUTHORITY\SYSTEM */
263 { 1, 5, {19}}, /* NT AUTHORITY\LOCAL SERVICE */
264 { 1, 5, {20}}, /* NT AUTHORITY\NETWORK SERVICE */
265 { 5 ,80, {956008885, 3418522649, 1831038044, 1853292631, 2271478464}}, /* trusted installer */
266 { 2 ,5, {32, 544} } /* BUILTIN\ADMINISTRATORS */
269 /* Generate a SID and return its size in bytes. */
271 generate_random_sid(wimlib_SID *sid, struct generation_context *ctx)
279 r = (r >> 1) % ARRAY_LEN(common_sids);
281 sid->sub_authority_count = common_sids[r].num_subauthorities;
282 for (int i = 0; i < 6; i++) {
283 sid->identifier_authority[i] =
284 common_sids[r].identifier_authority >> (40 - i * 8);
286 for (int i = 0; i < common_sids[r].num_subauthorities; i++)
287 sid->sub_authority[i] = cpu_to_le32(common_sids[r].subauthorities[i]);
291 sid->sub_authority_count = 1 + ((r >> 1) % 15);
293 for (int i = 0; i < 6; i++)
294 sid->identifier_authority[i] = rand8();
296 for (int i = 0; i < sid->sub_authority_count; i++)
297 sid->sub_authority[i] = cpu_to_le32(rand32());
299 return (u8 *)&sid->sub_authority[sid->sub_authority_count] - (u8 *)sid;
302 /* Generate an ACL and return its size in bytes. */
304 generate_random_acl(wimlib_ACL *acl, bool dacl, struct generation_context *ctx)
309 ace_count = rand32() % 16;
313 acl->ace_count = cpu_to_le16(ace_count);
318 for (int i = 0; i < ace_count; i++) {
319 wimlib_ACCESS_ALLOWED_ACE *ace = (wimlib_ACCESS_ALLOWED_ACE *)p;
321 /* ACCESS_ALLOWED, ACCESS_DENIED, or SYSTEM_AUDIT; format is the
324 ace->hdr.type = rand32() % 2;
327 ace->hdr.flags = rand8();
328 ace->mask = cpu_to_le32(rand32() & 0x001F01FF);
330 p += offsetof(wimlib_ACCESS_ALLOWED_ACE, sid) +
331 generate_random_sid(&ace->sid, ctx);
332 ace->hdr.size = cpu_to_le16(p - (u8 *)ace);
335 acl->acl_size = cpu_to_le16(p - (u8 *)acl);
336 return p - (u8 *)acl;
339 /* Generate a security descriptor and return its size in bytes. */
341 generate_random_security_descriptor(void *_desc, struct generation_context *ctx)
343 wimlib_SECURITY_DESCRIPTOR_RELATIVE *desc = _desc;
349 control &= (wimlib_SE_DACL_AUTO_INHERITED |
350 wimlib_SE_SACL_AUTO_INHERITED);
352 control |= wimlib_SE_SELF_RELATIVE |
353 wimlib_SE_DACL_PRESENT |
354 wimlib_SE_SACL_PRESENT;
358 desc->control = cpu_to_le16(control);
360 p = (u8 *)(desc + 1);
362 desc->owner_offset = cpu_to_le32(p - (u8 *)desc);
363 p += generate_random_sid((wimlib_SID *)p, ctx);
365 desc->group_offset = cpu_to_le32(p - (u8 *)desc);
366 p += generate_random_sid((wimlib_SID *)p, ctx);
368 if ((control & wimlib_SE_DACL_PRESENT) && randbool()) {
369 desc->dacl_offset = cpu_to_le32(p - (u8 *)desc);
370 p += generate_random_acl((wimlib_ACL *)p, true, ctx);
372 desc->dacl_offset = cpu_to_le32(0);
375 if ((control & wimlib_SE_SACL_PRESENT) && randbool()) {
376 desc->sacl_offset = cpu_to_le32(p - (u8 *)desc);
377 p += generate_random_acl((wimlib_ACL *)p, false, ctx);
379 desc->sacl_offset = cpu_to_le32(0);
382 return p - (u8 *)desc;
391 return (getuid() == 0);
421 # define S_IFLNK 0120000
424 # define S_IFSOCK 0140000
429 set_random_unix_metadata(struct wim_inode *inode)
431 struct wimlib_unix_data dat;
433 dat.uid = generate_uid();
434 dat.gid = generate_gid();
435 if (inode_is_symlink(inode))
436 dat.mode = S_IFLNK | 0777;
437 else if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY)
438 dat.mode = S_IFDIR | 0700 | (rand32() % 07777);
439 else if (is_zero_hash(inode_get_hash_of_unnamed_data_stream(inode)) &&
440 randbool() && am_root())
442 dat.mode = rand32() % 07777;
443 switch (rand32() % 4) {
449 dat.rdev = 261; /* /dev/zero */
453 dat.rdev = 261; /* /dev/zero */
456 dat.mode |= S_IFSOCK;
460 dat.mode = S_IFREG | 0400 | (rand32() % 07777);
464 if (!inode_set_unix_data(inode, &dat, UNIX_DATA_ALL))
465 return WIMLIB_ERR_NOMEM;
470 static noinline_for_stack int
471 set_random_xattrs(struct wim_inode *inode)
473 int num_xattrs = 1 + rand32() % 16;
475 struct wim_xattr_entry *entry = (void *)entries;
477 struct wimlib_unix_data unix_data;
478 const char *prefix = "user.";
479 static const char capability_name[] = "security.capability";
480 bool generated_capability_xattr = false;
483 * On Linux, xattrs in the "user" namespace are only permitted on
484 * regular files and directories. For other types of files we can use
485 * the "trusted" namespace, but this requires root.
487 if (inode_is_symlink(inode) ||
488 (inode_get_unix_data(inode, &unix_data) &&
489 !S_ISREG(unix_data.mode) && !S_ISDIR(unix_data.mode)))
496 for (int i = 0; i < num_xattrs; i++) {
497 int value_len = rand32() % 64;
500 entry->value_len = cpu_to_le16(value_len);
503 if (rand32() % 16 == 0 && am_root() &&
504 !generated_capability_xattr) {
505 int name_len = sizeof(capability_name) - 1;
506 entry->name_len = name_len;
507 p = mempcpy(entry->name, capability_name, name_len + 1);
508 generated_capability_xattr = true;
510 int name_len = 1 + rand32() % 64;
512 entry->name_len = strlen(prefix) + name_len;
513 p = mempcpy(entry->name, prefix, strlen(prefix));
515 for (int j = 1; j < name_len; j++) {
518 } while (*p == '\0');
523 for (int j = 0; j < value_len; j++)
529 entries_size = (char *)entry - entries;
530 wimlib_assert(entries_size > 0 && entries_size <= sizeof(entries));
532 if (!inode_set_xattrs(inode, entries, entries_size))
533 return WIMLIB_ERR_NOMEM;
539 set_random_metadata(struct wim_inode *inode, struct generation_context *ctx)
541 u32 attrib = (rand32() & (FILE_ATTRIBUTE_READONLY |
542 FILE_ATTRIBUTE_HIDDEN |
543 FILE_ATTRIBUTE_SYSTEM |
544 FILE_ATTRIBUTE_ARCHIVE |
545 FILE_ATTRIBUTE_NOT_CONTENT_INDEXED |
546 FILE_ATTRIBUTE_COMPRESSED |
547 FILE_ATTRIBUTE_SPARSE_FILE));
549 /* File attributes */
550 inode->i_attributes |= attrib;
553 inode->i_creation_time = generate_random_timestamp();
554 inode->i_last_access_time = generate_random_timestamp();
555 inode->i_last_write_time = generate_random_timestamp();
557 /* Security descriptor */
559 char desc[8192] _aligned_attribute(8);
562 size = generate_random_security_descriptor(desc, ctx);
564 wimlib_assert(size <= sizeof(desc));
566 inode->i_security_id = sd_set_add_sd(ctx->params->sd_set,
568 if (unlikely(inode->i_security_id < 0))
569 return WIMLIB_ERR_NOMEM;
573 if (rand32() % 32 == 0) {
574 struct wimlib_object_id object_id;
576 for (int i = 0; i < sizeof(object_id); i++)
577 *((u8 *)&object_id + i) = rand8();
578 if (!inode_set_object_id(inode, &object_id, sizeof(object_id)))
579 return WIMLIB_ERR_NOMEM;
582 /* Standard UNIX permissions and special files */
583 if (rand32() % 16 == 0) {
584 int ret = set_random_unix_metadata(inode);
589 /* Extended attributes */
590 if (rand32() % 32 == 0) {
591 int ret = set_random_xattrs(inode);
600 /* Choose a random size for generated file data. We want to usually generate
601 * empty, small, or medium files, but occasionally generate large files. */
603 select_stream_size(struct generation_context *ctx)
605 if (ctx->metadata_only)
608 switch (rand32() % 2048) {
614 return rand32() % 64;
617 return rand32() % 4096;
620 return rand32() % 32768;
623 return rand32() % 262144;
626 return rand32() % 134217728;
630 /* Fill 'buffer' with 'size' bytes of "interesting" file data. */
632 generate_data(u8 *buffer, size_t size, struct generation_context *ctx)
635 size_t num_byte_fills = rand32() % 256;
640 /* Start by initializing to a random byte */
641 memset(buffer, rand32() % 256, size);
643 /* Add some random bytes in some random places */
644 for (size_t i = 0; i < num_byte_fills; i++) {
647 size_t count = ((double)size / (double)num_byte_fills) *
648 ((double)rand32() / 2e9);
649 size_t offset = rand32() & ~mask;
653 ((rand32()) & mask)) % size] = b;
657 if (rand32() % 4 == 0)
658 mask = (size_t)-1 << rand32() % 4;
661 /* Sometimes add a wave pattern */
662 if (rand32() % 8 == 0) {
663 double magnitude = rand32() % 128;
664 double scale = 1.0 / (1 + (rand32() % 256));
666 for (size_t i = 0; i < size; i++)
667 buffer[i] += (int)(magnitude * cos(i * scale));
670 /* Sometimes add some zero regions (holes) */
671 if (rand32() % 4 == 0) {
672 size_t num_holes = 1 + (rand32() % 16);
673 for (size_t i = 0; i < num_holes; i++) {
674 size_t hole_offset = rand32() % size;
675 size_t hole_len = min(size - hole_offset,
676 size / (1 + (rand32() % 16)));
677 memset(&buffer[hole_offset], 0, hole_len);
682 static noinline_for_stack int
683 set_random_reparse_point(struct wim_inode *inode, struct generation_context *ctx)
685 struct reparse_buffer_disk rpbuf;
688 inode->i_attributes |= FILE_ATTRIBUTE_REPARSE_POINT;
693 utf16lechar *targets = (utf16lechar *)rpbuf.link.symlink.data;
695 inode->i_reparse_tag = WIM_IO_REPARSE_TAG_SYMLINK;
697 target_nchars = generate_random_filename(targets, 255, ctx);
699 rpbuf.link.substitute_name_offset = cpu_to_le16(0);
700 rpbuf.link.substitute_name_nbytes = cpu_to_le16(2*target_nchars);
701 rpbuf.link.print_name_offset = cpu_to_le16(2*(target_nchars + 1));
702 rpbuf.link.print_name_nbytes = cpu_to_le16(2*target_nchars);
703 targets[target_nchars] = cpu_to_le16(0);
704 memcpy(&targets[target_nchars + 1], targets, 2*target_nchars);
705 targets[target_nchars + 1 + target_nchars] = cpu_to_le16(0);
707 rpbuf.link.symlink.flags = cpu_to_le32(SYMBOLIC_LINK_RELATIVE);
708 rpdatalen = ((u8 *)targets - rpbuf.rpdata) +
709 2*(target_nchars + 1 + target_nchars + 1);
711 rpdatalen = select_stream_size(ctx) % REPARSE_DATA_MAX_SIZE;
712 generate_data(rpbuf.rpdata, rpdatalen, ctx);
714 if (rpdatalen >= GUID_SIZE && randbool()) {
715 /* Non-Microsoft reparse tag (16-byte GUID required) */
716 u8 *guid = rpbuf.rpdata;
717 guid[6] = (guid[6] & 0x0F) | 0x40;
718 guid[8] = (guid[8] & 0x3F) | 0x80;
719 inode->i_reparse_tag = 0x00000100;
721 /* Microsoft reparse tag */
722 inode->i_reparse_tag = 0x80000000;
724 inode->i_rp_reserved = rand16();
727 wimlib_assert(rpdatalen < REPARSE_DATA_MAX_SIZE);
729 if (!inode_add_stream_with_data(inode, STREAM_TYPE_REPARSE_POINT,
730 NO_STREAM_NAME, rpbuf.rpdata,
731 rpdatalen, ctx->params->blob_table))
732 return WIMLIB_ERR_NOMEM;
738 add_random_data_stream(struct wim_inode *inode, struct generation_context *ctx,
739 const utf16lechar *stream_name)
745 size = select_stream_size(ctx);
747 buffer = MALLOC(size);
749 return WIMLIB_ERR_NOMEM;
750 generate_data(buffer, size, ctx);
754 if (!inode_add_stream_with_data(inode, STREAM_TYPE_DATA, stream_name,
755 buffer, size, ctx->params->blob_table))
756 ret = WIMLIB_ERR_NOMEM;
762 set_random_streams(struct wim_inode *inode, struct generation_context *ctx)
767 /* Reparse point (sometimes) */
768 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
769 ret = set_random_reparse_point(inode, ctx);
774 /* Unnamed data stream (nondirectories and non-symlinks only) */
775 if (!(inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) &&
776 !inode_is_symlink(inode)) {
777 ret = add_random_data_stream(inode, ctx, NO_STREAM_NAME);
782 /* Named data streams (sometimes) */
785 utf16lechar stream_name[2] = {cpu_to_le16('a'), '\0'};
788 ret = add_random_data_stream(inode, ctx, stream_name);
791 stream_name[0] += cpu_to_le16(1);
799 select_inode_number(struct generation_context *ctx)
801 const struct wim_inode_table *table = ctx->params->inode_table;
802 const struct hlist_head *head;
803 const struct wim_inode *inode;
805 head = &table->array[rand32() % table->capacity];
806 hlist_for_each_entry(inode, head, i_hlist_node)
814 select_num_children(u32 depth, struct generation_context *ctx)
816 const double b = 1.01230;
817 u32 r = rand32() % 500;
818 return ((pow(b, pow(b, r)) - 1) / pow(depth, 1.5)) +
819 (2 - exp(0.04/depth));
823 is_name_valid_in_win32_namespace(const utf16lechar *name)
825 const utf16lechar *p;
827 static const char * const reserved_names[] = {
828 "CON", "PRN", "AUX", "NUL",
829 "COM1", "COM2", "COM3", "COM4", "COM5",
830 "COM6", "COM7", "COM8", "COM9",
831 "LPT1", "LPT2", "LPT3", "LPT4", "LPT5",
832 "LPT6", "LPT7", "LPT8", "LPT9",
835 /* The name must be nonempty. */
839 /* All characters must be valid on Windows. */
840 for (p = name; *p; p++)
841 if (!is_valid_windows_filename_char(*p))
844 /* Note: a trailing dot or space is permitted, even though on Windows
845 * such a file can only be accessed using a WinNT-style path. */
847 /* The name can't be one of the reserved names or be a reserved name
848 * with an extension. Case insensitive. */
849 for (size_t i = 0; i < ARRAY_LEN(reserved_names); i++) {
850 for (size_t j = 0; ; j++) {
851 u16 c1 = le16_to_cpu(name[j]);
852 u16 c2 = reserved_names[i][j];
854 if (c1 == '\0' || c1 == '.')
858 if (upcase[c1] != upcase[c2])
867 set_random_short_name(struct wim_dentry *dir, struct wim_dentry *child,
868 struct generation_context *ctx)
870 utf16lechar name[12 + 1];
873 struct wim_dentry **bucket;
875 /* If the long name is not allowed in the Win32 namespace, then it
876 * cannot be assigned a corresponding short name. */
877 if (!is_name_valid_in_win32_namespace(child->d_name))
881 /* Don't select a short name that is already used by a long name within
882 * the same directory. */
884 name_len = generate_random_short_name(name, ctx);
885 } while (get_dentry_child_with_utf16le_name(dir, name, name_len * 2,
886 WIMLIB_CASE_INSENSITIVE));
889 /* Don't select a short name that is already used by another short name
890 * within the same directory. */
892 for (const utf16lechar *p = name; *p; p++)
893 hash = (hash * 31) + *p;
894 FREE(child->d_short_name);
895 child->d_short_name = memdup(name, (name_len + 1) * 2);
896 child->d_short_name_nbytes = name_len * 2;
898 if (!child->d_short_name)
899 return WIMLIB_ERR_NOMEM;
901 bucket = &ctx->used_short_names[hash % ARRAY_LEN(ctx->used_short_names)];
903 for (struct wim_dentry *d = *bucket; d != NULL;
904 d = d->d_next_extraction_alias) {
905 if (!cmp_utf16le_strings(child->d_short_name, name_len,
906 d->d_short_name, d->d_short_name_nbytes / 2,
912 if (!is_name_valid_in_win32_namespace(child->d_short_name))
915 child->d_next_extraction_alias = *bucket;
921 inode_has_short_name(const struct wim_inode *inode)
923 const struct wim_dentry *dentry;
925 inode_for_each_dentry(dentry, inode)
926 if (dentry_has_short_name(dentry))
933 generate_dentry_tree_recursive(struct wim_dentry *dir, u32 depth,
934 struct generation_context *ctx)
936 u32 num_children = select_num_children(depth, ctx);
937 struct wim_dentry *child;
940 memset(ctx->used_short_names, 0, sizeof(ctx->used_short_names));
942 /* Generate 'num_children' dentries within 'dir'. Some may be
943 * directories themselves. */
945 for (u32 i = 0; i < num_children; i++) {
947 /* Generate the next child dentry. */
948 struct wim_inode *inode;
950 bool is_directory = (rand32() % 16 <= 6);
951 bool is_reparse = (rand32() % 8 == 0);
952 utf16lechar name[63 + 1]; /* for UNIX extraction: 63 * 4 <= 255 */
954 struct wim_dentry *duplicate;
957 * Select an inode number for the new file. Sometimes choose an
958 * existing inode number (i.e. create a hard link). However,
959 * wimlib intentionally doesn't honor directory hard links, and
960 * reparse points cannot be represented in the WIM file format
961 * at all; so don't create hard links for such files.
963 if (is_directory || is_reparse)
966 ino = select_inode_number(ctx);
968 /* Create the dentry. */
969 ret = inode_table_new_dentry(ctx->params->inode_table, NULL,
970 ino, 0, ino == 0, &child);
974 /* Choose a filename that is unique within the directory.*/
976 name_len = generate_random_filename(name,
979 } while (get_dentry_child_with_utf16le_name(dir, name, name_len * 2,
980 WIMLIB_CASE_PLATFORM_DEFAULT));
982 ret = dentry_set_name_utf16le(child, name, name_len * 2);
988 /* Add the dentry to the directory. */
989 duplicate = dentry_add_child(dir, child);
990 wimlib_assert(!duplicate);
992 inode = child->d_inode;
994 if (inode->i_nlink > 1) /* Existing inode? */
997 /* New inode; set attributes, metadata, and data. */
1000 inode->i_attributes |= FILE_ATTRIBUTE_DIRECTORY;
1002 inode->i_attributes |= FILE_ATTRIBUTE_REPARSE_POINT;
1004 ret = set_random_streams(inode, ctx);
1008 ret = set_random_metadata(inode, ctx);
1012 /* Recurse if it's a directory. */
1013 if (is_directory && !is_reparse) {
1014 ret = generate_dentry_tree_recursive(child, depth + 1,
1021 for_dentry_child(child, dir) {
1022 /* sometimes generate a unique short name */
1023 if (randbool() && !inode_has_short_name(child->d_inode)) {
1024 ret = set_random_short_name(dir, child, ctx);
1034 generate_dentry_tree(struct wim_dentry **root_ret, const tchar *_ignored,
1035 struct scan_params *params)
1038 struct wim_dentry *root = NULL;
1039 struct generation_context ctx = {
1043 ctx.metadata_only = ((rand32() % 8) != 0); /* usually metadata only */
1045 ret = inode_table_new_dentry(params->inode_table, NULL, 0, 0, true, &root);
1047 root->d_inode->i_attributes = FILE_ATTRIBUTE_DIRECTORY;
1048 ret = set_random_streams(root->d_inode, &ctx);
1051 ret = set_random_metadata(root->d_inode, &ctx);
1053 ret = generate_dentry_tree_recursive(root, 1, &ctx);
1057 free_dentry_tree(root, params->blob_table);
1061 /*----------------------------------------------------------------------------*
1062 * File tree comparison *
1063 *----------------------------------------------------------------------------*/
1065 #define INDEX_NODE_TO_DENTRY(node) \
1066 ((node) ? avl_tree_entry((node), struct wim_dentry, d_index_node) : NULL)
1068 static struct wim_dentry *
1069 dentry_first_child(struct wim_dentry *dentry)
1071 return INDEX_NODE_TO_DENTRY(
1072 avl_tree_first_in_order(dentry->d_inode->i_children));
1075 static struct wim_dentry *
1076 dentry_next_sibling(struct wim_dentry *dentry)
1078 return INDEX_NODE_TO_DENTRY(
1079 avl_tree_next_in_order(&dentry->d_index_node));
1083 * Verify that the dentries in the tree 'd1' exactly match the dentries in the
1084 * tree 'd2', considering long and short filenames. In addition, set
1085 * 'd_corresponding' of each dentry to point to the corresponding dentry in the
1086 * other tree, and set 'i_corresponding' of each inode to point to the
1087 * unverified corresponding inode in the other tree.
1090 calc_corresponding_files_recursive(struct wim_dentry *d1, struct wim_dentry *d2,
1093 struct wim_dentry *child1;
1094 struct wim_dentry *child2;
1097 /* Compare long filenames, case sensitively. */
1098 if (cmp_utf16le_strings(d1->d_name, d1->d_name_nbytes / 2,
1099 d2->d_name, d2->d_name_nbytes / 2,
1102 ERROR("Filename mismatch; path1=\"%"TS"\", path2=\"%"TS"\"",
1103 dentry_full_path(d1), dentry_full_path(d2));
1104 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1107 /* Compare short filenames, case insensitively. */
1108 if (!(d2->d_short_name_nbytes == 0 &&
1109 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)) &&
1110 cmp_utf16le_strings(d1->d_short_name, d1->d_short_name_nbytes / 2,
1111 d2->d_short_name, d2->d_short_name_nbytes / 2,
1114 ERROR("Short name mismatch; path=\"%"TS"\"",
1115 dentry_full_path(d1));
1116 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1119 /* Match up the dentries */
1120 d1->d_corresponding = d2;
1121 d2->d_corresponding = d1;
1123 /* Match up the inodes (may overwrite previous value) */
1124 d1->d_inode->i_corresponding = d2->d_inode;
1125 d2->d_inode->i_corresponding = d1->d_inode;
1127 /* Process children */
1128 child1 = dentry_first_child(d1);
1129 child2 = dentry_first_child(d2);
1130 while (child1 || child2) {
1132 if (!child1 || !child2) {
1133 ERROR("Child count mismatch; "
1134 "path1=\"%"TS"\", path2=\"%"TS"\"",
1135 dentry_full_path(d1), dentry_full_path(d2));
1136 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1139 /* Recurse on this pair of children. */
1140 ret = calc_corresponding_files_recursive(child1, child2,
1145 /* Continue to the next pair of children. */
1146 child1 = dentry_next_sibling(child1);
1147 child2 = dentry_next_sibling(child2);
1152 /* Perform sanity checks on an image's inodes. All assertions here should pass,
1153 * even if the images being compared are different. */
1155 assert_inodes_sane(const struct wim_image_metadata *imd)
1157 const struct wim_inode *inode;
1158 const struct wim_dentry *dentry;
1161 image_for_each_inode(inode, imd) {
1163 inode_for_each_dentry(dentry, inode) {
1164 wimlib_assert(dentry->d_inode == inode);
1167 wimlib_assert(link_count > 0);
1168 wimlib_assert(link_count == inode->i_nlink);
1169 wimlib_assert(inode->i_corresponding != NULL);
1174 check_hard_link(struct wim_dentry *dentry, void *_ignore)
1176 /* My inode is my corresponding dentry's inode's corresponding inode,
1177 * and my inode's corresponding inode is my corresponding dentry's
1179 const struct wim_inode *a = dentry->d_inode;
1180 const struct wim_inode *b = dentry->d_corresponding->d_inode;
1181 if (a == b->i_corresponding && a->i_corresponding == b)
1183 ERROR("Hard link difference; path=%"TS"", dentry_full_path(dentry));
1184 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1187 static const struct {
1190 } file_attr_flags[] = {
1191 {FILE_ATTRIBUTE_READONLY, "READONLY"},
1192 {FILE_ATTRIBUTE_HIDDEN, "HIDDEN"},
1193 {FILE_ATTRIBUTE_SYSTEM, "SYSTEM"},
1194 {FILE_ATTRIBUTE_DIRECTORY, "DIRECTORY"},
1195 {FILE_ATTRIBUTE_ARCHIVE, "ARCHIVE"},
1196 {FILE_ATTRIBUTE_DEVICE, "DEVICE"},
1197 {FILE_ATTRIBUTE_NORMAL, "NORMAL"},
1198 {FILE_ATTRIBUTE_TEMPORARY, "TEMPORARY"},
1199 {FILE_ATTRIBUTE_SPARSE_FILE, "SPARSE_FILE"},
1200 {FILE_ATTRIBUTE_REPARSE_POINT, "REPARSE_POINT"},
1201 {FILE_ATTRIBUTE_COMPRESSED, "COMPRESSED"},
1202 {FILE_ATTRIBUTE_OFFLINE, "OFFLINE"},
1203 {FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, "NOT_CONTENT_INDEXED"},
1204 {FILE_ATTRIBUTE_ENCRYPTED, "ENCRYPTED"},
1205 {FILE_ATTRIBUTE_VIRTUAL, "VIRTUAL"},
1209 cmp_attributes(const struct wim_inode *inode1,
1210 const struct wim_inode *inode2, int cmp_flags)
1212 const u32 changed = inode1->i_attributes ^ inode2->i_attributes;
1213 const u32 set = inode2->i_attributes & ~inode1->i_attributes;
1214 const u32 cleared = inode1->i_attributes & ~inode2->i_attributes;
1216 /* NORMAL may change, but it must never be set along with other
1218 if ((inode2->i_attributes & FILE_ATTRIBUTE_NORMAL) &&
1219 (inode2->i_attributes & ~FILE_ATTRIBUTE_NORMAL))
1222 /* DIRECTORY may change in UNIX mode for symlinks. */
1223 if (changed & FILE_ATTRIBUTE_DIRECTORY) {
1224 if (!(inode_is_symlink(inode1) &&
1225 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)))
1229 /* REPARSE_POINT may be cleared in UNIX mode if the inode is not a
1231 if ((changed & FILE_ATTRIBUTE_REPARSE_POINT) &&
1232 !((cleared & FILE_ATTRIBUTE_REPARSE_POINT) &&
1233 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE) &&
1234 !inode_is_symlink(inode1)))
1237 /* SPARSE_FILE may be cleared in UNIX and NTFS-3G modes, or in Windows
1238 * mode if the inode is a directory. */
1239 if ((changed & FILE_ATTRIBUTE_SPARSE_FILE) &&
1240 !((cleared & FILE_ATTRIBUTE_SPARSE_FILE) &&
1241 ((cmp_flags & (WIMLIB_CMP_FLAG_UNIX_MODE |
1242 WIMLIB_CMP_FLAG_NTFS_3G_MODE)) ||
1243 ((cmp_flags & WIMLIB_CMP_FLAG_WINDOWS_MODE) &&
1244 (inode1->i_attributes & FILE_ATTRIBUTE_DIRECTORY)))))
1247 /* COMPRESSED may change in UNIX and NTFS-3G modes. (It *should* be
1248 * preserved in NTFS-3G mode, but it's not implemented yet.) */
1249 if ((changed & FILE_ATTRIBUTE_COMPRESSED) &&
1250 !(cmp_flags & (WIMLIB_CMP_FLAG_UNIX_MODE |
1251 WIMLIB_CMP_FLAG_NTFS_3G_MODE)))
1254 /* All other attributes can change in UNIX mode, but not in any other
1256 if ((changed & ~(FILE_ATTRIBUTE_NORMAL |
1257 FILE_ATTRIBUTE_DIRECTORY |
1258 FILE_ATTRIBUTE_REPARSE_POINT |
1259 FILE_ATTRIBUTE_SPARSE_FILE |
1260 FILE_ATTRIBUTE_COMPRESSED)) &&
1261 !(cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE))
1267 ERROR("Attribute mismatch for %"TS": 0x%08"PRIx32" vs. 0x%08"PRIx32":",
1268 inode_any_full_path(inode1), inode1->i_attributes,
1269 inode2->i_attributes);
1270 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++) {
1271 u32 flag = file_attr_flags[i].flag;
1272 if (changed & flag) {
1273 fprintf(stderr, "\tFILE_ATTRIBUTE_%s was %s\n",
1274 file_attr_flags[i].name,
1275 (set & flag) ? "set" : "cleared");
1278 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1282 cmp_object_ids(const struct wim_inode *inode1,
1283 const struct wim_inode *inode2, int cmp_flags)
1285 const void *objid1, *objid2;
1288 objid1 = inode_get_object_id(inode1, &len1);
1289 objid2 = inode_get_object_id(inode2, &len2);
1291 if (!objid1 && !objid2)
1294 if (objid1 && !objid2) {
1295 if (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)
1297 ERROR("%"TS" unexpectedly lost its object ID",
1298 inode_any_full_path(inode1));
1299 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1302 if (!objid1 && objid2) {
1303 ERROR("%"TS" unexpectedly gained an object ID",
1304 inode_any_full_path(inode1));
1305 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1308 if (len1 != len2 || memcmp(objid1, objid2, len1) != 0) {
1309 ERROR("Object ID of %"TS" differs",
1310 inode_any_full_path(inode1));
1311 fprintf(stderr, "objid1=");
1312 print_byte_field(objid1, len1, stderr);
1313 fprintf(stderr, "\nobjid2=");
1314 print_byte_field(objid2, len2, stderr);
1315 fprintf(stderr, "\n");
1316 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1323 cmp_unix_metadata(const struct wim_inode *inode1,
1324 const struct wim_inode *inode2, int cmp_flags)
1326 struct wimlib_unix_data dat1, dat2;
1327 bool present1, present2;
1329 present1 = inode_get_unix_data(inode1, &dat1);
1330 present2 = inode_get_unix_data(inode2, &dat2);
1332 if (!present1 && !present2)
1335 if (present1 && !present2) {
1336 if (cmp_flags & (WIMLIB_CMP_FLAG_NTFS_3G_MODE |
1337 WIMLIB_CMP_FLAG_WINDOWS_MODE))
1339 ERROR("%"TS" unexpectedly lost its UNIX metadata",
1340 inode_any_full_path(inode1));
1341 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1344 if (!present1 && present2) {
1345 if (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)
1347 ERROR("%"TS" unexpectedly gained UNIX metadata",
1348 inode_any_full_path(inode1));
1349 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1352 if (memcmp(&dat1, &dat2, sizeof(dat1)) != 0) {
1353 ERROR("UNIX metadata of %"TS" differs: "
1354 "[uid=%u, gid=%u, mode=0%o, rdev=%u] vs. "
1355 "[uid=%u, gid=%u, mode=0%o, rdev=%u]",
1356 inode_any_full_path(inode1),
1357 dat1.uid, dat1.gid, dat1.mode, dat1.rdev,
1358 dat2.uid, dat2.gid, dat2.mode, dat2.rdev);
1359 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1366 cmp_xattr_names(const void *p1, const void *p2)
1368 const struct wim_xattr_entry *entry1 = *(const struct wim_xattr_entry **)p1;
1369 const struct wim_xattr_entry *entry2 = *(const struct wim_xattr_entry **)p2;
1372 res = entry1->name_len - entry2->name_len;
1376 return memcmp(entry1->name, entry2->name, entry1->name_len);
1379 /* Validate and sort by name a list of extended attributes */
1381 parse_xattrs(const void *xattrs, u32 len,
1382 const struct wim_xattr_entry *entries[],
1385 u32 limit = *num_entries_p;
1386 u32 num_entries = 0;
1387 const struct wim_xattr_entry *entry = xattrs;
1389 while ((void *)entry < xattrs + len) {
1390 if (!valid_xattr_entry(entry, xattrs + len - (void *)entry)) {
1391 ERROR("Invalid xattr entry");
1392 return WIMLIB_ERR_INVALID_XATTR;
1394 if (num_entries >= limit) {
1395 ERROR("Too many xattr entries");
1396 return WIMLIB_ERR_INVALID_XATTR;
1398 entries[num_entries++] = entry;
1399 entry = xattr_entry_next(entry);
1402 if (num_entries == 0) {
1403 ERROR("No xattr entries");
1404 return WIMLIB_ERR_INVALID_XATTR;
1407 qsort(entries, num_entries, sizeof(entries[0]), cmp_xattr_names);
1409 for (u32 i = 1; i < num_entries; i++) {
1410 if (cmp_xattr_names(&entries[i - 1], &entries[i]) == 0) {
1411 ERROR("Duplicate xattr names");
1412 return WIMLIB_ERR_INVALID_XATTR;
1416 *num_entries_p = num_entries;
1421 cmp_xattrs(const struct wim_inode *inode1, const struct wim_inode *inode2,
1424 const void *xattrs1, *xattrs2;
1427 xattrs1 = inode_get_xattrs(inode1, &len1);
1428 xattrs2 = inode_get_xattrs(inode2, &len2);
1430 if (!xattrs1 && !xattrs2) {
1432 } else if (xattrs1 && !xattrs2) {
1433 if (cmp_flags & (WIMLIB_CMP_FLAG_NTFS_3G_MODE |
1434 WIMLIB_CMP_FLAG_WINDOWS_MODE))
1436 ERROR("%"TS" unexpectedly lost its xattrs",
1437 inode_any_full_path(inode1));
1438 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1439 } else if (!xattrs1 && xattrs2) {
1440 ERROR("%"TS" unexpectedly gained xattrs",
1441 inode_any_full_path(inode1));
1442 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1444 const int max_entries = 64;
1445 const struct wim_xattr_entry *entries1[max_entries];
1446 const struct wim_xattr_entry *entries2[max_entries];
1447 u32 xattr_count1 = max_entries;
1448 u32 xattr_count2 = max_entries;
1451 ret = parse_xattrs(xattrs1, len1, entries1, &xattr_count1);
1453 ERROR("%"TS": invalid xattrs",
1454 inode_any_full_path(inode1));
1457 ret = parse_xattrs(xattrs2, len2, entries2, &xattr_count2);
1459 ERROR("%"TS": invalid xattrs",
1460 inode_any_full_path(inode2));
1463 if (xattr_count1 != xattr_count2) {
1464 ERROR("%"TS": number of xattrs changed. had %u "
1465 "before, now has %u", inode_any_full_path(inode1),
1466 xattr_count1, xattr_count2);
1468 for (u32 i = 0; i < xattr_count1; i++) {
1469 const struct wim_xattr_entry *entry1 = entries1[i];
1470 const struct wim_xattr_entry *entry2 = entries2[i];
1472 if (entry1->value_len != entry2->value_len ||
1473 entry1->name_len != entry2->name_len ||
1474 entry1->flags != entry2->flags ||
1475 memcmp(entry1->name, entry2->name,
1476 entry1->name_len) ||
1477 memcmp(entry1->name + entry1->name_len + 1,
1478 entry2->name + entry2->name_len + 1,
1479 le16_to_cpu(entry1->value_len)))
1481 ERROR("xattr %.*s of %"TS" differs",
1482 entry1->name_len, entry1->name,
1483 inode_any_full_path(inode1));
1484 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1492 cmp_timestamps(const struct wim_inode *inode1, const struct wim_inode *inode2,
1495 if (inode1->i_creation_time != inode2->i_creation_time &&
1496 !(cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)) {
1497 ERROR("Creation time of %"TS" differs",
1498 inode_any_full_path(inode1));
1499 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1502 if (inode1->i_last_write_time != inode2->i_last_write_time) {
1503 ERROR("Last write time of %"TS" differs",
1504 inode_any_full_path(inode1));
1505 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1508 if (inode1->i_last_access_time != inode2->i_last_access_time) {
1509 ERROR("Last access time of %"TS" differs",
1510 inode_any_full_path(inode1));
1511 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1518 cmp_inodes(const struct wim_inode *inode1, const struct wim_inode *inode2,
1519 const struct wim_image_metadata *imd1,
1520 const struct wim_image_metadata *imd2, int cmp_flags)
1524 /* Compare attributes */
1525 ret = cmp_attributes(inode1, inode2, cmp_flags);
1529 /* Compare security descriptors */
1530 if (inode_has_security_descriptor(inode1)) {
1531 if (inode_has_security_descriptor(inode2)) {
1532 const void *desc1 = imd1->security_data->descriptors[inode1->i_security_id];
1533 const void *desc2 = imd2->security_data->descriptors[inode2->i_security_id];
1534 size_t size1 = imd1->security_data->sizes[inode1->i_security_id];
1535 size_t size2 = imd2->security_data->sizes[inode2->i_security_id];
1537 if (size1 != size2 || memcmp(desc1, desc2, size1)) {
1538 ERROR("Security descriptor of %"TS" differs!",
1539 inode_any_full_path(inode1));
1540 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1542 } else if (!(cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)) {
1543 ERROR("%"TS" has a security descriptor in the first image but "
1544 "not in the second image!", inode_any_full_path(inode1));
1545 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1547 } else if (inode_has_security_descriptor(inode2)) {
1548 /* okay --- consider it acceptable if a default security
1549 * descriptor was assigned */
1550 /*ERROR("%"TS" has a security descriptor in the second image but "*/
1551 /*"not in the first image!", inode_any_full_path(inode1));*/
1552 /*return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;*/
1555 /* Compare streams */
1556 for (unsigned i = 0; i < inode1->i_num_streams; i++) {
1557 const struct wim_inode_stream *strm1 = &inode1->i_streams[i];
1558 const struct wim_inode_stream *strm2;
1560 if (strm1->stream_type == STREAM_TYPE_REPARSE_POINT &&
1561 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE &&
1562 !inode_is_symlink(inode1)))
1565 if (strm1->stream_type == STREAM_TYPE_UNKNOWN)
1568 /* Get the corresponding stream from the second file */
1569 strm2 = inode_get_stream(inode2, strm1->stream_type, strm1->stream_name);
1572 /* Corresponding stream not found */
1573 if (stream_is_named(strm1) &&
1574 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE))
1576 ERROR("Stream of %"TS" is missing in second image; "
1577 "type %d, named=%d, empty=%d",
1578 inode_any_full_path(inode1),
1580 stream_is_named(strm1),
1581 is_zero_hash(stream_hash(strm1)));
1582 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1585 if (!hashes_equal(stream_hash(strm1), stream_hash(strm2))) {
1586 ERROR("Stream of %"TS" differs; type %d",
1587 inode_any_full_path(inode1), strm1->stream_type);
1588 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1592 /* Compare object IDs */
1593 ret = cmp_object_ids(inode1, inode2, cmp_flags);
1597 /* Compare timestamps */
1598 ret = cmp_timestamps(inode1, inode2, cmp_flags);
1602 /* Compare standard UNIX metadata */
1603 ret = cmp_unix_metadata(inode1, inode2, cmp_flags);
1607 /* Compare extended attributes */
1608 ret = cmp_xattrs(inode1, inode2, cmp_flags);
1616 cmp_images(const struct wim_image_metadata *imd1,
1617 const struct wim_image_metadata *imd2, int cmp_flags)
1619 struct wim_dentry *root1 = imd1->root_dentry;
1620 struct wim_dentry *root2 = imd2->root_dentry;
1621 const struct wim_inode *inode;
1624 ret = calc_corresponding_files_recursive(root1, root2, cmp_flags);
1628 /* Verify that the hard links match up between the two images. */
1629 assert_inodes_sane(imd1);
1630 assert_inodes_sane(imd2);
1631 ret = for_dentry_in_tree(root1, check_hard_link, NULL);
1635 /* Compare corresponding inodes. */
1636 image_for_each_inode(inode, imd1) {
1637 ret = cmp_inodes(inode, inode->i_corresponding,
1638 imd1, imd2, cmp_flags);
1647 load_image(WIMStruct *wim, int image, struct wim_image_metadata **imd_ret)
1649 int ret = select_wim_image(wim, image);
1651 *imd_ret = wim_get_current_image_metadata(wim);
1652 mark_image_dirty(*imd_ret);
1658 wimlib_compare_images(WIMStruct *wim1, int image1,
1659 WIMStruct *wim2, int image2, int cmp_flags)
1662 struct wim_image_metadata *imd1, *imd2;
1664 ret = load_image(wim1, image1, &imd1);
1666 ret = load_image(wim2, image2, &imd2);
1668 ret = cmp_images(imd1, imd2, cmp_flags);
1672 #endif /* ENABLE_TEST_SUPPORT */