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:
parent
a195dd8e5a
commit
4f9a66a32d
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user