(root)/
tar-1.35/
gnu/
fchmodat.c
       1  /* Change the protections of file relative to an open directory.
       2     Copyright (C) 2006, 2009-2023 Free Software Foundation, Inc.
       3  
       4     This program is free software: you can redistribute it and/or modify
       5     it under the terms of the GNU General Public License as published by
       6     the Free Software Foundation, either version 3 of the License, or
       7     (at your option) any later version.
       8  
       9     This program is distributed in the hope that it will be useful,
      10     but WITHOUT ANY WARRANTY; without even the implied warranty of
      11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12     GNU General Public License for more details.
      13  
      14     You should have received a copy of the GNU General Public License
      15     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      16  
      17  /* written by Jim Meyering and Paul Eggert */
      18  
      19  /* If the user's config.h happens to include <sys/stat.h>, let it include only
      20     the system's <sys/stat.h> here, so that orig_fchmodat doesn't recurse to
      21     rpl_fchmodat.  */
      22  #define __need_system_sys_stat_h
      23  #include <config.h>
      24  
      25  /* Specification.  */
      26  #include <sys/stat.h>
      27  #undef __need_system_sys_stat_h
      28  
      29  #if HAVE_FCHMODAT
      30  static int
      31  orig_fchmodat (int dir, char const *file, mode_t mode, int flags)
      32  {
      33    return fchmodat (dir, file, mode, flags);
      34  }
      35  #endif
      36  
      37  #include <errno.h>
      38  #include <fcntl.h>
      39  #include <stdio.h>
      40  #include <stdlib.h>
      41  #include <string.h>
      42  #include <unistd.h>
      43  
      44  #ifdef __osf__
      45  /* Write "sys/stat.h" here, not <sys/stat.h>, otherwise OSF/1 5.1 DTK cc
      46     eliminates this include because of the preliminary #include <sys/stat.h>
      47     above.  */
      48  # include "sys/stat.h"
      49  #else
      50  # include <sys/stat.h>
      51  #endif
      52  
      53  #include <intprops.h>
      54  
      55  /* Invoke chmod or lchmod on FILE, using mode MODE, in the directory
      56     open on descriptor FD.  If possible, do it without changing the
      57     working directory.  Otherwise, resort to using save_cwd/fchdir,
      58     then (chmod|lchmod)/restore_cwd.  If either the save_cwd or the
      59     restore_cwd fails, then give a diagnostic and exit nonzero.
      60     Note that an attempt to use a FLAG value of AT_SYMLINK_NOFOLLOW
      61     on a system without lchmod support causes this function to fail.  */
      62  
      63  #if HAVE_FCHMODAT
      64  int
      65  fchmodat (int dir, char const *file, mode_t mode, int flags)
      66  {
      67  # if HAVE_NEARLY_WORKING_FCHMODAT
      68    /* Correct the trailing slash handling.  */
      69    size_t len = strlen (file);
      70    if (len && file[len - 1] == '/')
      71      {
      72        struct stat st;
      73        if (fstatat (dir, file, &st, flags & AT_SYMLINK_NOFOLLOW) < 0)
      74          return -1;
      75        if (!S_ISDIR (st.st_mode))
      76          {
      77            errno = ENOTDIR;
      78            return -1;
      79          }
      80      }
      81  # endif
      82  
      83  # if NEED_FCHMODAT_NONSYMLINK_FIX
      84    if (flags == AT_SYMLINK_NOFOLLOW)
      85      {
      86  #  if HAVE_READLINKAT
      87        char readlink_buf[1];
      88  
      89  #   ifdef O_PATH
      90        /* Open a file descriptor with O_NOFOLLOW, to make sure we don't
      91           follow symbolic links, if /proc is mounted.  O_PATH is used to
      92           avoid a failure if the file is not readable.
      93           Cf. <https://sourceware.org/bugzilla/show_bug.cgi?id=14578>  */
      94        int fd = openat (dir, file, O_PATH | O_NOFOLLOW | O_CLOEXEC);
      95        if (fd < 0)
      96          return fd;
      97  
      98        int err;
      99        if (0 <= readlinkat (fd, "", readlink_buf, sizeof readlink_buf))
     100          err = EOPNOTSUPP;
     101        else if (errno == EINVAL)
     102          {
     103            static char const fmt[] = "/proc/self/fd/%d";
     104            char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)];
     105            sprintf (buf, fmt, fd);
     106            err = chmod (buf, mode) == 0 ? 0 : errno == ENOENT ? -1 : errno;
     107          }
     108        else
     109          err = errno == ENOENT ? -1 : errno;
     110  
     111        close (fd);
     112  
     113        errno = err;
     114        if (0 <= err)
     115          return err == 0 ? 0 : -1;
     116  #   endif
     117  
     118        /* O_PATH + /proc is not supported.  */
     119  
     120        if (0 <= readlinkat (dir, file, readlink_buf, sizeof readlink_buf))
     121          {
     122            errno = EOPNOTSUPP;
     123            return -1;
     124          }
     125  #  endif
     126  
     127        /* Fall back on orig_fchmodat with no flags, despite a possible race.  */
     128        flags = 0;
     129      }
     130  # endif
     131  
     132    return orig_fchmodat (dir, file, mode, flags);
     133  }
     134  #else
     135  # define AT_FUNC_NAME fchmodat
     136  # define AT_FUNC_F1 lchmod
     137  # define AT_FUNC_F2 chmod
     138  # define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW
     139  # define AT_FUNC_POST_FILE_PARAM_DECLS , mode_t mode, int flag
     140  # define AT_FUNC_POST_FILE_ARGS        , mode
     141  # include "at-func.c"
     142  #endif