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
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;
474 char entries[8192] _aligned_attribute(4);
475 struct wimlib_xattr_entry *entry = (void *)entries;
477 struct wimlib_unix_data unix_data;
478 const char *prefix = "user.";
481 * On Linux, xattrs in the "user" namespace are only permitted on
482 * regular files and directories. For other types of files we can use
483 * the "trusted" namespace, but this requires root.
485 if (inode_is_symlink(inode) ||
486 (inode_get_unix_data(inode, &unix_data) &&
487 !S_ISREG(unix_data.mode) && !S_ISDIR(unix_data.mode)))
494 for (int i = 0; i < num_xattrs; i++) {
495 int name_len = 1 + rand32() % 64;
496 int value_len = rand32() % 64;
499 entry->name_len = cpu_to_le16(strlen(prefix) + name_len);
501 entry->value_len = cpu_to_le32(value_len);
502 p = mempcpy(entry->name, prefix, strlen(prefix));
504 for (int j = 1; j < name_len; j++) {
507 } while (*p == '\0');
510 for (int j = 0; j < value_len; j++)
513 while ((uintptr_t)p % 4)
519 entries_size = (char *)entry - entries;
520 wimlib_assert(entries_size > 0 && entries_size % 4 == 0 &&
521 entries_size <= sizeof(entries));
523 if (!inode_set_linux_xattrs(inode, entries, entries_size))
524 return WIMLIB_ERR_NOMEM;
530 set_random_metadata(struct wim_inode *inode, struct generation_context *ctx)
532 u32 attrib = (rand32() & (FILE_ATTRIBUTE_READONLY |
533 FILE_ATTRIBUTE_HIDDEN |
534 FILE_ATTRIBUTE_SYSTEM |
535 FILE_ATTRIBUTE_ARCHIVE |
536 FILE_ATTRIBUTE_NOT_CONTENT_INDEXED |
537 FILE_ATTRIBUTE_COMPRESSED |
538 FILE_ATTRIBUTE_SPARSE_FILE));
540 /* File attributes */
541 inode->i_attributes |= attrib;
544 inode->i_creation_time = generate_random_timestamp();
545 inode->i_last_access_time = generate_random_timestamp();
546 inode->i_last_write_time = generate_random_timestamp();
548 /* Security descriptor */
550 char desc[8192] _aligned_attribute(8);
553 size = generate_random_security_descriptor(desc, ctx);
555 wimlib_assert(size <= sizeof(desc));
557 inode->i_security_id = sd_set_add_sd(ctx->params->sd_set,
559 if (unlikely(inode->i_security_id < 0))
560 return WIMLIB_ERR_NOMEM;
564 if (rand32() % 32 == 0) {
565 struct wimlib_object_id object_id;
567 for (int i = 0; i < sizeof(object_id); i++)
568 *((u8 *)&object_id + i) = rand8();
569 if (!inode_set_object_id(inode, &object_id, sizeof(object_id)))
570 return WIMLIB_ERR_NOMEM;
573 /* Standard UNIX permissions and special files */
574 if (rand32() % 16 == 0) {
575 int ret = set_random_unix_metadata(inode);
580 /* Extended attributes */
581 if (rand32() % 32 == 0) {
582 int ret = set_random_xattrs(inode);
591 /* Choose a random size for generated file data. We want to usually generate
592 * empty, small, or medium files, but occasionally generate large files. */
594 select_stream_size(struct generation_context *ctx)
596 if (ctx->metadata_only)
599 switch (rand32() % 2048) {
605 return rand32() % 64;
608 return rand32() % 4096;
611 return rand32() % 32768;
614 return rand32() % 262144;
617 return rand32() % 134217728;
621 /* Fill 'buffer' with 'size' bytes of "interesting" file data. */
623 generate_data(u8 *buffer, size_t size, struct generation_context *ctx)
626 size_t num_byte_fills = rand32() % 256;
631 /* Start by initializing to a random byte */
632 memset(buffer, rand32() % 256, size);
634 /* Add some random bytes in some random places */
635 for (size_t i = 0; i < num_byte_fills; i++) {
638 size_t count = ((double)size / (double)num_byte_fills) *
639 ((double)rand32() / 2e9);
640 size_t offset = rand32() & ~mask;
644 ((rand32()) & mask)) % size] = b;
648 if (rand32() % 4 == 0)
649 mask = (size_t)-1 << rand32() % 4;
652 /* Sometimes add a wave pattern */
653 if (rand32() % 8 == 0) {
654 double magnitude = rand32() % 128;
655 double scale = 1.0 / (1 + (rand32() % 256));
657 for (size_t i = 0; i < size; i++)
658 buffer[i] += (int)(magnitude * cos(i * scale));
661 /* Sometimes add some zero regions (holes) */
662 if (rand32() % 4 == 0) {
663 size_t num_holes = 1 + (rand32() % 16);
664 for (size_t i = 0; i < num_holes; i++) {
665 size_t hole_offset = rand32() % size;
666 size_t hole_len = min(size - hole_offset,
667 size / (1 + (rand32() % 16)));
668 memset(&buffer[hole_offset], 0, hole_len);
674 add_stream(struct wim_inode *inode, struct generation_context *ctx,
675 int stream_type, const utf16lechar *stream_name,
676 void *buffer, size_t size)
678 struct blob_descriptor *blob = NULL;
679 struct wim_inode_stream *strm;
682 blob = new_blob_descriptor();
685 blob->attached_buffer = buffer;
686 blob->blob_location = BLOB_IN_ATTACHED_BUFFER;
690 strm = inode_add_stream(inode, stream_type, stream_name, blob);
694 prepare_unhashed_blob(blob, inode, strm->stream_id,
695 ctx->params->unhashed_blobs);
699 free_blob_descriptor(blob);
700 return WIMLIB_ERR_NOMEM;
703 static noinline_for_stack int
704 set_random_reparse_point(struct wim_inode *inode, struct generation_context *ctx)
706 struct reparse_buffer_disk rpbuf;
709 inode->i_attributes |= FILE_ATTRIBUTE_REPARSE_POINT;
714 utf16lechar *targets = (utf16lechar *)rpbuf.link.symlink.data;
716 inode->i_reparse_tag = WIM_IO_REPARSE_TAG_SYMLINK;
718 target_nchars = generate_random_filename(targets, 255, ctx);
720 rpbuf.link.substitute_name_offset = cpu_to_le16(0);
721 rpbuf.link.substitute_name_nbytes = cpu_to_le16(2*target_nchars);
722 rpbuf.link.print_name_offset = cpu_to_le16(2*(target_nchars + 1));
723 rpbuf.link.print_name_nbytes = cpu_to_le16(2*target_nchars);
724 targets[target_nchars] = cpu_to_le16(0);
725 memcpy(&targets[target_nchars + 1], targets, 2*target_nchars);
726 targets[target_nchars + 1 + target_nchars] = cpu_to_le16(0);
728 rpbuf.link.symlink.flags = cpu_to_le32(SYMBOLIC_LINK_RELATIVE);
729 rpdatalen = ((u8 *)targets - rpbuf.rpdata) +
730 2*(target_nchars + 1 + target_nchars + 1);
732 rpdatalen = select_stream_size(ctx) % REPARSE_DATA_MAX_SIZE;
733 generate_data(rpbuf.rpdata, rpdatalen, ctx);
735 if (rpdatalen >= GUID_SIZE && randbool()) {
736 /* Non-Microsoft reparse tag (16-byte GUID required) */
737 u8 *guid = rpbuf.rpdata;
738 guid[6] = (guid[6] & 0x0F) | 0x40;
739 guid[8] = (guid[8] & 0x3F) | 0x80;
740 inode->i_reparse_tag = 0x00000100;
742 /* Microsoft reparse tag */
743 inode->i_reparse_tag = 0x80000000;
745 inode->i_rp_reserved = rand16();
748 wimlib_assert(rpdatalen < REPARSE_DATA_MAX_SIZE);
750 if (!inode_add_stream_with_data(inode, STREAM_TYPE_REPARSE_POINT,
751 NO_STREAM_NAME, rpbuf.rpdata,
752 rpdatalen, ctx->params->blob_table))
753 return WIMLIB_ERR_NOMEM;
759 add_random_data_stream(struct wim_inode *inode, struct generation_context *ctx,
760 const utf16lechar *stream_name)
765 size = select_stream_size(ctx);
767 buffer = MALLOC(size);
769 return WIMLIB_ERR_NOMEM;
770 generate_data(buffer, size, ctx);
773 return add_stream(inode, ctx, STREAM_TYPE_DATA, stream_name,
778 set_random_streams(struct wim_inode *inode, struct generation_context *ctx)
783 /* Reparse point (sometimes) */
784 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
785 ret = set_random_reparse_point(inode, ctx);
790 /* Unnamed data stream (nondirectories and non-symlinks only) */
791 if (!(inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) &&
792 !inode_is_symlink(inode)) {
793 ret = add_random_data_stream(inode, ctx, NO_STREAM_NAME);
798 /* Named data streams (sometimes) */
801 utf16lechar stream_name[2] = {cpu_to_le16('a'), '\0'};
804 ret = add_random_data_stream(inode, ctx, stream_name);
807 stream_name[0] += cpu_to_le16(1);
815 select_inode_number(struct generation_context *ctx)
817 const struct wim_inode_table *table = ctx->params->inode_table;
818 const struct hlist_head *head;
819 const struct wim_inode *inode;
821 head = &table->array[rand32() % table->capacity];
822 hlist_for_each_entry(inode, head, i_hlist_node)
830 select_num_children(u32 depth, struct generation_context *ctx)
832 const double b = 1.01230;
833 u32 r = rand32() % 500;
834 return ((pow(b, pow(b, r)) - 1) / pow(depth, 1.5)) +
835 (2 - exp(0.04/depth));
839 is_name_valid_in_win32_namespace(const utf16lechar *name)
841 const utf16lechar *p;
843 static const char * const reserved_names[] = {
844 "CON", "PRN", "AUX", "NUL",
845 "COM1", "COM2", "COM3", "COM4", "COM5",
846 "COM6", "COM7", "COM8", "COM9",
847 "LPT1", "LPT2", "LPT3", "LPT4", "LPT5",
848 "LPT6", "LPT7", "LPT8", "LPT9",
851 /* The name must be nonempty. */
855 /* All characters must be valid on Windows. */
856 for (p = name; *p; p++)
857 if (!is_valid_windows_filename_char(*p))
860 /* Note: a trailing dot or space is permitted, even though on Windows
861 * such a file can only be accessed using a WinNT-style path. */
863 /* The name can't be one of the reserved names or be a reserved name
864 * with an extension. Case insensitive. */
865 for (size_t i = 0; i < ARRAY_LEN(reserved_names); i++) {
866 for (size_t j = 0; ; j++) {
867 u16 c1 = le16_to_cpu(name[j]);
868 u16 c2 = reserved_names[i][j];
870 if (c1 == '\0' || c1 == '.')
874 if (upcase[c1] != upcase[c2])
883 set_random_short_name(struct wim_dentry *dir, struct wim_dentry *child,
884 struct generation_context *ctx)
886 utf16lechar name[12 + 1];
889 struct wim_dentry **bucket;
891 /* If the long name is not allowed in the Win32 namespace, then it
892 * cannot be assigned a corresponding short name. */
893 if (!is_name_valid_in_win32_namespace(child->d_name))
897 /* Don't select a short name that is already used by a long name within
898 * the same directory. */
900 name_len = generate_random_short_name(name, ctx);
901 } while (get_dentry_child_with_utf16le_name(dir, name, name_len * 2,
902 WIMLIB_CASE_INSENSITIVE));
905 /* Don't select a short name that is already used by another short name
906 * within the same directory. */
908 for (const utf16lechar *p = name; *p; p++)
909 hash = (hash * 31) + *p;
910 FREE(child->d_short_name);
911 child->d_short_name = memdup(name, (name_len + 1) * 2);
912 child->d_short_name_nbytes = name_len * 2;
914 if (!child->d_short_name)
915 return WIMLIB_ERR_NOMEM;
917 bucket = &ctx->used_short_names[hash % ARRAY_LEN(ctx->used_short_names)];
919 for (struct wim_dentry *d = *bucket; d != NULL;
920 d = d->d_next_extraction_alias) {
921 if (!cmp_utf16le_strings(child->d_short_name, name_len,
922 d->d_short_name, d->d_short_name_nbytes / 2,
928 if (!is_name_valid_in_win32_namespace(child->d_short_name))
931 child->d_next_extraction_alias = *bucket;
937 inode_has_short_name(const struct wim_inode *inode)
939 const struct wim_dentry *dentry;
941 inode_for_each_dentry(dentry, inode)
942 if (dentry_has_short_name(dentry))
949 generate_dentry_tree_recursive(struct wim_dentry *dir, u32 depth,
950 struct generation_context *ctx)
952 u32 num_children = select_num_children(depth, ctx);
953 struct wim_dentry *child;
956 memset(ctx->used_short_names, 0, sizeof(ctx->used_short_names));
958 /* Generate 'num_children' dentries within 'dir'. Some may be
959 * directories themselves. */
961 for (u32 i = 0; i < num_children; i++) {
963 /* Generate the next child dentry. */
964 struct wim_inode *inode;
966 bool is_directory = (rand32() % 16 <= 6);
967 bool is_reparse = (rand32() % 8 == 0);
968 utf16lechar name[63 + 1]; /* for UNIX extraction: 63 * 4 <= 255 */
970 struct wim_dentry *duplicate;
973 * Select an inode number for the new file. Sometimes choose an
974 * existing inode number (i.e. create a hard link). However,
975 * wimlib intentionally doesn't honor directory hard links, and
976 * reparse points cannot be represented in the WIM file format
977 * at all; so don't create hard links for such files.
979 if (is_directory || is_reparse)
982 ino = select_inode_number(ctx);
984 /* Create the dentry. */
985 ret = inode_table_new_dentry(ctx->params->inode_table, NULL,
986 ino, 0, ino == 0, &child);
990 /* Choose a filename that is unique within the directory.*/
992 name_len = generate_random_filename(name,
995 } while (get_dentry_child_with_utf16le_name(dir, name, name_len * 2,
996 WIMLIB_CASE_PLATFORM_DEFAULT));
998 ret = dentry_set_name_utf16le(child, name, name_len * 2);
1004 /* Add the dentry to the directory. */
1005 duplicate = dentry_add_child(dir, child);
1006 wimlib_assert(!duplicate);
1008 inode = child->d_inode;
1010 if (inode->i_nlink > 1) /* Existing inode? */
1013 /* New inode; set attributes, metadata, and data. */
1016 inode->i_attributes |= FILE_ATTRIBUTE_DIRECTORY;
1018 inode->i_attributes |= FILE_ATTRIBUTE_REPARSE_POINT;
1020 ret = set_random_streams(inode, ctx);
1024 ret = set_random_metadata(inode, ctx);
1028 /* Recurse if it's a directory. */
1029 if (is_directory && !is_reparse) {
1030 ret = generate_dentry_tree_recursive(child, depth + 1,
1037 for_dentry_child(child, dir) {
1038 /* sometimes generate a unique short name */
1039 if (randbool() && !inode_has_short_name(child->d_inode)) {
1040 ret = set_random_short_name(dir, child, ctx);
1050 generate_dentry_tree(struct wim_dentry **root_ret, const tchar *_ignored,
1051 struct scan_params *params)
1054 struct wim_dentry *root = NULL;
1055 struct generation_context ctx = {
1059 ctx.metadata_only = ((rand32() % 8) != 0); /* usually metadata only */
1061 ret = inode_table_new_dentry(params->inode_table, NULL, 0, 0, true, &root);
1063 root->d_inode->i_attributes = FILE_ATTRIBUTE_DIRECTORY;
1064 ret = set_random_streams(root->d_inode, &ctx);
1067 ret = set_random_metadata(root->d_inode, &ctx);
1069 ret = generate_dentry_tree_recursive(root, 1, &ctx);
1073 free_dentry_tree(root, params->blob_table);
1077 /*----------------------------------------------------------------------------*
1078 * File tree comparison *
1079 *----------------------------------------------------------------------------*/
1081 #define INDEX_NODE_TO_DENTRY(node) \
1082 ((node) ? avl_tree_entry((node), struct wim_dentry, d_index_node) : NULL)
1084 static struct wim_dentry *
1085 dentry_first_child(struct wim_dentry *dentry)
1087 return INDEX_NODE_TO_DENTRY(
1088 avl_tree_first_in_order(dentry->d_inode->i_children));
1091 static struct wim_dentry *
1092 dentry_next_sibling(struct wim_dentry *dentry)
1094 return INDEX_NODE_TO_DENTRY(
1095 avl_tree_next_in_order(&dentry->d_index_node));
1099 * Verify that the dentries in the tree 'd1' exactly match the dentries in the
1100 * tree 'd2', considering long and short filenames. In addition, set
1101 * 'd_corresponding' of each dentry to point to the corresponding dentry in the
1102 * other tree, and set 'i_corresponding' of each inode to point to the
1103 * unverified corresponding inode in the other tree.
1106 calc_corresponding_files_recursive(struct wim_dentry *d1, struct wim_dentry *d2,
1109 struct wim_dentry *child1;
1110 struct wim_dentry *child2;
1113 /* Compare long filenames, case sensitively. */
1114 if (cmp_utf16le_strings(d1->d_name, d1->d_name_nbytes / 2,
1115 d2->d_name, d2->d_name_nbytes / 2,
1118 ERROR("Filename mismatch; path1=\"%"TS"\", path2=\"%"TS"\"",
1119 dentry_full_path(d1), dentry_full_path(d2));
1120 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1123 /* Compare short filenames, case insensitively. */
1124 if (!(d2->d_short_name_nbytes == 0 &&
1125 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)) &&
1126 cmp_utf16le_strings(d1->d_short_name, d1->d_short_name_nbytes / 2,
1127 d2->d_short_name, d2->d_short_name_nbytes / 2,
1130 ERROR("Short name mismatch; path=\"%"TS"\"",
1131 dentry_full_path(d1));
1132 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1135 /* Match up the dentries */
1136 d1->d_corresponding = d2;
1137 d2->d_corresponding = d1;
1139 /* Match up the inodes (may overwrite previous value) */
1140 d1->d_inode->i_corresponding = d2->d_inode;
1141 d2->d_inode->i_corresponding = d1->d_inode;
1143 /* Process children */
1144 child1 = dentry_first_child(d1);
1145 child2 = dentry_first_child(d2);
1146 while (child1 || child2) {
1148 if (!child1 || !child2) {
1149 ERROR("Child count mismatch; "
1150 "path1=\"%"TS"\", path2=\"%"TS"\"",
1151 dentry_full_path(d1), dentry_full_path(d2));
1152 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1155 /* Recurse on this pair of children. */
1156 ret = calc_corresponding_files_recursive(child1, child2,
1161 /* Continue to the next pair of children. */
1162 child1 = dentry_next_sibling(child1);
1163 child2 = dentry_next_sibling(child2);
1168 /* Perform sanity checks on an image's inodes. All assertions here should pass,
1169 * even if the images being compared are different. */
1171 assert_inodes_sane(const struct wim_image_metadata *imd)
1173 const struct wim_inode *inode;
1174 const struct wim_dentry *dentry;
1177 image_for_each_inode(inode, imd) {
1179 inode_for_each_dentry(dentry, inode) {
1180 wimlib_assert(dentry->d_inode == inode);
1183 wimlib_assert(link_count > 0);
1184 wimlib_assert(link_count == inode->i_nlink);
1185 wimlib_assert(inode->i_corresponding != NULL);
1190 check_hard_link(struct wim_dentry *dentry, void *_ignore)
1192 /* My inode is my corresponding dentry's inode's corresponding inode,
1193 * and my inode's corresponding inode is my corresponding dentry's
1195 const struct wim_inode *a = dentry->d_inode;
1196 const struct wim_inode *b = dentry->d_corresponding->d_inode;
1197 if (a == b->i_corresponding && a->i_corresponding == b)
1199 ERROR("Hard link difference; path=%"TS"", dentry_full_path(dentry));
1200 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1203 static const struct {
1206 } file_attr_flags[] = {
1207 {FILE_ATTRIBUTE_READONLY, "READONLY"},
1208 {FILE_ATTRIBUTE_HIDDEN, "HIDDEN"},
1209 {FILE_ATTRIBUTE_SYSTEM, "SYSTEM"},
1210 {FILE_ATTRIBUTE_DIRECTORY, "DIRECTORY"},
1211 {FILE_ATTRIBUTE_ARCHIVE, "ARCHIVE"},
1212 {FILE_ATTRIBUTE_DEVICE, "DEVICE"},
1213 {FILE_ATTRIBUTE_NORMAL, "NORMAL"},
1214 {FILE_ATTRIBUTE_TEMPORARY, "TEMPORARY"},
1215 {FILE_ATTRIBUTE_SPARSE_FILE, "SPARSE_FILE"},
1216 {FILE_ATTRIBUTE_REPARSE_POINT, "REPARSE_POINT"},
1217 {FILE_ATTRIBUTE_COMPRESSED, "COMPRESSED"},
1218 {FILE_ATTRIBUTE_OFFLINE, "OFFLINE"},
1219 {FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, "NOT_CONTENT_INDEXED"},
1220 {FILE_ATTRIBUTE_ENCRYPTED, "ENCRYPTED"},
1221 {FILE_ATTRIBUTE_VIRTUAL, "VIRTUAL"},
1225 cmp_attributes(const struct wim_inode *inode1,
1226 const struct wim_inode *inode2, int cmp_flags)
1228 const u32 changed = inode1->i_attributes ^ inode2->i_attributes;
1229 const u32 set = inode2->i_attributes & ~inode1->i_attributes;
1230 const u32 cleared = inode1->i_attributes & ~inode2->i_attributes;
1232 /* NORMAL may change, but it must never be set along with other
1234 if ((inode2->i_attributes & FILE_ATTRIBUTE_NORMAL) &&
1235 (inode2->i_attributes & ~FILE_ATTRIBUTE_NORMAL))
1238 /* DIRECTORY may change in UNIX mode for symlinks. */
1239 if (changed & FILE_ATTRIBUTE_DIRECTORY) {
1240 if (!(inode_is_symlink(inode1) &&
1241 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)))
1245 /* REPARSE_POINT may be cleared in UNIX mode if the inode is not a
1247 if ((changed & FILE_ATTRIBUTE_REPARSE_POINT) &&
1248 !((cleared & FILE_ATTRIBUTE_REPARSE_POINT) &&
1249 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE) &&
1250 !inode_is_symlink(inode1)))
1253 /* SPARSE_FILE may be cleared in UNIX and NTFS-3G modes, or in Windows
1254 * mode if the inode is a directory. */
1255 if ((changed & FILE_ATTRIBUTE_SPARSE_FILE) &&
1256 !((cleared & FILE_ATTRIBUTE_SPARSE_FILE) &&
1257 ((cmp_flags & (WIMLIB_CMP_FLAG_UNIX_MODE |
1258 WIMLIB_CMP_FLAG_NTFS_3G_MODE)) ||
1259 ((cmp_flags & WIMLIB_CMP_FLAG_WINDOWS_MODE) &&
1260 (inode1->i_attributes & FILE_ATTRIBUTE_DIRECTORY)))))
1263 /* COMPRESSED may change in UNIX and NTFS-3G modes. (It *should* be
1264 * preserved in NTFS-3G mode, but it's not implemented yet.) */
1265 if ((changed & FILE_ATTRIBUTE_COMPRESSED) &&
1266 !(cmp_flags & (WIMLIB_CMP_FLAG_UNIX_MODE |
1267 WIMLIB_CMP_FLAG_NTFS_3G_MODE)))
1270 /* All other attributes can change in UNIX mode, but not in any other
1272 if ((changed & ~(FILE_ATTRIBUTE_NORMAL |
1273 FILE_ATTRIBUTE_DIRECTORY |
1274 FILE_ATTRIBUTE_REPARSE_POINT |
1275 FILE_ATTRIBUTE_SPARSE_FILE |
1276 FILE_ATTRIBUTE_COMPRESSED)) &&
1277 !(cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE))
1283 ERROR("Attribute mismatch for %"TS": 0x%08"PRIx32" vs. 0x%08"PRIx32":",
1284 inode_any_full_path(inode1), inode1->i_attributes,
1285 inode2->i_attributes);
1286 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++) {
1287 u32 flag = file_attr_flags[i].flag;
1288 if (changed & flag) {
1289 fprintf(stderr, "\tFILE_ATTRIBUTE_%s was %s\n",
1290 file_attr_flags[i].name,
1291 (set & flag) ? "set" : "cleared");
1294 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1298 cmp_object_ids(const struct wim_inode *inode1,
1299 const struct wim_inode *inode2, int cmp_flags)
1301 const void *objid1, *objid2;
1304 objid1 = inode_get_object_id(inode1, &len1);
1305 objid2 = inode_get_object_id(inode2, &len2);
1307 if (!objid1 && !objid2)
1310 if (objid1 && !objid2) {
1311 if (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)
1313 ERROR("%"TS" unexpectedly lost its object ID",
1314 inode_any_full_path(inode1));
1315 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1318 if (!objid1 && objid2) {
1319 ERROR("%"TS" unexpectedly gained an object ID",
1320 inode_any_full_path(inode1));
1321 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1324 if (len1 != len2 || memcmp(objid1, objid2, len1) != 0) {
1325 ERROR("Object ID of %"TS" differs",
1326 inode_any_full_path(inode1));
1327 fprintf(stderr, "objid1=");
1328 print_byte_field(objid1, len1, stderr);
1329 fprintf(stderr, "\nobjid2=");
1330 print_byte_field(objid2, len2, stderr);
1331 fprintf(stderr, "\n");
1332 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1339 cmp_unix_metadata(const struct wim_inode *inode1,
1340 const struct wim_inode *inode2, int cmp_flags)
1342 struct wimlib_unix_data dat1, dat2;
1343 bool present1, present2;
1345 present1 = inode_get_unix_data(inode1, &dat1);
1346 present2 = inode_get_unix_data(inode2, &dat2);
1348 if (!present1 && !present2)
1351 if (present1 && !present2) {
1352 if (cmp_flags & (WIMLIB_CMP_FLAG_NTFS_3G_MODE |
1353 WIMLIB_CMP_FLAG_WINDOWS_MODE))
1355 ERROR("%"TS" unexpectedly lost its UNIX metadata",
1356 inode_any_full_path(inode1));
1357 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1360 if (!present1 && present2) {
1361 if (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)
1363 ERROR("%"TS" unexpectedly gained UNIX metadata",
1364 inode_any_full_path(inode1));
1365 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1368 if (memcmp(&dat1, &dat2, sizeof(dat1)) != 0) {
1369 ERROR("UNIX metadata of %"TS" differs: "
1370 "[uid=%u, gid=%u, mode=0%o, rdev=%u] vs. "
1371 "[uid=%u, gid=%u, mode=0%o, rdev=%u]",
1372 inode_any_full_path(inode1),
1373 dat1.uid, dat1.gid, dat1.mode, dat1.rdev,
1374 dat2.uid, dat2.gid, dat2.mode, dat2.rdev);
1375 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1382 cmp_xattr_names(const void *p1, const void *p2)
1384 const struct wimlib_xattr_entry *entry1 = *(const struct wimlib_xattr_entry **)p1;
1385 const struct wimlib_xattr_entry *entry2 = *(const struct wimlib_xattr_entry **)p2;
1386 u16 name_len1 = le16_to_cpu(entry1->name_len);
1387 u16 name_len2 = le16_to_cpu(entry2->name_len);
1390 res = cmp_u32(name_len1, name_len2);
1394 return memcmp(entry1->name, entry2->name, name_len1);
1397 /* Validate and sort by name a list of extended attributes */
1399 parse_xattrs(const void *xattrs, u32 len,
1400 const struct wimlib_xattr_entry *entries[],
1403 u32 limit = *num_entries_p;
1404 u32 num_entries = 0;
1405 const struct wimlib_xattr_entry *entry = xattrs;
1407 while ((void *)entry < xattrs + len) {
1408 if (!valid_xattr_entry(entry, xattrs + len - (void *)entry)) {
1409 ERROR("Invalid xattr entry");
1410 return WIMLIB_ERR_INVALID_XATTR;
1412 if (num_entries >= limit) {
1413 ERROR("Too many xattr entries");
1414 return WIMLIB_ERR_INVALID_XATTR;
1416 entries[num_entries++] = entry;
1417 entry = xattr_entry_next(entry);
1420 if (num_entries == 0) {
1421 ERROR("No xattr entries");
1422 return WIMLIB_ERR_INVALID_XATTR;
1425 qsort(entries, num_entries, sizeof(entries[0]), cmp_xattr_names);
1427 for (u32 i = 1; i < num_entries; i++) {
1428 if (cmp_xattr_names(&entries[i - 1], &entries[i]) == 0) {
1429 ERROR("Duplicate xattr names");
1430 return WIMLIB_ERR_INVALID_XATTR;
1434 *num_entries_p = num_entries;
1439 cmp_linux_xattrs(const struct wim_inode *inode1,
1440 const struct wim_inode *inode2, int cmp_flags)
1442 const void *xattrs1, *xattrs2;
1445 xattrs1 = inode_get_linux_xattrs(inode1, &len1);
1446 xattrs2 = inode_get_linux_xattrs(inode2, &len2);
1448 if (!xattrs1 && !xattrs2) {
1450 } else if (xattrs1 && !xattrs2) {
1451 if (cmp_flags & (WIMLIB_CMP_FLAG_NTFS_3G_MODE |
1452 WIMLIB_CMP_FLAG_WINDOWS_MODE))
1454 ERROR("%"TS" unexpectedly lost its xattrs",
1455 inode_any_full_path(inode1));
1456 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1457 } else if (!xattrs1 && xattrs2) {
1458 ERROR("%"TS" unexpectedly gained xattrs",
1459 inode_any_full_path(inode1));
1460 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1462 const int max_entries = 64;
1463 const struct wimlib_xattr_entry *entries1[max_entries];
1464 const struct wimlib_xattr_entry *entries2[max_entries];
1465 u32 xattr_count1 = max_entries;
1466 u32 xattr_count2 = max_entries;
1469 ret = parse_xattrs(xattrs1, len1, entries1, &xattr_count1);
1471 ERROR("%"TS": invalid xattrs",
1472 inode_any_full_path(inode1));
1475 ret = parse_xattrs(xattrs2, len2, entries2, &xattr_count2);
1477 ERROR("%"TS": invalid xattrs",
1478 inode_any_full_path(inode2));
1481 if (xattr_count1 != xattr_count2) {
1482 ERROR("%"TS": number of xattrs changed. had %u "
1483 "before, now has %u", inode_any_full_path(inode1),
1484 xattr_count1, xattr_count2);
1486 for (u32 i = 0; i < xattr_count1; i++) {
1487 const struct wimlib_xattr_entry *entry1 = entries1[i];
1488 const struct wimlib_xattr_entry *entry2 = entries2[i];
1490 if (entry1->name_len != entry2->name_len ||
1491 entry1->value_len != entry2->value_len ||
1492 entry1->reserved != entry2->reserved ||
1493 memcmp(entry1->name, entry2->name,
1494 le16_to_cpu(entry1->name_len)) ||
1495 memcmp(entry1->name + le16_to_cpu(entry1->name_len),
1496 entry2->name + le16_to_cpu(entry1->name_len),
1497 le32_to_cpu(entry1->value_len)))
1499 ERROR("xattr %.*s of %"TS" differs",
1500 le16_to_cpu(entry1->name_len),
1501 entry1->name, inode_any_full_path(inode1));
1502 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1510 cmp_inodes(const struct wim_inode *inode1, const struct wim_inode *inode2,
1511 const struct wim_image_metadata *imd1,
1512 const struct wim_image_metadata *imd2, int cmp_flags)
1516 /* Compare attributes */
1517 ret = cmp_attributes(inode1, inode2, cmp_flags);
1521 /* Compare security descriptors */
1522 if (inode_has_security_descriptor(inode1)) {
1523 if (inode_has_security_descriptor(inode2)) {
1524 const void *desc1 = imd1->security_data->descriptors[inode1->i_security_id];
1525 const void *desc2 = imd2->security_data->descriptors[inode2->i_security_id];
1526 size_t size1 = imd1->security_data->sizes[inode1->i_security_id];
1527 size_t size2 = imd2->security_data->sizes[inode2->i_security_id];
1529 if (size1 != size2 || memcmp(desc1, desc2, size1)) {
1530 ERROR("Security descriptor of %"TS" differs!",
1531 inode_any_full_path(inode1));
1532 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1534 } else if (!(cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)) {
1535 ERROR("%"TS" has a security descriptor in the first image but "
1536 "not in the second image!", inode_any_full_path(inode1));
1537 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1539 } else if (inode_has_security_descriptor(inode2)) {
1540 /* okay --- consider it acceptable if a default security
1541 * descriptor was assigned */
1542 /*ERROR("%"TS" has a security descriptor in the second image but "*/
1543 /*"not in the first image!", inode_any_full_path(inode1));*/
1544 /*return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;*/
1547 /* Compare streams */
1548 for (unsigned i = 0; i < inode1->i_num_streams; i++) {
1549 const struct wim_inode_stream *strm1 = &inode1->i_streams[i];
1550 const struct wim_inode_stream *strm2;
1552 if (strm1->stream_type == STREAM_TYPE_REPARSE_POINT &&
1553 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE &&
1554 !inode_is_symlink(inode1)))
1557 if (strm1->stream_type == STREAM_TYPE_UNKNOWN)
1560 /* Get the corresponding stream from the second file */
1561 strm2 = inode_get_stream(inode2, strm1->stream_type, strm1->stream_name);
1564 /* Corresponding stream not found */
1565 if (stream_is_named(strm1) &&
1566 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE))
1568 ERROR("Stream of %"TS" is missing in second image; "
1569 "type %d, named=%d, empty=%d",
1570 inode_any_full_path(inode1),
1572 stream_is_named(strm1),
1573 is_zero_hash(stream_hash(strm1)));
1574 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1577 if (!hashes_equal(stream_hash(strm1), stream_hash(strm2))) {
1578 ERROR("Stream of %"TS" differs; type %d",
1579 inode_any_full_path(inode1), strm1->stream_type);
1580 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1584 /* Compare object IDs */
1585 ret = cmp_object_ids(inode1, inode2, cmp_flags);
1589 /* Compare standard UNIX metadata */
1590 ret = cmp_unix_metadata(inode1, inode2, cmp_flags);
1594 /* Compare Linux-style xattrs */
1595 ret = cmp_linux_xattrs(inode1, inode2, cmp_flags);
1603 cmp_images(const struct wim_image_metadata *imd1,
1604 const struct wim_image_metadata *imd2, int cmp_flags)
1606 struct wim_dentry *root1 = imd1->root_dentry;
1607 struct wim_dentry *root2 = imd2->root_dentry;
1608 const struct wim_inode *inode;
1611 ret = calc_corresponding_files_recursive(root1, root2, cmp_flags);
1615 /* Verify that the hard links match up between the two images. */
1616 assert_inodes_sane(imd1);
1617 assert_inodes_sane(imd2);
1618 ret = for_dentry_in_tree(root1, check_hard_link, NULL);
1622 /* Compare corresponding inodes. */
1623 image_for_each_inode(inode, imd1) {
1624 ret = cmp_inodes(inode, inode->i_corresponding,
1625 imd1, imd2, cmp_flags);
1634 load_image(WIMStruct *wim, int image, struct wim_image_metadata **imd_ret)
1636 int ret = select_wim_image(wim, image);
1638 *imd_ret = wim_get_current_image_metadata(wim);
1639 mark_image_dirty(*imd_ret);
1645 wimlib_compare_images(WIMStruct *wim1, int image1,
1646 WIMStruct *wim2, int image2, int cmp_flags)
1649 struct wim_image_metadata *imd1, *imd2;
1651 ret = load_image(wim1, image1, &imd1);
1653 ret = load_image(wim2, image2, &imd2);
1655 ret = cmp_images(imd1, imd2, cmp_flags);
1659 #endif /* ENABLE_TEST_SUPPORT */