From: Lennart Poettering Date: Fri, 22 Sep 2017 15:55:53 +0000 (+0200) Subject: fileio: add new helper call read_line() as bounded getline() replacement X-Git-Tag: v235~67^2~12 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=4f9a66a32dda1d9a28f9bb3fa31c2148524bc46a;p=platform%2Fupstream%2Fsystemd.git fileio: add new helper call read_line() as bounded getline() replacement read_line() is much like getline(), and returns a line read from a FILE*, of arbitrary sizes. In contrast to gets() it will grow the buffer dynamically, and in contrast to getline() it will place a user-specified boundary on the line. --- diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 6220d1c..fc8776d 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -1526,3 +1526,80 @@ int mkdtemp_malloc(const char *template, char **ret) { *ret = p; return 0; } + +int read_line(FILE *f, size_t limit, char **ret) { + _cleanup_free_ char *buffer = NULL; + size_t n = 0, allocated = 0, count = 0; + int r; + + assert(f); + + /* Something like a bounded version of getline(). + * + * Considers EOF, \n and \0 end of line delimiters, and does not include these delimiters in the string + * returned. + * + * Returns the number of bytes read from the files (i.e. including delimiters — this hence usually differs from + * the number of characters in the returned string). When EOF is hit, 0 is returned. + * + * The input parameter limit is the maximum numbers of characters in the returned string, i.e. excluding + * delimiters. If the limit is hit we fail and return -ENOBUFS. + * + * If a line shall be skipped ret may be initialized as NULL. */ + + if (ret) { + if (!GREEDY_REALLOC(buffer, allocated, 1)) + return -ENOMEM; + } + + flockfile(f); + + for (;;) { + int c; + + if (n >= limit) { + funlockfile(f); + return -ENOBUFS; + } + + errno = 0; + c = fgetc_unlocked(f); + if (c == EOF) { + /* if we read an error, and have no data to return, then propagate the error */ + if (ferror_unlocked(f) && n == 0) { + r = errno > 0 ? -errno : -EIO; + funlockfile(f); + return r; + } + + break; + } + + count++; + + if (IN_SET(c, '\n', 0)) /* Reached a delimiter */ + break; + + if (ret) { + if (!GREEDY_REALLOC(buffer, allocated, n + 2)) { + funlockfile(f); + return -ENOMEM; + } + + buffer[n] = (char) c; + } + + n++; + } + + funlockfile(f); + + if (ret) { + buffer[n] = 0; + + *ret = buffer; + buffer = NULL; + } + + return (int) count; +} diff --git a/src/basic/fileio.h b/src/basic/fileio.h index b423874..9422bc7 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -101,3 +101,5 @@ int link_tmpfile(int fd, const char *path, const char *target); int read_nul_string(FILE *f, char **ret); int mkdtemp_malloc(const char *template, char **ret); + +int read_line(FILE *f, size_t limit, char **ret); diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index b1d688c..375b7a8 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -663,6 +663,49 @@ static void test_tempfn(void) { free(ret); } +static void test_read_line(void) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *line = NULL; + + char buffer[] = + "Some test data\n" + "With newlines, and a NUL byte\0" + "\n" + "an empty line\n" + "an ignored line\n" + "and a very long line that is supposed to be truncated, because it is so long\n"; + + f = fmemopen(buffer, sizeof(buffer), "re"); + assert_se(f); + + assert_se(read_line(f, (size_t) -1, &line) == 15 && streq(line, "Some test data")); + line = mfree(line); + + assert_se(read_line(f, 1024, &line) == 30 && streq(line, "With newlines, and a NUL byte")); + line = mfree(line); + + assert_se(read_line(f, 1024, &line) == 1 && streq(line, "")); + line = mfree(line); + + assert_se(read_line(f, 1024, &line) == 14 && streq(line, "an empty line")); + line = mfree(line); + + assert_se(read_line(f, (size_t) -1, NULL) == 16); + + assert_se(read_line(f, 16, &line) == -ENOBUFS); + line = mfree(line); + + /* read_line() stopped when it hit the limit, that means when we continue reading we'll read at the first + * character after the previous limit. Let's make use of tha to continue our test. */ + assert_se(read_line(f, 1024, &line) == 61 && streq(line, "line that is supposed to be truncated, because it is so long")); + line = mfree(line); + + assert_se(read_line(f, 1024, &line) == 1 && streq(line, "")); + line = mfree(line); + + assert_se(read_line(f, 1024, &line) == 0 && streq(line, "")); +} + int main(int argc, char *argv[]) { log_set_max_level(LOG_DEBUG); log_parse_environment(); @@ -684,6 +727,7 @@ int main(int argc, char *argv[]) { test_search_and_fopen_nulstr(); test_writing_tmpfile(); test_tempfn(); + test_read_line(); return 0; }