aboutsummaryrefslogtreecommitdiff
path: root/src/term.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/term.c100
1 files changed, 100 insertions, 0 deletions
diff --git a/src/term.c b/src/term.c
index 4cfce95..792832d 100644
--- a/src/term.c
+++ b/src/term.c
@@ -649,8 +649,104 @@ static int lst_tcsetattr(lua_State *L)
649 649
650 650
651 651
652#ifndef _WIN32
653/*
654reopen FDs for independent file descriptions.
655*/
656static void reopen_fd(lua_State *L, int fd, int flags) {
657 char path[64];
658 int newfd = -1;
659
660 if (fd != STDOUT_FILENO && fd != STDERR_FILENO) {
661 luaL_error(L, "Invalid file descriptor: %d. Only stdout (1) and stderr (2) are supported.", fd);
662 }
663
664 const char *fallback_path = (fd == STDOUT_FILENO) ? "/dev/stdout" :
665 (fd == STDERR_FILENO) ? "/dev/stderr" : NULL;
666
667 // If fd is a terminal, reopen its actual device (e.g. /dev/ttys003)
668 // Works on all POSIX platforms that have terminals (macOS, Linux, BSD, etc.)
669 if (isatty(fd)) {
670 const char *tty = ttyname(fd);
671 if (tty) {
672 newfd = open(tty, flags);
673 }
674 }
675
676 if (newfd < 0) {
677 // For non-tty: try /dev/fd/N — POSIX-compliant and standard on macOS, Linux, BSD.
678 // This gives a new file description even if the target is a file or pipe.
679 snprintf(path, sizeof(path), "/dev/fd/%d", fd);
680 newfd = open(path, flags);
681 }
682
683 if (newfd < 0) {
684 // Fallback: for platforms/environments where /dev/fd/N doesn't exist.
685 // /dev/stdout and /dev/stderr are standard on Linux, but may not create new descriptions.
686 if (fallback_path) {
687 newfd = open(fallback_path, flags);
688 }
689 }
690
691 if (newfd < 0) {
692 // All attempts failed — raise error with detailed info (call will not return)
693 luaL_error(L, "Failed to reopen fd %d: tried ttyname(), /dev/fd/%d, and fallback %s: %s",
694 fd, fd, fallback_path ? fallback_path : "(none)", strerror(errno));
695 }
696
697 // Replace the original fd with the new one
698 if (dup2(newfd, fd) < 0) {
699 close(newfd);
700 luaL_error(L, "dup2 failed for fd %d: %s", fd, strerror(errno));
701 }
702
703 close(newfd); // Close the new fd, as dup2 has replaced the original fd with it
704}
705#endif
706
707
708
709/***
710Creates new file descriptions for `stdout` and `stderr`.
711Even if the file descriptors are unique, they still might point to the same
712file description, and hence share settings like `O_NONBLOCK`. This means that
713if one of them is set to non-blocking, the other will be as well. This can
714lead to unexpected behavior.
715
716This function is used to detach `stdout` and `stderr` from the original
717file descriptions, and create new file descriptions for them. This allows
718independent control of flags (e.g., `O_NONBLOCK`) on `stdout` and `stderr`,
719avoiding shared side effects.
720
721Does not modify `stdin` (fd 0), and does nothing on Windows.
722@function detachfds
723@return boolean `true` on success, or throws an error on failure.
724@see setnonblock
725*/
726static int lst_detachfds(lua_State *L) {
727 static int already_detached = 0; // detaching is once per process(not per thread or Lua state)
728 if (already_detached) {
729 lua_pushnil(L);
730 lua_pushliteral(L, "stdout and stderr already detached");
731 return 1;
732 }
733 already_detached = 1;
734
735#ifndef _WIN32
736 // Reopen stdout and stderr with new file descriptions
737 reopen_fd(L, STDOUT_FILENO, O_WRONLY);
738 reopen_fd(L, STDERR_FILENO, O_WRONLY);
739#endif
740
741 lua_pushboolean(L, 1);
742 return 1;
743}
744
745
746
652/*** 747/***
653Enables or disables non-blocking mode for a file (Posix). 748Enables or disables non-blocking mode for a file (Posix).
749Check `detachfds` in case there are shared file descriptions.
654@function setnonblock 750@function setnonblock
655@tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr` 751@tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr`
656@tparam boolean make_non_block a truthy value will enable non-blocking mode, a falsy value will disable it. 752@tparam boolean make_non_block a truthy value will enable non-blocking mode, a falsy value will disable it.
@@ -659,8 +755,10 @@ Enables or disables non-blocking mode for a file (Posix).
659@treturn[2] string error message 755@treturn[2] string error message
660@treturn[2] int errnum 756@treturn[2] int errnum
661@see getnonblock 757@see getnonblock
758@see detachfds
662@usage 759@usage
663local sys = require('system') 760local sys = require('system')
761sys.detachfds() -- detach stdout and stderr, so only stdin becomes non-blocking
664 762
665-- set io.stdin to non-blocking mode 763-- set io.stdin to non-blocking mode
666local old_setting = sys.getnonblock(io.stdin) 764local old_setting = sys.getnonblock(io.stdin)
@@ -717,6 +815,7 @@ Gets non-blocking mode status for a file (Posix).
717@treturn[2] nil 815@treturn[2] nil
718@treturn[2] string error message 816@treturn[2] string error message
719@treturn[2] int errnum 817@treturn[2] int errnum
818@see setnonblock
720*/ 819*/
721static int lst_getnonblock(lua_State *L) 820static int lst_getnonblock(lua_State *L)
722{ 821{
@@ -1157,6 +1256,7 @@ static luaL_Reg func[] = {
1157 { "setconsoleflags", lst_setconsoleflags }, 1256 { "setconsoleflags", lst_setconsoleflags },
1158 { "tcgetattr", lst_tcgetattr }, 1257 { "tcgetattr", lst_tcgetattr },
1159 { "tcsetattr", lst_tcsetattr }, 1258 { "tcsetattr", lst_tcsetattr },
1259 { "detachfds", lst_detachfds },
1160 { "getnonblock", lst_getnonblock }, 1260 { "getnonblock", lst_getnonblock },
1161 { "setnonblock", lst_setnonblock }, 1261 { "setnonblock", lst_setnonblock },
1162 { "_readkey", lst_readkey }, 1262 { "_readkey", lst_readkey },