| /* Work around platform bugs in utime. |
| Copyright (C) 2017-2020 Free Software Foundation, Inc. |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <https://www.gnu.org/licenses/>. */ |
| |
| /* Written by Bruno Haible. */ |
| |
| #include <config.h> |
| |
| /* Specification. */ |
| #include <utime.h> |
| |
| #if defined _WIN32 && ! defined __CYGWIN__ |
| |
| # include <errno.h> |
| # include <stdbool.h> |
| # include <windows.h> |
| # include "filename.h" |
| # include "malloca.h" |
| |
| int |
| _gl_utimens_windows (const char *name, struct timespec ts[2]) |
| { |
| /* POSIX <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13> |
| specifies: "More than two leading <slash> characters shall be treated as |
| a single <slash> character." */ |
| if (ISSLASH (name[0]) && ISSLASH (name[1]) && ISSLASH (name[2])) |
| { |
| name += 2; |
| while (ISSLASH (name[1])) |
| name++; |
| } |
| |
| size_t len = strlen (name); |
| size_t drive_prefix_len = (HAS_DEVICE (name) ? 2 : 0); |
| |
| /* Remove trailing slashes (except the very first one, at position |
| drive_prefix_len), but remember their presence. */ |
| size_t rlen; |
| bool check_dir = false; |
| |
| rlen = len; |
| while (rlen > drive_prefix_len && ISSLASH (name[rlen-1])) |
| { |
| check_dir = true; |
| if (rlen == drive_prefix_len + 1) |
| break; |
| rlen--; |
| } |
| |
| const char *rname; |
| char *malloca_rname; |
| if (rlen == len) |
| { |
| rname = name; |
| malloca_rname = NULL; |
| } |
| else |
| { |
| malloca_rname = malloca (rlen + 1); |
| if (malloca_rname == NULL) |
| { |
| errno = ENOMEM; |
| return -1; |
| } |
| memcpy (malloca_rname, name, rlen); |
| malloca_rname[rlen] = '\0'; |
| rname = malloca_rname; |
| } |
| |
| DWORD error; |
| |
| /* Open a handle to the file. |
| CreateFile |
| <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createfilea> |
| <https://docs.microsoft.com/en-us/windows/desktop/FileIO/creating-and-opening-files> */ |
| HANDLE handle = |
| CreateFile (rname, |
| FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, |
| FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
| NULL, |
| OPEN_EXISTING, |
| /* FILE_FLAG_POSIX_SEMANTICS (treat file names that differ only |
| in case as different) makes sense only when applied to *all* |
| filesystem operations. */ |
| FILE_FLAG_BACKUP_SEMANTICS /* | FILE_FLAG_POSIX_SEMANTICS */, |
| NULL); |
| if (handle == INVALID_HANDLE_VALUE) |
| { |
| error = GetLastError (); |
| goto failed; |
| } |
| |
| if (check_dir) |
| { |
| /* GetFileAttributes |
| <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfileattributesa> */ |
| DWORD attributes = GetFileAttributes (rname); |
| if (attributes == INVALID_FILE_ATTRIBUTES) |
| { |
| error = GetLastError (); |
| CloseHandle (handle); |
| goto failed; |
| } |
| if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) |
| { |
| CloseHandle (handle); |
| if (malloca_rname != NULL) |
| freea (malloca_rname); |
| errno = ENOTDIR; |
| return -1; |
| } |
| } |
| |
| { |
| /* Use SetFileTime(). See |
| <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfiletime> |
| <https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-filetime> */ |
| FILETIME last_access_time; |
| FILETIME last_write_time; |
| if (ts == NULL) |
| { |
| /* GetSystemTimeAsFileTime is the same as |
| GetSystemTime followed by SystemTimeToFileTime. |
| <https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getsystemtimeasfiletime>. |
| It would be overkill to use |
| GetSystemTimePreciseAsFileTime |
| <https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getsystemtimepreciseasfiletime>. */ |
| FILETIME current_time; |
| GetSystemTimeAsFileTime (¤t_time); |
| last_access_time = current_time; |
| last_write_time = current_time; |
| } |
| else |
| { |
| { |
| ULONGLONG time_since_16010101 = |
| (ULONGLONG) ts[0].tv_sec * 10000000 + ts[0].tv_nsec / 100 + 116444736000000000LL; |
| last_access_time.dwLowDateTime = (DWORD) time_since_16010101; |
| last_access_time.dwHighDateTime = time_since_16010101 >> 32; |
| } |
| { |
| ULONGLONG time_since_16010101 = |
| (ULONGLONG) ts[1].tv_sec * 10000000 + ts[1].tv_nsec / 100 + 116444736000000000LL; |
| last_write_time.dwLowDateTime = (DWORD) time_since_16010101; |
| last_write_time.dwHighDateTime = time_since_16010101 >> 32; |
| } |
| } |
| if (SetFileTime (handle, NULL, &last_access_time, &last_write_time)) |
| { |
| CloseHandle (handle); |
| if (malloca_rname != NULL) |
| freea (malloca_rname); |
| return 0; |
| } |
| else |
| { |
| #if 0 |
| DWORD sft_error = GetLastError (); |
| fprintf (stderr, "utimens SetFileTime error 0x%x\n", (unsigned int) sft_error); |
| #endif |
| CloseHandle (handle); |
| if (malloca_rname != NULL) |
| freea (malloca_rname); |
| errno = EINVAL; |
| return -1; |
| } |
| } |
| |
| failed: |
| { |
| #if 0 |
| fprintf (stderr, "utimens CreateFile/GetFileAttributes error 0x%x\n", (unsigned int) error); |
| #endif |
| if (malloca_rname != NULL) |
| freea (malloca_rname); |
| |
| switch (error) |
| { |
| /* Some of these errors probably cannot happen with the specific flags |
| that we pass to CreateFile. But who knows... */ |
| case ERROR_FILE_NOT_FOUND: /* The last component of rname does not exist. */ |
| case ERROR_PATH_NOT_FOUND: /* Some directory component in rname does not exist. */ |
| case ERROR_BAD_PATHNAME: /* rname is such as '\\server'. */ |
| case ERROR_BAD_NETPATH: /* rname is such as '\\nonexistentserver\share'. */ |
| case ERROR_BAD_NET_NAME: /* rname is such as '\\server\nonexistentshare'. */ |
| case ERROR_INVALID_NAME: /* rname contains wildcards, misplaced colon, etc. */ |
| case ERROR_DIRECTORY: |
| errno = ENOENT; |
| break; |
| |
| case ERROR_ACCESS_DENIED: /* rname is such as 'C:\System Volume Information\foo'. */ |
| case ERROR_SHARING_VIOLATION: /* rname is such as 'C:\pagefile.sys'. */ |
| errno = (ts != NULL ? EPERM : EACCES); |
| break; |
| |
| case ERROR_OUTOFMEMORY: |
| errno = ENOMEM; |
| break; |
| |
| case ERROR_WRITE_PROTECT: |
| errno = EROFS; |
| break; |
| |
| case ERROR_WRITE_FAULT: |
| case ERROR_READ_FAULT: |
| case ERROR_GEN_FAILURE: |
| errno = EIO; |
| break; |
| |
| case ERROR_BUFFER_OVERFLOW: |
| case ERROR_FILENAME_EXCED_RANGE: |
| errno = ENAMETOOLONG; |
| break; |
| |
| case ERROR_DELETE_PENDING: /* XXX map to EACCES or EPERM? */ |
| errno = EPERM; |
| break; |
| |
| default: |
| errno = EINVAL; |
| break; |
| } |
| |
| return -1; |
| } |
| } |
| |
| int |
| utime (const char *name, const struct utimbuf *ts) |
| { |
| if (ts == NULL) |
| return _gl_utimens_windows (name, NULL); |
| else |
| { |
| struct timespec ts_with_nanoseconds[2]; |
| ts_with_nanoseconds[0].tv_sec = ts->actime; |
| ts_with_nanoseconds[0].tv_nsec = 0; |
| ts_with_nanoseconds[1].tv_sec = ts->modtime; |
| ts_with_nanoseconds[1].tv_nsec = 0; |
| return _gl_utimens_windows (name, ts_with_nanoseconds); |
| } |
| } |
| |
| #endif |