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 == '/' || 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 == '\\') {

View file

@ -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));
}

View file

@ -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);
}

View file

@ -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

View file

@ -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;
}