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