This is the mail archive of the libc-alpha@sourceware.org mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH 3/5] Linux: Emulate fchmodat with AT_SYMLINK_NOFOLLOW using O_PATH [BZ #14578]


/proc/self/fd files are special and chmod on O_PATH descriptors
in that directory operates on the symbolic link itself (like lchmod).
---
 sysdeps/unix/sysv/linux/fchmodat.c | 61 +++++++++++++++++++++++++-----
 1 file changed, 51 insertions(+), 10 deletions(-)

diff --git a/sysdeps/unix/sysv/linux/fchmodat.c b/sysdeps/unix/sysv/linux/fchmodat.c
index c41ebb290d..ac318ceb79 100644
--- a/sysdeps/unix/sysv/linux/fchmodat.c
+++ b/sysdeps/unix/sysv/linux/fchmodat.c
@@ -18,24 +18,65 @@
 
 #include <errno.h>
 #include <fcntl.h>
-#include <stddef.h>
+#include <not-cancel.h>
 #include <stdio.h>
-#include <string.h>
-#include <unistd.h>
+#include <sys/stat.h>
 #include <sys/types.h>
-#include <alloca.h>
 #include <sysdep.h>
+#include <unistd.h>
 
 int
 fchmodat (int fd, const char *file, mode_t mode, int flag)
 {
-  if (flag & ~AT_SYMLINK_NOFOLLOW)
+  if (flag == 0)
+    return INLINE_SYSCALL (fchmodat, 3, fd, file, mode);
+  else if (flag != AT_SYMLINK_NOFOLLOW)
     return INLINE_SYSCALL_ERROR_RETURN_VALUE (EINVAL);
-#ifndef __NR_lchmod		/* Linux so far has no lchmod syscall.  */
-  if (flag & AT_SYMLINK_NOFOLLOW)
-    return INLINE_SYSCALL_ERROR_RETURN_VALUE (ENOTSUP);
-#endif
+  else
+    {
+      /* The kernel system call does not have a mode argument.
+	 However, we can create an O_PATH descriptor and change that
+	 via /proc (which does not resolve symbolic links).  */
+
+      int pathfd = __openat_nocancel (fd, file,
+				      O_PATH | O_NOFOLLOW | O_CLOEXEC);
+      if (pathfd < 0)
+	{
+	  if (errno == ENFILE || errno == EMFILE)
+	    /* These errors cannot happen with a straight fchmodat
+	       operation because it does not create file descriptors,
+	       so hide them.  */
+	    __set_errno (EOPNOTSUPP);
+	  /* Otherwise, this should accurately reflect the expected
+	     error from fchmodat (e.g., EBADF or ENOENT).  */
+	  return pathfd;
+	}
+
+      char buf[32];
+      if (__snprintf (buf, sizeof (buf), "/proc/self/fd/%d", pathfd) < 0)
+	{
+	  __close_nocancel (pathfd);
+	  return INLINE_SYSCALL_ERROR_RETURN_VALUE (EOPNOTSUPP);
+	}
 
-  return INLINE_SYSCALL (fchmodat, 3, fd, file, mode);
+      /* This operates directly on the symbolic link if it is one.
+	 /proc/self/fd files look like symbolic links, but they are
+	 not.  (fchmod and fchmodat do not work on O_PATH descriptors,
+	 similar to fstat before Linux 3.6.)  */
+      int ret = __chmod (buf, mode);
+      if (ret != 0)
+	{
+	  if (errno == ENOENT)
+	    /* /proc has not been mounted.  In general, we cannot use
+	       openat with AT_EMPTY_PATH to upgrade the descriptor
+	       because we may not have permission to open the file,
+	       and opening files and closing them again may have side
+	       effects (such as rewinding tape devices, or releasing
+	       POSIX locks).  */
+	    __set_errno (EOPNOTSUPP);
+	}
+      __close_nocancel (pathfd);
+      return ret;
+    }
 }
 libc_hidden_def (fchmodat)
-- 
2.24.1



Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]