|  | /* This is part of GDB, the GNU debugger. | 
|  |  | 
|  | Copyright 2011-2024 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 <http://www.gnu.org/licenses/>. | 
|  | */ | 
|  |  | 
|  | #define _GNU_SOURCE 1 | 
|  | #include <dlfcn.h> | 
|  | #include <unistd.h> | 
|  | #include <fcntl.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <errno.h> | 
|  | #include <stdio.h> | 
|  |  | 
|  | /* Default READMORE method.  */ | 
|  | #define READMORE_METHOD_DEFAULT 2 | 
|  |  | 
|  | /* Default READMORE sleep time in miliseconds.  */ | 
|  | #define READMORE_SLEEP_DEFAULT 10 | 
|  |  | 
|  | /* Helper function.  Initialize *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); | 
|  | } |