FILE_ATTRIBUTE_SYSTEM |
FILE_ATTRIBUTE_ARCHIVE |
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED |
- FILE_ATTRIBUTE_COMPRESSED));
+ FILE_ATTRIBUTE_COMPRESSED |
+ FILE_ATTRIBUTE_SPARSE_FILE));
/* File attributes */
inode->i_attributes |= attrib;
size_t mask = -1;
size_t num_byte_fills = rand32() % 256;
+ /* Start by initializing to a random byte */
memset(buffer, rand32() % 256, size);
+ /* Add some random bytes in some random places */
for (size_t i = 0; i < num_byte_fills; i++) {
u8 b = rand8();
mask = (size_t)-1 << rand32() % 4;
}
+ /* Sometimes add a wave pattern */
if (rand32() % 8 == 0) {
double magnitude = rand32() % 128;
double scale = 1.0 / (1 + (rand32() % 256));
for (size_t i = 0; i < size; i++)
buffer[i] += (int)(magnitude * cos(i * scale));
}
+
+ /* Sometimes add some zero regions (holes) */
+ if (rand32() % 4 == 0) {
+ size_t num_holes = 1 + (rand32() % 16);
+ for (size_t i = 0; i < num_holes; i++) {
+ size_t hole_offset = rand32() % size;
+ size_t hole_len = min(size - hole_offset,
+ size / (1 + (rand32() % 16)));
+ memset(&buffer[hole_offset], 0, hole_len);
+ }
+ }
}
static int
return 0;
}
+static inline bool
+is_valid_windows_filename_char(utf16lechar c)
+{
+ return le16_to_cpu(c) > 31 &&
+ c != cpu_to_le16('/') &&
+ c != cpu_to_le16('<') &&
+ c != cpu_to_le16('>') &&
+ c != cpu_to_le16(':') &&
+ c != cpu_to_le16('"') &&
+ c != cpu_to_le16('/' ) &&
+ c != cpu_to_le16('\\') &&
+ c != cpu_to_le16('|') &&
+ c != cpu_to_le16('?') &&
+ c != cpu_to_le16('*');
+}
+
+/* Is the character valid in a filename on the current platform? */
+static inline bool
+is_valid_filename_char(utf16lechar c)
+{
+#ifdef __WIN32__
+ return is_valid_windows_filename_char(c);
+#else
+ return c != cpu_to_le16('\0') && c != cpu_to_le16('/');
+#endif
+}
+
+/* Generate a random filename and return its length. */
static int
-generate_random_file_name(tchar name[], int max_len,
- struct generation_context *ctx)
+generate_random_filename(utf16lechar name[], int max_len,
+ struct generation_context *ctx)
{
- int length;
+ int len;
+
+ /* Choose the length of the name. */
switch (rand32() % 8) {
default:
/* short name */
- length = 1 + (rand32() % 6);
+ len = 1 + (rand32() % 6);
break;
case 2:
case 3:
case 4:
/* medium-length name */
- length = 7 + (rand32() % 8);
+ len = 7 + (rand32() % 8);
break;
case 5:
case 6:
/* long name */
- length = 15 + (rand32() % 15);
+ len = 15 + (rand32() % 15);
break;
case 7:
/* very long name */
- length = 30 + (rand32() % 90);
+ len = 30 + (rand32() % 90);
break;
}
- length = min(length, max_len);
- for (int i = 0; i < length; i++)
- name[i] = 'a' + (rand32() % 26);
- name[length] = 0;
- return length;
+ len = min(len, max_len);
+
+retry:
+ /* Generate the characters in the name. */
+ for (int i = 0; i < len; i++) {
+ do {
+ name[i] = rand16();
+ } while (!is_valid_filename_char(name[i]));
+ }
+
+ /* Add a null terminator. */
+ name[len] = cpu_to_le16('\0');
+
+ /* Don't generate . and .. */
+ if (name[0] == cpu_to_le16('.') &&
+ (len == 1 || (len == 2 && name[1] == cpu_to_le16('.'))))
+ goto retry;
+
+ return len;
+}
+
+/* The set of characters which are valid in short filenames. */
+static const char valid_short_name_chars[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+ 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '!', '#', '$', '%', '&', '\'', '(', ')', '-', '@', '^', '_', '`', '{',
+ '}', '~',
+ /* Note: Windows does not allow space and 128-255 in short filenames
+ * (tested on both NTFS and FAT). */
+};
+
+static int
+generate_short_name_component(utf16lechar p[], int len)
+{
+ for (int i = 0; i < len; i++) {
+ char c = valid_short_name_chars[rand32() %
+ ARRAY_LEN(valid_short_name_chars)];
+ p[i] = cpu_to_le16(c);
+ }
+ return len;
+}
+
+/* Generate a random short (8.3) filename and return its length.
+ * The @name array must have length >= 13 (8 + 1 + 3 + 1). */
+static int
+generate_random_short_name(utf16lechar name[], struct generation_context *ctx)
+{
+ /*
+ * Legal short names on Windows consist of 1 to 8 characters, optionally
+ * followed by a dot then 1 to 3 more characters. Only certain
+ * characters are allowed.
+ */
+ int base_len = 1 + (rand32() % 8);
+ int ext_len = rand32() % 4;
+ int total_len;
+
+ base_len = generate_short_name_component(name, base_len);
+
+ if (ext_len) {
+ name[base_len] = cpu_to_le16('.');
+ ext_len = generate_short_name_component(&name[base_len + 1],
+ ext_len);
+ total_len = base_len + 1 + ext_len;
+ } else {
+ total_len = base_len;
+ }
+ name[total_len] = cpu_to_le16('\0');
+ return total_len;
}
static u64
}
static bool
-is_name_forbidden_in_win32_namespace(const utf16lechar *name)
+is_name_valid_in_win32_namespace(const utf16lechar *name)
{
- static const utf16lechar forbidden_names[][5] = {
- { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('N'), },
- { cpu_to_le16('P'), cpu_to_le16('R'), cpu_to_le16('N'), },
- { cpu_to_le16('A'), cpu_to_le16('U'), cpu_to_le16('X'), },
- { cpu_to_le16('N'), cpu_to_le16('U'), cpu_to_le16('L'), },
- { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M'), cpu_to_le16('1'), },
- { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M'), cpu_to_le16('2'), },
- { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M'), cpu_to_le16('3'), },
- { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M'), cpu_to_le16('4'), },
- { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M'), cpu_to_le16('5'), },
- { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M'), cpu_to_le16('6'), },
- { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M'), cpu_to_le16('7'), },
- { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M'), cpu_to_le16('8'), },
- { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M'), cpu_to_le16('9'), },
- { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), cpu_to_le16('1'), },
- { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), cpu_to_le16('2'), },
- { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), cpu_to_le16('3'), },
- { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), cpu_to_le16('4'), },
- { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), cpu_to_le16('5'), },
- { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), cpu_to_le16('6'), },
- { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), cpu_to_le16('7'), },
- { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), cpu_to_le16('8'), },
- { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), cpu_to_le16('9'), },
+ const utf16lechar *p;
+
+ static const char * const reserved_names[] = {
+ "CON", "PRN", "AUX", "NUL",
+ "COM1", "COM2", "COM3", "COM4", "COM5",
+ "COM6", "COM7", "COM8", "COM9",
+ "LPT1", "LPT2", "LPT3", "LPT4", "LPT5",
+ "LPT6", "LPT7", "LPT8", "LPT9",
};
- if (!name)
+ /* The name must be nonempty. */
+ if (!name || !*name)
return false;
- for (size_t i = 0; i < ARRAY_LEN(forbidden_names); i++)
- if (!cmp_utf16le_strings_z(forbidden_names[i], name, true))
- return true;
+ /* All characters must be valid on Windows. */
+ for (p = name; *p; p++)
+ if (!is_valid_windows_filename_char(*p))
+ return false;
+
+ /* Note: a trailing dot or space is permitted, even though on Windows
+ * such a file can only be accessed using a WinNT-style path. */
+
+ /* The name can't be one of the reserved names or be a reserved name
+ * with an extension. Case insensitive. */
+ for (size_t i = 0; i < ARRAY_LEN(reserved_names); i++) {
+ for (size_t j = 0; ; j++) {
+ u16 c1 = le16_to_cpu(name[j]);
+ u16 c2 = reserved_names[i][j];
+ if (c2 == '\0') {
+ if (c1 == '\0' || c1 == '.')
+ return false;
+ break;
+ }
+ if (upcase[c1] != upcase[c2])
+ break;
+ }
+ }
- return false;
+ return true;
}
static int
set_random_short_name(struct wim_dentry *dir, struct wim_dentry *child,
struct generation_context *ctx)
{
- tchar name[12 + 1];
- int ret;
- const utf16lechar *short_name;
+ utf16lechar name[12 + 1];
+ int name_len;
u32 hash;
struct wim_dentry **bucket;
/* If the long name is not allowed in the Win32 namespace, then it
* cannot be assigned a corresponding short name. */
- if (is_name_forbidden_in_win32_namespace(child->d_name))
+ if (!is_name_valid_in_win32_namespace(child->d_name))
return 0;
retry:
/* Don't select a short name that is already used by a long name within
* the same directory. */
do {
- int len = generate_random_file_name(name, 12, ctx);
-
- /* Legal short names on Windows take one of the following forms:
- *
- * - 1 to 8 characters
- * - 1 to 8 characters, then a dot, then 1 to 3 characters */
- if (len >= 9) {
- if (len == 9)
- len--;
- else
- name[8] = T('.');
- }
- name[len] = 0;
- } while (get_dentry_child_with_name(dir, name,
- WIMLIB_CASE_PLATFORM_DEFAULT));
+ name_len = generate_random_short_name(name, ctx);
+ } while (get_dentry_child_with_utf16le_name(dir, name, name_len * 2,
+ WIMLIB_CASE_INSENSITIVE));
/* Don't select a short name that is already used by another short name
* within the same directory. */
hash = 0;
- for (const tchar *p = name; *p; p++)
- hash = (hash * 31) + totlower(*p);
- ret = tstr_get_utf16le(name, &short_name);
- if (ret)
- return ret;
+ for (const utf16lechar *p = name; *p; p++)
+ hash = (hash * 31) + *p;
FREE(child->d_short_name);
- child->d_short_name = utf16le_dup(short_name);
- child->d_short_name_nbytes = utf16le_len_bytes(short_name);
- tstr_put_utf16le(short_name);
+ child->d_short_name = memdup(name, (name_len + 1) * 2);
+ child->d_short_name_nbytes = name_len * 2;
if (!child->d_short_name)
return WIMLIB_ERR_NOMEM;
bucket = &ctx->used_short_names[hash % ARRAY_LEN(ctx->used_short_names)];
for (struct wim_dentry *d = *bucket; d != NULL;
- d = d->d_next_extraction_alias)
- if (!cmp_utf16le_strings_z(child->d_short_name,
- d->d_short_name, true))
+ d = d->d_next_extraction_alias) {
+ if (!cmp_utf16le_strings(child->d_short_name, name_len,
+ d->d_short_name, d->d_short_name_nbytes / 2,
+ true)) {
goto retry;
+ }
+ }
- if (is_name_forbidden_in_win32_namespace(child->d_short_name))
+ if (!is_name_valid_in_win32_namespace(child->d_short_name))
goto retry;
child->d_next_extraction_alias = *bucket;
for (u32 i = 0; i < num_children; i++) {
/* Generate the next child dentry. */
-
- tchar name[128 + 1];
- struct wim_dentry *duplicate;
struct wim_inode *inode;
u64 ino;
bool is_directory;
+ utf16lechar name[63 + 1]; /* for UNIX extraction: 63 * 4 <= 255 */
+ int name_len;
+ struct wim_dentry *duplicate;
- /* Choose a long filename that is unique within the directory.*/
- do {
- generate_random_file_name(name, 128, ctx);
- } while (get_dentry_child_with_name(dir, name,
- WIMLIB_CASE_PLATFORM_DEFAULT));
-
- /* Decide whether to create a directory or not.
- * If not a directory, also decide on the inode number (i.e. we
- * may generate a "hard link" to an existing file). */
+ /* Decide whether to create a directory or not. If not a
+ * directory, also decide on the inode number (i.e. we may
+ * generate a "hard link" to an existing file). */
is_directory = ((rand32() % 16) <= 6);
if (is_directory)
ino = 0;
else
ino = select_inode_number(ctx);
- /* Create the dentry and add it to the directory. */
- ret = inode_table_new_dentry(ctx->params->inode_table, name,
+ /* Create the dentry. */
+ ret = inode_table_new_dentry(ctx->params->inode_table, NULL,
ino, 0, is_directory, &child);
if (ret)
return ret;
+ /* Choose a filename that is unique within the directory.*/
+ do {
+ name_len = generate_random_filename(name,
+ ARRAY_LEN(name) - 1,
+ ctx);
+ } while (get_dentry_child_with_utf16le_name(dir, name, name_len * 2,
+ WIMLIB_CASE_PLATFORM_DEFAULT));
+
+ ret = dentry_set_name_utf16le(child, name, name_len * 2);
+ if (ret) {
+ free_dentry(child);
+ return ret;
+ }
+
+ /* Add the dentry to the directory. */
duplicate = dentry_add_child(dir, child);
wimlib_assert(!duplicate);
/* Compare short filenames, case insensitively. */
if (!(d2->d_short_name_nbytes == 0 &&
- (cmp_flags & WIMLIB_CMP_FLAG_SHORT_NAMES_NOT_PRESERVED)) &&
+ (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE)) &&
cmp_utf16le_strings(d1->d_short_name, d1->d_short_name_nbytes / 2,
d2->d_short_name, d2->d_short_name_nbytes / 2,
true))
return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
}
+static const struct {
+ u32 flag;
+ const char *name;
+} file_attr_flags[] = {
+ {FILE_ATTRIBUTE_READONLY, "READONLY"},
+ {FILE_ATTRIBUTE_HIDDEN, "HIDDEN"},
+ {FILE_ATTRIBUTE_SYSTEM, "SYSTEM"},
+ {FILE_ATTRIBUTE_DIRECTORY, "DIRECTORY"},
+ {FILE_ATTRIBUTE_ARCHIVE, "ARCHIVE"},
+ {FILE_ATTRIBUTE_DEVICE, "DEVICE"},
+ {FILE_ATTRIBUTE_NORMAL, "NORMAL"},
+ {FILE_ATTRIBUTE_TEMPORARY, "TEMPORARY"},
+ {FILE_ATTRIBUTE_SPARSE_FILE, "SPARSE_FILE"},
+ {FILE_ATTRIBUTE_REPARSE_POINT, "REPARSE_POINT"},
+ {FILE_ATTRIBUTE_COMPRESSED, "COMPRESSED"},
+ {FILE_ATTRIBUTE_OFFLINE, "OFFLINE"},
+ {FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, "NOT_CONTENT_INDEXED"},
+ {FILE_ATTRIBUTE_ENCRYPTED, "ENCRYPTED"},
+ {FILE_ATTRIBUTE_VIRTUAL, "VIRTUAL"},
+};
+
+static int
+cmp_attributes(const struct wim_inode *inode1,
+ const struct wim_inode *inode2, int cmp_flags)
+{
+ const u32 changed = inode1->i_attributes ^ inode2->i_attributes;
+ const u32 set = inode2->i_attributes & ~inode1->i_attributes;
+ const u32 cleared = inode1->i_attributes & ~inode2->i_attributes;
+
+ /* NORMAL may change, but it must never be set along with other
+ * attributes. */
+ if ((inode2->i_attributes & FILE_ATTRIBUTE_NORMAL) &&
+ (inode2->i_attributes & ~FILE_ATTRIBUTE_NORMAL))
+ goto mismatch;
+
+ /* DIRECTORY must not change. */
+ if (changed & FILE_ATTRIBUTE_DIRECTORY)
+ goto mismatch;
+
+ /* REPARSE_POINT may be cleared in UNIX mode if the inode is not a
+ * symlink. */
+ if ((changed & FILE_ATTRIBUTE_REPARSE_POINT) &&
+ !((cleared & FILE_ATTRIBUTE_REPARSE_POINT) &&
+ (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE) &&
+ !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. */
+ 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)))))
+ goto mismatch;
+
+ /* COMPRESSED may change in UNIX and NTFS-3G modes. (It *should* be
+ * preserved in NTFS-3G mode, but it's not implemented yet.) */
+ if ((changed & FILE_ATTRIBUTE_COMPRESSED) &&
+ !(cmp_flags & (WIMLIB_CMP_FLAG_UNIX_MODE |
+ WIMLIB_CMP_FLAG_NTFS_3G_MODE)))
+ goto mismatch;
+
+ /* All other attributes can change in UNIX mode, but not in any other
+ * mode. */
+ if ((changed & ~(FILE_ATTRIBUTE_NORMAL |
+ FILE_ATTRIBUTE_DIRECTORY |
+ FILE_ATTRIBUTE_REPARSE_POINT |
+ FILE_ATTRIBUTE_SPARSE_FILE |
+ FILE_ATTRIBUTE_COMPRESSED)) &&
+ !(cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE))
+ goto mismatch;
+
+ return 0;
+
+mismatch:
+ ERROR("Attribute mismatch for %"TS": 0x%08"PRIx32" vs. 0x%08"PRIx32":",
+ inode_any_full_path(inode1), inode1->i_attributes,
+ inode2->i_attributes);
+ for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++) {
+ u32 flag = file_attr_flags[i].flag;
+ if (changed & flag) {
+ fprintf(stderr, "\tFILE_ATTRIBUTE_%s was %s\n",
+ file_attr_flags[i].name,
+ (set & flag) ? "set" : "cleared");
+ }
+ }
+ return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
+}
+
static int
cmp_inodes(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)
{
- const u32 attrib_diff = inode1->i_attributes ^ inode2->i_attributes;
- bool reparse_point_should_preserved = true;
+ int ret;
/* Compare attributes */
- if (cmp_flags & WIMLIB_CMP_FLAG_ATTRIBUTES_NOT_PRESERVED) {
-
- /* In this mode, we expect that most attributes are not
- * preserved. However, FILE_ATTRIBUTE_DIRECTORY should always
- * match. */
- if (attrib_diff & FILE_ATTRIBUTE_DIRECTORY)
- goto attrib_mismatch;
-
- /* We may also expect FILE_ATTRIBUTE_REPARSE_POINT to be
- * preserved for symlinks. It also shouldn't be set if it
- * wasn't set before. */
-
- if ((cmp_flags & WIMLIB_CMP_FLAG_IMAGE2_SHOULD_HAVE_SYMLINKS) &&
- inode_is_symlink(inode1))
- reparse_point_should_preserved = true;
- else
- reparse_point_should_preserved = false;
-
- if ((attrib_diff & FILE_ATTRIBUTE_REPARSE_POINT) &&
- (reparse_point_should_preserved ||
- (inode2->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)))
- goto attrib_mismatch;
- } else {
-
- /* Most attributes should be preserved. */
-
- /* Nothing other than COMPRESSED and NORMAL should have changed.
- */
- if (attrib_diff & ~(FILE_ATTRIBUTE_COMPRESSED |
- FILE_ATTRIBUTE_NORMAL))
- goto attrib_mismatch;
-
- /* COMPRESSED shouldn't have changed unless specifically
- * excluded. */
- if ((attrib_diff & FILE_ATTRIBUTE_COMPRESSED) &&
- !(cmp_flags & WIMLIB_CMP_FLAG_COMPRESSION_NOT_PRESERVED))
- goto attrib_mismatch;
-
- /* We allow NORMAL to change, but not if the file ended up with
- * other attributes set as well. */
- if ((attrib_diff & FILE_ATTRIBUTE_NORMAL) &&
- (inode2->i_attributes & ~FILE_ATTRIBUTE_NORMAL))
- goto attrib_mismatch;
- }
+ ret = cmp_attributes(inode1, inode2, cmp_flags);
+ if (ret)
+ return ret;
/* Compare security descriptors */
if (inode_has_security_descriptor(inode1)) {
inode_any_full_path(inode1));
return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
}
- } else if (!(cmp_flags & WIMLIB_CMP_FLAG_SECURITY_NOT_PRESERVED)) {
+ } 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;
const struct wim_inode_stream *strm2;
if (strm1->stream_type == STREAM_TYPE_REPARSE_POINT &&
- !reparse_point_should_preserved)
+ (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE &&
+ !inode_is_symlink(inode1)))
continue;
if (strm1->stream_type == STREAM_TYPE_UNKNOWN)
if (!strm2) {
/* Corresponding stream not found */
if (stream_is_named(strm1) &&
- (cmp_flags & WIMLIB_CMP_FLAG_ADS_NOT_PRESERVED))
+ (cmp_flags & WIMLIB_CMP_FLAG_UNIX_MODE))
continue;
ERROR("Stream of %"TS" is missing in second image; "
"type %d, named=%d, empty=%d",
}
return 0;
-
-attrib_mismatch:
- ERROR("Attribute mismatch; %"TS" has attributes 0x%08"PRIx32" "
- "in first image but attributes 0x%08"PRIx32" in second image",
- inode_any_full_path(inode1), inode1->i_attributes,
- inode2->i_attributes);
- return WIMLIB_ERR_IMAGES_ARE_DIFFERENT;
}
static int