2 * test_support.c - Supporting code for tests
6 * Copyright 2015-2023 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 https://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
48 #include "wimlib/endianness.h"
49 #include "wimlib/encoding.h"
50 #include "wimlib/metadata.h"
51 #include "wimlib/dentry.h"
52 #include "wimlib/inode.h"
53 #include "wimlib/object_id.h"
54 #include "wimlib/reparse.h"
55 #include "wimlib/scan.h"
56 #include "wimlib/security_descriptor.h"
57 #include "wimlib/test_support.h"
58 #include "wimlib/timestamp.h"
59 #include "wimlib/unix_data.h"
60 #include "wimlib/xattr.h"
62 /*----------------------------------------------------------------------------*
63 * File tree generation *
64 *----------------------------------------------------------------------------*/
66 struct generation_context {
67 struct scan_params *params;
68 struct wim_dentry *used_short_names[256];
72 static u64 random_state;
75 wimlib_seed_random(u64 seed)
83 /* A simple linear congruential generator */
84 random_state = (random_state * 25214903917 + 11) % (1ULL << 48);
85 return random_state >> 16;
103 return (u16)rand32();
109 return ((u64)rand32() << 32) | rand32();
113 generate_random_timestamp(void)
120 ts = time_t_to_wim_timestamp(rand64() % (1ULL << 34));
122 * When setting timestamps on Windows:
123 * - 0 is a special value meaning "not specified"
124 * - if the high bit is set you get STATUS_INVALID_PARAMETER
126 return max(1, ts % (1ULL << 63));
130 is_valid_windows_filename_char(utf16lechar c)
132 return le16_to_cpu(c) > 31 &&
133 c != cpu_to_le16('/') &&
134 c != cpu_to_le16('<') &&
135 c != cpu_to_le16('>') &&
136 c != cpu_to_le16(':') &&
137 c != cpu_to_le16('"') &&
138 c != cpu_to_le16('/' ) &&
139 c != cpu_to_le16('\\') &&
140 c != cpu_to_le16('|') &&
141 c != cpu_to_le16('?') &&
142 c != cpu_to_le16('*');
145 /* Is the character valid in a filename on the current platform? */
147 is_valid_filename_char(utf16lechar c)
150 return is_valid_windows_filename_char(c);
152 return c != cpu_to_le16('\0') && c != cpu_to_le16('/');
156 /* Generate a random filename and return its length. */
158 generate_random_filename(utf16lechar name[], int max_len,
159 struct generation_context *ctx)
163 /* Choose the length of the name. */
164 switch (rand32() % 8) {
167 len = 1 + (rand32() % 6);
172 /* medium-length name */
173 len = 7 + (rand32() % 8);
178 len = 15 + (rand32() % 15);
182 len = 30 + (rand32() % 90);
185 len = min(len, max_len);
188 /* Generate the characters in the name. */
189 for (int i = 0; i < len; i++) {
191 name[i] = cpu_to_le16(rand16());
192 } while (!is_valid_filename_char(name[i]));
195 /* Add a null terminator. */
196 name[len] = cpu_to_le16('\0');
198 /* Don't generate . and .. */
199 if (name[0] == cpu_to_le16('.') &&
200 (len == 1 || (len == 2 && name[1] == cpu_to_le16('.'))))
206 /* The set of characters which are valid in short filenames. */
207 static const char valid_short_name_chars[] = {
208 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
209 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
210 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
211 '!', '#', '$', '%', '&', '\'', '(', ')', '-', '@', '^', '_', '`', '{',
213 /* Note: Windows does not allow space and 128-255 in short filenames
214 * (tested on both NTFS and FAT). */
218 generate_short_name_component(utf16lechar p[], int len)
220 for (int i = 0; i < len; i++) {
221 char c = valid_short_name_chars[rand32() %
222 ARRAY_LEN(valid_short_name_chars)];
223 p[i] = cpu_to_le16(c);
228 /* Generate a random short (8.3) filename and return its length.
229 * The @name array must have length >= 13 (8 + 1 + 3 + 1). */
231 generate_random_short_name(utf16lechar name[], struct generation_context *ctx)
234 * Legal short names on Windows consist of 1 to 8 characters, optionally
235 * followed by a dot then 1 to 3 more characters. Only certain
236 * characters are allowed.
238 int base_len = 1 + (rand32() % 8);
239 int ext_len = rand32() % 4;
242 base_len = generate_short_name_component(name, base_len);
245 name[base_len] = cpu_to_le16('.');
246 ext_len = generate_short_name_component(&name[base_len + 1],
248 total_len = base_len + 1 + ext_len;
250 total_len = base_len;
252 name[total_len] = cpu_to_le16('\0');
257 static const struct {
258 u8 num_subauthorities;
259 u64 identifier_authority;
260 u32 subauthorities[6];
262 { 1, 0, {0}}, /* NULL_SID */
263 { 1, 1, {0}}, /* WORLD_SID */
264 { 1, 2, {0}}, /* LOCAL_SID */
265 { 1, 3, {0}}, /* CREATOR_OWNER_SID */
266 { 1, 3, {1}}, /* CREATOR_GROUP_SID */
267 { 1, 3, {2}}, /* CREATOR_OWNER_SERVER_SID */
268 { 1, 3, {3}}, /* CREATOR_GROUP_SERVER_SID */
269 // { 0, 5, {}}, /* NT_AUTHORITY_SID */
270 { 1, 5, {1}}, /* DIALUP_SID */
271 { 1, 5, {2}}, /* NETWORK_SID */
272 { 1, 5, {3}}, /* BATCH_SID */
273 { 1, 5, {4}}, /* INTERACTIVE_SID */
274 { 1, 5, {6}}, /* SERVICE_SID */
275 { 1, 5, {7}}, /* ANONYMOUS_LOGON_SID */
276 { 1, 5, {8}}, /* PROXY_SID */
277 { 1, 5, {9}}, /* SERVER_LOGON_SID */
278 { 1, 5, {10}}, /* SELF_SID */
279 { 1, 5, {11}}, /* AUTHENTICATED_USER_SID */
280 { 1, 5, {12}}, /* RESTRICTED_CODE_SID */
281 { 1, 5, {13}}, /* TERMINAL_SERVER_SID */
282 { 1, 5, {18}}, /* NT AUTHORITY\SYSTEM */
283 { 1, 5, {19}}, /* NT AUTHORITY\LOCAL SERVICE */
284 { 1, 5, {20}}, /* NT AUTHORITY\NETWORK SERVICE */
285 { 5 ,80, {956008885, 3418522649, 1831038044, 1853292631, 2271478464}}, /* trusted installer */
286 { 2 ,5, {32, 544} } /* BUILTIN\ADMINISTRATORS */
289 /* Generate a SID and return its size in bytes. */
291 generate_random_sid(wimlib_SID *sid, struct generation_context *ctx)
299 r = (r >> 1) % ARRAY_LEN(common_sids);
301 sid->sub_authority_count = common_sids[r].num_subauthorities;
302 for (int i = 0; i < 6; i++) {
303 sid->identifier_authority[i] =
304 common_sids[r].identifier_authority >> (40 - i * 8);
306 for (int i = 0; i < common_sids[r].num_subauthorities; i++)
307 sid->sub_authority[i] = cpu_to_le32(common_sids[r].subauthorities[i]);
311 sid->sub_authority_count = 1 + ((r >> 1) % 15);
313 for (int i = 0; i < 6; i++)
314 sid->identifier_authority[i] = rand8();
316 for (int i = 0; i < sid->sub_authority_count; i++)
317 sid->sub_authority[i] = cpu_to_le32(rand32());
319 return (u8 *)&sid->sub_authority[sid->sub_authority_count] - (u8 *)sid;
322 /* Generate an ACL and return its size in bytes. */
324 generate_random_acl(wimlib_ACL *acl, bool dacl, struct generation_context *ctx)
329 ace_count = rand32() % 16;
333 acl->ace_count = cpu_to_le16(ace_count);
338 for (int i = 0; i < ace_count; i++) {
339 wimlib_ACCESS_ALLOWED_ACE *ace = (wimlib_ACCESS_ALLOWED_ACE *)p;
341 /* ACCESS_ALLOWED, ACCESS_DENIED, or SYSTEM_AUDIT; format is the
344 ace->hdr.type = rand32() % 2;
347 ace->hdr.flags = rand8();
348 ace->mask = cpu_to_le32(rand32() & 0x001F01FF);
350 p += offsetof(wimlib_ACCESS_ALLOWED_ACE, sid) +
351 generate_random_sid(&ace->sid, ctx);
352 ace->hdr.size = cpu_to_le16(p - (u8 *)ace);
355 acl->acl_size = cpu_to_le16(p - (u8 *)acl);
356 return p - (u8 *)acl;
359 /* Generate a security descriptor and return its size in bytes. */
361 generate_random_security_descriptor(void *_desc, struct generation_context *ctx)
363 wimlib_SECURITY_DESCRIPTOR_RELATIVE *desc = _desc;
369 control &= (wimlib_SE_DACL_AUTO_INHERITED |
370 wimlib_SE_SACL_AUTO_INHERITED);
372 control |= wimlib_SE_SELF_RELATIVE |
373 wimlib_SE_DACL_PRESENT |
374 wimlib_SE_SACL_PRESENT;
378 desc->control = cpu_to_le16(control);
380 p = (u8 *)(desc + 1);
382 desc->owner_offset = cpu_to_le32(p - (u8 *)desc);
383 p += generate_random_sid((wimlib_SID *)p, ctx);
385 desc->group_offset = cpu_to_le32(p - (u8 *)desc);
386 p += generate_random_sid((wimlib_SID *)p, ctx);
388 if ((control & wimlib_SE_DACL_PRESENT) && randbool()) {
389 desc->dacl_offset = cpu_to_le32(p - (u8 *)desc);
390 p += generate_random_acl((wimlib_ACL *)p, true, ctx);
392 desc->dacl_offset = cpu_to_le32(0);
395 if ((control & wimlib_SE_SACL_PRESENT) && randbool()) {
396 desc->sacl_offset = cpu_to_le32(p - (u8 *)desc);
397 p += generate_random_acl((wimlib_ACL *)p, false, ctx);
399 desc->sacl_offset = cpu_to_le32(0);
402 return p - (u8 *)desc;
411 return (getuid() == 0);
441 # define S_IFLNK 0120000
444 # define S_IFSOCK 0140000
449 set_random_unix_metadata(struct wim_inode *inode)
451 struct wimlib_unix_data dat;
453 dat.uid = generate_uid();
454 dat.gid = generate_gid();
455 if (inode_is_symlink(inode))
456 dat.mode = S_IFLNK | 0777;
457 else if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY)
458 dat.mode = S_IFDIR | 0700 | (rand32() % 07777);
459 else if (is_zero_hash(inode_get_hash_of_unnamed_data_stream(inode)) &&
460 randbool() && am_root())
462 dat.mode = rand32() % 07777;
463 switch (rand32() % 4) {
469 dat.rdev = 261; /* /dev/zero */
473 dat.rdev = 261; /* /dev/zero */
476 dat.mode |= S_IFSOCK;
480 dat.mode = S_IFREG | 0400 | (rand32() % 07777);
484 if (!inode_set_unix_data(inode, &dat, UNIX_DATA_ALL))
485 return WIMLIB_ERR_NOMEM;
490 static noinline_for_stack int
491 set_random_xattrs(struct wim_inode *inode)
493 int num_xattrs = 1 + rand32() % 16;
495 struct wim_xattr_entry *entry = (void *)entries;
497 struct wimlib_unix_data unix_data;
499 const char *prefix = "";
501 const char *prefix = "user.";
503 static const char capability_name[] = "security.capability";
504 bool generated_capability_xattr = false;
507 * On Linux, xattrs in the "user" namespace are only permitted on
508 * regular files and directories. For other types of files we can use
509 * the "trusted" namespace, but this requires root.
511 if (inode_is_symlink(inode) ||
512 (inode_get_unix_data(inode, &unix_data) &&
513 !S_ISREG(unix_data.mode) && !S_ISDIR(unix_data.mode)))
520 for (int i = 0; i < num_xattrs; i++) {
521 int value_len = rand32() % 64;
529 entry->value_len = cpu_to_le16(value_len);
532 if (rand32() % 16 == 0 && am_root() &&
533 !generated_capability_xattr) {
534 int name_len = sizeof(capability_name) - 1;
535 entry->name_len = name_len;
536 p = mempcpy(entry->name, capability_name, name_len + 1);
537 generated_capability_xattr = true;
539 int name_len = 1 + rand32() % 64;
541 entry->name_len = strlen(prefix) + name_len;
542 p = mempcpy(entry->name, prefix, strlen(prefix));
544 for (int j = 1; j < name_len; j++) {
547 *p = 'A' + rand8() % 26;
551 } while (*p == '\0');
556 for (int j = 0; j < value_len; j++)
562 entries_size = (char *)entry - entries;
563 wimlib_assert(entries_size > 0 && entries_size <= sizeof(entries));
565 if (!inode_set_xattrs(inode, entries, entries_size))
566 return WIMLIB_ERR_NOMEM;
572 set_random_metadata(struct wim_inode *inode, struct generation_context *ctx)
574 u32 attrib = (rand32() & (FILE_ATTRIBUTE_READONLY |
575 FILE_ATTRIBUTE_HIDDEN |
576 FILE_ATTRIBUTE_SYSTEM |
577 FILE_ATTRIBUTE_ARCHIVE |
578 FILE_ATTRIBUTE_NOT_CONTENT_INDEXED |
579 FILE_ATTRIBUTE_COMPRESSED |
580 FILE_ATTRIBUTE_SPARSE_FILE));
582 /* File attributes */
583 inode->i_attributes |= attrib;
586 inode->i_creation_time = generate_random_timestamp();
587 inode->i_last_access_time = generate_random_timestamp();
588 inode->i_last_write_time = generate_random_timestamp();
590 /* Security descriptor */
592 char desc[8192] __attribute__((aligned(8)));
595 size = generate_random_security_descriptor(desc, ctx);
597 wimlib_assert(size <= sizeof(desc));
599 inode->i_security_id = sd_set_add_sd(ctx->params->sd_set,
601 if (unlikely(inode->i_security_id < 0))
602 return WIMLIB_ERR_NOMEM;
606 if (rand32() % 32 == 0) {
607 struct wimlib_object_id object_id;
609 for (int i = 0; i < sizeof(object_id); i++)
610 *((u8 *)&object_id + i) = rand8();
611 if (!inode_set_object_id(inode, &object_id, sizeof(object_id)))
612 return WIMLIB_ERR_NOMEM;
615 /* Standard UNIX permissions and special files */
616 if (rand32() % 16 == 0) {
617 int ret = set_random_unix_metadata(inode);
622 /* Extended attributes */
623 if (rand32() % 32 == 0) {
624 int ret = set_random_xattrs(inode);
633 /* Choose a random size for generated file data. We want to usually generate
634 * empty, small, or medium files, but occasionally generate large files. */
636 select_stream_size(struct generation_context *ctx)
638 if (ctx->metadata_only)
641 switch (rand32() % 2048) {
647 return rand32() % 64;
650 return rand32() % 4096;
653 return rand32() % 32768;
656 return rand32() % 262144;
659 return rand32() % 134217728;
663 /* Fill 'buffer' with 'size' bytes of "interesting" file data. */
665 generate_data(u8 *buffer, size_t size, struct generation_context *ctx)
668 size_t num_byte_fills = rand32() % 256;
673 /* Start by initializing to a random byte */
674 memset(buffer, rand32() % 256, size);
676 /* Add some random bytes in some random places */
677 for (size_t i = 0; i < num_byte_fills; i++) {
680 size_t count = ((double)size / (double)num_byte_fills) *
681 ((double)rand32() / 2e9);
682 size_t offset = rand32() & ~mask;
686 ((rand32()) & mask)) % size] = b;
690 if (rand32() % 4 == 0)
691 mask = (size_t)-1 << rand32() % 4;
694 /* Sometimes add a wave pattern */
695 if (rand32() % 8 == 0) {
696 double magnitude = rand32() % 128;
697 double scale = 1.0 / (1 + (rand32() % 256));
699 for (size_t i = 0; i < size; i++)
700 buffer[i] += (int)(magnitude * cos(i * scale));
703 /* Sometimes add some zero regions (holes) */
704 if (rand32() % 4 == 0) {
705 size_t num_holes = 1 + (rand32() % 16);
706 for (size_t i = 0; i < num_holes; i++) {
707 size_t hole_offset = rand32() % size;
708 size_t hole_len = min(size - hole_offset,
709 size / (1 + (rand32() % 16)));
710 memset(&buffer[hole_offset], 0, hole_len);
715 static noinline_for_stack int
716 set_random_reparse_point(struct wim_inode *inode, struct generation_context *ctx)
718 struct reparse_buffer_disk rpbuf;
721 inode->i_attributes |= FILE_ATTRIBUTE_REPARSE_POINT;
726 utf16lechar *targets = (utf16lechar *)rpbuf.link.symlink.data;
728 inode->i_reparse_tag = WIM_IO_REPARSE_TAG_SYMLINK;
730 target_nchars = generate_random_filename(targets, 255, ctx);
732 rpbuf.link.substitute_name_offset = cpu_to_le16(0);
733 rpbuf.link.substitute_name_nbytes = cpu_to_le16(2*target_nchars);
734 rpbuf.link.print_name_offset = cpu_to_le16(2*(target_nchars + 1));
735 rpbuf.link.print_name_nbytes = cpu_to_le16(2*target_nchars);
736 targets[target_nchars] = cpu_to_le16(0);
737 memcpy(&targets[target_nchars + 1], targets, 2*target_nchars);
738 targets[target_nchars + 1 + target_nchars] = cpu_to_le16(0);
740 rpbuf.link.symlink.flags = cpu_to_le32(SYMBOLIC_LINK_RELATIVE);
741 rpdatalen = ((u8 *)targets - rpbuf.rpdata) +
742 2*(target_nchars + 1 + target_nchars + 1);
744 rpdatalen = select_stream_size(ctx) % REPARSE_DATA_MAX_SIZE;
745 generate_data(rpbuf.rpdata, rpdatalen, ctx);
747 if (rpdatalen >= GUID_SIZE && randbool()) {
748 /* Non-Microsoft reparse tag (16-byte GUID required) */
749 u8 *guid = rpbuf.rpdata;
750 guid[6] = (guid[6] & 0x0F) | 0x40;
751 guid[8] = (guid[8] & 0x3F) | 0x80;
752 inode->i_reparse_tag = 0x00000100;
754 /* Microsoft reparse tag */
755 inode->i_reparse_tag = 0x80000000;
757 inode->i_rp_reserved = rand16();
760 wimlib_assert(rpdatalen < REPARSE_DATA_MAX_SIZE);
762 if (!inode_add_stream_with_data(inode, STREAM_TYPE_REPARSE_POINT,
763 NO_STREAM_NAME, rpbuf.rpdata,
764 rpdatalen, ctx->params->blob_table))
765 return WIMLIB_ERR_NOMEM;
771 add_random_data_stream(struct wim_inode *inode, struct generation_context *ctx,
772 const utf16lechar *stream_name)
778 size = select_stream_size(ctx);
780 buffer = MALLOC(size);
782 return WIMLIB_ERR_NOMEM;
783 generate_data(buffer, size, ctx);
787 if (!inode_add_stream_with_data(inode, STREAM_TYPE_DATA, stream_name,
788 buffer, size, ctx->params->blob_table))
789 ret = WIMLIB_ERR_NOMEM;
795 set_random_streams(struct wim_inode *inode, struct generation_context *ctx)
800 /* Reparse point (sometimes) */
801 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
802 ret = set_random_reparse_point(inode, ctx);
807 /* Unnamed data stream (nondirectories and non-symlinks only) */
808 if (!(inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) &&
809 !inode_is_symlink(inode)) {
810 ret = add_random_data_stream(inode, ctx, NO_STREAM_NAME);
815 /* Named data streams (sometimes) */
818 utf16lechar stream_name[2] = {cpu_to_le16('a'), '\0'};
821 ret = add_random_data_stream(inode, ctx, stream_name);
825 cpu_to_le16(le16_to_cpu(stream_name[0]) + 1);
833 select_inode_number(struct generation_context *ctx)
835 const struct wim_inode_table *table = ctx->params->inode_table;
836 const struct hlist_head *head;
837 const struct wim_inode *inode;
839 head = &table->array[rand32() % table->capacity];
840 hlist_for_each_entry(inode, head, i_hlist_node)
848 select_num_children(u32 depth, struct generation_context *ctx)
850 const double b = 1.01230;
851 u32 r = rand32() % 500;
852 return ((pow(b, pow(b, r)) - 1) / pow(depth, 1.5)) +
853 (2 - exp(0.04/depth));
857 is_name_valid_in_win32_namespace(const utf16lechar *name)
859 const utf16lechar *p;
861 static const char * const reserved_names[] = {
862 "CON", "PRN", "AUX", "NUL",
863 "COM1", "COM2", "COM3", "COM4", "COM5",
864 "COM6", "COM7", "COM8", "COM9",
865 "LPT1", "LPT2", "LPT3", "LPT4", "LPT5",
866 "LPT6", "LPT7", "LPT8", "LPT9",
869 /* The name must be nonempty. */
873 /* All characters must be valid on Windows. */
874 for (p = name; *p; p++)
875 if (!is_valid_windows_filename_char(*p))
878 /* Note: a trailing dot or space is permitted, even though on Windows
879 * such a file can only be accessed using a WinNT-style path. */
881 /* The name can't be one of the reserved names or be a reserved name
882 * with an extension. Case insensitive. */
883 for (size_t i = 0; i < ARRAY_LEN(reserved_names); i++) {
884 for (size_t j = 0; ; j++) {
885 u16 c1 = le16_to_cpu(name[j]);
886 u16 c2 = reserved_names[i][j];
888 if (c1 == '\0' || c1 == '.')
892 if (upcase[c1] != upcase[c2])
901 set_random_short_name(struct wim_dentry *dir, struct wim_dentry *child,
902 struct generation_context *ctx)
904 utf16lechar name[12 + 1];
907 struct wim_dentry **bucket;
909 /* If the long name is not allowed in the Win32 namespace, then it
910 * cannot be assigned a corresponding short name. */
911 if (!is_name_valid_in_win32_namespace(child->d_name))
915 /* Don't select a short name that is already used by a long name within
916 * the same directory. */
918 name_len = generate_random_short_name(name, ctx);
919 } while (get_dentry_child_with_utf16le_name(dir, name, name_len * 2,
920 WIMLIB_CASE_INSENSITIVE));
923 /* Don't select a short name that is already used by another short name
924 * within the same directory. */
926 for (const utf16lechar *p = name; *p; p++)
927 hash = (hash * 31) + le16_to_cpu(*p);
928 FREE(child->d_short_name);
929 child->d_short_name = memdup(name, (name_len + 1) * 2);
930 child->d_short_name_nbytes = name_len * 2;
932 if (!child->d_short_name)
933 return WIMLIB_ERR_NOMEM;
935 bucket = &ctx->used_short_names[hash % ARRAY_LEN(ctx->used_short_names)];
937 for (struct wim_dentry *d = *bucket; d != NULL;
938 d = d->d_next_extraction_alias) {
939 if (!cmp_utf16le_strings(child->d_short_name, name_len,
940 d->d_short_name, d->d_short_name_nbytes / 2,
946 if (!is_name_valid_in_win32_namespace(child->d_short_name))
949 child->d_next_extraction_alias = *bucket;
955 inode_has_short_name(const struct wim_inode *inode)
957 const struct wim_dentry *dentry;
959 inode_for_each_dentry(dentry, inode)
960 if (dentry_has_short_name(dentry))
967 generate_dentry_tree_recursive(struct wim_dentry *dir, u32 depth,
968 struct generation_context *ctx)
970 u32 num_children = select_num_children(depth, ctx);
971 struct wim_dentry *child;
974 memset(ctx->used_short_names, 0, sizeof(ctx->used_short_names));
976 /* Generate 'num_children' dentries within 'dir'. Some may be
977 * directories themselves. */
979 for (u32 i = 0; i < num_children; i++) {
981 /* Generate the next child dentry. */
982 struct wim_inode *inode;
984 bool is_directory = (rand32() % 16 <= 6);
985 bool is_reparse = (rand32() % 8 == 0);
986 utf16lechar name[63 + 1]; /* for UNIX extraction: 63 * 4 <= 255 */
988 struct wim_dentry *duplicate;
991 * Select an inode number for the new file. Sometimes choose an
992 * existing inode number (i.e. create a hard link). However,
993 * wimlib intentionally doesn't honor directory hard links, and
994 * reparse points cannot be represented in the WIM file format
995 * at all; so don't create hard links for such files.
997 if (is_directory || is_reparse)
1000 ino = select_inode_number(ctx);
1002 /* Create the dentry. */
1003 ret = inode_table_new_dentry(ctx->params->inode_table, NULL,
1004 ino, 0, ino == 0, &child);
1008 /* Choose a filename that is unique within the directory.*/
1010 name_len = generate_random_filename(name,
1011 ARRAY_LEN(name) - 1,
1013 } while (get_dentry_child_with_utf16le_name(dir, name, name_len * 2,
1014 WIMLIB_CASE_PLATFORM_DEFAULT));
1016 ret = dentry_set_name_utf16le(child, name, name_len * 2);
1022 /* Add the dentry to the directory. */
1023 duplicate = dentry_add_child(dir, child);
1024 wimlib_assert(!duplicate);
1026 inode = child->d_inode;
1028 if (inode->i_nlink > 1) /* Existing inode? */
1031 /* New inode; set attributes, metadata, and data. */
1034 inode->i_attributes |= FILE_ATTRIBUTE_DIRECTORY;
1036 inode->i_attributes |= FILE_ATTRIBUTE_REPARSE_POINT;
1038 ret = set_random_streams(inode, ctx);
1042 ret = set_random_metadata(inode, ctx);
1046 /* Recurse if it's a directory. */
1047 if (is_directory && !is_reparse) {
1048 ret = generate_dentry_tree_recursive(child, depth + 1,
1055 for_dentry_child(child, dir) {
1056 /* sometimes generate a unique short name */
1057 if (randbool() && !inode_has_short_name(child->d_inode)) {
1058 ret = set_random_short_name(dir, child, ctx);
1068 generate_dentry_tree(struct wim_dentry **root_ret, const tchar *_ignored,
1069 struct scan_params *params)
1072 struct wim_dentry *root = NULL;
1073 struct generation_context ctx = {
1077 ctx.metadata_only = ((rand32() % 8) != 0); /* usually metadata only */
1079 ret = inode_table_new_dentry(params->inode_table, NULL, 0, 0, true, &root);
1081 root->d_inode->i_attributes = FILE_ATTRIBUTE_DIRECTORY;
1082 ret = set_random_streams(root->d_inode, &ctx);
1085 ret = set_random_metadata(root->d_inode, &ctx);
1087 ret = generate_dentry_tree_recursive(root, 1, &ctx);
1091 free_dentry_tree(root, params->blob_table);
1095 /*----------------------------------------------------------------------------*
1096 * File tree comparison *
1097 *----------------------------------------------------------------------------*/
1099 #define INDEX_NODE_TO_DENTRY(node) \
1100 ((node) ? avl_tree_entry((node), struct wim_dentry, d_index_node) : NULL)
1102 static struct wim_dentry *
1103 dentry_first_child(struct wim_dentry *dentry)
1105 return INDEX_NODE_TO_DENTRY(
1106 avl_tree_first_in_order(dentry->d_inode->i_children));
1109 static struct wim_dentry *
1110 dentry_next_sibling(struct wim_dentry *dentry)
1112 return INDEX_NODE_TO_DENTRY(
1113 avl_tree_next_in_order(&dentry->d_index_node));
1117 * Verify that the dentries in the tree 'd1' exactly match the dentries in the
1118 * tree 'd2', considering long and short filenames. In addition, set
1119 * 'd_corresponding' of each dentry to point to the corresponding dentry in the
1120 * other tree, and set 'i_corresponding' of each inode to point to the
1121 * unverified corresponding inode in the other tree.
1124 calc_corresponding_files_recursive(struct wim_dentry *d1, struct wim_dentry *d2,
1127 struct wim_dentry *child1;
1128 struct wim_dentry *child2;
1131 /* Compare long filenames, case sensitively. */
1132 if (cmp_utf16le_strings(d1->d_name, d1->d_name_nbytes / 2,
1133 d2->d_name, d2->d_name_nbytes / 2,
1136 ERROR("Filename mismatch; path1=\"%"TS"\", path2=\"%"TS"\"",
1137 dentry_full_path(d1), dentry_full_path(d2));
1138 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1141 /* Compare short filenames, case insensitively. */
1142 if (!(d2->d_short_name_nbytes == 0 &&
1143 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)) &&
1144 cmp_utf16le_strings(d1->d_short_name, d1->d_short_name_nbytes / 2,
1145 d2->d_short_name, d2->d_short_name_nbytes / 2,
1148 ERROR("Short name mismatch; path=\"%"TS"\"",
1149 dentry_full_path(d1));
1150 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1153 /* Match up the dentries */
1154 d1->d_corresponding = d2;
1155 d2->d_corresponding = d1;
1157 /* Match up the inodes (may overwrite previous value) */
1158 d1->d_inode->i_corresponding = d2->d_inode;
1159 d2->d_inode->i_corresponding = d1->d_inode;
1161 /* Process children */
1162 child1 = dentry_first_child(d1);
1163 child2 = dentry_first_child(d2);
1164 while (child1 || child2) {
1166 if (!child1 || !child2) {
1167 ERROR("Child count mismatch; "
1168 "path1=\"%"TS"\", path2=\"%"TS"\"",
1169 dentry_full_path(d1), dentry_full_path(d2));
1170 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1173 /* Recurse on this pair of children. */
1174 ret = calc_corresponding_files_recursive(child1, child2,
1179 /* Continue to the next pair of children. */
1180 child1 = dentry_next_sibling(child1);
1181 child2 = dentry_next_sibling(child2);
1186 /* Perform sanity checks on an image's inodes. All assertions here should pass,
1187 * even if the images being compared are different. */
1189 assert_inodes_sane(const struct wim_image_metadata *imd)
1191 const struct wim_inode *inode;
1192 const struct wim_dentry *dentry;
1195 image_for_each_inode(inode, imd) {
1197 inode_for_each_dentry(dentry, inode) {
1198 wimlib_assert(dentry->d_inode == inode);
1201 wimlib_assert(link_count > 0);
1202 wimlib_assert(link_count == inode->i_nlink);
1203 wimlib_assert(inode->i_corresponding != NULL);
1208 check_hard_link(struct wim_dentry *dentry, void *_ignore)
1210 /* My inode is my corresponding dentry's inode's corresponding inode,
1211 * and my inode's corresponding inode is my corresponding dentry's
1213 const struct wim_inode *a = dentry->d_inode;
1214 const struct wim_inode *b = dentry->d_corresponding->d_inode;
1215 if (a == b->i_corresponding && a->i_corresponding == b)
1217 ERROR("Hard link difference; path=%"TS"", dentry_full_path(dentry));
1218 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1221 static const struct {
1224 } file_attr_flags[] = {
1225 {FILE_ATTRIBUTE_READONLY, "READONLY"},
1226 {FILE_ATTRIBUTE_HIDDEN, "HIDDEN"},
1227 {FILE_ATTRIBUTE_SYSTEM, "SYSTEM"},
1228 {FILE_ATTRIBUTE_DIRECTORY, "DIRECTORY"},
1229 {FILE_ATTRIBUTE_ARCHIVE, "ARCHIVE"},
1230 {FILE_ATTRIBUTE_DEVICE, "DEVICE"},
1231 {FILE_ATTRIBUTE_NORMAL, "NORMAL"},
1232 {FILE_ATTRIBUTE_TEMPORARY, "TEMPORARY"},
1233 {FILE_ATTRIBUTE_SPARSE_FILE, "SPARSE_FILE"},
1234 {FILE_ATTRIBUTE_REPARSE_POINT, "REPARSE_POINT"},
1235 {FILE_ATTRIBUTE_COMPRESSED, "COMPRESSED"},
1236 {FILE_ATTRIBUTE_OFFLINE, "OFFLINE"},
1237 {FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, "NOT_CONTENT_INDEXED"},
1238 {FILE_ATTRIBUTE_ENCRYPTED, "ENCRYPTED"},
1239 {FILE_ATTRIBUTE_VIRTUAL, "VIRTUAL"},
1243 cmp_attributes(const struct wim_inode *inode1,
1244 const struct wim_inode *inode2, int cmp_flags)
1246 const u32 changed = inode1->i_attributes ^ inode2->i_attributes;
1247 const u32 set = inode2->i_attributes & ~inode1->i_attributes;
1248 const u32 cleared = inode1->i_attributes & ~inode2->i_attributes;
1250 /* NORMAL may change, but it must never be set along with other
1252 if ((inode2->i_attributes & FILE_ATTRIBUTE_NORMAL) &&
1253 (inode2->i_attributes & ~FILE_ATTRIBUTE_NORMAL))
1256 /* DIRECTORY may change in UNIX mode for symlinks. */
1257 if (changed & FILE_ATTRIBUTE_DIRECTORY) {
1258 if (!(inode_is_symlink(inode1) &&
1259 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)))
1263 /* REPARSE_POINT may be cleared in UNIX mode if the inode is not a
1265 if ((changed & FILE_ATTRIBUTE_REPARSE_POINT) &&
1266 !((cleared & FILE_ATTRIBUTE_REPARSE_POINT) &&
1267 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE) &&
1268 !inode_is_symlink(inode1)))
1271 /* SPARSE_FILE may be cleared. This is true in UNIX and NTFS-3G modes.
1272 * In Windows mode it should only be true for directories, but even on
1273 * nondirectories it doesn't work 100% of the time for some reason. */
1274 if ((changed & FILE_ATTRIBUTE_SPARSE_FILE) &&
1275 !(cleared & FILE_ATTRIBUTE_SPARSE_FILE))
1278 /* COMPRESSED may change in UNIX and NTFS-3G modes. (It *should* be
1279 * preserved in NTFS-3G mode, but it's not implemented yet.) */
1280 if ((changed & FILE_ATTRIBUTE_COMPRESSED) &&
1281 !(cmp_flags & (WIMLIB_CMP_FLAG_UNIX_MODE |
1282 WIMLIB_CMP_FLAG_NTFS_3G_MODE)))
1285 /* All other attributes can change in UNIX mode, but not in any other
1287 if ((changed & ~(FILE_ATTRIBUTE_NORMAL |
1288 FILE_ATTRIBUTE_DIRECTORY |
1289 FILE_ATTRIBUTE_REPARSE_POINT |
1290 FILE_ATTRIBUTE_SPARSE_FILE |
1291 FILE_ATTRIBUTE_COMPRESSED)) &&
1292 !(cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE))
1298 ERROR("Attribute mismatch for %"TS": 0x%08"PRIx32" vs. 0x%08"PRIx32":",
1299 inode_any_full_path(inode1), inode1->i_attributes,
1300 inode2->i_attributes);
1301 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++) {
1302 u32 flag = file_attr_flags[i].flag;
1303 if (changed & flag) {
1304 fprintf(stderr, "\tFILE_ATTRIBUTE_%s was %s\n",
1305 file_attr_flags[i].name,
1306 (set & flag) ? "set" : "cleared");
1309 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1313 print_security_descriptor(const void *desc, size_t size, FILE *fp)
1315 print_byte_field(desc, size, fp);
1317 wchar_t *str = NULL;
1318 ConvertSecurityDescriptorToStringSecurityDescriptorW(
1321 OWNER_SECURITY_INFORMATION |
1322 GROUP_SECURITY_INFORMATION |
1323 DACL_SECURITY_INFORMATION |
1324 SACL_SECURITY_INFORMATION,
1328 fprintf(fp, " [ %ls ]", str);
1335 cmp_security(const struct wim_inode *inode1, const struct wim_inode *inode2,
1336 const struct wim_image_metadata *imd1,
1337 const struct wim_image_metadata *imd2, int cmp_flags)
1340 * Unfortunately this has to be disabled on Windows for now, since
1341 * Windows changes security descriptors upon backup/restore in ways that
1342 * are difficult to replicate...
1344 if (cmp_flags & WIMLIB_CMP_FLAG_WINDOWS_MODE)
1347 if (inode_has_security_descriptor(inode1)) {
1348 if (inode_has_security_descriptor(inode2)) {
1349 const void *desc1 = imd1->security_data->descriptors[inode1->i_security_id];
1350 const void *desc2 = imd2->security_data->descriptors[inode2->i_security_id];
1351 size_t size1 = imd1->security_data->sizes[inode1->i_security_id];
1352 size_t size2 = imd2->security_data->sizes[inode2->i_security_id];
1354 if (size1 != size2 || memcmp(desc1, desc2, size1)) {
1355 ERROR("Security descriptor of %"TS" differs!",
1356 inode_any_full_path(inode1));
1357 fprintf(stderr, "desc1=");
1358 print_security_descriptor(desc1, size1, stderr);
1359 fprintf(stderr, "\ndesc2=");
1360 print_security_descriptor(desc2, size2, stderr);
1361 fprintf(stderr, "\n");
1362 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1364 } else if (!(cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)) {
1365 ERROR("%"TS" has a security descriptor in the first image but "
1366 "not in the second image!", inode_any_full_path(inode1));
1367 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1369 } else if (inode_has_security_descriptor(inode2)) {
1370 /* okay --- consider it acceptable if a default security
1371 * descriptor was assigned */
1372 /*ERROR("%"TS" has a security descriptor in the second image but "*/
1373 /*"not in the first image!", inode_any_full_path(inode1));*/
1374 /*return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;*/
1380 cmp_object_ids(const struct wim_inode *inode1,
1381 const struct wim_inode *inode2, int cmp_flags)
1383 const void *objid1, *objid2;
1386 objid1 = inode_get_object_id(inode1, &len1);
1387 objid2 = inode_get_object_id(inode2, &len2);
1389 if (!objid1 && !objid2)
1392 if (objid1 && !objid2) {
1393 if (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)
1395 ERROR("%"TS" unexpectedly lost its object ID",
1396 inode_any_full_path(inode1));
1397 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1400 if (!objid1 && objid2) {
1401 ERROR("%"TS" unexpectedly gained an object ID",
1402 inode_any_full_path(inode1));
1403 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1406 if (len1 != len2 || memcmp(objid1, objid2, len1) != 0) {
1407 ERROR("Object ID of %"TS" differs",
1408 inode_any_full_path(inode1));
1409 fprintf(stderr, "objid1=");
1410 print_byte_field(objid1, len1, stderr);
1411 fprintf(stderr, "\nobjid2=");
1412 print_byte_field(objid2, len2, stderr);
1413 fprintf(stderr, "\n");
1414 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1421 cmp_unix_metadata(const struct wim_inode *inode1,
1422 const struct wim_inode *inode2, int cmp_flags)
1424 struct wimlib_unix_data dat1, dat2;
1425 bool present1, present2;
1427 present1 = inode_get_unix_data(inode1, &dat1);
1428 present2 = inode_get_unix_data(inode2, &dat2);
1430 if (!present1 && !present2)
1433 if (present1 && !present2) {
1434 if (cmp_flags & (WIMLIB_CMP_FLAG_NTFS_3G_MODE |
1435 WIMLIB_CMP_FLAG_WINDOWS_MODE))
1437 ERROR("%"TS" unexpectedly lost its UNIX metadata",
1438 inode_any_full_path(inode1));
1439 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1442 if (!present1 && present2) {
1443 if (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)
1445 ERROR("%"TS" unexpectedly gained UNIX metadata",
1446 inode_any_full_path(inode1));
1447 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1450 if (memcmp(&dat1, &dat2, sizeof(dat1)) != 0) {
1451 ERROR("UNIX metadata of %"TS" differs: "
1452 "[uid=%u, gid=%u, mode=0%o, rdev=%u] vs. "
1453 "[uid=%u, gid=%u, mode=0%o, rdev=%u]",
1454 inode_any_full_path(inode1),
1455 dat1.uid, dat1.gid, dat1.mode, dat1.rdev,
1456 dat2.uid, dat2.gid, dat2.mode, dat2.rdev);
1457 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1464 cmp_xattr_names(const void *p1, const void *p2)
1466 const struct wim_xattr_entry *entry1 = *(const struct wim_xattr_entry **)p1;
1467 const struct wim_xattr_entry *entry2 = *(const struct wim_xattr_entry **)p2;
1470 res = entry1->name_len - entry2->name_len;
1474 return memcmp(entry1->name, entry2->name, entry1->name_len);
1477 /* Validate and sort by name a list of extended attributes */
1479 parse_xattrs(const void *xattrs, u32 len,
1480 const struct wim_xattr_entry *entries[],
1483 u32 limit = *num_entries_p;
1484 u32 num_entries = 0;
1485 const struct wim_xattr_entry *entry = xattrs;
1487 while ((void *)entry < xattrs + len) {
1488 if (!valid_xattr_entry(entry, xattrs + len - (void *)entry)) {
1489 ERROR("Invalid xattr entry");
1490 return WIMLIB_ERR_INVALID_XATTR;
1492 if (num_entries >= limit) {
1493 ERROR("Too many xattr entries");
1494 return WIMLIB_ERR_INVALID_XATTR;
1496 entries[num_entries++] = entry;
1497 entry = xattr_entry_next(entry);
1500 if (num_entries == 0) {
1501 ERROR("No xattr entries");
1502 return WIMLIB_ERR_INVALID_XATTR;
1505 qsort(entries, num_entries, sizeof(entries[0]), cmp_xattr_names);
1507 for (u32 i = 1; i < num_entries; i++) {
1508 if (cmp_xattr_names(&entries[i - 1], &entries[i]) == 0) {
1509 ERROR("Duplicate xattr names");
1510 return WIMLIB_ERR_INVALID_XATTR;
1514 *num_entries_p = num_entries;
1519 cmp_xattrs(const struct wim_inode *inode1, const struct wim_inode *inode2,
1522 const void *xattrs1, *xattrs2;
1525 xattrs1 = inode_get_xattrs(inode1, &len1);
1526 xattrs2 = inode_get_xattrs(inode2, &len2);
1528 if (!xattrs1 && !xattrs2) {
1530 } else if (xattrs1 && !xattrs2) {
1531 if (cmp_flags & WIMLIB_CMP_FLAG_NTFS_3G_MODE)
1533 ERROR("%"TS" unexpectedly lost its xattrs",
1534 inode_any_full_path(inode1));
1535 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1536 } else if (!xattrs1 && xattrs2) {
1537 ERROR("%"TS" unexpectedly gained xattrs",
1538 inode_any_full_path(inode1));
1539 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1541 const int max_entries = 64;
1542 const struct wim_xattr_entry *entries1[max_entries];
1543 const struct wim_xattr_entry *entries2[max_entries];
1544 u32 xattr_count1 = max_entries;
1545 u32 xattr_count2 = max_entries;
1548 ret = parse_xattrs(xattrs1, len1, entries1, &xattr_count1);
1550 ERROR("%"TS": invalid xattrs",
1551 inode_any_full_path(inode1));
1554 ret = parse_xattrs(xattrs2, len2, entries2, &xattr_count2);
1556 ERROR("%"TS": invalid xattrs",
1557 inode_any_full_path(inode2));
1560 if (xattr_count1 != xattr_count2) {
1561 ERROR("%"TS": number of xattrs changed. had %u "
1562 "before, now has %u", inode_any_full_path(inode1),
1563 xattr_count1, xattr_count2);
1565 for (u32 i = 0; i < xattr_count1; i++) {
1566 const struct wim_xattr_entry *entry1 = entries1[i];
1567 const struct wim_xattr_entry *entry2 = entries2[i];
1569 if (entry1->value_len != entry2->value_len ||
1570 entry1->name_len != entry2->name_len ||
1571 entry1->flags != entry2->flags ||
1572 memcmp(entry1->name, entry2->name,
1573 entry1->name_len) ||
1574 memcmp(entry1->name + entry1->name_len + 1,
1575 entry2->name + entry2->name_len + 1,
1576 le16_to_cpu(entry1->value_len)))
1578 ERROR("xattr %.*s of %"TS" differs",
1579 entry1->name_len, entry1->name,
1580 inode_any_full_path(inode1));
1581 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1589 * ext4 only supports timestamps from years 1901 to 2446, more specifically the
1590 * range [-0x80000000, 0x380000000) seconds relative to the start of UNIX epoch.
1593 in_ext4_range(u64 ts)
1595 return ts >= time_t_to_wim_timestamp(-0x80000000LL) &&
1596 ts < time_t_to_wim_timestamp(0x380000000LL);
1600 timestamps_differ(u64 ts1, u64 ts2, int cmp_flags)
1604 if ((cmp_flags & WIMLIB_CMP_FLAG_EXT4) &&
1605 (!in_ext4_range(ts1) || !in_ext4_range(ts2)))
1611 cmp_timestamps(const struct wim_inode *inode1, const struct wim_inode *inode2,
1614 if (timestamps_differ(inode1->i_creation_time,
1615 inode2->i_creation_time, cmp_flags) &&
1616 !(cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)) {
1617 ERROR("Creation time of %"TS" differs; %"PRIu64" != %"PRIu64,
1618 inode_any_full_path(inode1),
1619 inode1->i_creation_time, inode2->i_creation_time);
1620 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1623 if (timestamps_differ(inode1->i_last_write_time,
1624 inode2->i_last_write_time, cmp_flags)) {
1625 ERROR("Last write time of %"TS" differs; %"PRIu64" != %"PRIu64,
1626 inode_any_full_path(inode1),
1627 inode1->i_last_write_time, inode2->i_last_write_time);
1628 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1631 if (timestamps_differ(inode1->i_last_access_time,
1632 inode2->i_last_access_time, cmp_flags) &&
1634 * On Windows, sometimes a file's last access time will end up as
1635 * the current time rather than the expected time. Maybe caused by
1636 * some OS process scanning the files?
1638 !(cmp_flags & WIMLIB_CMP_FLAG_WINDOWS_MODE)) {
1639 ERROR("Last access time of %"TS" differs; %"PRIu64" != %"PRIu64,
1640 inode_any_full_path(inode1),
1641 inode1->i_last_access_time, inode2->i_last_access_time);
1642 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1649 cmp_inodes(const struct wim_inode *inode1, const struct wim_inode *inode2,
1650 const struct wim_image_metadata *imd1,
1651 const struct wim_image_metadata *imd2, int cmp_flags)
1655 /* Compare attributes */
1656 ret = cmp_attributes(inode1, inode2, cmp_flags);
1660 /* Compare security descriptors */
1661 ret = cmp_security(inode1, inode2, imd1, imd2, cmp_flags);
1665 /* Compare streams */
1666 for (unsigned i = 0; i < inode1->i_num_streams; i++) {
1667 const struct wim_inode_stream *strm1 = &inode1->i_streams[i];
1668 const struct wim_inode_stream *strm2;
1670 if (strm1->stream_type == STREAM_TYPE_REPARSE_POINT &&
1671 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE &&
1672 !inode_is_symlink(inode1)))
1675 if (strm1->stream_type == STREAM_TYPE_UNKNOWN)
1678 /* Get the corresponding stream from the second file */
1679 strm2 = inode_get_stream(inode2, strm1->stream_type, strm1->stream_name);
1682 /* Corresponding stream not found */
1683 if (stream_is_named(strm1) &&
1684 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE))
1686 ERROR("Stream of %"TS" is missing in second image; "
1687 "type %d, named=%d, empty=%d",
1688 inode_any_full_path(inode1),
1690 stream_is_named(strm1),
1691 is_zero_hash(stream_hash(strm1)));
1692 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1695 if (!hashes_equal(stream_hash(strm1), stream_hash(strm2))) {
1696 ERROR("Stream of %"TS" differs; type %d",
1697 inode_any_full_path(inode1), strm1->stream_type);
1698 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1702 /* Compare object IDs */
1703 ret = cmp_object_ids(inode1, inode2, cmp_flags);
1707 /* Compare timestamps */
1708 ret = cmp_timestamps(inode1, inode2, cmp_flags);
1712 /* Compare standard UNIX metadata */
1713 ret = cmp_unix_metadata(inode1, inode2, cmp_flags);
1717 /* Compare extended attributes */
1718 ret = cmp_xattrs(inode1, inode2, cmp_flags);
1726 cmp_images(const struct wim_image_metadata *imd1,
1727 const struct wim_image_metadata *imd2, int cmp_flags)
1729 struct wim_dentry *root1 = imd1->root_dentry;
1730 struct wim_dentry *root2 = imd2->root_dentry;
1731 const struct wim_inode *inode;
1734 ret = calc_corresponding_files_recursive(root1, root2, cmp_flags);
1738 /* Verify that the hard links match up between the two images. */
1739 assert_inodes_sane(imd1);
1740 assert_inodes_sane(imd2);
1741 ret = for_dentry_in_tree(root1, check_hard_link, NULL);
1745 /* Compare corresponding inodes. */
1746 image_for_each_inode(inode, imd1) {
1747 ret = cmp_inodes(inode, inode->i_corresponding,
1748 imd1, imd2, cmp_flags);
1757 load_image(WIMStruct *wim, int image, struct wim_image_metadata **imd_ret)
1759 int ret = select_wim_image(wim, image);
1761 *imd_ret = wim_get_current_image_metadata(wim);
1762 mark_image_dirty(*imd_ret);
1768 wimlib_compare_images(WIMStruct *wim1, int image1,
1769 WIMStruct *wim2, int image2, int cmp_flags)
1772 struct wim_image_metadata *imd1, *imd2;
1774 ret = load_image(wim1, image1, &imd1);
1776 ret = load_image(wim2, image2, &imd2);
1778 ret = cmp_images(imd1, imd2, cmp_flags);
1782 #endif /* ENABLE_TEST_SUPPORT */