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, 2009,
5   2010 Free 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/* Use this to suppress gcc's `...may be used before initialized' warnings. */
55#ifdef lint
56# define IF_LINT(Code) Code
57#else
58# define IF_LINT(Code) /* empty */
59#endif
60
61/* The maximum value that getndelim2 can return without suffering from
62   overflow problems, either internally (because of pointer
63   subtraction overflow) or due to the API (because of ssize_t).  */
64#define GETNDELIM2_MAXIMUM (PTRDIFF_MAX < SSIZE_MAX ? PTRDIFF_MAX : SSIZE_MAX)
65
66/* Try to add at least this many bytes when extending the buffer.
67   MIN_CHUNK must be no greater than GETNDELIM2_MAXIMUM.  */
68#define MIN_CHUNK 64
69
70ssize_t
71getndelim2 (char **lineptr, size_t *linesize, size_t offset, size_t nmax,
72            int delim1, int delim2, FILE *stream)
73{
74  size_t nbytes_avail;          /* Allocated but unused bytes in *LINEPTR.  */
75  char *read_pos;               /* Where we're reading into *LINEPTR. */
76  ssize_t bytes_stored = -1;
77  char *ptr = *lineptr;
78  size_t size = *linesize;
79  bool found_delimiter;
80
81  if (!ptr)
82    {
83      size = nmax < MIN_CHUNK ? nmax : MIN_CHUNK;
84      ptr = malloc (size);
85      if (!ptr)
86        return -1;
87    }
88
89  if (size < offset)
90    goto done;
91
92  nbytes_avail = size - offset;
93  read_pos = ptr + offset;
94
95  if (nbytes_avail == 0 && nmax <= size)
96    goto done;
97
98  /* Normalize delimiters, since memchr2 doesn't handle EOF.  */
99  if (delim1 == EOF)
100    delim1 = delim2;
101  else if (delim2 == EOF)
102    delim2 = delim1;
103
104  flockfile (stream);
105
106  found_delimiter = false;
107  do
108    {
109      /* Here always ptr + size == read_pos + nbytes_avail.
110         Also nbytes_avail > 0 || size < nmax.  */
111
112      int c IF_LINT (= 0);
113      const char *buffer;
114      size_t buffer_len;
115
116      buffer = freadptr (stream, &buffer_len);
117      if (buffer)
118        {
119          if (delim1 != EOF)
120            {
121              const char *end = memchr2 (buffer, delim1, delim2, buffer_len);
122              if (end)
123                {
124                  buffer_len = end - buffer + 1;
125                  found_delimiter = true;
126                }
127            }
128        }
129      else
130        {
131          c = getc (stream);
132          if (c == EOF)
133            {
134              /* Return partial line, if any.  */
135              if (read_pos == ptr)
136                goto unlock_done;
137              else
138                break;
139            }
140          if (c == delim1 || c == delim2)
141            found_delimiter = true;
142          buffer_len = 1;
143        }
144
145      /* We always want at least one byte left in the buffer, since we
146         always (unless we get an error while reading the first byte)
147         NUL-terminate the line buffer.  */
148
149      if (nbytes_avail < buffer_len + 1 && size < nmax)
150        {
151          /* Grow size proportionally, not linearly, to avoid O(n^2)
152             running time.  */
153          size_t newsize = size < MIN_CHUNK ? size + MIN_CHUNK : 2 * size;
154          char *newptr;
155
156          /* Increase newsize so that it becomes
157             >= (read_pos - ptr) + buffer_len.  */
158          if (newsize - (read_pos - ptr) < buffer_len + 1)
159            newsize = (read_pos - ptr) + buffer_len + 1;
160          /* Respect nmax.  This handles possible integer overflow.  */
161          if (! (size < newsize && newsize <= nmax))
162            newsize = nmax;
163
164          if (GETNDELIM2_MAXIMUM < newsize - offset)
165            {
166              size_t newsizemax = offset + GETNDELIM2_MAXIMUM + 1;
167              if (size == newsizemax)
168                goto unlock_done;
169              newsize = newsizemax;
170            }
171
172          nbytes_avail = newsize - (read_pos - ptr);
173          newptr = realloc (ptr, newsize);
174          if (!newptr)
175            goto unlock_done;
176          ptr = newptr;
177          size = newsize;
178          read_pos = size - nbytes_avail + ptr;
179        }
180
181      /* Here, if size < nmax, nbytes_avail >= buffer_len + 1.
182         If size == nmax, nbytes_avail > 0.  */
183
184      if (1 < nbytes_avail)
185        {
186          size_t copy_len = nbytes_avail - 1;
187          if (buffer_len < copy_len)
188            copy_len = buffer_len;
189          if (buffer)
190            memcpy (read_pos, buffer, copy_len);
191          else
192            *read_pos = c;
193          read_pos += copy_len;
194          nbytes_avail -= copy_len;
195        }
196
197      /* Here still nbytes_avail > 0.  */
198
199      if (buffer && freadseek (stream, buffer_len))
200        goto unlock_done;
201    }
202  while (!found_delimiter);
203
204  /* Done - NUL terminate and return the number of bytes read.
205     At this point we know that nbytes_avail >= 1.  */
206  *read_pos = '\0';
207
208  bytes_stored = read_pos - (ptr + offset);
209
210 unlock_done:
211  funlockfile (stream);
212
213 done:
214  *lineptr = ptr;
215  *linesize = size;
216  return bytes_stored ? bytes_stored : -1;
217}
218