renameat2: port to RHEL 7 + NFS
[gnulib.git] / lib / renameat2.c
blobfd38a71e7fb822c22e40d9e5cad73d3b6256f01b
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 #ifdef SYS_renameat2
78 int r = syscall (SYS_renameat2, fd1, src, fd2, dst, flags);
79 if (! (r < 0 && (errno == ENOSYS || errno == EINVAL)))
80 return r;
81 #endif
83 #if HAVE_RENAMEAT
85 size_t src_len;
86 size_t dst_len;
87 char *src_temp = (char *) src;
88 char *dst_temp = (char *) dst;
89 bool src_slash;
90 bool dst_slash;
91 int ret_val;
92 int rename_errno = ENOTDIR;
93 struct stat src_st;
94 struct stat dst_st;
95 bool dst_found_nonexistent = false;
97 if (flags != 0)
99 /* RENAME_NOREPLACE is the only flag currently supported. */
100 if (flags & ~RENAME_NOREPLACE)
101 return errno_fail (ENOTSUP);
102 else
104 /* This has a race between the call to lstatat and the calls to
105 renameat below. */
106 if (lstatat (fd2, dst, &dst_st) == 0 || errno == EOVERFLOW)
107 return errno_fail (EEXIST);
108 if (errno != ENOENT)
109 return -1;
110 dst_found_nonexistent = true;
114 /* Let strace see any ENOENT failure. */
115 src_len = strlen (src);
116 dst_len = strlen (dst);
117 if (!src_len || !dst_len)
118 return renameat (fd1, src, fd2, dst);
120 src_slash = src[src_len - 1] == '/';
121 dst_slash = dst[dst_len - 1] == '/';
122 if (!src_slash && !dst_slash)
123 return renameat (fd1, src, fd2, dst);
125 /* Presence of a trailing slash requires directory semantics. If
126 the source does not exist, or if the destination cannot be turned
127 into a directory, give up now. Otherwise, strip trailing slashes
128 before calling rename. */
129 if (lstatat (fd1, src, &src_st))
130 return -1;
131 if (dst_found_nonexistent)
133 if (!S_ISDIR (src_st.st_mode))
134 return errno_fail (ENOENT);
136 else if (lstatat (fd2, dst, &dst_st))
138 if (errno != ENOENT || !S_ISDIR (src_st.st_mode))
139 return -1;
141 else if (!S_ISDIR (dst_st.st_mode))
142 return errno_fail (ENOTDIR);
143 else if (!S_ISDIR (src_st.st_mode))
144 return errno_fail (EISDIR);
146 # if RENAME_TRAILING_SLASH_SOURCE_BUG
147 /* See the lengthy comment in rename.c why Solaris 9 is forced to
148 GNU behavior, while Solaris 10 is left with POSIX behavior,
149 regarding symlinks with trailing slash. */
150 ret_val = -1;
151 if (src_slash)
153 src_temp = strdup (src);
154 if (!src_temp)
156 /* Rather than rely on strdup-posix, we set errno ourselves. */
157 rename_errno = ENOMEM;
158 goto out;
160 strip_trailing_slashes (src_temp);
161 if (lstatat (fd1, src_temp, &src_st))
163 rename_errno = errno;
164 goto out;
166 if (S_ISLNK (src_st.st_mode))
167 goto out;
169 if (dst_slash)
171 dst_temp = strdup (dst);
172 if (!dst_temp)
174 rename_errno = ENOMEM;
175 goto out;
177 strip_trailing_slashes (dst_temp);
178 if (lstatat (fd2, dst_temp, &dst_st))
180 if (errno != ENOENT)
182 rename_errno = errno;
183 goto out;
186 else if (S_ISLNK (dst_st.st_mode))
187 goto out;
189 # endif /* RENAME_TRAILING_SLASH_SOURCE_BUG */
191 /* renameat does not honor trailing / on Solaris 10. Solve it in a
192 similar manner to rename. No need to worry about bugs not present
193 on Solaris, since all other systems either lack renameat or honor
194 trailing slash correctly. */
196 ret_val = renameat (fd1, src_temp, fd2, dst_temp);
197 rename_errno = errno;
198 goto out;
199 out:
200 if (src_temp != src)
201 free (src_temp);
202 if (dst_temp != dst)
203 free (dst_temp);
204 errno = rename_errno;
205 return ret_val;
207 #else /* !HAVE_RENAMEAT */
209 /* RENAME_NOREPLACE is the only flag currently supported. */
210 if (flags & ~RENAME_NOREPLACE)
211 return errno_fail (ENOTSUP);
212 return at_func2 (fd1, src, fd2, dst, flags ? rename_noreplace : rename);
214 #endif /* !HAVE_RENAMEAT */