calloc-gnu: Fix bug on 32-bit AIX (regression 2024-11-04).
[gnulib.git] / lib / utime.c
blob3535764a2ff3b7a8f83740ecfb3a16f2b1597e7f
1 /* Work around platform bugs in utime.
2 Copyright (C) 2017-2024 Free Software Foundation, Inc.
4 This file is free software: you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation, either version 3 of the
7 License, or (at your option) any later version.
9 This file 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 Lesser General Public License for more details.
14 You should have received a copy of the GNU Lesser General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17 /* Written by Bruno Haible. */
19 #include <config.h>
21 /* Specification. */
22 #include <utime.h>
24 #if defined _WIN32 && ! defined __CYGWIN__
26 # include <errno.h>
27 # include <windows.h>
28 # include "filename.h"
29 # include "malloca.h"
31 /* Don't assume that UNICODE is not defined. */
32 # undef CreateFile
33 # define CreateFile CreateFileA
34 # undef GetFileAttributes
35 # define GetFileAttributes GetFileAttributesA
37 int
38 _gl_utimens_windows (const char *name, struct timespec ts[2])
40 /* POSIX <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>
41 specifies: "More than two leading <slash> characters shall be treated as
42 a single <slash> character." */
43 if (ISSLASH (name[0]) && ISSLASH (name[1]) && ISSLASH (name[2]))
45 name += 2;
46 while (ISSLASH (name[1]))
47 name++;
50 size_t len = strlen (name);
51 size_t drive_prefix_len = (HAS_DEVICE (name) ? 2 : 0);
53 /* Remove trailing slashes (except the very first one, at position
54 drive_prefix_len), but remember their presence. */
55 size_t rlen;
56 bool check_dir = false;
58 rlen = len;
59 while (rlen > drive_prefix_len && ISSLASH (name[rlen-1]))
61 check_dir = true;
62 if (rlen == drive_prefix_len + 1)
63 break;
64 rlen--;
67 const char *rname;
68 char *malloca_rname;
69 if (rlen == len)
71 rname = name;
72 malloca_rname = NULL;
74 else
76 malloca_rname = malloca (rlen + 1);
77 if (malloca_rname == NULL)
79 errno = ENOMEM;
80 return -1;
82 memcpy (malloca_rname, name, rlen);
83 malloca_rname[rlen] = '\0';
84 rname = malloca_rname;
87 DWORD error;
89 /* Open a handle to the file.
90 CreateFile
91 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createfilea>
92 <https://docs.microsoft.com/en-us/windows/desktop/FileIO/creating-and-opening-files> */
93 HANDLE handle =
94 CreateFile (rname,
95 FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
96 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
97 NULL,
98 OPEN_EXISTING,
99 /* FILE_FLAG_POSIX_SEMANTICS (treat file names that differ only
100 in case as different) makes sense only when applied to *all*
101 filesystem operations. */
102 FILE_FLAG_BACKUP_SEMANTICS /* | FILE_FLAG_POSIX_SEMANTICS */,
103 NULL);
104 if (handle == INVALID_HANDLE_VALUE)
106 error = GetLastError ();
107 goto failed;
110 if (check_dir)
112 /* GetFileAttributes
113 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileattributesa> */
114 DWORD attributes = GetFileAttributes (rname);
115 if (attributes == INVALID_FILE_ATTRIBUTES)
117 error = GetLastError ();
118 CloseHandle (handle);
119 goto failed;
121 if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
123 CloseHandle (handle);
124 if (malloca_rname != NULL)
125 freea (malloca_rname);
126 errno = ENOTDIR;
127 return -1;
132 /* Use SetFileTime(). See
133 <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfiletime>
134 <https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-filetime> */
135 FILETIME last_access_time;
136 FILETIME last_write_time;
137 if (ts == NULL)
139 /* GetSystemTimeAsFileTime is the same as
140 GetSystemTime followed by SystemTimeToFileTime.
141 <https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getsystemtimeasfiletime>.
142 It would be overkill to use
143 GetSystemTimePreciseAsFileTime
144 <https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getsystemtimepreciseasfiletime>. */
145 FILETIME current_time;
146 GetSystemTimeAsFileTime (&current_time);
147 last_access_time = current_time;
148 last_write_time = current_time;
150 else
153 ULONGLONG time_since_16010101 =
154 (ULONGLONG) ts[0].tv_sec * 10000000 + ts[0].tv_nsec / 100 + 116444736000000000LL;
155 last_access_time.dwLowDateTime = (DWORD) time_since_16010101;
156 last_access_time.dwHighDateTime = time_since_16010101 >> 32;
159 ULONGLONG time_since_16010101 =
160 (ULONGLONG) ts[1].tv_sec * 10000000 + ts[1].tv_nsec / 100 + 116444736000000000LL;
161 last_write_time.dwLowDateTime = (DWORD) time_since_16010101;
162 last_write_time.dwHighDateTime = time_since_16010101 >> 32;
165 if (SetFileTime (handle, NULL, &last_access_time, &last_write_time))
167 CloseHandle (handle);
168 if (malloca_rname != NULL)
169 freea (malloca_rname);
170 return 0;
172 else
174 #if 0
175 DWORD sft_error = GetLastError ();
176 fprintf (stderr, "utimens SetFileTime error 0x%x\n", (unsigned int) sft_error);
177 #endif
178 CloseHandle (handle);
179 if (malloca_rname != NULL)
180 freea (malloca_rname);
181 errno = EINVAL;
182 return -1;
186 failed:
188 #if 0
189 fprintf (stderr, "utimens CreateFile/GetFileAttributes error 0x%x\n", (unsigned int) error);
190 #endif
191 if (malloca_rname != NULL)
192 freea (malloca_rname);
194 switch (error)
196 /* Some of these errors probably cannot happen with the specific flags
197 that we pass to CreateFile. But who knows... */
198 case ERROR_FILE_NOT_FOUND: /* The last component of rname does not exist. */
199 case ERROR_PATH_NOT_FOUND: /* Some directory component in rname does not exist. */
200 case ERROR_BAD_PATHNAME: /* rname is such as '\\server'. */
201 case ERROR_BAD_NETPATH: /* rname is such as '\\nonexistentserver\share'. */
202 case ERROR_BAD_NET_NAME: /* rname is such as '\\server\nonexistentshare'. */
203 case ERROR_INVALID_NAME: /* rname contains wildcards, misplaced colon, etc. */
204 case ERROR_DIRECTORY:
205 errno = ENOENT;
206 break;
208 case ERROR_ACCESS_DENIED: /* rname is such as 'C:\System Volume Information\foo'. */
209 case ERROR_SHARING_VIOLATION: /* rname is such as 'C:\pagefile.sys'. */
210 errno = (ts != NULL ? EPERM : EACCES);
211 break;
213 case ERROR_OUTOFMEMORY:
214 errno = ENOMEM;
215 break;
217 case ERROR_WRITE_PROTECT:
218 errno = EROFS;
219 break;
221 case ERROR_WRITE_FAULT:
222 case ERROR_READ_FAULT:
223 case ERROR_GEN_FAILURE:
224 errno = EIO;
225 break;
227 case ERROR_BUFFER_OVERFLOW:
228 case ERROR_FILENAME_EXCED_RANGE:
229 errno = ENAMETOOLONG;
230 break;
232 case ERROR_DELETE_PENDING: /* XXX map to EACCES or EPERM? */
233 errno = EPERM;
234 break;
236 default:
237 errno = EINVAL;
238 break;
241 return -1;
246 utime (const char *name, const struct utimbuf *ts)
248 if (ts == NULL)
249 return _gl_utimens_windows (name, NULL);
250 else
252 struct timespec ts_with_nanoseconds[2];
253 ts_with_nanoseconds[0].tv_sec = ts->actime;
254 ts_with_nanoseconds[0].tv_nsec = 0;
255 ts_with_nanoseconds[1].tv_sec = ts->modtime;
256 ts_with_nanoseconds[1].tv_nsec = 0;
257 return _gl_utimens_windows (name, ts_with_nanoseconds);
261 #else
263 # include <errno.h>
264 # include <sys/stat.h>
265 # include "filename.h"
268 utime (const char *name, const struct utimbuf *ts)
269 #undef utime
271 # if REPLACE_FUNC_UTIME_FILE
272 /* macOS 10.13 mistakenly succeeds when given a symbolic link to a
273 non-directory with a trailing slash. */
274 size_t len = strlen (name);
275 if (len > 0 && ISSLASH (name[len - 1]))
277 struct stat buf;
279 if (stat (name, &buf) == -1 && errno != EOVERFLOW)
280 return -1;
282 # endif /* REPLACE_FUNC_UTIME_FILE */
284 return utime (name, ts);
287 #endif