Commit | Line | Data |
---|---|---|
6a29c58e YQ |
1 | /* Work around rename bugs in some systems. |
2 | ||
5df4cba6 | 3 | Copyright (C) 2001-2003, 2005-2006, 2009-2020 Free Software Foundation, Inc. |
6a29c58e YQ |
4 | |
5 | This program is free software: you can redistribute it and/or modify | |
6 | it under the terms of the GNU General Public License as published by | |
7 | the Free Software Foundation; either version 3 of the License, or | |
8 | (at your option) any later version. | |
9 | ||
10 | This program is distributed in the hope that it will be useful, | |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | GNU General Public License for more details. | |
14 | ||
15 | You should have received a copy of the GNU General Public License | |
c0c3707f | 16 | along with this program. If not, see <https://www.gnu.org/licenses/>. */ |
6a29c58e YQ |
17 | |
18 | /* Written by Volker Borchert, Eric Blake. */ | |
19 | ||
20 | #include <config.h> | |
21 | ||
22 | #include <stdio.h> | |
23 | ||
24 | #undef rename | |
25 | ||
c0c3707f | 26 | #if defined _WIN32 && ! defined __CYGWIN__ |
6a29c58e YQ |
27 | /* The mingw rename has problems with trailing slashes; it also |
28 | requires use of native Windows calls to allow atomic renames over | |
29 | existing files. */ | |
30 | ||
31 | # include <errno.h> | |
32 | # include <stdbool.h> | |
33 | # include <stdlib.h> | |
34 | # include <sys/stat.h> | |
35 | # include <unistd.h> | |
36 | ||
37 | # define WIN32_LEAN_AND_MEAN | |
38 | # include <windows.h> | |
39 | ||
40 | # include "dirname.h" | |
41 | ||
42 | /* Rename the file SRC to DST. This replacement is necessary on | |
43 | Windows, on which the system rename function will not replace | |
44 | an existing DST. */ | |
45 | int | |
46 | rpl_rename (char const *src, char const *dst) | |
47 | { | |
48 | int error; | |
49 | size_t src_len = strlen (src); | |
50 | size_t dst_len = strlen (dst); | |
51 | char *src_base = last_component (src); | |
52 | char *dst_base = last_component (dst); | |
53 | bool src_slash; | |
54 | bool dst_slash; | |
55 | bool dst_exists; | |
56 | struct stat src_st; | |
57 | struct stat dst_st; | |
58 | ||
59 | /* Filter out dot as last component. */ | |
60 | if (!src_len || !dst_len) | |
61 | { | |
62 | errno = ENOENT; | |
63 | return -1; | |
64 | } | |
65 | if (*src_base == '.') | |
66 | { | |
67 | size_t len = base_len (src_base); | |
68 | if (len == 1 || (len == 2 && src_base[1] == '.')) | |
69 | { | |
70 | errno = EINVAL; | |
71 | return -1; | |
72 | } | |
73 | } | |
74 | if (*dst_base == '.') | |
75 | { | |
76 | size_t len = base_len (dst_base); | |
77 | if (len == 1 || (len == 2 && dst_base[1] == '.')) | |
78 | { | |
79 | errno = EINVAL; | |
80 | return -1; | |
81 | } | |
82 | } | |
83 | ||
84 | /* Presence of a trailing slash requires directory semantics. If | |
85 | the source does not exist, or if the destination cannot be turned | |
86 | into a directory, give up now. Otherwise, strip trailing slashes | |
87 | before calling rename. There are no symlinks on mingw, so stat | |
88 | works instead of lstat. */ | |
89 | src_slash = ISSLASH (src[src_len - 1]); | |
90 | dst_slash = ISSLASH (dst[dst_len - 1]); | |
91 | if (stat (src, &src_st)) | |
92 | return -1; | |
93 | if (stat (dst, &dst_st)) | |
94 | { | |
95 | if (errno != ENOENT || (!S_ISDIR (src_st.st_mode) && dst_slash)) | |
96 | return -1; | |
97 | dst_exists = false; | |
98 | } | |
99 | else | |
100 | { | |
101 | if (S_ISDIR (dst_st.st_mode) != S_ISDIR (src_st.st_mode)) | |
102 | { | |
103 | errno = S_ISDIR (dst_st.st_mode) ? EISDIR : ENOTDIR; | |
104 | return -1; | |
105 | } | |
106 | dst_exists = true; | |
107 | } | |
108 | ||
109 | /* There are no symlinks, so if a file existed with a trailing | |
110 | slash, it must be a directory, and we don't have to worry about | |
111 | stripping strip trailing slash. However, mingw refuses to | |
112 | replace an existing empty directory, so we have to help it out. | |
113 | And canonicalize_file_name is not yet ported to mingw; however, | |
114 | for directories, getcwd works as a viable alternative. Ensure | |
115 | that we can get back to where we started before using it; later | |
116 | attempts to return are fatal. Note that we can end up losing a | |
117 | directory if rename then fails, but it was empty, so not much | |
118 | damage was done. */ | |
119 | if (dst_exists && S_ISDIR (dst_st.st_mode)) | |
120 | { | |
121 | char *cwd = getcwd (NULL, 0); | |
122 | char *src_temp; | |
123 | char *dst_temp; | |
124 | if (!cwd || chdir (cwd)) | |
125 | return -1; | |
126 | if (IS_ABSOLUTE_FILE_NAME (src)) | |
127 | { | |
128 | dst_temp = chdir (dst) ? NULL : getcwd (NULL, 0); | |
129 | src_temp = chdir (src) ? NULL : getcwd (NULL, 0); | |
130 | } | |
131 | else | |
132 | { | |
133 | src_temp = chdir (src) ? NULL : getcwd (NULL, 0); | |
134 | if (!IS_ABSOLUTE_FILE_NAME (dst) && chdir (cwd)) | |
135 | abort (); | |
136 | dst_temp = chdir (dst) ? NULL : getcwd (NULL, 0); | |
137 | } | |
138 | if (chdir (cwd)) | |
139 | abort (); | |
140 | free (cwd); | |
141 | if (!src_temp || !dst_temp) | |
142 | { | |
143 | free (src_temp); | |
144 | free (dst_temp); | |
145 | errno = ENOMEM; | |
146 | return -1; | |
147 | } | |
148 | src_len = strlen (src_temp); | |
149 | if (strncmp (src_temp, dst_temp, src_len) == 0 | |
150 | && (ISSLASH (dst_temp[src_len]) || dst_temp[src_len] == '\0')) | |
151 | { | |
152 | error = dst_temp[src_len]; | |
153 | free (src_temp); | |
154 | free (dst_temp); | |
155 | if (error) | |
156 | { | |
157 | errno = EINVAL; | |
158 | return -1; | |
159 | } | |
160 | return 0; | |
161 | } | |
162 | if (rmdir (dst)) | |
163 | { | |
164 | error = errno; | |
165 | free (src_temp); | |
166 | free (dst_temp); | |
167 | errno = error; | |
168 | return -1; | |
169 | } | |
170 | free (src_temp); | |
171 | free (dst_temp); | |
172 | } | |
173 | ||
174 | /* MoveFileEx works if SRC is a directory without any flags, but | |
175 | fails with MOVEFILE_REPLACE_EXISTING, so try without flags first. | |
176 | Thankfully, MoveFileEx handles hard links correctly, even though | |
177 | rename() does not. */ | |
178 | if (MoveFileEx (src, dst, 0)) | |
179 | return 0; | |
180 | ||
181 | /* Retry with MOVEFILE_REPLACE_EXISTING if the move failed | |
182 | due to the destination already existing. */ | |
183 | error = GetLastError (); | |
184 | if (error == ERROR_FILE_EXISTS || error == ERROR_ALREADY_EXISTS) | |
185 | { | |
186 | if (MoveFileEx (src, dst, MOVEFILE_REPLACE_EXISTING)) | |
187 | return 0; | |
188 | ||
189 | error = GetLastError (); | |
190 | } | |
191 | ||
192 | switch (error) | |
193 | { | |
194 | case ERROR_FILE_NOT_FOUND: | |
195 | case ERROR_PATH_NOT_FOUND: | |
196 | case ERROR_BAD_PATHNAME: | |
197 | case ERROR_DIRECTORY: | |
198 | errno = ENOENT; | |
199 | break; | |
200 | ||
201 | case ERROR_ACCESS_DENIED: | |
202 | case ERROR_SHARING_VIOLATION: | |
203 | errno = EACCES; | |
204 | break; | |
205 | ||
206 | case ERROR_OUTOFMEMORY: | |
207 | errno = ENOMEM; | |
208 | break; | |
209 | ||
210 | case ERROR_CURRENT_DIRECTORY: | |
211 | errno = EBUSY; | |
212 | break; | |
213 | ||
214 | case ERROR_NOT_SAME_DEVICE: | |
215 | errno = EXDEV; | |
216 | break; | |
217 | ||
218 | case ERROR_WRITE_PROTECT: | |
219 | errno = EROFS; | |
220 | break; | |
221 | ||
222 | case ERROR_WRITE_FAULT: | |
223 | case ERROR_READ_FAULT: | |
224 | case ERROR_GEN_FAILURE: | |
225 | errno = EIO; | |
226 | break; | |
227 | ||
228 | case ERROR_HANDLE_DISK_FULL: | |
229 | case ERROR_DISK_FULL: | |
230 | case ERROR_DISK_TOO_FRAGMENTED: | |
231 | errno = ENOSPC; | |
232 | break; | |
233 | ||
234 | case ERROR_FILE_EXISTS: | |
235 | case ERROR_ALREADY_EXISTS: | |
236 | errno = EEXIST; | |
237 | break; | |
238 | ||
239 | case ERROR_BUFFER_OVERFLOW: | |
240 | case ERROR_FILENAME_EXCED_RANGE: | |
241 | errno = ENAMETOOLONG; | |
242 | break; | |
243 | ||
244 | case ERROR_INVALID_NAME: | |
245 | case ERROR_DELETE_PENDING: | |
246 | errno = EPERM; /* ? */ | |
247 | break; | |
248 | ||
249 | # ifndef ERROR_FILE_TOO_LARGE | |
250 | /* This value is documented but not defined in all versions of windows.h. */ | |
251 | # define ERROR_FILE_TOO_LARGE 223 | |
252 | # endif | |
253 | case ERROR_FILE_TOO_LARGE: | |
254 | errno = EFBIG; | |
255 | break; | |
256 | ||
257 | default: | |
258 | errno = EINVAL; | |
259 | break; | |
260 | } | |
261 | ||
262 | return -1; | |
263 | } | |
264 | ||
265 | #else /* ! W32 platform */ | |
266 | ||
267 | # include <errno.h> | |
268 | # include <stdio.h> | |
269 | # include <stdlib.h> | |
270 | # include <string.h> | |
271 | # include <sys/stat.h> | |
272 | # include <unistd.h> | |
273 | ||
274 | # include "dirname.h" | |
275 | # include "same-inode.h" | |
276 | ||
277 | /* Rename the file SRC to DST, fixing any trailing slash bugs. */ | |
278 | ||
279 | int | |
280 | rpl_rename (char const *src, char const *dst) | |
281 | { | |
282 | size_t src_len = strlen (src); | |
283 | size_t dst_len = strlen (dst); | |
284 | char *src_temp = (char *) src; | |
285 | char *dst_temp = (char *) dst; | |
286 | bool src_slash; | |
287 | bool dst_slash; | |
4a626d0a | 288 | bool dst_exists _GL_UNUSED; |
6a29c58e YQ |
289 | int ret_val = -1; |
290 | int rename_errno = ENOTDIR; | |
291 | struct stat src_st; | |
292 | struct stat dst_st; | |
293 | ||
294 | if (!src_len || !dst_len) | |
295 | return rename (src, dst); /* Let strace see the ENOENT failure. */ | |
296 | ||
297 | # if RENAME_DEST_EXISTS_BUG | |
298 | { | |
299 | char *src_base = last_component (src); | |
300 | char *dst_base = last_component (dst); | |
301 | if (*src_base == '.') | |
302 | { | |
303 | size_t len = base_len (src_base); | |
304 | if (len == 1 || (len == 2 && src_base[1] == '.')) | |
305 | { | |
306 | errno = EINVAL; | |
307 | return -1; | |
308 | } | |
309 | } | |
310 | if (*dst_base == '.') | |
311 | { | |
312 | size_t len = base_len (dst_base); | |
313 | if (len == 1 || (len == 2 && dst_base[1] == '.')) | |
314 | { | |
315 | errno = EINVAL; | |
316 | return -1; | |
317 | } | |
318 | } | |
319 | } | |
320 | # endif /* RENAME_DEST_EXISTS_BUG */ | |
321 | ||
322 | src_slash = src[src_len - 1] == '/'; | |
323 | dst_slash = dst[dst_len - 1] == '/'; | |
324 | ||
325 | # if !RENAME_HARD_LINK_BUG && !RENAME_DEST_EXISTS_BUG | |
326 | /* If there are no trailing slashes, then trust the native | |
327 | implementation unless we also suspect issues with hard link | |
328 | detection or file/directory conflicts. */ | |
329 | if (!src_slash && !dst_slash) | |
330 | return rename (src, dst); | |
331 | # endif /* !RENAME_HARD_LINK_BUG && !RENAME_DEST_EXISTS_BUG */ | |
332 | ||
333 | /* Presence of a trailing slash requires directory semantics. If | |
334 | the source does not exist, or if the destination cannot be turned | |
335 | into a directory, give up now. Otherwise, strip trailing slashes | |
336 | before calling rename. */ | |
337 | if (lstat (src, &src_st)) | |
338 | return -1; | |
339 | if (lstat (dst, &dst_st)) | |
340 | { | |
341 | if (errno != ENOENT || (!S_ISDIR (src_st.st_mode) && dst_slash)) | |
342 | return -1; | |
343 | dst_exists = false; | |
344 | } | |
345 | else | |
346 | { | |
347 | if (S_ISDIR (dst_st.st_mode) != S_ISDIR (src_st.st_mode)) | |
348 | { | |
349 | errno = S_ISDIR (dst_st.st_mode) ? EISDIR : ENOTDIR; | |
350 | return -1; | |
351 | } | |
352 | # if RENAME_HARD_LINK_BUG | |
353 | if (SAME_INODE (src_st, dst_st)) | |
354 | return 0; | |
355 | # endif /* RENAME_HARD_LINK_BUG */ | |
356 | dst_exists = true; | |
357 | } | |
358 | ||
359 | # if (RENAME_TRAILING_SLASH_SOURCE_BUG || RENAME_DEST_EXISTS_BUG \ | |
360 | || RENAME_HARD_LINK_BUG) | |
361 | /* If the only bug was that a trailing slash was allowed on a | |
362 | non-existing file destination, as in Solaris 10, then we've | |
363 | already covered that situation. But if there is any problem with | |
364 | a trailing slash on an existing source or destination, as in | |
365 | Solaris 9, or if a directory can overwrite a symlink, as on | |
366 | Cygwin 1.5, or if directories cannot be created with trailing | |
367 | slash, as on NetBSD 1.6, then we must strip the offending slash | |
368 | and check that we have not encountered a symlink instead of a | |
369 | directory. | |
370 | ||
371 | Stripping a trailing slash interferes with POSIX semantics, where | |
372 | rename behavior on a symlink with a trailing slash operates on | |
373 | the corresponding target directory. We prefer the GNU semantics | |
374 | of rejecting any use of a symlink with trailing slash, but do not | |
375 | enforce them, since Solaris 10 is able to obey POSIX semantics | |
376 | and there might be clients expecting it, as counter-intuitive as | |
377 | those semantics are. | |
378 | ||
379 | Technically, we could also follow the POSIX behavior by chasing a | |
380 | readlink trail, but that is harder to implement. */ | |
381 | if (src_slash) | |
382 | { | |
383 | src_temp = strdup (src); | |
384 | if (!src_temp) | |
385 | { | |
386 | /* Rather than rely on strdup-posix, we set errno ourselves. */ | |
387 | rename_errno = ENOMEM; | |
388 | goto out; | |
389 | } | |
390 | strip_trailing_slashes (src_temp); | |
391 | if (lstat (src_temp, &src_st)) | |
392 | { | |
393 | rename_errno = errno; | |
394 | goto out; | |
395 | } | |
396 | if (S_ISLNK (src_st.st_mode)) | |
397 | goto out; | |
398 | } | |
399 | if (dst_slash) | |
400 | { | |
401 | dst_temp = strdup (dst); | |
402 | if (!dst_temp) | |
403 | { | |
404 | rename_errno = ENOMEM; | |
405 | goto out; | |
406 | } | |
407 | strip_trailing_slashes (dst_temp); | |
408 | if (lstat (dst_temp, &dst_st)) | |
409 | { | |
410 | if (errno != ENOENT) | |
411 | { | |
412 | rename_errno = errno; | |
413 | goto out; | |
414 | } | |
415 | } | |
416 | else if (S_ISLNK (dst_st.st_mode)) | |
417 | goto out; | |
418 | } | |
419 | # endif /* RENAME_TRAILING_SLASH_SOURCE_BUG || RENAME_DEST_EXISTS_BUG | |
420 | || RENAME_HARD_LINK_BUG */ | |
421 | ||
422 | # if RENAME_DEST_EXISTS_BUG | |
423 | /* Cygwin 1.5 sometimes behaves oddly when moving a non-empty | |
424 | directory on top of an empty one (the old directory name can | |
425 | reappear if the new directory tree is removed). Work around this | |
426 | by removing the target first, but don't remove the target if it | |
427 | is a subdirectory of the source. Note that we can end up losing | |
428 | a directory if rename then fails, but it was empty, so not much | |
429 | damage was done. */ | |
430 | if (dst_exists && S_ISDIR (dst_st.st_mode)) | |
431 | { | |
432 | if (src_st.st_dev != dst_st.st_dev) | |
433 | { | |
434 | rename_errno = EXDEV; | |
435 | goto out; | |
436 | } | |
437 | if (src_temp != src) | |
438 | free (src_temp); | |
439 | src_temp = canonicalize_file_name (src); | |
440 | if (dst_temp != dst) | |
441 | free (dst_temp); | |
442 | dst_temp = canonicalize_file_name (dst); | |
443 | if (!src_temp || !dst_temp) | |
444 | { | |
445 | rename_errno = ENOMEM; | |
446 | goto out; | |
447 | } | |
448 | src_len = strlen (src_temp); | |
449 | if (strncmp (src_temp, dst_temp, src_len) == 0 | |
450 | && dst_temp[src_len] == '/') | |
451 | { | |
452 | rename_errno = EINVAL; | |
453 | goto out; | |
454 | } | |
455 | if (rmdir (dst)) | |
456 | { | |
457 | rename_errno = errno; | |
458 | goto out; | |
459 | } | |
460 | } | |
461 | # endif /* RENAME_DEST_EXISTS_BUG */ | |
462 | ||
463 | ret_val = rename (src_temp, dst_temp); | |
464 | rename_errno = errno; | |
4a626d0a PA |
465 | |
466 | out: _GL_UNUSED_LABEL; | |
467 | ||
6a29c58e YQ |
468 | if (src_temp != src) |
469 | free (src_temp); | |
470 | if (dst_temp != dst) | |
471 | free (dst_temp); | |
472 | errno = rename_errno; | |
473 | return ret_val; | |
474 | } | |
475 | #endif /* ! W32 platform */ |