Following Linux, allow the pathname argument to be an empty string,
provided the dirfd argument refers to a symlink opened with O_PATH and
O_NOFOLLOW.  The readlinkat or fchownat call then operates on that
symlink.  In the case of fchownat, the call must specify the
AT_EMPTY_PATH flag.
---
 winsup/cygwin/syscalls.cc | 40 ++++++++++++++++++++++++++++++++++-----
 1 file changed, 35 insertions(+), 5 deletions(-)

diff --git a/winsup/cygwin/syscalls.cc b/winsup/cygwin/syscalls.cc
index 038a316db..3d87fd685 100644
--- a/winsup/cygwin/syscalls.cc
+++ b/winsup/cygwin/syscalls.cc
@@ -4785,14 +4785,29 @@ fchownat (int dirfd, const char *pathname, uid_t uid, 
gid_t gid, int flags)
   tmp_pathbuf tp;
   __try
     {
-      if (flags & ~AT_SYMLINK_NOFOLLOW)
+      if (flags & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH))
        {
          set_errno (EINVAL);
          __leave;
        }
       char *path = tp.c_get ();
-      if (gen_full_path_at (path, dirfd, pathname))
-       __leave;
+      int res = gen_full_path_at (path, dirfd, pathname);
+      if (res)
+       {
+         if (!(errno == ENOENT && (flags & AT_EMPTY_PATH)))
+           __leave;
+         /* pathname is an empty string.  This is OK if dirfd refers
+            to a symlink that was opened with O_PATH and O_NOFOLLOW.
+            In this case, fchownat operates on the symlink. */
+         cygheap_fdget cfd (dirfd);
+         if (cfd < 0)
+           __leave;
+         if (!(cfd->issymlink ()
+               && cfd->get_flags () & O_PATH
+               && cfd->get_flags () & O_NOFOLLOW))
+           __leave;
+         return lchown (cfd->get_name (), uid, gid);
+       }
       return chown_worker (path, (flags & AT_SYMLINK_NOFOLLOW)
                                 ? PC_SYM_NOFOLLOW : PC_SYM_FOLLOW, uid, gid);
     }
@@ -4979,8 +4994,23 @@ readlinkat (int dirfd, const char *__restrict pathname, 
char *__restrict buf,
   __try
     {
       char *path = tp.c_get ();
-      if (gen_full_path_at (path, dirfd, pathname))
-       __leave;
+      int res = gen_full_path_at (path, dirfd, pathname);
+      if (res)
+       {
+         if (errno != ENOENT)
+           __leave;
+         /* pathname is an empty string.  This is OK if dirfd refers
+            to a symlink that was opened with O_PATH and O_NOFOLLOW.
+            In this case, readlinkat operates on the symlink. */
+         cygheap_fdget cfd (dirfd);
+         if (cfd < 0)
+           __leave;
+         if (!(cfd->issymlink ()
+               && cfd->get_flags () & O_PATH
+               && cfd->get_flags () & O_NOFOLLOW))
+           __leave;
+         strcpy (path, cfd->get_name ());
+       }
       return readlink (path, buf, bufsize);
     }
   __except (EFAULT) {}
-- 
2.21.0

Reply via email to