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.
This commit is contained in:
Mateusz Kazimierczuk 2023-09-02 16:00:52 +02:00
parent aca2261cda
commit 2bb9db87c7
5 changed files with 68 additions and 72 deletions

View file

@ -90,26 +90,13 @@ textwindows int mkntcmdline(char16_t cmdline[32767], char *const argv[]) {
} }
} }
if (!x) break; if (!x) break;
if (x == '/' || x == '\\') { if (!i && (x == '/' || x == '\\')) {
if (!i) { // turn / into \ for first argv[i]
// turn / into \ for first argv[i] x = '\\';
x = '\\'; // turn \c\... into c:\ for first argv[i]
// turn \c\... into c:\ for first argv[i] if (k == 2 && IsAlpha(cmdline[1]) && cmdline[0] == '\\') {
if (k == 2 && IsAlpha(cmdline[1]) && cmdline[0] == '\\') { cmdline[0] = cmdline[1];
cmdline[0] = cmdline[1]; 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 (x == '\\') { if (x == '\\') {

View file

@ -19,39 +19,54 @@
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/intrin/safemacros.internal.h" #include "libc/intrin/safemacros.internal.h"
#include "libc/limits.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/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. * Displays wall of text in terminal with pagination.
*/ */
void __paginate(int fd, const char *s) { void __paginate(int fd, const char *s) {
int tfd, pid; int ofd, pid, pfd[2];
char *args[3] = {0}; char *prog;
char tmppath[] = "/tmp/paginate.XXXXXX"; if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) &&
char progpath[PATH_MAX]; strcmp(nulltoempty(getenv("TERM")), "dumb") &&
if (strcmp(nulltoempty(getenv("TERM")), "dumb") && isatty(0) && isatty(1) && ((prog = commandv("less", _gc(malloc(PATH_MAX)), PATH_MAX)) ||
((args[0] = commandv("less", progpath, sizeof(progpath))) || (prog = commandv("more", _gc(malloc(PATH_MAX)), PATH_MAX))) &&
(args[0] = commandv("more", progpath, sizeof(progpath))))) { !pipe2(pfd, O_CLOEXEC)) {
if ((tfd = mkstemp(tmppath)) != -1) { if ((pid = fork()) != -1) {
write(tfd, s, strlen(s)); putenv("LC_ALL=C.UTF-8");
close(tfd); putenv("LESSCHARSET=utf-8");
args[1] = tmppath; putenv("LESS=-RS");
if ((pid = fork()) != -1) { if (!pid) {
putenv("LC_ALL=C.UTF-8"); RUN(STDIN_FILENO, ofd, pfd, execv(prog, (char *const[]){prog, NULL}));
putenv("LESSCHARSET=utf-8"); _Exit(127);
putenv("LESS=-RS"); } else {
if (!pid) { RUN(STDOUT_FILENO, ofd, pfd, write(STDOUT_FILENO, s, strlen(s)));
execv(args[0], args); waitpid(pid, NULL, 0);
_Exit(127);
}
waitpid(pid, 0, 0);
unlink(tmppath);
return;
} }
unlink(tmppath); return;
} }
close(pfd[0]);
close(pfd[1]);
} }
write(fd, s, strlen(s)); write(fd, s, strlen(s));
} }

View file

@ -64,39 +64,27 @@ TEST(mkntcmdline, justSlash) {
} }
TEST(mkntcmdline, testUnicode) { TEST(mkntcmdline, testUnicode) {
char *argv1[] = { char *argv[] = {
gc(strdup("(╯°□°)╯")), gc(strdup("(╯°□°)╯")),
gc(strdup("要依法治国是赞美那些谁是公义的和惩罚恶人。 - 韩非")), gc(strdup("要依法治国是赞美那些谁是公义的和惩罚恶人。 - 韩非")),
NULL, NULL,
}; };
EXPECT_NE(-1, mkntcmdline(cmdline, argv1)); EXPECT_NE(-1, mkntcmdline(cmdline, argv));
EXPECT_STREQ(u"(╯°□°)╯ \"要依法治国是赞美那些谁是公义的和惩罚恶人。 - 韩非\"", EXPECT_STREQ(u"(╯°□°)╯ \"要依法治国是赞美那些谁是公义的和惩罚恶人。 - 韩非\"",
cmdline); cmdline);
} }
TEST(mkntcmdline, fixAsBestAsWeCanForNow1) { TEST(mkntcmdline, testPathlikeArgv0) {
char *argv1[] = { char *argv[] = {
"/C/WINDOWS/system32/cmd.exe", "/C/WINDOWS/system32/cmd.exe",
"/C", "/S",
"more <\"/C/Users/jart/AppData/Local/Temp/tmplquaa_d6\"", "/D/c",
"less C:\\Users\\jart\\AppData\\Local\\Temp\\tmplquaa_d6",
NULL, NULL,
}; };
EXPECT_NE(-1, mkntcmdline(cmdline, argv1)); EXPECT_NE(-1, mkntcmdline(cmdline, argv));
EXPECT_STREQ(u"C:\\WINDOWS\\system32\\cmd.exe /C \"more <" EXPECT_STREQ(u"C:\\WINDOWS\\system32\\cmd.exe /S /D/c \"less "
u"\"\"\"C:/Users/jart/AppData/Local/Temp/tmplquaa_d6\"\"\"\"", 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\"",
cmdline); cmdline);
} }

View file

@ -260,7 +260,7 @@ SECURITY
redbean provides hardened ASAN (Address Sanitizer) builds that redbean provides hardened ASAN (Address Sanitizer) builds that
proactively guard against any potential memory weaknesses that may be proactively guard against any potential memory weaknesses that may be
discovered, such as buffer overruns, use after free, etc. MDOE=asan is 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 redbean also supports robust sandboxing on Linux Kernel 5.13+ and
OpenBSD. The recommended way to harden your redbean is to call the OpenBSD. The recommended way to harden your redbean is to call the

View file

@ -83,6 +83,7 @@
#include "libc/sysv/consts/ex.h" #include "libc/sysv/consts/ex.h"
#include "libc/sysv/consts/exit.h" #include "libc/sysv/consts/exit.h"
#include "libc/sysv/consts/f.h" #include "libc/sysv/consts/f.h"
#include "libc/sysv/consts/fileno.h"
#include "libc/sysv/consts/hwcap.h" #include "libc/sysv/consts/hwcap.h"
#include "libc/sysv/consts/inaddr.h" #include "libc/sysv/consts/inaddr.h"
#include "libc/sysv/consts/ipproto.h" #include "libc/sysv/consts/ipproto.h"
@ -215,7 +216,7 @@ __static_yoink("blink_xnu_aarch64"); // is apple silicon
// digits not used: 0123456789 // digits not used: 0123456789
// puncts not used: !"#$&'()+,-./;<=>@[\]^_`{|}~ // puncts not used: !"#$&'()+,-./;<=>@[\]^_`{|}~
#define GETOPTS \ #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[] = { static const uint8_t kGzipHeader[] = {
0x1F, // MAGNUM 0x1F, // MAGNUM
@ -7294,7 +7295,6 @@ static void GetOpts(int argc, char *argv[]) {
break; break;
CASE('r', ProgramRedirectArg(307, optarg)); CASE('r', ProgramRedirectArg(307, optarg));
CASE('t', ProgramTimeout(ParseInt(optarg))); CASE('t', ProgramTimeout(ParseInt(optarg)));
CASE('h', PrintUsage(1, EXIT_SUCCESS));
CASE('M', ProgramMaxPayloadSize(ParseInt(optarg))); CASE('M', ProgramMaxPayloadSize(ParseInt(optarg)));
#if !IsTiny() #if !IsTiny()
CASE('W', monitortty = optarg); CASE('W', monitortty = optarg);
@ -7331,8 +7331,14 @@ static void GetOpts(int argc, char *argv[]) {
CASE('C', ProgramFile(optarg, ProgramCertificate)); CASE('C', ProgramFile(optarg, ProgramCertificate));
CASE('K', ProgramFile(optarg, ProgramPrivateKey)); CASE('K', ProgramFile(optarg, ProgramPrivateKey));
#endif #endif
case 'h':
case '?':
default: 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 // if storing asset(s) is requested, don't need to continue
@ -7481,5 +7487,5 @@ int main(int argc, char *argv[]) {
CheckForMemoryLeaks(); CheckForMemoryLeaks();
} }
return 0; return EXIT_SUCCESS;
} }