From patchwork Mon Apr 27 17:56:39 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 87011 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 85393FF886B for ; Mon, 27 Apr 2026 17:56:58 +0000 (UTC) Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.2119.1777312614610159156 for ; Mon, 27 Apr 2026 10:56:54 -0700 Authentication-Results: mx.groups.io; dkim=none (message not signed); spf=pass (domain: kernel.crashing.org, ip: 63.228.1.57, mailfrom: mark.hatle@kernel.crashing.org) Received: from kernel.crashing.org.net (70-99-78-136.nuveramail.net [70.99.78.136] (may be forged)) by gate.crashing.org (8.18.1/8.18.1/Debian-2) with ESMTP id 63RHugAm876837; Mon, 27 Apr 2026 12:56:45 -0500 From: Mark Hatle To: yocto-patches@lists.yoctoproject.org, richard.purdie@linuxfoundation.org Cc: dburgener@linux.microsoft.com, peter.kjellerstedt@axis.com Subject: [pseudo][PATCH 09/11] test: Add test symlinkat and related Date: Mon, 27 Apr 2026 12:56:39 -0500 Message-Id: <1777312601-1393-10-git-send-email-mark.hatle@kernel.crashing.org> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1777312601-1393-1-git-send-email-mark.hatle@kernel.crashing.org> References: <1777312601-1393-1-git-send-email-mark.hatle@kernel.crashing.org> List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Mon, 27 Apr 2026 17:56:58 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/3856 From: Mark Hatle 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 Signed-off-by: Mark Hatle --- 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 +#include +#include +#include +#include +#include +#include +#include +#include + +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"