/*
* wlfuzz.c - Randomized tests for wimlib
*/
/*
* Copyright (C) 2015-2021 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 uint32_t
get_random_chunk_size(int min_order, int max_order)
{
return 1 << (min_order + (rand32() % (max_order - min_order + 1)));
}
static void
op__create_new_wim(void)
{
printf(":::op__create_new_wim\n");
const tchar *wimfile;
enum wimlib_compression_type ctype = WIMLIB_COMPRESSION_TYPE_NONE;
uint32_t chunk_size = 0;
uint32_t solid_chunk_size = 0;
int write_flags;
WIMStruct *wim;
if (num_wimfiles_in_use == MAX_NUM_WIMS)
return;
wimfile = select_new_wimfile();
/* Select a random compression type and chunk size. */
switch (rand32() % 4) {
case 0:
break;
case 1:
ctype = WIMLIB_COMPRESSION_TYPE_XPRESS;
chunk_size = get_random_chunk_size(12, 16);
break;
case 2:
ctype = WIMLIB_COMPRESSION_TYPE_LZX;
if (randbool())
chunk_size = 1 << 15;
else
chunk_size = get_random_chunk_size(15, 21);
break;
case 3:
ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
chunk_size = get_random_chunk_size(15, 28);
if (randbool())
solid_chunk_size = get_random_chunk_size(15, 26);
else
solid_chunk_size = get_random_chunk_size(26, 28);
break;
}
/* Select random write flags. */
write_flags = get_random_write_flags();
printf("Creating %"TS" with write flags 0x%08x, compression_type=%"TS", chunk_size=%u, solid_chunk_size=%u\n",
wimfile, write_flags,
wimlib_get_compression_type_string(ctype),
chunk_size, solid_chunk_size);
CHECK_RET(wimlib_create_new_wim(ctype, &wim));
if (chunk_size != 0)
CHECK_RET(wimlib_set_output_chunk_size(wim, chunk_size));
if (solid_chunk_size != 0)
CHECK_RET(wimlib_set_output_pack_chunk_size(wim, solid_chunk_size));
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));
image = get_image_count(wim);
printf("generated wim%d image %d\n", index, image);
{
/*
* Compare the in-memory version of the generated image with a
* version written to disk
*/
WIMStruct *tmp_wim;
CHECK_RET(wimlib_write(wim, T("tmp.wim"), image, 0, 0));
CHECK_RET(wimlib_open_wim(T("tmp.wim"), 0, &tmp_wim));
CHECK_RET(wimlib_compare_images(wim, image, tmp_wim, 1, 0));
wimlib_free(tmp_wim);
}
overwrite_wim(wim);
wimlib_free(wim);
/* Apply the generated image. */
wim = open_wim(index);
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_NTFS_3G_MODE;
create_ntfs_volume(TMP_TARGET_NAME);
} else
#endif
{
#ifdef __WIN32__
printf("applying in Windows mode\n");
cmp_flags |= WIMLIB_CMP_FLAG_WINDOWS_MODE;
#else /* __WIN32__ */
printf("applying in UNIX mode\n");
extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
cmp_flags |= WIMLIB_CMP_FLAG_UNIX_MODE;
#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 WIM_PROVIDER_OVERLAY_ENTRY *entry;
struct {
WOF_EXTERNAL_INFO wof_info;
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(WOF_EXTERNAL_INFO),
overlay_list, 32768, &bytes_returned, NULL))
{
ASSERT(GetLastError() == ERROR_INVALID_FUNCTION ||
GetLastError() == ERROR_INVALID_PARAMETER ||
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->DataSourceId.QuadPart);
in.wim.DataSourceId = entry->DataSourceId;
ASSERT(DeviceIoControl(h, FSCTL_REMOVE_OVERLAY, &in, sizeof(in),
NULL, 0, &bytes_returned, NULL),
"FSCTL_REMOVE_OVERLAY failed; error=%u",
(unsigned )GetLastError());
if (entry->NextEntryOffset == 0)
break;
entry = (const WIM_PROVIDER_OVERLAY_ENTRY *)
((const uint8_t *)entry + entry->NextEntryOffset);
}
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,
WIMLIB_CMP_FLAG_WINDOWS_MODE));
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;
}