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