1433d6423SLionel Sambuc /* Test 74 - mmap functionality & regression test.
2433d6423SLionel Sambuc *
3433d6423SLionel Sambuc * This test tests some basic functionality of mmap, and also some
4433d6423SLionel Sambuc * cases that are quite complex for the system to handle.
5433d6423SLionel Sambuc *
6433d6423SLionel Sambuc * Memory pages are generally made available on demand. Memory copying
7433d6423SLionel Sambuc * is done by the kernel. As the kernel may encounter pagefaults in
8433d6423SLionel Sambuc * legitimate memory ranges (e.g. pages that aren't mapped; pages that
9433d6423SLionel Sambuc * are mapped RO as they are COW), it cooperates with VM to make the
10433d6423SLionel Sambuc * mappings and let the copy succeed transparently.
11433d6423SLionel Sambuc *
12433d6423SLionel Sambuc * With file-mapped ranges this can result in a deadlock, if care is
13433d6423SLionel Sambuc * not taken, as the copy might be request by VFS or an FS. This test
14433d6423SLionel Sambuc * triggers as many of these states as possible to ensure they are
15433d6423SLionel Sambuc * successful or (where appropriate) fail gracefully, i.e. without
16433d6423SLionel Sambuc * deadlock.
17433d6423SLionel Sambuc *
18433d6423SLionel Sambuc * To do this, system calls are done with source or target buffers with
19433d6423SLionel Sambuc * missing or readonly mappings, both anonymous and file-mapped. The
20433d6423SLionel Sambuc * cache is flushed before mmap() so that we know the mappings should
21433d6423SLionel Sambuc * not be present on mmap() time. Then e.g. a read() or write() is
22433d6423SLionel Sambuc * executed with that buffer as target. This triggers a FS copying
23433d6423SLionel Sambuc * to or from a missing range that it itself is needed to map in first.
24433d6423SLionel Sambuc * VFS detects this, requests VM to map in the pages, which does so with
25433d6423SLionel Sambuc * the help of another VFS thread and the FS, and then re-issues the
26433d6423SLionel Sambuc * request to the FS.
27433d6423SLionel Sambuc *
28433d6423SLionel Sambuc * Another case is the VFS itself does such a copy. This is actually
29433d6423SLionel Sambuc * unusual as filenames are already faulted in by the requesting process
30433d6423SLionel Sambuc * in libc by strlen(). select() allows such a case, however, so this
31433d6423SLionel Sambuc * is tested too. We are satisfied if the call completes.
32433d6423SLionel Sambuc */
33433d6423SLionel Sambuc
34433d6423SLionel Sambuc #include <sys/types.h>
35433d6423SLionel Sambuc #include <sys/mman.h>
36433d6423SLionel Sambuc #include <sys/ioctl.h>
37433d6423SLionel Sambuc #include <sys/ioc_memory.h>
38433d6423SLionel Sambuc #include <sys/param.h>
39e321f655SDavid van Moolenbroek #include <minix/paths.h>
40433d6423SLionel Sambuc #include <stdio.h>
41433d6423SLionel Sambuc #include <assert.h>
42433d6423SLionel Sambuc #include <string.h>
43433d6423SLionel Sambuc #include <stdlib.h>
44433d6423SLionel Sambuc #include <unistd.h>
45433d6423SLionel Sambuc #include <fcntl.h>
46433d6423SLionel Sambuc #include <dirent.h>
47433d6423SLionel Sambuc
48433d6423SLionel Sambuc #include "common.h"
49433d6423SLionel Sambuc #include "testcache.h"
50433d6423SLionel Sambuc
51433d6423SLionel Sambuc int max_error = 0; /* make all e()'s fatal */
52433d6423SLionel Sambuc
53433d6423SLionel Sambuc int
dowriteblock(int b,int blocksize,u32_t seed,char * data)54433d6423SLionel Sambuc dowriteblock(int b, int blocksize, u32_t seed, char *data)
55433d6423SLionel Sambuc {
56433d6423SLionel Sambuc u64_t offset;
57433d6423SLionel Sambuc int fd;
58433d6423SLionel Sambuc
59433d6423SLionel Sambuc get_fd_offset(b, blocksize, &offset, &fd);
60433d6423SLionel Sambuc
61433d6423SLionel Sambuc if(pwrite(fd, data, blocksize, offset) < blocksize) {
62433d6423SLionel Sambuc perror("pwrite");
63433d6423SLionel Sambuc return -1;
64433d6423SLionel Sambuc }
65433d6423SLionel Sambuc
66433d6423SLionel Sambuc return blocksize;
67433d6423SLionel Sambuc }
68433d6423SLionel Sambuc
69433d6423SLionel Sambuc int
readblock(int b,int blocksize,u32_t seed,char * data)70433d6423SLionel Sambuc readblock(int b, int blocksize, u32_t seed, char *data)
71433d6423SLionel Sambuc {
72433d6423SLionel Sambuc u64_t offset;
73433d6423SLionel Sambuc int fd;
74433d6423SLionel Sambuc char *mmapdata;
75433d6423SLionel Sambuc int pread_first = random() % 2;
76433d6423SLionel Sambuc
77433d6423SLionel Sambuc get_fd_offset(b, blocksize, &offset, &fd);
78433d6423SLionel Sambuc
79433d6423SLionel Sambuc if(pread_first) {
80433d6423SLionel Sambuc if(pread(fd, data, blocksize, offset) < blocksize) {
81433d6423SLionel Sambuc perror("pread");
82433d6423SLionel Sambuc return -1;
83433d6423SLionel Sambuc }
84433d6423SLionel Sambuc }
85433d6423SLionel Sambuc
86433d6423SLionel Sambuc if((mmapdata = mmap(NULL, blocksize, PROT_READ, MAP_PRIVATE | MAP_FILE,
87433d6423SLionel Sambuc fd, offset)) == MAP_FAILED) {
88433d6423SLionel Sambuc perror("mmap");
89433d6423SLionel Sambuc return -1;
90433d6423SLionel Sambuc }
91433d6423SLionel Sambuc
92433d6423SLionel Sambuc if(!pread_first) {
93433d6423SLionel Sambuc if(pread(fd, data, blocksize, offset) < blocksize) {
94433d6423SLionel Sambuc perror("pread");
95433d6423SLionel Sambuc return -1;
96433d6423SLionel Sambuc }
97433d6423SLionel Sambuc }
98433d6423SLionel Sambuc
99433d6423SLionel Sambuc if(memcmp(mmapdata, data, blocksize)) {
100433d6423SLionel Sambuc fprintf(stderr, "readblock: mmap, pread mismatch\n");
101433d6423SLionel Sambuc return -1;
102433d6423SLionel Sambuc }
103433d6423SLionel Sambuc
104433d6423SLionel Sambuc if(munmap(mmapdata, blocksize) < 0) {
105433d6423SLionel Sambuc perror("munmap");
106433d6423SLionel Sambuc return -1;
107433d6423SLionel Sambuc }
108433d6423SLionel Sambuc
109433d6423SLionel Sambuc return blocksize;
110433d6423SLionel Sambuc }
111433d6423SLionel Sambuc
testend(void)112433d6423SLionel Sambuc void testend(void) { }
113433d6423SLionel Sambuc
do_read(void * buf,int fd,int writable)114433d6423SLionel Sambuc static void do_read(void *buf, int fd, int writable)
115433d6423SLionel Sambuc {
116433d6423SLionel Sambuc ssize_t ret;
117433d6423SLionel Sambuc size_t n = PAGE_SIZE;
118433d6423SLionel Sambuc struct stat sb;
119433d6423SLionel Sambuc if(fstat(fd, &sb) < 0) e(1);
120433d6423SLionel Sambuc if(S_ISDIR(sb.st_mode)) return;
121433d6423SLionel Sambuc ret = read(fd, buf, n);
122433d6423SLionel Sambuc
123433d6423SLionel Sambuc /* if the buffer is writable, it should succeed */
124433d6423SLionel Sambuc if(writable) { if(ret != n) e(3); return; }
125433d6423SLionel Sambuc
126433d6423SLionel Sambuc /* if the buffer is not writable, it should fail with EFAULT */
127433d6423SLionel Sambuc if(ret >= 0) e(4);
128433d6423SLionel Sambuc if(errno != EFAULT) e(5);
129433d6423SLionel Sambuc }
130433d6423SLionel Sambuc
do_write(void * buf,int fd,int writable)131433d6423SLionel Sambuc static void do_write(void *buf, int fd, int writable)
132433d6423SLionel Sambuc {
133433d6423SLionel Sambuc size_t n = PAGE_SIZE;
134433d6423SLionel Sambuc struct stat sb;
135433d6423SLionel Sambuc if(fstat(fd, &sb) < 0) e(1);
136433d6423SLionel Sambuc if(S_ISDIR(sb.st_mode)) return;
137433d6423SLionel Sambuc if(write(fd, buf, n) != n) e(3);
138433d6423SLionel Sambuc }
139433d6423SLionel Sambuc
do_stat(void * buf,int fd,int writable)140433d6423SLionel Sambuc static void do_stat(void *buf, int fd, int writable)
141433d6423SLionel Sambuc {
142433d6423SLionel Sambuc int r;
143433d6423SLionel Sambuc r = fstat(fd, (struct stat *) buf);
144433d6423SLionel Sambuc
145433d6423SLionel Sambuc /* should succeed if buf is writable */
146433d6423SLionel Sambuc if(writable) { if(r < 0) e(3); return; }
147433d6423SLionel Sambuc
148433d6423SLionel Sambuc /* should fail with EFAULT if buf is not */
149433d6423SLionel Sambuc if(r >= 0) e(4);
150433d6423SLionel Sambuc if(errno != EFAULT) e(5);
151433d6423SLionel Sambuc }
152433d6423SLionel Sambuc
do_getdents(void * buf,int fd,int writable)153433d6423SLionel Sambuc static void do_getdents(void *buf, int fd, int writable)
154433d6423SLionel Sambuc {
155433d6423SLionel Sambuc struct stat sb;
156433d6423SLionel Sambuc int r;
157433d6423SLionel Sambuc if(fstat(fd, &sb) < 0) e(1);
158433d6423SLionel Sambuc if(!S_ISDIR(sb.st_mode)) return; /* OK */
159433d6423SLionel Sambuc r = getdents(fd, buf, PAGE_SIZE);
160433d6423SLionel Sambuc if(writable) { if(r < 0) e(3); return; }
161433d6423SLionel Sambuc
162433d6423SLionel Sambuc /* should fail with EFAULT if buf is not */
163433d6423SLionel Sambuc if(r >= 0) e(4);
164433d6423SLionel Sambuc if(errno != EFAULT) e(5);
165433d6423SLionel Sambuc }
166433d6423SLionel Sambuc
do_readlink1(void * buf,int fd,int writable)167433d6423SLionel Sambuc static void do_readlink1(void *buf, int fd, int writable)
168433d6423SLionel Sambuc {
169433d6423SLionel Sambuc char target[200];
170433d6423SLionel Sambuc /* the system call just has to fail gracefully */
171433d6423SLionel Sambuc readlink(buf, target, sizeof(target));
172433d6423SLionel Sambuc }
173433d6423SLionel Sambuc
174433d6423SLionel Sambuc #define NODENAME "a"
175433d6423SLionel Sambuc #define TARGETNAME "b"
176433d6423SLionel Sambuc
do_readlink2(void * buf,int fd,int writable)177433d6423SLionel Sambuc static void do_readlink2(void *buf, int fd, int writable)
178433d6423SLionel Sambuc {
179433d6423SLionel Sambuc ssize_t rl;
180433d6423SLionel Sambuc unlink(NODENAME);
181433d6423SLionel Sambuc if(symlink(TARGETNAME, NODENAME) < 0) e(1);
182433d6423SLionel Sambuc rl=readlink(NODENAME, buf, sizeof(buf));
183433d6423SLionel Sambuc
184433d6423SLionel Sambuc /* if buf is writable, it should succeed, with a certain result */
185433d6423SLionel Sambuc if(writable) {
186433d6423SLionel Sambuc if(rl < 0) e(2);
187433d6423SLionel Sambuc ((char *) buf)[rl] = '\0';
188433d6423SLionel Sambuc if(strcmp(buf, TARGETNAME)) {
189433d6423SLionel Sambuc fprintf(stderr, "readlink: expected %s, got %s\n",
1907c48de6cSDavid van Moolenbroek TARGETNAME, (char *)buf);
191433d6423SLionel Sambuc e(3);
192433d6423SLionel Sambuc }
193433d6423SLionel Sambuc return;
194433d6423SLionel Sambuc }
195433d6423SLionel Sambuc
196433d6423SLionel Sambuc /* if buf is not writable, it should fail with EFAULT */
197433d6423SLionel Sambuc if(rl >= 0) e(4);
198433d6423SLionel Sambuc
199433d6423SLionel Sambuc if(errno != EFAULT) e(5);
200433d6423SLionel Sambuc }
201433d6423SLionel Sambuc
do_symlink1(void * buf,int fd,int writable)202433d6423SLionel Sambuc static void do_symlink1(void *buf, int fd, int writable)
203433d6423SLionel Sambuc {
204433d6423SLionel Sambuc /* the system call just has to fail gracefully */
2053083d603SDavid van Moolenbroek (void)symlink(buf, NODENAME);
206433d6423SLionel Sambuc }
207433d6423SLionel Sambuc
do_symlink2(void * buf,int fd,int writable)208433d6423SLionel Sambuc static void do_symlink2(void *buf, int fd, int writable)
209433d6423SLionel Sambuc {
210433d6423SLionel Sambuc /* the system call just has to fail gracefully */
2113083d603SDavid van Moolenbroek (void)symlink(NODENAME, buf);
212433d6423SLionel Sambuc }
213433d6423SLionel Sambuc
do_open(void * buf,int fd,int writable)214433d6423SLionel Sambuc static void do_open(void *buf, int fd, int writable)
215433d6423SLionel Sambuc {
216433d6423SLionel Sambuc int r;
217433d6423SLionel Sambuc /* the system call just has to fail gracefully */
218433d6423SLionel Sambuc r = open(buf, O_RDONLY);
219433d6423SLionel Sambuc if(r >= 0) close(r);
220433d6423SLionel Sambuc }
221433d6423SLionel Sambuc
do_select1(void * buf,int fd,int writable)222433d6423SLionel Sambuc static void do_select1(void *buf, int fd, int writable)
223433d6423SLionel Sambuc {
224433d6423SLionel Sambuc struct timeval timeout = { 0, 200000 }; /* 0.2 sec */
225433d6423SLionel Sambuc /* the system call just has to fail gracefully */
2263083d603SDavid van Moolenbroek (void)select(1, buf, NULL, NULL, &timeout);
227433d6423SLionel Sambuc }
228433d6423SLionel Sambuc
do_select2(void * buf,int fd,int writable)229433d6423SLionel Sambuc static void do_select2(void *buf, int fd, int writable)
230433d6423SLionel Sambuc {
231433d6423SLionel Sambuc struct timeval timeout = { 0, 200000 }; /* 1 sec */
232433d6423SLionel Sambuc /* the system call just has to fail gracefully */
2333083d603SDavid van Moolenbroek (void)select(1, NULL, buf, NULL, &timeout);
234433d6423SLionel Sambuc }
235433d6423SLionel Sambuc
do_select3(void * buf,int fd,int writable)236433d6423SLionel Sambuc static void do_select3(void *buf, int fd, int writable)
237433d6423SLionel Sambuc {
238433d6423SLionel Sambuc struct timeval timeout = { 0, 200000 }; /* 1 sec */
239433d6423SLionel Sambuc /* the system call just has to fail gracefully */
2403083d603SDavid van Moolenbroek (void)select(1, NULL, NULL, buf, &timeout);
241433d6423SLionel Sambuc }
242433d6423SLionel Sambuc
fillfile(int fd,int size)243433d6423SLionel Sambuc static void fillfile(int fd, int size)
244433d6423SLionel Sambuc {
245433d6423SLionel Sambuc char *buf = malloc(size);
246433d6423SLionel Sambuc
247433d6423SLionel Sambuc if(size < 1 || size % PAGE_SIZE || !buf) { e(1); }
248433d6423SLionel Sambuc memset(buf, 'A', size);
249433d6423SLionel Sambuc buf[50] = '\0'; /* so it can be used as a filename arg */
250433d6423SLionel Sambuc buf[size-1] = '\0';
251433d6423SLionel Sambuc if(write(fd, buf, size) != size) { e(2); }
252433d6423SLionel Sambuc if(lseek(fd, SEEK_SET, 0) < 0) { e(3); }
253433d6423SLionel Sambuc free(buf);
254433d6423SLionel Sambuc }
255433d6423SLionel Sambuc
make_buffers(int size,int * ret_fd_rw,int * ret_fd_ro,void ** filebuf_rw,void ** filebuf_ro,void ** anonbuf)256433d6423SLionel Sambuc static void make_buffers(int size,
257433d6423SLionel Sambuc int *ret_fd_rw, int *ret_fd_ro,
258433d6423SLionel Sambuc void **filebuf_rw, void **filebuf_ro, void **anonbuf)
259433d6423SLionel Sambuc {
260433d6423SLionel Sambuc char fn_rw[] = "testfile_rw.XXXXXX", fn_ro[] = "testfile_ro.XXXXXX";
261433d6423SLionel Sambuc *ret_fd_rw = mkstemp(fn_rw);
262433d6423SLionel Sambuc *ret_fd_ro = mkstemp(fn_ro);
263433d6423SLionel Sambuc
264433d6423SLionel Sambuc if(size < 1 || size % PAGE_SIZE) { e(2); }
265433d6423SLionel Sambuc if(*ret_fd_rw < 0) { e(1); }
266433d6423SLionel Sambuc if(*ret_fd_ro < 0) { e(1); }
267433d6423SLionel Sambuc fillfile(*ret_fd_rw, size);
268433d6423SLionel Sambuc fillfile(*ret_fd_ro, size);
269433d6423SLionel Sambuc if(fcntl(*ret_fd_rw, F_FLUSH_FS_CACHE) < 0) { e(4); }
270433d6423SLionel Sambuc if(fcntl(*ret_fd_ro, F_FLUSH_FS_CACHE) < 0) { e(4); }
271433d6423SLionel Sambuc
272433d6423SLionel Sambuc if((*filebuf_rw = mmap(0, size, PROT_READ | PROT_WRITE,
273433d6423SLionel Sambuc MAP_PRIVATE | MAP_FILE, *ret_fd_rw, 0)) == MAP_FAILED) {
274433d6423SLionel Sambuc e(5);
275433d6423SLionel Sambuc quit();
276433d6423SLionel Sambuc }
277433d6423SLionel Sambuc
278433d6423SLionel Sambuc if((*filebuf_ro = mmap(0, size, PROT_READ,
279433d6423SLionel Sambuc MAP_PRIVATE | MAP_FILE, *ret_fd_ro, 0)) == MAP_FAILED) {
280433d6423SLionel Sambuc e(5);
281433d6423SLionel Sambuc quit();
282433d6423SLionel Sambuc }
283433d6423SLionel Sambuc
284433d6423SLionel Sambuc if((*anonbuf = mmap(0, size, PROT_READ | PROT_WRITE,
285433d6423SLionel Sambuc MAP_PRIVATE | MAP_ANON, -1, 0)) == MAP_FAILED) {
286433d6423SLionel Sambuc e(6);
287433d6423SLionel Sambuc quit();
288433d6423SLionel Sambuc }
289433d6423SLionel Sambuc
290433d6423SLionel Sambuc if(unlink(fn_rw) < 0) { e(12); }
291433d6423SLionel Sambuc if(unlink(fn_ro) < 0) { e(12); }
292433d6423SLionel Sambuc }
293433d6423SLionel Sambuc
forget_buffers(void * buf1,void * buf2,void * buf3,int fd1,int fd2,int size)294433d6423SLionel Sambuc static void forget_buffers(void *buf1, void *buf2, void *buf3, int fd1, int fd2, int size)
295433d6423SLionel Sambuc {
296433d6423SLionel Sambuc if(munmap(buf1, size) < 0) { e(1); }
297433d6423SLionel Sambuc if(munmap(buf2, size) < 0) { e(2); }
298433d6423SLionel Sambuc if(munmap(buf3, size) < 0) { e(2); }
299433d6423SLionel Sambuc if(fcntl(fd1, F_FLUSH_FS_CACHE) < 0) { e(3); }
300433d6423SLionel Sambuc if(fcntl(fd2, F_FLUSH_FS_CACHE) < 0) { e(3); }
301433d6423SLionel Sambuc if(close(fd1) < 0) { e(4); }
302433d6423SLionel Sambuc if(close(fd2) < 0) { e(4); }
303433d6423SLionel Sambuc }
304433d6423SLionel Sambuc
305433d6423SLionel Sambuc #define NEXPERIMENTS 12
306433d6423SLionel Sambuc struct {
307433d6423SLionel Sambuc void (*do_operation)(void * buf, int fd, int writable);
308433d6423SLionel Sambuc } experiments[NEXPERIMENTS] = {
309433d6423SLionel Sambuc { do_read },
310433d6423SLionel Sambuc { do_write },
311433d6423SLionel Sambuc { do_stat },
312433d6423SLionel Sambuc { do_getdents },
313433d6423SLionel Sambuc { do_readlink1 },
314433d6423SLionel Sambuc { do_readlink2 },
315433d6423SLionel Sambuc { do_symlink1 },
316433d6423SLionel Sambuc { do_symlink2 },
317433d6423SLionel Sambuc { do_open, },
318433d6423SLionel Sambuc { do_select1 },
319433d6423SLionel Sambuc { do_select2 },
320433d6423SLionel Sambuc { do_select3 },
321433d6423SLionel Sambuc };
322433d6423SLionel Sambuc
test_memory_types_vs_operations(void)323cbc8a0dfSDavid van Moolenbroek static void test_memory_types_vs_operations(void)
324433d6423SLionel Sambuc {
325433d6423SLionel Sambuc #define NFDS 4
326433d6423SLionel Sambuc #define BUFSIZE (10 * PAGE_SIZE)
327433d6423SLionel Sambuc int exp, fds[NFDS];
328433d6423SLionel Sambuc int f = 0, size = BUFSIZE;
329433d6423SLionel Sambuc
330433d6423SLionel Sambuc /* open some test fd's */
331433d6423SLionel Sambuc #define OPEN(fn, mode) { assert(f >= 0 && f < NFDS); \
332433d6423SLionel Sambuc fds[f] = open(fn, mode); if(fds[f] < 0) { e(2); } f++; }
333433d6423SLionel Sambuc OPEN("regular", O_RDWR | O_CREAT);
334433d6423SLionel Sambuc OPEN(".", O_RDONLY);
335433d6423SLionel Sambuc OPEN("/dev/ram", O_RDWR);
336433d6423SLionel Sambuc OPEN("/dev/zero", O_RDWR);
337433d6423SLionel Sambuc
338433d6423SLionel Sambuc /* make sure the regular file has plenty of size to play with */
339433d6423SLionel Sambuc fillfile(fds[0], BUFSIZE);
340433d6423SLionel Sambuc
341433d6423SLionel Sambuc /* and the ramdisk too */
342433d6423SLionel Sambuc if(ioctl(fds[2], MIOCRAMSIZE, &size) < 0) { e(3); }
343433d6423SLionel Sambuc
344433d6423SLionel Sambuc for(exp = 0; exp < NEXPERIMENTS; exp++) {
345433d6423SLionel Sambuc for(f = 0; f < NFDS; f++) {
346433d6423SLionel Sambuc void *anonmem, *filemem_rw, *filemem_ro;
347433d6423SLionel Sambuc int buffd_rw, buffd_ro;
348433d6423SLionel Sambuc
349433d6423SLionel Sambuc make_buffers(BUFSIZE, &buffd_rw, &buffd_ro,
350433d6423SLionel Sambuc &filemem_rw, &filemem_ro, &anonmem);
351433d6423SLionel Sambuc
352433d6423SLionel Sambuc if(lseek(fds[f], 0, SEEK_SET) != 0) { e(10); }
353433d6423SLionel Sambuc experiments[exp].do_operation(anonmem, fds[f], 1);
354433d6423SLionel Sambuc
355433d6423SLionel Sambuc if(lseek(fds[f], 0, SEEK_SET) != 0) { e(11); }
356433d6423SLionel Sambuc experiments[exp].do_operation(filemem_rw, fds[f], 1);
357433d6423SLionel Sambuc
358433d6423SLionel Sambuc if(lseek(fds[f], 0, SEEK_SET) != 0) { e(12); }
359433d6423SLionel Sambuc experiments[exp].do_operation(filemem_ro, fds[f], 0);
360433d6423SLionel Sambuc
361433d6423SLionel Sambuc forget_buffers(filemem_rw, filemem_ro, anonmem, buffd_rw, buffd_ro, BUFSIZE);
362433d6423SLionel Sambuc }
363433d6423SLionel Sambuc }
364433d6423SLionel Sambuc }
365433d6423SLionel Sambuc
basic_regression(void)366cbc8a0dfSDavid van Moolenbroek static void basic_regression(void)
367433d6423SLionel Sambuc {
368433d6423SLionel Sambuc int fd, fd1, fd2;
369433d6423SLionel Sambuc ssize_t rb, wr;
370433d6423SLionel Sambuc char buf[PAGE_SIZE*2];
371433d6423SLionel Sambuc void *block, *block1, *block2;
372433d6423SLionel Sambuc #define BLOCKSIZE (PAGE_SIZE*10)
373433d6423SLionel Sambuc block = mmap(0, BLOCKSIZE, PROT_READ | PROT_WRITE,
374433d6423SLionel Sambuc MAP_PRIVATE | MAP_ANON, -1, 0);
375433d6423SLionel Sambuc
376433d6423SLionel Sambuc if(block == MAP_FAILED) { e(1); }
377433d6423SLionel Sambuc
378433d6423SLionel Sambuc memset(block, 0, BLOCKSIZE);
379433d6423SLionel Sambuc
380433d6423SLionel Sambuc /* shrink from bottom */
381433d6423SLionel Sambuc munmap(block, PAGE_SIZE);
382433d6423SLionel Sambuc
383433d6423SLionel Sambuc /* Next test: use a system call write() to access a block of
384433d6423SLionel Sambuc * unavailable file-mapped memory.
385433d6423SLionel Sambuc *
386433d6423SLionel Sambuc * This is a thorny corner case to make succeed transparently
387433d6423SLionel Sambuc * because
388433d6423SLionel Sambuc * (1) it is a filesystem that is doing the memory access
389433d6423SLionel Sambuc * (copy from the constblock1 range in this process to the
390433d6423SLionel Sambuc * FS) but is also the FS needed to satisfy the range if it
391433d6423SLionel Sambuc * isn't in the cache.
392433d6423SLionel Sambuc * (2) there are two separate memory regions involved, requiring
393433d6423SLionel Sambuc * separate VFS requests from VM to properly satisfy, requiring
394433d6423SLionel Sambuc * some complex state to be kept.
395433d6423SLionel Sambuc */
396433d6423SLionel Sambuc
397433d6423SLionel Sambuc fd1 = open("../testsh1", O_RDONLY);
398*b52b83f9SDavid van Moolenbroek if (fd1 < 0) fd1 = open("../testsh1.sh", O_RDONLY);
399433d6423SLionel Sambuc fd2 = open("../testsh2", O_RDONLY);
400*b52b83f9SDavid van Moolenbroek if (fd2 < 0) fd2 = open("../testsh2.sh", O_RDONLY);
401433d6423SLionel Sambuc if(fd1 < 0 || fd2 < 0) { e(2); }
402433d6423SLionel Sambuc
403433d6423SLionel Sambuc /* just check that we can't mmap() a file writable */
404433d6423SLionel Sambuc if(mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FILE, fd1, 0) != MAP_FAILED) {
405433d6423SLionel Sambuc e(1);
406433d6423SLionel Sambuc }
407433d6423SLionel Sambuc
408433d6423SLionel Sambuc /* check that we can mmap() a file MAP_SHARED readonly */
409433d6423SLionel Sambuc if(mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED | MAP_FILE, fd1, 0) == MAP_FAILED) {
410433d6423SLionel Sambuc e(1);
411433d6423SLionel Sambuc }
412433d6423SLionel Sambuc
413433d6423SLionel Sambuc /* clear cache of files before mmap so pages won't be present already */
414433d6423SLionel Sambuc if(fcntl(fd1, F_FLUSH_FS_CACHE) < 0) { e(1); }
415433d6423SLionel Sambuc if(fcntl(fd2, F_FLUSH_FS_CACHE) < 0) { e(1); }
416433d6423SLionel Sambuc
417433d6423SLionel Sambuc #define LOCATION1 (void *) 0x90000000
418cbc8a0dfSDavid van Moolenbroek #define LOCATION2 ((void *)((char *)LOCATION1 + PAGE_SIZE))
419433d6423SLionel Sambuc block1 = mmap(LOCATION1, PAGE_SIZE, PROT_READ, MAP_PRIVATE | MAP_FILE, fd1, 0);
420433d6423SLionel Sambuc if(block1 == MAP_FAILED) { e(4); }
421433d6423SLionel Sambuc if(block1 != LOCATION1) { e(5); }
422433d6423SLionel Sambuc
423433d6423SLionel Sambuc block2 = mmap(LOCATION2, PAGE_SIZE, PROT_READ, MAP_PRIVATE | MAP_FILE, fd2, 0);
424433d6423SLionel Sambuc if(block2 == MAP_FAILED) { e(10); }
425433d6423SLionel Sambuc if(block2 != LOCATION2) { e(11); }
426433d6423SLionel Sambuc
427433d6423SLionel Sambuc unlink("testfile");
428433d6423SLionel Sambuc fd = open("testfile", O_CREAT | O_RDWR);
429433d6423SLionel Sambuc if(fd < 0) { e(15); }
430433d6423SLionel Sambuc
431433d6423SLionel Sambuc /* write() using the mmap()ped memory as buffer */
432433d6423SLionel Sambuc
433433d6423SLionel Sambuc if((wr=write(fd, LOCATION1, sizeof(buf))) != sizeof(buf)) {
434433d6423SLionel Sambuc fprintf(stderr, "wrote %zd bytes instead of %zd\n",
435433d6423SLionel Sambuc wr, sizeof(buf));
436433d6423SLionel Sambuc e(20);
437433d6423SLionel Sambuc quit();
438433d6423SLionel Sambuc }
439433d6423SLionel Sambuc
440433d6423SLionel Sambuc /* verify written contents */
441433d6423SLionel Sambuc
442433d6423SLionel Sambuc if((rb=pread(fd, buf, sizeof(buf), 0)) != sizeof(buf)) {
443433d6423SLionel Sambuc if(rb < 0) perror("pread");
444433d6423SLionel Sambuc fprintf(stderr, "wrote %zd bytes\n", wr);
445433d6423SLionel Sambuc fprintf(stderr, "read %zd bytes instead of %zd\n",
446433d6423SLionel Sambuc rb, sizeof(buf));
447433d6423SLionel Sambuc e(21);
448433d6423SLionel Sambuc quit();
449433d6423SLionel Sambuc }
450433d6423SLionel Sambuc
451433d6423SLionel Sambuc if(memcmp(buf, LOCATION1, sizeof(buf))) {
452433d6423SLionel Sambuc e(22);
453433d6423SLionel Sambuc quit();
454433d6423SLionel Sambuc }
455433d6423SLionel Sambuc
456433d6423SLionel Sambuc close(fd);
457433d6423SLionel Sambuc close(fd1);
458433d6423SLionel Sambuc close(fd2);
459433d6423SLionel Sambuc
460433d6423SLionel Sambuc }
461433d6423SLionel Sambuc
462e321f655SDavid van Moolenbroek /*
463e321f655SDavid van Moolenbroek * Test mmap on none-dev file systems - file systems that do not have a buffer
464e321f655SDavid van Moolenbroek * cache and therefore have to fake mmap support. We use procfs as target.
465e321f655SDavid van Moolenbroek * The idea is that while we succeed in mapping in /proc/uptime, we also get
466e321f655SDavid van Moolenbroek * a new uptime value every time we map in the page -- VM must not cache it.
467e321f655SDavid van Moolenbroek */
468e321f655SDavid van Moolenbroek static void
nonedev_regression(void)469e321f655SDavid van Moolenbroek nonedev_regression(void)
470e321f655SDavid van Moolenbroek {
471f202792eSDavid van Moolenbroek int fd, fd2;
472e321f655SDavid van Moolenbroek char *buf;
473e321f655SDavid van Moolenbroek unsigned long uptime1, uptime2, uptime3;
474e321f655SDavid van Moolenbroek
475e321f655SDavid van Moolenbroek subtest++;
476e321f655SDavid van Moolenbroek
477e321f655SDavid van Moolenbroek if ((fd = open(_PATH_PROC "uptime", O_RDONLY)) < 0) e(1);
478e321f655SDavid van Moolenbroek
479e321f655SDavid van Moolenbroek buf = mmap(NULL, 4096, PROT_READ, MAP_PRIVATE | MAP_FILE, fd, 0);
480e321f655SDavid van Moolenbroek if (buf == MAP_FAILED) e(2);
481e321f655SDavid van Moolenbroek
482e321f655SDavid van Moolenbroek if (buf[4095] != 0) e(3);
483e321f655SDavid van Moolenbroek
484e321f655SDavid van Moolenbroek if ((uptime1 = atoi(buf)) == 0) e(4);
485e321f655SDavid van Moolenbroek
486e321f655SDavid van Moolenbroek if (munmap(buf, 4096) != 0) e(5);
487e321f655SDavid van Moolenbroek
488e321f655SDavid van Moolenbroek sleep(2);
489e321f655SDavid van Moolenbroek
490e321f655SDavid van Moolenbroek buf = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FILE,
491e321f655SDavid van Moolenbroek fd, 0);
492e321f655SDavid van Moolenbroek if (buf == MAP_FAILED) e(6);
493e321f655SDavid van Moolenbroek
494e321f655SDavid van Moolenbroek if (buf[4095] != 0) e(7);
495e321f655SDavid van Moolenbroek
496e321f655SDavid van Moolenbroek if ((uptime2 = atoi(buf)) == 0) e(8);
497e321f655SDavid van Moolenbroek
498e321f655SDavid van Moolenbroek if (uptime1 == uptime2) e(9);
499e321f655SDavid van Moolenbroek
500e321f655SDavid van Moolenbroek if (munmap(buf, 4096) != 0) e(10);
501e321f655SDavid van Moolenbroek
502e321f655SDavid van Moolenbroek sleep(2);
503e321f655SDavid van Moolenbroek
504e321f655SDavid van Moolenbroek buf = mmap(NULL, 4096, PROT_READ, MAP_SHARED | MAP_FILE, fd, 0);
505e321f655SDavid van Moolenbroek if (buf == MAP_FAILED) e(11);
506e321f655SDavid van Moolenbroek
507e321f655SDavid van Moolenbroek if (buf[4095] != 0) e(12);
508e321f655SDavid van Moolenbroek
509e321f655SDavid van Moolenbroek if ((uptime3 = atoi(buf)) == 0) e(13);
510e321f655SDavid van Moolenbroek
511e321f655SDavid van Moolenbroek if (uptime1 == uptime3) e(14);
512e321f655SDavid van Moolenbroek if (uptime2 == uptime3) e(15);
513e321f655SDavid van Moolenbroek
514e321f655SDavid van Moolenbroek if (munmap(buf, 4096) != 0) e(16);
515e321f655SDavid van Moolenbroek
516f202792eSDavid van Moolenbroek /* Also test page faults not incurred by the process itself. */
517f202792eSDavid van Moolenbroek if ((fd2 = open("testfile", O_CREAT | O_TRUNC | O_WRONLY)) < 0) e(17);
518f202792eSDavid van Moolenbroek
519f202792eSDavid van Moolenbroek if (unlink("testfile") != 0) e(18);
520f202792eSDavid van Moolenbroek
521f202792eSDavid van Moolenbroek buf = mmap(NULL, 4096, PROT_READ, MAP_SHARED | MAP_FILE, fd, 0);
522f202792eSDavid van Moolenbroek if (buf == MAP_FAILED) e(19);
523f202792eSDavid van Moolenbroek
524f202792eSDavid van Moolenbroek if (write(fd2, buf, 10) != 10) e(20);
525f202792eSDavid van Moolenbroek
526f202792eSDavid van Moolenbroek if (munmap(buf, 4096) != 0) e(21);
527f202792eSDavid van Moolenbroek
528f202792eSDavid van Moolenbroek close(fd2);
529e321f655SDavid van Moolenbroek close(fd);
530e321f655SDavid van Moolenbroek }
531e321f655SDavid van Moolenbroek
532e94f856bSDavid van Moolenbroek /*
533e94f856bSDavid van Moolenbroek * Regression test for a nasty memory-mapped file corruption bug, which is not
534e94f856bSDavid van Moolenbroek * easy to reproduce but, before being solved, did occur in practice every once
535e94f856bSDavid van Moolenbroek * in a while. The executive summary is that through stale inode associations,
536e94f856bSDavid van Moolenbroek * VM could end up using an old block to satisfy a memory mapping.
537e94f856bSDavid van Moolenbroek *
538e94f856bSDavid van Moolenbroek * This subtest relies on a number of assumptions regarding allocation and
539e94f856bSDavid van Moolenbroek * reuse of inode numbers and blocks. These assumptions hold for MFS but
540e94f856bSDavid van Moolenbroek * possibly no other file system. However, if the subtest's assumptions are
541e94f856bSDavid van Moolenbroek * not met, it will simply succeed.
542e94f856bSDavid van Moolenbroek */
543e94f856bSDavid van Moolenbroek static void
corruption_regression(void)544e94f856bSDavid van Moolenbroek corruption_regression(void)
545e94f856bSDavid van Moolenbroek {
546e94f856bSDavid van Moolenbroek char *ptr, *buf;
547e94f856bSDavid van Moolenbroek struct statvfs sf;
548e94f856bSDavid van Moolenbroek struct stat st;
549e94f856bSDavid van Moolenbroek size_t block_size;
550e94f856bSDavid van Moolenbroek off_t size;
551e94f856bSDavid van Moolenbroek int fd, fd2;
552e94f856bSDavid van Moolenbroek
553e94f856bSDavid van Moolenbroek subtest = 1;
554e94f856bSDavid van Moolenbroek
555e94f856bSDavid van Moolenbroek if (statvfs(".", &sf) != 0) e(0);
556e94f856bSDavid van Moolenbroek block_size = sf.f_bsize;
557e94f856bSDavid van Moolenbroek
558e94f856bSDavid van Moolenbroek if ((buf = malloc(block_size * 2)) == NULL) e(0);
559e94f856bSDavid van Moolenbroek
560e94f856bSDavid van Moolenbroek /*
561e94f856bSDavid van Moolenbroek * We first need a file that is just large enough that it requires the
562e94f856bSDavid van Moolenbroek * allocation of a metadata block - an indirect block - when more data
563e94f856bSDavid van Moolenbroek * is written to it. This is fileA. We keep it open throughout the
564e94f856bSDavid van Moolenbroek * test so we can unlink it immediately.
565e94f856bSDavid van Moolenbroek */
566e94f856bSDavid van Moolenbroek if ((fd = open("fileA", O_CREAT | O_TRUNC | O_WRONLY, 0600)) == -1)
567e94f856bSDavid van Moolenbroek e(0);
568e94f856bSDavid van Moolenbroek if (unlink("fileA") != 0) e(0);
569e94f856bSDavid van Moolenbroek
570e94f856bSDavid van Moolenbroek /*
571e94f856bSDavid van Moolenbroek * Write to fileA until its next block requires the allocation of an
572e94f856bSDavid van Moolenbroek * additional metadata block - an indirect block.
573e94f856bSDavid van Moolenbroek */
574e94f856bSDavid van Moolenbroek size = 0;
575e94f856bSDavid van Moolenbroek memset(buf, 'A', block_size);
576e94f856bSDavid van Moolenbroek do {
577e94f856bSDavid van Moolenbroek /*
578e94f856bSDavid van Moolenbroek * Repeatedly write an extra block, until the file consists of
579e94f856bSDavid van Moolenbroek * more blocks than just the file data.
580e94f856bSDavid van Moolenbroek */
581e94f856bSDavid van Moolenbroek if (write(fd, buf, block_size) != block_size) e(0);
582e94f856bSDavid van Moolenbroek size += block_size;
583e94f856bSDavid van Moolenbroek if (size >= block_size * 64) {
584e94f856bSDavid van Moolenbroek /*
585e94f856bSDavid van Moolenbroek * It doesn't look like this is going to work.
586e94f856bSDavid van Moolenbroek * Skip this subtest altogether.
587e94f856bSDavid van Moolenbroek */
588e94f856bSDavid van Moolenbroek if (close(fd) != 0) e(0);
589e94f856bSDavid van Moolenbroek free(buf);
590e94f856bSDavid van Moolenbroek
591e94f856bSDavid van Moolenbroek return;
592e94f856bSDavid van Moolenbroek }
593e94f856bSDavid van Moolenbroek if (fstat(fd, &st) != 0) e(0);
594e94f856bSDavid van Moolenbroek } while (st.st_blocks * 512 == size);
595e94f856bSDavid van Moolenbroek
596e94f856bSDavid van Moolenbroek /* Once we get there, go one step back by truncating by one block. */
597e94f856bSDavid van Moolenbroek size -= block_size; /* for MFS, size will end up being 7*block_size */
598e94f856bSDavid van Moolenbroek if (ftruncate(fd, size) != 0) e(0);
599e94f856bSDavid van Moolenbroek
600e94f856bSDavid van Moolenbroek /*
601e94f856bSDavid van Moolenbroek * Create a first file, fileB, and write two blocks to it. FileB's
602e94f856bSDavid van Moolenbroek * blocks are going to end up in the secondary VM cache, associated to
603e94f856bSDavid van Moolenbroek * fileB's inode number (and two different offsets within the file).
604e94f856bSDavid van Moolenbroek * The block cache does not know about files getting deleted, so we can
605e94f856bSDavid van Moolenbroek * unlink fileB immediately after creating it. So far so good.
606e94f856bSDavid van Moolenbroek */
607e94f856bSDavid van Moolenbroek if ((fd2 = open("fileB", O_CREAT | O_TRUNC | O_WRONLY, 0600)) == -1)
608e94f856bSDavid van Moolenbroek e(0);
609e94f856bSDavid van Moolenbroek if (unlink("fileB") != 0) e(0);
610e94f856bSDavid van Moolenbroek memset(buf, 'B', block_size * 2);
611e94f856bSDavid van Moolenbroek if (write(fd2, buf, block_size * 2) != block_size * 2) e(0);
612e94f856bSDavid van Moolenbroek if (close(fd2) != 0) e(0);
613e94f856bSDavid van Moolenbroek
614e94f856bSDavid van Moolenbroek /*
615e94f856bSDavid van Moolenbroek * Write one extra block to fileA, hoping that this causes allocation
616e94f856bSDavid van Moolenbroek * of a metadata block as well. This is why we tried to get fileA to
617e94f856bSDavid van Moolenbroek * the point that one more block would also require the allocation of a
618e94f856bSDavid van Moolenbroek * metadata block. Our intent is to recycle the blocks that we just
619e94f856bSDavid van Moolenbroek * allocated and freed for fileB. As of writing, for the metadata
620e94f856bSDavid van Moolenbroek * block, this will *not* break the association with fileB's inode,
621e94f856bSDavid van Moolenbroek * which by itself is not a problem, yet crucial to reproducing
622e94f856bSDavid van Moolenbroek * the actual problem a bit later. Note that the test does not rely on
623e94f856bSDavid van Moolenbroek * whether the file system allocates the data block or the metadata
624e94f856bSDavid van Moolenbroek * block first, although it does need reverse deallocation (see below).
625e94f856bSDavid van Moolenbroek */
626e94f856bSDavid van Moolenbroek memset(buf, 'A', block_size);
627e94f856bSDavid van Moolenbroek if (write(fd, buf, block_size) != block_size) e(0);
628e94f856bSDavid van Moolenbroek
629e94f856bSDavid van Moolenbroek /*
630e94f856bSDavid van Moolenbroek * Create a new file, fileC, which recycles the inode number of fileB,
631e94f856bSDavid van Moolenbroek * but uses two new blocks to store its data. These new blocks will
632e94f856bSDavid van Moolenbroek * get associated to the fileB inode number, and one of them will
633e94f856bSDavid van Moolenbroek * thereby eclipse (but not remove) the association of fileA's metadata
634e94f856bSDavid van Moolenbroek * block to the inode of fileB.
635e94f856bSDavid van Moolenbroek */
636e94f856bSDavid van Moolenbroek if ((fd2 = open("fileC", O_CREAT | O_TRUNC | O_WRONLY, 0600)) == -1)
637e94f856bSDavid van Moolenbroek e(0);
638e94f856bSDavid van Moolenbroek if (unlink("fileC") != 0) e(0);
639e94f856bSDavid van Moolenbroek memset(buf, 'C', block_size * 2);
640e94f856bSDavid van Moolenbroek if (write(fd2, buf, block_size * 2) != block_size * 2) e(0);
641e94f856bSDavid van Moolenbroek if (close(fd2) != 0) e(0);
642e94f856bSDavid van Moolenbroek
643e94f856bSDavid van Moolenbroek /*
644e94f856bSDavid van Moolenbroek * Free up the extra fileA blocks for reallocation, in particular
645e94f856bSDavid van Moolenbroek * including the metadata block. Again, this will not affect the
646e94f856bSDavid van Moolenbroek * contents of the VM cache in any way. FileA's metadata block remains
647e94f856bSDavid van Moolenbroek * cached in VM, with the inode association for fileB's block.
648e94f856bSDavid van Moolenbroek */
649e94f856bSDavid van Moolenbroek if (ftruncate(fd, size) != 0) e(0);
650e94f856bSDavid van Moolenbroek
651e94f856bSDavid van Moolenbroek /*
652e94f856bSDavid van Moolenbroek * Now create yet one more file, fileD, which also recycles the inode
653e94f856bSDavid van Moolenbroek * number of fileB and fileC. Write two blocks to it; these blocks
654e94f856bSDavid van Moolenbroek * should recycle the blocks we just freed. One of these is fileA's
655e94f856bSDavid van Moolenbroek * just-freed metadata block, for which the new inode association will
656e94f856bSDavid van Moolenbroek * be equal to the inode association it had already (as long as blocks
657e94f856bSDavid van Moolenbroek * are freed in reverse order of their allocation, which happens to be
658e94f856bSDavid van Moolenbroek * the case for MFS). As a result, the block is not updated in the VM
659e94f856bSDavid van Moolenbroek * cache, and VM will therefore continue to see the inode association
660e94f856bSDavid van Moolenbroek * for the corresponding block of fileC which is still in the VM cache.
661e94f856bSDavid van Moolenbroek */
662e94f856bSDavid van Moolenbroek if ((fd2 = open("fileD", O_CREAT | O_TRUNC | O_RDWR, 0600)) == -1)
663e94f856bSDavid van Moolenbroek e(0);
664e94f856bSDavid van Moolenbroek memset(buf, 'D', block_size * 2);
665e94f856bSDavid van Moolenbroek if (write(fd2, buf, block_size * 2) != block_size * 2) e(0);
666e94f856bSDavid van Moolenbroek
667e94f856bSDavid van Moolenbroek ptr = mmap(NULL, block_size * 2, PROT_READ, MAP_FILE, fd2, 0);
668e94f856bSDavid van Moolenbroek if (ptr == MAP_FAILED) e(0);
669e94f856bSDavid van Moolenbroek
670e94f856bSDavid van Moolenbroek /*
671e94f856bSDavid van Moolenbroek * Finally, we can test the issue. Since fileC's block is still the
672e94f856bSDavid van Moolenbroek * block for which VM has the corresponding inode association, VM will
673e94f856bSDavid van Moolenbroek * now find and map in fileC's block, instead of fileD's block. The
674e94f856bSDavid van Moolenbroek * result is that we get a memory-mapped area with stale contents,
675e94f856bSDavid van Moolenbroek * different from those of the underlying file.
676e94f856bSDavid van Moolenbroek */
677e94f856bSDavid van Moolenbroek if (memcmp(buf, ptr, block_size * 2)) e(0);
678e94f856bSDavid van Moolenbroek
679e94f856bSDavid van Moolenbroek /* Clean up. */
680e94f856bSDavid van Moolenbroek if (munmap(ptr, block_size * 2) != 0) e(0);
681e94f856bSDavid van Moolenbroek
682e94f856bSDavid van Moolenbroek if (close(fd2) != 0) e(0);
683e94f856bSDavid van Moolenbroek if (unlink("fileD") != 0) e(0);
684e94f856bSDavid van Moolenbroek
685e94f856bSDavid van Moolenbroek if (close(fd) != 0) e(0);
686e94f856bSDavid van Moolenbroek
687e94f856bSDavid van Moolenbroek free(buf);
688e94f856bSDavid van Moolenbroek }
689e94f856bSDavid van Moolenbroek
690d75faf18SDavid van Moolenbroek /*
691d75faf18SDavid van Moolenbroek * Test mmap on file holes. Holes are a tricky case with the current VM
692d75faf18SDavid van Moolenbroek * implementation. There are two main issues. First, whenever a file data
693d75faf18SDavid van Moolenbroek * block is freed, VM has to know about this, or it will later blindly map in
694d75faf18SDavid van Moolenbroek * the old data. This, file systems explicitly tell VM (through libminixfs)
695d75faf18SDavid van Moolenbroek * whenever a block is freed, upon which VM cache forgets the block. Second,
696d75faf18SDavid van Moolenbroek * blocks are accessed primarily by a <dev,dev_off> pair and only additionally
697d75faf18SDavid van Moolenbroek * by a <ino,ino_off> pair. Holes have no meaningful value for the first pair,
698d75faf18SDavid van Moolenbroek * but do need to be registered in VM with the second pair, or accessing them
699d75faf18SDavid van Moolenbroek * will generate a segmentation fault. Thus, file systems explicitly tell VM
700d75faf18SDavid van Moolenbroek * (through libminixfs) when a hole is being peeked; libminixfs currently fakes
701d75faf18SDavid van Moolenbroek * a device offset to make this work.
702d75faf18SDavid van Moolenbroek */
703d75faf18SDavid van Moolenbroek static void
hole_regression(void)704d75faf18SDavid van Moolenbroek hole_regression(void)
705d75faf18SDavid van Moolenbroek {
706d75faf18SDavid van Moolenbroek struct statvfs st;
707d75faf18SDavid van Moolenbroek size_t block_size;
708d75faf18SDavid van Moolenbroek char *buf;
709d75faf18SDavid van Moolenbroek int fd;
710d75faf18SDavid van Moolenbroek
711d75faf18SDavid van Moolenbroek if (statvfs(".", &st) < 0) e(1);
712d75faf18SDavid van Moolenbroek
713d75faf18SDavid van Moolenbroek block_size = st.f_bsize;
714d75faf18SDavid van Moolenbroek
715d75faf18SDavid van Moolenbroek if ((buf = malloc(block_size)) == NULL) e(2);
716d75faf18SDavid van Moolenbroek
717d75faf18SDavid van Moolenbroek if ((fd = open("testfile", O_CREAT | O_TRUNC | O_RDWR)) < 0) e(3);
718d75faf18SDavid van Moolenbroek
719d75faf18SDavid van Moolenbroek if (unlink("testfile") != 0) e(4);
720d75faf18SDavid van Moolenbroek
721d75faf18SDavid van Moolenbroek /*
722d75faf18SDavid van Moolenbroek * We perform the test twice, in a not-so-perfect attempt to test the
723d75faf18SDavid van Moolenbroek * two aspects independently. The first part immediately creates a
724d75faf18SDavid van Moolenbroek * hole, and is supposed to fail only if reporting holes to VM does not
725d75faf18SDavid van Moolenbroek * work. However, it may also fail if a page for a previous file with
726d75faf18SDavid van Moolenbroek * the same inode number as "testfile" is still in the VM cache.
727d75faf18SDavid van Moolenbroek */
728d75faf18SDavid van Moolenbroek memset(buf, 12, block_size);
729d75faf18SDavid van Moolenbroek
730d75faf18SDavid van Moolenbroek if (write(fd, buf, block_size) != block_size) e(5);
731d75faf18SDavid van Moolenbroek
732d75faf18SDavid van Moolenbroek if (lseek(fd, block_size * 2, SEEK_CUR) != block_size * 3) e(6);
733d75faf18SDavid van Moolenbroek
734d75faf18SDavid van Moolenbroek memset(buf, 78, block_size);
735d75faf18SDavid van Moolenbroek
736d75faf18SDavid van Moolenbroek if (write(fd, buf, block_size) != block_size) e(7);
737d75faf18SDavid van Moolenbroek
738d75faf18SDavid van Moolenbroek free(buf);
739d75faf18SDavid van Moolenbroek
740d75faf18SDavid van Moolenbroek if ((buf = mmap(NULL, 4 * block_size, PROT_READ, MAP_SHARED | MAP_FILE,
741d75faf18SDavid van Moolenbroek fd, 0)) == MAP_FAILED) e(8);
742d75faf18SDavid van Moolenbroek
743d75faf18SDavid van Moolenbroek if (buf[0 * block_size] != 12 || buf[1 * block_size - 1] != 12) e(9);
744d75faf18SDavid van Moolenbroek if (buf[1 * block_size] != 0 || buf[2 * block_size - 1] != 0) e(10);
745d75faf18SDavid van Moolenbroek if (buf[2 * block_size] != 0 || buf[3 * block_size - 1] != 0) e(11);
746d75faf18SDavid van Moolenbroek if (buf[3 * block_size] != 78 || buf[4 * block_size - 1] != 78) e(12);
747d75faf18SDavid van Moolenbroek
748d75faf18SDavid van Moolenbroek if (munmap(buf, 4 * block_size) != 0) e(13);
749d75faf18SDavid van Moolenbroek
750d75faf18SDavid van Moolenbroek /*
751d75faf18SDavid van Moolenbroek * The second part first creates file content and only turns part of it
752d75faf18SDavid van Moolenbroek * into a file hole, thus ensuring that VM has previously cached pages
753d75faf18SDavid van Moolenbroek * for the blocks that are freed. The test will fail if VM keeps the
754d75faf18SDavid van Moolenbroek * pages around in its cache.
755d75faf18SDavid van Moolenbroek */
756d75faf18SDavid van Moolenbroek if ((buf = malloc(block_size)) == NULL) e(14);
757d75faf18SDavid van Moolenbroek
758d75faf18SDavid van Moolenbroek if (lseek(fd, block_size, SEEK_SET) != block_size) e(15);
759d75faf18SDavid van Moolenbroek
760d75faf18SDavid van Moolenbroek memset(buf, 34, block_size);
761d75faf18SDavid van Moolenbroek
762d75faf18SDavid van Moolenbroek if (write(fd, buf, block_size) != block_size) e(16);
763d75faf18SDavid van Moolenbroek
764d75faf18SDavid van Moolenbroek memset(buf, 56, block_size);
765d75faf18SDavid van Moolenbroek
766d75faf18SDavid van Moolenbroek if (write(fd, buf, block_size) != block_size) e(17);
767d75faf18SDavid van Moolenbroek
768d75faf18SDavid van Moolenbroek if (ftruncate(fd, block_size) != 0) e(18);
769d75faf18SDavid van Moolenbroek
770d75faf18SDavid van Moolenbroek if (lseek(fd, block_size * 3, SEEK_SET) != block_size * 3) e(19);
771d75faf18SDavid van Moolenbroek
772d75faf18SDavid van Moolenbroek memset(buf, 78, block_size);
773d75faf18SDavid van Moolenbroek
774d75faf18SDavid van Moolenbroek if (write(fd, buf, block_size) != block_size) e(20);
775d75faf18SDavid van Moolenbroek
776d75faf18SDavid van Moolenbroek free(buf);
777d75faf18SDavid van Moolenbroek
778d75faf18SDavid van Moolenbroek if ((buf = mmap(NULL, 4 * block_size, PROT_READ, MAP_SHARED | MAP_FILE,
779d75faf18SDavid van Moolenbroek fd, 0)) == MAP_FAILED) e(21);
780d75faf18SDavid van Moolenbroek
781d75faf18SDavid van Moolenbroek if (buf[0 * block_size] != 12 || buf[1 * block_size - 1] != 12) e(22);
782d75faf18SDavid van Moolenbroek if (buf[1 * block_size] != 0 || buf[2 * block_size - 1] != 0) e(23);
783d75faf18SDavid van Moolenbroek if (buf[2 * block_size] != 0 || buf[3 * block_size - 1] != 0) e(24);
784d75faf18SDavid van Moolenbroek if (buf[3 * block_size] != 78 || buf[4 * block_size - 1] != 78) e(25);
785d75faf18SDavid van Moolenbroek
786d75faf18SDavid van Moolenbroek if (munmap(buf, 4 * block_size) != 0) e(26);
787d75faf18SDavid van Moolenbroek
788d75faf18SDavid van Moolenbroek close(fd);
789d75faf18SDavid van Moolenbroek }
790d75faf18SDavid van Moolenbroek
79110b7016bSDavid van Moolenbroek /*
79210b7016bSDavid van Moolenbroek * Test that soft faults during file system I/O do not cause functions to
79310b7016bSDavid van Moolenbroek * return partial I/O results.
79410b7016bSDavid van Moolenbroek *
79510b7016bSDavid van Moolenbroek * We refer to the faults that are caused internally within the operating
79610b7016bSDavid van Moolenbroek * system as a result of the deadlock mitigation described at the top of this
79710b7016bSDavid van Moolenbroek * file, as a particular class of "soft faults". Such soft faults may occur in
79810b7016bSDavid van Moolenbroek * the middle of an I/O operation, and general I/O semantics dictate that upon
79910b7016bSDavid van Moolenbroek * partial success, the partial success is returned (and *not* an error). As a
80010b7016bSDavid van Moolenbroek * result, these soft faults, if not handled as special cases, may cause even
80110b7016bSDavid van Moolenbroek * common file system operations such as read(2) on a regular file to return
80210b7016bSDavid van Moolenbroek * fewer bytes than requested. Such unexpected short reads are typically not
80310b7016bSDavid van Moolenbroek * handled well by userland, and the OS must prevent them from occurring if it
80410b7016bSDavid van Moolenbroek * can. Note that read(2) is the most problematic, but certainly not the only,
80510b7016bSDavid van Moolenbroek * case where this problem can occur.
80610b7016bSDavid van Moolenbroek *
80710b7016bSDavid van Moolenbroek * Unfortunately, several file system services are not following the proper
80810b7016bSDavid van Moolenbroek * general I/O semantics - and this includes MFS. Therefore, for now, we have
80910b7016bSDavid van Moolenbroek * to test this case using block device I/O, which does do the right thing.
81010b7016bSDavid van Moolenbroek * In this test we hope that the root file system is mounted on a block device
81110b7016bSDavid van Moolenbroek * usable for (read-only!) testing purposes.
81210b7016bSDavid van Moolenbroek */
81310b7016bSDavid van Moolenbroek static void
softfault_partial(void)81410b7016bSDavid van Moolenbroek softfault_partial(void)
81510b7016bSDavid van Moolenbroek {
81610b7016bSDavid van Moolenbroek struct statvfs stf;
81710b7016bSDavid van Moolenbroek struct stat st;
81810b7016bSDavid van Moolenbroek char *buf, *buf2;
81910b7016bSDavid van Moolenbroek ssize_t size;
82010b7016bSDavid van Moolenbroek int fd;
82110b7016bSDavid van Moolenbroek
82210b7016bSDavid van Moolenbroek if (statvfs("/", &stf) != 0) e(0);
82310b7016bSDavid van Moolenbroek
82410b7016bSDavid van Moolenbroek /*
82510b7016bSDavid van Moolenbroek * If the root file system is not mounted off a block device, or if we
82610b7016bSDavid van Moolenbroek * cannot open that device ourselves, simply skip this subtest.
82710b7016bSDavid van Moolenbroek */
82810b7016bSDavid van Moolenbroek if (stat(stf.f_mntfromname, &st) != 0 || !S_ISBLK(st.st_mode))
82910b7016bSDavid van Moolenbroek return; /* skip subtest */
83010b7016bSDavid van Moolenbroek
83110b7016bSDavid van Moolenbroek if ((fd = open(stf.f_mntfromname, O_RDONLY)) == -1)
83210b7016bSDavid van Moolenbroek return; /* skip subtest */
83310b7016bSDavid van Moolenbroek
83410b7016bSDavid van Moolenbroek /*
83510b7016bSDavid van Moolenbroek * See if we can read in the first two full blocks, or two pages worth
83610b7016bSDavid van Moolenbroek * of data, whichever is larger. If that fails, there is no point in
83710b7016bSDavid van Moolenbroek * continuing the test.
83810b7016bSDavid van Moolenbroek */
83910b7016bSDavid van Moolenbroek size = MAX(stf.f_bsize, PAGE_SIZE) * 2;
84010b7016bSDavid van Moolenbroek
84110b7016bSDavid van Moolenbroek if ((buf = mmap(NULL, size, PROT_READ | PROT_READ,
84210b7016bSDavid van Moolenbroek MAP_ANON | MAP_PRIVATE | MAP_PREALLOC, -1, 0)) == MAP_FAILED) e(0);
84310b7016bSDavid van Moolenbroek
84410b7016bSDavid van Moolenbroek if (read(fd, buf, size) != size) {
84510b7016bSDavid van Moolenbroek munmap(buf, size);
84610b7016bSDavid van Moolenbroek close(fd);
84710b7016bSDavid van Moolenbroek return; /* skip subtest */
84810b7016bSDavid van Moolenbroek }
84910b7016bSDavid van Moolenbroek
85010b7016bSDavid van Moolenbroek lseek(fd, 0, SEEK_SET);
85110b7016bSDavid van Moolenbroek
85210b7016bSDavid van Moolenbroek /*
85310b7016bSDavid van Moolenbroek * Now attempt a read to a partially faulted-in buffer. The first time
85410b7016bSDavid van Moolenbroek * around, the I/O transfer will generate a fault and return partial
85510b7016bSDavid van Moolenbroek * success. In that case, the entire I/O transfer should be retried
85610b7016bSDavid van Moolenbroek * after faulting in the missing page(s), thus resulting in the read
85710b7016bSDavid van Moolenbroek * succeeding in full.
85810b7016bSDavid van Moolenbroek */
85910b7016bSDavid van Moolenbroek if ((buf2 = mmap(NULL, size, PROT_READ | PROT_READ,
86010b7016bSDavid van Moolenbroek MAP_ANON | MAP_PRIVATE, -1, 0)) == MAP_FAILED) e(0);
86110b7016bSDavid van Moolenbroek buf2[0] = '\0'; /* fault in the first page */
86210b7016bSDavid van Moolenbroek
86310b7016bSDavid van Moolenbroek if (read(fd, buf2, size) != size) e(0);
86410b7016bSDavid van Moolenbroek
86510b7016bSDavid van Moolenbroek /* The result should be correct, too. */
86610b7016bSDavid van Moolenbroek if (memcmp(buf, buf2, size)) e(0);
86710b7016bSDavid van Moolenbroek
86810b7016bSDavid van Moolenbroek /* Clean up. */
86910b7016bSDavid van Moolenbroek munmap(buf2, size);
87010b7016bSDavid van Moolenbroek munmap(buf, size);
87110b7016bSDavid van Moolenbroek
87210b7016bSDavid van Moolenbroek close(fd);
87310b7016bSDavid van Moolenbroek }
87410b7016bSDavid van Moolenbroek
875433d6423SLionel Sambuc int
main(int argc,char * argv[])876433d6423SLionel Sambuc main(int argc, char *argv[])
877433d6423SLionel Sambuc {
878e94f856bSDavid van Moolenbroek int i, iter = 2;
879433d6423SLionel Sambuc
880433d6423SLionel Sambuc start(74);
881433d6423SLionel Sambuc
882433d6423SLionel Sambuc basic_regression();
883433d6423SLionel Sambuc
884e321f655SDavid van Moolenbroek nonedev_regression();
885e321f655SDavid van Moolenbroek
886e94f856bSDavid van Moolenbroek /*
887e94f856bSDavid van Moolenbroek * Any inode or block allocation happening concurrently with this
888e94f856bSDavid van Moolenbroek * subtest will make the subtest succeed without testing the actual
889e94f856bSDavid van Moolenbroek * issue. Thus, repeat the subtest a fair number of times.
890e94f856bSDavid van Moolenbroek */
891e94f856bSDavid van Moolenbroek for (i = 0; i < 10; i++)
892e94f856bSDavid van Moolenbroek corruption_regression();
893e94f856bSDavid van Moolenbroek
894d75faf18SDavid van Moolenbroek hole_regression();
895d75faf18SDavid van Moolenbroek
896433d6423SLionel Sambuc test_memory_types_vs_operations();
897433d6423SLionel Sambuc
89810b7016bSDavid van Moolenbroek softfault_partial();
89910b7016bSDavid van Moolenbroek
900433d6423SLionel Sambuc makefiles(MAXFILES);
901433d6423SLionel Sambuc
902433d6423SLionel Sambuc cachequiet(!bigflag);
903433d6423SLionel Sambuc if(bigflag) iter = 3;
904433d6423SLionel Sambuc
905433d6423SLionel Sambuc /* Try various combinations working set sizes
906433d6423SLionel Sambuc * and block sizes in order to specifically
907433d6423SLionel Sambuc * target the primary cache, then primary+secondary
908433d6423SLionel Sambuc * cache, then primary+secondary cache+secondary
909433d6423SLionel Sambuc * cache eviction.
910433d6423SLionel Sambuc */
911433d6423SLionel Sambuc
912433d6423SLionel Sambuc if(dotest(PAGE_SIZE, 100, iter)) e(5);
913433d6423SLionel Sambuc if(dotest(PAGE_SIZE*2, 100, iter)) e(2);
914433d6423SLionel Sambuc if(dotest(PAGE_SIZE*3, 100, iter)) e(3);
915433d6423SLionel Sambuc if(dotest(PAGE_SIZE, 20000, iter)) e(5);
916433d6423SLionel Sambuc
917433d6423SLionel Sambuc if(bigflag) {
918433d6423SLionel Sambuc u32_t totalmem, freemem, cachedmem;
919433d6423SLionel Sambuc if(dotest(PAGE_SIZE, 150000, iter)) e(5);
920433d6423SLionel Sambuc getmem(&totalmem, &freemem, &cachedmem);
921433d6423SLionel Sambuc if(dotest(PAGE_SIZE, totalmem*1.5, iter)) e(6);
922433d6423SLionel Sambuc }
923433d6423SLionel Sambuc
924433d6423SLionel Sambuc quit();
925433d6423SLionel Sambuc
926433d6423SLionel Sambuc return 0;
927433d6423SLionel Sambuc }
928433d6423SLionel Sambuc
929