warnings: fix compilation with old autoconf
[gnulib/ericb.git] / lib / renameat2.c
blob1e107fc50063990f692cc20a68117a0c4399c4da
1 /* Rename a file relative to open directories.
2 Copyright (C) 2009-2017 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 <http://www.gnu.org/licenses/>. */
17 /* written by Eric Blake and Paul Eggert */
19 #include <config.h>
21 #include "renameat2.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). */
73 int
74 renameat2 (int fd1, char const *src, int fd2, char const *dst,
75 unsigned int flags)
77 int ret_val = -1;
78 int err = EINVAL;
80 #ifdef SYS_renameat2
81 ret_val = syscall (SYS_renameat2, fd1, src, fd2, dst, flags);
82 err = errno;
83 #elif defined RENAME_EXCL
84 if (! (flags & ~(RENAME_EXCHANGE | RENAME_NOREPLACE)))
86 ret_val = renameatx_np (fd1, src, fd2, dst,
87 ((flags & RENAME_EXCHANGE ? RENAME_SWAP : 0)
88 | (flags & RENAME_NOREPLACE ? RENAME_EXCL : 0)));
89 err = errno;
91 #endif
93 if (! (ret_val < 0 && (err == EINVAL || err == ENOSYS || err == ENOTSUP)))
94 return ret_val;
96 #if HAVE_RENAMEAT
98 size_t src_len;
99 size_t dst_len;
100 char *src_temp = (char *) src;
101 char *dst_temp = (char *) dst;
102 bool src_slash;
103 bool dst_slash;
104 int rename_errno = ENOTDIR;
105 struct stat src_st;
106 struct stat dst_st;
107 bool dst_found_nonexistent = false;
109 if (flags != 0)
111 /* RENAME_NOREPLACE is the only flag currently supported. */
112 if (flags & ~RENAME_NOREPLACE)
113 return errno_fail (ENOTSUP);
114 else
116 /* This has a race between the call to lstatat and the calls to
117 renameat below. */
118 if (lstatat (fd2, dst, &dst_st) == 0 || errno == EOVERFLOW)
119 return errno_fail (EEXIST);
120 if (errno != ENOENT)
121 return -1;
122 dst_found_nonexistent = true;
126 /* Let strace see any ENOENT failure. */
127 src_len = strlen (src);
128 dst_len = strlen (dst);
129 if (!src_len || !dst_len)
130 return renameat (fd1, src, fd2, dst);
132 src_slash = src[src_len - 1] == '/';
133 dst_slash = dst[dst_len - 1] == '/';
134 if (!src_slash && !dst_slash)
135 return renameat (fd1, src, fd2, dst);
137 /* Presence of a trailing slash requires directory semantics. If
138 the source does not exist, or if the destination cannot be turned
139 into a directory, give up now. Otherwise, strip trailing slashes
140 before calling rename. */
141 if (lstatat (fd1, src, &src_st))
142 return -1;
143 if (dst_found_nonexistent)
145 if (!S_ISDIR (src_st.st_mode))
146 return errno_fail (ENOENT);
148 else if (lstatat (fd2, dst, &dst_st))
150 if (errno != ENOENT || !S_ISDIR (src_st.st_mode))
151 return -1;
153 else if (!S_ISDIR (dst_st.st_mode))
154 return errno_fail (ENOTDIR);
155 else if (!S_ISDIR (src_st.st_mode))
156 return errno_fail (EISDIR);
158 # if RENAME_TRAILING_SLASH_SOURCE_BUG
159 /* See the lengthy comment in rename.c why Solaris 9 is forced to
160 GNU behavior, while Solaris 10 is left with POSIX behavior,
161 regarding symlinks with trailing slash. */
162 ret_val = -1;
163 if (src_slash)
165 src_temp = strdup (src);
166 if (!src_temp)
168 /* Rather than rely on strdup-posix, we set errno ourselves. */
169 rename_errno = ENOMEM;
170 goto out;
172 strip_trailing_slashes (src_temp);
173 if (lstatat (fd1, src_temp, &src_st))
175 rename_errno = errno;
176 goto out;
178 if (S_ISLNK (src_st.st_mode))
179 goto out;
181 if (dst_slash)
183 dst_temp = strdup (dst);
184 if (!dst_temp)
186 rename_errno = ENOMEM;
187 goto out;
189 strip_trailing_slashes (dst_temp);
190 if (lstatat (fd2, dst_temp, &dst_st))
192 if (errno != ENOENT)
194 rename_errno = errno;
195 goto out;
198 else if (S_ISLNK (dst_st.st_mode))
199 goto out;
201 # endif /* RENAME_TRAILING_SLASH_SOURCE_BUG */
203 /* renameat does not honor trailing / on Solaris 10. Solve it in a
204 similar manner to rename. No need to worry about bugs not present
205 on Solaris, since all other systems either lack renameat or honor
206 trailing slash correctly. */
208 ret_val = renameat (fd1, src_temp, fd2, dst_temp);
209 rename_errno = errno;
210 goto out;
211 out:
212 if (src_temp != src)
213 free (src_temp);
214 if (dst_temp != dst)
215 free (dst_temp);
216 errno = rename_errno;
217 return ret_val;
219 #else /* !HAVE_RENAMEAT */
221 /* RENAME_NOREPLACE is the only flag currently supported. */
222 if (flags & ~RENAME_NOREPLACE)
223 return errno_fail (ENOTSUP);
224 return at_func2 (fd1, src, fd2, dst, flags ? rename_noreplace : rename);
226 #endif /* !HAVE_RENAMEAT */