findprog-in: Relicense under LGPLv2+.
[gnulib.git] / lib / renameatu.c
blob89396024d094cff2559ef8846f94c03bca1ac3fc
1 /* Rename a file relative to open directories.
2 Copyright (C) 2009-2020 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 #elif defined RENAME_EXCL
90 if (! (flags & ~(RENAME_EXCHANGE | RENAME_NOREPLACE)))
92 ret_val = renameatx_np (fd1, src, fd2, dst,
93 ((flags & RENAME_EXCHANGE ? RENAME_SWAP : 0)
94 | (flags & RENAME_NOREPLACE ? RENAME_EXCL : 0)));
95 err = errno;
97 #endif
99 if (! (ret_val < 0 && (err == EINVAL || err == ENOSYS || err == ENOTSUP)))
100 return ret_val;
102 #if HAVE_RENAMEAT
104 size_t src_len;
105 size_t dst_len;
106 char *src_temp = (char *) src;
107 char *dst_temp = (char *) dst;
108 bool src_slash;
109 bool dst_slash;
110 int rename_errno = ENOTDIR;
111 struct stat src_st;
112 struct stat dst_st;
113 bool dst_found_nonexistent = false;
115 if (flags != 0)
117 /* RENAME_NOREPLACE is the only flag currently supported. */
118 if (flags & ~RENAME_NOREPLACE)
119 return errno_fail (ENOTSUP);
120 else
122 /* This has a race between the call to lstatat and the calls to
123 renameat below. */
124 if (lstatat (fd2, dst, &dst_st) == 0 || errno == EOVERFLOW)
125 return errno_fail (EEXIST);
126 if (errno != ENOENT)
127 return -1;
128 dst_found_nonexistent = true;
132 /* Let strace see any ENOENT failure. */
133 src_len = strlen (src);
134 dst_len = strlen (dst);
135 if (!src_len || !dst_len)
136 return renameat (fd1, src, fd2, dst);
138 src_slash = src[src_len - 1] == '/';
139 dst_slash = dst[dst_len - 1] == '/';
140 if (!src_slash && !dst_slash)
141 return renameat (fd1, src, fd2, dst);
143 /* Presence of a trailing slash requires directory semantics. If
144 the source does not exist, or if the destination cannot be turned
145 into a directory, give up now. Otherwise, strip trailing slashes
146 before calling rename. */
147 if (lstatat (fd1, src, &src_st))
148 return -1;
149 if (dst_found_nonexistent)
151 if (!S_ISDIR (src_st.st_mode))
152 return errno_fail (ENOENT);
154 else if (lstatat (fd2, dst, &dst_st))
156 if (errno != ENOENT || !S_ISDIR (src_st.st_mode))
157 return -1;
159 else if (!S_ISDIR (dst_st.st_mode))
160 return errno_fail (ENOTDIR);
161 else if (!S_ISDIR (src_st.st_mode))
162 return errno_fail (EISDIR);
164 # if RENAME_TRAILING_SLASH_SOURCE_BUG
165 /* See the lengthy comment in rename.c why Solaris 9 is forced to
166 GNU behavior, while Solaris 10 is left with POSIX behavior,
167 regarding symlinks with trailing slash. */
168 ret_val = -1;
169 if (src_slash)
171 src_temp = strdup (src);
172 if (!src_temp)
174 /* Rather than rely on strdup-posix, we set errno ourselves. */
175 rename_errno = ENOMEM;
176 goto out;
178 strip_trailing_slashes (src_temp);
179 if (lstatat (fd1, src_temp, &src_st))
181 rename_errno = errno;
182 goto out;
184 if (S_ISLNK (src_st.st_mode))
185 goto out;
187 if (dst_slash)
189 dst_temp = strdup (dst);
190 if (!dst_temp)
192 rename_errno = ENOMEM;
193 goto out;
195 strip_trailing_slashes (dst_temp);
196 if (lstatat (fd2, dst_temp, &dst_st))
198 if (errno != ENOENT)
200 rename_errno = errno;
201 goto out;
204 else if (S_ISLNK (dst_st.st_mode))
205 goto out;
207 # endif /* RENAME_TRAILING_SLASH_SOURCE_BUG */
209 /* renameat does not honor trailing / on Solaris 10. Solve it in a
210 similar manner to rename. No need to worry about bugs not present
211 on Solaris, since all other systems either lack renameat or honor
212 trailing slash correctly. */
214 ret_val = renameat (fd1, src_temp, fd2, dst_temp);
215 rename_errno = errno;
216 goto out;
217 out:
218 if (src_temp != src)
219 free (src_temp);
220 if (dst_temp != dst)
221 free (dst_temp);
222 errno = rename_errno;
223 return ret_val;
225 #else /* !HAVE_RENAMEAT */
227 /* RENAME_NOREPLACE is the only flag currently supported. */
228 if (flags & ~RENAME_NOREPLACE)
229 return errno_fail (ENOTSUP);
230 return at_func2 (fd1, src, fd2, dst, flags ? rename_noreplace : rename);
232 #endif /* !HAVE_RENAMEAT */