diff --git a/src/basic/tmpfile-util.c b/src/basic/tmpfile-util.c index e0a338c163..34d3016ba9 100644 --- a/src/basic/tmpfile-util.c +++ b/src/basic/tmpfile-util.c @@ -86,12 +86,76 @@ int fmkostemp_safe(char *pattern, const char *mode, FILE **ret_f) { return 0; } -int tempfn_xxxxxx(const char *p, const char *extra, char **ret) { - _cleanup_free_ char *d = NULL, *fn = NULL, *nf = NULL; +static int tempfn_build(const char *p, const char *pre, const char *post, bool child, char **ret) { + _cleanup_free_ char *d = NULL, *fn = NULL, *nf = NULL, *result = NULL; + size_t len_pre, len_post, len_add; int r; + assert(p); assert(ret); + /* + * Turns this: + * /foo/bar/waldo + * + * Into this : + * /foo/bar/waldo/.#
(child == true) + * /foo/bar/.# waldo(child == false) + */ + + if (pre && strchr(pre, '/')) + return -EINVAL; + + if (post && strchr(post, '/')) + return -EINVAL; + + len_pre = strlen_ptr(pre); + len_post = strlen_ptr(post); + /* NAME_MAX is counted *without* the trailing NUL byte. */ + if (len_pre > NAME_MAX - STRLEN(".#") || + len_post > NAME_MAX - STRLEN(".#") - len_pre) + return -EINVAL; + + len_add = len_pre + len_post + STRLEN(".#"); + + if (child) { + d = strdup(p); + if (!d) + return -ENOMEM; + } else { + r = path_extract_directory(p, &d); + if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → No directory specified, just a filename */ + return r; + + r = path_extract_filename(p, &fn); + if (r < 0) + return r; + + if (strlen(fn) > NAME_MAX - len_add) + /* We cannot simply prepend and append strings to the filename. Let's truncate the filename. */ + fn[NAME_MAX - len_add] = '\0'; + } + + nf = strjoin(".#", strempty(pre), strempty(fn), strempty(post)); + if (!nf) + return -ENOMEM; + + if (d) { + if (!path_extend(&d, nf)) + return -ENOMEM; + + result = path_simplify(TAKE_PTR(d)); + } else + result = TAKE_PTR(nf); + + if (!path_is_valid(result)) /* New path is not valid? (Maybe because too long?) Refuse. */ + return -EINVAL; + + *ret = TAKE_PTR(result); + return 0; +} + +int tempfn_xxxxxx(const char *p, const char *extra, char **ret) { /* * Turns this: * /foo/bar/waldo @@ -100,36 +164,13 @@ int tempfn_xxxxxx(const char *p, const char *extra, char **ret) { * /foo/bar/.# waldoXXXXXX */ - r = path_extract_directory(p, &d); - if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → No directory specified, just a filename */ - return r; - - r = path_extract_filename(p, &fn); - if (r < 0) - return r; - - nf = strjoin(".#", strempty(extra), fn, "XXXXXX"); - if (!nf) - return -ENOMEM; - - if (!filename_is_valid(nf)) /* New name is not valid? (Maybe because too long?) Refuse. */ - return -EINVAL; - - if (d) { - if (!path_extend(&d, nf)) - return -ENOMEM; - - *ret = path_simplify(TAKE_PTR(d)); - } else - *ret = TAKE_PTR(nf); - - return 0; + return tempfn_build(p, extra, "XXXXXX", /* child = */ false, ret); } int tempfn_random(const char *p, const char *extra, char **ret) { - _cleanup_free_ char *d = NULL, *fn = NULL, *nf = NULL; - int r; + _cleanup_free_ char *s = NULL; + assert(p); assert(ret); /* @@ -140,37 +181,14 @@ int tempfn_random(const char *p, const char *extra, char **ret) { * /foo/bar/.# waldobaa2a261115984a9 */ - r = path_extract_directory(p, &d); - if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → No directory specified, just a filename */ - return r; - - r = path_extract_filename(p, &fn); - if (r < 0) - return r; - - if (asprintf(&nf, ".#%s%s%016" PRIx64, - strempty(extra), - fn, - random_u64()) < 0) + if (asprintf(&s, "%016" PRIx64, random_u64()) < 0) return -ENOMEM; - if (!filename_is_valid(nf)) /* Not valid? (maybe because too long now?) — refuse early */ - return -EINVAL; - - if (d) { - if (!path_extend(&d, nf)) - return -ENOMEM; - - *ret = path_simplify(TAKE_PTR(d)); - } else - *ret = TAKE_PTR(nf); - - return 0; + return tempfn_build(p, extra, s, /* child = */ false, ret); } int tempfn_random_child(const char *p, const char *extra, char **ret) { - char *t, *x; - uint64_t u; + _cleanup_free_ char *s = NULL; int r; assert(ret); @@ -187,27 +205,10 @@ int tempfn_random_child(const char *p, const char *extra, char **ret) { return r; } - extra = strempty(extra); - - t = new(char, strlen(p) + 3 + strlen(extra) + 16 + 1); - if (!t) + if (asprintf(&s, "%016" PRIx64, random_u64()) < 0) return -ENOMEM; - if (isempty(p)) - x = stpcpy(stpcpy(t, ".#"), extra); - else - x = stpcpy(stpcpy(stpcpy(t, p), "/.#"), extra); - - u = random_u64(); - for (unsigned i = 0; i < 16; i++) { - *(x++) = hexchar(u & 0xF); - u >>= 4; - } - - *x = 0; - - *ret = path_simplify(t); - return 0; + return tempfn_build(p, extra, s, /* child = */ true, ret); } int open_tmpfile_unlinkable(const char *directory, int flags) { diff --git a/src/test/test-tmpfile-util.c b/src/test/test-tmpfile-util.c index 5e5f60bdc8..002bd46162 100644 --- a/src/test/test-tmpfile-util.c +++ b/src/test/test-tmpfile-util.c @@ -3,6 +3,7 @@ #include "alloc-util.h" #include "errno-util.h" #include "log.h" +#include "path-util.h" #include "string-util.h" #include "tests.h" #include "tmpfile-util.h" @@ -26,10 +27,13 @@ static void test_tempfn_random_one(const char *p, const char *extra, const char } TEST(tempfn_random) { + _cleanup_free_ char *dir = NULL, *p = NULL, *q = NULL; + test_tempfn_random_one("", NULL, NULL, -EINVAL); test_tempfn_random_one(".", NULL, NULL, -EADDRNOTAVAIL); test_tempfn_random_one("..", NULL, NULL, -EINVAL); test_tempfn_random_one("/", NULL, NULL, -EADDRNOTAVAIL); + test_tempfn_random_one("foo", "hoge/aaa", NULL, -EINVAL); test_tempfn_random_one("foo", NULL, ".#foo", 0); test_tempfn_random_one("foo", "bar", ".#barfoo", 0); @@ -48,6 +52,36 @@ TEST(tempfn_random) { test_tempfn_random_one("./foo/", "bar", ".#barfoo", 0); test_tempfn_random_one("../foo/", NULL, "../.#foo", 0); test_tempfn_random_one("../foo/", "bar", "../.#barfoo", 0); + + assert_se(dir = new(char, PATH_MAX - 20)); + memset(dir, 'x', PATH_MAX - 21); + dir[PATH_MAX - 21] = '\0'; + for (size_t i = 0; i < PATH_MAX - 21; i += NAME_MAX + 1) + dir[i] = '/'; + + assert_se(p = path_join(dir, "a")); + assert_se(q = path_join(dir, ".#a")); + + test_tempfn_random_one(p, NULL, q, 0); + test_tempfn_random_one(p, "b", NULL, -EINVAL); + + p = mfree(p); + q = mfree(q); + + assert_se(p = new(char, NAME_MAX + 1)); + memset(p, 'x', NAME_MAX); + p[NAME_MAX] = '\0'; + + assert_se(q = new(char, NAME_MAX + 1)); + memset(stpcpy(q, ".#"), 'x', NAME_MAX - STRLEN(".#") - 16); + q[NAME_MAX - 16] = '\0'; + + test_tempfn_random_one(p, NULL, q, 0); + + memset(stpcpy(q, ".#hoge"), 'x', NAME_MAX - STRLEN(".#hoge") - 16); + q[NAME_MAX - 16] = '\0'; + + test_tempfn_random_one(p, "hoge", q, 0); } static void test_tempfn_xxxxxx_one(const char *p, const char *extra, const char *expect, int ret) { @@ -68,10 +102,13 @@ static void test_tempfn_xxxxxx_one(const char *p, const char *extra, const char } TEST(tempfn_xxxxxx) { + _cleanup_free_ char *dir = NULL, *p = NULL, *q = NULL; + test_tempfn_xxxxxx_one("", NULL, NULL, -EINVAL); test_tempfn_xxxxxx_one(".", NULL, NULL, -EADDRNOTAVAIL); test_tempfn_xxxxxx_one("..", NULL, NULL, -EINVAL); test_tempfn_xxxxxx_one("/", NULL, NULL, -EADDRNOTAVAIL); + test_tempfn_xxxxxx_one("foo", "hoge/aaa", NULL, -EINVAL); test_tempfn_xxxxxx_one("foo", NULL, ".#foo", 0); test_tempfn_xxxxxx_one("foo", "bar", ".#barfoo", 0); @@ -90,6 +127,108 @@ TEST(tempfn_xxxxxx) { test_tempfn_xxxxxx_one("./foo/", "bar", ".#barfoo", 0); test_tempfn_xxxxxx_one("../foo/", NULL, "../.#foo", 0); test_tempfn_xxxxxx_one("../foo/", "bar", "../.#barfoo", 0); + + assert_se(dir = new(char, PATH_MAX - 10)); + memset(dir, 'x', PATH_MAX - 11); + dir[PATH_MAX - 11] = '\0'; + for (size_t i = 0; i < PATH_MAX - 11; i += NAME_MAX + 1) + dir[i] = '/'; + + assert_se(p = path_join(dir, "a")); + assert_se(q = path_join(dir, ".#a")); + + test_tempfn_xxxxxx_one(p, NULL, q, 0); + test_tempfn_xxxxxx_one(p, "b", NULL, -EINVAL); + + p = mfree(p); + q = mfree(q); + + assert_se(p = new(char, NAME_MAX + 1)); + memset(p, 'x', NAME_MAX); + p[NAME_MAX] = '\0'; + + assert_se(q = new(char, NAME_MAX + 1)); + memset(stpcpy(q, ".#"), 'x', NAME_MAX - STRLEN(".#") - 6); + q[NAME_MAX - 6] = '\0'; + + test_tempfn_xxxxxx_one(p, NULL, q, 0); + + memset(stpcpy(q, ".#hoge"), 'x', NAME_MAX - STRLEN(".#hoge") - 6); + q[NAME_MAX - 6] = '\0'; + + test_tempfn_xxxxxx_one(p, "hoge", q, 0); +} + +static void test_tempfn_random_child_one(const char *p, const char *extra, const char *expect, int ret) { + _cleanup_free_ char *s = NULL; + int r; + + r = tempfn_random_child(p, extra, &s); + log_info_errno(r, "%s+%s → %s vs. %s (%i/%m vs. %i/%s)", p, strna(extra), strna(s), strna(expect), r, ret, strerror_safe(ret)); + + assert_se(!s == !expect); + if (s) { + const char *suffix; + + assert_se(suffix = startswith(s, expect)); + assert_se(in_charset(suffix, HEXDIGITS)); + assert_se(strlen(suffix) == 16); + } + assert_se(ret == r); +} + +TEST(tempfn_random_child) { + _cleanup_free_ char *dir = NULL, *p = NULL, *q = NULL; + + test_tempfn_random_child_one("", NULL, ".#", 0); + test_tempfn_random_child_one(".", NULL, ".#", 0); + test_tempfn_random_child_one("..", NULL, "../.#", 0); + test_tempfn_random_child_one("/", NULL, "/.#", 0); + test_tempfn_random_child_one("foo", "hoge/aaa", NULL, -EINVAL); + + test_tempfn_random_child_one("foo", NULL, "foo/.#", 0); + test_tempfn_random_child_one("foo", "bar", "foo/.#bar", 0); + test_tempfn_random_child_one("/tmp/foo", NULL, "/tmp/foo/.#", 0); + test_tempfn_random_child_one("/tmp/foo", "bar", "/tmp/foo/.#bar", 0); + test_tempfn_random_child_one("./foo", NULL, "foo/.#", 0); + test_tempfn_random_child_one("./foo", "bar", "foo/.#bar", 0); + test_tempfn_random_child_one("../foo", NULL, "../foo/.#", 0); + test_tempfn_random_child_one("../foo", "bar", "../foo/.#bar", 0); + + test_tempfn_random_child_one("foo/", NULL, "foo/.#", 0); + test_tempfn_random_child_one("foo/", "bar", "foo/.#bar", 0); + test_tempfn_random_child_one("/tmp/foo/", NULL, "/tmp/foo/.#", 0); + test_tempfn_random_child_one("/tmp/foo/", "bar", "/tmp/foo/.#bar", 0); + test_tempfn_random_child_one("./foo/", NULL, "foo/.#", 0); + test_tempfn_random_child_one("./foo/", "bar", "foo/.#bar", 0); + test_tempfn_random_child_one("../foo/", NULL, "../foo/.#", 0); + test_tempfn_random_child_one("../foo/", "bar", "../foo/.#bar", 0); + + assert_se(dir = new(char, PATH_MAX - 21)); + memset(dir, 'x', PATH_MAX - 22); + dir[PATH_MAX - 22] = '\0'; + for (size_t i = 0; i < PATH_MAX - 22; i += NAME_MAX + 1) + dir[i] = '/'; + + assert_se(p = path_join(dir, "a")); + assert_se(q = path_join(p, ".#")); + + test_tempfn_random_child_one(p, NULL, q, 0); + test_tempfn_random_child_one(p, "b", NULL, -EINVAL); + + p = mfree(p); + q = mfree(q); + + assert_se(p = new(char, NAME_MAX + 1)); + memset(p, 'x', NAME_MAX); + p[NAME_MAX] = '\0'; + + assert_se(q = path_join(p, ".#")); + + test_tempfn_random_child_one(p, NULL, q, 0); + + assert_se(strextend(&q, "hoge")); + test_tempfn_random_child_one(p, "hoge", q, 0); } DEFINE_TEST_MAIN(LOG_DEBUG);