fchmod-tests, fchmodat tests, lchmod tests: Add more tests.
[gnulib.git] / lib / renameatu.c
blobc493a38dc1f387cddb1440ed4a8cb26cc0d01d7f
1 /* Rename a file relative to open directories.
2 Copyright (C) 2009-2021 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 <stdbool.h>
42 # include <stdlib.h>
43 # include <string.h>
45 # include "dirname.h"
46 # include "openat.h"
48 #else
49 # include "openat-priv.h"
51 static int
52 rename_noreplace (char const *src, char const *dst)
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);
60 #endif
62 #undef renameat
64 /* Rename FILE1, in the directory open on descriptor FD1, to FILE2, in
65 the directory open on descriptor FD2. If possible, do it without
66 changing the working directory. Otherwise, resort to using
67 save_cwd/fchdir, then rename/restore_cwd. If either the save_cwd or
68 the restore_cwd fails, then give a diagnostic and exit nonzero.
70 Obey FLAGS when doing the renaming. If FLAGS is zero, this
71 function is equivalent to renameat (FD1, SRC, FD2, DST).
72 Otherwise, attempt to implement FLAGS even if the implementation is
73 not atomic; this differs from the GNU/Linux native renameat2,
74 which fails if it cannot guarantee atomicity. */
76 int
77 renameatu (int fd1, char const *src, int fd2, char const *dst,
78 unsigned int flags)
80 int ret_val = -1;
81 int err = EINVAL;
83 #ifdef HAVE_RENAMEAT2
84 ret_val = renameat2 (fd1, src, fd2, dst, flags);
85 err = errno;
86 #elif defined SYS_renameat2
87 ret_val = syscall (SYS_renameat2, fd1, src, fd2, dst, flags);
88 err = errno;
89 #endif
91 if (! (ret_val < 0 && (err == EINVAL || err == ENOSYS || err == ENOTSUP)))
92 return ret_val;
94 #if HAVE_RENAMEAT
96 # if defined RENAME_EXCL /* macOS */
97 unsigned int uflags;
98 # endif
99 size_t src_len;
100 size_t dst_len;
101 char *src_temp = (char *) src;
102 char *dst_temp = (char *) dst;
103 bool src_slash;
104 bool dst_slash;
105 int rename_errno = ENOTDIR;
106 struct stat src_st;
107 struct stat dst_st;
108 bool dst_found_nonexistent = false;
110 /* Check the flags. */
111 # if defined RENAME_EXCL
112 /* We can support RENAME_EXCHANGE and RENAME_NOREPLACE. */
113 if (flags & ~(RENAME_EXCHANGE | RENAME_NOREPLACE))
114 # else
115 /* RENAME_NOREPLACE is the only flag currently supported. */
116 if (flags & ~RENAME_NOREPLACE)
117 # endif
118 return errno_fail (ENOTSUP);
120 # if defined RENAME_EXCL
121 uflags = ((flags & RENAME_EXCHANGE ? RENAME_SWAP : 0)
122 | (flags & RENAME_NOREPLACE ? RENAME_EXCL : 0));
123 # endif
125 # if !defined RENAME_EXCL
126 if ((flags & RENAME_NOREPLACE) != 0)
128 /* This has a race between the call to lstatat and the calls to
129 renameat below. */
130 if (lstatat (fd2, dst, &dst_st) == 0 || errno == EOVERFLOW)
131 return errno_fail (EEXIST);
132 if (errno != ENOENT)
133 return -1;
134 dst_found_nonexistent = true;
136 # endif
138 /* Let strace see any ENOENT failure. */
139 src_len = strlen (src);
140 dst_len = strlen (dst);
141 if (!src_len || !dst_len)
142 # if defined RENAME_EXCL
143 return renameatx_np (fd1, src, fd2, dst, uflags);
144 # else
145 return renameat (fd1, src, fd2, dst);
146 # endif
148 src_slash = src[src_len - 1] == '/';
149 dst_slash = dst[dst_len - 1] == '/';
150 if (!src_slash && !dst_slash)
151 # if defined RENAME_EXCL
152 return renameatx_np (fd1, src, fd2, dst, uflags);
153 # else
154 return renameat (fd1, src, fd2, dst);
155 # endif
157 /* Presence of a trailing slash requires directory semantics. If
158 the source does not exist, or if the destination cannot be turned
159 into a directory, give up now. Otherwise, strip trailing slashes
160 before calling rename. */
161 if (lstatat (fd1, src, &src_st))
162 return -1;
163 if (dst_found_nonexistent)
165 if (!S_ISDIR (src_st.st_mode))
166 return errno_fail (ENOENT);
168 else if (lstatat (fd2, dst, &dst_st))
170 if (errno != ENOENT || !S_ISDIR (src_st.st_mode))
171 return -1;
173 else if (!S_ISDIR (dst_st.st_mode))
174 return errno_fail (ENOTDIR);
175 else if (!S_ISDIR (src_st.st_mode))
176 return errno_fail (EISDIR);
178 # if RENAME_TRAILING_SLASH_SOURCE_BUG
179 /* See the lengthy comment in rename.c why Solaris 9 is forced to
180 GNU behavior, while Solaris 10 is left with POSIX behavior,
181 regarding symlinks with trailing slash. */
182 ret_val = -1;
183 if (src_slash)
185 src_temp = strdup (src);
186 if (!src_temp)
188 /* Rather than rely on strdup-posix, we set errno ourselves. */
189 rename_errno = ENOMEM;
190 goto out;
192 strip_trailing_slashes (src_temp);
193 if (lstatat (fd1, src_temp, &src_st))
195 rename_errno = errno;
196 goto out;
198 if (S_ISLNK (src_st.st_mode))
199 goto out;
201 if (dst_slash)
203 dst_temp = strdup (dst);
204 if (!dst_temp)
206 rename_errno = ENOMEM;
207 goto out;
209 strip_trailing_slashes (dst_temp);
210 if (lstatat (fd2, dst_temp, &dst_st))
212 if (errno != ENOENT)
214 rename_errno = errno;
215 goto out;
218 else if (S_ISLNK (dst_st.st_mode))
219 goto out;
221 # endif /* RENAME_TRAILING_SLASH_SOURCE_BUG */
223 /* renameat does not honor trailing / on Solaris 10. Solve it in a
224 similar manner to rename. No need to worry about bugs not present
225 on Solaris, since all other systems either lack renameat or honor
226 trailing slash correctly. */
228 # if defined RENAME_EXCL
229 ret_val = renameatx_np (fd1, src_temp, fd2, dst_temp, uflags);
230 # else
231 ret_val = renameat (fd1, src_temp, fd2, dst_temp);
232 # endif
233 rename_errno = errno;
234 goto out;
235 out:
236 if (src_temp != src)
237 free (src_temp);
238 if (dst_temp != dst)
239 free (dst_temp);
240 errno = rename_errno;
241 return ret_val;
243 #else /* !HAVE_RENAMEAT */
245 /* RENAME_NOREPLACE is the only flag currently supported. */
246 if (flags & ~RENAME_NOREPLACE)
247 return errno_fail (ENOTSUP);
248 return at_func2 (fd1, src, fd2, dst, flags ? rename_noreplace : rename);
250 #endif /* !HAVE_RENAMEAT */