*/
/*
- * Copyright (C) 2015-2016 Eric Biggers
+ * Copyright 2015-2023 Eric Biggers
*
* This file is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* details.
*
* You should have received a copy of the GNU Lesser General Public License
- * along with this file; if not, see http://www.gnu.org/licenses/.
+ * along with this file; if not, see https://www.gnu.org/licenses/.
*/
/*
#include <ctype.h>
#include <math.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#ifdef _WIN32
+# include <windows.h>
+# include <sddl.h>
+# undef ERROR
+#endif
#include "wimlib.h"
#include "wimlib/endianness.h"
#include "wimlib/scan.h"
#include "wimlib/security_descriptor.h"
#include "wimlib/test_support.h"
+#include "wimlib/timestamp.h"
+#include "wimlib/unix_data.h"
+#include "wimlib/xattr.h"
/*----------------------------------------------------------------------------*
* File tree generation *
bool metadata_only;
};
+static u64 random_state;
+
+WIMLIBAPI void
+wimlib_seed_random(u64 seed)
+{
+ random_state = seed;
+}
+
static u32
rand32(void)
{
- static u64 state = 0x55DB93D0AB838771;
-
- /* A simple linear congruential generator */
- state = (state * 25214903917 + 11) & ((1ULL << 48) - 1);
- return state >> 16;
+ /* A simple linear congruential generator */
+ random_state = (random_state * 25214903917 + 11) % (1ULL << 48);
+ return random_state >> 16;
}
static bool
randbool(void)
{
- return (rand32() & 1) != 0;
+ return rand32() % 2;
}
static u8
static u64
generate_random_timestamp(void)
{
- /* When setting timestamps on Windows:
+ u64 ts;
+
+ if (randbool())
+ ts = rand64();
+ else
+ ts = time_t_to_wim_timestamp(rand64() % (1ULL << 34));
+ /*
+ * When setting timestamps on Windows:
* - 0 is a special value meaning "not specified"
- * - if the high bit is set you get STATUS_INVALID_PARAMETER */
- return (1 + rand64()) & ~(1ULL << 63);
+ * - if the high bit is set you get STATUS_INVALID_PARAMETER
+ */
+ return max(1, ts % (1ULL << 63));
}
static inline bool
static inline bool
is_valid_filename_char(utf16lechar c)
{
-#ifdef __WIN32__
+#ifdef _WIN32
return is_valid_windows_filename_char(c);
#else
return c != cpu_to_le16('\0') && c != cpu_to_le16('/');
/* Generate the characters in the name. */
for (int i = 0; i < len; i++) {
do {
- name[i] = rand16();
+ name[i] = cpu_to_le16(rand16());
} while (!is_valid_filename_char(name[i]));
}
return p - (u8 *)desc;
}
+static bool
+am_root(void)
+{
+#ifdef _WIN32
+ return false;
+#else
+ return (getuid() == 0);
+#endif
+}
+
+static u32
+generate_uid(void)
+{
+#ifdef _WIN32
+ return 0;
+#else
+ if (am_root())
+ return rand32();
+ return getuid();
+#endif
+}
+
+static u32
+generate_gid(void)
+{
+#ifdef _WIN32
+ return 0;
+#else
+ if (am_root())
+ return rand32();
+ return getgid();
+#endif
+}
+
+#ifdef _WIN32
+# ifndef S_IFLNK
+# define S_IFLNK 0120000
+# endif
+# ifndef S_IFSOCK
+# define S_IFSOCK 0140000
+# endif
+#endif
+
+static int
+set_random_unix_metadata(struct wim_inode *inode)
+{
+ struct wimlib_unix_data dat;
+
+ dat.uid = generate_uid();
+ dat.gid = generate_gid();
+ if (inode_is_symlink(inode))
+ dat.mode = S_IFLNK | 0777;
+ else if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY)
+ dat.mode = S_IFDIR | 0700 | (rand32() % 07777);
+ else if (is_zero_hash(inode_get_hash_of_unnamed_data_stream(inode)) &&
+ randbool() && am_root())
+ {
+ dat.mode = rand32() % 07777;
+ switch (rand32() % 4) {
+ case 0:
+ dat.mode |= S_IFIFO;
+ break;
+ case 1:
+ dat.mode |= S_IFCHR;
+ dat.rdev = 261; /* /dev/zero */
+ break;
+ case 2:
+ dat.mode |= S_IFBLK;
+ dat.rdev = 261; /* /dev/zero */
+ break;
+ default:
+ dat.mode |= S_IFSOCK;
+ break;
+ }
+ } else {
+ dat.mode = S_IFREG | 0400 | (rand32() % 07777);
+ }
+ dat.rdev = 0;
+
+ if (!inode_set_unix_data(inode, &dat, UNIX_DATA_ALL))
+ return WIMLIB_ERR_NOMEM;
+
+ return 0;
+}
+
+static noinline_for_stack int
+set_random_xattrs(struct wim_inode *inode)
+{
+ int num_xattrs = 1 + rand32() % 16;
+ char entries[8192];
+ struct wim_xattr_entry *entry = (void *)entries;
+ size_t entries_size;
+ struct wimlib_unix_data unix_data;
+#ifdef _WIN32
+ const char *prefix = "";
+#else
+ const char *prefix = "user.";
+#endif
+ static const char capability_name[] = "security.capability";
+ bool generated_capability_xattr = false;
+
+ /*
+ * On Linux, xattrs in the "user" namespace are only permitted on
+ * regular files and directories. For other types of files we can use
+ * the "trusted" namespace, but this requires root.
+ */
+ if (inode_is_symlink(inode) ||
+ (inode_get_unix_data(inode, &unix_data) &&
+ !S_ISREG(unix_data.mode) && !S_ISDIR(unix_data.mode)))
+ {
+ if (!am_root())
+ return 0;
+ prefix = "trusted.";
+ }
+
+ for (int i = 0; i < num_xattrs; i++) {
+ int value_len = rand32() % 64;
+ u8 *p;
+
+ #ifdef _WIN32
+ if (value_len == 0)
+ value_len++;
+ #endif
+
+ entry->value_len = cpu_to_le16(value_len);
+ entry->flags = 0;
+
+ if (rand32() % 16 == 0 && am_root() &&
+ !generated_capability_xattr) {
+ int name_len = sizeof(capability_name) - 1;
+ entry->name_len = name_len;
+ p = mempcpy(entry->name, capability_name, name_len + 1);
+ generated_capability_xattr = true;
+ } else {
+ int name_len = 1 + rand32() % 64;
+
+ entry->name_len = strlen(prefix) + name_len;
+ p = mempcpy(entry->name, prefix, strlen(prefix));
+ *p++ = 'A' + i;
+ for (int j = 1; j < name_len; j++) {
+ do {
+ #ifdef _WIN32
+ *p = 'A' + rand8() % 26;
+ #else
+ *p = rand8();
+ #endif
+ } while (*p == '\0');
+ p++;
+ }
+ *p++ = '\0';
+ }
+ for (int j = 0; j < value_len; j++)
+ *p++ = rand8();
+
+ entry = (void *)p;
+ }
+
+ entries_size = (char *)entry - entries;
+ wimlib_assert(entries_size > 0 && entries_size <= sizeof(entries));
+
+ if (!inode_set_xattrs(inode, entries, entries_size))
+ return WIMLIB_ERR_NOMEM;
+
+ return 0;
+}
+
static int
set_random_metadata(struct wim_inode *inode, struct generation_context *ctx)
{
/* Security descriptor */
if (randbool()) {
- char desc[8192] _aligned_attribute(8);
+ char desc[8192] __attribute__((aligned(8)));
size_t size;
size = generate_random_security_descriptor(desc, ctx);
return WIMLIB_ERR_NOMEM;
}
+ /* Standard UNIX permissions and special files */
+ if (rand32() % 16 == 0) {
+ int ret = set_random_unix_metadata(inode);
+ if (ret)
+ return ret;
+ }
+
+ /* Extended attributes */
+ if (rand32() % 32 == 0) {
+ int ret = set_random_xattrs(inode);
+ if (ret)
+ return ret;
+ }
+
return 0;
}
}
}
-static int
-add_stream(struct wim_inode *inode, struct generation_context *ctx,
- int stream_type, const utf16lechar *stream_name,
- void *buffer, size_t size)
-{
- struct blob_descriptor *blob = NULL;
- struct wim_inode_stream *strm;
-
- if (size) {
- blob = new_blob_descriptor();
- if (!blob)
- goto err_nomem;
- blob->attached_buffer = buffer;
- blob->blob_location = BLOB_IN_ATTACHED_BUFFER;
- blob->size = size;
- }
-
- strm = inode_add_stream(inode, stream_type, stream_name, blob);
- if (unlikely(!strm))
- goto err_nomem;
-
- prepare_unhashed_blob(blob, inode, strm->stream_id,
- ctx->params->unhashed_blobs);
- return 0;
-
-err_nomem:
- free_blob_descriptor(blob);
- return WIMLIB_ERR_NOMEM;
-}
-
static noinline_for_stack int
set_random_reparse_point(struct wim_inode *inode, struct generation_context *ctx)
{
{
void *buffer = NULL;
size_t size;
+ int ret;
size = select_stream_size(ctx);
if (size) {
generate_data(buffer, size, ctx);
}
- return add_stream(inode, ctx, STREAM_TYPE_DATA, stream_name,
- buffer, size);
+ ret = 0;
+ if (!inode_add_stream_with_data(inode, STREAM_TYPE_DATA, stream_name,
+ buffer, size, ctx->params->blob_table))
+ ret = WIMLIB_ERR_NOMEM;
+ FREE(buffer);
+ return ret;
}
static int
ret = add_random_data_stream(inode, ctx, stream_name);
if (ret)
return ret;
- stream_name[0] += cpu_to_le16(1);
+ stream_name[0] =
+ cpu_to_le16(le16_to_cpu(stream_name[0]) + 1);
}
}
* within the same directory. */
hash = 0;
for (const utf16lechar *p = name; *p; p++)
- hash = (hash * 31) + *p;
+ hash = (hash * 31) + le16_to_cpu(*p);
FREE(child->d_short_name);
child->d_short_name = memdup(name, (name_len + 1) * 2);
child->d_short_name_nbytes = name_len * 2;
!inode_is_symlink(inode1)))
goto mismatch;
- /* SPARSE_FILE may be cleared in UNIX and NTFS-3G modes, or in Windows
- * mode if the inode is a directory. */
+ /* SPARSE_FILE may be cleared. This is true in UNIX and NTFS-3G modes.
+ * In Windows mode it should only be true for directories, but even on
+ * nondirectories it doesn't work 100% of the time for some reason. */
if ((changed & FILE_ATTRIBUTE_SPARSE_FILE) &&
- !((cleared & FILE_ATTRIBUTE_SPARSE_FILE) &&
- ((cmp_flags & (WIMLIB_CMP_FLAG_UNIX_MODE |
- WIMLIB_CMP_FLAG_NTFS_3G_MODE)) ||
- ((cmp_flags & WIMLIB_CMP_FLAG_WINDOWS_MODE) &&
- (inode1->i_attributes & FILE_ATTRIBUTE_DIRECTORY)))))
+ !(cleared & FILE_ATTRIBUTE_SPARSE_FILE))
goto mismatch;
/* COMPRESSED may change in UNIX and NTFS-3G modes. (It *should* be
return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
}
+static void
+print_security_descriptor(const void *desc, size_t size, FILE *fp)
+{
+ print_byte_field(desc, size, fp);
+#ifdef _WIN32
+ wchar_t *str = NULL;
+ ConvertSecurityDescriptorToStringSecurityDescriptorW(
+ (void *)desc,
+ SDDL_REVISION_1,
+ OWNER_SECURITY_INFORMATION |
+ GROUP_SECURITY_INFORMATION |
+ DACL_SECURITY_INFORMATION |
+ SACL_SECURITY_INFORMATION,
+ &str,
+ NULL);
+ if (str) {
+ fprintf(fp, " [ %ls ]", str);
+ LocalFree(str);
+ }
+#endif /* _WIN32 */
+}
+
+static int
+cmp_security(const struct wim_inode *inode1, const struct wim_inode *inode2,
+ const struct wim_image_metadata *imd1,
+ const struct wim_image_metadata *imd2, int cmp_flags)
+{
+ /*
+ * Unfortunately this has to be disabled on Windows for now, since
+ * Windows changes security descriptors upon backup/restore in ways that
+ * are difficult to replicate...
+ */
+ if (cmp_flags & WIMLIB_CMP_FLAG_WINDOWS_MODE)
+ return 0;
+
+ if (inode_has_security_descriptor(inode1)) {
+ if (inode_has_security_descriptor(inode2)) {
+ const void *desc1 = imd1->security_data->descriptors[inode1->i_security_id];
+ const void *desc2 = imd2->security_data->descriptors[inode2->i_security_id];
+ size_t size1 = imd1->security_data->sizes[inode1->i_security_id];
+ size_t size2 = imd2->security_data->sizes[inode2->i_security_id];
+
+ if (size1 != size2 || memcmp(desc1, desc2, size1)) {
+ ERROR("Security descriptor of %"TS" differs!",
+ inode_any_full_path(inode1));
+ fprintf(stderr, "desc1=");
+ print_security_descriptor(desc1, size1, stderr);
+ fprintf(stderr, "\ndesc2=");
+ print_security_descriptor(desc2, size2, stderr);
+ fprintf(stderr, "\n");
+ return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
+ }
+ } else if (!(cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)) {
+ ERROR("%"TS" has a security descriptor in the first image but "
+ "not in the second image!", inode_any_full_path(inode1));
+ return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
+ }
+ } else if (inode_has_security_descriptor(inode2)) {
+ /* okay --- consider it acceptable if a default security
+ * descriptor was assigned */
+ /*ERROR("%"TS" has a security descriptor in the second image but "*/
+ /*"not in the first image!", inode_any_full_path(inode1));*/
+ /*return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;*/
+ }
+ return 0;
+}
+
static int
cmp_object_ids(const struct wim_inode *inode1,
const struct wim_inode *inode2, int cmp_flags)
return 0;
}
+static int
+cmp_unix_metadata(const struct wim_inode *inode1,
+ const struct wim_inode *inode2, int cmp_flags)
+{
+ struct wimlib_unix_data dat1, dat2;
+ bool present1, present2;
+
+ present1 = inode_get_unix_data(inode1, &dat1);
+ present2 = inode_get_unix_data(inode2, &dat2);
+
+ if (!present1 && !present2)
+ return 0;
+
+ if (present1 && !present2) {
+ if (cmp_flags & (WIMLIB_CMP_FLAG_NTFS_3G_MODE |
+ WIMLIB_CMP_FLAG_WINDOWS_MODE))
+ return 0;
+ ERROR("%"TS" unexpectedly lost its UNIX metadata",
+ inode_any_full_path(inode1));
+ return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
+ }
+
+ if (!present1 && present2) {
+ if (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)
+ return 0;
+ ERROR("%"TS" unexpectedly gained UNIX metadata",
+ inode_any_full_path(inode1));
+ return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
+ }
+
+ if (memcmp(&dat1, &dat2, sizeof(dat1)) != 0) {
+ ERROR("UNIX metadata of %"TS" differs: "
+ "[uid=%u, gid=%u, mode=0%o, rdev=%u] vs. "
+ "[uid=%u, gid=%u, mode=0%o, rdev=%u]",
+ inode_any_full_path(inode1),
+ dat1.uid, dat1.gid, dat1.mode, dat1.rdev,
+ dat2.uid, dat2.gid, dat2.mode, dat2.rdev);
+ return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
+ }
+
+ return 0;
+}
+
+static int
+cmp_xattr_names(const void *p1, const void *p2)
+{
+ const struct wim_xattr_entry *entry1 = *(const struct wim_xattr_entry **)p1;
+ const struct wim_xattr_entry *entry2 = *(const struct wim_xattr_entry **)p2;
+ int res;
+
+ res = entry1->name_len - entry2->name_len;
+ if (res)
+ return res;
+
+ return memcmp(entry1->name, entry2->name, entry1->name_len);
+}
+
+/* Validate and sort by name a list of extended attributes */
+static int
+parse_xattrs(const void *xattrs, u32 len,
+ const struct wim_xattr_entry *entries[],
+ u32 *num_entries_p)
+{
+ u32 limit = *num_entries_p;
+ u32 num_entries = 0;
+ const struct wim_xattr_entry *entry = xattrs;
+
+ while ((void *)entry < xattrs + len) {
+ if (!valid_xattr_entry(entry, xattrs + len - (void *)entry)) {
+ ERROR("Invalid xattr entry");
+ return WIMLIB_ERR_INVALID_XATTR;
+ }
+ if (num_entries >= limit) {
+ ERROR("Too many xattr entries");
+ return WIMLIB_ERR_INVALID_XATTR;
+ }
+ entries[num_entries++] = entry;
+ entry = xattr_entry_next(entry);
+ }
+
+ if (num_entries == 0) {
+ ERROR("No xattr entries");
+ return WIMLIB_ERR_INVALID_XATTR;
+ }
+
+ qsort(entries, num_entries, sizeof(entries[0]), cmp_xattr_names);
+
+ for (u32 i = 1; i < num_entries; i++) {
+ if (cmp_xattr_names(&entries[i - 1], &entries[i]) == 0) {
+ ERROR("Duplicate xattr names");
+ return WIMLIB_ERR_INVALID_XATTR;
+ }
+ }
+
+ *num_entries_p = num_entries;
+ return 0;
+}
+
+static int
+cmp_xattrs(const struct wim_inode *inode1, const struct wim_inode *inode2,
+ int cmp_flags)
+{
+ const void *xattrs1, *xattrs2;
+ u32 len1, len2;
+
+ xattrs1 = inode_get_xattrs(inode1, &len1);
+ xattrs2 = inode_get_xattrs(inode2, &len2);
+
+ if (!xattrs1 && !xattrs2) {
+ return 0;
+ } else if (xattrs1 && !xattrs2) {
+ if (cmp_flags & WIMLIB_CMP_FLAG_NTFS_3G_MODE)
+ return 0;
+ ERROR("%"TS" unexpectedly lost its xattrs",
+ inode_any_full_path(inode1));
+ return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
+ } else if (!xattrs1 && xattrs2) {
+ ERROR("%"TS" unexpectedly gained xattrs",
+ inode_any_full_path(inode1));
+ return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
+ } else {
+ const int max_entries = 64;
+ const struct wim_xattr_entry *entries1[max_entries];
+ const struct wim_xattr_entry *entries2[max_entries];
+ u32 xattr_count1 = max_entries;
+ u32 xattr_count2 = max_entries;
+ int ret;
+
+ ret = parse_xattrs(xattrs1, len1, entries1, &xattr_count1);
+ if (ret) {
+ ERROR("%"TS": invalid xattrs",
+ inode_any_full_path(inode1));
+ return ret;
+ }
+ ret = parse_xattrs(xattrs2, len2, entries2, &xattr_count2);
+ if (ret) {
+ ERROR("%"TS": invalid xattrs",
+ inode_any_full_path(inode2));
+ return ret;
+ }
+ if (xattr_count1 != xattr_count2) {
+ ERROR("%"TS": number of xattrs changed. had %u "
+ "before, now has %u", inode_any_full_path(inode1),
+ xattr_count1, xattr_count2);
+ }
+ for (u32 i = 0; i < xattr_count1; i++) {
+ const struct wim_xattr_entry *entry1 = entries1[i];
+ const struct wim_xattr_entry *entry2 = entries2[i];
+
+ if (entry1->value_len != entry2->value_len ||
+ entry1->name_len != entry2->name_len ||
+ entry1->flags != entry2->flags ||
+ memcmp(entry1->name, entry2->name,
+ entry1->name_len) ||
+ memcmp(entry1->name + entry1->name_len + 1,
+ entry2->name + entry2->name_len + 1,
+ le16_to_cpu(entry1->value_len)))
+ {
+ ERROR("xattr %.*s of %"TS" differs",
+ entry1->name_len, entry1->name,
+ inode_any_full_path(inode1));
+ return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
+ }
+ }
+ return 0;
+ }
+}
+
+/*
+ * ext4 only supports timestamps from years 1901 to 2446, more specifically the
+ * range [-0x80000000, 0x380000000) seconds relative to the start of UNIX epoch.
+ */
+static bool
+in_ext4_range(u64 ts)
+{
+ return ts >= time_t_to_wim_timestamp(-0x80000000LL) &&
+ ts < time_t_to_wim_timestamp(0x380000000LL);
+}
+
+static bool
+timestamps_differ(u64 ts1, u64 ts2, int cmp_flags)
+{
+ if (ts1 == ts2)
+ return false;
+ if ((cmp_flags & WIMLIB_CMP_FLAG_EXT4) &&
+ (!in_ext4_range(ts1) || !in_ext4_range(ts2)))
+ return false;
+ return true;
+}
+
+static int
+cmp_timestamps(const struct wim_inode *inode1, const struct wim_inode *inode2,
+ int cmp_flags)
+{
+ if (timestamps_differ(inode1->i_creation_time,
+ inode2->i_creation_time, cmp_flags) &&
+ !(cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)) {
+ ERROR("Creation time of %"TS" differs; %"PRIu64" != %"PRIu64,
+ inode_any_full_path(inode1),
+ inode1->i_creation_time, inode2->i_creation_time);
+ return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
+ }
+
+ if (timestamps_differ(inode1->i_last_write_time,
+ inode2->i_last_write_time, cmp_flags)) {
+ ERROR("Last write time of %"TS" differs; %"PRIu64" != %"PRIu64,
+ inode_any_full_path(inode1),
+ inode1->i_last_write_time, inode2->i_last_write_time);
+ return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
+ }
+
+ if (timestamps_differ(inode1->i_last_access_time,
+ inode2->i_last_access_time, cmp_flags) &&
+ /*
+ * On Windows, sometimes a file's last access time will end up as
+ * the current time rather than the expected time. Maybe caused by
+ * some OS process scanning the files?
+ */
+ !(cmp_flags & WIMLIB_CMP_FLAG_WINDOWS_MODE)) {
+ ERROR("Last access time of %"TS" differs; %"PRIu64" != %"PRIu64,
+ inode_any_full_path(inode1),
+ inode1->i_last_access_time, inode2->i_last_access_time);
+ return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
+ }
+
+ return 0;
+}
+
static int
cmp_inodes(const struct wim_inode *inode1, const struct wim_inode *inode2,
const struct wim_image_metadata *imd1,
return ret;
/* Compare security descriptors */
- if (inode_has_security_descriptor(inode1)) {
- if (inode_has_security_descriptor(inode2)) {
- const void *desc1 = imd1->security_data->descriptors[inode1->i_security_id];
- const void *desc2 = imd2->security_data->descriptors[inode2->i_security_id];
- size_t size1 = imd1->security_data->sizes[inode1->i_security_id];
- size_t size2 = imd2->security_data->sizes[inode2->i_security_id];
-
- if (size1 != size2 || memcmp(desc1, desc2, size1)) {
- ERROR("Security descriptor of %"TS" differs!",
- inode_any_full_path(inode1));
- return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
- }
- } else if (!(cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)) {
- ERROR("%"TS" has a security descriptor in the first image but "
- "not in the second image!", inode_any_full_path(inode1));
- return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
- }
- } else if (inode_has_security_descriptor(inode2)) {
- /* okay --- consider it acceptable if a default security
- * descriptor was assigned */
- /*ERROR("%"TS" has a security descriptor in the second image but "*/
- /*"not in the first image!", inode_any_full_path(inode1));*/
- /*return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;*/
- }
+ ret = cmp_security(inode1, inode2, imd1, imd2, cmp_flags);
+ if (ret)
+ return ret;
/* Compare streams */
for (unsigned i = 0; i < inode1->i_num_streams; i++) {
if (ret)
return ret;
+ /* Compare timestamps */
+ ret = cmp_timestamps(inode1, inode2, cmp_flags);
+ if (ret)
+ return ret;
+
+ /* Compare standard UNIX metadata */
+ ret = cmp_unix_metadata(inode1, inode2, cmp_flags);
+ if (ret)
+ return ret;
+
+ /* Compare extended attributes */
+ ret = cmp_xattrs(inode1, inode2, cmp_flags);
+ if (ret)
+ return ret;
+
return 0;
}