From 7a2349072e165c27ed0655934b05530c19d23779 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Thu, 15 Feb 2024 07:01:17 +0900
Subject: [PATCH] fs-util: readlinkat() supports an empty string

From readlinkat(2):
Since Linux 2.6.39, pathname can be an empty string, in which case the
call operates on the symbolic link referred to by dirfd (which should
have been obtained using open(2) with the O_PATH and O_NOFOLLOW flags).

(cherry picked from commit e4c094c05543410ba05a16f757d1e11652f4f6bd)
(cherry picked from commit 30142e781d7afcfa93185d2543f59e9cf90dc882)

Conflict:NA
Reference:https://github.com/systemd/systemd-stable/commit/7a2349072e165c27ed0655934b05530c19d23779
---
 src/basic/fs-util.c     |  8 ++++++--
 src/test/test-fs-util.c | 35 +++++++++++++++++++++++++++++++++++
 2 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c
index ee38e0266a..9ba9268d77 100644
--- a/src/basic/fs-util.c
+++ b/src/basic/fs-util.c
@@ -116,7 +116,11 @@ int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char
 int readlinkat_malloc(int fd, const char *p, char **ret) {
         size_t l = PATH_MAX;
 
-        assert(p);
+        assert(fd >= 0 || fd == AT_FDCWD);
+
+        if (fd < 0 && isempty(p))
+                return -EISDIR; /* In this case, the fd points to the current working directory, and is
+                                 * definitely not a symlink. Let's return earlier. */
 
         for (;;) {
                 _cleanup_free_ char *c = NULL;
@@ -126,7 +130,7 @@ int readlinkat_malloc(int fd, const char *p, char **ret) {
                 if (!c)
                         return -ENOMEM;
 
-                n = readlinkat(fd, p, c, l);
+                n = readlinkat(fd, strempty(p), c, l);
                 if (n < 0)
                         return -errno;
 
diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c
index 5de1eea0d4..ef335b43ae 100644
--- a/src/test/test-fs-util.c
+++ b/src/test/test-fs-util.c
@@ -758,4 +758,39 @@ static int intro(void) {
         return EXIT_SUCCESS;
 }
 
+TEST(readlinkat_malloc) {
+        _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+        _cleanup_close_ int tfd = -EBADF, fd = -EBADF;
+        _cleanup_free_ char *p = NULL, *q = NULL;
+        const char *expect = "hgoehogefoobar";
+
+        tfd = mkdtemp_open(NULL, O_PATH, &t);
+        assert_se(tfd >= 0);
+
+        assert_se(symlinkat(expect, tfd, "linkname") >= 0);
+
+        assert_se(readlinkat_malloc(tfd, "linkname", &p) >= 0);
+        assert_se(streq(p, expect));
+        p = mfree(p);
+
+        fd = openat(tfd, "linkname", O_PATH | O_NOFOLLOW | O_CLOEXEC);
+        assert_se(fd >= 0);
+        assert_se(readlinkat_malloc(fd, NULL, &p) >= 0);
+        assert_se(streq(p, expect));
+        p = mfree(p);
+        assert_se(readlinkat_malloc(fd, "", &p) >= 0);
+        assert_se(streq(p, expect));
+        p = mfree(p);
+        fd = safe_close(fd);
+
+        assert_se(q = path_join(t, "linkname"));
+        assert_se(readlinkat_malloc(AT_FDCWD, q, &p) >= 0);
+        assert_se(streq(p, expect));
+        p = mfree(p);
+        assert_se(readlinkat_malloc(INT_MAX, q, &p) >= 0);
+        assert_se(streq(p, expect));
+        p = mfree(p);
+        q = mfree(q);
+}
+
 DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro);
-- 
2.33.0

