Add randomized testing program
[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 void
502 op__create_new_wim(void)
503 {
504         printf(":::op__create_new_wim\n");
505
506         const tchar *wimfile;
507         WIMStruct *wim;
508         int write_flags;
509
510         if (num_wimfiles_in_use == MAX_NUM_WIMS)
511                 return;
512
513         wimfile = select_new_wimfile();
514
515         CHECK_RET(wimlib_create_new_wim(WIMLIB_COMPRESSION_TYPE_NONE, &wim));
516
517         /* Select a random compression type and chunk size.  */
518         switch (rand32() % 8) {
519         default:
520                 CHECK_RET(wimlib_set_output_compression_type(wim, WIMLIB_COMPRESSION_TYPE_NONE));
521                 break;
522         case 3 ... 4:
523                 CHECK_RET(wimlib_set_output_compression_type(wim, WIMLIB_COMPRESSION_TYPE_XPRESS));
524                 CHECK_RET(wimlib_set_output_chunk_size(wim, 1 << (12 + rand32() % 5)));
525                 break;
526         case 5 ... 6:
527                 CHECK_RET(wimlib_set_output_compression_type(wim, WIMLIB_COMPRESSION_TYPE_LZX));
528                 if (randbool())
529                         CHECK_RET(wimlib_set_output_chunk_size(wim, 1 << 15));
530                 else
531                         CHECK_RET(wimlib_set_output_chunk_size(wim, 1 << (15 + rand32() % 7)));
532                 break;
533         case 7:
534                 CHECK_RET(wimlib_set_output_compression_type(wim, WIMLIB_COMPRESSION_TYPE_LZMS));
535                 CHECK_RET(wimlib_set_output_chunk_size(wim, 1 << (15 + rand32() % 12)));
536                 break;
537         }
538
539         /* Select random write flags.  */
540         write_flags = get_random_write_flags();
541
542         printf("Creating %"TS" with write flags 0x%08x\n", wimfile, write_flags);
543
544         CHECK_RET(wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES, write_flags, 0));
545
546         wimlib_free(wim);
547 }
548
549 static void
550 op__add_empty_image_to_random_wim(void)
551 {
552         printf(":::op__add_empty_image_to_random_wim\n");
553
554         WIMStruct *wim;
555         int new_idx;
556
557         if (num_wimfiles_in_use < 1)
558                 return;
559
560         wim = open_random_wim();
561         CHECK_RET(wimlib_add_empty_image(wim, NULL, &new_idx));
562         printf("Adding empty image to %"TS" at index %d\n", wimfile, new_idx);
563         overwrite_wim(wim);
564         wimlib_free(wim);
565 }
566
567 static void
568 op__delete_random_image_from_random_wim(void)
569 {
570         printf(":::op__delete_random_image_from_random_wim\n");
571
572         WIMStruct *wim;
573         int image;
574         int image_count;
575
576         if (num_wimfiles_in_use == 0)
577                 return;
578
579         wim = open_random_wim();
580         image_count = get_image_count(wim);
581         if (image_count != 0) {
582                 image = 1 + (rand32() % image_count);
583                 CHECK_RET(wimlib_delete_image(wim, image));
584                 printf("Deleting image %d from %"TS"\n", image, wimfile);
585                 overwrite_wim(wim);
586         }
587         wimlib_free(wim);
588 }
589
590 static void
591 op__delete_random_wim(void)
592 {
593         printf(":::op__delete_random_wim\n");
594
595         const tchar *wimfile;
596         int which;
597         int index;
598
599         if (num_wimfiles_in_use == 0)
600                 return;
601
602         which = rand32() % num_wimfiles_in_use;
603         index = in_use_wimfile_indices[which];
604
605         wimfile = get_wimfile(index);
606
607         ASSERT(!tunlink(wimfile), "failed to unlink %"TS": %m", wimfile);
608
609         printf("Deleted %"TS"\n", wimfile);
610
611         for (int i = which; i < num_wimfiles_in_use - 1; i++)
612                 in_use_wimfile_indices[i] = in_use_wimfile_indices[i + 1];
613         num_wimfiles_in_use--;
614         wimfile_in_use[index] = false;
615 }
616
617 static void
618 op__verify_random_wim(void)
619 {
620         printf(":::op__verify_random_wim\n");
621
622         WIMStruct *wim;
623
624         if (num_wimfiles_in_use == 0)
625                 return;
626
627         wim = open_random_wim();
628         CHECK_RET(wimlib_verify_wim(wim, 0));
629         printf("Verified %"TS"\n", wimfile);
630         wimlib_free(wim);
631 }
632
633 static void
634 op__overwrite_with_no_changes(void)
635 {
636         printf(":::op__overwrite_with_no_changes\n");
637
638         WIMStruct *wim;
639
640         if (num_wimfiles_in_use == 0)
641                 return;
642
643         wim = open_random_wim();
644         overwrite_wim(wim);
645         wimlib_free(wim);
646 }
647
648 static void
649 op__export_random_image(void)
650 {
651         printf(":::op__export_random_image\n");
652
653         int src_wimfile_index;
654         int dst_wimfile_index;
655         WIMStruct *src_wim;
656         WIMStruct *dst_wim;
657         int src_image_count;
658         int dst_image_count;
659         int src_image;
660         int dst_image;
661
662         if (num_wimfiles_in_use < 2)
663                 return;
664
665         src_wimfile_index = select_random_wimfile_index();
666         do {
667                 dst_wimfile_index = select_random_wimfile_index();
668         } while (dst_wimfile_index == src_wimfile_index);
669
670         src_wim = open_wim(src_wimfile_index);
671         dst_wim = open_wim(dst_wimfile_index);
672
673         src_image_count = get_image_count(src_wim);
674         dst_image_count = get_image_count(dst_wim);
675
676         /* Choose a random source image --- single or all.  */
677         src_image = WIMLIB_ALL_IMAGES;
678         if (src_image_count != 0 && randbool())
679                 src_image = 1 + (rand32() % src_image_count);
680
681         printf("Exporting image %d of %d from wim %d into wim %d\n",
682                src_image, src_image_count, src_wimfile_index, dst_wimfile_index);
683         CHECK_RET(wimlib_export_image(src_wim, src_image, dst_wim, NULL, NULL, 0));
684
685         overwrite_wim(dst_wim);
686         wimlib_free(dst_wim);
687
688         dst_wim = open_wim(dst_wimfile_index);
689
690         /* Compare the images.  */
691         dst_image = dst_image_count;
692         for (int image = (src_image == WIMLIB_ALL_IMAGES ? 1 : src_image);
693              image <= (src_image == WIMLIB_ALL_IMAGES ? src_image_count : src_image);
694              image++)
695         {
696                 CHECK_RET(wimlib_compare_images(src_wim, image, dst_wim, ++dst_image, 0));
697         }
698
699         wimlib_free(src_wim);
700         wimlib_free(dst_wim);
701 }
702
703 static void
704 op__apply_and_capture_test(void)
705 {
706         printf(":::op__apply_and_capture_test\n");
707
708         WIMStruct *wim;
709         int image;
710         int index;
711         int extract_flags = 0;
712         int add_flags = 0;
713         int cmp_flags = 0;
714
715         if (num_wimfiles_in_use == 0)
716                 return;
717
718         /* Generate a random image.  */
719         index = select_random_wimfile_index();
720         wim = open_wim(index);
721
722         CHECK_RET(wimlib_add_image(wim, (void *)rand32, NULL, NULL,
723                                    WIMLIB_ADD_FLAG_GENERATE_TEST_DATA |
724                                    WIMLIB_ADD_FLAG_NORPFIX));
725         overwrite_wim(wim);
726         wimlib_free(wim);
727
728         /* Apply the generated image.  */
729         wim = open_wim(index);
730         image = get_image_count(wim);
731         printf("apply and capture wim%d; generated image is index %d\n",
732                index, image);
733         delete_directory_tree(TMP_TARGET_NAME);
734 #ifdef WITH_NTFS_3G
735         if (rand32() & 1) {
736                 printf("applying in NTFS mode\n");
737                 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
738                 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
739                 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES;
740                 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_TIMESTAMPS;
741                 add_flags |= WIMLIB_ADD_FLAG_NTFS;
742                 cmp_flags |= WIMLIB_CMP_FLAG_COMPRESSION_NOT_PRESERVED;
743                 create_ntfs_volume(TMP_TARGET_NAME);
744         } else
745 #endif
746         {
747 #ifdef __WIN32__
748                 printf("applying in Windows mode\n");
749 #else /* __WIN32__ */
750                 printf("applying in UNIX mode\n");
751                 cmp_flags |= WIMLIB_CMP_FLAG_SHORT_NAMES_NOT_PRESERVED;
752                 cmp_flags |= WIMLIB_CMP_FLAG_ATTRIBUTES_NOT_PRESERVED;
753                 cmp_flags |= WIMLIB_CMP_FLAG_SECURITY_NOT_PRESERVED;
754                 cmp_flags |= WIMLIB_CMP_FLAG_ADS_NOT_PRESERVED;
755                 cmp_flags |= WIMLIB_CMP_FLAG_IMAGE2_SHOULD_HAVE_SYMLINKS;
756 #endif /* !__WIN32__ */
757         }
758         add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
759         CHECK_RET(wimlib_extract_image(wim, image, TMP_TARGET_NAME,
760                                        extract_flags));
761
762         /* Sometimes extract twice so that we test overwriting existing files.
763          */
764         if (!(extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) && randbool()) {
765                 CHECK_RET(wimlib_extract_image(wim, image, TMP_TARGET_NAME,
766                                                extract_flags));
767         }
768
769         /* Capture the applied image.  */
770         CHECK_RET(wimlib_add_image(wim, TMP_TARGET_NAME, NULL, NULL, add_flags));
771         overwrite_wim(wim);
772         wimlib_free(wim);
773
774         /* Compare the generated image with the captured image.  */
775         wim = open_wim(index);
776         CHECK_RET(wimlib_compare_images(wim, image, wim, image + 1, cmp_flags));
777         wimlib_free(wim);
778 }
779
780 #ifdef __WIN32__
781
782 /* Enumerate and unregister all backing WIMs from the specified volume  */
783 static void
784 unregister_all_backing_wims(const tchar drive_letter)
785 {
786         wchar_t volume[7];
787         HANDLE h;
788         void *overlay_list;
789         DWORD bytes_returned;
790         const struct wim_provider_overlay_entry *entry;
791         struct {
792                 struct wof_external_info wof_info;
793                 struct wim_provider_remove_overlay_input wim;
794         } in;
795
796         wsprintf(volume, L"\\\\.\\%lc:", drive_letter);
797
798         h = CreateFile(volume, GENERIC_READ | GENERIC_WRITE,
799                        FILE_SHARE_VALID_FLAGS, NULL, OPEN_EXISTING,
800                        FILE_FLAG_BACKUP_SEMANTICS, NULL);
801         ASSERT(h != INVALID_HANDLE_VALUE,
802                "Failed to open %ls; error=%u", volume, (unsigned)GetLastError());
803
804         overlay_list = malloc(32768);
805         ASSERT(overlay_list != NULL, "out of memory");
806
807         in.wof_info.version = WOF_CURRENT_VERSION;
808         in.wof_info.provider = WOF_PROVIDER_WIM;
809
810         if (!DeviceIoControl(h, FSCTL_ENUM_OVERLAY,
811                              &in, sizeof(struct wof_external_info),
812                              overlay_list, 32768, &bytes_returned, NULL))
813         {
814                 ASSERT(GetLastError() == ERROR_INVALID_FUNCTION ||
815                        GetLastError() == ERROR_FILE_NOT_FOUND,
816                        "FSCTL_ENUM_OVERLAY failed; error=%u", GetLastError());
817                 return;
818         }
819
820         entry = overlay_list;
821         for (;;) {
822                 printf("Unregistering data source ID %"PRIu64"\n",
823                        entry->data_source_id);
824                 in.wim.data_source_id = entry->data_source_id;
825                 ASSERT(DeviceIoControl(h, FSCTL_REMOVE_OVERLAY, &in, sizeof(in),
826                                        NULL, 0, &bytes_returned, NULL),
827                        "FSCTL_REMOVE_OVERLAY failed; error=%u",
828                        (unsigned )GetLastError());
829                 if (entry->next_entry_offset == 0)
830                         break;
831                 entry = (const struct wim_provider_overlay_entry *)
832                         ((const uint8_t *)entry + entry->next_entry_offset);
833         }
834         free(overlay_list);
835         CloseHandle(h);
836 }
837
838 static void
839 op__wimboot_test(void)
840 {
841         int index;
842         int index2;
843         WIMStruct *wim;
844         WIMStruct *wim2;
845         int image_count;
846         int image;
847
848         if (num_wimfiles_in_use == 0)
849                 return;
850
851         index = select_random_wimfile_index();
852
853         unregister_all_backing_wims(L'E');
854         copy_file(get_wimfile(index), L"wimboot.wim");
855
856         CHECK_RET(wimlib_open_wim(L"wimboot.wim", 0, &wim));
857
858         image_count = get_image_count(wim);
859         if (image_count == 0 || !is_wimboot_capable(wim)) {
860                 wimlib_free(wim);
861                 return;
862         }
863
864
865         image = 1 + (rand32() % image_count);
866
867         printf("WIMBOOT test; wim%d image %d\n", index, image);
868
869         delete_directory_tree(TMP_TARGET_NAME);
870
871         CHECK_RET(wimlib_extract_image(wim, image, TMP_TARGET_NAME,
872                                        WIMLIB_EXTRACT_FLAG_WIMBOOT));
873
874         if (randbool()) {
875                 CHECK_RET(wimlib_extract_image(wim, image, TMP_TARGET_NAME,
876                                                WIMLIB_EXTRACT_FLAG_WIMBOOT));
877         }
878
879         index2 = select_random_wimfile_index();
880         wim2 = open_wim(index2);
881         image_count = get_image_count(wim2);
882
883         CHECK_RET(wimlib_add_image(wim2, TMP_TARGET_NAME, NULL, NULL,
884                                    WIMLIB_ADD_FLAG_NORPFIX));
885
886         overwrite_wim(wim2);
887         wimlib_free(wim2);
888
889         wim2 = open_wim(index2);
890
891         printf("comparing wimboot.wim:%d with wim%d:%d\n",
892                image, index2, image_count + 1);
893
894         CHECK_RET(wimlib_compare_images(wim, image, wim2, image_count + 1, 0));
895
896         wimlib_free(wim);
897         wimlib_free(wim2);
898 }
899 #endif /* __WIN32__ */
900
901 static int
902 is_solid_resource(const struct wimlib_resource_entry *resource, void *_ctx)
903 {
904         return resource->packed;
905 }
906
907 static bool
908 wim_contains_solid_resources(WIMStruct *wim)
909 {
910         return wimlib_iterate_lookup_table(wim, 0, is_solid_resource, NULL);
911 }
912
913 static void
914 op__split_test(void)
915 {
916         printf(":::op__split_test\n");
917
918         WIMStruct *wim;
919         WIMStruct *swm;
920         WIMStruct *joined_wim;
921         uint64_t part_size;
922         int write_flags;
923         const tchar *globs[] = { T("tmp*.swm") };
924         int image_count;
925
926         if (num_wimfiles_in_use == 0)
927                 return;
928
929         /* split, join, and compare  */
930
931         wim = open_random_wim();
932
933         if (wim_contains_solid_resources(wim)) {
934                 /* Can't split a WIM containing solid resources  */
935                 wimlib_free(wim);
936                 return;
937         }
938
939         image_count = get_image_count(wim);
940
941         part_size = 10000 + (rand32() % 1000000);
942         write_flags = get_random_write_flags();
943         write_flags &= ~WIMLIB_WRITE_FLAG_SOLID;
944
945         printf("splitting WIM %"TS": part_size=%"PRIu64", write_flags=0x%08x\n",
946                wimfile, part_size, write_flags);
947
948         CHECK_RET(wimlib_split(wim, T("tmp.swm"), part_size, write_flags));
949
950         CHECK_RET(wimlib_open_wim(T("tmp.swm"), WIMLIB_OPEN_FLAG_CHECK_INTEGRITY,
951                                   &swm));
952
953         CHECK_RET(wimlib_reference_resource_files(swm, globs, 1,
954                                                   WIMLIB_REF_FLAG_GLOB_ENABLE |
955                                                         WIMLIB_REF_FLAG_GLOB_ERR_ON_NOMATCH,
956                                                   WIMLIB_OPEN_FLAG_CHECK_INTEGRITY));
957
958         CHECK_RET(wimlib_verify_wim(swm, 0));
959
960         CHECK_RET(wimlib_write(swm, T("joined.wim"), WIMLIB_ALL_IMAGES, write_flags, 0));
961         wimlib_free(swm);
962
963         CHECK_RET(wimlib_open_wim(T("joined.wim"), 0, &joined_wim));
964         for (int i = 1; i <= image_count; i++)
965                 CHECK_RET(wimlib_compare_images(wim, 1, joined_wim, 1, 0));
966         CHECK_RET(wimlib_verify_wim(joined_wim, 0));
967         wimlib_free(joined_wim);
968         wimlib_free(wim);
969
970         tunlink(T("tmp.swm"));
971         for (int i = 2; ; i++) {
972                 tchar name[32];
973                 tsprintf(name, T("tmp%d.swm"), i);
974                 if (tunlink(name))
975                         break;
976         }
977 }
978
979 static void
980 op__set_compression_level(void)
981 {
982         printf(":::op__set_compression_level\n");
983
984         unsigned int level = rand32() % 100;
985         printf("Changing compression levels to %d\n", level);
986         wimlib_set_default_compression_level(-1, level);
987 }
988
989 typedef void (*operation_func)(void);
990
991 static const operation_func operation_table[] = {
992         op__create_new_wim,
993         op__add_empty_image_to_random_wim,
994         op__delete_random_image_from_random_wim,
995         op__delete_random_wim,
996         op__delete_random_wim,
997         op__verify_random_wim,
998         op__overwrite_with_no_changes,
999         op__export_random_image,
1000         op__apply_and_capture_test,
1001         op__apply_and_capture_test,
1002         op__apply_and_capture_test,
1003         op__apply_and_capture_test,
1004         op__apply_and_capture_test,
1005         op__split_test,
1006         op__set_compression_level,
1007 #ifdef __WIN32__
1008         op__wimboot_test,
1009 #endif
1010 };
1011
1012 #ifdef __WIN32__
1013 extern int wmain(int argc, wchar_t **argv);
1014 #define main wmain
1015 #endif
1016
1017 int
1018 main(int argc, tchar **argv)
1019 {
1020         unsigned long long num_iterations;
1021
1022         if (argc < 2) {
1023                 num_iterations = ULLONG_MAX;
1024                 printf("Starting test runner\n");
1025         } else {
1026                 num_iterations = tstrtoull(argv[1], NULL, 10);
1027                 printf("Starting test runner with %llu iterations\n",
1028                        num_iterations);
1029         }
1030
1031         CHECK_RET(wimlib_global_init(0));
1032         wimlib_set_print_errors(true);
1033
1034         change_to_temporary_directory();
1035
1036         for (int i = 0; i < MAX_NUM_WIMS; i++)
1037                 ASSERT(!tunlink(get_wimfile(i)) || errno == ENOENT, "unlink: %m");
1038
1039         for (unsigned long long i = 0; i < num_iterations; i++) {
1040                 printf("--> iteration %llu\n", i);
1041                 (*operation_table[rand32() % ARRAY_LEN(operation_table)])();
1042         }
1043
1044         wimlib_global_cleanup();
1045         return 0;
1046 }