35cefbe5c3846d9fcaae2c4d5a8fb762f983dff0
[wimlib] / tests / wlfuzz.c
1 /*
2  * wlfuzz.c - Randomized tests for wimlib
3  */
4
5 /*
6  * Copyright (C) 2015-2016 Eric Biggers
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21
22 /*
23  * This program is a randomized test runner for wimlib.  It must be linked
24  * against a build of the library compiled with --enable-test-support.
25  *
26  * Various types of tests are run. Most important is the "apply and capture"
27  * test, which works as follows:
28  *
29  *      1. Generate an in-memory WIM image containing a random directory tree
30  *      2. Persist the image into a WIM file
31  *      3. Apply the WIM image to somewhere
32  *      4. Re-capture the applied image
33  *      5. Compare the directory tree of the re-captured image to the original
34  *
35  * Note that this is an "apply and capture" test, not a "capture and apply"
36  * test.  By using the filesystem as the intermediary rather than as the
37  * starting point and ending point, the tests will run nearly unchanged
38  * regardless of filesystem type (e.g. UNIX, Windows, or NTFS-3G).  This style
39  * of test has been effective at finding bugs in wimlib as well as bugs in
40  * NTFS-3G where its behavior differs from that of Windows.
41  *
42  * Care is taken to exercise different options, such as different compression
43  * formats, when multiple are available.
44  */
45
46 #ifdef HAVE_CONFIG_H
47 #  include "config.h"
48 #endif
49
50 #ifndef ENABLE_TEST_SUPPORT
51 #  error "This program requires that wimlib was configured with --enable-test-support."
52 #endif
53
54 #include <errno.h>
55 #include <dirent.h>
56 #include <fcntl.h>
57 #include <inttypes.h>
58 #include <limits.h>
59 #include <stdarg.h>
60 #include <stdbool.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #ifdef WITH_NTFS_3G
65 #  include <sys/wait.h>
66 #endif
67 #include <unistd.h>
68
69 #ifdef __WIN32__
70 #  include <windows.h>
71 #  include <winternl.h>
72 #  include <ntstatus.h>
73 #endif
74
75 #include "wimlib.h"
76 #include "wimlib_tchar.h"
77 #include "wimlib/test_support.h"
78 #include "wimlib/wof.h"
79
80 #ifndef O_BINARY
81 #  define O_BINARY 0
82 #endif
83
84 #define ARRAY_LEN(A)    (sizeof(A) / sizeof((A)[0]))
85
86 #define TMP_TARGET_NAME T("wlfuzz-tmp-target")
87 #define MAX_NUM_WIMS            4
88
89 static bool wimfile_in_use[MAX_NUM_WIMS];
90 static int in_use_wimfile_indices[MAX_NUM_WIMS];
91 static int num_wimfiles_in_use = 0;
92
93 static void
94 assertion_failed(int line, const char *format, ...)
95 {
96         va_list va;
97
98         va_start(va, format);
99         fprintf(stderr, "ASSERTION FAILED at line %d: ", line);
100         vfprintf(stderr, format, va);
101         fputc('\n', stderr);
102         va_end(va);
103
104         exit(1);
105 }
106
107 #define ASSERT(expr, msg, ...)                                          \
108 ({                                                                      \
109         if (__builtin_expect(!(expr), 0))                               \
110                 assertion_failed(__LINE__, (msg), ##__VA_ARGS__);       \
111 })
112
113 #define CHECK_RET(ret)                                                  \
114 ({                                                                      \
115         int r = (ret);                                                  \
116         ASSERT(!r, "%"TS, wimlib_get_error_string(r));                  \
117 })
118
119 static void
120 change_to_temporary_directory(void)
121 {
122 #ifdef __WIN32__
123         ASSERT(SetCurrentDirectory(L"E:\\"),
124                "failed to change directory to E:\\");
125 #else
126         const char *tmpdir = getenv("TMPDIR");
127         if (!tmpdir)
128                 tmpdir = P_tmpdir;
129         ASSERT(!chdir(tmpdir),
130                "failed to change to temporary directory \"%s\": %m", tmpdir);
131 #endif
132 }
133
134 static void __attribute__((unused))
135 copy_file(const tchar *src, const tchar *dst)
136 {
137         int in_fd = topen(src, O_RDONLY|O_BINARY);
138         int out_fd = topen(dst, O_WRONLY|O_TRUNC|O_CREAT|O_BINARY, 0644);
139         char buf[32768];
140         ssize_t bytes_read, bytes_written, i;
141
142         ASSERT(in_fd >= 0, "%"TS": open error: %m", src);
143         ASSERT(out_fd >= 0, "%"TS": open error: %m", dst);
144         while ((bytes_read = read(in_fd, buf, sizeof(buf))) > 0) {
145                 for (i = 0; i < bytes_read; i += bytes_written) {
146                         bytes_written = write(out_fd, &buf[i], bytes_read - i);
147                         ASSERT(bytes_written > 0, "%"TS": write error: %m", dst);
148                 }
149         }
150         ASSERT(bytes_read == 0, "%"TS": read error: %m", src);
151         close(in_fd);
152         close(out_fd);
153 }
154
155 #ifdef WITH_NTFS_3G
156 static void
157 create_ntfs_volume(const char *name)
158 {
159         int fd;
160         int pid;
161         int status;
162         static const char buffer[1] = {0};
163
164         fd = open(name, O_WRONLY|O_TRUNC|O_CREAT|O_NOFOLLOW, 0644);
165         ASSERT(fd >= 0, "%s: open error: %m", name);
166
167         ASSERT(lseek(fd, 999999999, SEEK_SET) != -1, "%s: lseek error: %m", name);
168
169         ASSERT(write(fd, buffer, 1) == 1, "%s: write error: %m", name);
170
171         ASSERT(close(fd) == 0, "%s: close error: %m", name);
172
173         pid = fork();
174         ASSERT(pid >= 0, "fork error: %m");
175         if (pid == 0) {
176                 close(STDOUT_FILENO);
177                 close(STDERR_FILENO);
178                 execlp("mkntfs", "mkntfs", "--force", "--fast",
179                        name, (char *)NULL);
180                 ASSERT(false, "Failed to execute mkntfs: %m");
181         }
182
183         ASSERT(wait(&status) != -1, "wait error: %m");
184         ASSERT(WIFEXITED(status) && WEXITSTATUS(status) == 0,
185                "mkntfs error: exited with status %d", status);
186 }
187 #endif /* WITH_NTFS_3G */
188
189 #ifdef __WIN32__
190
191 extern WINAPI NTSTATUS NtQueryDirectoryFile (HANDLE FileHandle,
192                                              HANDLE Event,
193                                              PIO_APC_ROUTINE ApcRoutine,
194                                              PVOID ApcContext,
195                                              PIO_STATUS_BLOCK IoStatusBlock,
196                                              PVOID FileInformation,
197                                              ULONG Length,
198                                              FILE_INFORMATION_CLASS FileInformationClass,
199                                              BOOLEAN ReturnSingleEntry,
200                                              PUNICODE_STRING FileName,
201                                              BOOLEAN RestartScan);
202
203 static void
204 delete_directory_tree_recursive(HANDLE cur_dir, UNICODE_STRING *name)
205 {
206         OBJECT_ATTRIBUTES attr = { .Length = sizeof(attr), };
207         IO_STATUS_BLOCK iosb;
208         FILE_BASIC_INFORMATION basic = { .FileAttributes = FILE_ATTRIBUTE_NORMAL, };
209         HANDLE h;
210         const size_t bufsize = 8192;
211         void *buf;
212         NTSTATUS status;
213         ULONG perms;
214         ULONG flags;
215
216         flags = FILE_DELETE_ON_CLOSE |
217                       FILE_OPEN_REPARSE_POINT |
218                       FILE_OPEN_FOR_BACKUP_INTENT |
219                       FILE_SYNCHRONOUS_IO_NONALERT |
220                       FILE_SEQUENTIAL_ONLY;
221
222         name->MaximumLength = name->Length;
223
224         attr.RootDirectory = cur_dir;
225         attr.ObjectName = name;
226
227         perms = DELETE | SYNCHRONIZE | FILE_LIST_DIRECTORY | FILE_TRAVERSE;
228 retry:
229         status = NtOpenFile(&h, perms, &attr, &iosb, FILE_SHARE_VALID_FLAGS, flags);
230         if (!NT_SUCCESS(status)) {
231                 if (status == STATUS_OBJECT_NAME_NOT_FOUND)
232                         return;
233                 if (status == STATUS_CANNOT_DELETE && (perms & DELETE)) {
234                         perms &= ~DELETE;
235                         flags &= ~FILE_DELETE_ON_CLOSE;
236                         perms |= FILE_WRITE_ATTRIBUTES;
237                         goto retry;
238                 }
239                 ASSERT(false, "NtOpenFile() for deletion failed; status=0x%08"PRIx32, status);
240         }
241         if (perms & FILE_WRITE_ATTRIBUTES) {
242                 status = NtSetInformationFile(h, &iosb, &basic,
243                                               sizeof(basic), FileBasicInformation);
244                 NtClose(h);
245                 if (!NT_SUCCESS(status)) {
246                         ASSERT(false, "NtSetInformationFile() for deletion "
247                                "failed; status=0x%08"PRIx32, status);
248                 }
249                 perms &= ~FILE_WRITE_ATTRIBUTES;
250                 perms |= DELETE;
251                 flags |= FILE_DELETE_ON_CLOSE;
252                 goto retry;
253         }
254
255         buf = malloc(bufsize);
256         ASSERT(buf != NULL, "out of memory!");
257
258         while (NT_SUCCESS(status = NtQueryDirectoryFile(h, NULL, NULL, NULL,
259                                                         &iosb, buf, bufsize,
260                                                         FileNamesInformation,
261                                                         FALSE, NULL, FALSE)))
262         {
263                 const FILE_NAMES_INFORMATION *info = buf;
264                 for (;;) {
265                         if (!(info->FileNameLength == 2 && info->FileName[0] == L'.') &&
266                             !(info->FileNameLength == 4 && info->FileName[0] == L'.' &&
267                                                            info->FileName[1] == L'.'))
268                         {
269                                 name->Buffer = (wchar_t *)info->FileName;
270                                 name->Length = info->FileNameLength;
271                                 delete_directory_tree_recursive(h, name);
272                         }
273                         if (info->NextEntryOffset == 0)
274                                 break;
275                         info = (const FILE_NAMES_INFORMATION *)
276                                         ((const char *)info + info->NextEntryOffset);
277                 }
278         }
279
280         ASSERT(status == STATUS_NO_MORE_FILES || /* end of directory  */
281                status == STATUS_INVALID_PARAMETER, /* not a directory  */
282                "NtQueryDirectoryFile() for deletion failed; "
283                "status=0x%08"PRIx32, status);
284
285         free(buf);
286         NtClose(h);
287 }
288
289 static void
290 delete_directory_tree(const wchar_t *name)
291 {
292         UNICODE_STRING uname;
293         void *buffer;
294
295         ASSERT(RtlDosPathNameToNtPathName_U(name, &uname, NULL, NULL),
296                "Unable to translate %ls to NT namespace path", name);
297         buffer = uname.Buffer;
298         delete_directory_tree_recursive(NULL, &uname);
299         HeapFree(GetProcessHeap(), 0, buffer);
300         ASSERT(GetFileAttributes(name) == 0xFFFFFFFF, "Deletion didn't work!");
301 }
302
303 #else /* __WIN32__ */
304
305 static void
306 delete_directory_tree_recursive(int dirfd, const char *name)
307 {
308         int fd;
309         DIR *dir;
310         struct dirent *ent;
311
312         if (!unlinkat(dirfd, name, 0) || errno == ENOENT)
313                 return;
314         ASSERT(errno == EISDIR, "%s: unlink error: %m", name);
315
316         fd = openat(dirfd, name, O_RDONLY | O_NOFOLLOW | O_DIRECTORY);
317         ASSERT(fd >= 0, "%m");
318
319         dir = fdopendir(fd);
320         ASSERT(dir != NULL, "%m");
321         while (errno = 0, (ent = readdir(dir)))
322                 if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, ".."))
323                         delete_directory_tree_recursive(fd, ent->d_name);
324         closedir(dir);
325
326         ASSERT(!unlinkat(dirfd, name, AT_REMOVEDIR), "%m");
327 }
328
329 static void
330 delete_directory_tree(const tchar *name)
331 {
332         delete_directory_tree_recursive(AT_FDCWD, name);
333 }
334
335 #endif /* !__WIN32__ */
336
337 static uint32_t
338 rand32(void)
339 {
340         static uint64_t state;
341
342         /* A simple linear congruential generator  */
343         state = (state * 25214903917 + 11) & (((uint64_t)1 << 48) - 1);
344         return state >> 16;
345 }
346
347 static inline bool
348 randbool(void)
349 {
350         return rand32() & 1;
351 }
352
353 static tchar wimfile[32];
354
355 static const tchar *
356 get_wimfile(int index)
357 {
358         tsprintf(wimfile, T("wim%d"), index);
359         return wimfile;
360 }
361
362 static int
363 select_random_wimfile_index(void)
364 {
365         return in_use_wimfile_indices[rand32() % num_wimfiles_in_use];
366 }
367
368 static const tchar *
369 select_new_wimfile(void)
370 {
371         int index = 0;
372
373         while (wimfile_in_use[index])
374                 index++;
375
376         in_use_wimfile_indices[num_wimfiles_in_use++] = index;
377         wimfile_in_use[index] = true;
378
379         return get_wimfile(index);
380 }
381
382 static WIMStruct *
383 open_wim(int index)
384 {
385         const tchar *wimfile = get_wimfile(index);
386         WIMStruct *wim;
387         int open_flags = 0;
388
389         open_flags |= randbool() ? 0 : WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
390
391         printf("Opening %"TS" with flags 0x%08x\n", wimfile, open_flags);
392
393         CHECK_RET(wimlib_open_wim(wimfile, open_flags, &wim));
394
395         return wim;
396 }
397
398 static WIMStruct *
399 open_random_wim(void)
400 {
401         return open_wim(select_random_wimfile_index());
402 }
403
404 static int
405 get_image_count(WIMStruct *wim)
406 {
407         struct wimlib_wim_info info;
408
409         CHECK_RET(wimlib_get_wim_info(wim, &info));
410
411         return info.image_count;
412 }
413
414 #ifdef __WIN32__
415 static bool
416 is_wimboot_capable(WIMStruct *wim)
417 {
418         struct wimlib_wim_info info;
419
420         CHECK_RET(wimlib_get_wim_info(wim, &info));
421
422         return info.wim_version == 0x10D00 &&
423                 ((info.compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS &&
424                   (info.chunk_size == 4096 || info.chunk_size == 8192 ||
425                    info.chunk_size == 16384 || info.chunk_size == 32768)) ||
426                  (info.compression_type == WIMLIB_COMPRESSION_TYPE_LZX &&
427                   info.chunk_size == 32768));
428 }
429 #endif /* __WIN32__ */
430
431 static void
432 overwrite_wim(WIMStruct *wim)
433 {
434         int write_flags = 0;
435         struct wimlib_wim_info info;
436
437         CHECK_RET(wimlib_get_wim_info(wim, &info));
438
439         switch (rand32() % 4) {
440         case 0:
441                 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
442                 break;
443         case 1:
444                 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
445                 break;
446         }
447
448         switch (rand32() % 8) {
449         case 0:
450                 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
451                 break;
452         case 1:
453                 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
454                 break;
455         }
456
457         write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_RECOMPRESS;
458         write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_FSYNC;
459         write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_REBUILD;
460         write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_SOFT_DELETE;
461         write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_IGNORE_READONLY_FLAG;
462         write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_RETAIN_GUID;
463         write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_SEND_DONE_WITH_FILE_MESSAGES;
464         write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
465
466         if (rand32() % 8 == 0 &&
467             !(write_flags & WIMLIB_WRITE_FLAG_PIPABLE) &&
468             (!info.pipable || (write_flags & WIMLIB_WRITE_FLAG_NOT_PIPABLE)))
469                 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
470
471         if (randbool() && !info.pipable &&
472             !(write_flags & (WIMLIB_WRITE_FLAG_RECOMPRESS |
473                              WIMLIB_WRITE_FLAG_PIPABLE)))
474                 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
475
476         printf("overwrite with flags: 0x%08x\n", write_flags);
477
478         CHECK_RET(wimlib_overwrite(wim, write_flags, 0));
479 }
480
481 static int
482 get_random_write_flags(void)
483 {
484         int write_flags = 0;
485
486         write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
487         write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_SEND_DONE_WITH_FILE_MESSAGES;
488         write_flags |= randbool() ? 0 : WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
489         switch (rand32() % 8) {
490         case 0:
491                 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
492                 break;
493         case 1:
494                 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
495                 break;
496         }
497
498         return write_flags;
499 }
500
501 static uint32_t
502 get_random_chunk_size(int min_order, int max_order)
503 {
504         return 1 << (min_order + (rand32() % (max_order - min_order + 1)));
505 }
506
507 static void
508 op__create_new_wim(void)
509 {
510         printf(":::op__create_new_wim\n");
511
512         const tchar *wimfile;
513         enum wimlib_compression_type ctype = WIMLIB_COMPRESSION_TYPE_NONE;
514         uint32_t chunk_size = 0;
515         uint32_t solid_chunk_size = 0;
516         int write_flags;
517         WIMStruct *wim;
518
519         if (num_wimfiles_in_use == MAX_NUM_WIMS)
520                 return;
521
522         wimfile = select_new_wimfile();
523
524         /* Select a random compression type and chunk size.  */
525         switch (rand32() % 4) {
526         case 0:
527                 break;
528         case 1:
529                 ctype = WIMLIB_COMPRESSION_TYPE_XPRESS;
530                 chunk_size = get_random_chunk_size(12, 16);
531                 break;
532         case 2:
533                 ctype = WIMLIB_COMPRESSION_TYPE_LZX;
534                 if (randbool())
535                         chunk_size = 1 << 15;
536                 else
537                         chunk_size = get_random_chunk_size(15, 21);
538                 break;
539         case 3:
540                 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
541                 chunk_size = get_random_chunk_size(15, 28);
542                 if (randbool())
543                         solid_chunk_size = get_random_chunk_size(15, 26);
544                 else
545                         solid_chunk_size = get_random_chunk_size(26, 28);
546                 break;
547         }
548
549         /* Select random write flags.  */
550         write_flags = get_random_write_flags();
551
552         printf("Creating %"TS" with write flags 0x%08x, compression_type=%"TS", chunk_size=%u, solid_chunk_size=%u\n",
553                wimfile, write_flags,
554                wimlib_get_compression_type_string(ctype),
555                chunk_size, solid_chunk_size);
556
557         CHECK_RET(wimlib_create_new_wim(ctype, &wim));
558         if (chunk_size != 0)
559                 CHECK_RET(wimlib_set_output_chunk_size(wim, chunk_size));
560         if (solid_chunk_size != 0)
561                 CHECK_RET(wimlib_set_output_pack_chunk_size(wim, solid_chunk_size));
562
563         CHECK_RET(wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES, write_flags, 0));
564
565         wimlib_free(wim);
566 }
567
568 static void
569 op__add_empty_image_to_random_wim(void)
570 {
571         printf(":::op__add_empty_image_to_random_wim\n");
572
573         WIMStruct *wim;
574         int new_idx;
575
576         if (num_wimfiles_in_use < 1)
577                 return;
578
579         wim = open_random_wim();
580         CHECK_RET(wimlib_add_empty_image(wim, NULL, &new_idx));
581         printf("Adding empty image to %"TS" at index %d\n", wimfile, new_idx);
582         overwrite_wim(wim);
583         wimlib_free(wim);
584 }
585
586 static void
587 op__delete_random_image_from_random_wim(void)
588 {
589         printf(":::op__delete_random_image_from_random_wim\n");
590
591         WIMStruct *wim;
592         int image;
593         int image_count;
594
595         if (num_wimfiles_in_use == 0)
596                 return;
597
598         wim = open_random_wim();
599         image_count = get_image_count(wim);
600         if (image_count != 0) {
601                 image = 1 + (rand32() % image_count);
602                 CHECK_RET(wimlib_delete_image(wim, image));
603                 printf("Deleting image %d from %"TS"\n", image, wimfile);
604                 overwrite_wim(wim);
605         }
606         wimlib_free(wim);
607 }
608
609 static void
610 op__delete_random_wim(void)
611 {
612         printf(":::op__delete_random_wim\n");
613
614         const tchar *wimfile;
615         int which;
616         int index;
617
618         if (num_wimfiles_in_use == 0)
619                 return;
620
621         which = rand32() % num_wimfiles_in_use;
622         index = in_use_wimfile_indices[which];
623
624         wimfile = get_wimfile(index);
625
626         ASSERT(!tunlink(wimfile), "failed to unlink %"TS": %m", wimfile);
627
628         printf("Deleted %"TS"\n", wimfile);
629
630         for (int i = which; i < num_wimfiles_in_use - 1; i++)
631                 in_use_wimfile_indices[i] = in_use_wimfile_indices[i + 1];
632         num_wimfiles_in_use--;
633         wimfile_in_use[index] = false;
634 }
635
636 static void
637 op__verify_random_wim(void)
638 {
639         printf(":::op__verify_random_wim\n");
640
641         WIMStruct *wim;
642
643         if (num_wimfiles_in_use == 0)
644                 return;
645
646         wim = open_random_wim();
647         CHECK_RET(wimlib_verify_wim(wim, 0));
648         printf("Verified %"TS"\n", wimfile);
649         wimlib_free(wim);
650 }
651
652 static void
653 op__overwrite_with_no_changes(void)
654 {
655         printf(":::op__overwrite_with_no_changes\n");
656
657         WIMStruct *wim;
658
659         if (num_wimfiles_in_use == 0)
660                 return;
661
662         wim = open_random_wim();
663         overwrite_wim(wim);
664         wimlib_free(wim);
665 }
666
667 static void
668 op__export_random_image(void)
669 {
670         printf(":::op__export_random_image\n");
671
672         int src_wimfile_index;
673         int dst_wimfile_index;
674         WIMStruct *src_wim;
675         WIMStruct *dst_wim;
676         int src_image_count;
677         int dst_image_count;
678         int src_image;
679         int dst_image;
680
681         if (num_wimfiles_in_use < 2)
682                 return;
683
684         src_wimfile_index = select_random_wimfile_index();
685         do {
686                 dst_wimfile_index = select_random_wimfile_index();
687         } while (dst_wimfile_index == src_wimfile_index);
688
689         src_wim = open_wim(src_wimfile_index);
690         dst_wim = open_wim(dst_wimfile_index);
691
692         src_image_count = get_image_count(src_wim);
693         dst_image_count = get_image_count(dst_wim);
694
695         /* Choose a random source image --- single or all.  */
696         src_image = WIMLIB_ALL_IMAGES;
697         if (src_image_count != 0 && randbool())
698                 src_image = 1 + (rand32() % src_image_count);
699
700         printf("Exporting image %d of %d from wim %d into wim %d\n",
701                src_image, src_image_count, src_wimfile_index, dst_wimfile_index);
702         CHECK_RET(wimlib_export_image(src_wim, src_image, dst_wim, NULL, NULL, 0));
703
704         overwrite_wim(dst_wim);
705         wimlib_free(dst_wim);
706
707         dst_wim = open_wim(dst_wimfile_index);
708
709         /* Compare the images.  */
710         dst_image = dst_image_count;
711         for (int image = (src_image == WIMLIB_ALL_IMAGES ? 1 : src_image);
712              image <= (src_image == WIMLIB_ALL_IMAGES ? src_image_count : src_image);
713              image++)
714         {
715                 CHECK_RET(wimlib_compare_images(src_wim, image, dst_wim, ++dst_image, 0));
716         }
717
718         wimlib_free(src_wim);
719         wimlib_free(dst_wim);
720 }
721
722 static void
723 op__apply_and_capture_test(void)
724 {
725         printf(":::op__apply_and_capture_test\n");
726
727         WIMStruct *wim;
728         int image;
729         int index;
730         int extract_flags = 0;
731         int add_flags = 0;
732         int cmp_flags = 0;
733
734         if (num_wimfiles_in_use == 0)
735                 return;
736
737         /* Generate a random image.  */
738         index = select_random_wimfile_index();
739         wim = open_wim(index);
740
741         CHECK_RET(wimlib_add_image(wim, (void *)rand32, NULL, NULL,
742                                    WIMLIB_ADD_FLAG_GENERATE_TEST_DATA |
743                                    WIMLIB_ADD_FLAG_NORPFIX));
744
745         image = get_image_count(wim);
746
747         printf("generated wim%d image %d\n", index, image);
748
749         {
750                 /*
751                  * Compare the in-memory version of the generated image with a
752                  * version written to disk
753                  */
754                 WIMStruct *tmp_wim;
755
756                 CHECK_RET(wimlib_write(wim, T("tmp.wim"), image, 0, 0));
757                 CHECK_RET(wimlib_open_wim(T("tmp.wim"), 0, &tmp_wim));
758                 CHECK_RET(wimlib_compare_images(wim, image, tmp_wim, 1, 0));
759                 wimlib_free(tmp_wim);
760         }
761
762         overwrite_wim(wim);
763         wimlib_free(wim);
764
765         /* Apply the generated image.  */
766         wim = open_wim(index);
767         delete_directory_tree(TMP_TARGET_NAME);
768 #ifdef WITH_NTFS_3G
769         if (rand32() & 1) {
770                 printf("applying in NTFS mode\n");
771                 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
772                 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
773                 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES;
774                 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_TIMESTAMPS;
775                 add_flags |= WIMLIB_ADD_FLAG_NTFS;
776                 cmp_flags |= WIMLIB_CMP_FLAG_NTFS_3G_MODE;
777                 create_ntfs_volume(TMP_TARGET_NAME);
778         } else
779 #endif
780         {
781 #ifdef __WIN32__
782                 printf("applying in Windows mode\n");
783                 cmp_flags |= WIMLIB_CMP_FLAG_WINDOWS_MODE;
784 #else /* __WIN32__ */
785                 printf("applying in UNIX mode\n");
786                 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
787                 add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
788                 cmp_flags |= WIMLIB_CMP_FLAG_UNIX_MODE;
789 #endif /* !__WIN32__ */
790         }
791         add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
792         CHECK_RET(wimlib_extract_image(wim, image, TMP_TARGET_NAME,
793                                        extract_flags));
794
795         /* Sometimes extract twice so that we test overwriting existing files.
796          */
797         if (!(extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) && randbool()) {
798                 CHECK_RET(wimlib_extract_image(wim, image, TMP_TARGET_NAME,
799                                                extract_flags));
800         }
801
802         /* Capture the applied image.  */
803         CHECK_RET(wimlib_add_image(wim, TMP_TARGET_NAME, NULL, NULL, add_flags));
804         overwrite_wim(wim);
805         wimlib_free(wim);
806
807         /* Compare the generated image with the captured image.  */
808         wim = open_wim(index);
809         CHECK_RET(wimlib_compare_images(wim, image, wim, image + 1, cmp_flags));
810         wimlib_free(wim);
811 }
812
813 #ifdef __WIN32__
814
815 /* Enumerate and unregister all backing WIMs from the specified volume  */
816 static void
817 unregister_all_backing_wims(const tchar drive_letter)
818 {
819         wchar_t volume[7];
820         HANDLE h;
821         void *overlay_list;
822         DWORD bytes_returned;
823         const struct wim_provider_overlay_entry *entry;
824         struct {
825                 struct wof_external_info wof_info;
826                 struct wim_provider_remove_overlay_input wim;
827         } in;
828
829         wsprintf(volume, L"\\\\.\\%lc:", drive_letter);
830
831         h = CreateFile(volume, GENERIC_READ | GENERIC_WRITE,
832                        FILE_SHARE_VALID_FLAGS, NULL, OPEN_EXISTING,
833                        FILE_FLAG_BACKUP_SEMANTICS, NULL);
834         ASSERT(h != INVALID_HANDLE_VALUE,
835                "Failed to open %ls; error=%u", volume, (unsigned)GetLastError());
836
837         overlay_list = malloc(32768);
838         ASSERT(overlay_list != NULL, "out of memory");
839
840         in.wof_info.version = WOF_CURRENT_VERSION;
841         in.wof_info.provider = WOF_PROVIDER_WIM;
842
843         if (!DeviceIoControl(h, FSCTL_ENUM_OVERLAY,
844                              &in, sizeof(struct wof_external_info),
845                              overlay_list, 32768, &bytes_returned, NULL))
846         {
847                 ASSERT(GetLastError() == ERROR_INVALID_FUNCTION ||
848                        GetLastError() == ERROR_INVALID_PARAMETER ||
849                        GetLastError() == ERROR_FILE_NOT_FOUND,
850                        "FSCTL_ENUM_OVERLAY failed; error=%u", GetLastError());
851                 return;
852         }
853
854         entry = overlay_list;
855         for (;;) {
856                 printf("Unregistering data source ID %"PRIu64"\n",
857                        entry->data_source_id);
858                 in.wim.data_source_id = entry->data_source_id;
859                 ASSERT(DeviceIoControl(h, FSCTL_REMOVE_OVERLAY, &in, sizeof(in),
860                                        NULL, 0, &bytes_returned, NULL),
861                        "FSCTL_REMOVE_OVERLAY failed; error=%u",
862                        (unsigned )GetLastError());
863                 if (entry->next_entry_offset == 0)
864                         break;
865                 entry = (const struct wim_provider_overlay_entry *)
866                         ((const uint8_t *)entry + entry->next_entry_offset);
867         }
868         free(overlay_list);
869         CloseHandle(h);
870 }
871
872 static void
873 op__wimboot_test(void)
874 {
875         int index;
876         int index2;
877         WIMStruct *wim;
878         WIMStruct *wim2;
879         int image_count;
880         int image;
881
882         if (num_wimfiles_in_use == 0)
883                 return;
884
885         index = select_random_wimfile_index();
886
887         unregister_all_backing_wims(L'E');
888         copy_file(get_wimfile(index), L"wimboot.wim");
889
890         CHECK_RET(wimlib_open_wim(L"wimboot.wim", 0, &wim));
891
892         image_count = get_image_count(wim);
893         if (image_count == 0 || !is_wimboot_capable(wim)) {
894                 wimlib_free(wim);
895                 return;
896         }
897
898
899         image = 1 + (rand32() % image_count);
900
901         printf("WIMBOOT test; wim%d image %d\n", index, image);
902
903         delete_directory_tree(TMP_TARGET_NAME);
904
905         CHECK_RET(wimlib_extract_image(wim, image, TMP_TARGET_NAME,
906                                        WIMLIB_EXTRACT_FLAG_WIMBOOT));
907
908         if (randbool()) {
909                 CHECK_RET(wimlib_extract_image(wim, image, TMP_TARGET_NAME,
910                                                WIMLIB_EXTRACT_FLAG_WIMBOOT));
911         }
912
913         index2 = select_random_wimfile_index();
914         wim2 = open_wim(index2);
915         image_count = get_image_count(wim2);
916
917         CHECK_RET(wimlib_add_image(wim2, TMP_TARGET_NAME, NULL, NULL,
918                                    WIMLIB_ADD_FLAG_NORPFIX));
919
920         overwrite_wim(wim2);
921         wimlib_free(wim2);
922
923         wim2 = open_wim(index2);
924
925         printf("comparing wimboot.wim:%d with wim%d:%d\n",
926                image, index2, image_count + 1);
927
928         CHECK_RET(wimlib_compare_images(wim, image, wim2, image_count + 1,
929                                         WIMLIB_CMP_FLAG_WINDOWS_MODE));
930
931         wimlib_free(wim);
932         wimlib_free(wim2);
933 }
934 #endif /* __WIN32__ */
935
936 static int
937 is_solid_resource(const struct wimlib_resource_entry *resource, void *_ctx)
938 {
939         return resource->packed;
940 }
941
942 static bool
943 wim_contains_solid_resources(WIMStruct *wim)
944 {
945         return wimlib_iterate_lookup_table(wim, 0, is_solid_resource, NULL);
946 }
947
948 static void
949 op__split_test(void)
950 {
951         printf(":::op__split_test\n");
952
953         WIMStruct *wim;
954         WIMStruct *swm;
955         WIMStruct *joined_wim;
956         uint64_t part_size;
957         int write_flags;
958         const tchar *globs[] = { T("tmp*.swm") };
959         int image_count;
960
961         if (num_wimfiles_in_use == 0)
962                 return;
963
964         /* split, join, and compare  */
965
966         wim = open_random_wim();
967
968         if (wim_contains_solid_resources(wim)) {
969                 /* Can't split a WIM containing solid resources  */
970                 wimlib_free(wim);
971                 return;
972         }
973
974         image_count = get_image_count(wim);
975
976         part_size = 10000 + (rand32() % 1000000);
977         write_flags = get_random_write_flags();
978         write_flags &= ~WIMLIB_WRITE_FLAG_SOLID;
979
980         printf("splitting WIM %"TS": part_size=%"PRIu64", write_flags=0x%08x\n",
981                wimfile, part_size, write_flags);
982
983         CHECK_RET(wimlib_split(wim, T("tmp.swm"), part_size, write_flags));
984
985         CHECK_RET(wimlib_open_wim(T("tmp.swm"), WIMLIB_OPEN_FLAG_CHECK_INTEGRITY,
986                                   &swm));
987
988         CHECK_RET(wimlib_reference_resource_files(swm, globs, 1,
989                                                   WIMLIB_REF_FLAG_GLOB_ENABLE |
990                                                         WIMLIB_REF_FLAG_GLOB_ERR_ON_NOMATCH,
991                                                   WIMLIB_OPEN_FLAG_CHECK_INTEGRITY));
992
993         CHECK_RET(wimlib_verify_wim(swm, 0));
994
995         CHECK_RET(wimlib_write(swm, T("joined.wim"), WIMLIB_ALL_IMAGES, write_flags, 0));
996         wimlib_free(swm);
997
998         CHECK_RET(wimlib_open_wim(T("joined.wim"), 0, &joined_wim));
999         for (int i = 1; i <= image_count; i++)
1000                 CHECK_RET(wimlib_compare_images(wim, 1, joined_wim, 1, 0));
1001         CHECK_RET(wimlib_verify_wim(joined_wim, 0));
1002         wimlib_free(joined_wim);
1003         wimlib_free(wim);
1004
1005         tunlink(T("tmp.swm"));
1006         for (int i = 2; ; i++) {
1007                 tchar name[32];
1008                 tsprintf(name, T("tmp%d.swm"), i);
1009                 if (tunlink(name))
1010                         break;
1011         }
1012 }
1013
1014 static void
1015 op__set_compression_level(void)
1016 {
1017         printf(":::op__set_compression_level\n");
1018
1019         unsigned int level = rand32() % 100;
1020         printf("Changing compression levels to %d\n", level);
1021         wimlib_set_default_compression_level(-1, level);
1022 }
1023
1024 typedef void (*operation_func)(void);
1025
1026 static const operation_func operation_table[] = {
1027         op__create_new_wim,
1028         op__add_empty_image_to_random_wim,
1029         op__delete_random_image_from_random_wim,
1030         op__delete_random_wim,
1031         op__delete_random_wim,
1032         op__verify_random_wim,
1033         op__overwrite_with_no_changes,
1034         op__export_random_image,
1035         op__apply_and_capture_test,
1036         op__apply_and_capture_test,
1037         op__apply_and_capture_test,
1038         op__apply_and_capture_test,
1039         op__apply_and_capture_test,
1040         op__split_test,
1041         op__set_compression_level,
1042 #ifdef __WIN32__
1043         op__wimboot_test,
1044 #endif
1045 };
1046
1047 #ifdef __WIN32__
1048 extern int wmain(int argc, wchar_t **argv);
1049 #define main wmain
1050 #endif
1051
1052 int
1053 main(int argc, tchar **argv)
1054 {
1055         unsigned long long num_iterations;
1056
1057         if (argc < 2) {
1058                 num_iterations = ULLONG_MAX;
1059                 printf("Starting test runner\n");
1060         } else {
1061                 num_iterations = tstrtoull(argv[1], NULL, 10);
1062                 printf("Starting test runner with %llu iterations\n",
1063                        num_iterations);
1064         }
1065
1066         CHECK_RET(wimlib_global_init(0));
1067         wimlib_set_print_errors(true);
1068
1069         change_to_temporary_directory();
1070
1071         for (int i = 0; i < MAX_NUM_WIMS; i++)
1072                 ASSERT(!tunlink(get_wimfile(i)) || errno == ENOENT, "unlink: %m");
1073
1074         for (unsigned long long i = 0; i < num_iterations; i++) {
1075                 printf("--> iteration %llu\n", i);
1076                 (*operation_table[rand32() % ARRAY_LEN(operation_table)])();
1077         }
1078
1079         wimlib_global_cleanup();
1080         return 0;
1081 }