diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 028372c011..892cdefdba 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -790,7 +790,9 @@ int read_full_stream_full( if (feof(f)) break; - if (size != SIZE_MAX && !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER)) { /* If we got asked to read some specific size, we already sized the buffer right, hence leave */ + if (size != SIZE_MAX && !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER)) { + /* If we got asked to read some specific size, we already sized the buffer right, + * hence leave. */ assert(l == size); break; } @@ -834,9 +836,9 @@ int read_full_stream_full( } if (!ret_size) { - /* Safety check: if the caller doesn't want to know the size of what we just read it will rely on the - * trailing NUL byte. But if there's an embedded NUL byte, then we should refuse operation as otherwise - * there'd be ambiguity about what we just read. */ + /* Safety check: if the caller doesn't want to know the size of what we just read it will + * rely on the trailing NUL byte. But if there's an embedded NUL byte, then we should refuse + * operation as otherwise there'd be ambiguity about what we just read. */ if (memchr(buf, 0, l)) { r = -EBADMSG; @@ -1295,7 +1297,7 @@ static int search_and_fopen_internal( assert(path); assert(mode || !ret_file); - r = search_and_open( + r = search_and_open_internal( path, mode ? fopen_mode_to_flags(mode) : 0, root, diff --git a/src/basic/path-util.c b/src/basic/path-util.c index b41029b157..71486cc9e0 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -264,20 +264,20 @@ char** path_strv_resolve(char **l, const char *root) { if (strv_isempty(l)) return l; - /* Goes through every item in the string list and canonicalize - * the path. This works in place and won't rollback any - * changes on failure. */ + /* Go through every item in the string list and canonicalize the path. + * This works in place and doesn't roll back any changes on failure. */ STRV_FOREACH(s, l) { _cleanup_free_ char *orig = NULL; char *t, *u; - if (!path_is_absolute(*s)) { - free(*s); - continue; - } - if (root) { + if (!path_is_absolute(*s)) { + log_debug("Ignoring non-absolute search path '%s'.", *s); + free(*s); + continue; + } + orig = *s; t = path_join(root, orig); if (!t) { diff --git a/src/shared/hostname-setup.c b/src/shared/hostname-setup.c index 2512292b0c..4abf662002 100644 --- a/src/shared/hostname-setup.c +++ b/src/shared/hostname-setup.c @@ -331,45 +331,72 @@ static bool normalize_and_validate_word(char *word) { return hostname_is_valid(word, /* flags= */ 0); } -static int pick_word_linear_scan(FILE *f, off_t offset, char **ret) { - int r; - - assert(f); +static int pick_word_at(const char *words, size_t size, size_t offset, char **ret) { + assert(words); + assert(offset <= size); assert(ret); - if (fseeko(f, offset, SEEK_SET) < 0) - return -errno; + /* Extracts the line starting at 'offset', strips and validates it. Returns 1 and the word on + * success, 0 if the line is a comment, empty, or not a valid hostname word. */ - bool wrapped = false; - r = read_line(f, LONG_LINE_MAX, NULL); /* discard the partial line we landed in */ - if (r < 0) - return r; - if (r == 0) { - wrapped = true; - rewind(f); + const char *p = words + offset; + size_t len = MIN(size - offset, LONG_LINE_MAX + 1); + + const char *e = memchr(p, '\n', len); + if (e) + len = e - p; + if (len > LONG_LINE_MAX) + return -EUCLEAN; + + /* Strip leading and trailing whitespace */ + while (len > 0 && strchr(WHITESPACE, p[0])) { + p++; + len--; } + while (len > 0 && strchr(WHITESPACE, p[len - 1])) + len--; + + _cleanup_free_ char *word = strndup(p, len); + if (!word) + return -ENOMEM; + + bool good = normalize_and_validate_word(word); + *ret = good ? TAKE_PTR(word) : NULL; + return good; +} + +static int pick_word_linear_scan(const char *words, size_t size, size_t offset, char **ret) { + int r; + + assert(words); + assert(size > 0); + assert(offset <= size); + assert(ret); + + /* Skip the partial line we landed in */ + bool wrapped = false; + const char *e = memchr(words + offset, '\n', size - offset); + offset = e ? (size_t) (e - words) + 1 : size; for (;;) { - _cleanup_free_ char *line = NULL; - - r = read_stripped_line(f, LONG_LINE_MAX, &line); - if (r < 0) - return r; - if (r == 0) { /* hit EOF: we started at a random offset, wrap around to the beginning */ + if (offset >= size) { /* hit EOF: we started at a random offset, wrap around to the beginning */ if (wrapped) /* already wrapped once, the file contains no usable word at all */ return -ENOENT; wrapped = true; - rewind(f); - continue; - } - if (normalize_and_validate_word(line)) { - *ret = TAKE_PTR(line); - return 0; + offset = 0; } + + r = pick_word_at(words, size, offset, ret); + if (r != 0) + return r < 0 ? r : 0; + + /* Advance to the next line */ + e = memchr(words + offset, '\n', size - offset); + offset = e ? (size_t) (e - words) + 1 : size; } } -static int hostname_pick_word(sd_id128_t mid, size_t pos, char **ret) { +int hostname_pick_word(sd_id128_t mid, size_t pos, char **ret) { static const sd_id128_t word_key = SD_ID128_MAKE(2d,9f,1c,7a,4b,8e,43,11,9a,6d,5f,02,c8,77,e3,14); _cleanup_fclose_ FILE *f = NULL; struct stat st; @@ -391,55 +418,48 @@ static int hostname_pick_word(sd_id128_t mid, size_t pos, char **ret) { r = stat_verify_regular(&st); if (r < 0) return r; - if (st.st_size == 0) + + _cleanup_free_ char *words = NULL; + size_t size; + r = read_full_stream(f, &words, &size); + if (r < 0) + return r; + if (size == 0) return -ENOENT; - /* Pick a word without reading the whole list into memory: - * 1. pick a random offset in the file [0 … st.st_size-1] - * 2. if offset is zero, read a full line from the beginning of the file, use that. - * 3. otherwise, seek to offset minus 1 and read one character. - * 4. if that character is newline, then read a full line after it, and use that as result - * 5. otherwise, goto 1 + /* Pick a word: + * 1. pick a random offset in the buffer [0 … size-1] + * 2. if the offset is zero or the preceding byte is a newline, it starts a line: use that line + * 3. otherwise, goto 1 * * As a safety net terminate after a fixed number iterations (for pathological wordlists) - * This stream is independent of the '?' nibble stream so pure-'?' * templates keep producing + * This stream is independent of the '?' nibble stream so pure-'?' templates keep producing * byte-identical output. Stable as long as the wordlist is stable. */ - off_t offset = 0; + size_t offset = 0; const unsigned int MAX_ITERATIONS = 64; for (unsigned i = 0; i < MAX_ITERATIONS; i++) { - _cleanup_free_ char *line = NULL; - struct siphash state; siphash24_init(&state, word_key.bytes); siphash24_compress_typesafe(mid, &state); siphash24_compress_typesafe(pos, &state); siphash24_compress_typesafe(i, &state); /* counter mode */ - offset = (off_t) (siphash24_finalize(&state) % (uint64_t) st.st_size); + offset = (size_t) (siphash24_finalize(&state) % (uint64_t) size); - if (offset > 0) { - if (fseeko(f, offset - 1, SEEK_SET) < 0) - return -errno; - if (fgetc(f) != '\n') - continue; /* not a line start */ - } else if (fseeko(f, 0, SEEK_SET) < 0) /* offset 0 always begins the first line */ - return -errno; + if (offset > 0 && words[offset - 1] != '\n') + continue; /* not a line start */ + + r = pick_word_at(words, size, offset, ret); + if (r != 0) + return r < 0 ? r : 0; - r = read_stripped_line(f, LONG_LINE_MAX, &line); - if (r < 0) - return r; - if (r == 0) /* raced with truncation */ - continue; - if (normalize_and_validate_word(line)) { - *ret = TAKE_PTR(line); - return 0; - } /* Comment/empty/invalid line: resample rather than advancing, to keep the pick uniform. */ } - /* We exhausted the uniform attempts, this should never happen but if it does fallback to picking the - * next word after our last attempt. */ - log_warning("hostname_pick_word did not find a usable word after %u in wordlist %zu", MAX_ITERATIONS, pos); - return pick_word_linear_scan(f, offset, ret); + /* We exhausted the uniform attempts, this occasionally happens, so fall back to picking the next + * word after our last offset. */ + log_debug("Falling back to linear scan for a word in wordlist %zu after trying %u random offsets.", + pos, MAX_ITERATIONS); + return pick_word_linear_scan(words, size, offset, ret); } int hostname_substitute_wildcards(const char *name, char **ret) { diff --git a/src/shared/hostname-setup.h b/src/shared/hostname-setup.h index 6e68b6fa79..c9f1e46eb8 100644 --- a/src/shared/hostname-setup.h +++ b/src/shared/hostname-setup.h @@ -22,6 +22,7 @@ int read_etc_hostname(const char *path, bool substitute_wildcards, char **ret); void hostname_update_source_hint(const char *hostname, HostnameSource source); int hostname_setup(bool really); +int hostname_pick_word(sd_id128_t mid, size_t pos, char **ret); int hostname_substitute_wildcards(const char *name, char **ret); char* get_default_hostname(void); diff --git a/src/test/meson.build b/src/test/meson.build index d0d4f25f17..ebdd46788a 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -399,6 +399,10 @@ executables += [ ], 'timeout' : 180, }, + test_template + { + 'sources': files('test-hostname-pick-word.c'), + 'type' : 'manual', + }, test_template + { 'sources' : files('test-ipcrm.c'), 'type' : 'unsafe', diff --git a/src/test/test-hostname-pick-word.c b/src/test/test-hostname-pick-word.c new file mode 100644 index 0000000000..3021c6e6ef --- /dev/null +++ b/src/test/test-hostname-pick-word.c @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-id128.h" + +#include "alloc-util.h" +#include "hostname-setup.h" +#include "log.h" +#include "main-func.h" +#include "parse-util.h" +#include "tests.h" + +static int run(int argc, char **argv) { + int r; + + log_parse_environment(); + + unsigned c = 100, pos = 1; + + if (argc > 1) { + r = safe_atou(argv[1], &pos); + if (r < 0) + return log_error_errno(r, "Cannot parse uint %s: %m", argv[1]); + } + + if (argc > 2) { + r = safe_atou(argv[2], &c); + if (r < 0) + return log_error_errno(r, "Cannot parse uint %s: %m", argv[2]); + } + + sd_id128_t mid = SD_ID128_ARRAY(aa,bb,cc,ee,33,21,42,31,89,ba,a4,91,33,22,11,00); + + while (c-- > 0) { + _cleanup_free_ char *w = NULL; + + ASSERT_OK(hostname_pick_word(mid, pos, &w)); + ASSERT_PTR(w); + + printf("%s\n", w); + + mid.qwords[0] += 3; + } + + return 0; +} + +DEFINE_MAIN_FUNCTION(run);