Merge pull request #6891 from poettering/read-line
add read_line() helper as bounded getline() and make use of it at some places
This commit is contained in:
@@ -24,6 +24,8 @@ Janitorial Clean-ups:
|
||||
|
||||
Features:
|
||||
|
||||
* replace all uses of fgets() + LINE_MAX by read_line()
|
||||
|
||||
* dissect: when we discover squashfs, don't claim we had a "writable" partition
|
||||
in systemd-dissect
|
||||
|
||||
|
||||
@@ -2326,7 +2326,6 @@ int cg_mask_supported(CGroupMask *ret) {
|
||||
|
||||
int cg_kernel_controllers(Set *controllers) {
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
char buf[LINE_MAX];
|
||||
int r;
|
||||
|
||||
assert(controllers);
|
||||
@@ -2344,7 +2343,7 @@ int cg_kernel_controllers(Set *controllers) {
|
||||
}
|
||||
|
||||
/* Ignore the header line */
|
||||
(void) fgets(buf, sizeof(buf), f);
|
||||
(void) read_line(f, (size_t) -1, NULL);
|
||||
|
||||
for (;;) {
|
||||
char *controller;
|
||||
|
||||
@@ -84,3 +84,5 @@
|
||||
"/usr/local/lib/" n "\0" \
|
||||
"/usr/lib/" n "\0" \
|
||||
_CONF_PATHS_SPLIT_USR(n)
|
||||
|
||||
#define LONG_LINE_MAX (1U*1024U*1024U)
|
||||
|
||||
+78
-16
@@ -30,6 +30,7 @@
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "ctype.h"
|
||||
#include "def.h"
|
||||
#include "env-util.h"
|
||||
#include "escape.h"
|
||||
#include "fd-util.h"
|
||||
@@ -188,7 +189,7 @@ fail:
|
||||
|
||||
int read_one_line_file(const char *fn, char **line) {
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
char t[LINE_MAX], *c;
|
||||
int r;
|
||||
|
||||
assert(fn);
|
||||
assert(line);
|
||||
@@ -197,21 +198,8 @@ int read_one_line_file(const char *fn, char **line) {
|
||||
if (!f)
|
||||
return -errno;
|
||||
|
||||
if (!fgets(t, sizeof(t), f)) {
|
||||
|
||||
if (ferror(f))
|
||||
return errno > 0 ? -errno : -EIO;
|
||||
|
||||
t[0] = 0;
|
||||
}
|
||||
|
||||
c = strdup(t);
|
||||
if (!c)
|
||||
return -ENOMEM;
|
||||
truncate_nl(c);
|
||||
|
||||
*line = c;
|
||||
return 0;
|
||||
r = read_line(f, LONG_LINE_MAX, line);
|
||||
return r < 0 ? r : 0;
|
||||
}
|
||||
|
||||
int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
|
||||
@@ -1538,3 +1526,77 @@ int mkdtemp_malloc(const char *template, char **ret) {
|
||||
*ret = p;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void funlockfilep(FILE **f) {
|
||||
funlockfile(*f);
|
||||
}
|
||||
|
||||
int read_line(FILE *f, size_t limit, char **ret) {
|
||||
_cleanup_free_ char *buffer = NULL;
|
||||
size_t n = 0, allocated = 0, count = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
{
|
||||
_cleanup_(funlockfilep) FILE *flocked = f;
|
||||
flockfile(f);
|
||||
|
||||
for (;;) {
|
||||
int c;
|
||||
|
||||
if (n >= limit)
|
||||
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)
|
||||
return errno > 0 ? -errno : -EIO;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
count++;
|
||||
|
||||
if (IN_SET(c, '\n', 0)) /* Reached a delimiter */
|
||||
break;
|
||||
|
||||
if (ret) {
|
||||
if (!GREEDY_REALLOC(buffer, allocated, n + 2))
|
||||
return -ENOMEM;
|
||||
|
||||
buffer[n] = (char) c;
|
||||
}
|
||||
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
+32
-11
@@ -28,8 +28,10 @@
|
||||
#include "alloc-util.h"
|
||||
#include "conf-files.h"
|
||||
#include "conf-parser.h"
|
||||
#include "def.h"
|
||||
#include "extract-word.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "fs-util.h"
|
||||
#include "log.h"
|
||||
#include "macro.h"
|
||||
@@ -316,24 +318,44 @@ int config_parse(const char *unit,
|
||||
fd_warn_permissions(filename, fileno(f));
|
||||
|
||||
for (;;) {
|
||||
char buf[LINE_MAX], *l, *p, *c = NULL, *e;
|
||||
_cleanup_free_ char *buf = NULL;
|
||||
char *l, *p, *c = NULL, *e;
|
||||
bool escaped = false;
|
||||
|
||||
if (!fgets(buf, sizeof buf, f)) {
|
||||
if (feof(f))
|
||||
break;
|
||||
r = read_line(f, LONG_LINE_MAX, &buf);
|
||||
if (r == 0)
|
||||
break;
|
||||
if (r == -ENOBUFS) {
|
||||
if (warn)
|
||||
log_error_errno(r, "%s:%u: Line too long", filename, line);
|
||||
|
||||
return log_error_errno(errno, "Failed to read configuration file '%s': %m", filename);
|
||||
return r;
|
||||
}
|
||||
if (r < 0) {
|
||||
if (warn)
|
||||
log_error_errno(r, "%s:%u: Error while reading configuration file: %m", filename, line);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
l = buf;
|
||||
if (allow_bom && startswith(l, UTF8_BYTE_ORDER_MARK))
|
||||
l += strlen(UTF8_BYTE_ORDER_MARK);
|
||||
allow_bom = false;
|
||||
if (allow_bom) {
|
||||
char *q;
|
||||
|
||||
truncate_nl(l);
|
||||
q = startswith(buf, UTF8_BYTE_ORDER_MARK);
|
||||
if (q) {
|
||||
l = q;
|
||||
allow_bom = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (continuation) {
|
||||
if (strlen(continuation) + strlen(l) > LONG_LINE_MAX) {
|
||||
if (warn)
|
||||
log_error("%s:%u: Continuation line too long", filename, line);
|
||||
return -ENOBUFS;
|
||||
}
|
||||
|
||||
c = strappend(continuation, l);
|
||||
if (!c) {
|
||||
if (warn)
|
||||
@@ -387,8 +409,7 @@ int config_parse(const char *unit,
|
||||
|
||||
if (r < 0) {
|
||||
if (warn)
|
||||
log_warning_errno(r, "Failed to parse file '%s': %m",
|
||||
filename);
|
||||
log_warning_errno(r, "%s:%u: Failed to parse file: %m", filename, line);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
+130
-6
@@ -18,6 +18,8 @@
|
||||
***/
|
||||
|
||||
#include "conf-parser.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "log.h"
|
||||
#include "macro.h"
|
||||
#include "string-util.h"
|
||||
@@ -25,12 +27,10 @@
|
||||
#include "util.h"
|
||||
|
||||
static void test_config_parse_path_one(const char *rvalue, const char *expected) {
|
||||
char *path = NULL;
|
||||
_cleanup_free_ char *path = NULL;
|
||||
|
||||
assert_se(config_parse_path("unit", "filename", 1, "section", 1, "lvalue", 0, rvalue, &path, NULL) >= 0);
|
||||
assert_se(streq_ptr(expected, path));
|
||||
|
||||
free(path);
|
||||
}
|
||||
|
||||
static void test_config_parse_log_level_one(const char *rvalue, int expected) {
|
||||
@@ -76,12 +76,10 @@ static void test_config_parse_unsigned_one(const char *rvalue, unsigned expected
|
||||
}
|
||||
|
||||
static void test_config_parse_strv_one(const char *rvalue, char **expected) {
|
||||
char **strv = 0;
|
||||
_cleanup_strv_free_ char **strv = NULL;
|
||||
|
||||
assert_se(config_parse_strv("unit", "filename", 1, "section", 1, "lvalue", 0, rvalue, &strv, NULL) >= 0);
|
||||
assert_se(strv_equal(expected, strv));
|
||||
|
||||
strv_free(strv);
|
||||
}
|
||||
|
||||
static void test_config_parse_mode_one(const char *rvalue, mode_t expected) {
|
||||
@@ -227,7 +225,130 @@ static void test_config_parse_iec_uint64(void) {
|
||||
assert_se(config_parse_iec_uint64(NULL, "/this/file", 11, "Section", 22, "Size", 0, "4.5M", &offset, NULL) == 0);
|
||||
}
|
||||
|
||||
#define x10(x) x x x x x x x x x x
|
||||
#define x100(x) x10(x10(x))
|
||||
#define x1000(x) x10(x100(x))
|
||||
|
||||
static const char* const config_file[] = {
|
||||
"[Section]\n"
|
||||
"setting1=1\n",
|
||||
|
||||
"[Section]\n"
|
||||
"setting1=1", /* no terminating newline */
|
||||
|
||||
"\n\n\n\n[Section]\n\n\n"
|
||||
"setting1=1", /* some whitespace, no terminating newline */
|
||||
|
||||
"[Section]\n"
|
||||
"[Section]\n"
|
||||
"setting1=1\n"
|
||||
"setting1=2\n"
|
||||
"setting1=1\n", /* repeated settings */
|
||||
|
||||
"[Section]\n"
|
||||
"setting1=1\\\n" /* normal continuation */
|
||||
"2\\\n"
|
||||
"3\n",
|
||||
|
||||
"[Section]\n"
|
||||
"setting1=1\\\\\\\n" /* continuation with trailing escape symbols */
|
||||
"\\\\2\n", /* note that C requires one level of escaping, so the
|
||||
* parser gets "…1 BS BS BS NL BS BS 2 NL", which
|
||||
* it translates into "…1 BS BS SP BS BS 2" */
|
||||
|
||||
"\n[Section]\n\n"
|
||||
"setting1=" /* a line above LINE_MAX length */
|
||||
x1000("ABCD")
|
||||
"\n",
|
||||
|
||||
"[Section]\n"
|
||||
"setting1=" /* a line above LINE_MAX length, with continuation */
|
||||
x1000("ABCD") "\\\n"
|
||||
"foobar",
|
||||
|
||||
"[Section]\n"
|
||||
"setting1=" /* a line above the allowed limit: 9 + 1050000 + 1 */
|
||||
x1000(x1000("x") x10("abcde")) "\n",
|
||||
|
||||
"[Section]\n"
|
||||
"setting1=" /* many continuation lines, together above the limit */
|
||||
x1000(x1000("x") x10("abcde") "\\\n") "xxx",
|
||||
};
|
||||
|
||||
static void test_config_parse(unsigned i, const char *s) {
|
||||
char name[] = "/tmp/test-conf-parser.XXXXXX";
|
||||
int fd, r;
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
_cleanup_free_ char *setting1 = NULL;
|
||||
|
||||
const ConfigTableItem items[] = {
|
||||
{ "Section", "setting1", config_parse_string, 0, &setting1},
|
||||
{}
|
||||
};
|
||||
|
||||
log_info("== %s[%i] ==", __func__, i);
|
||||
|
||||
fd = mkostemp_safe(name);
|
||||
assert_se(fd >= 0);
|
||||
assert_se((size_t) write(fd, s, strlen(s)) == strlen(s));
|
||||
|
||||
assert_se(lseek(fd, 0, SEEK_SET) == 0);
|
||||
assert_se(f = fdopen(fd, "r"));
|
||||
|
||||
/*
|
||||
int config_parse(const char *unit,
|
||||
const char *filename,
|
||||
FILE *f,
|
||||
const char *sections,
|
||||
ConfigItemLookup lookup,
|
||||
const void *table,
|
||||
bool relaxed,
|
||||
bool allow_include,
|
||||
bool warn,
|
||||
void *userdata)
|
||||
*/
|
||||
|
||||
r = config_parse(NULL, name, f,
|
||||
"Section\0",
|
||||
config_item_table_lookup, items,
|
||||
false, false, true, NULL);
|
||||
|
||||
switch (i) {
|
||||
case 0 ... 3:
|
||||
assert_se(r == 0);
|
||||
assert_se(streq(setting1, "1"));
|
||||
break;
|
||||
|
||||
case 4:
|
||||
assert_se(r == 0);
|
||||
assert_se(streq(setting1, "1 2 3"));
|
||||
break;
|
||||
|
||||
case 5:
|
||||
assert_se(r == 0);
|
||||
assert_se(streq(setting1, "1\\\\ \\\\2"));
|
||||
break;
|
||||
|
||||
case 6:
|
||||
assert_se(r == 0);
|
||||
assert_se(streq(setting1, x1000("ABCD")));
|
||||
break;
|
||||
|
||||
case 7:
|
||||
assert_se(r == 0);
|
||||
assert_se(streq(setting1, x1000("ABCD") " foobar"));
|
||||
break;
|
||||
|
||||
case 8 ... 9:
|
||||
assert_se(r == -ENOBUFS);
|
||||
assert_se(setting1 == NULL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
unsigned i;
|
||||
|
||||
log_parse_environment();
|
||||
log_open();
|
||||
|
||||
@@ -244,5 +365,8 @@ int main(int argc, char **argv) {
|
||||
test_config_parse_nsec();
|
||||
test_config_parse_iec_uint64();
|
||||
|
||||
for (i = 0; i < ELEMENTSOF(config_file); i++)
|
||||
test_config_parse(i, config_file[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
+84
-2
@@ -209,7 +209,7 @@ static void test_parse_multiline_env_file(void) {
|
||||
static void test_merge_env_file(void) {
|
||||
char t[] = "/tmp/test-fileio-XXXXXX";
|
||||
int fd, r;
|
||||
FILE *f;
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
_cleanup_strv_free_ char **a = NULL;
|
||||
char **i;
|
||||
|
||||
@@ -278,7 +278,7 @@ static void test_merge_env_file(void) {
|
||||
static void test_merge_env_file_invalid(void) {
|
||||
char t[] = "/tmp/test-fileio-XXXXXX";
|
||||
int fd, r;
|
||||
FILE *f;
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
_cleanup_strv_free_ char **a = NULL;
|
||||
char **i;
|
||||
|
||||
@@ -663,6 +663,85 @@ static void test_tempfn(void) {
|
||||
free(ret);
|
||||
}
|
||||
|
||||
static const 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";
|
||||
|
||||
static void test_read_line_one_file(FILE *f) {
|
||||
_cleanup_free_ char *line = NULL;
|
||||
|
||||
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, ""));
|
||||
}
|
||||
|
||||
static void test_read_line(void) {
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
_cleanup_free_ char *line = NULL;
|
||||
|
||||
f = fmemopen((void*) buffer, sizeof(buffer), "re");
|
||||
assert_se(f);
|
||||
|
||||
test_read_line_one_file(f);
|
||||
}
|
||||
|
||||
static void test_read_line2(void) {
|
||||
char name[] = "/tmp/test-fileio.XXXXXX";
|
||||
int fd;
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
|
||||
fd = mkostemp_safe(name);
|
||||
assert_se(fd >= 0);
|
||||
assert_se((size_t) write(fd, buffer, sizeof(buffer)) == sizeof(buffer));
|
||||
|
||||
assert_se(lseek(fd, 0, SEEK_SET) == 0);
|
||||
assert_se(f = fdopen(fd, "r"));
|
||||
|
||||
test_read_line_one_file(f);
|
||||
}
|
||||
|
||||
static void test_read_line3(void) {
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
_cleanup_free_ char *line = NULL;
|
||||
int r;
|
||||
|
||||
f = fopen("/proc/cmdline", "re");
|
||||
if (!f && IN_SET(errno, ENOENT, EPERM))
|
||||
return;
|
||||
assert_se(f);
|
||||
|
||||
r = read_line(f, LINE_MAX, &line);
|
||||
assert_se((size_t) r == strlen(line) + 1);
|
||||
assert_se(read_line(f, LINE_MAX, NULL) == 0);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
log_set_max_level(LOG_DEBUG);
|
||||
log_parse_environment();
|
||||
@@ -684,6 +763,9 @@ int main(int argc, char *argv[]) {
|
||||
test_search_and_fopen_nulstr();
|
||||
test_writing_tmpfile();
|
||||
test_tempfn();
|
||||
test_read_line();
|
||||
test_read_line2();
|
||||
test_read_line3();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user