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 }