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. */
27static ssize_t
28fail_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). */
37static ssize_t
38readline_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
89ssize_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}
170libc_hidden_def (__libc_readline_unlocked)
171