1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-21 02:50:18 +03:00

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.
This commit is contained in:
Lennart Poettering 2017-09-22 17:55:53 +02:00
parent a195dd8e5a
commit 4f9a66a32d
3 changed files with 123 additions and 0 deletions

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}