rename: fix Solaris 10 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_SOURCE_BUG || RENAME_TRAILING_SLASH_DEST_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 RENAME_TRAILING_SLASH_SOURCE_BUG
196   /* If the only bug was that a trailing slash was allowed on a
197      non-existing file destination, as in Solaris 10, then we've
198      already covered that situation.  But if there is any problem with
199      a trailing slash on an existing source or destination, as in
200      Solaris 9, then we must strip the offending slash and check that
201      we have not encountered a symlink instead of a directory.
202
203      Stripping a trailing slash interferes with POSIX semantics, where
204      rename behavior on a symlink with a trailing slash operates on
205      the corresponding target directory.  We prefer the GNU semantics
206      of rejecting any use of a symlink with trailing slash, but do not
207      enforce them, since Solaris 10 is able to obey POSIX semantics
208      and there might be clients expecting it, as counter-intuitive as
209      those semantics are.
210
211      Technically, we could also follow the POSIX behavior by chasing a
212      readlink trail, but that is harder to implement.  */
213   if (src_slash)
214     {
215       src_temp = strdup (src);
216       if (!src_temp)
217         {
218           /* Rather than rely on strdup-posix, we set errno ourselves.  */
219           rename_errno = ENOMEM;
220           goto out;
221         }
222       strip_trailing_slashes (src_temp);
223       if (lstat (src_temp, &src_st))
224         {
225           rename_errno = errno;
226           goto out;
227         }
228       if (S_ISLNK (src_st.st_mode))
229         goto out;
230     }
231   if (dst_slash)
232     {
233       dst_temp = strdup (dst);
234       if (!dst_temp)
235         {
236           rename_errno = ENOMEM;
237           goto out;
238         }
239       strip_trailing_slashes (dst_temp);
240       if (lstat (dst_temp, &dst_st))
241         {
242           if (errno != ENOENT)
243             {
244               rename_errno = errno;
245               goto out;
246             }
247         }
248       else if (S_ISLNK (dst_st.st_mode))
249         goto out;
250     }
251 #  endif /* RENAME_TRAILING_SLASH_SOURCE_BUG */
252
253   ret_val = rename (src_temp, dst_temp);
254   rename_errno = errno;
255  out:
256   if (src_temp != src)
257     free (src_temp);
258   if (dst_temp != dst)
259     free (dst_temp);
260   errno = rename_errno;
261   return ret_val;
262 }
263 # endif /* RENAME_TRAILING_SLASH_*_BUG */
264 #endif /* ! W32 platform */