[pseudo][PATCH 09/11] test: Add test symlinkat and related

Mark Hatle <[email protected]>
Newsgroups org.yoctoproject.lists.yocto-patches
Message-ID <[email protected]>
From: Mark Hatle <[email protected]>

Add a test case that verifies link, linkat, symlink, symlinkat, and
readlink.

Test both with and without chroot.

AI-Generated: test cases generated by github copilot (claude opus 6.4)

Signed-off-by: Mark Hatle <[email protected]>
Signed-off-by: Mark Hatle <[email protected]>
---
 test/test-link-symlink.c  | 175 ++++++++++++++++++++++++++++++++++++++++++++++
 test/test-link-symlink.sh |  11 +++
 2 files changed, 186 insertions(+)
 create mode 100644 test/test-link-symlink.c
 create mode 100755 test/test-link-symlink.sh

diff --git a/test/test-link-symlink.c b/test/test-link-symlink.c
new file mode 100644
index 0000000..f8a1026
--- /dev/null
+++ b/test/test-link-symlink.c
@@ -0,0 +1,175 @@
+/*
+ * Test link, linkat, symlink, symlinkat, readlink, readlinkat
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+
+static int failures = 0;
+
+static void check(const char *desc, int condition) {
+    if (!condition) {
+        fprintf(stderr, "FAIL: %s\n", desc);
+        failures++;
+    }
+}
+
+static int test_basic(void) {
+    struct stat st, st2;
+    char buf[PATH_MAX];
+    ssize_t len;
+    int fd, dirfd;
+
+    /* Create a test file */
+    fd = open("test_link_file", O_CREAT | O_WRONLY, 0644);
+    check("write", write(fd, "hello", 5) == 5);
+    close(fd);
+    check("chown", chown("test_link_file", 100, 200) == 0);
+
+    /* symlink */
+    check("symlink", symlink("test_link_file", "test_link_sym") == 0);
+    check("symlink lstat", lstat("test_link_sym", &st) == 0);
+    check("symlink is link", S_ISLNK(st.st_mode));
+
+    /* readlink */
+    len = readlink("test_link_sym", buf, sizeof(buf));
+    check("readlink len", len > 0);
+    buf[len] = '\0';
+    check("readlink value", strcmp(buf, "test_link_file") == 0);
+
+    /* symlink target stat should show chown'd values */
+    stat("test_link_sym", &st);
+    check("symlink target uid", st.st_uid == 100);
+    check("symlink target gid", st.st_gid == 200);
+
+    /* link (hard link) */
+    check("link", link("test_link_file", "test_link_hard") == 0);
+    stat("test_link_hard", &st);
+    stat("test_link_file", &st2);
+    check("hardlink same inode", st.st_ino == st2.st_ino);
+    check("hardlink uid", st.st_uid == 100);
+    check("hardlink gid", st.st_gid == 200);
+
+    /* linkat */
+    dirfd = open(".", O_RDONLY | O_DIRECTORY);
+    check("linkat", linkat(dirfd, "test_link_file", dirfd, "test_link_hard2", 0) == 0);
+    stat("test_link_hard2", &st);
+    check("linkat uid", st.st_uid == 100);
+
+    /* symlinkat */
+    check("symlinkat", symlinkat("test_link_file", dirfd, "test_link_sym2") == 0);
+    check("symlinkat lstat", fstatat(dirfd, "test_link_sym2", &st, AT_SYMLINK_NOFOLLOW) == 0);
+    check("symlinkat is link", S_ISLNK(st.st_mode));
+
+    /* readlinkat */
+    len = readlinkat(dirfd, "test_link_sym2", buf, sizeof(buf));
+    check("readlinkat len", len > 0);
+    buf[len] = '\0';
+    check("readlinkat value", strcmp(buf, "test_link_file") == 0);
+
+    close(dirfd);
+    unlink("test_link_sym");
+    unlink("test_link_sym2");
+    unlink("test_link_hard");
+    unlink("test_link_hard2");
+    unlink("test_link_file");
+
+    return 0;
+}
+
+static int test_chroot(const char *chroot_dir) {
+    struct stat st;
+    char buf[PATH_MAX];
+    char path[PATH_MAX];
+    ssize_t len;
+    int fd, dirfd;
+
+    /* Create a file inside the chroot directory */
+    snprintf(path, sizeof(path), "%s/cr_link_file", chroot_dir);
+    fd = open(path, O_CREAT | O_WRONLY, 0644);
+    if (fd < 0) { perror("open chroot file"); return 1; }
+    check("chroot: write", write(fd, "hello", 5) == 5);
+    close(fd);
+    check("chroot: chown", chown(path, 100, 200) == 0);
+
+    if (chroot(chroot_dir) != 0) {
+        perror("chroot");
+        return 1;
+    }
+    if (chdir("/") != 0) {
+        perror("chdir /");
+        return 1;
+    }
+
+    /* symlink with absolute target inside chroot.
+     * The fix in pseudo_util.c strips a doubled chroot prefix
+     * from absolute symlink targets during path resolution.
+     * Without the fix, stat through this symlink would fail or
+     * resolve to the wrong path.
+     */
+    check("chroot: symlink abs", symlink("/cr_link_file", "/cr_sym_abs") == 0);
+    check("chroot: stat through abs symlink", stat("/cr_sym_abs", &st) == 0);
+    check("chroot: abs symlink uid", st.st_uid == 100);
+    check("chroot: abs symlink gid", st.st_gid == 200);
+
+    /* readlink should return the chroot-relative target */
+    len = readlink("/cr_sym_abs", buf, sizeof(buf));
+    check("chroot: readlink abs len", len > 0);
+    if (len > 0) {
+        buf[len] = '\0';
+        check("chroot: readlink abs value", strcmp(buf, "/cr_link_file") == 0);
+    }
+
+    /* symlink with relative target inside chroot */
+    check("chroot: symlink rel", symlink("cr_link_file", "/cr_sym_rel") == 0);
+    check("chroot: stat through rel symlink", stat("/cr_sym_rel", &st) == 0);
+    check("chroot: rel symlink uid", st.st_uid == 100);
+
+    /* symlinkat with absolute target inside chroot */
+    dirfd = open("/", O_RDONLY | O_DIRECTORY);
+    check("chroot: open dirfd", dirfd >= 0);
+    check("chroot: symlinkat abs", symlinkat("/cr_link_file", dirfd, "cr_symat_abs") == 0);
+    check("chroot: stat symlinkat abs", fstatat(dirfd, "cr_symat_abs", &st, 0) == 0);
+    check("chroot: symlinkat abs uid", st.st_uid == 100);
+
+    /* readlinkat should return the chroot-relative target */
+    len = readlinkat(dirfd, "cr_symat_abs", buf, sizeof(buf));
+    check("chroot: readlinkat abs len", len > 0);
+    if (len > 0) {
+        buf[len] = '\0';
+        check("chroot: readlinkat abs value", strcmp(buf, "/cr_link_file") == 0);
+    }
+
+    /* linkat inside chroot */
+    check("chroot: linkat", linkat(dirfd, "cr_link_file", dirfd, "cr_hard", 0) == 0);
+    check("chroot: stat linkat", fstatat(dirfd, "cr_hard", &st, 0) == 0);
+    check("chroot: linkat uid", st.st_uid == 100);
+
+    close(dirfd);
+    return 0;
+}
+
+int main(int argc, char *argv[]) {
+    int rc;
+
+    rc = test_basic();
+    if (rc)
+        return rc;
+
+    if (argc > 1) {
+        rc = test_chroot(argv[1]);
+        if (rc)
+            return rc;
+    }
+
+    return failures;
+}
diff --git a/test/test-link-symlink.sh b/test/test-link-symlink.sh
new file mode 100755
index 0000000..f758f37
--- /dev/null
+++ b/test/test-link-symlink.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# SPDX-License-Identifier: LGPL-2.1-only
+#
+# Test link, linkat, symlink, symlinkat, readlink, readlinkat (basic + chroot)
+
+CHROOT_DIR=$(mktemp -d "${PWD}/chroot_link_XXXXXX")
+trap "rm -rf '$CHROOT_DIR'" 0
+
+rm -f test_link_file test_link_sym test_link_sym2 test_link_hard test_link_hard2
+./test/test-link-symlink "$CHROOT_DIR"
-- 
1.8.3.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.