aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js
diff options
context:
space:
mode:
authorGravatar Dylan Conway <35280289+dylan-conway@users.noreply.github.com> 2023-08-19 00:11:24 -0700
committerGravatar GitHub <noreply@github.com> 2023-08-19 00:11:24 -0700
commitdb09ed15fd561b89b24b979b986e21a04576f7cc (patch)
treeafe32e4eccaf1fe3f2f4cd536c1f2a61efad28c1 /src/bun.js
parentbf517d9f8e993a9ed3a02d21094c3ce76d7953a1 (diff)
downloadbun-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.cpp5
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp44
-rw-r--r--src/bun.js/bindings/exports.zig2
-rw-r--r--src/bun.js/bindings/wtf-bindings.cpp134
-rw-r--r--src/bun.js/modules/_NativeModule.h1
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) \