/* This is part of GDB, the GNU debugger. Copyright 2011-2023 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #define _GNU_SOURCE 1 #include #include #include #include #include #include #include /* Default READMORE method. */ #define READMORE_METHOD_DEFAULT 2 /* Default READMORE sleep time in miliseconds. */ #define READMORE_SLEEP_DEFAULT 10 /* Helper function. Intialize *METHOD according to environment variable READMORE_METHOD, and *SLEEP according to environment variable READMORE_SLEEP. */ static void init_readmore (int *method, unsigned int *sleep, FILE **log) { char *env = getenv ("READMORE_METHOD"); if (env == NULL) *method = READMORE_METHOD_DEFAULT; else if (strcmp (env, "1") == 0) *method = 1; else if (strcmp (env, "2") == 0) *method = 2; else /* Default. */ *method = READMORE_METHOD_DEFAULT; env = getenv ("READMORE_SLEEP"); if (env == NULL) *sleep = READMORE_SLEEP_DEFAULT; else *sleep = atoi (env); env = getenv ("READMORE_LOG"); if (env == NULL) *log = NULL; else *log = fopen (env, "w"); } /* Wrap 'read', and modify it's behaviour using READ1 or READMORE style. */ ssize_t read (int fd, void *buf, size_t count) { static ssize_t (*read2) (int fd, void *buf, size_t count) = NULL; static FILE *log; int readmore; #ifdef READMORE readmore = 1; #else readmore = 0; #endif static int readmore_method; static unsigned int readmore_sleep; if (read2 == NULL) { /* Use setenv (v, "", 1) rather than unsetenv (v) to work around https://core.tcl-lang.org/tcl/tktview?name=67fd4f973a "incorrect results of 'info exists' when unset env var in one interp and check for existence from another interp". */ setenv ("LD_PRELOAD", "", 1); read2 = dlsym (RTLD_NEXT, "read"); if (readmore) init_readmore (&readmore_method, &readmore_sleep, &log); } /* Only modify 'read' behaviour when reading from the terminal. */ if (isatty (fd) == 0) goto fallback; if (!readmore) { /* READ1. Force read to return only one byte at a time. */ return read2 (fd, buf, 1); } if (readmore_method == 1) { /* READMORE, method 1. Wait a little before doing a read. */ usleep (readmore_sleep * 1000); return read2 (fd, buf, count); } if (readmore_method == 2) { /* READMORE, method 2. After doing a read, either return or wait a little and do another read, and so on. */ ssize_t res, total; int iteration; int max_iterations = -1; total = 0; for (iteration = 1; ; iteration++) { res = read2 (fd, (char *)buf + total, count - total); if (log != NULL) fprintf (log, "READ (%d): fd: %d, COUNT: %zd, RES: %zd, ERRNO: %s\n", iteration, fd, count - total, res, res == -1 ? strerror (errno) : "none"); if (res == -1) { if (iteration == 1) { /* Error on first read, report. */ total = -1; break; } if (total > 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EIO)) { /* Ignore error, but don't try anymore reading. */ errno = 0; break; } /* Other error, report back. */ total = -1; break; } total += res; if (total == count) /* Buf full, no need to do any more reading. */ break; /* Handle end-of-file. */ if (res == 0) break; if (iteration == max_iterations) break; usleep (readmore_sleep * 1000); } if (log) fprintf (log, "READ returning: RES: %zd, ERRNO: %s\n", total, total == -1 ? strerror (errno) : "none"); return total; } fallback: /* Fallback, regular read. */ return read2 (fd, buf, count); }