diff options
| author | Thijs Schreijer <thijs@thijsschreijer.nl> | 2025-04-10 10:04:19 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-04-10 10:04:19 +0200 |
| commit | 98ca844bc6170ee332353abdeaac0be0e4a43af0 (patch) | |
| tree | 88b0e2119a2c4b67c126349214a53b3ef8782c2b /src | |
| parent | 49e7dac558178e6200bc5886db3ef28c73d5edd9 (diff) | |
| download | luasystem-98ca844bc6170ee332353abdeaac0be0e4a43af0.tar.gz luasystem-98ca844bc6170ee332353abdeaac0be0e4a43af0.tar.bz2 luasystem-98ca844bc6170ee332353abdeaac0be0e4a43af0.zip | |
feat(term): detach fd of stderr+stdout to be independent (#59)
Diffstat (limited to 'src')
| -rw-r--r-- | src/term.c | 100 |
1 files changed, 100 insertions, 0 deletions
| @@ -649,8 +649,104 @@ static int lst_tcsetattr(lua_State *L) | |||
| 649 | 649 | ||
| 650 | 650 | ||
| 651 | 651 | ||
| 652 | #ifndef _WIN32 | ||
| 653 | /* | ||
| 654 | reopen FDs for independent file descriptions. | ||
| 655 | */ | ||
| 656 | static 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 | /*** | ||
| 710 | Creates new file descriptions for `stdout` and `stderr`. | ||
| 711 | Even if the file descriptors are unique, they still might point to the same | ||
| 712 | file description, and hence share settings like `O_NONBLOCK`. This means that | ||
| 713 | if one of them is set to non-blocking, the other will be as well. This can | ||
| 714 | lead to unexpected behavior. | ||
| 715 | |||
| 716 | This function is used to detach `stdout` and `stderr` from the original | ||
| 717 | file descriptions, and create new file descriptions for them. This allows | ||
| 718 | independent control of flags (e.g., `O_NONBLOCK`) on `stdout` and `stderr`, | ||
| 719 | avoiding shared side effects. | ||
| 720 | |||
| 721 | Does 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 | */ | ||
| 726 | static 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 | /*** |
| 653 | Enables or disables non-blocking mode for a file (Posix). | 748 | Enables or disables non-blocking mode for a file (Posix). |
| 749 | Check `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 |
| 663 | local sys = require('system') | 760 | local sys = require('system') |
| 761 | sys.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 |
| 666 | local old_setting = sys.getnonblock(io.stdin) | 764 | local 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 | */ |
| 721 | static int lst_getnonblock(lua_State *L) | 820 | static 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 }, |
