(root)/
tar-1.35/
gnu/
at-func2.c
       1  /* Define 2-FD at-style functions like linkat or renameat.
       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 Eric Blake */
      18  
      19  #include <config.h>
      20  
      21  #include "openat-priv.h"
      22  
      23  #include <errno.h>
      24  #include <stdlib.h>
      25  #include <string.h>
      26  #include <unistd.h>
      27  
      28  #include "filename.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */
      29  #include "filenamecat.h"
      30  #include "openat.h"
      31  #include "same-inode.h"
      32  #include "save-cwd.h"
      33  
      34  /* Call FUNC to operate on a pair of files, where FILE1 is relative to FD1,
      35     and FILE2 is relative to FD2.  If possible, do it without changing the
      36     working directory.  Otherwise, resort to using save_cwd/fchdir,
      37     FUNC, restore_cwd (up to two times).  If either the save_cwd or the
      38     restore_cwd fails, then give a diagnostic and exit nonzero.  */
      39  int
      40  at_func2 (int fd1, char const *file1,
      41            int fd2, char const *file2,
      42            int (*func) (char const *file1, char const *file2))
      43  {
      44    struct saved_cwd saved_cwd;
      45    int saved_errno;
      46    int err;
      47    char *file1_alt;
      48    char *file2_alt;
      49    struct stat st1;
      50    struct stat st2;
      51  
      52    /* There are 16 possible scenarios, based on whether an fd is
      53       AT_FDCWD or real, and whether a file is absolute or relative:
      54  
      55           fd1  file1 fd2  file2  action
      56       0   cwd  abs   cwd  abs    direct call
      57       1   cwd  abs   cwd  rel    direct call
      58       2   cwd  abs   fd   abs    direct call
      59       3   cwd  abs   fd   rel    chdir to fd2
      60       4   cwd  rel   cwd  abs    direct call
      61       5   cwd  rel   cwd  rel    direct call
      62       6   cwd  rel   fd   abs    direct call
      63       7   cwd  rel   fd   rel    convert file1 to abs, then case 3
      64       8   fd   abs   cwd  abs    direct call
      65       9   fd   abs   cwd  rel    direct call
      66       10  fd   abs   fd   abs    direct call
      67       11  fd   abs   fd   rel    chdir to fd2
      68       12  fd   rel   cwd  abs    chdir to fd1
      69       13  fd   rel   cwd  rel    convert file2 to abs, then case 12
      70       14  fd   rel   fd   abs    chdir to fd1
      71       15a fd1  rel   fd1  rel    chdir to fd1
      72       15b fd1  rel   fd2  rel    chdir to fd1, then case 7
      73  
      74       Try some optimizations to reduce fd to AT_FDCWD, or to at least
      75       avoid converting an absolute name or doing a double chdir.  */
      76  
      77    if ((fd1 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file1))
      78        && (fd2 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file2)))
      79      return func (file1, file2); /* Case 0-2, 4-6, 8-10.  */
      80  
      81    /* If /proc/self/fd works, we don't need any stat or chdir.  */
      82    {
      83      char proc_buf1[OPENAT_BUFFER_SIZE];
      84      char *proc_file1 = ((fd1 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file1))
      85                          ? (char *) file1
      86                          : openat_proc_name (proc_buf1, fd1, file1));
      87      if (proc_file1)
      88        {
      89          char proc_buf2[OPENAT_BUFFER_SIZE];
      90          char *proc_file2 = ((fd2 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file2))
      91                              ? (char *) file2
      92                              : openat_proc_name (proc_buf2, fd2, file2));
      93          if (proc_file2)
      94            {
      95              int proc_result = func (proc_file1, proc_file2);
      96              int proc_errno = errno;
      97              if (proc_file1 != proc_buf1 && proc_file1 != file1)
      98                free (proc_file1);
      99              if (proc_file2 != proc_buf2 && proc_file2 != file2)
     100                free (proc_file2);
     101              /* If the syscall succeeds, or if it fails with an unexpected
     102                 errno value, then return right away.  Otherwise, fall through
     103                 and resort to using save_cwd/restore_cwd.  */
     104              if (0 <= proc_result)
     105                return proc_result;
     106              if (! EXPECTED_ERRNO (proc_errno))
     107                {
     108                  errno = proc_errno;
     109                  return proc_result;
     110                }
     111            }
     112          else if (proc_file1 != proc_buf1 && proc_file1 != file1)
     113            free (proc_file1);
     114        }
     115    }
     116  
     117    /* Cases 3, 7, 11-15 remain.  Time to normalize directory fds, if
     118       possible.  */
     119    if (IS_ABSOLUTE_FILE_NAME (file1))
     120      fd1 = AT_FDCWD; /* Case 11 reduced to 3.  */
     121    else if (IS_ABSOLUTE_FILE_NAME (file2))
     122      fd2 = AT_FDCWD; /* Case 14 reduced to 12.  */
     123  
     124    /* Cases 3, 7, 12, 13, 15 remain.  */
     125  
     126    if (fd1 == AT_FDCWD) /* Cases 3, 7.  */
     127      {
     128        if (stat (".", &st1) == -1 || fstat (fd2, &st2) == -1)
     129          return -1;
     130        if (!S_ISDIR (st2.st_mode))
     131          {
     132            errno = ENOTDIR;
     133            return -1;
     134          }
     135        if (SAME_INODE (st1, st2)) /* Reduced to cases 1, 5.  */
     136          return func (file1, file2);
     137      }
     138    else if (fd2 == AT_FDCWD) /* Cases 12, 13.  */
     139      {
     140        if (stat (".", &st2) == -1 || fstat (fd1, &st1) == -1)
     141          return -1;
     142        if (!S_ISDIR (st1.st_mode))
     143          {
     144            errno = ENOTDIR;
     145            return -1;
     146          }
     147        if (SAME_INODE (st1, st2)) /* Reduced to cases 4, 5.  */
     148          return func (file1, file2);
     149      }
     150    else if (fd1 != fd2) /* Case 15b.  */
     151      {
     152        if (fstat (fd1, &st1) == -1 || fstat (fd2, &st2) == -1)
     153          return -1;
     154        if (!S_ISDIR (st1.st_mode) || !S_ISDIR (st2.st_mode))
     155          {
     156            errno = ENOTDIR;
     157            return -1;
     158          }
     159        if (SAME_INODE (st1, st2)) /* Reduced to case 15a.  */
     160          {
     161            fd2 = fd1;
     162            if (stat (".", &st1) == 0 && SAME_INODE (st1, st2))
     163              return func (file1, file2); /* Further reduced to case 5.  */
     164          }
     165      }
     166    else /* Case 15a.  */
     167      {
     168        if (fstat (fd1, &st1) == -1)
     169          return -1;
     170        if (!S_ISDIR (st1.st_mode))
     171          {
     172            errno = ENOTDIR;
     173            return -1;
     174          }
     175        if (stat (".", &st2) == 0 && SAME_INODE (st1, st2))
     176          return func (file1, file2); /* Reduced to case 5.  */
     177      }
     178  
     179    /* Catch invalid arguments before changing directories.  */
     180    if (file1[0] == '\0' || file2[0] == '\0')
     181      {
     182        errno = ENOENT;
     183        return -1;
     184      }
     185  
     186    /* Cases 3, 7, 12, 13, 15a, 15b remain.  With all reductions in
     187       place, it is time to start changing directories.  */
     188  
     189    if (save_cwd (&saved_cwd) != 0)
     190      openat_save_fail (errno);
     191  
     192    if (fd1 != AT_FDCWD && fd2 != AT_FDCWD && fd1 != fd2) /* Case 15b.  */
     193      {
     194        if (fchdir (fd1) != 0)
     195          {
     196            saved_errno = errno;
     197            free_cwd (&saved_cwd);
     198            errno = saved_errno;
     199            return -1;
     200          }
     201        fd1 = AT_FDCWD; /* Reduced to case 7.  */
     202      }
     203  
     204    /* Cases 3, 7, 12, 13, 15a remain.  Convert one relative name to
     205       absolute, if necessary.  */
     206  
     207    file1_alt = (char *) file1;
     208    file2_alt = (char *) file2;
     209  
     210    if (fd1 == AT_FDCWD && !IS_ABSOLUTE_FILE_NAME (file1)) /* Case 7.  */
     211      {
     212        /* It would be nicer to use:
     213           file1_alt = file_name_concat (xgetcwd (), file1, NULL);
     214           but libraries should not call xalloc_die.  */
     215        char *cwd = getcwd (NULL, 0);
     216        if (!cwd)
     217          {
     218            saved_errno = errno;
     219            free_cwd (&saved_cwd);
     220            errno = saved_errno;
     221            return -1;
     222          }
     223        file1_alt = mfile_name_concat (cwd, file1, NULL);
     224        if (!file1_alt)
     225          {
     226            saved_errno = errno;
     227            free (cwd);
     228            free_cwd (&saved_cwd);
     229            errno = saved_errno;
     230            return -1;
     231          }
     232        free (cwd); /* Reduced to case 3.  */
     233      }
     234    else if (fd2 == AT_FDCWD && !IS_ABSOLUTE_FILE_NAME (file2)) /* Case 13.  */
     235      {
     236        char *cwd = getcwd (NULL, 0);
     237        if (!cwd)
     238          {
     239            saved_errno = errno;
     240            free_cwd (&saved_cwd);
     241            errno = saved_errno;
     242            return -1;
     243          }
     244        file2_alt = mfile_name_concat (cwd, file2, NULL);
     245        if (!file2_alt)
     246          {
     247            saved_errno = errno;
     248            free (cwd);
     249            free_cwd (&saved_cwd);
     250            errno = saved_errno;
     251            return -1;
     252          }
     253        free (cwd); /* Reduced to case 12.  */
     254      }
     255  
     256    /* Cases 3, 12, 15a remain.  Change to the correct directory.  */
     257    if (fchdir (fd1 == AT_FDCWD ? fd2 : fd1) != 0)
     258      {
     259        saved_errno = errno;
     260        free_cwd (&saved_cwd);
     261        if (file1 != file1_alt)
     262          free (file1_alt);
     263        else if (file2 != file2_alt)
     264          free (file2_alt);
     265        errno = saved_errno;
     266        return -1;
     267      }
     268  
     269    /* Finally safe to perform the user's function, then clean up.  */
     270  
     271    err = func (file1_alt, file2_alt);
     272    saved_errno = (err < 0 ? errno : 0);
     273  
     274    if (file1 != file1_alt)
     275      free (file1_alt);
     276    else if (file2 != file2_alt)
     277      free (file2_alt);
     278  
     279    if (restore_cwd (&saved_cwd) != 0)
     280      openat_restore_fail (errno);
     281  
     282    free_cwd (&saved_cwd);
     283  
     284    if (saved_errno)
     285      errno = saved_errno;
     286    return err;
     287  }
     288  #undef CALL_FUNC
     289  #undef FUNC_RESULT