rename: fix Solaris 9 bug
[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 # if RENAME_DEST_EXISTS_BUG
138 #  error Please report your platform and this message to bug-gnulib@gnu.org.
139 # elif RENAME_TRAILING_SLASH_BUG
140
141 #  include <errno.h>
142 #  include <stdio.h>
143 #  include <stdlib.h>
144 #  include <string.h>
145 #  include <sys/stat.h>
146
147 #  include "dirname.h"
148
149 /* Rename the file SRC to DST, fixing any trailing slash bugs.  */
150
151 int
152 rpl_rename (char const *src, char const *dst)
153 {
154   size_t src_len = strlen (src);
155   size_t dst_len = strlen (dst);
156   char *src_temp = (char *) src;
157   char *dst_temp = (char *) dst;
158   bool src_slash;
159   bool dst_slash;
160   int ret_val = -1;
161   int rename_errno = ENOTDIR;
162   struct stat src_st;
163   struct stat dst_st;
164
165   if (!src_len || !dst_len)
166     return rename (src, dst); /* Let strace see the ENOENT failure.  */
167
168   src_slash = src[src_len - 1] == '/';
169   dst_slash = dst[dst_len - 1] == '/';
170   if (!src_slash && !dst_slash)
171     return rename (src, dst);
172
173   /* Presence of a trailing slash requires directory semantics.  If
174      the source does not exist, or if the destination cannot be turned
175      into a directory, give up now.  Otherwise, strip trailing slashes
176      before calling rename.  */
177   if (lstat (src, &src_st))
178     return -1;
179   if (lstat (dst, &dst_st))
180     {
181       if (errno != ENOENT || !S_ISDIR (src_st.st_mode))
182         return -1;
183     }
184   else if (!S_ISDIR (dst_st.st_mode))
185     {
186       errno = ENOTDIR;
187       return -1;
188     }
189   else if (!S_ISDIR (src_st.st_mode))
190     {
191       errno = EISDIR;
192       return -1;
193     }
194
195   /* If stripping the trailing slashes changes from a directory to a
196      symlink, follow the GNU behavior of rejecting the rename.
197      Technically, we could follow the POSIX behavior by chasing a
198      readlink trail, but that is counter-intuitive and harder.  */
199   if (src_slash)
200     {
201       src_temp = strdup (src);
202       if (!src_temp)
203         {
204           /* Rather than rely on strdup-posix, we set errno ourselves.  */
205           rename_errno = ENOMEM;
206           goto out;
207         }
208       strip_trailing_slashes (src_temp);
209       if (lstat (src_temp, &src_st))
210         {
211           rename_errno = errno;
212           goto out;
213         }
214       if (S_ISLNK (src_st.st_mode))
215         goto out;
216     }
217   if (dst_slash)
218     {
219       dst_temp = strdup (dst);
220       if (!dst_temp)
221         {
222           rename_errno = ENOMEM;
223           goto out;
224         }
225       strip_trailing_slashes (dst_temp);
226       if (lstat (dst_temp, &dst_st))
227         {
228           if (errno != ENOENT)
229             {
230               rename_errno = errno;
231               goto out;
232             }
233         }
234       else if (S_ISLNK (dst_st.st_mode))
235         goto out;
236     }
237   ret_val = rename (src_temp, dst_temp);
238   rename_errno = errno;
239  out:
240   if (src_temp != src)
241     free (src_temp);
242   if (dst_temp != dst)
243     free (dst_temp);
244   errno = rename_errno;
245   return ret_val;
246 }
247 # endif /* RENAME_TRAILING_SLASH_BUG */
248 #endif /* ! W32 platform */