1  /* Change the protections of file relative to open directory.  Linux version.
       2     Copyright (C) 2006-2023 Free Software Foundation, Inc.
       3     This file is part of the GNU C Library.
       4  
       5     The GNU C Library is free software; you can redistribute it and/or
       6     modify it under the terms of the GNU Lesser General Public
       7     License as published by the Free Software Foundation; either
       8     version 2.1 of the License, or (at your option) any later version.
       9  
      10     The GNU C Library is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      13     Lesser General Public License for more details.
      14  
      15     You should have received a copy of the GNU Lesser General Public
      16     License along with the GNU C Library; if not, see
      17     <https://www.gnu.org/licenses/>.  */
      18  
      19  #include <errno.h>
      20  #include <fcntl.h>
      21  #include <fd_to_filename.h>
      22  #include <not-cancel.h>
      23  #include <stdio.h>
      24  #include <sys/stat.h>
      25  #include <sys/types.h>
      26  #include <sysdep.h>
      27  #include <unistd.h>
      28  
      29  int
      30  fchmodat (int fd, const char *file, mode_t mode, int flag)
      31  {
      32    if (flag == 0)
      33      return INLINE_SYSCALL (fchmodat, 3, fd, file, mode);
      34    else if (flag != AT_SYMLINK_NOFOLLOW)
      35      return INLINE_SYSCALL_ERROR_RETURN_VALUE (EINVAL);
      36    else
      37      {
      38        /* The kernel system call does not have a mode argument.
      39  	 However, we can create an O_PATH descriptor and change that
      40  	 via /proc (which does not resolve symbolic links).  */
      41  
      42        int pathfd = __openat_nocancel (fd, file,
      43  				      O_PATH | O_NOFOLLOW | O_CLOEXEC);
      44        if (pathfd < 0)
      45  	/* This may report errors such as ENFILE and EMFILE.  The
      46  	   caller can treat them as temporary if necessary.  */
      47  	return pathfd;
      48  
      49        /* Use fstatat because fstat does not work on O_PATH descriptors
      50  	 before Linux 3.6.  */
      51        struct __stat64_t64 st;
      52        if (__fstatat64_time64 (pathfd, "", &st, AT_EMPTY_PATH) != 0)
      53  	{
      54  	  __close_nocancel (pathfd);
      55  	  return -1;
      56  	}
      57  
      58        /* Some Linux versions with some file systems can actually
      59  	 change symbolic link permissions via /proc, but this is not
      60  	 intentional, and it gives inconsistent results (e.g., error
      61  	 return despite mode change).  The expected behavior is that
      62  	 symbolic link modes cannot be changed at all, and this check
      63  	 enforces that.  */
      64        if (S_ISLNK (st.st_mode))
      65  	{
      66  	  __close_nocancel (pathfd);
      67  	  __set_errno (EOPNOTSUPP);
      68  	  return -1;
      69  	}
      70  
      71        /* For most file systems, fchmod does not operate on O_PATH
      72  	 descriptors, so go through /proc.  */
      73        struct fd_to_filename filename;
      74        int ret = __chmod (__fd_to_filename (pathfd, &filename), mode);
      75        if (ret != 0)
      76  	{
      77  	  if (errno == ENOENT)
      78  	    /* /proc has not been mounted.  Without /proc, there is no
      79  	       way to upgrade the O_PATH descriptor to a full
      80  	       descriptor.  It is also not possible to re-open the
      81  	       file without O_PATH because the file name may refer to
      82  	       another file, and opening that without O_PATH may have
      83  	       side effects (such as blocking, device rewinding, or
      84  	       releasing POSIX locks).  */
      85  	    __set_errno (EOPNOTSUPP);
      86  	}
      87        __close_nocancel (pathfd);
      88        return ret;
      89      }
      90  }
      91  libc_hidden_def (fchmodat)