blob: 18e8a1c0e575ff4a3543fea8036dad87ffd8fb85 [file] [log] [blame]
/* 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 <errno.h>
#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;
}