]>
Commit | Line | Data |
---|---|---|
3f5e3f5d | 1 | /* fgets with ERANGE error reporting and size_t buffer length. |
04277e02 | 2 | Copyright (C) 2018-2019 Free Software Foundation, Inc. |
3f5e3f5d FW |
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) |