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