Optimize getndelim2 to use block operations when possible.
[gnulib.git] / lib / getndelim2.c
1 /* getndelim2 - Read a line from a stream, stopping at one of 2 delimiters,
2    with bounded memory allocation.
3
4    Copyright (C) 1993, 1996, 1997, 1998, 2000, 2003, 2004, 2006, 2008 Free
5    Software Foundation, Inc.
6
7    This program is free software: you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 3 of the License, or
10    (at your option) any later version.
11
12    This program 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 General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
19
20 /* Originally written by Jan Brittenson, bson@gnu.ai.mit.edu.  */
21
22 #include <config.h>
23
24 #include "getndelim2.h"
25
26 #include <stdbool.h>
27 #include <stddef.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #if USE_UNLOCKED_IO
32 # include "unlocked-io.h"
33 #endif
34 #if !HAVE_FLOCKFILE
35 # undef flockfile
36 # define flockfile(x) ((void) 0)
37 #endif
38 #if !HAVE_FUNLOCKFILE
39 # undef funlockfile
40 # define funlockfile(x) ((void) 0)
41 #endif
42
43 #include <limits.h>
44 #include <stdint.h>
45
46 #include "freadptr.h"
47 #include "freadseek.h"
48 #include "memchr2.h"
49
50 #ifndef SSIZE_MAX
51 # define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2))
52 #endif
53
54 /* The maximum value that getndelim2 can return without suffering from
55    overflow problems, either internally (because of pointer
56    subtraction overflow) or due to the API (because of ssize_t).  */
57 #define GETNDELIM2_MAXIMUM (PTRDIFF_MAX < SSIZE_MAX ? PTRDIFF_MAX : SSIZE_MAX)
58
59 /* Try to add at least this many bytes when extending the buffer.
60    MIN_CHUNK must be no greater than GETNDELIM2_MAXIMUM.  */
61 #define MIN_CHUNK 64
62
63 ssize_t
64 getndelim2 (char **lineptr, size_t *linesize, size_t offset, size_t nmax,
65             int delim1, int delim2, FILE *stream)
66 {
67   size_t nbytes_avail;          /* Allocated but unused bytes in *LINEPTR.  */
68   char *read_pos;               /* Where we're reading into *LINEPTR. */
69   ssize_t bytes_stored = -1;
70   char *ptr = *lineptr;
71   size_t size = *linesize;
72   bool done = false;
73
74   if (!ptr)
75     {
76       size = nmax < MIN_CHUNK ? nmax : MIN_CHUNK;
77       ptr = malloc (size);
78       if (!ptr)
79         return -1;
80     }
81
82   if (size < offset)
83     goto done;
84
85   nbytes_avail = size - offset;
86   read_pos = ptr + offset;
87
88   if (nbytes_avail == 0 && nmax <= size)
89     goto done;
90
91   /* Normalize delimiters, since memchr2 doesn't handle EOF.  */
92   if (delim1 == EOF)
93     delim1 = delim2;
94   else if (delim2 == EOF)
95     delim2 = delim1;
96
97   flockfile (stream);
98
99   while (!done)
100     {
101       /* Here always ptr + size == read_pos + nbytes_avail.  */
102
103       int c;
104       const char *buffer;
105       size_t buffer_len;
106
107       buffer = freadptr (stream, &buffer_len);
108       if (buffer)
109         {
110           if (delim1 != EOF)
111             {
112               const char *end = memchr2 (buffer, delim1, delim2, buffer_len);
113               if (end)
114                 {
115                   buffer_len = end - buffer + 1;
116                   done = true;
117                 }
118             }
119         }
120       else
121         {
122           c = getc (stream);
123           if (c == EOF)
124             {
125               /* Return partial line, if any.  */
126               if (read_pos == ptr)
127                 goto unlock_done;
128               else
129                 break;
130             }
131           if (c == delim1 || c == delim2)
132             done = true;
133           buffer_len = 1;
134         }
135
136       /* We always want at least one byte left in the buffer, since we
137          always (unless we get an error while reading the first byte)
138          NUL-terminate the line buffer.  */
139
140       if (nbytes_avail < 1 + buffer_len && size < nmax)
141         {
142           size_t newsize = size < MIN_CHUNK ? size + MIN_CHUNK : 2 * size;
143           char *newptr;
144
145           if (newsize < buffer_len)
146             newsize = buffer_len + size;
147           if (! (size < newsize && newsize <= nmax))
148             newsize = nmax;
149
150           if (GETNDELIM2_MAXIMUM < newsize - offset)
151             {
152               size_t newsizemax = offset + GETNDELIM2_MAXIMUM + 1;
153               if (size == newsizemax)
154                 goto unlock_done;
155               newsize = newsizemax;
156             }
157
158           nbytes_avail = newsize - (read_pos - ptr);
159           newptr = realloc (ptr, newsize);
160           if (!newptr)
161             goto unlock_done;
162           ptr = newptr;
163           size = newsize;
164           read_pos = size - nbytes_avail + ptr;
165         }
166
167       if (1 < nbytes_avail)
168         {
169           size_t copy_len = nbytes_avail - 1;
170           if (buffer_len < copy_len)
171             copy_len = buffer_len;
172           if (buffer)
173             memcpy (read_pos, buffer, copy_len);
174           else
175             *read_pos = c;
176           read_pos += copy_len;
177           nbytes_avail -= copy_len;
178         }
179       if (buffer && freadseek (stream, buffer_len))
180         goto unlock_done;
181     }
182
183   /* Done - NUL terminate and return the number of bytes read.
184      At this point we know that nbytes_avail >= 1.  */
185   *read_pos = '\0';
186
187   bytes_stored = read_pos - (ptr + offset);
188
189  unlock_done:
190   funlockfile (stream);
191
192  done:
193   *lineptr = ptr;
194   *linesize = size;
195   return bytes_stored ? bytes_stored : -1;
196 }