(root)/
tar-1.35/
gnu/
renameatu.c
       1  /* Rename a file relative to open directories.
       2     Copyright (C) 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 Eric Blake and Paul Eggert */
      18  
      19  #include <config.h>
      20  
      21  #include "renameatu.h"
      22  
      23  #include <errno.h>
      24  #include <stdio.h>
      25  #include <sys/stat.h>
      26  #include <unistd.h>
      27  
      28  #ifdef __linux__
      29  # include <sys/syscall.h>
      30  #endif
      31  
      32  static int
      33  errno_fail (int e)
      34  {
      35    errno = e;
      36    return -1;
      37  }
      38  
      39  #if HAVE_RENAMEAT
      40  
      41  # include <stdlib.h>
      42  # include <string.h>
      43  
      44  # include "dirname.h"
      45  # include "openat.h"
      46  
      47  #else
      48  # include "openat-priv.h"
      49  
      50  static int
      51  rename_noreplace (char const *src, char const *dst)
      52  {
      53    /* This has a race between the call to lstat and the call to rename.  */
      54    struct stat st;
      55    return (lstat (dst, &st) == 0 || errno == EOVERFLOW ? errno_fail (EEXIST)
      56            : errno == ENOENT ? rename (src, dst)
      57            : -1);
      58  }
      59  #endif
      60  
      61  #undef renameat
      62  
      63  #if HAVE_RENAMEAT
      64  
      65  /* Act like renameat (FD1, SRC, FD2, DST), except fail with EEXIST if
      66     FLAGS is nonzero and it is easy to fail atomically if DST already exists.
      67     This lets renameatu be atomic when it can be implemented in terms
      68     of renameatx_np.  */
      69  static int
      70  renameat2ish (int fd1, char const *src, int fd2, char const *dst,
      71                unsigned int flags)
      72  {
      73  # ifdef RENAME_EXCL
      74    if (flags)
      75      {
      76        int r = renameatx_np (fd1, src, fd2, dst, RENAME_EXCL);
      77        if (r == 0 || errno != ENOTSUP)
      78          return r;
      79      }
      80  # endif
      81  
      82    return renameat (fd1, src, fd2, dst);
      83  }
      84  #endif
      85  
      86  /* Rename FILE1, in the directory open on descriptor FD1, to FILE2, in
      87     the directory open on descriptor FD2.  If possible, do it without
      88     changing the working directory.  Otherwise, resort to using
      89     save_cwd/fchdir, then rename/restore_cwd.  If either the save_cwd or
      90     the restore_cwd fails, then give a diagnostic and exit nonzero.
      91  
      92     Obey FLAGS when doing the renaming.  If FLAGS is zero, this
      93     function is equivalent to renameat (FD1, SRC, FD2, DST).
      94     Otherwise, attempt to implement FLAGS even if the implementation is
      95     not atomic; this differs from the GNU/Linux native renameat2,
      96     which fails if it cannot guarantee atomicity.  */
      97  
      98  int
      99  renameatu (int fd1, char const *src, int fd2, char const *dst,
     100             unsigned int flags)
     101  {
     102    int ret_val = -1;
     103    int err = EINVAL;
     104  
     105  #ifdef HAVE_RENAMEAT2
     106    ret_val = renameat2 (fd1, src, fd2, dst, flags);
     107    err = errno;
     108  #elif defined SYS_renameat2
     109    ret_val = syscall (SYS_renameat2, fd1, src, fd2, dst, flags);
     110    err = errno;
     111  #endif
     112  
     113    if (! (ret_val < 0 && (err == EINVAL || err == ENOSYS || err == ENOTSUP)))
     114      return ret_val;
     115  
     116  #if HAVE_RENAMEAT
     117    {
     118    size_t src_len;
     119    size_t dst_len;
     120    char *src_temp = (char *) src;
     121    char *dst_temp = (char *) dst;
     122    bool src_slash;
     123    bool dst_slash;
     124    int rename_errno = ENOTDIR;
     125    struct stat src_st;
     126    struct stat dst_st;
     127    bool dst_found_nonexistent = false;
     128  
     129    switch (flags)
     130      {
     131      case 0:
     132        break;
     133  
     134      case RENAME_NOREPLACE:
     135        /* This has a race between the call to fstatat and the calls to
     136           renameat below.  This fstatat is needed even if RENAME_EXCL
     137           is defined, because RENAME_EXCL is buggy on macOS 11.2:
     138           renameatx_np (fd, "X", fd, "X", RENAME_EXCL) incorrectly
     139           succeeds when X exists.  */
     140        if (fstatat (fd2, dst, &dst_st, AT_SYMLINK_NOFOLLOW) == 0
     141            || errno == EOVERFLOW)
     142          return errno_fail (EEXIST);
     143        if (errno != ENOENT)
     144          return -1;
     145        dst_found_nonexistent = true;
     146        break;
     147  
     148      default:
     149        return errno_fail (ENOTSUP);
     150      }
     151  
     152    /* Let strace see any ENOENT failure.  */
     153    src_len = strlen (src);
     154    dst_len = strlen (dst);
     155    if (!src_len || !dst_len)
     156      return renameat2ish (fd1, src, fd2, dst, flags);
     157  
     158    src_slash = src[src_len - 1] == '/';
     159    dst_slash = dst[dst_len - 1] == '/';
     160    if (!src_slash && !dst_slash)
     161      return renameat2ish (fd1, src, fd2, dst, flags);
     162  
     163    /* Presence of a trailing slash requires directory semantics.  If
     164       the source does not exist, or if the destination cannot be turned
     165       into a directory, give up now.  Otherwise, strip trailing slashes
     166       before calling rename.  */
     167    if (fstatat (fd1, src, &src_st, AT_SYMLINK_NOFOLLOW))
     168      return -1;
     169    if (dst_found_nonexistent)
     170      {
     171        if (!S_ISDIR (src_st.st_mode))
     172          return errno_fail (ENOENT);
     173      }
     174    else if (fstatat (fd2, dst, &dst_st, AT_SYMLINK_NOFOLLOW))
     175      {
     176        if (errno != ENOENT || !S_ISDIR (src_st.st_mode))
     177          return -1;
     178      }
     179    else if (!S_ISDIR (dst_st.st_mode))
     180      return errno_fail (ENOTDIR);
     181    else if (!S_ISDIR (src_st.st_mode))
     182      return errno_fail (EISDIR);
     183  
     184  # if RENAME_TRAILING_SLASH_SOURCE_BUG
     185    /* See the lengthy comment in rename.c why Solaris 9 is forced to
     186       GNU behavior, while Solaris 10 is left with POSIX behavior,
     187       regarding symlinks with trailing slash.  */
     188    ret_val = -1;
     189    if (src_slash)
     190      {
     191        src_temp = strdup (src);
     192        if (!src_temp)
     193          {
     194            /* Rather than rely on strdup-posix, we set errno ourselves.  */
     195            rename_errno = ENOMEM;
     196            goto out;
     197          }
     198        strip_trailing_slashes (src_temp);
     199        if (fstatat (fd1, src_temp, &src_st, AT_SYMLINK_NOFOLLOW))
     200          {
     201            rename_errno = errno;
     202            goto out;
     203          }
     204        if (S_ISLNK (src_st.st_mode))
     205          goto out;
     206      }
     207    if (dst_slash)
     208      {
     209        dst_temp = strdup (dst);
     210        if (!dst_temp)
     211          {
     212            rename_errno = ENOMEM;
     213            goto out;
     214          }
     215        strip_trailing_slashes (dst_temp);
     216        char readlink_buf[1];
     217        if (readlinkat (fd2, dst_temp, readlink_buf, sizeof readlink_buf) < 0)
     218          {
     219            if (errno != ENOENT && errno != EINVAL)
     220              {
     221                rename_errno = errno;
     222                goto out;
     223              }
     224          }
     225        else
     226          goto out;
     227      }
     228  # endif /* RENAME_TRAILING_SLASH_SOURCE_BUG */
     229  
     230    /* renameat does not honor trailing / on Solaris 10.  Solve it in a
     231       similar manner to rename.  No need to worry about bugs not present
     232       on Solaris, since all other systems either lack renameat or honor
     233       trailing slash correctly.  */
     234  
     235    ret_val = renameat2ish (fd1, src_temp, fd2, dst_temp, flags);
     236    rename_errno = errno;
     237    goto out;
     238   out:
     239    if (src_temp != src)
     240      free (src_temp);
     241    if (dst_temp != dst)
     242      free (dst_temp);
     243    errno = rename_errno;
     244    return ret_val;
     245    }
     246  #else /* !HAVE_RENAMEAT */
     247  
     248    /* RENAME_NOREPLACE is the only flag currently supported.  */
     249    if (flags & ~RENAME_NOREPLACE)
     250      return errno_fail (ENOTSUP);
     251    return at_func2 (fd1, src, fd2, dst, flags ? rename_noreplace : rename);
     252  
     253  #endif /* !HAVE_RENAMEAT */
     254  }