2 * test_support.c - Supporting code for tests
6 * Copyright (C) 2015-2016 Eric Biggers
8 * This file is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU Lesser General Public License as published by the Free
10 * Software Foundation; either version 3 of the License, or (at your option) any
13 * This file is distributed in the hope that it will be useful, but WITHOUT
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this file; if not, see http://www.gnu.org/licenses/.
23 * This file contains specialized test code which is only compiled when the
24 * library is configured with --enable-test-support. The major features are:
26 * - Random directory tree generation
27 * - Directory tree comparison
34 #ifdef ENABLE_TEST_SUPPORT
40 #include "wimlib/endianness.h"
41 #include "wimlib/encoding.h"
42 #include "wimlib/metadata.h"
43 #include "wimlib/dentry.h"
44 #include "wimlib/inode.h"
45 #include "wimlib/reparse.h"
46 #include "wimlib/scan.h"
47 #include "wimlib/security_descriptor.h"
48 #include "wimlib/test_support.h"
50 /*----------------------------------------------------------------------------*
51 * File tree generation *
52 *----------------------------------------------------------------------------*/
54 struct generation_context {
55 struct scan_params *params;
56 struct wim_dentry *used_short_names[256];
63 static u64 state = 0x55DB93D0AB838771;
65 /* A simple linear congruential generator */
66 state = (state * 25214903917 + 11) & ((1ULL << 48) - 1);
73 return (rand32() & 1) != 0;
91 return ((u64)rand32() << 32) | rand32();
95 generate_random_timestamp(void)
97 /* When setting timestamps on Windows:
98 * - 0 is a special value meaning "not specified"
99 * - if the high bit is set you get STATUS_INVALID_PARAMETER */
100 return (1 + rand64()) & ~(1ULL << 63);
103 static const struct {
104 u8 num_subauthorities;
105 u64 identifier_authority;
106 u32 subauthorities[6];
108 { 1, 0, {0}}, /* NULL_SID */
109 { 1, 1, {0}}, /* WORLD_SID */
110 { 1, 2, {0}}, /* LOCAL_SID */
111 { 1, 3, {0}}, /* CREATOR_OWNER_SID */
112 { 1, 3, {1}}, /* CREATOR_GROUP_SID */
113 { 1, 3, {2}}, /* CREATOR_OWNER_SERVER_SID */
114 { 1, 3, {3}}, /* CREATOR_GROUP_SERVER_SID */
115 // { 0, 5, {}}, /* NT_AUTHORITY_SID */
116 { 1, 5, {1}}, /* DIALUP_SID */
117 { 1, 5, {2}}, /* NETWORK_SID */
118 { 1, 5, {3}}, /* BATCH_SID */
119 { 1, 5, {4}}, /* INTERACTIVE_SID */
120 { 1, 5, {6}}, /* SERVICE_SID */
121 { 1, 5, {7}}, /* ANONYMOUS_LOGON_SID */
122 { 1, 5, {8}}, /* PROXY_SID */
123 { 1, 5, {9}}, /* SERVER_LOGON_SID */
124 { 1, 5, {10}}, /* SELF_SID */
125 { 1, 5, {11}}, /* AUTHENTICATED_USER_SID */
126 { 1, 5, {12}}, /* RESTRICTED_CODE_SID */
127 { 1, 5, {13}}, /* TERMINAL_SERVER_SID */
128 { 1, 5, {18}}, /* NT AUTHORITY\SYSTEM */
129 { 1, 5, {19}}, /* NT AUTHORITY\LOCAL SERVICE */
130 { 1, 5, {20}}, /* NT AUTHORITY\NETWORK SERVICE */
131 { 5 ,80, {956008885, 3418522649, 1831038044, 1853292631, 2271478464}}, /* trusted installer */
132 { 2 ,5, {32, 544} } /* BUILTIN\ADMINISTRATORS */
135 /* Generate a SID and return its size in bytes. */
137 generate_random_sid(wimlib_SID *sid, struct generation_context *ctx)
145 r = (r >> 1) % ARRAY_LEN(common_sids);
147 sid->sub_authority_count = common_sids[r].num_subauthorities;
148 for (int i = 0; i < 6; i++) {
149 sid->identifier_authority[i] =
150 common_sids[r].identifier_authority >> (40 - i * 8);
152 for (int i = 0; i < common_sids[r].num_subauthorities; i++)
153 sid->sub_authority[i] = cpu_to_le32(common_sids[r].subauthorities[i]);
157 sid->sub_authority_count = 1 + ((r >> 1) % 15);
159 for (int i = 0; i < 6; i++)
160 sid->identifier_authority[i] = rand8();
162 for (int i = 0; i < sid->sub_authority_count; i++)
163 sid->sub_authority[i] = cpu_to_le32(rand32());
165 return (u8 *)&sid->sub_authority[sid->sub_authority_count] - (u8 *)sid;
168 /* Generate an ACL and return its size in bytes. */
170 generate_random_acl(wimlib_ACL *acl, bool dacl, struct generation_context *ctx)
175 ace_count = rand32() % 16;
179 acl->ace_count = cpu_to_le16(ace_count);
184 for (int i = 0; i < ace_count; i++) {
185 wimlib_ACCESS_ALLOWED_ACE *ace = (wimlib_ACCESS_ALLOWED_ACE *)p;
187 /* ACCESS_ALLOWED, ACCESS_DENIED, or SYSTEM_AUDIT; format is the
190 ace->hdr.type = rand32() % 2;
193 ace->hdr.flags = rand8();
194 ace->mask = cpu_to_le32(rand32() & 0x001F01FF);
196 p += offsetof(wimlib_ACCESS_ALLOWED_ACE, sid) +
197 generate_random_sid(&ace->sid, ctx);
198 ace->hdr.size = cpu_to_le16(p - (u8 *)ace);
201 acl->acl_size = cpu_to_le16(p - (u8 *)acl);
202 return p - (u8 *)acl;
205 /* Generate a security descriptor and return its size in bytes. */
207 generate_random_security_descriptor(void *_desc, struct generation_context *ctx)
209 wimlib_SECURITY_DESCRIPTOR_RELATIVE *desc = _desc;
215 control &= (wimlib_SE_DACL_AUTO_INHERITED |
216 wimlib_SE_SACL_AUTO_INHERITED);
218 control |= wimlib_SE_SELF_RELATIVE |
219 wimlib_SE_DACL_PRESENT |
220 wimlib_SE_SACL_PRESENT;
224 desc->control = cpu_to_le16(control);
226 p = (u8 *)(desc + 1);
228 desc->owner_offset = cpu_to_le32(p - (u8 *)desc);
229 p += generate_random_sid((wimlib_SID *)p, ctx);
231 desc->group_offset = cpu_to_le32(p - (u8 *)desc);
232 p += generate_random_sid((wimlib_SID *)p, ctx);
234 if ((control & wimlib_SE_DACL_PRESENT) && randbool()) {
235 desc->dacl_offset = cpu_to_le32(p - (u8 *)desc);
236 p += generate_random_acl((wimlib_ACL *)p, true, ctx);
238 desc->dacl_offset = cpu_to_le32(0);
241 if ((control & wimlib_SE_SACL_PRESENT) && randbool()) {
242 desc->sacl_offset = cpu_to_le32(p - (u8 *)desc);
243 p += generate_random_acl((wimlib_ACL *)p, false, ctx);
245 desc->sacl_offset = cpu_to_le32(0);
248 return p - (u8 *)desc;
252 set_random_metadata(struct wim_inode *inode, struct generation_context *ctx)
254 u32 attrib = (rand32() & (FILE_ATTRIBUTE_READONLY |
255 FILE_ATTRIBUTE_HIDDEN |
256 FILE_ATTRIBUTE_SYSTEM |
257 FILE_ATTRIBUTE_ARCHIVE |
258 FILE_ATTRIBUTE_NOT_CONTENT_INDEXED |
259 FILE_ATTRIBUTE_COMPRESSED |
260 FILE_ATTRIBUTE_SPARSE_FILE));
262 /* File attributes */
263 inode->i_attributes |= attrib;
266 inode->i_creation_time = generate_random_timestamp();
267 inode->i_last_access_time = generate_random_timestamp();
268 inode->i_last_write_time = generate_random_timestamp();
270 /* Security descriptor */
272 char desc[8192] _aligned_attribute(8);
275 size = generate_random_security_descriptor(desc, ctx);
277 wimlib_assert(size <= sizeof(desc));
279 inode->i_security_id = sd_set_add_sd(ctx->params->sd_set,
281 if (unlikely(inode->i_security_id < 0))
282 return WIMLIB_ERR_NOMEM;
289 /* Choose a random size for generated file data. We want to usually generate
290 * empty, small, or medium files, but occasionally generate large files. */
292 select_stream_size(struct generation_context *ctx)
294 if (ctx->metadata_only)
297 switch (rand32() % 2048) {
303 return rand32() % 64;
306 return rand32() % 4096;
309 return rand32() % 32768;
312 return rand32() % 262144;
315 return rand32() % 134217728;
319 /* Fill 'buffer' with 'size' bytes of "interesting" file data. */
321 generate_data(u8 *buffer, size_t size, struct generation_context *ctx)
324 size_t num_byte_fills = rand32() % 256;
326 /* Start by initializing to a random byte */
327 memset(buffer, rand32() % 256, size);
329 /* Add some random bytes in some random places */
330 for (size_t i = 0; i < num_byte_fills; i++) {
333 size_t count = ((double)size / (double)num_byte_fills) *
334 ((double)rand32() / 2e9);
335 size_t offset = rand32() & ~mask;
339 ((rand32()) & mask)) % size] = b;
343 if (rand32() % 4 == 0)
344 mask = (size_t)-1 << rand32() % 4;
347 /* Sometimes add a wave pattern */
348 if (rand32() % 8 == 0) {
349 double magnitude = rand32() % 128;
350 double scale = 1.0 / (1 + (rand32() % 256));
352 for (size_t i = 0; i < size; i++)
353 buffer[i] += (int)(magnitude * cos(i * scale));
356 /* Sometimes add some zero regions (holes) */
357 if (rand32() % 4 == 0) {
358 size_t num_holes = 1 + (rand32() % 16);
359 for (size_t i = 0; i < num_holes; i++) {
360 size_t hole_offset = rand32() % size;
361 size_t hole_len = min(size - hole_offset,
362 size / (1 + (rand32() % 16)));
363 memset(&buffer[hole_offset], 0, hole_len);
369 add_stream(struct wim_inode *inode, struct generation_context *ctx,
370 int stream_type, const utf16lechar *stream_name,
371 void *buffer, size_t size)
373 struct blob_descriptor *blob = NULL;
374 struct wim_inode_stream *strm;
377 blob = new_blob_descriptor();
380 blob->attached_buffer = buffer;
381 blob->blob_location = BLOB_IN_ATTACHED_BUFFER;
385 strm = inode_add_stream(inode, stream_type, stream_name, blob);
389 prepare_unhashed_blob(blob, inode, strm->stream_id,
390 ctx->params->unhashed_blobs);
394 free_blob_descriptor(blob);
395 return WIMLIB_ERR_NOMEM;
399 set_random_reparse_point(struct wim_inode *inode, struct generation_context *ctx)
402 size_t rpdatalen = select_stream_size(ctx) % (REPARSE_DATA_MAX_SIZE + 1);
405 buffer = MALLOC(rpdatalen);
407 return WIMLIB_ERR_NOMEM;
408 generate_data(buffer, rpdatalen, ctx);
411 inode->i_attributes |= FILE_ATTRIBUTE_REPARSE_POINT;
412 inode->i_rp_reserved = rand16();
414 if (rpdatalen >= GUID_SIZE && randbool()) {
415 /* Non-Microsoft reparse tag (16-byte GUID required) */
417 guid[6] = (guid[6] & 0x0F) | 0x40;
418 guid[8] = (guid[8] & 0x3F) | 0x80;
419 inode->i_reparse_tag = 0x00000100;
421 /* Microsoft reparse tag */
422 inode->i_reparse_tag = 0x80000000;
425 return add_stream(inode, ctx, STREAM_TYPE_REPARSE_POINT, NO_STREAM_NAME,
430 add_random_data_stream(struct wim_inode *inode, struct generation_context *ctx,
431 const utf16lechar *stream_name)
436 size = select_stream_size(ctx);
438 buffer = MALLOC(size);
440 return WIMLIB_ERR_NOMEM;
441 generate_data(buffer, size, ctx);
444 return add_stream(inode, ctx, STREAM_TYPE_DATA, stream_name,
449 set_random_streams(struct wim_inode *inode, struct generation_context *ctx)
454 /* Reparse point (sometimes) */
455 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
456 ret = set_random_reparse_point(inode, ctx);
461 /* Unnamed data stream (nondirectories only) */
462 if (!(inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY)) {
463 ret = add_random_data_stream(inode, ctx, NO_STREAM_NAME);
468 /* Named data streams (sometimes) */
471 utf16lechar stream_name[2] = {cpu_to_le16('a'), '\0'};
474 ret = add_random_data_stream(inode, ctx, stream_name);
477 stream_name[0] += cpu_to_le16(1);
485 is_valid_windows_filename_char(utf16lechar c)
487 return le16_to_cpu(c) > 31 &&
488 c != cpu_to_le16('/') &&
489 c != cpu_to_le16('<') &&
490 c != cpu_to_le16('>') &&
491 c != cpu_to_le16(':') &&
492 c != cpu_to_le16('"') &&
493 c != cpu_to_le16('/' ) &&
494 c != cpu_to_le16('\\') &&
495 c != cpu_to_le16('|') &&
496 c != cpu_to_le16('?') &&
497 c != cpu_to_le16('*');
500 /* Is the character valid in a filename on the current platform? */
502 is_valid_filename_char(utf16lechar c)
505 return is_valid_windows_filename_char(c);
507 return c != cpu_to_le16('\0') && c != cpu_to_le16('/');
511 /* Generate a random filename and return its length. */
513 generate_random_filename(utf16lechar name[], int max_len,
514 struct generation_context *ctx)
518 /* Choose the length of the name. */
519 switch (rand32() % 8) {
522 len = 1 + (rand32() % 6);
527 /* medium-length name */
528 len = 7 + (rand32() % 8);
533 len = 15 + (rand32() % 15);
537 len = 30 + (rand32() % 90);
540 len = min(len, max_len);
543 /* Generate the characters in the name. */
544 for (int i = 0; i < len; i++) {
547 } while (!is_valid_filename_char(name[i]));
550 /* Add a null terminator. */
551 name[len] = cpu_to_le16('\0');
553 /* Don't generate . and .. */
554 if (name[0] == cpu_to_le16('.') &&
555 (len == 1 || (len == 2 && name[1] == cpu_to_le16('.'))))
561 /* The set of characters which are valid in short filenames. */
562 static const char valid_short_name_chars[] = {
563 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
564 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
565 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
566 '!', '#', '$', '%', '&', '\'', '(', ')', '-', '@', '^', '_', '`', '{',
568 /* Note: Windows does not allow space and 128-255 in short filenames
569 * (tested on both NTFS and FAT). */
573 generate_short_name_component(utf16lechar p[], int len)
575 for (int i = 0; i < len; i++) {
576 char c = valid_short_name_chars[rand32() %
577 ARRAY_LEN(valid_short_name_chars)];
578 p[i] = cpu_to_le16(c);
583 /* Generate a random short (8.3) filename and return its length.
584 * The @name array must have length >= 13 (8 + 1 + 3 + 1). */
586 generate_random_short_name(utf16lechar name[], struct generation_context *ctx)
589 * Legal short names on Windows consist of 1 to 8 characters, optionally
590 * followed by a dot then 1 to 3 more characters. Only certain
591 * characters are allowed.
593 int base_len = 1 + (rand32() % 8);
594 int ext_len = rand32() % 4;
597 base_len = generate_short_name_component(name, base_len);
600 name[base_len] = cpu_to_le16('.');
601 ext_len = generate_short_name_component(&name[base_len + 1],
603 total_len = base_len + 1 + ext_len;
605 total_len = base_len;
607 name[total_len] = cpu_to_le16('\0');
612 select_inode_number(struct generation_context *ctx)
614 const struct wim_inode_table *table = ctx->params->inode_table;
615 const struct hlist_head *head;
616 const struct wim_inode *inode;
618 head = &table->array[rand32() % table->capacity];
619 hlist_for_each_entry(inode, head, i_hlist_node)
627 select_num_children(u32 depth, struct generation_context *ctx)
629 const double b = 1.01230;
630 u32 r = rand32() % 500;
631 return ((pow(b, pow(b, r)) - 1) / pow(depth, 1.5)) +
632 (2 - exp(0.04/depth));
636 is_name_valid_in_win32_namespace(const utf16lechar *name)
638 const utf16lechar *p;
640 static const char * const reserved_names[] = {
641 "CON", "PRN", "AUX", "NUL",
642 "COM1", "COM2", "COM3", "COM4", "COM5",
643 "COM6", "COM7", "COM8", "COM9",
644 "LPT1", "LPT2", "LPT3", "LPT4", "LPT5",
645 "LPT6", "LPT7", "LPT8", "LPT9",
648 /* The name must be nonempty. */
652 /* All characters must be valid on Windows. */
653 for (p = name; *p; p++)
654 if (!is_valid_windows_filename_char(*p))
657 /* Note: a trailing dot or space is permitted, even though on Windows
658 * such a file can only be accessed using a WinNT-style path. */
660 /* The name can't be one of the reserved names or be a reserved name
661 * with an extension. Case insensitive. */
662 for (size_t i = 0; i < ARRAY_LEN(reserved_names); i++) {
663 for (size_t j = 0; ; j++) {
664 u16 c1 = le16_to_cpu(name[j]);
665 u16 c2 = reserved_names[i][j];
667 if (c1 == '\0' || c1 == '.')
671 if (upcase[c1] != upcase[c2])
680 set_random_short_name(struct wim_dentry *dir, struct wim_dentry *child,
681 struct generation_context *ctx)
683 utf16lechar name[12 + 1];
686 struct wim_dentry **bucket;
688 /* If the long name is not allowed in the Win32 namespace, then it
689 * cannot be assigned a corresponding short name. */
690 if (!is_name_valid_in_win32_namespace(child->d_name))
694 /* Don't select a short name that is already used by a long name within
695 * the same directory. */
697 name_len = generate_random_short_name(name, ctx);
698 } while (get_dentry_child_with_utf16le_name(dir, name, name_len * 2,
699 WIMLIB_CASE_INSENSITIVE));
702 /* Don't select a short name that is already used by another short name
703 * within the same directory. */
705 for (const utf16lechar *p = name; *p; p++)
706 hash = (hash * 31) + *p;
707 FREE(child->d_short_name);
708 child->d_short_name = memdup(name, (name_len + 1) * 2);
709 child->d_short_name_nbytes = name_len * 2;
711 if (!child->d_short_name)
712 return WIMLIB_ERR_NOMEM;
714 bucket = &ctx->used_short_names[hash % ARRAY_LEN(ctx->used_short_names)];
716 for (struct wim_dentry *d = *bucket; d != NULL;
717 d = d->d_next_extraction_alias) {
718 if (!cmp_utf16le_strings(child->d_short_name, name_len,
719 d->d_short_name, d->d_short_name_nbytes / 2,
725 if (!is_name_valid_in_win32_namespace(child->d_short_name))
728 child->d_next_extraction_alias = *bucket;
734 inode_has_short_name(const struct wim_inode *inode)
736 const struct wim_dentry *dentry;
738 inode_for_each_dentry(dentry, inode)
739 if (dentry_has_short_name(dentry))
746 generate_dentry_tree_recursive(struct wim_dentry *dir, u32 depth,
747 struct generation_context *ctx)
749 u32 num_children = select_num_children(depth, ctx);
750 struct wim_dentry *child;
753 memset(ctx->used_short_names, 0, sizeof(ctx->used_short_names));
755 /* Generate 'num_children' dentries within 'dir'. Some may be
756 * directories themselves. */
758 for (u32 i = 0; i < num_children; i++) {
760 /* Generate the next child dentry. */
761 struct wim_inode *inode;
763 bool is_directory = (rand32() % 16 <= 6);
764 bool is_reparse = (rand32() % 8 == 0);
765 utf16lechar name[63 + 1]; /* for UNIX extraction: 63 * 4 <= 255 */
767 struct wim_dentry *duplicate;
770 * Select an inode number for the new file. Sometimes choose an
771 * existing inode number (i.e. create a hard link). However,
772 * wimlib intentionally doesn't honor directory hard links, and
773 * reparse points cannot be represented in the WIM file format
774 * at all; so don't create hard links for such files.
776 if (is_directory || is_reparse)
779 ino = select_inode_number(ctx);
781 /* Create the dentry. */
782 ret = inode_table_new_dentry(ctx->params->inode_table, NULL,
783 ino, 0, ino == 0, &child);
787 /* Choose a filename that is unique within the directory.*/
789 name_len = generate_random_filename(name,
792 } while (get_dentry_child_with_utf16le_name(dir, name, name_len * 2,
793 WIMLIB_CASE_PLATFORM_DEFAULT));
795 ret = dentry_set_name_utf16le(child, name, name_len * 2);
801 /* Add the dentry to the directory. */
802 duplicate = dentry_add_child(dir, child);
803 wimlib_assert(!duplicate);
805 inode = child->d_inode;
807 if (inode->i_nlink > 1) /* Existing inode? */
810 /* New inode; set attributes, metadata, and data. */
813 inode->i_attributes |= FILE_ATTRIBUTE_DIRECTORY;
815 inode->i_attributes |= FILE_ATTRIBUTE_REPARSE_POINT;
817 ret = set_random_streams(inode, ctx);
821 ret = set_random_metadata(inode, ctx);
825 /* Recurse if it's a directory. */
826 if (is_directory && !is_reparse) {
827 ret = generate_dentry_tree_recursive(child, depth + 1,
834 for_dentry_child(child, dir) {
835 /* sometimes generate a unique short name */
836 if (randbool() && !inode_has_short_name(child->d_inode)) {
837 ret = set_random_short_name(dir, child, ctx);
847 generate_dentry_tree(struct wim_dentry **root_ret, const tchar *_ignored,
848 struct scan_params *params)
851 struct wim_dentry *root = NULL;
852 struct generation_context ctx = {
856 ctx.metadata_only = ((rand32() % 8) != 0); /* usually metadata only */
858 ret = inode_table_new_dentry(params->inode_table, NULL, 0, 0, true, &root);
860 root->d_inode->i_attributes = FILE_ATTRIBUTE_DIRECTORY;
861 ret = set_random_streams(root->d_inode, &ctx);
864 ret = set_random_metadata(root->d_inode, &ctx);
866 ret = generate_dentry_tree_recursive(root, 1, &ctx);
870 free_dentry_tree(root, params->blob_table);
874 /*----------------------------------------------------------------------------*
875 * File tree comparison *
876 *----------------------------------------------------------------------------*/
878 #define INDEX_NODE_TO_DENTRY(node) \
879 ((node) ? avl_tree_entry((node), struct wim_dentry, d_index_node) : NULL)
881 static struct wim_dentry *
882 dentry_first_child(struct wim_dentry *dentry)
884 return INDEX_NODE_TO_DENTRY(
885 avl_tree_first_in_order(dentry->d_inode->i_children));
888 static struct wim_dentry *
889 dentry_next_sibling(struct wim_dentry *dentry)
891 return INDEX_NODE_TO_DENTRY(
892 avl_tree_next_in_order(&dentry->d_index_node));
896 * Verify that the dentries in the tree 'd1' exactly match the dentries in the
897 * tree 'd2', considering long and short filenames. In addition, set
898 * 'd_corresponding' of each dentry to point to the corresponding dentry in the
899 * other tree, and set 'i_corresponding' of each inode to point to the
900 * unverified corresponding inode in the other tree.
903 calc_corresponding_files_recursive(struct wim_dentry *d1, struct wim_dentry *d2,
906 struct wim_dentry *child1;
907 struct wim_dentry *child2;
910 /* Compare long filenames, case sensitively. */
911 if (cmp_utf16le_strings(d1->d_name, d1->d_name_nbytes / 2,
912 d2->d_name, d2->d_name_nbytes / 2,
915 ERROR("Filename mismatch; path1=\"%"TS"\", path2=\"%"TS"\"",
916 dentry_full_path(d1), dentry_full_path(d2));
917 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
920 /* Compare short filenames, case insensitively. */
921 if (!(d2->d_short_name_nbytes == 0 &&
922 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)) &&
923 cmp_utf16le_strings(d1->d_short_name, d1->d_short_name_nbytes / 2,
924 d2->d_short_name, d2->d_short_name_nbytes / 2,
927 ERROR("Short name mismatch; path=\"%"TS"\"",
928 dentry_full_path(d1));
929 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
932 /* Match up the dentries */
933 d1->d_corresponding = d2;
934 d2->d_corresponding = d1;
936 /* Match up the inodes (may overwrite previous value) */
937 d1->d_inode->i_corresponding = d2->d_inode;
938 d2->d_inode->i_corresponding = d1->d_inode;
940 /* Process children */
941 child1 = dentry_first_child(d1);
942 child2 = dentry_first_child(d2);
943 while (child1 || child2) {
945 if (!child1 || !child2) {
946 ERROR("Child count mismatch; "
947 "path1=\"%"TS"\", path2=\"%"TS"\"",
948 dentry_full_path(d1), dentry_full_path(d2));
949 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
952 /* Recurse on this pair of children. */
953 ret = calc_corresponding_files_recursive(child1, child2,
958 /* Continue to the next pair of children. */
959 child1 = dentry_next_sibling(child1);
960 child2 = dentry_next_sibling(child2);
965 /* Perform sanity checks on an image's inodes. All assertions here should pass,
966 * even if the images being compared are different. */
968 assert_inodes_sane(const struct wim_image_metadata *imd)
970 const struct wim_inode *inode;
971 const struct wim_dentry *dentry;
974 image_for_each_inode(inode, imd) {
976 inode_for_each_dentry(dentry, inode) {
977 wimlib_assert(dentry->d_inode == inode);
980 wimlib_assert(link_count > 0);
981 wimlib_assert(link_count == inode->i_nlink);
982 wimlib_assert(inode->i_corresponding != NULL);
987 check_hard_link(struct wim_dentry *dentry, void *_ignore)
989 /* My inode is my corresponding dentry's inode's corresponding inode,
990 * and my inode's corresponding inode is my corresponding dentry's
992 const struct wim_inode *a = dentry->d_inode;
993 const struct wim_inode *b = dentry->d_corresponding->d_inode;
994 if (a == b->i_corresponding && a->i_corresponding == b)
996 ERROR("Hard link difference; path=%"TS"", dentry_full_path(dentry));
997 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1000 static const struct {
1003 } file_attr_flags[] = {
1004 {FILE_ATTRIBUTE_READONLY, "READONLY"},
1005 {FILE_ATTRIBUTE_HIDDEN, "HIDDEN"},
1006 {FILE_ATTRIBUTE_SYSTEM, "SYSTEM"},
1007 {FILE_ATTRIBUTE_DIRECTORY, "DIRECTORY"},
1008 {FILE_ATTRIBUTE_ARCHIVE, "ARCHIVE"},
1009 {FILE_ATTRIBUTE_DEVICE, "DEVICE"},
1010 {FILE_ATTRIBUTE_NORMAL, "NORMAL"},
1011 {FILE_ATTRIBUTE_TEMPORARY, "TEMPORARY"},
1012 {FILE_ATTRIBUTE_SPARSE_FILE, "SPARSE_FILE"},
1013 {FILE_ATTRIBUTE_REPARSE_POINT, "REPARSE_POINT"},
1014 {FILE_ATTRIBUTE_COMPRESSED, "COMPRESSED"},
1015 {FILE_ATTRIBUTE_OFFLINE, "OFFLINE"},
1016 {FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, "NOT_CONTENT_INDEXED"},
1017 {FILE_ATTRIBUTE_ENCRYPTED, "ENCRYPTED"},
1018 {FILE_ATTRIBUTE_VIRTUAL, "VIRTUAL"},
1022 cmp_attributes(const struct wim_inode *inode1,
1023 const struct wim_inode *inode2, int cmp_flags)
1025 const u32 changed = inode1->i_attributes ^ inode2->i_attributes;
1026 const u32 set = inode2->i_attributes & ~inode1->i_attributes;
1027 const u32 cleared = inode1->i_attributes & ~inode2->i_attributes;
1029 /* NORMAL may change, but it must never be set along with other
1031 if ((inode2->i_attributes & FILE_ATTRIBUTE_NORMAL) &&
1032 (inode2->i_attributes & ~FILE_ATTRIBUTE_NORMAL))
1035 /* DIRECTORY must not change. */
1036 if (changed & FILE_ATTRIBUTE_DIRECTORY)
1039 /* REPARSE_POINT may be cleared in UNIX mode if the inode is not a
1041 if ((changed & FILE_ATTRIBUTE_REPARSE_POINT) &&
1042 !((cleared & FILE_ATTRIBUTE_REPARSE_POINT) &&
1043 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE) &&
1044 !inode_is_symlink(inode1)))
1047 /* SPARSE_FILE may be cleared in UNIX and NTFS-3G modes, or in Windows
1048 * mode if the inode is a directory. */
1049 if ((changed & FILE_ATTRIBUTE_SPARSE_FILE) &&
1050 !((cleared & FILE_ATTRIBUTE_SPARSE_FILE) &&
1051 ((cmp_flags & (WIMLIB_CMP_FLAG_UNIX_MODE |
1052 WIMLIB_CMP_FLAG_NTFS_3G_MODE)) ||
1053 ((cmp_flags & WIMLIB_CMP_FLAG_WINDOWS_MODE) &&
1054 (inode1->i_attributes & FILE_ATTRIBUTE_DIRECTORY)))))
1057 /* COMPRESSED may change in UNIX and NTFS-3G modes. (It *should* be
1058 * preserved in NTFS-3G mode, but it's not implemented yet.) */
1059 if ((changed & FILE_ATTRIBUTE_COMPRESSED) &&
1060 !(cmp_flags & (WIMLIB_CMP_FLAG_UNIX_MODE |
1061 WIMLIB_CMP_FLAG_NTFS_3G_MODE)))
1064 /* All other attributes can change in UNIX mode, but not in any other
1066 if ((changed & ~(FILE_ATTRIBUTE_NORMAL |
1067 FILE_ATTRIBUTE_DIRECTORY |
1068 FILE_ATTRIBUTE_REPARSE_POINT |
1069 FILE_ATTRIBUTE_SPARSE_FILE |
1070 FILE_ATTRIBUTE_COMPRESSED)) &&
1071 !(cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE))
1077 ERROR("Attribute mismatch for %"TS": 0x%08"PRIx32" vs. 0x%08"PRIx32":",
1078 inode_any_full_path(inode1), inode1->i_attributes,
1079 inode2->i_attributes);
1080 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++) {
1081 u32 flag = file_attr_flags[i].flag;
1082 if (changed & flag) {
1083 fprintf(stderr, "\tFILE_ATTRIBUTE_%s was %s\n",
1084 file_attr_flags[i].name,
1085 (set & flag) ? "set" : "cleared");
1088 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1092 cmp_inodes(const struct wim_inode *inode1, const struct wim_inode *inode2,
1093 const struct wim_image_metadata *imd1,
1094 const struct wim_image_metadata *imd2, int cmp_flags)
1098 /* Compare attributes */
1099 ret = cmp_attributes(inode1, inode2, cmp_flags);
1103 /* Compare security descriptors */
1104 if (inode_has_security_descriptor(inode1)) {
1105 if (inode_has_security_descriptor(inode2)) {
1106 const void *desc1 = imd1->security_data->descriptors[inode1->i_security_id];
1107 const void *desc2 = imd2->security_data->descriptors[inode2->i_security_id];
1108 size_t size1 = imd1->security_data->sizes[inode1->i_security_id];
1109 size_t size2 = imd2->security_data->sizes[inode2->i_security_id];
1111 if (size1 != size2 || memcmp(desc1, desc2, size1)) {
1112 ERROR("Security descriptor of %"TS" differs!",
1113 inode_any_full_path(inode1));
1114 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1116 } else if (!(cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)) {
1117 ERROR("%"TS" has a security descriptor in the first image but "
1118 "not in the second image!", inode_any_full_path(inode1));
1119 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1121 } else if (inode_has_security_descriptor(inode2)) {
1122 /* okay --- consider it acceptable if a default security
1123 * descriptor was assigned */
1124 /*ERROR("%"TS" has a security descriptor in the second image but "*/
1125 /*"not in the first image!", inode_any_full_path(inode1));*/
1126 /*return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;*/
1129 /* Compare streams */
1130 for (unsigned i = 0; i < inode1->i_num_streams; i++) {
1131 const struct wim_inode_stream *strm1 = &inode1->i_streams[i];
1132 const struct wim_inode_stream *strm2;
1134 if (strm1->stream_type == STREAM_TYPE_REPARSE_POINT &&
1135 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE &&
1136 !inode_is_symlink(inode1)))
1139 if (strm1->stream_type == STREAM_TYPE_UNKNOWN)
1142 /* Get the corresponding stream from the second file */
1143 strm2 = inode_get_stream(inode2, strm1->stream_type, strm1->stream_name);
1146 /* Corresponding stream not found */
1147 if (stream_is_named(strm1) &&
1148 (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE))
1150 ERROR("Stream of %"TS" is missing in second image; "
1151 "type %d, named=%d, empty=%d",
1152 inode_any_full_path(inode1),
1154 stream_is_named(strm1),
1155 is_zero_hash(stream_hash(strm1)));
1156 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1159 if (!hashes_equal(stream_hash(strm1), stream_hash(strm2))) {
1160 ERROR("Stream of %"TS" differs; type %d",
1161 inode_any_full_path(inode1), strm1->stream_type);
1162 return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
1170 cmp_images(const struct wim_image_metadata *imd1,
1171 const struct wim_image_metadata *imd2, int cmp_flags)
1173 struct wim_dentry *root1 = imd1->root_dentry;
1174 struct wim_dentry *root2 = imd2->root_dentry;
1175 const struct wim_inode *inode;
1178 ret = calc_corresponding_files_recursive(root1, root2, cmp_flags);
1182 /* Verify that the hard links match up between the two images. */
1183 assert_inodes_sane(imd1);
1184 assert_inodes_sane(imd2);
1185 ret = for_dentry_in_tree(root1, check_hard_link, NULL);
1189 /* Compare corresponding inodes. */
1190 image_for_each_inode(inode, imd1) {
1191 ret = cmp_inodes(inode, inode->i_corresponding,
1192 imd1, imd2, cmp_flags);
1201 load_image(WIMStruct *wim, int image, struct wim_image_metadata **imd_ret)
1203 int ret = select_wim_image(wim, image);
1205 *imd_ret = wim_get_current_image_metadata(wim);
1206 mark_image_dirty(*imd_ret);
1212 wimlib_compare_images(WIMStruct *wim1, int image1,
1213 WIMStruct *wim2, int image2, int cmp_flags)
1216 struct wim_image_metadata *imd1, *imd2;
1218 ret = load_image(wim1, image1, &imd1);
1220 ret = load_image(wim2, image2, &imd2);
1222 ret = cmp_images(imd1, imd2, cmp_flags);
1226 #endif /* ENABLE_TEST_SUPPORT */