/* getline.c * version 0.1, released 2004-08-31. * * http://ioioio.net./misc/getline.c * * Copyright (C) 2004 Andy Goth * * This file is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2.0 of the License, or (at your option) * any later version. To obtain a copy of the License, see the following: * * http://www.gnu.org./licenses/gpl.html (HTML format) * http://www.gnu.org./licenses/gpl.txt (Plain text ) * * This implementation of getline() is loosely based on _IO_getdelim() as found * in the GNU C Library, glibc-2.3.3. * * You are encouraged to incorporate this file into your own projects, provided * you observe the terms of the License. Feel free to send me any bug reports, * patches, or news related to this file. */ #include #include #include #include /* getline() - line-based string input with automatic allocation * * getline() reads an entire line, storing the address of the buffer containing * the text into *buf. The buffer is NUL-terminated and includes the newline * character, if a newline delimiter was found. * * If *buf is NULL, getline() allocates a buffer for containing the line, which * must be freed by the user program. Alternatively, before calling getline(), * *buf can contain a poiner to a malloc()-allocated buffer *len bytes in * size. If the buffer isn't large enough to hold the line read in, getline() * grows the buffer with realloc(), updating *buf and *len as necessary. * * On success, getline() returns the number of characters read, including the * newline, but not including the terminating NUL. This value can be used to * handle embedded NUL characters in the line read. On failure to read a line * (including end-of-file condition), -1 is returned, and errno may be set. * getline() always updates *buf and *len to reflect the buffer address and * size. errno is set to EINVAL if bad parameters are passed to getline(). * * XXX: Unlike GNU getline(), this function cannot correctly handle files whose * last line contains embedded NUL bytes but lacks a final newline character. * However, the only time this is likely to happen is if getline() is used to * read binaries. In this exceptional condition, bytes including and following * the first NUL are not counted as part of the return value. */ ssize_t getline(char** buf, size_t* len, FILE* stream) { int i = 0, new_len, my_malloc = 0; char* nl, *new_buf; if (buf == NULL || len == NULL) { errno = EINVAL; return -1; } if (*buf == NULL || *len == 0) { *buf = NULL; *len = 0; my_malloc = 1; } if (*len <= 60) goto alloc; while (1) { if (fgets(*buf + i, *len - i, stream) == NULL) { if (!feof(stream) || i == 0) { /* The read failed with an error, or the file is empty. */ goto error; } else { /* The final line contains no newline, and the previous fgets() * read exactly as many characters as remained in the line. */ return i; } } if (feof(stream)) { /* We were able to successfully read at least one byte before * encountering EOF, but the file did not end in a newline. * Let's hope the last line doesn't contain any NUL bytes. */ return i + strlen(*buf + i); } if ((nl = memchr(*buf + i, '\n', *len - i - 1)) == NULL) { /* No newline found. Either we're at the end of a file with no * newline after its final line, or we need to grow the buffer. * This chunk of code is also used to allocate the initial buffer, * since realloc(NULL, x) works the same as malloc(x). */ i = *len - 1; alloc: new_len = *len < 60 ? 120 : *len * 2; if ((new_buf = realloc(*buf, new_len)) == NULL) goto error; *buf = new_buf; *len = new_len; } else { /* We have the newline, so we're done. */ return nl - *buf + 1; } /* Try again. */ } error: if (my_malloc) { free(*buf); *buf = NULL; *len = 0; } return -1; } /* Uncomment to provide a test utility. Delete or rename the above getline() * function to test GNU getline(), if present in your libc. */ #if 0 int main(int argc, char** argv) { char* buf = NULL; int len = 0, ret; while (1) { printf("getline() = %d", ret = getline(&buf, &len, stdin)); if (ret == -1) { if (feof(stdin)) printf("; EOF\n"); else perror("getline"); break; } else { printf("; buf = \""); fwrite(buf, ret, 1, stdout); printf("\"; len = %d\n", len); } } free(buf); return EXIT_SUCCESS; } #endif /* vim: set ts=4 sts=4 sw=4 tw=80 et: */