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