X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=tests%2Fwlfuzz.c;fp=tests%2Fwlfuzz.c;h=51607869fb11913b3ddc0a0b9e7093e0658eb34f;hp=0000000000000000000000000000000000000000;hb=37d9c3935130128901f6eee8d7531d8ae4b1bca6;hpb=e6aef6a0af9e26797ca1711a8014a5fb62b3755e
diff --git a/tests/wlfuzz.c b/tests/wlfuzz.c
new file mode 100644
index 00000000..51607869
--- /dev/null
+++ b/tests/wlfuzz.c
@@ -0,0 +1,1046 @@
+/*
+ * wlfuzz.c - Randomized tests for wimlib
+ */
+
+/*
+ * Copyright (C) 2015-2016 Eric Biggers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+/*
+ * This program is a randomized test runner for wimlib. It must be linked
+ * against a build of the library compiled with --enable-test-support.
+ *
+ * Various types of tests are run. Most important is the "apply and capture"
+ * test, which works as follows:
+ *
+ * 1. Generate an in-memory WIM image containing a random directory tree
+ * 2. Persist the image into a WIM file
+ * 3. Apply the WIM image to somewhere
+ * 4. Re-capture the applied image
+ * 5. Compare the directory tree of the re-captured image to the original
+ *
+ * Note that this is an "apply and capture" test, not a "capture and apply"
+ * test. By using the filesystem as the intermediary rather than as the
+ * starting point and ending point, the tests will run nearly unchanged
+ * regardless of filesystem type (e.g. UNIX, Windows, or NTFS-3G). This style
+ * of test has been effective at finding bugs in wimlib as well as bugs in
+ * NTFS-3G where its behavior differs from that of Windows.
+ *
+ * Care is taken to exercise different options, such as different compression
+ * formats, when multiple are available.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifndef ENABLE_TEST_SUPPORT
+# error "This program requires that wimlib was configured with --enable-test-support."
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#ifdef WITH_NTFS_3G
+# include
+#endif
+#include
+
+#ifdef __WIN32__
+# include
+# include
+# include
+#endif
+
+#include "wimlib.h"
+#include "wimlib_tchar.h"
+#include "wimlib/test_support.h"
+#include "wimlib/wof.h"
+
+#ifndef O_BINARY
+# define O_BINARY 0
+#endif
+
+#define ARRAY_LEN(A) (sizeof(A) / sizeof((A)[0]))
+
+#define TMP_TARGET_NAME T("wlfuzz-tmp-target")
+#define MAX_NUM_WIMS 4
+
+static bool wimfile_in_use[MAX_NUM_WIMS];
+static int in_use_wimfile_indices[MAX_NUM_WIMS];
+static int num_wimfiles_in_use = 0;
+
+static void
+assertion_failed(int line, const char *format, ...)
+{
+ va_list va;
+
+ va_start(va, format);
+ fprintf(stderr, "ASSERTION FAILED at line %d: ", line);
+ vfprintf(stderr, format, va);
+ fputc('\n', stderr);
+ va_end(va);
+
+ exit(1);
+}
+
+#define ASSERT(expr, msg, ...) \
+({ \
+ if (__builtin_expect(!(expr), 0)) \
+ assertion_failed(__LINE__, (msg), ##__VA_ARGS__); \
+})
+
+#define CHECK_RET(ret) \
+({ \
+ int r = (ret); \
+ ASSERT(!r, "%"TS, wimlib_get_error_string(r)); \
+})
+
+static void
+change_to_temporary_directory(void)
+{
+#ifdef __WIN32__
+ ASSERT(SetCurrentDirectory(L"E:\\"),
+ "failed to change directory to E:\\");
+#else
+ const char *tmpdir = getenv("TMPDIR");
+ if (!tmpdir)
+ tmpdir = P_tmpdir;
+ ASSERT(!chdir(tmpdir),
+ "failed to change to temporary directory \"%s\": %m", tmpdir);
+#endif
+}
+
+static void __attribute__((unused))
+copy_file(const tchar *src, const tchar *dst)
+{
+ int in_fd = topen(src, O_RDONLY|O_BINARY);
+ int out_fd = topen(dst, O_WRONLY|O_TRUNC|O_CREAT|O_BINARY, 0644);
+ char buf[32768];
+ ssize_t bytes_read, bytes_written, i;
+
+ ASSERT(in_fd >= 0, "%"TS": open error: %m", src);
+ ASSERT(out_fd >= 0, "%"TS": open error: %m", dst);
+ while ((bytes_read = read(in_fd, buf, sizeof(buf))) > 0) {
+ for (i = 0; i < bytes_read; i += bytes_written) {
+ bytes_written = write(out_fd, &buf[i], bytes_read - i);
+ ASSERT(bytes_written > 0, "%"TS": write error: %m", dst);
+ }
+ }
+ ASSERT(bytes_read == 0, "%"TS": read error: %m", src);
+ close(in_fd);
+ close(out_fd);
+}
+
+#ifdef WITH_NTFS_3G
+static void
+create_ntfs_volume(const char *name)
+{
+ int fd;
+ int pid;
+ int status;
+ static const char buffer[1] = {0};
+
+ fd = open(name, O_WRONLY|O_TRUNC|O_CREAT|O_NOFOLLOW, 0644);
+ ASSERT(fd >= 0, "%s: open error: %m", name);
+
+ ASSERT(lseek(fd, 999999999, SEEK_SET) != -1, "%s: lseek error: %m", name);
+
+ ASSERT(write(fd, buffer, 1) == 1, "%s: write error: %m", name);
+
+ ASSERT(close(fd) == 0, "%s: close error: %m", name);
+
+ pid = fork();
+ ASSERT(pid >= 0, "fork error: %m");
+ if (pid == 0) {
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ execlp("mkntfs", "mkntfs", "--force", "--fast",
+ name, (char *)NULL);
+ ASSERT(false, "Failed to execute mkntfs: %m");
+ }
+
+ ASSERT(wait(&status) != -1, "wait error: %m");
+ ASSERT(WIFEXITED(status) && WEXITSTATUS(status) == 0,
+ "mkntfs error: exited with status %d", status);
+}
+#endif /* WITH_NTFS_3G */
+
+#ifdef __WIN32__
+
+extern WINAPI NTSTATUS NtQueryDirectoryFile (HANDLE FileHandle,
+ HANDLE Event,
+ PIO_APC_ROUTINE ApcRoutine,
+ PVOID ApcContext,
+ PIO_STATUS_BLOCK IoStatusBlock,
+ PVOID FileInformation,
+ ULONG Length,
+ FILE_INFORMATION_CLASS FileInformationClass,
+ BOOLEAN ReturnSingleEntry,
+ PUNICODE_STRING FileName,
+ BOOLEAN RestartScan);
+
+static void
+delete_directory_tree_recursive(HANDLE cur_dir, UNICODE_STRING *name)
+{
+ OBJECT_ATTRIBUTES attr = { .Length = sizeof(attr), };
+ IO_STATUS_BLOCK iosb;
+ FILE_BASIC_INFORMATION basic = { .FileAttributes = FILE_ATTRIBUTE_NORMAL, };
+ HANDLE h;
+ const size_t bufsize = 8192;
+ void *buf;
+ NTSTATUS status;
+ ULONG perms;
+ ULONG flags;
+
+ flags = FILE_DELETE_ON_CLOSE |
+ FILE_OPEN_REPARSE_POINT |
+ FILE_OPEN_FOR_BACKUP_INTENT |
+ FILE_SYNCHRONOUS_IO_NONALERT |
+ FILE_SEQUENTIAL_ONLY;
+
+ name->MaximumLength = name->Length;
+
+ attr.RootDirectory = cur_dir;
+ attr.ObjectName = name;
+
+ perms = DELETE | SYNCHRONIZE | FILE_LIST_DIRECTORY | FILE_TRAVERSE;
+retry:
+ status = NtOpenFile(&h, perms, &attr, &iosb, FILE_SHARE_VALID_FLAGS, flags);
+ if (!NT_SUCCESS(status)) {
+ if (status == STATUS_OBJECT_NAME_NOT_FOUND)
+ return;
+ if (status == STATUS_CANNOT_DELETE && (perms & DELETE)) {
+ perms &= ~DELETE;
+ flags &= ~FILE_DELETE_ON_CLOSE;
+ perms |= FILE_WRITE_ATTRIBUTES;
+ goto retry;
+ }
+ ASSERT(false, "NtOpenFile() for deletion failed; status=0x%08"PRIx32, status);
+ }
+ if (perms & FILE_WRITE_ATTRIBUTES) {
+ status = NtSetInformationFile(h, &iosb, &basic,
+ sizeof(basic), FileBasicInformation);
+ NtClose(h);
+ if (!NT_SUCCESS(status)) {
+ ASSERT(false, "NtSetInformationFile() for deletion "
+ "failed; status=0x%08"PRIx32, status);
+ }
+ perms &= ~FILE_WRITE_ATTRIBUTES;
+ perms |= DELETE;
+ flags |= FILE_DELETE_ON_CLOSE;
+ goto retry;
+ }
+
+ buf = malloc(bufsize);
+ ASSERT(buf != NULL, "out of memory!");
+
+ while (NT_SUCCESS(status = NtQueryDirectoryFile(h, NULL, NULL, NULL,
+ &iosb, buf, bufsize,
+ FileNamesInformation,
+ FALSE, NULL, FALSE)))
+ {
+ const FILE_NAMES_INFORMATION *info = buf;
+ for (;;) {
+ if (!(info->FileNameLength == 2 && info->FileName[0] == L'.') &&
+ !(info->FileNameLength == 4 && info->FileName[0] == L'.' &&
+ info->FileName[1] == L'.'))
+ {
+ name->Buffer = (wchar_t *)info->FileName;
+ name->Length = info->FileNameLength;
+ delete_directory_tree_recursive(h, name);
+ }
+ if (info->NextEntryOffset == 0)
+ break;
+ info = (const FILE_NAMES_INFORMATION *)
+ ((const char *)info + info->NextEntryOffset);
+ }
+ }
+
+ ASSERT(status == STATUS_NO_MORE_FILES || /* end of directory */
+ status == STATUS_INVALID_PARAMETER, /* not a directory */
+ "NtQueryDirectoryFile() for deletion failed; "
+ "status=0x%08"PRIx32, status);
+
+ free(buf);
+ NtClose(h);
+}
+
+static void
+delete_directory_tree(const wchar_t *name)
+{
+ UNICODE_STRING uname;
+ void *buffer;
+
+ ASSERT(RtlDosPathNameToNtPathName_U(name, &uname, NULL, NULL),
+ "Unable to translate %ls to NT namespace path", name);
+ buffer = uname.Buffer;
+ delete_directory_tree_recursive(NULL, &uname);
+ HeapFree(GetProcessHeap(), 0, buffer);
+ ASSERT(GetFileAttributes(name) == 0xFFFFFFFF, "Deletion didn't work!");
+}
+
+#else /* __WIN32__ */
+
+static void
+delete_directory_tree_recursive(int dirfd, const char *name)
+{
+ int fd;
+ DIR *dir;
+ struct dirent *ent;
+
+ if (!unlinkat(dirfd, name, 0) || errno == ENOENT)
+ return;
+ ASSERT(errno == EISDIR, "%s: unlink error: %m", name);
+
+ fd = openat(dirfd, name, O_RDONLY | O_NOFOLLOW | O_DIRECTORY);
+ ASSERT(fd >= 0, "%m");
+
+ dir = fdopendir(fd);
+ ASSERT(dir != NULL, "%m");
+ while (errno = 0, (ent = readdir(dir)))
+ if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, ".."))
+ delete_directory_tree_recursive(fd, ent->d_name);
+ closedir(dir);
+
+ ASSERT(!unlinkat(dirfd, name, AT_REMOVEDIR), "%m");
+}
+
+static void
+delete_directory_tree(const tchar *name)
+{
+ delete_directory_tree_recursive(AT_FDCWD, name);
+}
+
+#endif /* !__WIN32__ */
+
+static uint32_t
+rand32(void)
+{
+ static uint64_t state;
+
+ /* A simple linear congruential generator */
+ state = (state * 25214903917 + 11) & (((uint64_t)1 << 48) - 1);
+ return state >> 16;
+}
+
+static inline bool
+randbool(void)
+{
+ return rand32() & 1;
+}
+
+static tchar wimfile[32];
+
+static const tchar *
+get_wimfile(int index)
+{
+ tsprintf(wimfile, T("wim%d"), index);
+ return wimfile;
+}
+
+static int
+select_random_wimfile_index(void)
+{
+ return in_use_wimfile_indices[rand32() % num_wimfiles_in_use];
+}
+
+static const tchar *
+select_new_wimfile(void)
+{
+ int index = 0;
+
+ while (wimfile_in_use[index])
+ index++;
+
+ in_use_wimfile_indices[num_wimfiles_in_use++] = index;
+ wimfile_in_use[index] = true;
+
+ return get_wimfile(index);
+}
+
+static WIMStruct *
+open_wim(int index)
+{
+ const tchar *wimfile = get_wimfile(index);
+ WIMStruct *wim;
+ int open_flags = 0;
+
+ open_flags |= randbool() ? 0 : WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
+
+ printf("Opening %"TS" with flags 0x%08x\n", wimfile, open_flags);
+
+ CHECK_RET(wimlib_open_wim(wimfile, open_flags, &wim));
+
+ return wim;
+}
+
+static WIMStruct *
+open_random_wim(void)
+{
+ return open_wim(select_random_wimfile_index());
+}
+
+static int
+get_image_count(WIMStruct *wim)
+{
+ struct wimlib_wim_info info;
+
+ CHECK_RET(wimlib_get_wim_info(wim, &info));
+
+ return info.image_count;
+}
+
+#ifdef __WIN32__
+static bool
+is_wimboot_capable(WIMStruct *wim)
+{
+ struct wimlib_wim_info info;
+
+ CHECK_RET(wimlib_get_wim_info(wim, &info));
+
+ return info.wim_version == 0x10D00 &&
+ ((info.compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS &&
+ (info.chunk_size == 4096 || info.chunk_size == 8192 ||
+ info.chunk_size == 16384 || info.chunk_size == 32768)) ||
+ (info.compression_type == WIMLIB_COMPRESSION_TYPE_LZX &&
+ info.chunk_size == 32768));
+}
+#endif /* __WIN32__ */
+
+static void
+overwrite_wim(WIMStruct *wim)
+{
+ int write_flags = 0;
+ struct wimlib_wim_info info;
+
+ CHECK_RET(wimlib_get_wim_info(wim, &info));
+
+ switch (rand32() % 4) {
+ case 0:
+ write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
+ break;
+ case 1:
+ write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
+ break;
+ }
+
+ switch (rand32() % 8) {
+ case 0:
+ write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
+ break;
+ case 1:
+ write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
+ break;
+ }
+
+ write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_RECOMPRESS;
+ write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_FSYNC;
+ write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_REBUILD;
+ write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_SOFT_DELETE;
+ write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_IGNORE_READONLY_FLAG;
+ write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_RETAIN_GUID;
+ write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_SEND_DONE_WITH_FILE_MESSAGES;
+ write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
+
+ if (rand32() % 8 == 0 &&
+ !(write_flags & WIMLIB_WRITE_FLAG_PIPABLE) &&
+ (!info.pipable || (write_flags & WIMLIB_WRITE_FLAG_NOT_PIPABLE)))
+ write_flags |= WIMLIB_WRITE_FLAG_SOLID;
+
+ if (randbool() && !info.pipable &&
+ !(write_flags & (WIMLIB_WRITE_FLAG_RECOMPRESS |
+ WIMLIB_WRITE_FLAG_PIPABLE)))
+ write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
+
+ printf("overwrite with flags: 0x%08x\n", write_flags);
+
+ CHECK_RET(wimlib_overwrite(wim, write_flags, 0));
+}
+
+static int
+get_random_write_flags(void)
+{
+ int write_flags = 0;
+
+ write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
+ write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_SEND_DONE_WITH_FILE_MESSAGES;
+ write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
+ switch (rand32() % 8) {
+ case 0:
+ write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
+ break;
+ case 1:
+ write_flags |= WIMLIB_WRITE_FLAG_SOLID;
+ break;
+ }
+
+ return write_flags;
+}
+
+static void
+op__create_new_wim(void)
+{
+ printf(":::op__create_new_wim\n");
+
+ const tchar *wimfile;
+ WIMStruct *wim;
+ int write_flags;
+
+ if (num_wimfiles_in_use == MAX_NUM_WIMS)
+ return;
+
+ wimfile = select_new_wimfile();
+
+ CHECK_RET(wimlib_create_new_wim(WIMLIB_COMPRESSION_TYPE_NONE, &wim));
+
+ /* Select a random compression type and chunk size. */
+ switch (rand32() % 8) {
+ default:
+ CHECK_RET(wimlib_set_output_compression_type(wim, WIMLIB_COMPRESSION_TYPE_NONE));
+ break;
+ case 3 ... 4:
+ CHECK_RET(wimlib_set_output_compression_type(wim, WIMLIB_COMPRESSION_TYPE_XPRESS));
+ CHECK_RET(wimlib_set_output_chunk_size(wim, 1 << (12 + rand32() % 5)));
+ break;
+ case 5 ... 6:
+ CHECK_RET(wimlib_set_output_compression_type(wim, WIMLIB_COMPRESSION_TYPE_LZX));
+ if (randbool())
+ CHECK_RET(wimlib_set_output_chunk_size(wim, 1 << 15));
+ else
+ CHECK_RET(wimlib_set_output_chunk_size(wim, 1 << (15 + rand32() % 7)));
+ break;
+ case 7:
+ CHECK_RET(wimlib_set_output_compression_type(wim, WIMLIB_COMPRESSION_TYPE_LZMS));
+ CHECK_RET(wimlib_set_output_chunk_size(wim, 1 << (15 + rand32() % 12)));
+ break;
+ }
+
+ /* Select random write flags. */
+ write_flags = get_random_write_flags();
+
+ printf("Creating %"TS" with write flags 0x%08x\n", wimfile, write_flags);
+
+ CHECK_RET(wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES, write_flags, 0));
+
+ wimlib_free(wim);
+}
+
+static void
+op__add_empty_image_to_random_wim(void)
+{
+ printf(":::op__add_empty_image_to_random_wim\n");
+
+ WIMStruct *wim;
+ int new_idx;
+
+ if (num_wimfiles_in_use < 1)
+ return;
+
+ wim = open_random_wim();
+ CHECK_RET(wimlib_add_empty_image(wim, NULL, &new_idx));
+ printf("Adding empty image to %"TS" at index %d\n", wimfile, new_idx);
+ overwrite_wim(wim);
+ wimlib_free(wim);
+}
+
+static void
+op__delete_random_image_from_random_wim(void)
+{
+ printf(":::op__delete_random_image_from_random_wim\n");
+
+ WIMStruct *wim;
+ int image;
+ int image_count;
+
+ if (num_wimfiles_in_use == 0)
+ return;
+
+ wim = open_random_wim();
+ image_count = get_image_count(wim);
+ if (image_count != 0) {
+ image = 1 + (rand32() % image_count);
+ CHECK_RET(wimlib_delete_image(wim, image));
+ printf("Deleting image %d from %"TS"\n", image, wimfile);
+ overwrite_wim(wim);
+ }
+ wimlib_free(wim);
+}
+
+static void
+op__delete_random_wim(void)
+{
+ printf(":::op__delete_random_wim\n");
+
+ const tchar *wimfile;
+ int which;
+ int index;
+
+ if (num_wimfiles_in_use == 0)
+ return;
+
+ which = rand32() % num_wimfiles_in_use;
+ index = in_use_wimfile_indices[which];
+
+ wimfile = get_wimfile(index);
+
+ ASSERT(!tunlink(wimfile), "failed to unlink %"TS": %m", wimfile);
+
+ printf("Deleted %"TS"\n", wimfile);
+
+ for (int i = which; i < num_wimfiles_in_use - 1; i++)
+ in_use_wimfile_indices[i] = in_use_wimfile_indices[i + 1];
+ num_wimfiles_in_use--;
+ wimfile_in_use[index] = false;
+}
+
+static void
+op__verify_random_wim(void)
+{
+ printf(":::op__verify_random_wim\n");
+
+ WIMStruct *wim;
+
+ if (num_wimfiles_in_use == 0)
+ return;
+
+ wim = open_random_wim();
+ CHECK_RET(wimlib_verify_wim(wim, 0));
+ printf("Verified %"TS"\n", wimfile);
+ wimlib_free(wim);
+}
+
+static void
+op__overwrite_with_no_changes(void)
+{
+ printf(":::op__overwrite_with_no_changes\n");
+
+ WIMStruct *wim;
+
+ if (num_wimfiles_in_use == 0)
+ return;
+
+ wim = open_random_wim();
+ overwrite_wim(wim);
+ wimlib_free(wim);
+}
+
+static void
+op__export_random_image(void)
+{
+ printf(":::op__export_random_image\n");
+
+ int src_wimfile_index;
+ int dst_wimfile_index;
+ WIMStruct *src_wim;
+ WIMStruct *dst_wim;
+ int src_image_count;
+ int dst_image_count;
+ int src_image;
+ int dst_image;
+
+ if (num_wimfiles_in_use < 2)
+ return;
+
+ src_wimfile_index = select_random_wimfile_index();
+ do {
+ dst_wimfile_index = select_random_wimfile_index();
+ } while (dst_wimfile_index == src_wimfile_index);
+
+ src_wim = open_wim(src_wimfile_index);
+ dst_wim = open_wim(dst_wimfile_index);
+
+ src_image_count = get_image_count(src_wim);
+ dst_image_count = get_image_count(dst_wim);
+
+ /* Choose a random source image --- single or all. */
+ src_image = WIMLIB_ALL_IMAGES;
+ if (src_image_count != 0 && randbool())
+ src_image = 1 + (rand32() % src_image_count);
+
+ printf("Exporting image %d of %d from wim %d into wim %d\n",
+ src_image, src_image_count, src_wimfile_index, dst_wimfile_index);
+ CHECK_RET(wimlib_export_image(src_wim, src_image, dst_wim, NULL, NULL, 0));
+
+ overwrite_wim(dst_wim);
+ wimlib_free(dst_wim);
+
+ dst_wim = open_wim(dst_wimfile_index);
+
+ /* Compare the images. */
+ dst_image = dst_image_count;
+ for (int image = (src_image == WIMLIB_ALL_IMAGES ? 1 : src_image);
+ image <= (src_image == WIMLIB_ALL_IMAGES ? src_image_count : src_image);
+ image++)
+ {
+ CHECK_RET(wimlib_compare_images(src_wim, image, dst_wim, ++dst_image, 0));
+ }
+
+ wimlib_free(src_wim);
+ wimlib_free(dst_wim);
+}
+
+static void
+op__apply_and_capture_test(void)
+{
+ printf(":::op__apply_and_capture_test\n");
+
+ WIMStruct *wim;
+ int image;
+ int index;
+ int extract_flags = 0;
+ int add_flags = 0;
+ int cmp_flags = 0;
+
+ if (num_wimfiles_in_use == 0)
+ return;
+
+ /* Generate a random image. */
+ index = select_random_wimfile_index();
+ wim = open_wim(index);
+
+ CHECK_RET(wimlib_add_image(wim, (void *)rand32, NULL, NULL,
+ WIMLIB_ADD_FLAG_GENERATE_TEST_DATA |
+ WIMLIB_ADD_FLAG_NORPFIX));
+ overwrite_wim(wim);
+ wimlib_free(wim);
+
+ /* Apply the generated image. */
+ wim = open_wim(index);
+ image = get_image_count(wim);
+ printf("apply and capture wim%d; generated image is index %d\n",
+ index, image);
+ delete_directory_tree(TMP_TARGET_NAME);
+#ifdef WITH_NTFS_3G
+ if (rand32() & 1) {
+ printf("applying in NTFS mode\n");
+ extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
+ extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
+ extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES;
+ extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_TIMESTAMPS;
+ add_flags |= WIMLIB_ADD_FLAG_NTFS;
+ cmp_flags |= WIMLIB_CMP_FLAG_COMPRESSION_NOT_PRESERVED;
+ create_ntfs_volume(TMP_TARGET_NAME);
+ } else
+#endif
+ {
+#ifdef __WIN32__
+ printf("applying in Windows mode\n");
+#else /* __WIN32__ */
+ printf("applying in UNIX mode\n");
+ cmp_flags |= WIMLIB_CMP_FLAG_SHORT_NAMES_NOT_PRESERVED;
+ cmp_flags |= WIMLIB_CMP_FLAG_ATTRIBUTES_NOT_PRESERVED;
+ cmp_flags |= WIMLIB_CMP_FLAG_SECURITY_NOT_PRESERVED;
+ cmp_flags |= WIMLIB_CMP_FLAG_ADS_NOT_PRESERVED;
+ cmp_flags |= WIMLIB_CMP_FLAG_IMAGE2_SHOULD_HAVE_SYMLINKS;
+#endif /* !__WIN32__ */
+ }
+ add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
+ CHECK_RET(wimlib_extract_image(wim, image, TMP_TARGET_NAME,
+ extract_flags));
+
+ /* Sometimes extract twice so that we test overwriting existing files.
+ */
+ if (!(extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) && randbool()) {
+ CHECK_RET(wimlib_extract_image(wim, image, TMP_TARGET_NAME,
+ extract_flags));
+ }
+
+ /* Capture the applied image. */
+ CHECK_RET(wimlib_add_image(wim, TMP_TARGET_NAME, NULL, NULL, add_flags));
+ overwrite_wim(wim);
+ wimlib_free(wim);
+
+ /* Compare the generated image with the captured image. */
+ wim = open_wim(index);
+ CHECK_RET(wimlib_compare_images(wim, image, wim, image + 1, cmp_flags));
+ wimlib_free(wim);
+}
+
+#ifdef __WIN32__
+
+/* Enumerate and unregister all backing WIMs from the specified volume */
+static void
+unregister_all_backing_wims(const tchar drive_letter)
+{
+ wchar_t volume[7];
+ HANDLE h;
+ void *overlay_list;
+ DWORD bytes_returned;
+ const struct wim_provider_overlay_entry *entry;
+ struct {
+ struct wof_external_info wof_info;
+ struct wim_provider_remove_overlay_input wim;
+ } in;
+
+ wsprintf(volume, L"\\\\.\\%lc:", drive_letter);
+
+ h = CreateFile(volume, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_VALID_FLAGS, NULL, OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ ASSERT(h != INVALID_HANDLE_VALUE,
+ "Failed to open %ls; error=%u", volume, (unsigned)GetLastError());
+
+ overlay_list = malloc(32768);
+ ASSERT(overlay_list != NULL, "out of memory");
+
+ in.wof_info.version = WOF_CURRENT_VERSION;
+ in.wof_info.provider = WOF_PROVIDER_WIM;
+
+ if (!DeviceIoControl(h, FSCTL_ENUM_OVERLAY,
+ &in, sizeof(struct wof_external_info),
+ overlay_list, 32768, &bytes_returned, NULL))
+ {
+ ASSERT(GetLastError() == ERROR_INVALID_FUNCTION ||
+ GetLastError() == ERROR_FILE_NOT_FOUND,
+ "FSCTL_ENUM_OVERLAY failed; error=%u", GetLastError());
+ return;
+ }
+
+ entry = overlay_list;
+ for (;;) {
+ printf("Unregistering data source ID %"PRIu64"\n",
+ entry->data_source_id);
+ in.wim.data_source_id = entry->data_source_id;
+ ASSERT(DeviceIoControl(h, FSCTL_REMOVE_OVERLAY, &in, sizeof(in),
+ NULL, 0, &bytes_returned, NULL),
+ "FSCTL_REMOVE_OVERLAY failed; error=%u",
+ (unsigned )GetLastError());
+ if (entry->next_entry_offset == 0)
+ break;
+ entry = (const struct wim_provider_overlay_entry *)
+ ((const uint8_t *)entry + entry->next_entry_offset);
+ }
+ free(overlay_list);
+ CloseHandle(h);
+}
+
+static void
+op__wimboot_test(void)
+{
+ int index;
+ int index2;
+ WIMStruct *wim;
+ WIMStruct *wim2;
+ int image_count;
+ int image;
+
+ if (num_wimfiles_in_use == 0)
+ return;
+
+ index = select_random_wimfile_index();
+
+ unregister_all_backing_wims(L'E');
+ copy_file(get_wimfile(index), L"wimboot.wim");
+
+ CHECK_RET(wimlib_open_wim(L"wimboot.wim", 0, &wim));
+
+ image_count = get_image_count(wim);
+ if (image_count == 0 || !is_wimboot_capable(wim)) {
+ wimlib_free(wim);
+ return;
+ }
+
+
+ image = 1 + (rand32() % image_count);
+
+ printf("WIMBOOT test; wim%d image %d\n", index, image);
+
+ delete_directory_tree(TMP_TARGET_NAME);
+
+ CHECK_RET(wimlib_extract_image(wim, image, TMP_TARGET_NAME,
+ WIMLIB_EXTRACT_FLAG_WIMBOOT));
+
+ if (randbool()) {
+ CHECK_RET(wimlib_extract_image(wim, image, TMP_TARGET_NAME,
+ WIMLIB_EXTRACT_FLAG_WIMBOOT));
+ }
+
+ index2 = select_random_wimfile_index();
+ wim2 = open_wim(index2);
+ image_count = get_image_count(wim2);
+
+ CHECK_RET(wimlib_add_image(wim2, TMP_TARGET_NAME, NULL, NULL,
+ WIMLIB_ADD_FLAG_NORPFIX));
+
+ overwrite_wim(wim2);
+ wimlib_free(wim2);
+
+ wim2 = open_wim(index2);
+
+ printf("comparing wimboot.wim:%d with wim%d:%d\n",
+ image, index2, image_count + 1);
+
+ CHECK_RET(wimlib_compare_images(wim, image, wim2, image_count + 1, 0));
+
+ wimlib_free(wim);
+ wimlib_free(wim2);
+}
+#endif /* __WIN32__ */
+
+static int
+is_solid_resource(const struct wimlib_resource_entry *resource, void *_ctx)
+{
+ return resource->packed;
+}
+
+static bool
+wim_contains_solid_resources(WIMStruct *wim)
+{
+ return wimlib_iterate_lookup_table(wim, 0, is_solid_resource, NULL);
+}
+
+static void
+op__split_test(void)
+{
+ printf(":::op__split_test\n");
+
+ WIMStruct *wim;
+ WIMStruct *swm;
+ WIMStruct *joined_wim;
+ uint64_t part_size;
+ int write_flags;
+ const tchar *globs[] = { T("tmp*.swm") };
+ int image_count;
+
+ if (num_wimfiles_in_use == 0)
+ return;
+
+ /* split, join, and compare */
+
+ wim = open_random_wim();
+
+ if (wim_contains_solid_resources(wim)) {
+ /* Can't split a WIM containing solid resources */
+ wimlib_free(wim);
+ return;
+ }
+
+ image_count = get_image_count(wim);
+
+ part_size = 10000 + (rand32() % 1000000);
+ write_flags = get_random_write_flags();
+ write_flags &= ~WIMLIB_WRITE_FLAG_SOLID;
+
+ printf("splitting WIM %"TS": part_size=%"PRIu64", write_flags=0x%08x\n",
+ wimfile, part_size, write_flags);
+
+ CHECK_RET(wimlib_split(wim, T("tmp.swm"), part_size, write_flags));
+
+ CHECK_RET(wimlib_open_wim(T("tmp.swm"), WIMLIB_OPEN_FLAG_CHECK_INTEGRITY,
+ &swm));
+
+ CHECK_RET(wimlib_reference_resource_files(swm, globs, 1,
+ WIMLIB_REF_FLAG_GLOB_ENABLE |
+ WIMLIB_REF_FLAG_GLOB_ERR_ON_NOMATCH,
+ WIMLIB_OPEN_FLAG_CHECK_INTEGRITY));
+
+ CHECK_RET(wimlib_verify_wim(swm, 0));
+
+ CHECK_RET(wimlib_write(swm, T("joined.wim"), WIMLIB_ALL_IMAGES, write_flags, 0));
+ wimlib_free(swm);
+
+ CHECK_RET(wimlib_open_wim(T("joined.wim"), 0, &joined_wim));
+ for (int i = 1; i <= image_count; i++)
+ CHECK_RET(wimlib_compare_images(wim, 1, joined_wim, 1, 0));
+ CHECK_RET(wimlib_verify_wim(joined_wim, 0));
+ wimlib_free(joined_wim);
+ wimlib_free(wim);
+
+ tunlink(T("tmp.swm"));
+ for (int i = 2; ; i++) {
+ tchar name[32];
+ tsprintf(name, T("tmp%d.swm"), i);
+ if (tunlink(name))
+ break;
+ }
+}
+
+static void
+op__set_compression_level(void)
+{
+ printf(":::op__set_compression_level\n");
+
+ unsigned int level = rand32() % 100;
+ printf("Changing compression levels to %d\n", level);
+ wimlib_set_default_compression_level(-1, level);
+}
+
+typedef void (*operation_func)(void);
+
+static const operation_func operation_table[] = {
+ op__create_new_wim,
+ op__add_empty_image_to_random_wim,
+ op__delete_random_image_from_random_wim,
+ op__delete_random_wim,
+ op__delete_random_wim,
+ op__verify_random_wim,
+ op__overwrite_with_no_changes,
+ op__export_random_image,
+ op__apply_and_capture_test,
+ op__apply_and_capture_test,
+ op__apply_and_capture_test,
+ op__apply_and_capture_test,
+ op__apply_and_capture_test,
+ op__split_test,
+ op__set_compression_level,
+#ifdef __WIN32__
+ op__wimboot_test,
+#endif
+};
+
+#ifdef __WIN32__
+extern int wmain(int argc, wchar_t **argv);
+#define main wmain
+#endif
+
+int
+main(int argc, tchar **argv)
+{
+ unsigned long long num_iterations;
+
+ if (argc < 2) {
+ num_iterations = ULLONG_MAX;
+ printf("Starting test runner\n");
+ } else {
+ num_iterations = tstrtoull(argv[1], NULL, 10);
+ printf("Starting test runner with %llu iterations\n",
+ num_iterations);
+ }
+
+ CHECK_RET(wimlib_global_init(0));
+ wimlib_set_print_errors(true);
+
+ change_to_temporary_directory();
+
+ for (int i = 0; i < MAX_NUM_WIMS; i++)
+ ASSERT(!tunlink(get_wimfile(i)) || errno == ENOENT, "unlink: %m");
+
+ for (unsigned long long i = 0; i < num_iterations; i++) {
+ printf("--> iteration %llu\n", i);
+ (*operation_table[rand32() % ARRAY_LEN(operation_table)])();
+ }
+
+ wimlib_global_cleanup();
+ return 0;
+}