|
|
ecbff1 |
From d7b56e186521ce2e48e27edda121d780a3d62d27 Mon Sep 17 00:00:00 2001
|
|
|
ecbff1 |
From: Jan Synacek <jsynacek@redhat.com>
|
|
|
ecbff1 |
Date: Thu, 23 Nov 2017 08:53:50 +0100
|
|
|
ecbff1 |
Subject: [PATCH] fileio: add new helper call read_line() as bounded getline()
|
|
|
ecbff1 |
replacement
|
|
|
ecbff1 |
|
|
|
ecbff1 |
read_line() is much like getline(), and returns a line read from a
|
|
|
ecbff1 |
FILE*, of arbitrary sizes. In contrast to gets() it will grow the buffer
|
|
|
ecbff1 |
dynamically, and in contrast to getline() it will place a user-specified
|
|
|
ecbff1 |
boundary on the line.
|
|
|
ecbff1 |
|
|
|
ecbff1 |
(cherry-picked from commit 4f9a66a32dda1d9a28f9bb3fa31c2148524bc46a)
|
|
|
ecbff1 |
|
|
|
ecbff1 |
Resolves: #1503106
|
|
|
ecbff1 |
---
|
|
|
de8967 |
src/shared/fileio.c | 77 ++++++++++++++++++++++++++++++++++++++++++
|
|
|
ecbff1 |
src/shared/fileio.h | 2 ++
|
|
|
de8967 |
src/test/test-fileio.c | 44 ++++++++++++++++++++++++
|
|
|
ecbff1 |
3 files changed, 123 insertions(+)
|
|
|
ecbff1 |
|
|
|
ecbff1 |
diff --git a/src/shared/fileio.c b/src/shared/fileio.c
|
|
|
ecbff1 |
index ff6b1a7ed..107737573 100644
|
|
|
ecbff1 |
--- a/src/shared/fileio.c
|
|
|
ecbff1 |
+++ b/src/shared/fileio.c
|
|
|
ecbff1 |
@@ -815,3 +815,80 @@ int get_status_field(const char *filename, const char *pattern, char **field) {
|
|
|
ecbff1 |
|
|
|
ecbff1 |
return 0;
|
|
|
ecbff1 |
}
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+int read_line(FILE *f, size_t limit, char **ret) {
|
|
|
ecbff1 |
+ _cleanup_free_ char *buffer = NULL;
|
|
|
ecbff1 |
+ size_t n = 0, allocated = 0, count = 0;
|
|
|
ecbff1 |
+ int r;
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ assert(f);
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ /* Something like a bounded version of getline().
|
|
|
ecbff1 |
+ *
|
|
|
ecbff1 |
+ * Considers EOF, \n and \0 end of line delimiters, and does not include these delimiters in the string
|
|
|
ecbff1 |
+ * returned.
|
|
|
ecbff1 |
+ *
|
|
|
ecbff1 |
+ * Returns the number of bytes read from the files (i.e. including delimiters — this hence usually differs from
|
|
|
ecbff1 |
+ * the number of characters in the returned string). When EOF is hit, 0 is returned.
|
|
|
ecbff1 |
+ *
|
|
|
ecbff1 |
+ * The input parameter limit is the maximum numbers of characters in the returned string, i.e. excluding
|
|
|
ecbff1 |
+ * delimiters. If the limit is hit we fail and return -ENOBUFS.
|
|
|
ecbff1 |
+ *
|
|
|
ecbff1 |
+ * If a line shall be skipped ret may be initialized as NULL. */
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ if (ret) {
|
|
|
ecbff1 |
+ if (!GREEDY_REALLOC(buffer, allocated, 1))
|
|
|
ecbff1 |
+ return -ENOMEM;
|
|
|
ecbff1 |
+ }
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ flockfile(f);
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ for (;;) {
|
|
|
ecbff1 |
+ int c;
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ if (n >= limit) {
|
|
|
ecbff1 |
+ funlockfile(f);
|
|
|
ecbff1 |
+ return -ENOBUFS;
|
|
|
ecbff1 |
+ }
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ errno = 0;
|
|
|
ecbff1 |
+ c = fgetc_unlocked(f);
|
|
|
ecbff1 |
+ if (c == EOF) {
|
|
|
ecbff1 |
+ /* if we read an error, and have no data to return, then propagate the error */
|
|
|
ecbff1 |
+ if (ferror_unlocked(f) && n == 0) {
|
|
|
ecbff1 |
+ r = errno > 0 ? -errno : -EIO;
|
|
|
ecbff1 |
+ funlockfile(f);
|
|
|
ecbff1 |
+ return r;
|
|
|
ecbff1 |
+ }
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ break;
|
|
|
ecbff1 |
+ }
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ count++;
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ if (IN_SET(c, '\n', 0)) /* Reached a delimiter */
|
|
|
ecbff1 |
+ break;
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ if (ret) {
|
|
|
ecbff1 |
+ if (!GREEDY_REALLOC(buffer, allocated, n + 2)) {
|
|
|
ecbff1 |
+ funlockfile(f);
|
|
|
ecbff1 |
+ return -ENOMEM;
|
|
|
ecbff1 |
+ }
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ buffer[n] = (char) c;
|
|
|
ecbff1 |
+ }
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ n++;
|
|
|
ecbff1 |
+ }
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ funlockfile(f);
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ if (ret) {
|
|
|
ecbff1 |
+ buffer[n] = 0;
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ *ret = buffer;
|
|
|
ecbff1 |
+ buffer = NULL;
|
|
|
ecbff1 |
+ }
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ return (int) count;
|
|
|
ecbff1 |
+}
|
|
|
ecbff1 |
diff --git a/src/shared/fileio.h b/src/shared/fileio.h
|
|
|
ecbff1 |
index 5ae51c1e2..f33464dce 100644
|
|
|
ecbff1 |
--- a/src/shared/fileio.h
|
|
|
ecbff1 |
+++ b/src/shared/fileio.h
|
|
|
ecbff1 |
@@ -43,3 +43,5 @@ int write_env_file(const char *fname, char **l);
|
|
|
ecbff1 |
int executable_is_script(const char *path, char **interpreter);
|
|
|
ecbff1 |
|
|
|
ecbff1 |
int get_status_field(const char *filename, const char *pattern, char **field);
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+int read_line(FILE *f, size_t limit, char **ret);
|
|
|
ecbff1 |
diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c
|
|
|
ecbff1 |
index 63e4a19b7..fc5969322 100644
|
|
|
ecbff1 |
--- a/src/test/test-fileio.c
|
|
|
ecbff1 |
+++ b/src/test/test-fileio.c
|
|
|
ecbff1 |
@@ -392,6 +392,49 @@ static void test_load_env_file_pairs(void) {
|
|
|
ecbff1 |
unlink(fn);
|
|
|
ecbff1 |
}
|
|
|
ecbff1 |
|
|
|
ecbff1 |
+static void test_read_line(void) {
|
|
|
ecbff1 |
+ _cleanup_fclose_ FILE *f = NULL;
|
|
|
ecbff1 |
+ _cleanup_free_ char *line = NULL;
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ char buffer[] =
|
|
|
ecbff1 |
+ "Some test data\n"
|
|
|
ecbff1 |
+ "With newlines, and a NUL byte\0"
|
|
|
ecbff1 |
+ "\n"
|
|
|
ecbff1 |
+ "an empty line\n"
|
|
|
ecbff1 |
+ "an ignored line\n"
|
|
|
ecbff1 |
+ "and a very long line that is supposed to be truncated, because it is so long\n";
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ f = fmemopen(buffer, sizeof(buffer), "re");
|
|
|
ecbff1 |
+ assert_se(f);
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ assert_se(read_line(f, (size_t) -1, &line) == 15 && streq(line, "Some test data"));
|
|
|
ecbff1 |
+ line = mfree(line);
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ assert_se(read_line(f, 1024, &line) == 30 && streq(line, "With newlines, and a NUL byte"));
|
|
|
ecbff1 |
+ line = mfree(line);
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ assert_se(read_line(f, 1024, &line) == 1 && streq(line, ""));
|
|
|
ecbff1 |
+ line = mfree(line);
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ assert_se(read_line(f, 1024, &line) == 14 && streq(line, "an empty line"));
|
|
|
ecbff1 |
+ line = mfree(line);
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ assert_se(read_line(f, (size_t) -1, NULL) == 16);
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ assert_se(read_line(f, 16, &line) == -ENOBUFS);
|
|
|
ecbff1 |
+ line = mfree(line);
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ /* read_line() stopped when it hit the limit, that means when we continue reading we'll read at the first
|
|
|
ecbff1 |
+ * character after the previous limit. Let's make use of tha to continue our test. */
|
|
|
ecbff1 |
+ assert_se(read_line(f, 1024, &line) == 61 && streq(line, "line that is supposed to be truncated, because it is so long"));
|
|
|
ecbff1 |
+ line = mfree(line);
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ assert_se(read_line(f, 1024, &line) == 1 && streq(line, ""));
|
|
|
ecbff1 |
+ line = mfree(line);
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
+ assert_se(read_line(f, 1024, &line) == 0 && streq(line, ""));
|
|
|
ecbff1 |
+}
|
|
|
ecbff1 |
+
|
|
|
ecbff1 |
int main(int argc, char *argv[]) {
|
|
|
ecbff1 |
log_parse_environment();
|
|
|
ecbff1 |
log_open();
|
|
|
ecbff1 |
@@ -405,6 +448,7 @@ int main(int argc, char *argv[]) {
|
|
|
ecbff1 |
test_write_string_file();
|
|
|
ecbff1 |
test_write_string_file_no_create();
|
|
|
ecbff1 |
test_load_env_file_pairs();
|
|
|
ecbff1 |
+ test_read_line();
|
|
|
ecbff1 |
|
|
|
ecbff1 |
return 0;
|
|
|
ecbff1 |
}
|