root/maint/gnulib/lib/renameatu.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. errno_fail
  2. rename_noreplace
  3. renameat2ish
  4. renameatu

   1 /* Rename a file relative to open directories.
   2    Copyright (C) 2009-2021 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)
     /* [previous][next][first][last][top][bottom][index][help] */
  34 {
  35   errno = e;
  36   return -1;
  37 }
  38 
  39 #if HAVE_RENAMEAT
  40 
  41 # include <stdbool.h>
  42 # include <stdlib.h>
  43 # include <string.h>
  44 
  45 # include "dirname.h"
  46 # include "openat.h"
  47 
  48 #else
  49 # include "openat-priv.h"
  50 
  51 static int
  52 rename_noreplace (char const *src, char const *dst)
     /* [previous][next][first][last][top][bottom][index][help] */
  53 {
  54   /* This has a race between the call to lstat and the call to rename.  */
  55   struct stat st;
  56   return (lstat (dst, &st) == 0 || errno == EOVERFLOW ? errno_fail (EEXIST)
  57           : errno == ENOENT ? rename (src, dst)
  58           : -1);
  59 }
  60 #endif
  61 
  62 #undef renameat
  63 
  64 #if HAVE_RENAMEAT
  65 
  66 /* Act like renameat (FD1, SRC, FD2, DST), except fail with EEXIST if
  67    FLAGS is nonzero and it is easy to fail atomically if DST already exists.
  68    This lets renameatu be atomic when it can be implemented in terms
  69    of renameatx_np.  */
  70 static int
  71 renameat2ish (int fd1, char const *src, int fd2, char const *dst,
     /* [previous][next][first][last][top][bottom][index][help] */
  72               unsigned int flags)
  73 {
  74 # ifdef RENAME_EXCL
  75   if (flags)
  76     {
  77       int r = renameatx_np (fd1, src, fd2, dst, RENAME_EXCL);
  78       if (r == 0 || errno != ENOTSUP)
  79         return r;
  80     }
  81 # endif
  82 
  83   return renameat (fd1, src, fd2, dst);
  84 }
  85 #endif
  86 
  87 /* Rename FILE1, in the directory open on descriptor FD1, to FILE2, in
  88    the directory open on descriptor FD2.  If possible, do it without
  89    changing the working directory.  Otherwise, resort to using
  90    save_cwd/fchdir, then rename/restore_cwd.  If either the save_cwd or
  91    the restore_cwd fails, then give a diagnostic and exit nonzero.
  92 
  93    Obey FLAGS when doing the renaming.  If FLAGS is zero, this
  94    function is equivalent to renameat (FD1, SRC, FD2, DST).
  95    Otherwise, attempt to implement FLAGS even if the implementation is
  96    not atomic; this differs from the GNU/Linux native renameat2,
  97    which fails if it cannot guarantee atomicity.  */
  98 
  99 int
 100 renameatu (int fd1, char const *src, int fd2, char const *dst,
     /* [previous][next][first][last][top][bottom][index][help] */
 101            unsigned int flags)
 102 {
 103   int ret_val = -1;
 104   int err = EINVAL;
 105 
 106 #ifdef HAVE_RENAMEAT2
 107   ret_val = renameat2 (fd1, src, fd2, dst, flags);
 108   err = errno;
 109 #elif defined SYS_renameat2
 110   ret_val = syscall (SYS_renameat2, fd1, src, fd2, dst, flags);
 111   err = errno;
 112 #endif
 113 
 114   if (! (ret_val < 0 && (err == EINVAL || err == ENOSYS || err == ENOTSUP)))
 115     return ret_val;
 116 
 117 #if HAVE_RENAMEAT
 118   {
 119   size_t src_len;
 120   size_t dst_len;
 121   char *src_temp = (char *) src;
 122   char *dst_temp = (char *) dst;
 123   bool src_slash;
 124   bool dst_slash;
 125   int rename_errno = ENOTDIR;
 126   struct stat src_st;
 127   struct stat dst_st;
 128   bool dst_found_nonexistent = false;
 129 
 130   switch (flags)
 131     {
 132     case 0:
 133       break;
 134 
 135     case RENAME_NOREPLACE:
 136       /* This has a race between the call to lstatat and the calls to
 137          renameat below.  This lstatat is needed even if RENAME_EXCL
 138          is defined, because RENAME_EXCL is buggy on macOS 11.2:
 139          renameatx_np (fd, "X", fd, "X", RENAME_EXCL) incorrectly
 140          succeeds when X exists.  */
 141       if (lstatat (fd2, dst, &dst_st) == 0 || 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 (lstatat (fd1, src, &src_st))
 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 (lstatat (fd2, dst, &dst_st))
 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 (lstatat (fd1, src_temp, &src_st))
 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       if (lstatat (fd2, dst_temp, &dst_st))
 217         {
 218           if (errno != ENOENT)
 219             {
 220               rename_errno = errno;
 221               goto out;
 222             }
 223         }
 224       else if (S_ISLNK (dst_st.st_mode))
 225         goto out;
 226     }
 227 # endif /* RENAME_TRAILING_SLASH_SOURCE_BUG */
 228 
 229   /* renameat does not honor trailing / on Solaris 10.  Solve it in a
 230      similar manner to rename.  No need to worry about bugs not present
 231      on Solaris, since all other systems either lack renameat or honor
 232      trailing slash correctly.  */
 233 
 234   ret_val = renameat2ish (fd1, src_temp, fd2, dst_temp, flags);
 235   rename_errno = errno;
 236   goto out;
 237  out:
 238   if (src_temp != src)
 239     free (src_temp);
 240   if (dst_temp != dst)
 241     free (dst_temp);
 242   errno = rename_errno;
 243   return ret_val;
 244   }
 245 #else /* !HAVE_RENAMEAT */
 246 
 247   /* RENAME_NOREPLACE is the only flag currently supported.  */
 248   if (flags & ~RENAME_NOREPLACE)
 249     return errno_fail (ENOTSUP);
 250   return at_func2 (fd1, src, fd2, dst, flags ? rename_noreplace : rename);
 251 
 252 #endif /* !HAVE_RENAMEAT */
 253 }

/* [previous][next][first][last][top][bottom][index][help] */