Skip to content

Commit eb366fc

Browse files
committed
Allow writing to MEMFS through mmap by implementing msync
1 parent c272112 commit eb366fc

File tree

6 files changed

+175
-3
lines changed

6 files changed

+175
-3
lines changed

src/library.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -2568,7 +2568,7 @@ LibraryManager.library = {
25682568
}
25692569
}
25702570

2571-
_mmap.mappings[ptr] = { malloc: ptr, num: num, allocated: allocated };
2571+
_mmap.mappings[ptr] = { malloc: ptr, num: num, allocated: allocated, fd: fd, flags: flags };
25722572
return ptr;
25732573
},
25742574

@@ -2578,6 +2578,13 @@ LibraryManager.library = {
25782578
var info = _mmap.mappings[start];
25792579
if (!info) return 0;
25802580
if (num == info.num) {
2581+
// At the Linux man page, it says:
2582+
// "The file may not actually be updated until msync(2) or munmap(2) are called."
2583+
// I guess that means we need to call msync when doing munmap
2584+
_msync(start, num); // todo: which flags?
2585+
2586+
FS.munmap(FS.getStream(info.fd));
2587+
25812588
_mmap.mappings[start] = null;
25822589
if (info.allocated) {
25832590
_free(info.malloc);
@@ -2598,7 +2605,14 @@ LibraryManager.library = {
25982605
msync: function(addr, len, flags) {
25992606
// int msync(void *addr, size_t len, int flags);
26002607
// http://pubs.opengroup.org/onlinepubs/009696799/functions/msync.html
2601-
// Pretend to succeed
2608+
// TODO: support sync'ing parts of allocations
2609+
var info = _mmap.mappings[addr];
2610+
if (!info) return 0;
2611+
if (len == info.num) {
2612+
var buffer = new Uint8Array(HEAPU8.buffer, addr, len);
2613+
return FS.msync(FS.getStream(info.fd), buffer, 0, len, info.flags);
2614+
}
2615+
26022616
return 0;
26032617
},
26042618

src/library_fs.js

+9
Original file line numberDiff line numberDiff line change
@@ -1154,6 +1154,15 @@ mergeInto(LibraryManager.library, {
11541154
}
11551155
return stream.stream_ops.mmap(stream, buffer, offset, length, position, prot, flags);
11561156
},
1157+
msync: function(stream, buffer, offset, length, mmapFlags) {
1158+
if (!stream.stream_ops.msync) {
1159+
return 0;
1160+
}
1161+
return stream.stream_ops.msync(stream, buffer, offset, length, mmapFlags);
1162+
},
1163+
munmap: function(stream) {
1164+
return 0;
1165+
},
11571166
ioctl: function(stream, cmd, arg) {
11581167
if (!stream.stream_ops.ioctl) {
11591168
throw new FS.ErrnoError(ERRNO_CODES.ENOTTY);

src/library_memfs.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ mergeInto(LibraryManager.library, {
3838
read: MEMFS.stream_ops.read,
3939
write: MEMFS.stream_ops.write,
4040
allocate: MEMFS.stream_ops.allocate,
41-
mmap: MEMFS.stream_ops.mmap
41+
mmap: MEMFS.stream_ops.mmap,
42+
msync: MEMFS.stream_ops.msync
4243
}
4344
},
4445
link: {
@@ -373,6 +374,19 @@ mergeInto(LibraryManager.library, {
373374
}
374375
return { ptr: ptr, allocated: allocated };
375376
},
377+
msync: function(stream, buffer, offset, length, mmapFlags) {
378+
if (!FS.isFile(stream.node.mode)) {
379+
throw new FS.ErrnoError(ERRNO_CODES.ENODEV);
380+
}
381+
if (mmapFlags & {{{ cDefine('MAP_PRIVATE') }}}) {
382+
// MAP_PRIVATE calls need not to be synced back to underlying fs
383+
return 0;
384+
}
385+
386+
var bytesWritten = MEMFS.stream_ops.write(stream, buffer, 0, length, offset, false);
387+
// should we check if bytesWritten and length are the same?
388+
return 0;
389+
}
376390
}
377391
}
378392
});

tests/fs/test_mmap.c

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#include <stdio.h>
2+
#include <sys/mman.h>
3+
#include <emscripten.h>
4+
#include <string.h>
5+
#include <assert.h>
6+
#include <stdlib.h>
7+
#include <fcntl.h>
8+
#include <sys/types.h>
9+
#include <sys/stat.h>
10+
#include <unistd.h>
11+
#include <sys/io.h>
12+
#include <sys/mman.h>
13+
14+
int main() {
15+
EM_ASM(
16+
FS.mkdir('yolo');
17+
FS.writeFile('/yolo/in.txt', 'mmap ftw!');
18+
);
19+
20+
// Use mmap to read in.txt
21+
{
22+
const char* path = "/yolo/in.txt";
23+
int fd = open(path, O_RDONLY);
24+
assert(fd != -1);
25+
26+
int filesize = 9;
27+
char* map = (char*)mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fd, 0);
28+
assert(map != MAP_FAILED);
29+
30+
printf("/yolo/in.txt content=");
31+
for (int i = 0; i < filesize; i++) {
32+
printf("%c", map[i]);
33+
}
34+
printf("\n");
35+
36+
int rc = munmap(map, filesize);
37+
assert(rc == 0);
38+
39+
close(fd);
40+
}
41+
42+
// Use mmap to write out.txt
43+
{
44+
const char* text = "written mmap";
45+
const char* path = "/yolo/out.txt";
46+
47+
int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, (mode_t)0600);
48+
assert(fd != -1);
49+
50+
size_t textsize = strlen(text) + 1; // + \0 null character
51+
assert(lseek(fd, textsize - 1, SEEK_SET) != -1);
52+
53+
// need to write something first to allow us to mmap
54+
assert(write(fd, "", 1) != -1);
55+
56+
char *map = (char*)mmap(0, textsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
57+
assert(map != MAP_FAILED);
58+
59+
for (size_t i = 0; i < textsize; i++) {
60+
map[i] = text[i];
61+
}
62+
63+
assert(msync(map, textsize, MS_SYNC) != -1);
64+
65+
assert(munmap(map, textsize) != -1);
66+
67+
close(fd);
68+
}
69+
70+
{
71+
FILE* fd = fopen("/yolo/out.txt", "r");
72+
if (fd == NULL) {
73+
printf("failed to open /yolo/out.txt\n");
74+
return 1;
75+
}
76+
char buffer[13];
77+
fread(buffer, 1, 14, fd);
78+
printf("/yolo/out.txt content=%s\n", buffer);
79+
fclose(fd);
80+
}
81+
82+
// MAP_PRIVATE
83+
{
84+
const char* text = "written mmap";
85+
const char* path = "/yolo/private.txt";
86+
87+
int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, (mode_t)0600);
88+
assert(fd != -1);
89+
90+
size_t textsize = strlen(text) + 1; // + \0 null character
91+
assert(lseek(fd, textsize - 1, SEEK_SET) != -1);
92+
93+
// need to write something first to allow us to mmap
94+
assert(write(fd, "", 1) != -1);
95+
96+
char *map = (char*)mmap(0, textsize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
97+
assert(map != MAP_FAILED);
98+
99+
for (size_t i = 0; i < textsize; i++) {
100+
map[i] = text[i];
101+
}
102+
103+
assert(msync(map, textsize, MS_SYNC) != -1);
104+
105+
assert(munmap(map, textsize) != -1);
106+
107+
close(fd);
108+
}
109+
110+
{
111+
FILE* fd = fopen("/yolo/private.txt", "r");
112+
if (fd == NULL) {
113+
printf("failed to open /yolo/private.txt\n");
114+
return 1;
115+
}
116+
char buffer[13];
117+
fread(buffer, 1, 14, fd);
118+
printf("/yolo/private.txt content=%s\n", buffer);
119+
fclose(fd);
120+
}
121+
122+
return 0;
123+
}

tests/fs/test_mmap.out

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/yolo/in.txt content=mmap ftw!
2+
/yolo/out.txt content=written mmap
3+
/yolo/private.txt content=

tests/test_core.py

+9
Original file line numberDiff line numberDiff line change
@@ -4698,6 +4698,15 @@ def test_fs_append(self):
46984698
src = open(path_from_root('tests', 'fs', 'test_append.c'), 'r').read()
46994699
self.do_run(src, 'success', force_c=True)
47004700

4701+
def test_fs_mmap(self):
4702+
if self.emcc_args is None: return self.skip('requires emcc')
4703+
orig_compiler_opts = Building.COMPILER_TEST_OPTS[:]
4704+
for fs in ['MEMFS']:
4705+
src = path_from_root('tests', 'fs', 'test_mmap.c')
4706+
out = path_from_root('tests', 'fs', 'test_mmap.out')
4707+
Building.COMPILER_TEST_OPTS = orig_compiler_opts + ['-D' + fs]
4708+
self.do_run_from_file(src, out)
4709+
47014710
def test_unistd_access(self):
47024711
self.clear()
47034712
if not self.is_emscripten_abi(): return self.skip('asmjs-unknown-emscripten needed for inline js')

0 commit comments

Comments
 (0)