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 (&current_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