From 2bb9db87c7689bbde0cad795c1de6a105de95d42 Mon Sep 17 00:00:00 2001 From: Mateusz Kazimierczuk Date: Sat, 2 Sep 2023 16:00:52 +0200 Subject: [PATCH] Paginate by writing data to pager via stdin On Windows, `more` is unable to open the file generated by `mkstemp()` due to not recognizing the first argument as a path, but as a parameter. In contrast to *nix, arguments on Windows are, by convention, prefixed with `/`, which `mkntcmdline()` happily transforms into path-like strings. This commit sidesteps this issue entirely by writing data to the pager in question via stdin, dumping text to the console as a last resort. --- libc/calls/mkntcmdline.c | 27 ++++--------- libc/proc/paginate.c | 65 ++++++++++++++++++------------ test/libc/calls/mkntcmdline_test.c | 32 +++++---------- tool/net/help.txt | 2 +- tool/net/redbean.c | 14 +++++-- 5 files changed, 68 insertions(+), 72 deletions(-) diff --git a/libc/calls/mkntcmdline.c b/libc/calls/mkntcmdline.c index f0cfc0e9d..d9551b1ae 100644 --- a/libc/calls/mkntcmdline.c +++ b/libc/calls/mkntcmdline.c @@ -90,26 +90,13 @@ textwindows int mkntcmdline(char16_t cmdline[32767], char *const argv[]) { } } if (!x) break; - if (x == '/' || x == '\\') { - if (!i) { - // turn / into \ for first argv[i] - x = '\\'; - // turn \c\... into c:\ for first argv[i] - if (k == 2 && IsAlpha(cmdline[1]) && cmdline[0] == '\\') { - cmdline[0] = cmdline[1]; - cmdline[1] = ':'; - } - } else { - // turn stuff like `less /c/...` - // into `less c:/...` - // turn stuff like `more <"/c/..."` - // into `more <"c:/..."` - if (k > 3 && IsAlpha(cmdline[k - 1]) && - (cmdline[k - 2] == '/' || cmdline[k - 2] == '\\') && - (cmdline[k - 3] == '"' || cmdline[k - 3] == ' ')) { - cmdline[k - 2] = cmdline[k - 1]; - cmdline[k - 1] = ':'; - } + if (!i && (x == '/' || x == '\\')) { + // turn / into \ for first argv[i] + x = '\\'; + // turn \c\... into c:\ for first argv[i] + if (k == 2 && IsAlpha(cmdline[1]) && cmdline[0] == '\\') { + cmdline[0] = cmdline[1]; + cmdline[1] = ':'; } } if (x == '\\') { diff --git a/libc/proc/paginate.c b/libc/proc/paginate.c index 87687163c..c0eac856d 100644 --- a/libc/proc/paginate.c +++ b/libc/proc/paginate.c @@ -19,39 +19,54 @@ #include "libc/calls/calls.h" #include "libc/intrin/safemacros.internal.h" #include "libc/limits.h" -#include "libc/runtime/runtime.h" +#include "libc/mem/gc.h" +#include "libc/mem/mem.h" +#include "libc/stdio/stdio.h" #include "libc/str/str.h" -#include "libc/temp.h" +#include "libc/sysv/consts/fileno.h" +#include "libc/sysv/consts/o.h" + +#define RUN(fd, ofd, pfd, func) \ + do { \ + int i; \ + for (i = 0; i < 2; i++) { \ + if (i == fd) { \ + ofd = dup(fd); \ + dup2(pfd[i], fd); \ + } \ + close(pfd[i]); \ + } \ + func; \ + dup2(ofd, fd); \ + close(ofd); \ + } while (0) /** * Displays wall of text in terminal with pagination. */ void __paginate(int fd, const char *s) { - int tfd, pid; - char *args[3] = {0}; - char tmppath[] = "/tmp/paginate.XXXXXX"; - char progpath[PATH_MAX]; - if (strcmp(nulltoempty(getenv("TERM")), "dumb") && isatty(0) && isatty(1) && - ((args[0] = commandv("less", progpath, sizeof(progpath))) || - (args[0] = commandv("more", progpath, sizeof(progpath))))) { - if ((tfd = mkstemp(tmppath)) != -1) { - write(tfd, s, strlen(s)); - close(tfd); - args[1] = tmppath; - if ((pid = fork()) != -1) { - putenv("LC_ALL=C.UTF-8"); - putenv("LESSCHARSET=utf-8"); - putenv("LESS=-RS"); - if (!pid) { - execv(args[0], args); - _Exit(127); - } - waitpid(pid, 0, 0); - unlink(tmppath); - return; + int ofd, pid, pfd[2]; + char *prog; + if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) && + strcmp(nulltoempty(getenv("TERM")), "dumb") && + ((prog = commandv("less", _gc(malloc(PATH_MAX)), PATH_MAX)) || + (prog = commandv("more", _gc(malloc(PATH_MAX)), PATH_MAX))) && + !pipe2(pfd, O_CLOEXEC)) { + if ((pid = fork()) != -1) { + putenv("LC_ALL=C.UTF-8"); + putenv("LESSCHARSET=utf-8"); + putenv("LESS=-RS"); + if (!pid) { + RUN(STDIN_FILENO, ofd, pfd, execv(prog, (char *const[]){prog, NULL})); + _Exit(127); + } else { + RUN(STDOUT_FILENO, ofd, pfd, write(STDOUT_FILENO, s, strlen(s))); + waitpid(pid, NULL, 0); } - unlink(tmppath); + return; } + close(pfd[0]); + close(pfd[1]); } write(fd, s, strlen(s)); } diff --git a/test/libc/calls/mkntcmdline_test.c b/test/libc/calls/mkntcmdline_test.c index 6b923f2f8..c8f5ade94 100644 --- a/test/libc/calls/mkntcmdline_test.c +++ b/test/libc/calls/mkntcmdline_test.c @@ -64,39 +64,27 @@ TEST(mkntcmdline, justSlash) { } TEST(mkntcmdline, testUnicode) { - char *argv1[] = { + char *argv[] = { gc(strdup("(╯°□°)╯")), gc(strdup("要依法治国是赞美那些谁是公义的和惩罚恶人。 - 韩非")), NULL, }; - EXPECT_NE(-1, mkntcmdline(cmdline, argv1)); + EXPECT_NE(-1, mkntcmdline(cmdline, argv)); EXPECT_STREQ(u"(╯°□°)╯ \"要依法治国是赞美那些谁是公义的和惩罚恶人。 - 韩非\"", cmdline); } -TEST(mkntcmdline, fixAsBestAsWeCanForNow1) { - char *argv1[] = { +TEST(mkntcmdline, testPathlikeArgv0) { + char *argv[] = { "/C/WINDOWS/system32/cmd.exe", - "/C", - "more <\"/C/Users/jart/AppData/Local/Temp/tmplquaa_d6\"", + "/S", + "/D/c", + "less C:\\Users\\jart\\AppData\\Local\\Temp\\tmplquaa_d6", NULL, }; - EXPECT_NE(-1, mkntcmdline(cmdline, argv1)); - EXPECT_STREQ(u"C:\\WINDOWS\\system32\\cmd.exe /C \"more <" - u"\"\"\"C:/Users/jart/AppData/Local/Temp/tmplquaa_d6\"\"\"\"", - cmdline); -} - -TEST(mkntcmdline, fixAsBestAsWeCanForNow2) { - char *argv1[] = { - "/C/WINDOWS/system32/cmd.exe", - "/C", - "less /C/Users/jart/AppData/Local/Temp/tmplquaa_d6", - NULL, - }; - EXPECT_NE(-1, mkntcmdline(cmdline, argv1)); - EXPECT_STREQ(u"C:\\WINDOWS\\system32\\cmd.exe /C \"less " - u"C:/Users/jart/AppData/Local/Temp/tmplquaa_d6\"", + EXPECT_NE(-1, mkntcmdline(cmdline, argv)); + EXPECT_STREQ(u"C:\\WINDOWS\\system32\\cmd.exe /S /D/c \"less " + u"C:\\Users\\jart\\AppData\\Local\\Temp\\tmplquaa_d6\"", cmdline); } diff --git a/tool/net/help.txt b/tool/net/help.txt index 226d6ee56..57a7002ac 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -260,7 +260,7 @@ SECURITY redbean provides hardened ASAN (Address Sanitizer) builds that proactively guard against any potential memory weaknesses that may be discovered, such as buffer overruns, use after free, etc. MDOE=asan is - recomended when serving on the public Internet. + recommended when serving on the public Internet. redbean also supports robust sandboxing on Linux Kernel 5.13+ and OpenBSD. The recommended way to harden your redbean is to call the diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 37bd88be5..7a5ed686c 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -83,6 +83,7 @@ #include "libc/sysv/consts/ex.h" #include "libc/sysv/consts/exit.h" #include "libc/sysv/consts/f.h" +#include "libc/sysv/consts/fileno.h" #include "libc/sysv/consts/hwcap.h" #include "libc/sysv/consts/inaddr.h" #include "libc/sysv/consts/ipproto.h" @@ -215,7 +216,7 @@ __static_yoink("blink_xnu_aarch64"); // is apple silicon // digits not used: 0123456789 // puncts not used: !"#$&'()+,-./;<=>@[\]^_`{|}~ #define GETOPTS \ - "*%BEJSVXZabdfghijkmsuvzA:C:D:F:G:H:K:L:M:P:R:T:U:W:c:e:l:p:r:t:w:" + "%*?BEJSVXZabdfghijkmsuvzA:C:D:F:G:H:K:L:M:P:R:T:U:W:c:e:l:p:r:t:w:" static const uint8_t kGzipHeader[] = { 0x1F, // MAGNUM @@ -7294,7 +7295,6 @@ static void GetOpts(int argc, char *argv[]) { break; CASE('r', ProgramRedirectArg(307, optarg)); CASE('t', ProgramTimeout(ParseInt(optarg))); - CASE('h', PrintUsage(1, EXIT_SUCCESS)); CASE('M', ProgramMaxPayloadSize(ParseInt(optarg))); #if !IsTiny() CASE('W', monitortty = optarg); @@ -7331,8 +7331,14 @@ static void GetOpts(int argc, char *argv[]) { CASE('C', ProgramFile(optarg, ProgramCertificate)); CASE('K', ProgramFile(optarg, ProgramPrivateKey)); #endif + case 'h': + case '?': default: - PrintUsage(2, EX_USAGE); + if (opt == optopt) { + PrintUsage(STDOUT_FILENO, EXIT_SUCCESS); + } else { + PrintUsage(STDERR_FILENO, EX_USAGE); + } } } // if storing asset(s) is requested, don't need to continue @@ -7481,5 +7487,5 @@ int main(int argc, char *argv[]) { CheckForMemoryLeaks(); } - return 0; + return EXIT_SUCCESS; }