Compare commits

...

5 commits

Author SHA1 Message Date
Cadence Ember c32585d75e
Merge 7500b02db5 into 2bfd6b37c1 2024-04-27 14:45:34 +00:00
Cadence Ember 7500b02db5 fgets_interrupt_test needs single core affinity 2024-04-28 02:44:54 +12:00
Gavin Hayes 2bfd6b37c1
Various paginate improvements (#1148)
* start on improving __paginate

* make __paginate more robust

* add __paginate_file

* cleanup __paginate unlinking
2024-04-26 23:12:25 -04:00
Cadence Ember e2bebc73e3 Test fgets, signals, and SA_RESTART interactions
The new test only passes thanks to the previous commit.
2024-04-26 01:34:46 +00:00
Cadence Ember 9df3689416 Let signals interrupt fgets unless SA_RESTART set
See investigation in #1130.

fgets internally calls readv. readv is a @restartable function that
understands SA_RESTART. If SA_RESTART is set, readv already handles
restarting the system call and eventually the string is transparently
returned to the fgets caller.

When SA_RESTART is not set, -1 EINTR should bubble up to the fgets
caller. However, until this commit, fgets itself would detect EINTR and
keep retrying until it read an entire line.

This commit fixes this behaviour so that fgets understands SA_RESTART.

I hereby assign copyright for this commit to Justine Tunney.

Signed-off-by: Cadence Ember <cadence@disroot.org>
2024-04-26 01:33:04 +00:00
4 changed files with 207 additions and 21 deletions

View file

@ -103,6 +103,7 @@ int verynice(void);
void __warn_if_powersave(void);
void _Exit1(int) libcesque wontreturn;
void __paginate(int, const char *);
void __paginate_file(int, const char *);
/* memory management */
void _weakfree(void *);
void *_mapanon(size_t) attributeallocsize((1)) mallocesque;

View file

@ -57,11 +57,7 @@ char *fgets_unlocked(char *s, int size, FILE *f) {
break;
} else {
if ((c = fgetc_unlocked(f)) == -1) {
if (ferror_unlocked(f) == EINTR) {
continue;
} else {
break;
}
break;
}
*p++ = c & 255;
if (c == '\n')

View file

@ -17,42 +17,93 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/dce.h"
#include "libc/intrin/safemacros.internal.h"
#include "libc/limits.h"
#include "libc/mem/gc.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/o.h"
#include "libc/temp.h"
#include "libc/x/x.h"
static char *get_pagerpath(char *pathbuf, size_t pathbufsz) {
char *pagerpath;
if (strcmp(nulltoempty(getenv("TERM")), "dumb") && isatty(0) && isatty(1) &&
((pagerpath = commandv("less", pathbuf, pathbufsz)) ||
(pagerpath = commandv("more", pathbuf, pathbufsz)) ||
(pagerpath = commandv("more.exe", pathbuf, pathbufsz)) ||
(pagerpath = commandv("more.com", pathbuf, pathbufsz)))) {
return pagerpath;
}
return 0;
}
static bool run_pager(char *args[hasatleast 3]) {
char16_t widepath[PATH_MAX];
int n, pid;
if (IsWindows() && !strcasecmp(args[0], "/C/Windows/System32/more.com") &&
(((n = __mkntpath(args[1], widepath)) == -1) ||
!(args[1] = gc(utf16to8(widepath, n, 0))))) {
return false;
}
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);
return true;
}
return false;
}
/**
* Displays wall of text in terminal with pagination.
*/
void __paginate(int fd, const char *s) {
int tfd, pid;
int tfd;
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))) ||
(args[0] = commandv("more.exe", progpath, sizeof(progpath))))) {
bool done;
if ((args[0] = get_pagerpath(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);
done = run_pager(args);
unlink(tmppath);
if (done) {
return;
}
unlink(tmppath);
}
}
write(fd, s, strlen(s));
}
/**
* Displays a file in terminal with pagination
*/
void __paginate_file(int fd, const char *path) {
char *args[3] = {0};
char progpath[PATH_MAX];
if ((args[0] = get_pagerpath(progpath, sizeof(progpath)))) {
args[1] = (char *)path;
if (run_pager(args)) {
return;
}
}
int sfd = open(path, O_RDONLY);
if (sfd != -1) {
ssize_t n;
while ((n = read(sfd, progpath, sizeof(progpath)) > 0)) {
write(fd, progpath, n);
}
}
close(sfd);
}

View file

@ -0,0 +1,138 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2024 Cadence Ember
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/isystem/errno.h"
#include "libc/isystem/sched.h"
#include "libc/isystem/signal.h"
#include "libc/isystem/stddef.h"
#include "libc/isystem/unistd.h"
#include "libc/stdio/stdio.h"
#include "libc/testlib/testlib.h"
#define MY_TEST_STRING_1 "He"
#define MY_TEST_STRING_2 "llo world!"
char buf[20] = {0};
int pipes[2];
int pid;
int got_sigusr1 = 0;
// -=- these get called for each test ------------------------------------------
void sigusr1_handler(int) {
got_sigusr1 = 1;
}
void write_pipe(int send_signal_before_end) {
// Set up pipe for writing
close(pipes[0]);
FILE *stream = fdopen(pipes[1], "w");
// Start writing the first part of the stream
fputs(MY_TEST_STRING_1, stream);
// Send SIGUSR1 to parent (if we're currently testing that)
if (send_signal_before_end) {
kill(getppid(), SIGUSR1);
}
// Send rest of stream
fputs(MY_TEST_STRING_2, stream);
// Close stream - this will cause the parent's fgets to end
fclose(stream);
}
void read_pipe() {
// Set up pipe for reading
close(pipes[1]);
FILE *stream = fdopen(pipes[0], "r");
// Read with fgets
fgets(buf, 20, stream);
// Tidy up
fclose(stream);
}
// -=- these set up the tests --------------------------------------------------
void SetUpOnce(void) {
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(1, &set);
if (sched_setaffinity(0, sizeof set, &set) == -1) {
perror("sched_setaffinity");
fprintf(stderr, "single core affinity is needed for test reliability\n");
_exit(1);
}
}
void setup_signal_and_pipe(uint64_t sa_flags) {
// Set up SIGUSR1 handler
struct sigaction sa = {.sa_handler = sigusr1_handler, .sa_flags = sa_flags};
if (sigaction(SIGUSR1, &sa, NULL) == -1) {
perror("sigaction");
_exit(1);
}
got_sigusr1 = 0;
// Set up pipe between parent and child
if (pipe(pipes) == -1) {
perror("pipe");
_exit(1);
}
}
// -=- these are the tests -----------------------------------------------------
TEST(fgets_eintr, testThatFgetsReadsFromPipeNormally) {
setup_signal_and_pipe(0); // 0 = no SA_RESTART
ASSERT_NE(-1, (pid = fork()));
if (!pid) {
write_pipe(0); // 0 = no signal
_exit(0);
}
read_pipe();
EXPECT_STREQ(MY_TEST_STRING_1 MY_TEST_STRING_2, buf);
}
TEST(fgets_eintr, testThatTheSignalInterruptsFgets) {
setup_signal_and_pipe(0); // 0 = no SA_RESTART
ASSERT_NE(-1, (pid = fork()));
if (!pid) {
write_pipe(1); // 1 = signal
_exit(0);
}
read_pipe();
EXPECT_STRNE(MY_TEST_STRING_1 MY_TEST_STRING_2, buf);
EXPECT_EQ(EINTR, errno);
EXPECT_EQ(1, got_sigusr1);
}
TEST(fgets_eintr, testThatFgetsRestartsWhenSaRestartIsSet) {
setup_signal_and_pipe(SA_RESTART); // SA_RESTART
ASSERT_NE(-1, (pid = fork()));
if (!pid) {
write_pipe(1); // 1 = signal
_exit(0);
}
read_pipe();
EXPECT_STREQ(MY_TEST_STRING_1 MY_TEST_STRING_2, buf);
EXPECT_NE(EINTR, errno);
EXPECT_EQ(1, got_sigusr1);
}