Skip to content

Commit 8fba427

Browse files
authored
WasmFS: Support reading from stdin (#19699)
This moves the stdin getChar() function into a shared location and then calls it from the old and new FSes. The moved code is unchanged except for a closure suppression which somehow became necessary after the movement (which is odd).
1 parent 412fd75 commit 8fba427

17 files changed

+127
-57
lines changed

src/library_fs_shared.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,73 @@ mergeInto(LibraryManager.library, {
102102
if (canWrite) mode |= {{{ cDefs.S_IWUGO }}};
103103
return mode;
104104
},
105+
106+
$FS_stdin_getChar_buffer: [],
107+
108+
// getChar has 3 particular return values:
109+
// a.) the next character represented as an integer
110+
// b.) undefined to signal that no data is currently available
111+
// c.) null to signal an EOF
112+
$FS_stdin_getChar__deps: [
113+
'$FS_stdin_getChar_buffer',
114+
'$intArrayFromString',
115+
],
116+
$FS_stdin_getChar: () => {
117+
if (!FS_stdin_getChar_buffer.length) {
118+
var result = null;
119+
#if ENVIRONMENT_MAY_BE_NODE
120+
if (ENVIRONMENT_IS_NODE) {
121+
// we will read data by chunks of BUFSIZE
122+
var BUFSIZE = 256;
123+
var buf = Buffer.alloc(BUFSIZE);
124+
var bytesRead = 0;
125+
126+
// For some reason we must suppress a closure warning here, even though
127+
// fd definitely exists on process.stdin, and is even the proper way to
128+
// get the fd of stdin,
129+
// https://github.com/nodejs/help/issues/2136#issuecomment-523649904
130+
// This started to happen after moving this logic out of library_tty.js,
131+
// so it is related to the surrounding code in some unclear manner.
132+
/** @suppress {missingProperties} */
133+
var fd = process.stdin.fd;
134+
135+
try {
136+
bytesRead = fs.readSync(fd, buf, 0, BUFSIZE, -1);
137+
} catch(e) {
138+
// Cross-platform differences: on Windows, reading EOF throws an exception, but on other OSes,
139+
// reading EOF returns 0. Uniformize behavior by treating the EOF exception to return 0.
140+
if (e.toString().includes('EOF')) bytesRead = 0;
141+
else throw e;
142+
}
143+
144+
if (bytesRead > 0) {
145+
result = buf.slice(0, bytesRead).toString('utf-8');
146+
} else {
147+
result = null;
148+
}
149+
} else
150+
#endif
151+
if (typeof window != 'undefined' &&
152+
typeof window.prompt == 'function') {
153+
// Browser.
154+
result = window.prompt('Input: '); // returns null on cancel
155+
if (result !== null) {
156+
result += '\n';
157+
}
158+
} else if (typeof readline == 'function') {
159+
// Command line.
160+
result = readline();
161+
if (result !== null) {
162+
result += '\n';
163+
}
164+
}
165+
if (!result) {
166+
return null;
167+
}
168+
FS_stdin_getChar_buffer = intArrayFromString(result, true);
169+
}
170+
return FS_stdin_getChar_buffer.shift();
171+
},
105172
});
106173

107174
// Normally only the FS things that the compiler sees are needed are included.

src/library_sigs.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ sigs = {
414414
_wasmfs_opfs_set_size_access__sig: 'vpijp',
415415
_wasmfs_opfs_set_size_file__sig: 'vpijp',
416416
_wasmfs_opfs_write_access__sig: 'iipii',
417+
_wasmfs_stdin_get_char__sig: 'i',
417418
_wasmfs_thread_utils_heartbeat__sig: 'vp',
418419
abort__sig: 'v',
419420
alBuffer3f__sig: 'viifff',

src/library_tty.js

Lines changed: 6 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
*/
66

77
mergeInto(LibraryManager.library, {
8-
$TTY__deps: ['$FS', '$intArrayFromString', '$UTF8ArrayToString'],
8+
$TTY__deps: [
9+
'$FS',
10+
'$UTF8ArrayToString',
11+
'$FS_stdin_getChar'
12+
],
913
#if !MINIMAL_RUNTIME
1014
$TTY__postset: function() {
1115
addAtInit('TTY.init();');
@@ -97,56 +101,8 @@ mergeInto(LibraryManager.library, {
97101
}
98102
},
99103
default_tty_ops: {
100-
// get_char has 3 particular return values:
101-
// a.) the next character represented as an integer
102-
// b.) undefined to signal that no data is currently available
103-
// c.) null to signal an EOF
104104
get_char: function(tty) {
105-
if (!tty.input.length) {
106-
var result = null;
107-
#if ENVIRONMENT_MAY_BE_NODE
108-
if (ENVIRONMENT_IS_NODE) {
109-
// we will read data by chunks of BUFSIZE
110-
var BUFSIZE = 256;
111-
var buf = Buffer.alloc(BUFSIZE);
112-
var bytesRead = 0;
113-
114-
try {
115-
bytesRead = fs.readSync(process.stdin.fd, buf, 0, BUFSIZE, -1);
116-
} catch(e) {
117-
// Cross-platform differences: on Windows, reading EOF throws an exception, but on other OSes,
118-
// reading EOF returns 0. Uniformize behavior by treating the EOF exception to return 0.
119-
if (e.toString().includes('EOF')) bytesRead = 0;
120-
else throw e;
121-
}
122-
123-
if (bytesRead > 0) {
124-
result = buf.slice(0, bytesRead).toString('utf-8');
125-
} else {
126-
result = null;
127-
}
128-
} else
129-
#endif
130-
if (typeof window != 'undefined' &&
131-
typeof window.prompt == 'function') {
132-
// Browser.
133-
result = window.prompt('Input: '); // returns null on cancel
134-
if (result !== null) {
135-
result += '\n';
136-
}
137-
} else if (typeof readline == 'function') {
138-
// Command line.
139-
result = readline();
140-
if (result !== null) {
141-
result += '\n';
142-
}
143-
}
144-
if (!result) {
145-
return null;
146-
}
147-
tty.input = intArrayFromString(result, true);
148-
}
149-
return tty.input.shift();
105+
return FS_stdin_getChar();
150106
},
151107
put_char: function(tty, val) {
152108
if (val === null || val === {{{ charCode('\n') }}}) {

src/library_wasmfs.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,4 +427,14 @@ FS.createPreloadedFile = FS_createPreloadedFile;
427427
}
428428
}, 50);
429429
},
430+
431+
_wasmfs_stdin_get_char__deps: ['$FS_stdin_getChar'],
432+
_wasmfs_stdin_get_char: () => {
433+
// Return the read character, or -1 to indicate EOF.
434+
var c = FS_stdin_getChar();
435+
if (typeof c === 'number') {
436+
return c;
437+
}
438+
return -1;
439+
}
430440
});

system/lib/standalone/standalone.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,26 @@ void emscripten_out(const char* text) { wasi_writeln(1, text); }
217217

218218
void emscripten_err(const char* text) { wasi_writeln(2, text); }
219219

220+
__attribute__((import_module("wasi_snapshot_preview1"),
221+
import_name("fd_read"))) __wasi_errno_t
222+
imported__wasi_fd_read(__wasi_fd_t fd,
223+
const __wasi_ciovec_t* iovs,
224+
size_t iovs_len,
225+
__wasi_size_t* nread);
226+
227+
int _wasmfs_stdin_get_char(void) {
228+
char c;
229+
struct __wasi_ciovec_t iov;
230+
iov.buf = (uint8_t*)&c;
231+
iov.buf_len = 1;
232+
__wasi_size_t nread;
233+
imported__wasi_fd_read(0, &iov, 1, &nread);
234+
if (nread == 0) {
235+
return -1;
236+
}
237+
return c;
238+
}
239+
220240
// In the non-standalone build we define this helper function in JS to avoid
221241
// signture mismatch issues.
222242
// See: https://github.com/emscripten-core/posixtestsuite/issues/6

system/lib/wasmfs/special_files.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <wasi/api.h>
1313

1414
#include "special_files.h"
15+
#include "wasmfs_internal.h"
1516

1617
namespace wasmfs::SpecialFiles {
1718

@@ -45,8 +46,15 @@ class StdinFile : public DataFile {
4546
}
4647

4748
ssize_t read(uint8_t* buf, size_t len, off_t offset) override {
48-
// TODO: Implement reading from stdin.
49-
abort();
49+
for (size_t i = 0; i < len; i++) {
50+
auto c = _wasmfs_stdin_get_char();
51+
if (c < 0) {
52+
// No more input can be read, return what we did read.
53+
return i;
54+
}
55+
buf[i] = c;
56+
}
57+
return len;
5058
};
5159

5260
int flush() override { return 0; }

system/lib/wasmfs/wasmfs_internal.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ void _wasmfs_get_preloaded_child_path(int index, char* childName);
1818
size_t _wasmfs_get_preloaded_file_size(uint32_t index);
1919
void _wasmfs_copy_preloaded_file_data(uint32_t index, uint8_t* data);
2020

21+
// Returns the next character from stdin, or -1 on EOF.
22+
int _wasmfs_stdin_get_char(void);
23+
2124
#ifdef __cplusplus
2225
}
2326
#endif

test/other/metadce/test_metadce_cxx_wasmfs.imports

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ env._wasmfs_get_preloaded_file_mode
88
env._wasmfs_get_preloaded_file_size
99
env._wasmfs_get_preloaded_parent_path
1010
env._wasmfs_get_preloaded_path_name
11+
env._wasmfs_stdin_get_char
1112
env.abort
1213
env.emscripten_console_error
1314
env.emscripten_date_now
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
12355
1+
12922

test/other/metadce/test_metadce_cxx_wasmfs.sent

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ _wasmfs_get_preloaded_file_mode
88
_wasmfs_get_preloaded_file_size
99
_wasmfs_get_preloaded_parent_path
1010
_wasmfs_get_preloaded_path_name
11+
_wasmfs_stdin_get_char
1112
abort
1213
emscripten_console_error
1314
emscripten_date_now
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
164439
1+
164531
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
r
21
s
32
t
43
u
4+
v

test/other/metadce/test_metadce_files_wasmfs.imports

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ a.n
1515
a.o
1616
a.p
1717
a.q
18+
a.r
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
6750
1+
7314

test/other/metadce/test_metadce_files_wasmfs.sent

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ n
1515
o
1616
p
1717
q
18+
r
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
52397
1+
52463

test/test_other.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,6 +1480,7 @@ def test_export_all_and_exported_functions(self):
14801480
self.emcc('lib.c', ['-sEXPORTED_FUNCTIONS=_libfunc2', '-sEXPORT_ALL', '--pre-js', 'main.js'], output_filename='a.out.js')
14811481
self.assertContained('libfunc\n', self.run_js('a.out.js'))
14821482

1483+
@also_with_wasmfs
14831484
@crossplatform
14841485
def test_stdin(self):
14851486
def run_test():

0 commit comments

Comments
 (0)