diff options
author | 2023-08-19 00:11:24 -0700 | |
---|---|---|
committer | 2023-08-19 00:11:24 -0700 | |
commit | db09ed15fd561b89b24b979b986e21a04576f7cc (patch) | |
tree | afe32e4eccaf1fe3f2f4cd536c1f2a61efad28c1 /src/bun.js | |
parent | bf517d9f8e993a9ed3a02d21094c3ce76d7953a1 (diff) | |
download | bun-db09ed15fd561b89b24b979b986e21a04576f7cc.tar.gz bun-db09ed15fd561b89b24b979b986e21a04576f7cc.tar.zst bun-db09ed15fd561b89b24b979b986e21a04576f7cc.zip |
tty `ReadStream`, `WriteStream`, and readline rawmode (#4179)
* tty `WriteStream`, `ReadStream`, and rawmode
* tests
* refactor prototypes
* fix failing test
* fix test and library usage
* more merge
* fix child_process test
* create pseudo terminal for tty tests
* match node logic
* handle invalid tty
* close descriptors
* move tests to another process
* fix test again
* fix test on linux
Diffstat (limited to 'src/bun.js')
-rw-r--r-- | src/bun.js/bindings/Process.cpp | 5 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 44 | ||||
-rw-r--r-- | src/bun.js/bindings/exports.zig | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/wtf-bindings.cpp | 134 | ||||
-rw-r--r-- | src/bun.js/modules/_NativeModule.h | 1 |
5 files changed, 180 insertions, 6 deletions
diff --git a/src/bun.js/bindings/Process.cpp b/src/bun.js/bindings/Process.cpp index 9a09eca66..5c9c03dd2 100644 --- a/src/bun.js/bindings/Process.cpp +++ b/src/bun.js/bindings/Process.cpp @@ -279,7 +279,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, } } - JSC::EncodedJSValue (*napi_register_module_v1)(JSC::JSGlobalObject * globalObject, + JSC::EncodedJSValue (*napi_register_module_v1)(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue exports); napi_register_module_v1 = reinterpret_cast<JSC::EncodedJSValue (*)(JSC::JSGlobalObject*, @@ -912,13 +912,10 @@ static JSValue constructStdioWriteStream(JSC::JSGlobalObject* globalObject, int { auto& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - JSC::JSFunction* getWindowSizeFunction = JSC::JSFunction::create(vm, globalObject, 2, - String("getWindowSize"_s), Process_functionInternalGetWindowSize, ImplementationVisibility::Public); JSC::JSFunction* getStdioWriteStream = JSC::JSFunction::create(vm, processObjectInternalsGetStdioWriteStreamCodeGenerator(vm), globalObject); JSC::MarkedArgumentBuffer args; args.append(JSC::jsNumber(fd)); - args.append(getWindowSizeFunction); auto clientData = WebCore::clientData(vm); JSC::CallData callData = JSC::getCallData(getStdioWriteStream); diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 016da0fd6..f8fc2ea2b 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -109,6 +109,7 @@ #include "NodeVMScript.h" #include "ProcessIdentifier.h" #include "SerializedScriptValue.h" +#include "NodeTTYModule.h" #include "ZigGeneratedClasses.h" #include "JavaScriptCore/DateInstance.h" @@ -1408,6 +1409,35 @@ JSC_DEFINE_HOST_FUNCTION(asyncHooksCleanupLater, (JSC::JSGlobalObject * globalOb return JSC::JSValue::encode(JSC::jsUndefined()); } +extern "C" int Bun__ttySetMode(int fd, int mode); + +JSC_DEFINE_HOST_FUNCTION(jsTTYSetMode, (JSC::JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (callFrame->argumentCount() != 2) { + throwTypeError(globalObject, scope, "Expected 2 arguments"_s); + return JSValue::encode(jsUndefined()); + } + + JSValue fd = callFrame->argument(0); + if (!fd.isNumber()) { + throwTypeError(globalObject, scope, "fd must be a number"_s); + return JSValue::encode(jsUndefined()); + } + + JSValue mode = callFrame->argument(1); + if (!mode.isNumber()) { + throwTypeError(globalObject, scope, "mode must be a number"_s); + return JSValue::encode(jsUndefined()); + } + + // Nodejs does not throw when ttySetMode fails. An Error event is emitted instead. + int err = Bun__ttySetMode(fd.asNumber(), mode.asNumber()); + return JSValue::encode(jsNumber(err)); +} + JSC_DEFINE_CUSTOM_GETTER(noop_getter, (JSGlobalObject*, EncodedJSValue, PropertyName)) { return JSC::JSValue::encode(JSC::jsUndefined()); @@ -1484,6 +1514,8 @@ JSC_DEFINE_HOST_FUNCTION(jsReceiveMessageOnPort, (JSGlobalObject * lexicalGlobal return JSC::JSValue::encode(jsUndefined()); } +extern JSC::EncodedJSValue Process_functionInternalGetWindowSize(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame); + // we're trying out a new way to do this lazy loading // this is $lazy() in js code static JSC_DEFINE_HOST_FUNCTION(functionLazyLoad, @@ -1660,6 +1692,18 @@ static JSC_DEFINE_HOST_FUNCTION(functionLazyLoad, return JSValue::encode(obj); } + if (string == "tty"_s) { + auto* obj = constructEmptyObject(globalObject); + + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "ttySetMode"_s)), JSFunction::create(vm, globalObject, 0, "ttySetMode"_s, jsTTYSetMode, ImplementationVisibility::Public), 1); + + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "isatty"_s)), JSFunction::create(vm, globalObject, 0, "isatty"_s, jsFunctionTty_isatty, ImplementationVisibility::Public), 1); + + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "getWindowSize"_s)), JSFunction::create(vm, globalObject, 0, "getWindowSize"_s, Process_functionInternalGetWindowSize, ImplementationVisibility::Public), 2); + + return JSValue::encode(obj); + } + if (UNLIKELY(string == "noop"_s)) { auto* obj = constructEmptyObject(globalObject); obj->putDirectCustomAccessor(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "getterSetter"_s)), JSC::CustomGetterSetter::create(vm, noop_getter, noop_setter), 0); diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index faf14a56a..ac19b4458 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -1008,7 +1008,7 @@ pub const ZigConsoleClient = struct { if (message_type == .Trace) { writeTrace(Writer, writer, global); - buffered_writer.flush() catch unreachable; + buffered_writer.flush() catch {}; } } diff --git a/src/bun.js/bindings/wtf-bindings.cpp b/src/bun.js/bindings/wtf-bindings.cpp index ccf71f8eb..6fd10721a 100644 --- a/src/bun.js/bindings/wtf-bindings.cpp +++ b/src/bun.js/bindings/wtf-bindings.cpp @@ -2,6 +2,8 @@ #include "wtf/StackTrace.h" #include "wtf/dtoa.h" +#include "wtf/Lock.h" +#include "termios.h" extern "C" double WTF__parseDouble(const LChar* string, size_t length, size_t* position) { @@ -13,6 +15,138 @@ extern "C" void WTF__copyLCharsFromUCharSource(LChar* destination, const UChar* WTF::StringImpl::copyCharacters(destination, source, length); } +static int orig_termios_fd = -1; +static struct termios orig_termios; +static WTF::Lock orig_termios_lock; + +static int current_tty_mode = 0; +static struct termios orig_tty_termios; + +int uv__tcsetattr(int fd, int how, const struct termios* term) +{ + int rc; + + do + rc = tcsetattr(fd, how, term); + while (rc == -1 && errno == EINTR); + + if (rc == -1) + return errno; + + return 0; +} + +static void uv__tty_make_raw(struct termios* tio) +{ + assert(tio != NULL); + +#if defined __sun || defined __MVS__ + /* + * This implementation of cfmakeraw for Solaris and derivatives is taken from + * http://www.perkin.org.uk/posts/solaris-portability-cfmakeraw.html. + */ + tio->c_iflag &= ~(IMAXBEL | IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); + tio->c_oflag &= ~OPOST; + tio->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + tio->c_cflag &= ~(CSIZE | PARENB); + tio->c_cflag |= CS8; + + /* + * By default, most software expects a pending read to block until at + * least one byte becomes available. As per termio(7I), this requires + * setting the MIN and TIME parameters appropriately. + * + * As a somewhat unfortunate artifact of history, the MIN and TIME slots + * in the control character array overlap with the EOF and EOL slots used + * for canonical mode processing. Because the EOF character needs to be + * the ASCII EOT value (aka Control-D), it has the byte value 4. When + * switching to raw mode, this is interpreted as a MIN value of 4; i.e., + * reads will block until at least four bytes have been input. + * + * Other platforms with a distinct MIN slot like Linux and FreeBSD appear + * to default to a MIN value of 1, so we'll force that value here: + */ + tio->c_cc[VMIN] = 1; + tio->c_cc[VTIME] = 0; +#else + cfmakeraw(tio); +#endif /* #ifdef __sun */ +} + +extern "C" int +Bun__ttySetMode(int fd, int mode) +{ + struct termios tmp; + int expected; + int rc; + + if (current_tty_mode == mode) + return 0; + + if (current_tty_mode == 0 && mode != 0) { + do { + rc = tcgetattr(fd, &orig_tty_termios); + } while (rc == -1 && errno == EINTR); + + if (rc == -1) + return errno; + + { + /* This is used for uv_tty_reset_mode() */ + LockHolder locker(orig_termios_lock); + + if (orig_termios_fd == -1) { + orig_termios = orig_termios; + orig_termios_fd = fd; + } + } + } + + tmp = orig_tty_termios; + switch (mode) { + case 0: // normal + break; + case 1: // raw + tmp.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + tmp.c_oflag |= (ONLCR); + tmp.c_cflag |= (CS8); + tmp.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + tmp.c_cc[VMIN] = 1; + tmp.c_cc[VTIME] = 0; + break; + case 2: // io + uv__tty_make_raw(&tmp); + break; + } + + /* Apply changes after draining */ + rc = uv__tcsetattr(fd, TCSADRAIN, &tmp); + if (rc == 0) + current_tty_mode = mode; + + return rc; +} + +int uv_tty_reset_mode(void) +{ + int saved_errno; + int err; + + saved_errno = errno; + + if (orig_termios_lock.tryLock()) + return 16; // UV_EBUSY; /* In uv_tty_set_mode(). */ + + err = 0; + if (orig_termios_fd != -1) + err = uv__tcsetattr(orig_termios_fd, TCSANOW, &orig_termios); + + orig_termios_lock.unlock(); + errno = saved_errno; + + return err; +} + extern "C" void Bun__crashReportWrite(void* ctx, const char* message, size_t length); extern "C" void Bun__crashReportDumpStackTrace(void* ctx) { diff --git a/src/bun.js/modules/_NativeModule.h b/src/bun.js/modules/_NativeModule.h index 485987e1c..b23906ad0 100644 --- a/src/bun.js/modules/_NativeModule.h +++ b/src/bun.js/modules/_NativeModule.h @@ -31,7 +31,6 @@ macro("node:module"_s, NodeModule) \ macro("node:process"_s, NodeProcess) \ macro("node:string_decoder"_s, NodeStringDecoder) \ - macro("node:tty"_s, NodeTTY) \ macro("node:util/types"_s, NodeUtilTypes) \ macro("utf-8-validate"_s, UTF8Validate) \ |