rename: fix cygwin 1.5.x bugs
[gnulib.git] / lib / rename.c
1 /* Work around rename bugs in some systems.
2
3    Copyright (C) 2001, 2002, 2003, 2005, 2006, 2009 Free Software
4    Foundation, Inc.
5
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
18
19 /* Written by Volker Borchert, Eric Blake.  */
20
21 #include <config.h>
22
23 #include <stdio.h>
24
25 #undef rename
26
27 #if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
28 /* The mingw rename has problems with trailing slashes; it also
29    requires use of native Windows calls to allow atomic renames over
30    existing files.  */
31
32 # include <errno.h>
33
34 # define WIN32_LEAN_AND_MEAN
35 # include <windows.h>
36
37 /* Rename the file SRC to DST.  This replacement is necessary on
38    Windows, on which the system rename function will not replace
39    an existing DST.  */
40 int
41 rpl_rename (char const *src, char const *dst)
42 {
43   int error;
44
45   /* MoveFileEx works if SRC is a directory without any flags,
46      but fails with MOVEFILE_REPLACE_EXISTING, so try without
47      flags first. */
48   if (MoveFileEx (src, dst, 0))
49     return 0;
50
51   /* Retry with MOVEFILE_REPLACE_EXISTING if the move failed
52    * due to the destination already existing. */
53   error = GetLastError ();
54   if (error == ERROR_FILE_EXISTS || error == ERROR_ALREADY_EXISTS)
55     {
56       if (MoveFileEx (src, dst, MOVEFILE_REPLACE_EXISTING))
57         return 0;
58
59       error = GetLastError ();
60     }
61
62   switch (error)
63     {
64     case ERROR_FILE_NOT_FOUND:
65     case ERROR_PATH_NOT_FOUND:
66     case ERROR_BAD_PATHNAME:
67     case ERROR_DIRECTORY:
68       errno = ENOENT;
69       break;
70
71     case ERROR_ACCESS_DENIED:
72     case ERROR_SHARING_VIOLATION:
73       errno = EACCES;
74       break;
75
76     case ERROR_OUTOFMEMORY:
77       errno = ENOMEM;
78       break;
79
80     case ERROR_CURRENT_DIRECTORY:
81       errno = EBUSY;
82       break;
83
84     case ERROR_NOT_SAME_DEVICE:
85       errno = EXDEV;
86       break;
87
88     case ERROR_WRITE_PROTECT:
89       errno = EROFS;
90       break;
91
92     case ERROR_WRITE_FAULT:
93     case ERROR_READ_FAULT:
94     case ERROR_GEN_FAILURE:
95       errno = EIO;
96       break;
97
98     case ERROR_HANDLE_DISK_FULL:
99     case ERROR_DISK_FULL:
100     case ERROR_DISK_TOO_FRAGMENTED:
101       errno = ENOSPC;
102       break;
103
104     case ERROR_FILE_EXISTS:
105     case ERROR_ALREADY_EXISTS:
106       errno = EEXIST;
107       break;
108
109     case ERROR_BUFFER_OVERFLOW:
110     case ERROR_FILENAME_EXCED_RANGE:
111       errno = ENAMETOOLONG;
112       break;
113
114     case ERROR_INVALID_NAME:
115     case ERROR_DELETE_PENDING:
116       errno = EPERM;        /* ? */
117       break;
118
119 # ifndef ERROR_FILE_TOO_LARGE
120 /* This value is documented but not defined in all versions of windows.h. */
121 #  define ERROR_FILE_TOO_LARGE 223
122 # endif
123     case ERROR_FILE_TOO_LARGE:
124       errno = EFBIG;
125       break;
126
127     default:
128       errno = EINVAL;
129       break;
130     }
131
132   return -1;
133 }
134
135 #else /* ! W32 platform */
136
137 # include <errno.h>
138 # include <stdio.h>
139 # include <stdlib.h>
140 # include <string.h>
141 # include <sys/stat.h>
142
143 # include "dirname.h"
144 # include "same-inode.h"
145
146 /* Rename the file SRC to DST, fixing any trailing slash bugs.  */
147
148 int
149 rpl_rename (char const *src, char const *dst)
150 {
151   size_t src_len = strlen (src);
152   size_t dst_len = strlen (dst);
153   char *src_temp = (char *) src;
154   char *dst_temp = (char *) dst;
155   bool src_slash;
156   bool dst_slash;
157   int ret_val = -1;
158   int rename_errno = ENOTDIR;
159   struct stat src_st;
160   struct stat dst_st;
161
162   if (!src_len || !dst_len)
163     return rename (src, dst); /* Let strace see the ENOENT failure.  */
164
165 # if RENAME_DEST_EXISTS_BUG
166   {
167     char *src_base = last_component (src);
168     char *dst_base = last_component (dst);
169     if (*src_base == '.')
170       {
171         size_t len = base_len (src_base);
172         if (len == 1 || (len == 2 && src_base[1] == '.'))
173           {
174             errno = EINVAL;
175             return -1;
176           }
177       }
178     if (*dst_base == '.')
179       {
180         size_t len = base_len (dst_base);
181         if (len == 1 || (len == 2 && dst_base[1] == '.'))
182           {
183             errno = EINVAL;
184             return -1;
185           }
186       }
187   }
188 # endif /* RENAME_DEST_EXISTS_BUG */
189
190   src_slash = src[src_len - 1] == '/';
191   dst_slash = dst[dst_len - 1] == '/';
192
193 # if !RENAME_DEST_EXISTS_BUG
194   /* If there are no trailing slashes, then trust the native
195      implementation unless we also suspect issues with hard link
196      detection.  */
197   if (!src_slash && !dst_slash)
198     return rename (src, dst);
199 # endif /* !RENAME_DEST_EXISTS_BUG */
200
201   /* Presence of a trailing slash requires directory semantics.  If
202      the source does not exist, or if the destination cannot be turned
203      into a directory, give up now.  Otherwise, strip trailing slashes
204      before calling rename.  */
205   if (lstat (src, &src_st))
206     return -1;
207   if (lstat (dst, &dst_st))
208     {
209       if (errno != ENOENT || (!S_ISDIR (src_st.st_mode) && dst_slash))
210         return -1;
211     }
212   else
213     {
214       if (S_ISDIR (dst_st.st_mode) != S_ISDIR (src_st.st_mode))
215         {
216           errno = S_ISDIR (dst_st.st_mode) ? EISDIR : ENOTDIR;
217           return -1;
218         }
219 # if RENAME_DEST_EXISTS_BUG
220       if (SAME_INODE (src_st, dst_st))
221         return 0;
222 # endif /* RENAME_DEST_EXISTS_BUG */
223     }
224
225 # if RENAME_TRAILING_SLASH_SOURCE_BUG || RENAME_DEST_EXISTS_BUG
226   /* If the only bug was that a trailing slash was allowed on a
227      non-existing file destination, as in Solaris 10, then we've
228      already covered that situation.  But if there is any problem with
229      a trailing slash on an existing source or destination, as in
230      Solaris 9, or if a directory can overwrite a symlink, as on
231      Cygwin 1.5, then we must strip the offending slash and check that
232      we have not encountered a symlink instead of a directory.
233
234      Stripping a trailing slash interferes with POSIX semantics, where
235      rename behavior on a symlink with a trailing slash operates on
236      the corresponding target directory.  We prefer the GNU semantics
237      of rejecting any use of a symlink with trailing slash, but do not
238      enforce them, since Solaris 10 is able to obey POSIX semantics
239      and there might be clients expecting it, as counter-intuitive as
240      those semantics are.
241
242      Technically, we could also follow the POSIX behavior by chasing a
243      readlink trail, but that is harder to implement.  */
244   if (src_slash)
245     {
246       src_temp = strdup (src);
247       if (!src_temp)
248         {
249           /* Rather than rely on strdup-posix, we set errno ourselves.  */
250           rename_errno = ENOMEM;
251           goto out;
252         }
253       strip_trailing_slashes (src_temp);
254       if (lstat (src_temp, &src_st))
255         {
256           rename_errno = errno;
257           goto out;
258         }
259       if (S_ISLNK (src_st.st_mode))
260         goto out;
261     }
262   if (dst_slash)
263     {
264       dst_temp = strdup (dst);
265       if (!dst_temp)
266         {
267           rename_errno = ENOMEM;
268           goto out;
269         }
270       strip_trailing_slashes (dst_temp);
271       if (lstat (dst_temp, &dst_st))
272         {
273           if (errno != ENOENT)
274             {
275               rename_errno = errno;
276               goto out;
277             }
278         }
279       else if (S_ISLNK (dst_st.st_mode))
280         goto out;
281     }
282 # endif /* RENAME_TRAILING_SLASH_SOURCE_BUG || RENAME_DEST_EXISTS_BUG */
283
284   ret_val = rename (src_temp, dst_temp);
285   rename_errno = errno;
286  out:
287   if (src_temp != src)
288     free (src_temp);
289   if (dst_temp != dst)
290     free (dst_temp);
291   errno = rename_errno;
292   return ret_val;
293 }
294 #endif /* ! W32 platform */