/* Copyright (C) 2011 The Android Open Source Project ** ** This software is licensed under the terms of the GNU General Public ** License version 2, as published by the Free Software Foundation, and ** may be copied, distributed, and modified under those terms. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. */ #include #include "android/utils/system.h" #include "android/utils/assert.h" #include "android/utils/lineinput.h" struct LineInput { char* line; size_t line_size; int line_num; int error; int eof; struct { FILE* file; } std; char line0[128]; }; /* Error codes returned by the internal line reading function(s) */ enum { LINEINPUT_ERROR = -1, LINEINPUT_EOF = -2, }; static LineInput* _lineInput_new( void ) { LineInput* input; ANEW0(input); input->line = input->line0; input->line_size = sizeof(input->line0); return input; } /* Create a LineInput object that reads from a FILE* object */ LineInput* lineInput_newFromStdFile( FILE* file ) { LineInput* input = _lineInput_new(); input->std.file = file; return input; } /* Grow the line buffer a bit */ static void _lineInput_grow( LineInput* input ) { char* line; input->line_size += input->line_size >> 1; line = input->line; if (line == input->line0) line = NULL; AARRAY_RENEW(line, input->line_size); input->line = line; } /* Forward declaration */ static int _lineInput_getLineFromStdFile( LineInput* input, FILE* file ); const char* lineInput_getLine( LineInput* input ) { return lineInput_getLineAndSize(input, NULL); } const char* lineInput_getLineAndSize( LineInput* input, size_t *pSize ) { int ret; /* be safe */ if (pSize) *pSize = 0; /* check parameters */ if (input == NULL) { errno = EINVAL; return NULL; } /* check state */ if (input->error) { return NULL; } if (input->eof) { return NULL; } ret = _lineInput_getLineFromStdFile(input, input->std.file); if (ret >= 0) { input->line_num += 1; if (pSize != NULL) { *pSize = ret; return input->line; } return input->line; } if (ret == LINEINPUT_EOF) { input->line_num += 1; input->eof = 1; return NULL; } if (ret == LINEINPUT_ERROR) { input->error = errno; return NULL; } AASSERT_UNREACHED(); return NULL; } /* Returns the number of the last line read by lineInput_getLine */ int lineInput_getLineNumber( LineInput* input ) { return input->line_num; } /* Returns TRUE iff the end of file was reached */ int lineInput_isEof( LineInput* input ) { return (input->eof != 0); } /* Return the error condition of a LineInput object. * These are standard errno code for the last operation. * Note: EOF corresponds to 0 here. */ int lineInput_getError( LineInput* input ) { return input->error; } void lineInput_free( LineInput* input ) { if (input != NULL) { if (input->line != NULL) { if (input->line != input->line0) AFREE(input->line); input->line = NULL; input->line_size = 0; } AFREE(input); } } /* Internal function used to read a new line from a FILE* using fgets(). * We assume that this is more efficient than calling fgetc() in a loop. * * Return length of line, or either LINEINPUT_EOF / LINEINPUT_ERROR */ static int _lineInput_getLineFromStdFile( LineInput* input, FILE* file ) { int offset = 0; char* p; input->line[0] = '\0'; for (;;) { char* buffer = input->line + offset; int avail = input->line_size - offset; if (!fgets(buffer, avail, file)) { /* We either reached the end of file or an i/o error occured. * If we already read line data, just return it this time. */ if (offset > 0) { return offset; } goto INPUT_ERROR; } /* Find the terminating zero */ p = memchr(buffer, '\0', avail); AASSERT(p != NULL); if (p == buffer) { /* This happens when the file has an embedded '\0', treat it * as an eof, or bad things usually happen after that. */ input->eof = 1; if (offset > 0) return offset; else return LINEINPUT_EOF; } if (p[-1] != '\n' && p[-1] != '\r') { /* This happens when the line is longer than our current buffer, * so grow its size and try again. */ offset = p - input->line; _lineInput_grow(input); continue; } break; } /* Get rid of trailing newline(s). Consider: \n, \r, and \r\n */ if (p[-1] == '\n') { p -= 1; if (p > input->line && p[-1] == '\r') { p -= 1; } p[0] = '\0'; } else if (p[-1] == '\r') { p -= 1; p[0] = '\0'; } /* We did it */ return (p - input->line); INPUT_ERROR: if (feof(file)) { input->eof = 1; return LINEINPUT_EOF; } input->error = errno; return LINEINPUT_ERROR; }