| /* This is part of GDB, the GNU debugger. |
| |
| Copyright 2011-2021 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. 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); |
| } |