]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/test/test-fs-util.c
tree-wide: use UINT64_MAX or friends
[thirdparty/systemd.git] / src / test / test-fs-util.c
index ac8b95aece0eaed479a1d419a67fe8965909c97f..c42c48722f9fe6b214c767532d2905b6888c0abd 100644 (file)
@@ -1,14 +1,17 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include <unistd.h>
 
 #include "alloc-util.h"
+#include "copy.h"
 #include "fd-util.h"
+#include "fileio.h"
 #include "fs-util.h"
 #include "id128-util.h"
 #include "macro.h"
 #include "mkdir.h"
 #include "path-util.h"
+#include "random-util.h"
 #include "rm-rf.h"
 #include "stdio-util.h"
 #include "string-util.h"
@@ -148,6 +151,7 @@ static void test_chase_symlinks(void) {
         r = chase_symlinks(p, NULL, 0, &result, NULL);
         assert_se(r > 0);
         assert_se(path_equal(result, "/usr"));
+        assert_se(streq(result, "/usr")); /* we guarantee that we drop redundant slashes */
         result = mfree(result);
 
         r = chase_symlinks(p, temp, 0, &result, NULL);
@@ -371,6 +375,15 @@ static void test_chase_symlinks(void) {
         assert_se(streq("/usr", result));
         result = mfree(result);
 
+        /* Make sure that symlinks in the "root" path are not resolved, but those below are */
+        p = strjoina("/etc/..", temp, "/self");
+        assert_se(symlink(".", p) >= 0);
+        q = strjoina(p, "/top/dot/dotdota");
+        r = chase_symlinks(q, p, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(path_startswith(result, p), "usr"));
+        result = mfree(result);
+
  cleanup:
         assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
 }
@@ -605,8 +618,8 @@ static void test_touch_file(void) {
         assert_se(timespec_load(&st.st_mtim) == test_mtime);
 
         if (geteuid() == 0) {
-                a = strjoina(p, "/cdev");
-                r = mknod(a, 0775 | S_IFCHR, makedev(0, 0));
+                a = strjoina(p, "/bdev");
+                r = mknod(a, 0775 | S_IFBLK, makedev(0, 0));
                 if (r < 0 && errno == EPERM && detect_container() > 0) {
                         log_notice("Running in unprivileged container? Skipping remaining tests in %s", __func__);
                         return;
@@ -616,17 +629,17 @@ static void test_touch_file(void) {
                 assert_se(lstat(a, &st) >= 0);
                 assert_se(st.st_uid == test_uid);
                 assert_se(st.st_gid == test_gid);
-                assert_se(S_ISCHR(st.st_mode));
+                assert_se(S_ISBLK(st.st_mode));
                 assert_se((st.st_mode & 0777) == 0640);
                 assert_se(timespec_load(&st.st_mtim) == test_mtime);
 
-                a = strjoina(p, "/bdev");
-                assert_se(mknod(a, 0775 | S_IFBLK, makedev(0, 0)) >= 0);
+                a = strjoina(p, "/cdev");
+                assert_se(mknod(a, 0775 | S_IFCHR, makedev(0, 0)) >= 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);
                 assert_se(st.st_gid == test_gid);
-                assert_se(S_ISBLK(st.st_mode));
+                assert_se(S_ISCHR(st.st_mode));
                 assert_se((st.st_mode & 0777) == 0640);
                 assert_se(timespec_load(&st.st_mtim) == test_mtime);
         }
@@ -660,7 +673,7 @@ static void test_unlinkat_deallocate(void) {
         assert_se(st.st_blocks > 0);
         assert_se(st.st_nlink == 1);
 
-        assert_se(unlinkat_deallocate(AT_FDCWD, p, 0) >= 0);
+        assert_se(unlinkat_deallocate(AT_FDCWD, p, UNLINK_ERASE) >= 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
@@ -729,7 +742,7 @@ static void test_rename_noreplace(void) {
                 STRV_FOREACH(b, (char**) table) {
                         _cleanup_free_ char *w = NULL;
 
-                        w = strjoin(w, *b);
+                        w = strjoin(z, *b);
                         assert_se(w);
 
                         if (access(w, F_OK) < 0) {
@@ -737,7 +750,7 @@ static void test_rename_noreplace(void) {
                                 continue;
                         }
 
-                        assert_se(rename_noreplace(AT_FDCWD, w, AT_FDCWD, y) == -EEXIST);
+                        assert_se(rename_noreplace(AT_FDCWD, x, AT_FDCWD, w) == -EEXIST);
                 }
 
                 y = strjoin(z, "/somethingelse");
@@ -792,6 +805,98 @@ static void test_chmod_and_chown(void) {
         assert_se(S_ISLNK(st.st_mode));
 }
 
+static void test_path_is_encrypted_one(const char *p, int expect) {
+        int r;
+
+        r = path_is_encrypted(p);
+        if (r == -ENOENT || ERRNO_IS_PRIVILEGE(r)) /* This might fail, if btrfs is used and we run in a
+                           * container. In that case we cannot resolve the device node paths that
+                           * BTRFS_IOC_DEV_INFO returns, because the device nodes are unlikely to exist in
+                           * the container. But if we can't stat() them we cannot determine the dev_t of
+                           * them, and thus cannot figure out if they are enrypted. Hence let's just ignore
+                           * ENOENT here. Also skip the test if we lack privileges. */
+                return;
+        assert_se(r >= 0);
+
+        log_info("%s encrypted: %s", p, yes_no(r));
+
+        assert_se(expect < 0 || ((r > 0) == (expect > 0)));
+}
+
+static void test_path_is_encrypted(void) {
+        int booted = sd_booted(); /* If this is run in build environments such as koji, /dev might be a
+                                   * reguar fs. Don't assume too much if not running under systemd. */
+
+        log_info("/* %s (sd_booted=%d)*/", __func__, booted);
+
+        test_path_is_encrypted_one("/home", -1);
+        test_path_is_encrypted_one("/var", -1);
+        test_path_is_encrypted_one("/", -1);
+        test_path_is_encrypted_one("/proc", false);
+        test_path_is_encrypted_one("/sys", false);
+        test_path_is_encrypted_one("/dev", booted > 0 ? false : -1);
+}
+
+static void create_binary_file(const char *p, const void *data, size_t l) {
+        _cleanup_close_ int fd = -1;
+
+        fd = open(p, O_CREAT|O_WRONLY|O_EXCL|O_CLOEXEC, 0600);
+        assert_se(fd >= 0);
+        assert_se(write(fd, data, l) == (ssize_t) l);
+}
+
+static void test_conservative_rename(void) {
+        _cleanup_(unlink_and_freep) char *p = NULL;
+        _cleanup_free_ char *q = NULL;
+        size_t l = 16*1024 + random_u64() % (32 * 1024); /* some randomly sized buffer 16k…48k */
+        uint8_t buffer[l+1];
+
+        random_bytes(buffer, l);
+
+        assert_se(tempfn_random_child(NULL, NULL, &p) >= 0);
+        create_binary_file(p, buffer, l);
+
+        assert_se(tempfn_random_child(NULL, NULL, &q) >= 0);
+
+        /* Check that the hardlinked "copy" is detected */
+        assert_se(link(p, q) >= 0);
+        assert_se(conservative_renameat(AT_FDCWD, q, AT_FDCWD, p) == 0);
+        assert_se(access(q, F_OK) < 0 && errno == ENOENT);
+
+        /* Check that a manual copy is detected */
+        assert_se(copy_file(p, q, 0, MODE_INVALID, 0, 0, COPY_REFLINK) >= 0);
+        assert_se(conservative_renameat(AT_FDCWD, q, AT_FDCWD, p) == 0);
+        assert_se(access(q, F_OK) < 0 && errno == ENOENT);
+
+        /* Check that a manual new writeout is also detected */
+        create_binary_file(q, buffer, l);
+        assert_se(conservative_renameat(AT_FDCWD, q, AT_FDCWD, p) == 0);
+        assert_se(access(q, F_OK) < 0 && errno == ENOENT);
+
+        /* Check that a minimally changed version is detected */
+        buffer[47] = ~buffer[47];
+        create_binary_file(q, buffer, l);
+        assert_se(conservative_renameat(AT_FDCWD, q, AT_FDCWD, p) > 0);
+        assert_se(access(q, F_OK) < 0 && errno == ENOENT);
+
+        /* Check that this really is new updated version */
+        create_binary_file(q, buffer, l);
+        assert_se(conservative_renameat(AT_FDCWD, q, AT_FDCWD, p) == 0);
+        assert_se(access(q, F_OK) < 0 && errno == ENOENT);
+
+        /* Make sure we detect extended files */
+        buffer[l++] = 47;
+        create_binary_file(q, buffer, l);
+        assert_se(conservative_renameat(AT_FDCWD, q, AT_FDCWD, p) > 0);
+        assert_se(access(q, F_OK) < 0 && errno == ENOENT);
+
+        /* Make sure we detect truncated files */
+        l--;
+        create_binary_file(q, buffer, l);
+        assert_se(conservative_renameat(AT_FDCWD, q, AT_FDCWD, p) > 0);
+        assert_se(access(q, F_OK) < 0 && errno == ENOENT);
+}
+
 int main(int argc, char *argv[]) {
         test_setup_logging(LOG_INFO);
 
@@ -809,6 +914,8 @@ int main(int argc, char *argv[]) {
         test_fsync_directory_of_file();
         test_rename_noreplace();
         test_chmod_and_chown();
+        test_path_is_encrypted();
+        test_conservative_rename();
 
         return 0;
 }