Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions src/lib/libdev.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/**
* @license
* Copyright 2013 The Emscripten Authors
* SPDX-License-Identifier: MIT
*/

addToLibrary({
$DEV__deps: [
'$FS',
'$ERRNO_CODES',
#if ENVIRONMENT_MAY_BE_NODE
'$nodeTTY',
'$nodeFsync',
#endif
],
$DEV: {
readWriteHelper: (stream, cb, method) => {
try {
var nbytes = cb();
} catch (e) {
// Convert Node errors into ErrnoError
if (e && e.code && ERRNO_CODES[e.code]) {
throw new FS.ErrnoError(ERRNO_CODES[e.code]);
}
if (e?.errno) {
// propagate errno
throw e;
}
// Other errors converted to EIO.
#if ASSERTIONS
console.error(`Error thrown in ${method}:`);
console.error(e);
#endif
throw new FS.ErrnoError({{{ cDefs.EIO }}});
}
if (nbytes === undefined) {
// Prevent an infinite loop caused by incorrect code that doesn't return a
// value
// Maybe we should set nbytes = buffer.length here instead?
#if ASSERTIONS
console.warn(
`${method} returned undefined; a correct implementation must return a number`,
);
#endif
throw new FS.ErrnoError({{{ cDefs.EIO }}});
}
if (nbytes !== 0) {
stream.node.timestamp = Date.now();
}
return nbytes;
},
devs: [],
register(dev, ops) {
DEV.devs[dev] = ops;
FS.registerDevice(dev, DEV.stream_ops);
},
TTY_OPS: {
ioctl_tiocgwinsz(tty) {
const { rows = 24, columns = 80 } = tty.devops.getTerminalSize?.() ?? {};
return [rows, columns];
},
},
stream_ops: {
open(stream) {
var devops = DEV.devs[stream.node.rdev];
if (!devops) {
throw new FS.ErrnoError({{{ cDefs.ENODEV }}});
}
stream.devops = devops;
stream.seekable = false;
stream.tty =
stream.devops.tty ??
(stream.devops.isatty
? {
ops: DEV.TTY_OPS,
devops,
}
: undefined);
devops.open?.(stream);
},
close(stream) {
// flush any pending line data
stream.stream_ops.fsync(stream);
},
fsync(stream) {
stream.devops.fsync?.(stream.devops);
},
read: function (stream, buffer, offset, length, pos /* ignored */) {
buffer = buffer.subarray(offset, offset + length);
return DEV.readWriteHelper(stream, () => stream.devops.read(stream.devops, buffer), "read");
},
write: function (stream, buffer, offset, length, pos /* ignored */) {
buffer = buffer.subarray(offset, offset + length);
return DEV.readWriteHelper(stream, () => stream.devops.write(stream.devops, buffer), "write");
},
},
#if ENVIRONMENT_MAY_BE_NODE
nodeInputDevice: (nodeStream) => ({
isatty: nodeTTY.isatty(nodeStream.fd),
fsync() {
nodeFsync(nodeStream.fd);
},
read(ops, buffer) {
return fs.readSync(nodeStream.fd, buffer, 0, buffer.length);
},
}),
nodeOutputDevice: (nodeStream) => ({
isatty: nodeTTY.isatty(nodeStream.fd),
fsync() {
nodeFsync(nodeStream.fd);
},
write(ops, buffer) {
return fs.writeSync(nodeStream.fd, buffer, 0, buffer.length);
},
getTerminalSize() {
return nodeStream;
}
}),
#endif
},
#if ENVIRONMENT_MAY_BE_NODE
$nodeTTY: "require('node:tty');",
$nodeFsync: (fd) => {
try {
fs.fsyncSync(fd);
} catch (e) {
if (e?.code === "EINVAL") {
return;
}
// On Mac, calling fsync when not isatty returns ENOTSUP
// On Windows, stdin/stdout/stderr may be closed, returning EBADF or EPERM
const isStdStream = fd === 0 || fd === 1 || fd === 2;
if (
isStdStream &&
(e?.code === "ENOTSUP" || e?.code === "EBADF" || e?.code === "EPERM")
) {
return;
}

throw e;
}
}
#endif
});
29 changes: 26 additions & 3 deletions src/lib/libfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -1465,17 +1465,40 @@ FS.staticInit();`;
// them instead.
if (input) {
FS.createDevice('/dev', 'stdin', input);
} else {
} else
#if ENVIRONMENT_MAY_BE_NODE
if (ENVIRONMENT_IS_NODE) {
DEV.register(FS.makedev(7, 0), DEV.nodeInputDevice(process.stdin));
FS.mkdev('/dev/stdin', FS.makedev(7, 0));
} else
#endif
{
FS.symlink('/dev/tty', '/dev/stdin');
}

if (output) {
FS.createDevice('/dev', 'stdout', null, output);
} else {
} else
#if ENVIRONMENT_MAY_BE_NODE
if (ENVIRONMENT_IS_NODE) {
DEV.register(FS.makedev(7, 1), DEV.nodeOutputDevice(process.stdout));
FS.mkdev('/dev/stdout', FS.makedev(7, 1));
} else
#endif
{
FS.symlink('/dev/tty', '/dev/stdout');
}

if (error) {
FS.createDevice('/dev', 'stderr', null, error);
} else {
} else
#if ENVIRONMENT_MAY_BE_NODE
if (ENVIRONMENT_IS_NODE) {
DEV.register(FS.makedev(7, 2), DEV.nodeOutputDevice(process.stderr));
FS.mkdev('/dev/stderr', FS.makedev(7, 2));
} else
#endif
{
FS.symlink('/dev/tty1', '/dev/stderr');
}

Expand Down
117 changes: 33 additions & 84 deletions src/lib/libtty.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,99 +6,48 @@

addToLibrary({
$TTY__deps: [
'$DEV',
'$FS',
'$UTF8ArrayToString',
'$FS_stdin_getChar'
'$FS_stdin_getChar',
],
#if !MINIMAL_RUNTIME
$TTY__postset: () => {
addAtInit('TTY.init();');
addAtExit('TTY.shutdown();');
},
#endif
$TTY: {
ttys: [],
init() {
// https://github.com/emscripten-core/emscripten/pull/1555
// if (ENVIRONMENT_IS_NODE) {
// // currently, FS.init does not distinguish if process.stdin is a file or TTY
// // device, it always assumes it's a TTY device. because of this, we're forcing
// // process.stdin to UTF8 encoding to at least make stdin reading compatible
// // with text files until FS.init can be refactored.
// process.stdin.setEncoding('utf8');
// }
},
shutdown() {
// https://github.com/emscripten-core/emscripten/pull/1555
// if (ENVIRONMENT_IS_NODE) {
// // inolen: any idea as to why node -e 'process.stdin.read()' wouldn't exit immediately (with process.stdin being a tty)?
// // isaacs: because now it's reading from the stream, you've expressed interest in it, so that read() kicks off a _read() which creates a ReadReq operation
// // inolen: I thought read() in that case was a synchronous operation that just grabbed some amount of buffered data if it exists?
// // isaacs: it is. but it also triggers a _read() call, which calls readStart() on the handle
// // isaacs: do process.stdin.pause() and i'd think it'd probably close the pending call
// process.stdin.pause();
// }
},
ttys: {},
register(dev, ops) {
TTY.ttys[dev] = { input: [], output: [], ops: ops };
FS.registerDevice(dev, TTY.stream_ops);
},
stream_ops: {
open(stream) {
var tty = TTY.ttys[stream.node.rdev];
if (!tty) {
throw new FS.ErrnoError({{{ cDefs.ENODEV }}});
}
stream.tty = tty;
stream.seekable = false;
},
close(stream) {
// flush any pending line data
stream.tty.ops.fsync(stream.tty);
},
fsync(stream) {
stream.tty.ops.fsync(stream.tty);
},
read(stream, buffer, offset, length, pos /* ignored */) {
if (!stream.tty || !stream.tty.ops.get_char) {
throw new FS.ErrnoError({{{ cDefs.ENXIO }}});
}
var bytesRead = 0;
for (var i = 0; i < length; i++) {
var result;
try {
result = stream.tty.ops.get_char(stream.tty);
} catch (e) {
throw new FS.ErrnoError({{{ cDefs.EIO }}});
const tty = { input: [], output: [], ops };
TTY.ttys[dev] = tty;
const devops = {
tty,
write(devops, buffer) {
if (!ops.put_char) {
throw new FS.ErrnoError({{{ cDefs.ENXIO }}});
}
if (result === undefined && bytesRead === 0) {
throw new FS.ErrnoError({{{ cDefs.EAGAIN }}});
for (var i = 0; i < buffer.length; i++) {
ops.put_char(tty, buffer[i]);
}
if (result === null || result === undefined) break;
bytesRead++;
buffer[offset+i] = result;
}
if (bytesRead) {
stream.node.atime = Date.now();
}
return bytesRead;
},
write(stream, buffer, offset, length, pos) {
if (!stream.tty || !stream.tty.ops.put_char) {
throw new FS.ErrnoError({{{ cDefs.ENXIO }}});
}
try {
for (var i = 0; i < length; i++) {
stream.tty.ops.put_char(stream.tty, buffer[offset+i]);
return i;
},
read(devops, buffer) {
if (!ops.get_char) {
throw new FS.ErrnoError({{{ cDefs.ENXIO }}});
}
} catch (e) {
throw new FS.ErrnoError({{{ cDefs.EIO }}});
}
if (length) {
stream.node.mtime = stream.node.ctime = Date.now();
}
return i;
var bytesRead = 0;
for (var i = 0; i < buffer.length; i++) {
var result = ops.get_char(tty);
if (result === undefined && bytesRead === 0) {
throw new FS.ErrnoError({{{ cDefs.EAGAIN }}});
}
if (result === null || result === undefined) break;
bytesRead++;
buffer[i] = result;
}
return bytesRead;
},
};
if (ops.fsync) {
devops.fsync = (devops) => ops.fsync(tty)
}
DEV.register(dev, devops);
},
default_tty_ops: {
get_char(tty) {
Expand Down
1 change: 1 addition & 0 deletions src/modules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ function calculateLibraries() {
'libfs.js',
'libmemfs.js',
'libtty.js',
'libdev.js',
'libpipefs.js', // ok to include it by default since it's only used if the syscall is used
'libsockfs.js', // ok to include it by default since it's only used if the syscall is used
);
Expand Down
8 changes: 4 additions & 4 deletions test/codesize/test_codesize_cxx_ctors1.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"a.out.js": 19194,
"a.out.js.gz": 7969,
"a.out.js": 21541,
"a.out.js.gz": 9245,
"a.out.nodebug.wasm": 132638,
"a.out.nodebug.wasm.gz": 49927,
"total": 151832,
"total_gz": 57896,
"total": 154179,
"total_gz": 59172,
"sent": [
"__cxa_throw",
"_abort_js",
Expand Down
8 changes: 4 additions & 4 deletions test/codesize/test_codesize_cxx_ctors2.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"a.out.js": 19171,
"a.out.js.gz": 7957,
"a.out.js": 21518,
"a.out.js.gz": 9227,
"a.out.nodebug.wasm": 132064,
"a.out.nodebug.wasm.gz": 49586,
"total": 151235,
"total_gz": 57543,
"total": 153582,
"total_gz": 58813,
"sent": [
"__cxa_throw",
"_abort_js",
Expand Down
8 changes: 4 additions & 4 deletions test/codesize/test_codesize_cxx_except.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"a.out.js": 23174,
"a.out.js.gz": 8960,
"a.out.js": 25523,
"a.out.js.gz": 10247,
"a.out.nodebug.wasm": 172516,
"a.out.nodebug.wasm.gz": 57438,
"total": 195690,
"total_gz": 66398,
"total": 198039,
"total_gz": 67685,
"sent": [
"__cxa_begin_catch",
"__cxa_end_catch",
Expand Down
8 changes: 4 additions & 4 deletions test/codesize/test_codesize_cxx_except_wasm.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"a.out.js": 19026,
"a.out.js.gz": 7904,
"a.out.js": 21374,
"a.out.js.gz": 9183,
"a.out.nodebug.wasm": 147922,
"a.out.nodebug.wasm.gz": 55312,
"total": 166948,
"total_gz": 63216,
"total": 169296,
"total_gz": 64495,
"sent": [
"_abort_js",
"_tzset_js",
Expand Down
Loading
Loading