1 /* Work around platform bugs in utime.
2 Copyright (C) 2017-2018 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 int
_gl_utimens_windows(const char * name,struct timespec ts[2])33 _gl_utimens_windows (const char *name, struct timespec ts[2])
34 {
35 /* POSIX <http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>
36 specifies: "More than two leading <slash> characters shall be treated as
37 a single <slash> character." */
38 if (ISSLASH (name[0]) && ISSLASH (name[1]) && ISSLASH (name[2]))
39 {
40 name += 2;
41 while (ISSLASH (name[1]))
42 name++;
43 }
44
45 size_t len = strlen (name);
46 size_t drive_prefix_len = (HAS_DEVICE (name) ? 2 : 0);
47
48 /* Remove trailing slashes (except the very first one, at position
49 drive_prefix_len), but remember their presence. */
50 size_t rlen;
51 bool check_dir = false;
52
53 rlen = len;
54 while (rlen > drive_prefix_len && ISSLASH (name[rlen-1]))
55 {
56 check_dir = true;
57 if (rlen == drive_prefix_len + 1)
58 break;
59 rlen--;
60 }
61
62 const char *rname;
63 char *malloca_rname;
64 if (rlen == len)
65 {
66 rname = name;
67 malloca_rname = NULL;
68 }
69 else
70 {
71 malloca_rname = malloca (rlen + 1);
72 if (malloca_rname == NULL)
73 {
74 errno = ENOMEM;
75 return -1;
76 }
77 memcpy (malloca_rname, name, rlen);
78 malloca_rname[rlen] = '\0';
79 rname = malloca_rname;
80 }
81
82 DWORD error;
83
84 /* Open a handle to the file.
85 CreateFile
86 <https://msdn.microsoft.com/en-us/library/aa363858.aspx>
87 <https://msdn.microsoft.com/en-us/library/aa363874.aspx> */
88 HANDLE handle =
89 CreateFile (rname,
90 FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
91 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
92 NULL,
93 OPEN_EXISTING,
94 /* FILE_FLAG_POSIX_SEMANTICS (treat file names that differ only
95 in case as different) makes sense only when applied to *all*
96 filesystem operations. */
97 FILE_FLAG_BACKUP_SEMANTICS /* | FILE_FLAG_POSIX_SEMANTICS */,
98 NULL);
99 if (handle == INVALID_HANDLE_VALUE)
100 {
101 error = GetLastError ();
102 goto failed;
103 }
104
105 if (check_dir)
106 {
107 /* GetFileAttributes
108 <https://msdn.microsoft.com/en-us/library/aa364944.aspx> */
109 DWORD attributes = GetFileAttributes (rname);
110 if (attributes == INVALID_FILE_ATTRIBUTES)
111 {
112 error = GetLastError ();
113 CloseHandle (handle);
114 goto failed;
115 }
116 if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
117 {
118 CloseHandle (handle);
119 if (malloca_rname != NULL)
120 freea (malloca_rname);
121 errno = ENOTDIR;
122 return -1;
123 }
124 }
125
126 {
127 /* Use SetFileTime(). See
128 <https://msdn.microsoft.com/en-us/library/ms724933.aspx>
129 <https://msdn.microsoft.com/en-us/library/ms724284.aspx> */
130 FILETIME last_access_time;
131 FILETIME last_write_time;
132 if (ts == NULL)
133 {
134 /* GetSystemTimeAsFileTime is the same as
135 GetSystemTime followed by SystemTimeToFileTime.
136 <https://msdn.microsoft.com/en-us/library/ms724397.aspx>.
137 It would be overkill to use
138 GetSystemTimePreciseAsFileTime
139 <https://msdn.microsoft.com/en-us/library/hh706895.aspx>. */
140 FILETIME current_time;
141 GetSystemTimeAsFileTime (¤t_time);
142 last_access_time = current_time;
143 last_write_time = current_time;
144 }
145 else
146 {
147 {
148 ULONGLONG time_since_16010101 =
149 (ULONGLONG) ts[0].tv_sec * 10000000 + ts[0].tv_nsec / 100 + 116444736000000000LL;
150 last_access_time.dwLowDateTime = (DWORD) time_since_16010101;
151 last_access_time.dwHighDateTime = time_since_16010101 >> 32;
152 }
153 {
154 ULONGLONG time_since_16010101 =
155 (ULONGLONG) ts[1].tv_sec * 10000000 + ts[1].tv_nsec / 100 + 116444736000000000LL;
156 last_write_time.dwLowDateTime = (DWORD) time_since_16010101;
157 last_write_time.dwHighDateTime = time_since_16010101 >> 32;
158 }
159 }
160 if (SetFileTime (handle, NULL, &last_access_time, &last_write_time))
161 {
162 CloseHandle (handle);
163 if (malloca_rname != NULL)
164 freea (malloca_rname);
165 return 0;
166 }
167 else
168 {
169 #if 0
170 DWORD sft_error = GetLastError ();
171 fprintf (stderr, "utimens SetFileTime error 0x%x\n", (unsigned int) sft_error);
172 #endif
173 CloseHandle (handle);
174 if (malloca_rname != NULL)
175 freea (malloca_rname);
176 errno = EINVAL;
177 return -1;
178 }
179 }
180
181 failed:
182 {
183 #if 0
184 fprintf (stderr, "utimens CreateFile/GetFileAttributes error 0x%x\n", (unsigned int) error);
185 #endif
186 if (malloca_rname != NULL)
187 freea (malloca_rname);
188
189 switch (error)
190 {
191 /* Some of these errors probably cannot happen with the specific flags
192 that we pass to CreateFile. But who knows... */
193 case ERROR_FILE_NOT_FOUND: /* The last component of rname does not exist. */
194 case ERROR_PATH_NOT_FOUND: /* Some directory component in rname does not exist. */
195 case ERROR_BAD_PATHNAME: /* rname is such as '\\server'. */
196 case ERROR_BAD_NETPATH: /* rname is such as '\\nonexistentserver\share'. */
197 case ERROR_BAD_NET_NAME: /* rname is such as '\\server\nonexistentshare'. */
198 case ERROR_INVALID_NAME: /* rname contains wildcards, misplaced colon, etc. */
199 case ERROR_DIRECTORY:
200 errno = ENOENT;
201 break;
202
203 case ERROR_ACCESS_DENIED: /* rname is such as 'C:\System Volume Information\foo'. */
204 case ERROR_SHARING_VIOLATION: /* rname is such as 'C:\pagefile.sys'. */
205 errno = (ts != NULL ? EPERM : EACCES);
206 break;
207
208 case ERROR_OUTOFMEMORY:
209 errno = ENOMEM;
210 break;
211
212 case ERROR_WRITE_PROTECT:
213 errno = EROFS;
214 break;
215
216 case ERROR_WRITE_FAULT:
217 case ERROR_READ_FAULT:
218 case ERROR_GEN_FAILURE:
219 errno = EIO;
220 break;
221
222 case ERROR_BUFFER_OVERFLOW:
223 case ERROR_FILENAME_EXCED_RANGE:
224 errno = ENAMETOOLONG;
225 break;
226
227 case ERROR_DELETE_PENDING: /* XXX map to EACCESS or EPERM? */
228 errno = EPERM;
229 break;
230
231 default:
232 errno = EINVAL;
233 break;
234 }
235
236 return -1;
237 }
238 }
239
240 int
utime(const char * name,const struct utimbuf * ts)241 utime (const char *name, const struct utimbuf *ts)
242 {
243 if (ts == NULL)
244 return _gl_utimens_windows (name, NULL);
245 else
246 {
247 struct timespec ts_with_nanoseconds[2];
248 ts_with_nanoseconds[0].tv_sec = ts->actime;
249 ts_with_nanoseconds[0].tv_nsec = 0;
250 ts_with_nanoseconds[1].tv_sec = ts->modtime;
251 ts_with_nanoseconds[1].tv_nsec = 0;
252 return _gl_utimens_windows (name, ts_with_nanoseconds);
253 }
254 }
255
256 #endif
257