| 1 | /* fgets with ERANGE error reporting and size_t buffer length. |
| 2 | Copyright (C) 2018-2019 Free Software Foundation, Inc. |
| 3 | This file is part of the GNU C Library. |
| 4 | |
| 5 | The GNU C Library is free software; you can redistribute it and/or |
| 6 | modify it under the terms of the GNU Lesser General Public |
| 7 | License as published by the Free Software Foundation; either |
| 8 | version 2.1 of the License, or (at your option) any later version. |
| 9 | |
| 10 | The GNU C Library is distributed in the hope that it will be useful, |
| 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 13 | Lesser General Public License for more details. |
| 14 | |
| 15 | You should have received a copy of the GNU Lesser General Public |
| 16 | License along with the GNU C Library; if not, see |
| 17 | <http://www.gnu.org/licenses/>. */ |
| 18 | |
| 19 | #include <assert.h> |
| 20 | #include <errno.h> |
| 21 | #include <stdio.h> |
| 22 | #include <string.h> |
| 23 | |
| 24 | #include "libioP.h" |
| 25 | |
| 26 | /* Return -1 and set errno to EINVAL if it is ERANGE. */ |
| 27 | static ssize_t |
| 28 | fail_no_erange (void) |
| 29 | { |
| 30 | if (errno == ERANGE) |
| 31 | __set_errno (EINVAL); |
| 32 | return -1; |
| 33 | } |
| 34 | |
| 35 | /* Slow path for reading the line. Called with no data in the stream |
| 36 | read buffer. Write data to [BUFFER, BUFFER_END). */ |
| 37 | static ssize_t |
| 38 | readline_slow (FILE *fp, char *buffer, char *buffer_end) |
| 39 | { |
| 40 | char *start = buffer; |
| 41 | |
| 42 | while (buffer < buffer_end) |
| 43 | { |
| 44 | if (__underflow (fp) == EOF) |
| 45 | { |
| 46 | if (_IO_ferror_unlocked (fp)) |
| 47 | /* If the EOF was caused by a read error, report it. */ |
| 48 | return fail_no_erange (); |
| 49 | *buffer = '\0'; |
| 50 | /* Do not include the null terminator. */ |
| 51 | return buffer - start; |
| 52 | } |
| 53 | |
| 54 | /* __underflow has filled the buffer. */ |
| 55 | char *readptr = fp->_IO_read_ptr; |
| 56 | ssize_t readlen = fp->_IO_read_end - readptr; |
| 57 | /* Make sure that __underflow really has acquired some data. */ |
| 58 | assert (readlen > 0); |
| 59 | char *pnl = memchr (readptr, '\n', readlen); |
| 60 | if (pnl != NULL) |
| 61 | { |
| 62 | /* We found the terminator. */ |
| 63 | size_t line_length = pnl - readptr; |
| 64 | if (line_length + 2 > buffer_end - buffer) |
| 65 | /* Not enough room in the caller-supplied buffer. */ |
| 66 | break; |
| 67 | memcpy (buffer, readptr, line_length + 1); |
| 68 | buffer[line_length + 1] = '\0'; |
| 69 | fp->_IO_read_ptr = pnl + 1; |
| 70 | /* Do not include the null terminator. */ |
| 71 | return buffer - start + line_length + 1; |
| 72 | } |
| 73 | |
| 74 | if (readlen >= buffer_end - buffer) |
| 75 | /* Not enough room in the caller-supplied buffer. */ |
| 76 | break; |
| 77 | |
| 78 | /* Save and consume the stream buffer. */ |
| 79 | memcpy (buffer, readptr, readlen); |
| 80 | fp->_IO_read_ptr = fp->_IO_read_end; |
| 81 | buffer += readlen; |
| 82 | } |
| 83 | |
| 84 | /* The line does not fit into the buffer. */ |
| 85 | __set_errno (ERANGE); |
| 86 | return -1; |
| 87 | } |
| 88 | |
| 89 | ssize_t |
| 90 | __libc_readline_unlocked (FILE *fp, char *buffer, size_t buffer_length) |
| 91 | { |
| 92 | char *buffer_end = buffer + buffer_length; |
| 93 | |
| 94 | /* Orient the stream. */ |
| 95 | if (__builtin_expect (fp->_mode, -1) == 0) |
| 96 | _IO_fwide (fp, -1); |
| 97 | |
| 98 | /* Fast path: The line terminator is found in the buffer. */ |
| 99 | char *readptr = fp->_IO_read_ptr; |
| 100 | ssize_t readlen = fp->_IO_read_end - readptr; |
| 101 | off64_t start_offset; /* File offset before reading anything. */ |
| 102 | if (readlen > 0) |
| 103 | { |
| 104 | char *pnl = memchr (readptr, '\n', readlen); |
| 105 | if (pnl != NULL) |
| 106 | { |
| 107 | size_t line_length = pnl - readptr; |
| 108 | /* Account for line and null terminators. */ |
| 109 | if (line_length + 2 > buffer_length) |
| 110 | { |
| 111 | __set_errno (ERANGE); |
| 112 | return -1; |
| 113 | } |
| 114 | memcpy (buffer, readptr, line_length + 1); |
| 115 | buffer[line_length + 1] = '\0'; |
| 116 | /* Consume the entire line. */ |
| 117 | fp->_IO_read_ptr = pnl + 1; |
| 118 | return line_length + 1; |
| 119 | } |
| 120 | |
| 121 | /* If the buffer does not have enough space for what is pending |
| 122 | in the stream (plus a NUL terminator), the buffer is too |
| 123 | small. */ |
| 124 | if (readlen + 1 > buffer_length) |
| 125 | { |
| 126 | __set_errno (ERANGE); |
| 127 | return -1; |
| 128 | } |
| 129 | |
| 130 | /* End of line not found. We need all the buffered data. Fall |
| 131 | through to the slow path. */ |
| 132 | memcpy (buffer, readptr, readlen); |
| 133 | buffer += readlen; |
| 134 | /* The original length is invalid after this point. Use |
| 135 | buffer_end instead. */ |
| 136 | #pragma GCC poison buffer_length |
| 137 | /* Read the old offset before updating the read pointer. */ |
| 138 | start_offset = __ftello64 (fp); |
| 139 | fp->_IO_read_ptr = fp->_IO_read_end; |
| 140 | } |
| 141 | else |
| 142 | { |
| 143 | readlen = 0; |
| 144 | start_offset = __ftello64 (fp); |
| 145 | } |
| 146 | |
| 147 | /* Slow path: Read more data from the underlying file. We need to |
| 148 | restore the file pointer if the buffer is too small. First, |
| 149 | check if the __ftello64 call above failed. */ |
| 150 | if (start_offset < 0) |
| 151 | return fail_no_erange (); |
| 152 | |
| 153 | ssize_t result = readline_slow (fp, buffer, buffer_end); |
| 154 | if (result < 0) |
| 155 | { |
| 156 | if (errno == ERANGE) |
| 157 | { |
| 158 | /* Restore the file pointer so that the caller may read the |
| 159 | same line again. */ |
| 160 | if (__fseeko64 (fp, start_offset, SEEK_SET) < 0) |
| 161 | return fail_no_erange (); |
| 162 | __set_errno (ERANGE); |
| 163 | } |
| 164 | /* Do not restore the file position on other errors; it is |
| 165 | likely that the __fseeko64 call would fail, too. */ |
| 166 | return -1; |
| 167 | } |
| 168 | return readlen + result; |
| 169 | } |
| 170 | libc_hidden_def (__libc_readline_unlocked) |
| 171 | |