doc: Prefer https urls where possible.
[gnulib.git] / lib / getndelim2.c
blobdb61e2a5e6ed952350dbaa35187365c1a4b25d4e
1 /* getndelim2 - Read a line from a stream, stopping at one of 2 delimiters,
2 with bounded memory allocation.
4 Copyright (C) 1993, 1996-1998, 2000, 2003-2004, 2006, 2008-2024 Free
5 Software Foundation, Inc.
7 This file is free software: you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as
9 published by the Free Software Foundation; either version 2.1 of the
10 License, or (at your option) any later version.
12 This file is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with this program. If not, see <https://www.gnu.org/licenses/>. */
20 /* Originally written by Jan Brittenson, bson@gnu.ai.mit.edu. */
22 #include <config.h>
24 #include "getndelim2.h"
26 #include <stddef.h>
27 #include <stdlib.h>
28 #include <string.h>
30 #if USE_UNLOCKED_IO
31 # include "unlocked-io.h"
32 #endif
33 #if !HAVE_FLOCKFILE
34 # undef flockfile
35 # define flockfile(x) ((void) 0)
36 #endif
37 #if !HAVE_FUNLOCKFILE
38 # undef funlockfile
39 # define funlockfile(x) ((void) 0)
40 #endif
42 #include <limits.h>
43 #include <stdint.h>
45 #include "freadptr.h"
46 #include "freadseek.h"
47 #include "memchr2.h"
49 /* Avoid false GCC warning "'c' may be used uninitialized". */
50 #if defined GCC_LINT || defined lint
51 # define IF_LINT(Code) Code
52 #else
53 # define IF_LINT(Code) /* empty */
54 #endif
56 /* The maximum value that getndelim2 can return without suffering from
57 overflow problems, either internally (because of pointer
58 subtraction overflow) or due to the API (because of ssize_t). */
59 #define GETNDELIM2_MAXIMUM (PTRDIFF_MAX < SSIZE_MAX ? PTRDIFF_MAX : SSIZE_MAX)
61 /* Try to add at least this many bytes when extending the buffer.
62 MIN_CHUNK must be no greater than GETNDELIM2_MAXIMUM. */
63 #define MIN_CHUNK 64
65 ssize_t
66 getndelim2 (char **lineptr, size_t *linesize, size_t offset, size_t nmax,
67 int delim1, int delim2, FILE *stream)
69 size_t nbytes_avail; /* Allocated but unused bytes in *LINEPTR. */
70 char *read_pos; /* Where we're reading into *LINEPTR. */
71 ssize_t bytes_stored = -1;
72 char *ptr = *lineptr;
73 size_t size = *linesize;
74 bool found_delimiter;
76 if (!ptr)
78 size = nmax < MIN_CHUNK ? nmax : MIN_CHUNK;
79 ptr = malloc (size);
80 if (!ptr)
81 return -1;
84 if (size < offset)
85 goto done;
87 nbytes_avail = size - offset;
88 read_pos = ptr + offset;
90 if (nbytes_avail == 0 && nmax <= size)
91 goto done;
93 /* Normalize delimiters, since memchr2 doesn't handle EOF. */
94 if (delim1 == EOF)
95 delim1 = delim2;
96 else if (delim2 == EOF)
97 delim2 = delim1;
99 flockfile (stream);
101 found_delimiter = false;
104 /* Here always ptr + size == read_pos + nbytes_avail.
105 Also nbytes_avail > 0 || size < nmax. */
107 int c IF_LINT (= EOF);
108 const char *buffer;
109 size_t buffer_len;
111 buffer = freadptr (stream, &buffer_len);
112 if (buffer)
114 if (delim1 != EOF)
116 const char *end = memchr2 (buffer, delim1, delim2, buffer_len);
117 if (end)
119 buffer_len = end - buffer + 1;
120 found_delimiter = true;
124 else
126 c = getc (stream);
127 if (c == EOF)
129 /* Return partial line, if any. */
130 if (read_pos == ptr)
131 goto unlock_done;
132 else
133 break;
135 if (c == delim1 || c == delim2)
136 found_delimiter = true;
137 buffer_len = 1;
140 /* We always want at least one byte left in the buffer, since we
141 always (unless we get an error while reading the first byte)
142 NUL-terminate the line buffer. */
144 if (nbytes_avail < buffer_len + 1 && size < nmax)
146 /* Grow size proportionally, not linearly, to avoid O(n^2)
147 running time. */
148 size_t newsize = size < MIN_CHUNK ? size + MIN_CHUNK : 2 * size;
149 char *newptr;
151 /* Increase newsize so that it becomes
152 >= (read_pos - ptr) + buffer_len. */
153 if (newsize - (read_pos - ptr) < buffer_len + 1)
154 newsize = (read_pos - ptr) + buffer_len + 1;
155 /* Respect nmax. This handles possible integer overflow. */
156 if (! (size < newsize && newsize <= nmax))
157 newsize = nmax;
159 if (GETNDELIM2_MAXIMUM < newsize - offset)
161 size_t newsizemax = offset + GETNDELIM2_MAXIMUM + 1;
162 if (size == newsizemax)
163 goto unlock_done;
164 newsize = newsizemax;
167 nbytes_avail = newsize - (read_pos - ptr);
168 newptr = realloc (ptr, newsize);
169 if (!newptr)
170 goto unlock_done;
171 ptr = newptr;
172 size = newsize;
173 read_pos = size - nbytes_avail + ptr;
176 /* Here, if size < nmax, nbytes_avail >= buffer_len + 1.
177 If size == nmax, nbytes_avail > 0. */
179 if (1 < nbytes_avail)
181 size_t copy_len = nbytes_avail - 1;
182 if (buffer_len < copy_len)
183 copy_len = buffer_len;
184 if (buffer)
185 memcpy (read_pos, buffer, copy_len);
186 else
187 *read_pos = c;
188 read_pos += copy_len;
189 nbytes_avail -= copy_len;
192 /* Here still nbytes_avail > 0. */
194 if (buffer && freadseek (stream, buffer_len))
195 goto unlock_done;
197 while (!found_delimiter);
199 /* Done - NUL terminate and return the number of bytes read.
200 At this point we know that nbytes_avail >= 1. */
201 *read_pos = '\0';
203 bytes_stored = read_pos - (ptr + offset);
205 unlock_done:
206 funlockfile (stream);
208 done:
209 *lineptr = ptr;
210 *linesize = size;
211 return bytes_stored ? bytes_stored : -1;