]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/test/test-fs-util.c
Merge pull request #11827 from keszybz/pkgconfig-variables
[thirdparty/systemd.git] / src / test / test-fs-util.c
index 284167cff2be348d9e0a56f374efd6eec828edeb..e049abc4a40b03ee66c6d55e7b249024112d477b 100644 (file)
@@ -1,16 +1,9 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include <unistd.h>
 
 #include "alloc-util.h"
 #include "fd-util.h"
-#include "fd-util.h"
-#include "fileio.h"
 #include "fs-util.h"
 #include "id128-util.h"
 #include "macro.h"
 #include "stdio-util.h"
 #include "string-util.h"
 #include "strv.h"
+#include "tests.h"
+#include "tmpfile-util.h"
 #include "user-util.h"
 #include "util.h"
+#include "virt.h"
+
+static const char *arg_test_dir = NULL;
 
 static void test_chase_symlinks(void) {
         _cleanup_free_ char *result = NULL;
-        char temp[] = "/tmp/test-chase.XXXXXX";
+        char *temp;
         const char *top, *p, *pslash, *q, *qslash;
+        struct stat st;
         int r, pfd;
 
+        log_info("/* %s */", __func__);
+
+        temp = strjoina(arg_test_dir ?: "/tmp", "/test-chase.XXXXXX");
         assert_se(mkdtemp(temp));
 
         top = strjoina(temp, "/top");
         assert_se(mkdir(top, 0700) >= 0);
 
         p = strjoina(top, "/dot");
-        assert_se(symlink(".", p) >= 0);
+        if (symlink(".", p) < 0) {
+                assert_se(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM));
+                log_tests_skipped_errno(errno, "symlink() not possible");
+                goto cleanup;
+        };
 
         p = strjoina(top, "/dotdot");
         assert_se(symlink("..", p) >= 0);
@@ -242,11 +248,11 @@ static void test_chase_symlinks(void) {
                 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) >= 0);
 
                 assert_se(chown(q, 0, 0) >= 0);
-                assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) == -EPERM);
+                assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) == -ENOLINK);
 
                 assert_se(rmdir(q) >= 0);
                 assert_se(symlink("/etc/passwd", q) >= 0);
-                assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) == -EPERM);
+                assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) == -ENOLINK);
 
                 assert_se(chown(p, 0, 0) >= 0);
                 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) >= 0);
@@ -271,54 +277,130 @@ static void test_chase_symlinks(void) {
                 assert_se(sd_id128_equal(a, b));
         }
 
+        /* Test CHASE_NOFOLLOW */
+
+        p = strjoina(temp, "/target");
+        q = strjoina(temp, "/symlink");
+        assert_se(symlink(p, q) >= 0);
+        pfd = chase_symlinks(q, NULL, CHASE_OPEN|CHASE_NOFOLLOW, &result);
+        assert_se(pfd > 0);
+        assert_se(path_equal(result, q));
+        assert_se(fstat(pfd, &st) >= 0);
+        assert_se(S_ISLNK(st.st_mode));
+        result = mfree(result);
+
+        /* s1 -> s2 -> nonexistent */
+        q = strjoina(temp, "/s1");
+        assert_se(symlink("s2", q) >= 0);
+        p = strjoina(temp, "/s2");
+        assert_se(symlink("nonexistent", p) >= 0);
+        pfd = chase_symlinks(q, NULL, CHASE_OPEN|CHASE_NOFOLLOW, &result);
+        assert_se(pfd > 0);
+        assert_se(path_equal(result, q));
+        assert_se(fstat(pfd, &st) >= 0);
+        assert_se(S_ISLNK(st.st_mode));
+        result = mfree(result);
+
+        /* Test CHASE_ONE */
+
+        p = strjoina(temp, "/start");
+        r = chase_symlinks(p, NULL, CHASE_STEP, &result);
+        assert_se(r == 0);
+        p = strjoina(temp, "/top/dot/dotdota");
+        assert_se(streq(p, result));
+        result = mfree(result);
+
+        r = chase_symlinks(p, NULL, CHASE_STEP, &result);
+        assert_se(r == 0);
+        p = strjoina(temp, "/top/./dotdota");
+        assert_se(streq(p, result));
+        result = mfree(result);
+
+        r = chase_symlinks(p, NULL, CHASE_STEP, &result);
+        assert_se(r == 0);
+        p = strjoina(temp, "/top/../a");
+        assert_se(streq(p, result));
+        result = mfree(result);
+
+        r = chase_symlinks(p, NULL, CHASE_STEP, &result);
+        assert_se(r == 0);
+        p = strjoina(temp, "/a");
+        assert_se(streq(p, result));
+        result = mfree(result);
+
+        r = chase_symlinks(p, NULL, CHASE_STEP, &result);
+        assert_se(r == 0);
+        p = strjoina(temp, "/b");
+        assert_se(streq(p, result));
+        result = mfree(result);
+
+        r = chase_symlinks(p, NULL, CHASE_STEP, &result);
+        assert_se(r == 0);
+        assert_se(streq("/usr", result));
+        result = mfree(result);
+
+        r = chase_symlinks("/usr", NULL, CHASE_STEP, &result);
+        assert_se(r > 0);
+        assert_se(streq("/usr", result));
+        result = mfree(result);
+
+ cleanup:
         assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
 }
 
 static void test_unlink_noerrno(void) {
-        char name[] = "/tmp/test-close_nointr.XXXXXX";
+        char *name;
         int fd;
 
+        log_info("/* %s */", __func__);
+
+        name = strjoina(arg_test_dir ?: "/tmp", "/test-close_nointr.XXXXXX");
         fd = mkostemp_safe(name);
         assert_se(fd >= 0);
         assert_se(close_nointr(fd) >= 0);
 
         {
                 PROTECT_ERRNO;
-                errno = -42;
+                errno = 42;
                 assert_se(unlink_noerrno(name) >= 0);
-                assert_se(errno == -42);
+                assert_se(errno == 42);
                 assert_se(unlink_noerrno(name) < 0);
-                assert_se(errno == -42);
+                assert_se(errno == 42);
         }
 }
 
 static void test_readlink_and_make_absolute(void) {
-        char tempdir[] = "/tmp/test-readlink_and_make_absolute";
-        char name[] = "/tmp/test-readlink_and_make_absolute/original";
-        char name2[] = "test-readlink_and_make_absolute/original";
-        char name_alias[] = "/tmp/test-readlink_and_make_absolute-alias";
-        char *r = NULL;
-        _cleanup_free_ char *pwd = NULL;
+        const char *tempdir, *name, *name2, *name_alias;
+        _cleanup_free_ char *r1 = NULL, *r2 = NULL, *pwd = NULL;
+
+        log_info("/* %s */", __func__);
+
+        tempdir = strjoina(arg_test_dir ?: "/tmp", "/test-readlink_and_make_absolute");
+        name = strjoina(tempdir, "/original");
+        name2 = "test-readlink_and_make_absolute/original";
+        name_alias = strjoina(arg_test_dir ?: "/tmp", "/test-readlink_and_make_absolute-alias");
 
         assert_se(mkdir_safe(tempdir, 0755, getuid(), getgid(), MKDIR_WARN_MODE) >= 0);
         assert_se(touch(name) >= 0);
 
-        assert_se(symlink(name, name_alias) >= 0);
-        assert_se(readlink_and_make_absolute(name_alias, &r) >= 0);
-        assert_se(streq(r, name));
-        free(r);
-        assert_se(unlink(name_alias) >= 0);
+        if (symlink(name, name_alias) < 0) {
+                assert_se(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM));
+                log_tests_skipped_errno(errno, "symlink() not possible");
+        } else {
+                assert_se(readlink_and_make_absolute(name_alias, &r1) >= 0);
+                assert_se(streq(r1, name));
+                assert_se(unlink(name_alias) >= 0);
 
-        assert_se(safe_getcwd(&pwd) >= 0);
+                assert_se(safe_getcwd(&pwd) >= 0);
 
-        assert_se(chdir(tempdir) >= 0);
-        assert_se(symlink(name2, name_alias) >= 0);
-        assert_se(readlink_and_make_absolute(name_alias, &r) >= 0);
-        assert_se(streq(r, name));
-        free(r);
-        assert_se(unlink(name_alias) >= 0);
+                assert_se(chdir(tempdir) >= 0);
+                assert_se(symlink(name2, name_alias) >= 0);
+                assert_se(readlink_and_make_absolute(name_alias, &r2) >= 0);
+                assert_se(streq(r2, name));
+                assert_se(unlink(name_alias) >= 0);
 
-        assert_se(chdir(pwd) >= 0);
+                assert_se(chdir(pwd) >= 0);
+        }
 
         assert_se(rm_rf(tempdir, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
 }
@@ -326,7 +408,7 @@ static void test_readlink_and_make_absolute(void) {
 static void test_get_files_in_directory(void) {
         _cleanup_strv_free_ char **l = NULL, **t = NULL;
 
-        assert_se(get_files_in_directory("/tmp", &l) >= 0);
+        assert_se(get_files_in_directory(arg_test_dir ?: "/tmp", &l) >= 0);
         assert_se(get_files_in_directory(".", &t) >= 0);
         assert_se(get_files_in_directory(".", NULL) >= 0);
 }
@@ -335,6 +417,8 @@ static void test_var_tmp(void) {
         _cleanup_free_ char *tmpdir_backup = NULL, *temp_backup = NULL, *tmp_backup = NULL;
         const char *tmp_dir = NULL, *t;
 
+        log_info("/* %s */", __func__);
+
         t = getenv("TMPDIR");
         if (t) {
                 tmpdir_backup = strdup(t);
@@ -389,6 +473,8 @@ static void test_var_tmp(void) {
 }
 
 static void test_dot_or_dot_dot(void) {
+        log_info("/* %s */", __func__);
+
         assert_se(!dot_or_dot_dot(NULL));
         assert_se(!dot_or_dot_dot(""));
         assert_se(!dot_or_dot_dot("xxx"));
@@ -401,8 +487,12 @@ static void test_dot_or_dot_dot(void) {
 static void test_access_fd(void) {
         _cleanup_(rmdir_and_freep) char *p = NULL;
         _cleanup_close_ int fd = -1;
+        const char *a;
+
+        log_info("/* %s */", __func__);
 
-        assert_se(mkdtemp_malloc("/tmp/access-fd.XXXXXX", &p) >= 0);
+        a = strjoina(arg_test_dir ?: "/tmp", "/access-fd.XXXXXX");
+        assert_se(mkdtemp_malloc(a, &p) >= 0);
 
         fd = open(p, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
         assert_se(fd >= 0);
@@ -430,16 +520,26 @@ static void test_touch_file(void) {
         struct stat st;
         const char *a;
         usec_t test_mtime;
+        int r;
+
+        log_info("/* %s */", __func__);
 
         test_uid = geteuid() == 0 ? 65534 : getuid();
         test_gid = geteuid() == 0 ? 65534 : getgid();
 
         test_mtime = usec_sub_unsigned(now(CLOCK_REALTIME), USEC_PER_WEEK);
 
-        assert_se(mkdtemp_malloc("/dev/shm/touch-file-XXXXXX", &p) >= 0);
+        a = strjoina(arg_test_dir ?: "/dev/shm", "/touch-file-XXXXXX");
+        assert_se(mkdtemp_malloc(a, &p) >= 0);
 
         a = strjoina(p, "/regular");
-        assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
+        r = touch_file(a, false, test_mtime, test_uid, test_gid, 0640);
+        if (r < 0) {
+                assert_se(IN_SET(r, -EINVAL, -ENOSYS, -ENOTTY, -EPERM));
+                log_tests_skipped_errno(errno, "touch_file() not possible");
+                return;
+        }
+
         assert_se(lstat(a, &st) >= 0);
         assert_se(st.st_uid == test_uid);
         assert_se(st.st_gid == test_gid);
@@ -479,7 +579,12 @@ static void test_touch_file(void) {
 
         if (geteuid() == 0) {
                 a = strjoina(p, "/cdev");
-                assert_se(mknod(a, 0775 | S_IFCHR, makedev(0, 0)) >= 0);
+                r = mknod(a, 0775 | S_IFCHR, makedev(0, 0));
+                if (r < 0 && errno == EPERM && detect_container() > 0) {
+                        log_notice("Running in unprivileged container? Skipping remaining tests in %s", __func__);
+                        return;
+                }
+                assert_se(r >= 0);
                 assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
                 assert_se(lstat(a, &st) >= 0);
                 assert_se(st.st_uid == test_uid);
@@ -515,7 +620,9 @@ static void test_unlinkat_deallocate(void) {
         _cleanup_close_ int fd = -1;
         struct stat st;
 
-        assert_se(tempfn_random_child(NULL, "unlink-deallocation", &p) >= 0);
+        log_info("/* %s */", __func__);
+
+        assert_se(tempfn_random_child(arg_test_dir, "unlink-deallocation", &p) >= 0);
 
         fd = open(p, O_WRONLY|O_CLOEXEC|O_CREAT|O_EXCL, 0600);
         assert_se(fd >= 0);
@@ -530,7 +637,8 @@ static void test_unlinkat_deallocate(void) {
         assert_se(unlinkat_deallocate(AT_FDCWD, p, 0) >= 0);
 
         assert_se(fstat(fd, &st) >= 0);
-        assert_se(IN_SET(st.st_size, 0, 6)); /* depending on whether hole punching worked the size will be 6 (it worked) or 0 (we had to resort to truncation) */
+        assert_se(IN_SET(st.st_size, 0, 6)); /* depending on whether hole punching worked the size will be 6
+                                                (it worked) or 0 (we had to resort to truncation) */
         assert_se(st.st_blocks == 0);
         assert_se(st.st_nlink == 0);
 }
@@ -538,13 +646,87 @@ static void test_unlinkat_deallocate(void) {
 static void test_fsync_directory_of_file(void) {
         _cleanup_close_ int fd = -1;
 
-        fd = open_tmpfile_unlinkable(NULL, O_RDWR);
+        log_info("/* %s */", __func__);
+
+        fd = open_tmpfile_unlinkable(arg_test_dir, O_RDWR);
         assert_se(fd >= 0);
 
         assert_se(fsync_directory_of_file(fd) >= 0);
 }
 
+static void test_rename_noreplace(void) {
+        static const char* const table[] = {
+                "/reg",
+                "/dir",
+                "/fifo",
+                "/socket",
+                "/symlink",
+                NULL
+        };
+
+        _cleanup_(rm_rf_physical_and_freep) char *z = NULL;
+        const char *j = NULL;
+        char **a, **b;
+
+        log_info("/* %s */", __func__);
+
+        if (arg_test_dir)
+                j = strjoina(arg_test_dir, "/testXXXXXX");
+        assert_se(mkdtemp_malloc(j, &z) >= 0);
+
+        j = strjoina(z, table[0]);
+        assert_se(touch(j) >= 0);
+
+        j = strjoina(z, table[1]);
+        assert_se(mkdir(j, 0777) >= 0);
+
+        j = strjoina(z, table[2]);
+        (void) mkfifo(j, 0777);
+
+        j = strjoina(z, table[3]);
+        (void) mknod(j, S_IFSOCK | 0777, 0);
+
+        j = strjoina(z, table[4]);
+        (void) symlink("foobar", j);
+
+        STRV_FOREACH(a, (char**) table) {
+                _cleanup_free_ char *x = NULL, *y = NULL;
+
+                x = strjoin(z, *a);
+                assert_se(x);
+
+                if (access(x, F_OK) < 0) {
+                        assert_se(errno == ENOENT);
+                        continue;
+                }
+
+                STRV_FOREACH(b, (char**) table) {
+                        _cleanup_free_ char *w = NULL;
+
+                        w = strjoin(w, *b);
+                        assert_se(w);
+
+                        if (access(w, F_OK) < 0) {
+                                assert_se(errno == ENOENT);
+                                continue;
+                        }
+
+                        assert_se(rename_noreplace(AT_FDCWD, w, AT_FDCWD, y) == -EEXIST);
+                }
+
+                y = strjoin(z, "/somethingelse");
+                assert_se(y);
+
+                assert_se(rename_noreplace(AT_FDCWD, x, AT_FDCWD, y) >= 0);
+                assert_se(rename_noreplace(AT_FDCWD, y, AT_FDCWD, x) >= 0);
+        }
+}
+
 int main(int argc, char *argv[]) {
+        test_setup_logging(LOG_INFO);
+
+        arg_test_dir = argv[1];
+
         test_unlink_noerrno();
         test_get_files_in_directory();
         test_readlink_and_make_absolute();
@@ -555,6 +737,7 @@ int main(int argc, char *argv[]) {
         test_touch_file();
         test_unlinkat_deallocate();
         test_fsync_directory_of_file();
+        test_rename_noreplace();
 
         return 0;
 }