openat: don’t close (-1)
[gnulib.git] / lib / renameatu.c
blob437fc8f71a2151857809bd8afedc92e3de94ca56
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 */
19 #include <config.h>
21 #include "renameatu.h"
23 #include <errno.h>
24 #include <stdio.h>
25 #include <sys/stat.h>
26 #include <unistd.h>
28 #ifdef __linux__
29 # include <sys/syscall.h>
30 #endif
32 static int
33 errno_fail (int e)
35 errno = e;
36 return -1;
39 #if HAVE_RENAMEAT
41 # include <stdlib.h>
42 # include <string.h>
44 # include "dirname.h"
45 # include "openat.h"
47 #else
48 # include "openat-priv.h"
50 static int
51 rename_noreplace (char const *src, char const *dst)
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);
59 #endif
61 #undef renameat
63 #if HAVE_RENAMEAT
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)
73 # ifdef RENAME_SWAP
74 if (flags & RENAME_EXCHANGE)
75 return renameatx_np (fd1, src, fd2, dst, RENAME_SWAP);
76 # endif
77 # ifdef RENAME_EXCL
78 if (flags)
80 int r = renameatx_np (fd1, src, fd2, dst, RENAME_EXCL);
81 if (r == 0 || (errno != ENOTSUP && errno != ENOSYS))
82 return r;
84 # endif
86 return renameat (fd1, src, fd2, dst);
88 #endif
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,
104 unsigned int flags)
106 int ret_val = -1;
107 int err = EINVAL;
109 #if HAVE_WORKING_RENAMEAT2
110 ret_val = renameat2 (fd1, src, fd2, dst, flags);
111 err = errno;
112 #elif defined SYS_renameat2
113 ret_val = syscall (SYS_renameat2, fd1, src, fd2, dst, flags);
114 err = errno;
115 #endif
117 if (! (ret_val < 0 && (err == EINVAL || err == ENOSYS || err == ENOTSUP)))
118 return ret_val;
120 #if HAVE_RENAMEAT
122 size_t src_len;
123 size_t dst_len;
124 char *src_temp = (char *) src;
125 char *dst_temp = (char *) dst;
126 bool src_slash;
127 bool dst_slash;
128 int rename_errno = ENOTDIR;
129 struct stat src_st;
130 struct stat dst_st;
131 bool dst_found_nonexistent = false;
133 switch (flags)
135 case 0:
136 break;
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);
147 if (errno != ENOENT)
148 return -1;
149 dst_found_nonexistent = true;
150 break;
152 case RENAME_EXCHANGE:
153 #ifdef RENAME_SWAP
154 break;
155 #endif
156 default:
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))
176 return -1;
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))
185 return -1;
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. */
196 ret_val = -1;
197 if (src_slash)
199 src_temp = strdup (src);
200 if (!src_temp)
202 /* Rather than rely on strdup-posix, we set errno ourselves. */
203 rename_errno = ENOMEM;
204 goto out;
206 strip_trailing_slashes (src_temp);
207 if (fstatat (fd1, src_temp, &src_st, AT_SYMLINK_NOFOLLOW))
209 rename_errno = errno;
210 goto out;
212 if (S_ISLNK (src_st.st_mode))
213 goto out;
215 if (dst_slash)
217 dst_temp = strdup (dst);
218 if (!dst_temp)
220 rename_errno = ENOMEM;
221 goto out;
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;
230 goto out;
233 else
234 goto out;
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;
245 goto out;
246 out:
247 if (src_temp != src)
248 free (src_temp);
249 if (dst_temp != dst)
250 free (dst_temp);
251 errno = rename_errno;
252 return ret_val;
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 */