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);
683 add_stream(struct wim_inode *inode, struct generation_context *ctx,
684 int stream_type, const utf16lechar *stream_name,
685 void *buffer, size_t size)
687 struct blob_descriptor *blob = NULL;
688 struct wim_inode_stream *strm;
691 blob = new_blob_descriptor();
694 blob->attached_buffer = buffer;
695 blob->blob_location = BLOB_IN_ATTACHED_BUFFER;
699 strm = inode_add_stream(inode, stream_type, stream_name, blob);
703 prepare_unhashed_blob(blob, inode, strm->stream_id,
704 ctx->params->unhashed_blobs);
708 free_blob_descriptor(blob);
709 return WIMLIB_ERR_NOMEM;
712 static noinline_for_stack int
713 set_random_reparse_point(struct wim_inode *inode, struct generation_context *ctx)
715 struct reparse_buffer_disk rpbuf;
718 inode->i_attributes |= FILE_ATTRIBUTE_REPARSE_POINT;
723 utf16lechar *targets = (utf16lechar *)rpbuf.link.symlink.data;
725 inode->i_reparse_tag = WIM_IO_REPARSE_TAG_SYMLINK;
727 target_nchars = generate_random_filename(targets, 255, ctx);
729 rpbuf.link.substitute_name_offset = cpu_to_le16(0);
730 rpbuf.link.substitute_name_nbytes = cpu_to_le16(2*target_nchars);
731 rpbuf.link.print_name_offset = cpu_to_le16(2*(target_nchars + 1));
732 rpbuf.link.print_name_nbytes = cpu_to_le16(2*target_nchars);
733 targets[target_nchars] = cpu_to_le16(0);
734 memcpy(&targets[target_nchars + 1], targets, 2*target_nchars);
735 targets[target_nchars + 1 + target_nchars] = cpu_to_le16(0);
737 rpbuf.link.symlink.flags = cpu_to_le32(SYMBOLIC_LINK_RELATIVE);
738 rpdatalen = ((u8 *)targets - rpbuf.rpdata) +
739 2*(target_nchars + 1 + target_nchars + 1);
741 rpdatalen = select_stream_size(ctx) % REPARSE_DATA_MAX_SIZE;
742 generate_data(rpbuf.rpdata, rpdatalen, ctx);
744 if (rpdatalen >= GUID_SIZE && randbool()) {
745 /* Non-Microsoft reparse tag (16-byte GUID required) */
746 u8 *guid = rpbuf.rpdata;
747 guid[6] = (guid[6] & 0x0F) | 0x40;
748 guid[8] = (guid[8] & 0x3F) | 0x80;
749 inode->i_reparse_tag = 0x00000100;
751 /* Microsoft reparse tag */
752 inode->i_reparse_tag = 0x80000000;
754 inode->i_rp_reserved = rand16();
757 wimlib_assert(rpdatalen < REPARSE_DATA_MAX_SIZE);
759 if (!inode_add_stream_with_data(inode, STREAM_TYPE_REPARSE_POINT,
760 NO_STREAM_NAME, rpbuf.rpdata,
761 rpdatalen, ctx->params->blob_table))
762 return WIMLIB_ERR_NOMEM;
768 add_random_data_stream(struct wim_inode *inode, struct generation_context *ctx,
769 const utf16lechar *stream_name)
774 size = select_stream_size(ctx);
776 buffer = MALLOC(size);
778 return WIMLIB_ERR_NOMEM;
779 generate_data(buffer, size, ctx);
782 return add_stream(inode, ctx, STREAM_TYPE_DATA, stream_name,
787 set_random_streams(struct wim_inode *inode, struct generation_context *ctx)
792 /* Reparse point (sometimes) */
793 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
794 ret = set_random_reparse_point(inode, ctx);
799 /* Unnamed data stream (nondirectories and non-symlinks only) */
800 if (!(inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) &&
801 !inode_is_symlink(inode)) {
802 ret = add_random_data_stream(inode, ctx, NO_STREAM_NAME);
807 /* Named data streams (sometimes) */
810 utf16lechar stream_name[2] = {cpu_to_le16('a'), '\0'};
813 ret = add_random_data_stream(inode, ctx, stream_name);
816 stream_name[0] += cpu_to_le16(1);
824 select_inode_number(struct generation_context *ctx)
826 const struct wim_inode_table *table = ctx->params->inode_table;
827 const struct hlist_head *head;
828 const struct wim_inode *inode;
830 head = &table->array[rand32() % table->capacity];
831 hlist_for_each_entry(inode, head, i_hlist_node)
839 select_num_children(u32 depth, struct generation_context *ctx)
841 const double b = 1.01230;
842 u32 r = rand32() % 500;
843 return ((pow(b, pow(b, r)) - 1) / pow(depth, 1.5)) +
844 (2 - exp(0.04/depth));
848 is_name_valid_in_win32_namespace(const utf16lechar *name)
850 const utf16lechar *p;
852 static const char * const reserved_names[] = {
853 "CON", "PRN", "AUX", "NUL",
854 "COM1", "COM2", "COM3", "COM4", "COM5",
855 "COM6", "COM7", "COM8", "COM9",
856 "LPT1", "LPT2", "LPT3", "LPT4", "LPT5",
857 "LPT6", "LPT7", "LPT8", "LPT9",
860 /* The name must be nonempty. */
864 /* All characters must be valid on Windows. */
865 for (p = name; *p; p++)
866 if (!is_valid_windows_filename_char(*p))
869 /* Note: a trailing dot or space is permitted, even though on Windows
870 * such a file can only be accessed using a WinNT-style path. */
872 /* The name can't be one of the reserved names or be a reserved name
873 * with an extension. Case insensitive. */
874 for (size_t i = 0; i < ARRAY_LEN(reserved_names); i++) {
875 for (size_t j = 0; ; j++) {
876 u16 c1 = le16_to_cpu(name[j]);
877 u16 c2 = reserved_names[i][j];
879 if (c1 == '\0' || c1 == '.')
883 if (upcase[c1] != upcase[c2])
892 set_random_short_name(struct wim_dentry *dir, struct wim_dentry *child,
893 struct generation_context *ctx)
895 utf16lechar name[12 + 1];
898 struct wim_dentry **bucket;
900 /* If the long name is not allowed in the Win32 namespace, then it
901 * cannot be assigned a corresponding short name. */
902 if (!is_name_valid_in_win32_namespace(child->d_name))
906 /* Don't select a short name that is already used by a long name within
907 * the same directory. */
909 name_len = generate_random_short_name(name, ctx);
910 } while (get_dentry_child_with_utf16le_name(dir, name, name_len * 2,
911 WIMLIB_CASE_INSENSITIVE));
914 /* Don't select a short name that is already used by another short name
915 * within the same directory. */
917 for (const utf16lechar *p = name; *p; p++)
918 hash = (hash * 31) + *p;
919 FREE(child->d_short_name);
920 child->d_short_name = memdup(name, (name_len + 1) * 2);
921 child->d_short_name_nbytes = name_len * 2;
923 if (!child->d_short_name)
924 return WIMLIB_ERR_NOMEM;
926 bucket = &ctx->used_short_names[hash % ARRAY_LEN(ctx->used_short_names)];
928 for (struct wim_dentry *d = *bucket; d != NULL;
929 d = d->d_next_extraction_alias) {
930 if (!cmp_utf16le_strings(child->d_short_name, name_len,
931 d->d_short_name, d->d_short_name_nbytes / 2,
937 if (!is_name_valid_in_win32_namespace(child->d_short_name))
940 child->d_next_extraction_alias = *bucket;
946 inode_has_short_name(const struct wim_inode *inode)
948 const struct wim_dentry *dentry;
950 inode_for_each_dentry(dentry, inode)
951 if (dentry_has_short_name(dentry))
958 generate_dentry_tree_recursive(struct wim_dentry *dir, u32 depth,
959 struct generation_context *ctx)
961 u32 num_children = select_num_children(depth, ctx);
962 struct wim_dentry *child;
965 memset(ctx->used_short_names, 0, sizeof(ctx->used_short_names));
967 /* Generate 'num_children' dentries within 'dir'. Some may be
968 * directories themselves. */
970 for (u32 i = 0; i < num_children; i++) {
972 /* Generate the next child dentry. */
973 struct wim_inode *inode;
975 bool is_directory = (rand32() % 16 <= 6);
976 bool is_reparse = (rand32() % 8 == 0);
977 utf16lechar name[63 + 1]; /* for UNIX extraction: 63 * 4 <= 255 */
979 struct wim_dentry *duplicate;
982 * Select an inode number for the new file. Sometimes choose an
983 * existing inode number (i.e. create a hard link). However,
984 * wimlib intentionally doesn't honor directory hard links, and
985 * reparse points cannot be represented in the WIM file format
986 * at all; so don't create hard links for such files.
988 if (is_directory || is_reparse)
991 ino = select_inode_number(ctx);
993 /* Create the dentry. */
994 ret = inode_table_new_dentry(ctx->params->inode_table, NULL,
995 ino, 0, ino == 0, &child);
999 /* Choose a filename that is unique within the directory.*/
1001 name_len = generate_random_filename(name,
1002 ARRAY_LEN(name) - 1,
1004 } while (get_dentry_child_with_utf16le_name(dir, name, name_len * 2,
1005 WIMLIB_CASE_PLATFORM_DEFAULT));
1007 ret = dentry_set_name_utf16le(child, name, name_len * 2);
1013 /* Add the dentry to the directory. */
1014 duplicate = dentry_add_child(dir, child);
1015 wimlib_assert(!duplicate);
1017 inode = child->d_inode;
1019 if (inode->i_nlink > 1) /* Existing inode? */
1022 /* New inode; set attributes, metadata, and data. */
1025 inode->i_attributes |= FILE_ATTRIBUTE_DIRECTORY;
1027 inode->i_attributes |= FILE_ATTRIBUTE_REPARSE_POINT;
1029 ret = set_random_streams(inode, ctx);
1033 ret = set_random_metadata(inode, ctx);
1037 /* Recurse if it's a directory. */
1038 if (is_directory && !is_reparse) {
1039 ret = generate_dentry_tree_recursive(child, depth + 1,
1046 for_dentry_child(child, dir) {
1047 /* sometimes generate a unique short name */
1048 if (randbool() && !inode_has_short_name(child->d_inode)) {
1049 ret = set_random_short_name(dir, child, ctx);
1059 generate_dentry_tree(struct wim_dentry **root_ret, const tchar *_ignored,
1060 struct scan_params *params)
1063 struct wim_dentry *root = NULL;
1064 struct generation_context ctx = {
1068 ctx.metadata_only = ((rand32() % 8) != 0); /* usually metadata only */
1070 ret = inode_table_new_dentry(params->inode_table, NULL, 0, 0, true, &root);
1072 root->d_inode->i_attributes = FILE_ATTRIBUTE_DIRECTORY;
1073 ret = set_random_streams(root->d_inode, &ctx);
1076 ret = set_random_metadata(root->d_inode, &ctx);
1078 ret = generate_dentry_tree_recursive(root, 1, &ctx);
1082 free_dentry_tree(root, params->blob_table);
1086 /*----------------------------------------------------------------------------*
1087 * File tree comparison *
1088 *----------------------------------------------------------------------------*/
1090 #define INDEX_NODE_TO_DENTRY(node) \
1091 ((node) ? avl_tree_entry((node), struct wim_dentry, d_index_node) : NULL)
1093 static struct wim_dentry *
1094 dentry_first_child(struct wim_dentry *dentry)
1096 return INDEX_NODE_TO_DENTRY(
1097 avl_tree_first_in_order(dentry->d_inode->i_children));
1100 static struct wim_dentry *
1101 dentry_next_sibling(struct wim_dentry *dentry)
1103 return INDEX_NODE_TO_DENTRY(
1104 avl_tree_next_in_order(&dentry->d_index_node));
1108 * Verify that the dentries in the tree 'd1' exactly match the dentries in the
1109 * tree 'd2', considering long and short filenames. In addition, set
1110 * 'd_corresponding' of each dentry to point to the corresponding dentry in the
1111 * other tree, and set 'i_corresponding' of each inode to point to the
1112 * unverified corresponding inode in the other tree.
1115 calc_corresponding_files_recursive(struct wim_dentry *d1, struct wim_dentry *d2,
1118 struct wim_dentry *child1;
1119 struct wim_dentry *child2;
1122 /* Compare long filenames, case sensitively. */
1123 if (cmp_utf16le_strings(d1->d_name, d1->d_name_nbytes / 2,
1124 d2->d_name, d2->d_name_nbytes / 2,
1127 ERROR("Filename mismatch; path1=\"%"TS"\", path2=\"%"TS"\"",
1128 dentry_full_path(d1), dentry_full_path(d2));
1129 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1132 /* Compare short filenames, case insensitively. */
1133 if (!(d2->d_short_name_nbytes == 0 &&
1134 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)) &&
1135 cmp_utf16le_strings(d1->d_short_name, d1->d_short_name_nbytes / 2,
1136 d2->d_short_name, d2->d_short_name_nbytes / 2,
1139 ERROR("Short name mismatch; path=\"%"TS"\"",
1140 dentry_full_path(d1));
1141 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1144 /* Match up the dentries */
1145 d1->d_corresponding = d2;
1146 d2->d_corresponding = d1;
1148 /* Match up the inodes (may overwrite previous value) */
1149 d1->d_inode->i_corresponding = d2->d_inode;
1150 d2->d_inode->i_corresponding = d1->d_inode;
1152 /* Process children */
1153 child1 = dentry_first_child(d1);
1154 child2 = dentry_first_child(d2);
1155 while (child1 || child2) {
1157 if (!child1 || !child2) {
1158 ERROR("Child count mismatch; "
1159 "path1=\"%"TS"\", path2=\"%"TS"\"",
1160 dentry_full_path(d1), dentry_full_path(d2));
1161 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1164 /* Recurse on this pair of children. */
1165 ret = calc_corresponding_files_recursive(child1, child2,
1170 /* Continue to the next pair of children. */
1171 child1 = dentry_next_sibling(child1);
1172 child2 = dentry_next_sibling(child2);
1177 /* Perform sanity checks on an image's inodes. All assertions here should pass,
1178 * even if the images being compared are different. */
1180 assert_inodes_sane(const struct wim_image_metadata *imd)
1182 const struct wim_inode *inode;
1183 const struct wim_dentry *dentry;
1186 image_for_each_inode(inode, imd) {
1188 inode_for_each_dentry(dentry, inode) {
1189 wimlib_assert(dentry->d_inode == inode);
1192 wimlib_assert(link_count > 0);
1193 wimlib_assert(link_count == inode->i_nlink);
1194 wimlib_assert(inode->i_corresponding != NULL);
1199 check_hard_link(struct wim_dentry *dentry, void *_ignore)
1201 /* My inode is my corresponding dentry's inode's corresponding inode,
1202 * and my inode's corresponding inode is my corresponding dentry's
1204 const struct wim_inode *a = dentry->d_inode;
1205 const struct wim_inode *b = dentry->d_corresponding->d_inode;
1206 if (a == b->i_corresponding && a->i_corresponding == b)
1208 ERROR("Hard link difference; path=%"TS"", dentry_full_path(dentry));
1209 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1212 static const struct {
1215 } file_attr_flags[] = {
1216 {FILE_ATTRIBUTE_READONLY, "READONLY"},
1217 {FILE_ATTRIBUTE_HIDDEN, "HIDDEN"},
1218 {FILE_ATTRIBUTE_SYSTEM, "SYSTEM"},
1219 {FILE_ATTRIBUTE_DIRECTORY, "DIRECTORY"},
1220 {FILE_ATTRIBUTE_ARCHIVE, "ARCHIVE"},
1221 {FILE_ATTRIBUTE_DEVICE, "DEVICE"},
1222 {FILE_ATTRIBUTE_NORMAL, "NORMAL"},
1223 {FILE_ATTRIBUTE_TEMPORARY, "TEMPORARY"},
1224 {FILE_ATTRIBUTE_SPARSE_FILE, "SPARSE_FILE"},
1225 {FILE_ATTRIBUTE_REPARSE_POINT, "REPARSE_POINT"},
1226 {FILE_ATTRIBUTE_COMPRESSED, "COMPRESSED"},
1227 {FILE_ATTRIBUTE_OFFLINE, "OFFLINE"},
1228 {FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, "NOT_CONTENT_INDEXED"},
1229 {FILE_ATTRIBUTE_ENCRYPTED, "ENCRYPTED"},
1230 {FILE_ATTRIBUTE_VIRTUAL, "VIRTUAL"},
1234 cmp_attributes(const struct wim_inode *inode1,
1235 const struct wim_inode *inode2, int cmp_flags)
1237 const u32 changed = inode1->i_attributes ^ inode2->i_attributes;
1238 const u32 set = inode2->i_attributes & ~inode1->i_attributes;
1239 const u32 cleared = inode1->i_attributes & ~inode2->i_attributes;
1241 /* NORMAL may change, but it must never be set along with other
1243 if ((inode2->i_attributes & FILE_ATTRIBUTE_NORMAL) &&
1244 (inode2->i_attributes & ~FILE_ATTRIBUTE_NORMAL))
1247 /* DIRECTORY may change in UNIX mode for symlinks. */
1248 if (changed & FILE_ATTRIBUTE_DIRECTORY) {
1249 if (!(inode_is_symlink(inode1) &&
1250 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)))
1254 /* REPARSE_POINT may be cleared in UNIX mode if the inode is not a
1256 if ((changed & FILE_ATTRIBUTE_REPARSE_POINT) &&
1257 !((cleared & FILE_ATTRIBUTE_REPARSE_POINT) &&
1258 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE) &&
1259 !inode_is_symlink(inode1)))
1262 /* SPARSE_FILE may be cleared in UNIX and NTFS-3G modes, or in Windows
1263 * mode if the inode is a directory. */
1264 if ((changed & FILE_ATTRIBUTE_SPARSE_FILE) &&
1265 !((cleared & FILE_ATTRIBUTE_SPARSE_FILE) &&
1266 ((cmp_flags & (WIMLIB_CMP_FLAG_UNIX_MODE |
1267 WIMLIB_CMP_FLAG_NTFS_3G_MODE)) ||
1268 ((cmp_flags & WIMLIB_CMP_FLAG_WINDOWS_MODE) &&
1269 (inode1->i_attributes & FILE_ATTRIBUTE_DIRECTORY)))))
1272 /* COMPRESSED may change in UNIX and NTFS-3G modes. (It *should* be
1273 * preserved in NTFS-3G mode, but it's not implemented yet.) */
1274 if ((changed & FILE_ATTRIBUTE_COMPRESSED) &&
1275 !(cmp_flags & (WIMLIB_CMP_FLAG_UNIX_MODE |
1276 WIMLIB_CMP_FLAG_NTFS_3G_MODE)))
1279 /* All other attributes can change in UNIX mode, but not in any other
1281 if ((changed & ~(FILE_ATTRIBUTE_NORMAL |
1282 FILE_ATTRIBUTE_DIRECTORY |
1283 FILE_ATTRIBUTE_REPARSE_POINT |
1284 FILE_ATTRIBUTE_SPARSE_FILE |
1285 FILE_ATTRIBUTE_COMPRESSED)) &&
1286 !(cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE))
1292 ERROR("Attribute mismatch for %"TS": 0x%08"PRIx32" vs. 0x%08"PRIx32":",
1293 inode_any_full_path(inode1), inode1->i_attributes,
1294 inode2->i_attributes);
1295 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++) {
1296 u32 flag = file_attr_flags[i].flag;
1297 if (changed & flag) {
1298 fprintf(stderr, "\tFILE_ATTRIBUTE_%s was %s\n",
1299 file_attr_flags[i].name,
1300 (set & flag) ? "set" : "cleared");
1303 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1307 cmp_object_ids(const struct wim_inode *inode1,
1308 const struct wim_inode *inode2, int cmp_flags)
1310 const void *objid1, *objid2;
1313 objid1 = inode_get_object_id(inode1, &len1);
1314 objid2 = inode_get_object_id(inode2, &len2);
1316 if (!objid1 && !objid2)
1319 if (objid1 && !objid2) {
1320 if (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)
1322 ERROR("%"TS" unexpectedly lost its object ID",
1323 inode_any_full_path(inode1));
1324 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1327 if (!objid1 && objid2) {
1328 ERROR("%"TS" unexpectedly gained an object ID",
1329 inode_any_full_path(inode1));
1330 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1333 if (len1 != len2 || memcmp(objid1, objid2, len1) != 0) {
1334 ERROR("Object ID of %"TS" differs",
1335 inode_any_full_path(inode1));
1336 fprintf(stderr, "objid1=");
1337 print_byte_field(objid1, len1, stderr);
1338 fprintf(stderr, "\nobjid2=");
1339 print_byte_field(objid2, len2, stderr);
1340 fprintf(stderr, "\n");
1341 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1348 cmp_unix_metadata(const struct wim_inode *inode1,
1349 const struct wim_inode *inode2, int cmp_flags)
1351 struct wimlib_unix_data dat1, dat2;
1352 bool present1, present2;
1354 present1 = inode_get_unix_data(inode1, &dat1);
1355 present2 = inode_get_unix_data(inode2, &dat2);
1357 if (!present1 && !present2)
1360 if (present1 && !present2) {
1361 if (cmp_flags & (WIMLIB_CMP_FLAG_NTFS_3G_MODE |
1362 WIMLIB_CMP_FLAG_WINDOWS_MODE))
1364 ERROR("%"TS" unexpectedly lost its UNIX metadata",
1365 inode_any_full_path(inode1));
1366 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1369 if (!present1 && present2) {
1370 if (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)
1372 ERROR("%"TS" unexpectedly gained UNIX metadata",
1373 inode_any_full_path(inode1));
1374 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1377 if (memcmp(&dat1, &dat2, sizeof(dat1)) != 0) {
1378 ERROR("UNIX metadata of %"TS" differs: "
1379 "[uid=%u, gid=%u, mode=0%o, rdev=%u] vs. "
1380 "[uid=%u, gid=%u, mode=0%o, rdev=%u]",
1381 inode_any_full_path(inode1),
1382 dat1.uid, dat1.gid, dat1.mode, dat1.rdev,
1383 dat2.uid, dat2.gid, dat2.mode, dat2.rdev);
1384 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1391 cmp_xattr_names(const void *p1, const void *p2)
1393 const struct wim_xattr_entry *entry1 = *(const struct wim_xattr_entry **)p1;
1394 const struct wim_xattr_entry *entry2 = *(const struct wim_xattr_entry **)p2;
1397 res = entry1->name_len - entry2->name_len;
1401 return memcmp(entry1->name, entry2->name, entry1->name_len);
1404 /* Validate and sort by name a list of extended attributes */
1406 parse_xattrs(const void *xattrs, u32 len,
1407 const struct wim_xattr_entry *entries[],
1410 u32 limit = *num_entries_p;
1411 u32 num_entries = 0;
1412 const struct wim_xattr_entry *entry = xattrs;
1414 while ((void *)entry < xattrs + len) {
1415 if (!valid_xattr_entry(entry, xattrs + len - (void *)entry)) {
1416 ERROR("Invalid xattr entry");
1417 return WIMLIB_ERR_INVALID_XATTR;
1419 if (num_entries >= limit) {
1420 ERROR("Too many xattr entries");
1421 return WIMLIB_ERR_INVALID_XATTR;
1423 entries[num_entries++] = entry;
1424 entry = xattr_entry_next(entry);
1427 if (num_entries == 0) {
1428 ERROR("No xattr entries");
1429 return WIMLIB_ERR_INVALID_XATTR;
1432 qsort(entries, num_entries, sizeof(entries[0]), cmp_xattr_names);
1434 for (u32 i = 1; i < num_entries; i++) {
1435 if (cmp_xattr_names(&entries[i - 1], &entries[i]) == 0) {
1436 ERROR("Duplicate xattr names");
1437 return WIMLIB_ERR_INVALID_XATTR;
1441 *num_entries_p = num_entries;
1446 cmp_xattrs(const struct wim_inode *inode1, const struct wim_inode *inode2,
1449 const void *xattrs1, *xattrs2;
1452 xattrs1 = inode_get_xattrs(inode1, &len1);
1453 xattrs2 = inode_get_xattrs(inode2, &len2);
1455 if (!xattrs1 && !xattrs2) {
1457 } else if (xattrs1 && !xattrs2) {
1458 if (cmp_flags & (WIMLIB_CMP_FLAG_NTFS_3G_MODE |
1459 WIMLIB_CMP_FLAG_WINDOWS_MODE))
1461 ERROR("%"TS" unexpectedly lost its xattrs",
1462 inode_any_full_path(inode1));
1463 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1464 } else if (!xattrs1 && xattrs2) {
1465 ERROR("%"TS" unexpectedly gained xattrs",
1466 inode_any_full_path(inode1));
1467 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1469 const int max_entries = 64;
1470 const struct wim_xattr_entry *entries1[max_entries];
1471 const struct wim_xattr_entry *entries2[max_entries];
1472 u32 xattr_count1 = max_entries;
1473 u32 xattr_count2 = max_entries;
1476 ret = parse_xattrs(xattrs1, len1, entries1, &xattr_count1);
1478 ERROR("%"TS": invalid xattrs",
1479 inode_any_full_path(inode1));
1482 ret = parse_xattrs(xattrs2, len2, entries2, &xattr_count2);
1484 ERROR("%"TS": invalid xattrs",
1485 inode_any_full_path(inode2));
1488 if (xattr_count1 != xattr_count2) {
1489 ERROR("%"TS": number of xattrs changed. had %u "
1490 "before, now has %u", inode_any_full_path(inode1),
1491 xattr_count1, xattr_count2);
1493 for (u32 i = 0; i < xattr_count1; i++) {
1494 const struct wim_xattr_entry *entry1 = entries1[i];
1495 const struct wim_xattr_entry *entry2 = entries2[i];
1497 if (entry1->value_len != entry2->value_len ||
1498 entry1->name_len != entry2->name_len ||
1499 entry1->flags != entry2->flags ||
1500 memcmp(entry1->name, entry2->name,
1501 entry1->name_len) ||
1502 memcmp(entry1->name + entry1->name_len + 1,
1503 entry2->name + entry2->name_len + 1,
1504 le16_to_cpu(entry1->value_len)))
1506 ERROR("xattr %.*s of %"TS" differs",
1507 entry1->name_len, entry1->name,
1508 inode_any_full_path(inode1));
1509 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1517 cmp_timestamps(const struct wim_inode *inode1, const struct wim_inode *inode2,
1520 if (inode1->i_creation_time != inode2->i_creation_time &&
1521 !(cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)) {
1522 ERROR("Creation time of %"TS" differs",
1523 inode_any_full_path(inode1));
1524 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1527 if (inode1->i_last_write_time != inode2->i_last_write_time) {
1528 ERROR("Last write time of %"TS" differs",
1529 inode_any_full_path(inode1));
1530 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1533 if (inode1->i_last_access_time != inode2->i_last_access_time) {
1534 ERROR("Last access time of %"TS" differs",
1535 inode_any_full_path(inode1));
1536 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1543 cmp_inodes(const struct wim_inode *inode1, const struct wim_inode *inode2,
1544 const struct wim_image_metadata *imd1,
1545 const struct wim_image_metadata *imd2, int cmp_flags)
1549 /* Compare attributes */
1550 ret = cmp_attributes(inode1, inode2, cmp_flags);
1554 /* Compare security descriptors */
1555 if (inode_has_security_descriptor(inode1)) {
1556 if (inode_has_security_descriptor(inode2)) {
1557 const void *desc1 = imd1->security_data->descriptors[inode1->i_security_id];
1558 const void *desc2 = imd2->security_data->descriptors[inode2->i_security_id];
1559 size_t size1 = imd1->security_data->sizes[inode1->i_security_id];
1560 size_t size2 = imd2->security_data->sizes[inode2->i_security_id];
1562 if (size1 != size2 || memcmp(desc1, desc2, size1)) {
1563 ERROR("Security descriptor of %"TS" differs!",
1564 inode_any_full_path(inode1));
1565 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1567 } else if (!(cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)) {
1568 ERROR("%"TS" has a security descriptor in the first image but "
1569 "not in the second image!", inode_any_full_path(inode1));
1570 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1572 } else if (inode_has_security_descriptor(inode2)) {
1573 /* okay --- consider it acceptable if a default security
1574 * descriptor was assigned */
1575 /*ERROR("%"TS" has a security descriptor in the second image but "*/
1576 /*"not in the first image!", inode_any_full_path(inode1));*/
1577 /*return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;*/
1580 /* Compare streams */
1581 for (unsigned i = 0; i < inode1->i_num_streams; i++) {
1582 const struct wim_inode_stream *strm1 = &inode1->i_streams[i];
1583 const struct wim_inode_stream *strm2;
1585 if (strm1->stream_type == STREAM_TYPE_REPARSE_POINT &&
1586 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE &&
1587 !inode_is_symlink(inode1)))
1590 if (strm1->stream_type == STREAM_TYPE_UNKNOWN)
1593 /* Get the corresponding stream from the second file */
1594 strm2 = inode_get_stream(inode2, strm1->stream_type, strm1->stream_name);
1597 /* Corresponding stream not found */
1598 if (stream_is_named(strm1) &&
1599 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE))
1601 ERROR("Stream of %"TS" is missing in second image; "
1602 "type %d, named=%d, empty=%d",
1603 inode_any_full_path(inode1),
1605 stream_is_named(strm1),
1606 is_zero_hash(stream_hash(strm1)));
1607 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1610 if (!hashes_equal(stream_hash(strm1), stream_hash(strm2))) {
1611 ERROR("Stream of %"TS" differs; type %d",
1612 inode_any_full_path(inode1), strm1->stream_type);
1613 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1617 /* Compare object IDs */
1618 ret = cmp_object_ids(inode1, inode2, cmp_flags);
1622 /* Compare timestamps */
1623 ret = cmp_timestamps(inode1, inode2, cmp_flags);
1627 /* Compare standard UNIX metadata */
1628 ret = cmp_unix_metadata(inode1, inode2, cmp_flags);
1632 /* Compare extended attributes */
1633 ret = cmp_xattrs(inode1, inode2, cmp_flags);
1641 cmp_images(const struct wim_image_metadata *imd1,
1642 const struct wim_image_metadata *imd2, int cmp_flags)
1644 struct wim_dentry *root1 = imd1->root_dentry;
1645 struct wim_dentry *root2 = imd2->root_dentry;
1646 const struct wim_inode *inode;
1649 ret = calc_corresponding_files_recursive(root1, root2, cmp_flags);
1653 /* Verify that the hard links match up between the two images. */
1654 assert_inodes_sane(imd1);
1655 assert_inodes_sane(imd2);
1656 ret = for_dentry_in_tree(root1, check_hard_link, NULL);
1660 /* Compare corresponding inodes. */
1661 image_for_each_inode(inode, imd1) {
1662 ret = cmp_inodes(inode, inode->i_corresponding,
1663 imd1, imd2, cmp_flags);
1672 load_image(WIMStruct *wim, int image, struct wim_image_metadata **imd_ret)
1674 int ret = select_wim_image(wim, image);
1676 *imd_ret = wim_get_current_image_metadata(wim);
1677 mark_image_dirty(*imd_ret);
1683 wimlib_compare_images(WIMStruct *wim1, int image1,
1684 WIMStruct *wim2, int image2, int cmp_flags)
1687 struct wim_image_metadata *imd1, *imd2;
1689 ret = load_image(wim1, image1, &imd1);
1691 ret = load_image(wim2, image2, &imd2);
1693 ret = cmp_images(imd1, imd2, cmp_flags);
1697 #endif /* ENABLE_TEST_SUPPORT */