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
4 changes: 2 additions & 2 deletions src/lib/libtty.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ addToLibrary({
},
close(stream) {
// flush any pending line data
stream.tty.ops.fsync(stream.tty);
stream.tty.ops.fsync?.(stream.tty);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this change also make the fsync operation optional for tty objects? Or was it always optional, but not tested?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't optional but in my opinion it should be.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you mention this in the PR description?

},
fsync(stream) {
stream.tty.ops.fsync(stream.tty);
stream.tty.ops.fsync?.(stream.tty);
},
read(stream, buffer, offset, length, pos /* ignored */) {
if (!stream.tty || !stream.tty.ops.get_char) {
Expand Down
209 changes: 209 additions & 0 deletions test/other/libtty.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
#include <assert.h>
#include <emscripten.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>

EM_JS_DEPS(main, "$TTY");

// clang-format off
EM_JS(void, init, (void), {
var major = 100;
var tty_ops = {
get_char: function (tty) {
if (tty.input.length > 0) {
return tty.input.shift();
}
return undefined;
},
put_char: function (tty, val) {
if (val !== 0 && val !== 10) {
tty.output.push(val);
}
},
fsync: function (tty) {
console.log('fsync called');
tty.output = [];
},
ioctl_tcgets: function (tty) {
return {
c_iflag: 0,
c_oflag: 0,
c_cflag: 0,
c_lflag: 0,
c_cc: new Array(32).fill(0),
};
},
ioctl_tcsets: function (tty, optional_actions, data) {
return 0;
},
ioctl_tiocgwinsz: function (tty) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do wonder if this unterface (get_char, put_char, ioctl_tcgets, ioctl_tcsets, ioctl_tiocgwinsz) is actually the right one, but I suppose its what we have today so putting some testing in place is probably a good thing!

Also, as always with FS changes, it begs the question if whether we should be working on old FS changes to focusing on WasmFS going forward?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_char and put_char is definitely terrible. I don't have as strong of an opinion on the others but it's certainly not clear they are a good interface. If we do change any of this behavior, we can update the test and that will clarify what behavior changes are happening.

we should be working on old FS changes to focusing on WasmFS going forward?

I'm still using the old FS extensively and the migration is going to take a long time. For instance, I'll need to repeat the project to get the CPython test suite happy with old FS on WasmFS.
python/cpython#127146
Then there is the issue of all my custom FS code that has to be ported, and lots of small differences of behaviors to be understood and papered over or documented, and the fact that different file systems are supported between WasmFS and old FS so we need to understand who is using the file systems that are not supported and what their migration path will be.

So in the meantime for me it is certainly worth improving the old FS. Not sure about for you.

return [25, 80];
},
};
var device = FS.makedev(major, 0);
TTY.register(device, tty_ops);
FS.mkdev('/custom_tty', device);
// Populate the TTY input buffer with test data "ABCD"
TTY.ttys[device].input = [65, 66, 67, 68];

// TTY without get_char - should cause ENXIO on read
var tty_no_getchar = {put_char: function (tty, val) {}};
var device_no_getchar = FS.makedev(major + 1, 0);
TTY.register(device_no_getchar, tty_no_getchar);
FS.mkdev('/tty_no_getchar', device_no_getchar);

// TTY without put_char - should cause ENXIO on write
var tty_no_putchar = {
get_char: function (tty) {
return 0;
},
};
var device_no_putchar = FS.makedev(major + 2, 0);
TTY.register(device_no_putchar, tty_no_putchar);
FS.mkdev('/tty_no_putchar', device_no_putchar);

// TTY with throwing get_char - should cause EIO on read
var tty_throw_getchar = {
get_char: function (tty) {
throw new Error('get_char error');
},
put_char: function (tty, val) {},
};
var device_throw_getchar = FS.makedev(major + 3, 0);
TTY.register(device_throw_getchar, tty_throw_getchar);
FS.mkdev('/tty_throw_getchar', device_throw_getchar);

// TTY with throwing put_char - should cause EIO on write
var tty_throw_putchar = {
get_char: function (tty) {
return 0;
},
put_char: function (tty, val) {
throw new Error('put_char error');
},
};
var device_throw_putchar = FS.makedev(major + 4, 0);
TTY.register(device_throw_putchar, tty_throw_putchar);
FS.mkdev('/tty_throw_putchar', device_throw_putchar);

// TTY with empty input (returns undefined immediately) - should cause EAGAIN on
// read
var tty_empty = {
get_char: function (tty) {
return undefined;
},
put_char: function (tty, val) {},
};
var device_empty = FS.makedev(major + 5, 0);
TTY.register(device_empty, tty_empty);
FS.mkdev('/tty_empty', device_empty);
});
// clang-format on

int main() {
init();
char readBuffer[256] = {0};
char writeBuffer[] = "Test";
struct winsize ws;
struct termios term;

printf("\nTest 1: open custom TTY device and check isatty\n");
int fd = open("/custom_tty", O_RDWR);
assert(fd >= 0);
printf("isatty: %d\n", isatty(fd));
printf("errno after open: %s\n", strerror(errno));
errno = 0;

printf("\nTest 2: read from TTY with data\n");
ssize_t bytesRead = read(fd, readBuffer, sizeof(readBuffer));
printf("read bytes: %zd\n", bytesRead);
printf("read data: %s\n", bytesRead > 0 ? readBuffer : "(none)");
printf("errno after read: %s\n", strerror(errno));
errno = 0;

printf("\nTest 3: write to TTY\n");
ssize_t bytesWritten = write(fd, writeBuffer, strlen(writeBuffer));
printf("write bytes: %zd\n", bytesWritten);
printf("errno after write: %s\n", strerror(errno));
errno = 0;

printf("\nTest 4: ioctl TIOCGWINSZ\n");
int result = ioctl(fd, TIOCGWINSZ, &ws);
printf("ioctl TIOCGWINSZ: %d\n", result);
printf("ws_row: %d ws_col: %d\n", ws.ws_row, ws.ws_col);
printf("errno after ioctl: %s\n", strerror(errno));
errno = 0;

printf("\nTest 5: ioctl TCGETS\n");
result = ioctl(fd, TCGETS, &term);
printf("ioctl TCGETS: %d\n", result);
printf("errno after TCGETS: %s\n", strerror(errno));
errno = 0;

printf("\nTest 6: ioctl TCSETS\n");
result = ioctl(fd, TCSETS, &term);
printf("ioctl TCSETS: %d\n", result);
printf("errno after TCSETS: %s\n", strerror(errno));
errno = 0;

printf("\nTest 7: fsync\n");
result = fsync(fd);
printf("fsync: %d\n", result);
printf("errno after fsync: %s\n", strerror(errno));
errno = 0;

close(fd);

printf("\nTest 8: no put_char\n");
fd = open("/tty_no_putchar", O_WRONLY);
assert(fd >= 0);
bytesWritten = write(fd, writeBuffer, strlen(writeBuffer));
printf("write: %zd\n", bytesWritten);
printf("errno: %s\n", strerror(errno));
errno = 0;
close(fd);

printf("\nTest 9: no get_char\n");
fd = open("/tty_no_getchar", O_RDONLY);
assert(fd >= 0);
bytesRead = read(fd, readBuffer, sizeof(readBuffer));
printf("read: %zd\n", bytesRead);
printf("errno: %s\n", strerror(errno));
errno = 0;
close(fd);

printf("\nTest 10: put_char throws\n");
fd = open("/tty_throw_putchar", O_WRONLY);
assert(fd >= 0);
bytesWritten = write(fd, writeBuffer, strlen(writeBuffer));
printf("write: %zd\n", bytesWritten);
printf("errno: %s\n", strerror(errno));
errno = 0;
close(fd);

printf("\nTest 11: get_char throws\n");
fd = open("/tty_throw_getchar", O_RDONLY);
assert(fd >= 0);
bytesRead = read(fd, readBuffer, sizeof(readBuffer));
printf("read: %zd\n", bytesRead);
printf("errno: %s\n", strerror(errno));
errno = 0;
close(fd);

printf("\nTest 12: get_char returns undefined\n");
fd = open("/tty_empty", O_RDONLY);
assert(fd >= 0);
bytesRead = read(fd, readBuffer, sizeof(readBuffer));
printf("read: %zd\n", bytesRead);
printf("errno: %s\n", strerror(errno));
errno = 0;
close(fd);

printf("\ndone\n");
return 0;
}
54 changes: 54 additions & 0 deletions test/other/libtty.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@

Test 1: open custom TTY device and check isatty
isatty: 1
errno after open: Success

Test 2: read from TTY with data
read bytes: 4
read data: ABCD
errno after read: Success

Test 3: write to TTY
write bytes: 4
errno after write: Success

Test 4: ioctl TIOCGWINSZ
ioctl TIOCGWINSZ: 0
ws_row: 25 ws_col: 80
errno after ioctl: Success

Test 5: ioctl TCGETS
ioctl TCGETS: 0
errno after TCGETS: Success

Test 6: ioctl TCSETS
ioctl TCSETS: 0
errno after TCSETS: Success

Test 7: fsync
fsync called
fsync: 0
errno after fsync: Success
fsync called

Test 8: no put_char
write: -1
errno: No such device or address

Test 9: no get_char
read: -1
errno: No such device or address

Test 10: put_char throws
write: -1
errno: I/O error

Test 11: get_char throws
read: -1
errno: I/O error

Test 12: get_char returns undefined
read: -1
errno: Resource temporarily unavailable

done
3 changes: 3 additions & 0 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -13176,6 +13176,9 @@ def test_unistd_isatty(self):
self.skipTest('depends on /dev filesystem')
self.do_runf('unistd/isatty.c', 'success')

def test_libtty(self):
self.do_other_test('libtty.c')

def test_unistd_login(self):
self.do_run_in_out_file_test('unistd/login.c')

Expand Down
Loading