New upstream version 1.9
[debian/gzip] / lib / utime.c
diff --git a/lib/utime.c b/lib/utime.c
new file mode 100644 (file)
index 0000000..80291d3
--- /dev/null
@@ -0,0 +1,256 @@
+/* Work around platform bugs in utime.
+   Copyright (C) 2017-2018 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 __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 <http://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://msdn.microsoft.com/en-us/library/aa363858.aspx>
+     <https://msdn.microsoft.com/en-us/library/aa363874.aspx>  */
+  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://msdn.microsoft.com/en-us/library/aa364944.aspx>  */
+      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://msdn.microsoft.com/en-us/library/ms724933.aspx>
+       <https://msdn.microsoft.com/en-us/library/ms724284.aspx>  */
+    FILETIME last_access_time;
+    FILETIME last_write_time;
+    if (ts == NULL)
+      {
+        /* GetSystemTimeAsFileTime is the same as
+           GetSystemTime followed by SystemTimeToFileTime.
+           <https://msdn.microsoft.com/en-us/library/ms724397.aspx>.
+           It would be overkill to use
+           GetSystemTimePreciseAsFileTime
+           <https://msdn.microsoft.com/en-us/library/hh706895.aspx>.  */
+        FILETIME current_time;
+        GetSystemTimeAsFileTime (&current_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 EACCESS 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