1 | /* fgets with ERANGE error reporting and size_t buffer length. |
2 | Copyright (C) 2018 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 | |