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; +}