1 /* Rename a file relative to open directories.
2 Copyright (C) 2009-2024 Free Software Foundation, Inc.
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.
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.
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/>. */
17 /* written by Eric Blake and Paul Eggert */
21 #include "renameatu.h"
29 # include <sys/syscall.h>
48 # include "openat-priv.h"
51 rename_noreplace (char const *src
, char const *dst
)
53 /* This has a race between the call to lstat and the call to rename. */
55 return (lstat (dst
, &st
) == 0 || errno
== EOVERFLOW
? errno_fail (EEXIST
)
56 : errno
== ENOENT
? rename (src
, dst
)
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
70 renameat2ish (int fd1
, char const *src
, int fd2
, char const *dst
,
74 if (flags
& RENAME_EXCHANGE
)
75 return renameatx_np (fd1
, src
, fd2
, dst
, RENAME_SWAP
);
80 int r
= renameatx_np (fd1
, src
, fd2
, dst
, RENAME_EXCL
);
81 if (r
== 0 || (errno
!= ENOTSUP
&& errno
!= ENOSYS
))
86 return renameat (fd1
, src
, fd2
, dst
);
90 /* Rename FILE1, in the directory open on descriptor FD1, to FILE2, in
91 the directory open on descriptor FD2. If possible, do it without
92 changing the working directory. Otherwise, resort to using
93 save_cwd/fchdir, then rename/restore_cwd. If either the save_cwd or
94 the restore_cwd fails, then give a diagnostic and exit nonzero.
96 Obey FLAGS when doing the renaming. If FLAGS is zero, this
97 function is equivalent to renameat (FD1, SRC, FD2, DST).
98 Otherwise, attempt to implement FLAGS even if the implementation is
99 not atomic; this differs from the GNU/Linux native renameat2,
100 which fails if it cannot guarantee atomicity. */
103 renameatu (int fd1
, char const *src
, int fd2
, char const *dst
,
109 #if HAVE_WORKING_RENAMEAT2
110 ret_val
= renameat2 (fd1
, src
, fd2
, dst
, flags
);
112 #elif defined SYS_renameat2
113 ret_val
= syscall (SYS_renameat2
, fd1
, src
, fd2
, dst
, flags
);
117 if (! (ret_val
< 0 && (err
== EINVAL
|| err
== ENOSYS
|| err
== ENOTSUP
)))
124 char *src_temp
= (char *) src
;
125 char *dst_temp
= (char *) dst
;
128 int rename_errno
= ENOTDIR
;
131 bool dst_found_nonexistent
= false;
138 case RENAME_NOREPLACE
:
139 /* This has a race between the call to fstatat and the calls to
140 renameat below. This fstatat is needed even if RENAME_EXCL
141 is defined, because RENAME_EXCL is buggy on macOS 11.2:
142 renameatx_np (fd, "X", fd, "X", RENAME_EXCL) incorrectly
143 succeeds when X exists. */
144 if (fstatat (fd2
, dst
, &dst_st
, AT_SYMLINK_NOFOLLOW
) == 0
145 || errno
== EOVERFLOW
)
146 return errno_fail (EEXIST
);
149 dst_found_nonexistent
= true;
152 case RENAME_EXCHANGE
:
157 return errno_fail (ENOTSUP
);
160 /* Let strace see any ENOENT failure. */
161 src_len
= strlen (src
);
162 dst_len
= strlen (dst
);
163 if (!src_len
|| !dst_len
)
164 return renameat2ish (fd1
, src
, fd2
, dst
, flags
);
166 src_slash
= src
[src_len
- 1] == '/';
167 dst_slash
= dst
[dst_len
- 1] == '/';
168 if (!src_slash
&& !dst_slash
)
169 return renameat2ish (fd1
, src
, fd2
, dst
, flags
);
171 /* Presence of a trailing slash requires directory semantics. If
172 the source does not exist, or if the destination cannot be turned
173 into a directory, give up now. Otherwise, strip trailing slashes
174 before calling rename. */
175 if (fstatat (fd1
, src
, &src_st
, AT_SYMLINK_NOFOLLOW
))
177 if (dst_found_nonexistent
)
179 if (!S_ISDIR (src_st
.st_mode
))
180 return errno_fail (ENOENT
);
182 else if (fstatat (fd2
, dst
, &dst_st
, AT_SYMLINK_NOFOLLOW
))
184 if (errno
!= ENOENT
|| !S_ISDIR (src_st
.st_mode
))
187 else if (!S_ISDIR (dst_st
.st_mode
))
188 return errno_fail (ENOTDIR
);
189 else if (!S_ISDIR (src_st
.st_mode
))
190 return errno_fail (EISDIR
);
192 # if RENAME_TRAILING_SLASH_SOURCE_BUG
193 /* See the lengthy comment in rename.c why Solaris 9 is forced to
194 GNU behavior, while Solaris 10 is left with POSIX behavior,
195 regarding symlinks with trailing slash. */
199 src_temp
= strdup (src
);
202 /* Rather than rely on strdup-posix, we set errno ourselves. */
203 rename_errno
= ENOMEM
;
206 strip_trailing_slashes (src_temp
);
207 if (fstatat (fd1
, src_temp
, &src_st
, AT_SYMLINK_NOFOLLOW
))
209 rename_errno
= errno
;
212 if (S_ISLNK (src_st
.st_mode
))
217 dst_temp
= strdup (dst
);
220 rename_errno
= ENOMEM
;
223 strip_trailing_slashes (dst_temp
);
224 char readlink_buf
[1];
225 if (readlinkat (fd2
, dst_temp
, readlink_buf
, sizeof readlink_buf
) < 0)
227 if (errno
!= ENOENT
&& errno
!= EINVAL
)
229 rename_errno
= errno
;
236 # endif /* RENAME_TRAILING_SLASH_SOURCE_BUG */
238 /* renameat does not honor trailing / on Solaris 10. Solve it in a
239 similar manner to rename. No need to worry about bugs not present
240 on Solaris, since all other systems either lack renameat or honor
241 trailing slash correctly. */
243 ret_val
= renameat2ish (fd1
, src_temp
, fd2
, dst_temp
, flags
);
244 rename_errno
= errno
;
251 errno
= rename_errno
;
254 #else /* !HAVE_RENAMEAT */
256 /* RENAME_NOREPLACE is the only flag currently supported. */
257 if (flags
& ~RENAME_NOREPLACE
)
258 return errno_fail (ENOTSUP
);
259 return at_func2 (fd1
, src
, fd2
, dst
, flags
? rename_noreplace
: rename
);
261 #endif /* !HAVE_RENAMEAT */