1 /* Work around platform bugs in utime.
2 Copyright (C) 2017-2021 Free Software Foundation, Inc.
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 3 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
16
17 /* Written by Bruno Haible. */
18
19 #include <config.h>
20
21 /* Specification. */
22 #include <utime.h>
23
24 #if defined _WIN32 && ! defined __CYGWIN__
25
26 # include <errno.h>
27 # include <stdbool.h>
28 # include <windows.h>
29 # include "filename.h"
30 # include "malloca.h"
31
32 /* Don't assume that UNICODE is not defined. */
33 # undef CreateFile
34 # define CreateFile CreateFileA
35 # undef GetFileAttributes
36 # define GetFileAttributes GetFileAttributesA
37
38 int
_gl_utimens_windows(const char * name,struct timespec ts[2])39 _gl_utimens_windows (const char *name, struct timespec ts[2])
40 {
41 /* POSIX <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>
42 specifies: "More than two leading <slash> characters shall be treated as
43 a single <slash> character." */
44 if (ISSLASH (name[0]) && ISSLASH (name[1]) && ISSLASH (name[2]))
45 {
46 name += 2;
47 while (ISSLASH (name[1]))
48 name++;
49 }
50
51 size_t len = strlen (name);
52 size_t drive_prefix_len = (HAS_DEVICE (name) ? 2 : 0);
53
54 /* Remove trailing slashes (except the very first one, at position
55 drive_prefix_len), but remember their presence. */
56 size_t rlen;
57 bool check_dir = false;
58
59 rlen = len;
60 while (rlen > drive_prefix_len && ISSLASH (name[rlen-1]))
61 {
62 check_dir = true;
63 if (rlen == drive_prefix_len + 1)
64 break;
65 rlen--;
66 }
67
68 const char *rname;
69 char *malloca_rname;
70 if (rlen == len)
71 {
72 rname = name;
73 malloca_rname = NULL;
74 }
75 else
76 {
77 malloca_rname = malloca (rlen + 1);
78 if (malloca_rname == NULL)
79 {
80 errno = ENOMEM;
81 return -1;
82 }
83 memcpy (malloca_rname, name, rlen);
84 malloca_rname[rlen] = '\0';
85 rname = malloca_rname;
86 }
87
88 DWORD error;
89
90 /* Open a handle to the file.
91 CreateFile
92 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createfilea>
93 <https://docs.microsoft.com/en-us/windows/desktop/FileIO/creating-and-opening-files> */
94 HANDLE handle =
95 CreateFile (rname,
96 FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
97 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
98 NULL,
99 OPEN_EXISTING,
100 /* FILE_FLAG_POSIX_SEMANTICS (treat file names that differ only
101 in case as different) makes sense only when applied to *all*
102 filesystem operations. */
103 FILE_FLAG_BACKUP_SEMANTICS /* | FILE_FLAG_POSIX_SEMANTICS */,
104 NULL);
105 if (handle == INVALID_HANDLE_VALUE)
106 {
107 error = GetLastError ();
108 goto failed;
109 }
110
111 if (check_dir)
112 {
113 /* GetFileAttributes
114 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileattributesa> */
115 DWORD attributes = GetFileAttributes (rname);
116 if (attributes == INVALID_FILE_ATTRIBUTES)
117 {
118 error = GetLastError ();
119 CloseHandle (handle);
120 goto failed;
121 }
122 if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
123 {
124 CloseHandle (handle);
125 if (malloca_rname != NULL)
126 freea (malloca_rname);
127 errno = ENOTDIR;
128 return -1;
129 }
130 }
131
132 {
133 /* Use SetFileTime(). See
134 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfiletime>
135 <https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-filetime> */
136 FILETIME last_access_time;
137 FILETIME last_write_time;
138 if (ts == NULL)
139 {
140 /* GetSystemTimeAsFileTime is the same as
141 GetSystemTime followed by SystemTimeToFileTime.
142 <https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getsystemtimeasfiletime>.
143 It would be overkill to use
144 GetSystemTimePreciseAsFileTime
145 <https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getsystemtimepreciseasfiletime>. */
146 FILETIME current_time;
147 GetSystemTimeAsFileTime (¤t_time);
148 last_access_time = current_time;
149 last_write_time = current_time;
150 }
151 else
152 {
153 {
154 ULONGLONG time_since_16010101 =
155 (ULONGLONG) ts[0].tv_sec * 10000000 + ts[0].tv_nsec / 100 + 116444736000000000LL;
156 last_access_time.dwLowDateTime = (DWORD) time_since_16010101;
157 last_access_time.dwHighDateTime = time_since_16010101 >> 32;
158 }
159 {
160 ULONGLONG time_since_16010101 =
161 (ULONGLONG) ts[1].tv_sec * 10000000 + ts[1].tv_nsec / 100 + 116444736000000000LL;
162 last_write_time.dwLowDateTime = (DWORD) time_since_16010101;
163 last_write_time.dwHighDateTime = time_since_16010101 >> 32;
164 }
165 }
166 if (SetFileTime (handle, NULL, &last_access_time, &last_write_time))
167 {
168 CloseHandle (handle);
169 if (malloca_rname != NULL)
170 freea (malloca_rname);
171 return 0;
172 }
173 else
174 {
175 #if 0
176 DWORD sft_error = GetLastError ();
177 fprintf (stderr, "utimens SetFileTime error 0x%x\n", (unsigned int) sft_error);
178 #endif
179 CloseHandle (handle);
180 if (malloca_rname != NULL)
181 freea (malloca_rname);
182 errno = EINVAL;
183 return -1;
184 }
185 }
186
187 failed:
188 {
189 #if 0
190 fprintf (stderr, "utimens CreateFile/GetFileAttributes error 0x%x\n", (unsigned int) error);
191 #endif
192 if (malloca_rname != NULL)
193 freea (malloca_rname);
194
195 switch (error)
196 {
197 /* Some of these errors probably cannot happen with the specific flags
198 that we pass to CreateFile. But who knows... */
199 case ERROR_FILE_NOT_FOUND: /* The last component of rname does not exist. */
200 case ERROR_PATH_NOT_FOUND: /* Some directory component in rname does not exist. */
201 case ERROR_BAD_PATHNAME: /* rname is such as '\\server'. */
202 case ERROR_BAD_NETPATH: /* rname is such as '\\nonexistentserver\share'. */
203 case ERROR_BAD_NET_NAME: /* rname is such as '\\server\nonexistentshare'. */
204 case ERROR_INVALID_NAME: /* rname contains wildcards, misplaced colon, etc. */
205 case ERROR_DIRECTORY:
206 errno = ENOENT;
207 break;
208
209 case ERROR_ACCESS_DENIED: /* rname is such as 'C:\System Volume Information\foo'. */
210 case ERROR_SHARING_VIOLATION: /* rname is such as 'C:\pagefile.sys'. */
211 errno = (ts != NULL ? EPERM : EACCES);
212 break;
213
214 case ERROR_OUTOFMEMORY:
215 errno = ENOMEM;
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_BUFFER_OVERFLOW:
229 case ERROR_FILENAME_EXCED_RANGE:
230 errno = ENAMETOOLONG;
231 break;
232
233 case ERROR_DELETE_PENDING: /* XXX map to EACCES or EPERM? */
234 errno = EPERM;
235 break;
236
237 default:
238 errno = EINVAL;
239 break;
240 }
241
242 return -1;
243 }
244 }
245
246 int
utime(const char * name,const struct utimbuf * ts)247 utime (const char *name, const struct utimbuf *ts)
248 {
249 if (ts == NULL)
250 return _gl_utimens_windows (name, NULL);
251 else
252 {
253 struct timespec ts_with_nanoseconds[2];
254 ts_with_nanoseconds[0].tv_sec = ts->actime;
255 ts_with_nanoseconds[0].tv_nsec = 0;
256 ts_with_nanoseconds[1].tv_sec = ts->modtime;
257 ts_with_nanoseconds[1].tv_nsec = 0;
258 return _gl_utimens_windows (name, ts_with_nanoseconds);
259 }
260 }
261
262 #else
263
264 # include <errno.h>
265 # include <sys/stat.h>
266 # include "filename.h"
267
268 int
utime(const char * name,const struct utimbuf * ts)269 utime (const char *name, const struct utimbuf *ts)
270 #undef utime
271 {
272 # if REPLACE_FUNC_UTIME_FILE
273 /* macOS 10.13 mistakenly succeeds when given a symbolic link to a
274 non-directory with a trailing slash. */
275 size_t len = strlen (name);
276 if (len > 0 && ISSLASH (name[len - 1]))
277 {
278 struct stat buf;
279
280 if (stat (name, &buf) == -1 && errno != EOVERFLOW)
281 return -1;
282 }
283 # endif /* REPLACE_FUNC_UTIME_FILE */
284
285 return utime (name, ts);
286 }
287
288 #endif
289