blob: 18e8a1c0e575ff4a3543fea8036dad87ffd8fb85 [file] [log] [blame]
David 'Digit' Turner0a879bf2011-05-12 18:45:18 +02001/* Copyright (C) 2011 The Android Open Source Project
2**
3** This software is licensed under the terms of the GNU General Public
4** License version 2, as published by the Free Software Foundation, and
5** may be copied, distributed, and modified under those terms.
6**
7** This program is distributed in the hope that it will be useful,
8** but WITHOUT ANY WARRANTY; without even the implied warranty of
9** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10** GNU General Public License for more details.
11*/
12#include <errno.h>
13#include "android/utils/system.h"
14#include "android/utils/assert.h"
15#include "android/utils/lineinput.h"
16
17struct LineInput {
18 char* line;
19 size_t line_size;
20 int line_num;
21 int error;
22 int eof;
23
24 struct {
25 FILE* file;
26 } std;
27
28 char line0[128];
29};
30
31/* Error codes returned by the internal line reading function(s) */
32enum {
33 LINEINPUT_ERROR = -1,
34 LINEINPUT_EOF = -2,
35};
36
37
38static LineInput*
39_lineInput_new( void )
40{
41 LineInput* input;
42
43 ANEW0(input);
44 input->line = input->line0;
45 input->line_size = sizeof(input->line0);
46
47 return input;
48}
49
50/* Create a LineInput object that reads from a FILE* object */
51LineInput*
52lineInput_newFromStdFile( FILE* file )
53{
54 LineInput* input = _lineInput_new();
55
56 input->std.file = file;
57 return input;
58}
59
60/* Grow the line buffer a bit */
61static void
62_lineInput_grow( LineInput* input )
63{
64 char* line;
65
66 input->line_size += input->line_size >> 1;
67 line = input->line;
68 if (line == input->line0)
69 line = NULL;
70
71 AARRAY_RENEW(line, input->line_size);
72 input->line = line;
73}
74
75/* Forward declaration */
76static int _lineInput_getLineFromStdFile( LineInput* input, FILE* file );
77
78const char*
79lineInput_getLine( LineInput* input )
80{
81 return lineInput_getLineAndSize(input, NULL);
82}
83
84const char*
85lineInput_getLineAndSize( LineInput* input, size_t *pSize )
86{
87 int ret;
88
89 /* be safe */
90 if (pSize)
91 *pSize = 0;
92
93 /* check parameters */
94 if (input == NULL) {
95 errno = EINVAL;
96 return NULL;
97 }
98
99 /* check state */
100 if (input->error) {
101 return NULL;
102 }
103 if (input->eof) {
104 return NULL;
105 }
106
107 ret = _lineInput_getLineFromStdFile(input, input->std.file);
108 if (ret >= 0) {
109 input->line_num += 1;
110 if (pSize != NULL) {
111 *pSize = ret;
112 return input->line;
113 }
114 return input->line;
115 }
116 if (ret == LINEINPUT_EOF) {
117 input->line_num += 1;
118 input->eof = 1;
119 return NULL;
120 }
121 if (ret == LINEINPUT_ERROR) {
122 input->error = errno;
123 return NULL;
124 }
125 AASSERT_UNREACHED();
126 return NULL;
127}
128
129/* Returns the number of the last line read by lineInput_getLine */
130int
131lineInput_getLineNumber( LineInput* input )
132{
133 return input->line_num;
134}
135
136/* Returns TRUE iff the end of file was reached */
137int
138lineInput_isEof( LineInput* input )
139{
140 return (input->eof != 0);
141}
142
143/* Return the error condition of a LineInput object.
144 * These are standard errno code for the last operation.
145 * Note: EOF corresponds to 0 here.
146 */
147int
148lineInput_getError( LineInput* input )
149{
150 return input->error;
151}
152
153void
154lineInput_free( LineInput* input )
155{
156 if (input != NULL) {
157 if (input->line != NULL) {
158 if (input->line != input->line0)
159 AFREE(input->line);
160 input->line = NULL;
161 input->line_size = 0;
162 }
163 AFREE(input);
164 }
165}
166
167
168/* Internal function used to read a new line from a FILE* using fgets().
169 * We assume that this is more efficient than calling fgetc() in a loop.
170 *
171 * Return length of line, or either LINEINPUT_EOF / LINEINPUT_ERROR
172 */
173static int
174_lineInput_getLineFromStdFile( LineInput* input, FILE* file )
175{
176 int offset = 0;
177 char* p;
178
179 input->line[0] = '\0';
180
181 for (;;) {
182 char* buffer = input->line + offset;
183 int avail = input->line_size - offset;
184
185 if (!fgets(buffer, avail, file)) {
186 /* We either reached the end of file or an i/o error occured.
187 * If we already read line data, just return it this time.
188 */
189 if (offset > 0) {
190 return offset;
191 }
192 goto INPUT_ERROR;
193 }
194
195 /* Find the terminating zero */
196 p = memchr(buffer, '\0', avail);
197 AASSERT(p != NULL);
198
199 if (p == buffer) {
200 /* This happens when the file has an embedded '\0', treat it
201 * as an eof, or bad things usually happen after that. */
202 input->eof = 1;
203 if (offset > 0)
204 return offset;
205 else
206 return LINEINPUT_EOF;
207 }
208
209 if (p[-1] != '\n' && p[-1] != '\r') {
210 /* This happens when the line is longer than our current buffer,
211 * so grow its size and try again. */
212 offset = p - input->line;
213 _lineInput_grow(input);
214 continue;
215 }
216
217 break;
218 }
219
220 /* Get rid of trailing newline(s). Consider: \n, \r, and \r\n */
221 if (p[-1] == '\n') {
222 p -= 1;
223 if (p > input->line && p[-1] == '\r') {
224 p -= 1;
225 }
226 p[0] = '\0';
227 }
228 else if (p[-1] == '\r') {
229 p -= 1;
230 p[0] = '\0';
231 }
232
233 /* We did it */
234 return (p - input->line);
235
236INPUT_ERROR:
237 if (feof(file)) {
238 input->eof = 1;
239 return LINEINPUT_EOF;
240 }
241 input->error = errno;
242 return LINEINPUT_ERROR;
243}
244