Merge branch 'js/mingw-ns-filetime'
authorJunio C Hamano <gitster@pobox.com>
Tue, 6 Nov 2018 06:50:21 +0000 (15:50 +0900)
committerJunio C Hamano <gitster@pobox.com>
Tue, 6 Nov 2018 06:50:21 +0000 (15:50 +0900)
Windows port learned to use nano-second resolution file timestamps.

* js/mingw-ns-filetime:
mingw: implement nanosecond-precision file times
mingw: replace MSVCRT's fstat() with a Win32-based implementation
mingw: factor out code to set stat() data

1  2 
compat/mingw.c
compat/mingw.h
diff --combined compat/mingw.c
index 81ef24286a2757920e411b3858bcbdfd51d69a3b,26016d02e3fdd5e79b984cc600f9203f82c3f8d4..3b44dd99d79aed48a325b1e59b28d98d0c0790b9
@@@ -5,7 -5,6 +5,7 @@@
  #include "../strbuf.h"
  #include "../run-command.h"
  #include "../cache.h"
 +#include "win32/lazyload.h"
  
  #define HCAST(type, handle) ((type)(intptr_t)handle)
  
@@@ -203,31 -202,6 +203,31 @@@ static int ask_yes_no_if_possible(cons
        }
  }
  
 +/* Normalizes NT paths as returned by some low-level APIs. */
 +static wchar_t *normalize_ntpath(wchar_t *wbuf)
 +{
 +      int i;
 +      /* fix absolute path prefixes */
 +      if (wbuf[0] == '\\') {
 +              /* strip NT namespace prefixes */
 +              if (!wcsncmp(wbuf, L"\\??\\", 4) ||
 +                  !wcsncmp(wbuf, L"\\\\?\\", 4))
 +                      wbuf += 4;
 +              else if (!wcsnicmp(wbuf, L"\\DosDevices\\", 12))
 +                      wbuf += 12;
 +              /* replace remaining '...UNC\' with '\\' */
 +              if (!wcsnicmp(wbuf, L"UNC\\", 4)) {
 +                      wbuf += 2;
 +                      *wbuf = '\\';
 +              }
 +      }
 +      /* convert backslashes to slashes */
 +      for (i = 0; wbuf[i]; i++)
 +              if (wbuf[i] == '\\')
 +                      wbuf[i] = '/';
 +      return wbuf;
 +}
 +
  int mingw_unlink(const char *pathname)
  {
        int ret, tries = 0;
@@@ -618,9 -592,11 +618,11 @@@ static inline long long filetime_to_hns
        return winTime - 116444736000000000LL;
  }
  
- static inline time_t filetime_to_time_t(const FILETIME *ft)
+ static inline void filetime_to_timespec(const FILETIME *ft, struct timespec *ts)
  {
-       return (time_t)(filetime_to_hnsec(ft) / 10000000);
+       long long hnsec = filetime_to_hnsec(ft);
+       ts->tv_sec = (time_t)(hnsec / 10000000);
+       ts->tv_nsec = (hnsec % 10000000) * 100;
  }
  
  /**
@@@ -679,9 -655,9 +681,9 @@@ static int do_lstat(int follow, const c
                buf->st_size = fdata.nFileSizeLow |
                        (((off_t)fdata.nFileSizeHigh)<<32);
                buf->st_dev = buf->st_rdev = 0; /* not used by Git */
-               buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
-               buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
-               buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
+               filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
+               filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
+               filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
                if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
                        WIN32_FIND_DATAW findbuf;
                        HANDLE handle = FindFirstFileW(wfilename, &findbuf);
@@@ -762,6 -738,29 +764,29 @@@ static int do_stat_internal(int follow
        return do_lstat(follow, alt_name, buf);
  }
  
+ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
+ {
+       BY_HANDLE_FILE_INFORMATION fdata;
+       if (!GetFileInformationByHandle(hnd, &fdata)) {
+               errno = err_win_to_posix(GetLastError());
+               return -1;
+       }
+       buf->st_ino = 0;
+       buf->st_gid = 0;
+       buf->st_uid = 0;
+       buf->st_nlink = 1;
+       buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
+       buf->st_size = fdata.nFileSizeLow |
+               (((off_t)fdata.nFileSizeHigh)<<32);
+       buf->st_dev = buf->st_rdev = 0; /* not used by Git */
+       filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
+       filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
+       filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
+       return 0;
+ }
  int mingw_lstat(const char *file_name, struct stat *buf)
  {
        return do_stat_internal(0, file_name, buf);
@@@ -774,32 -773,31 +799,31 @@@ int mingw_stat(const char *file_name, s
  int mingw_fstat(int fd, struct stat *buf)
  {
        HANDLE fh = (HANDLE)_get_osfhandle(fd);
-       BY_HANDLE_FILE_INFORMATION fdata;
+       DWORD avail, type = GetFileType(fh) & ~FILE_TYPE_REMOTE;
  
-       if (fh == INVALID_HANDLE_VALUE) {
-               errno = EBADF;
-               return -1;
-       }
-       /* direct non-file handles to MS's fstat() */
-       if (GetFileType(fh) != FILE_TYPE_DISK)
-               return _fstati64(fd, buf);
+       switch (type) {
+       case FILE_TYPE_DISK:
+               return get_file_info_by_handle(fh, buf);
  
-       if (GetFileInformationByHandle(fh, &fdata)) {
-               buf->st_ino = 0;
-               buf->st_gid = 0;
-               buf->st_uid = 0;
+       case FILE_TYPE_CHAR:
+       case FILE_TYPE_PIPE:
+               /* initialize stat fields */
+               memset(buf, 0, sizeof(*buf));
                buf->st_nlink = 1;
-               buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
-               buf->st_size = fdata.nFileSizeLow |
-                       (((off_t)fdata.nFileSizeHigh)<<32);
-               buf->st_dev = buf->st_rdev = 0; /* not used by Git */
-               buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
-               buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
-               buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
+               if (type == FILE_TYPE_CHAR) {
+                       buf->st_mode = _S_IFCHR;
+               } else {
+                       buf->st_mode = _S_IFIFO;
+                       if (PeekNamedPipe(fh, NULL, 0, NULL, &avail, NULL))
+                               buf->st_size = avail;
+               }
                return 0;
+       default:
+               errno = EBADF;
+               return -1;
        }
-       errno = EBADF;
-       return -1;
  }
  
  static inline void time_t_to_filetime(time_t t, FILETIME *ft)
@@@ -943,29 -941,8 +967,29 @@@ struct tm *localtime_r(const time_t *ti
  
  char *mingw_getcwd(char *pointer, int len)
  {
 -      wchar_t wpointer[MAX_PATH];
 -      if (!_wgetcwd(wpointer, ARRAY_SIZE(wpointer)))
 +      wchar_t cwd[MAX_PATH], wpointer[MAX_PATH];
 +      DWORD ret = GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd);
 +
 +      if (!ret || ret >= ARRAY_SIZE(cwd)) {
 +              errno = ret ? ENAMETOOLONG : err_win_to_posix(GetLastError());
 +              return NULL;
 +      }
 +      ret = GetLongPathNameW(cwd, wpointer, ARRAY_SIZE(wpointer));
 +      if (!ret && GetLastError() == ERROR_ACCESS_DENIED) {
 +              HANDLE hnd = CreateFileW(cwd, 0,
 +                      FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
 +                      OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
 +              if (hnd == INVALID_HANDLE_VALUE)
 +                      return NULL;
 +              ret = GetFinalPathNameByHandleW(hnd, wpointer, ARRAY_SIZE(wpointer), 0);
 +              CloseHandle(hnd);
 +              if (!ret || ret >= ARRAY_SIZE(wpointer))
 +                      return NULL;
 +              if (xwcstoutf(pointer, normalize_ntpath(wpointer), len) < 0)
 +                      return NULL;
 +              return pointer;
 +      }
 +      if (!ret || ret >= ARRAY_SIZE(wpointer))
                return NULL;
        if (xwcstoutf(pointer, wpointer, len) < 0)
                return NULL;
@@@ -1624,8 -1601,7 +1648,8 @@@ static void ensure_socket_initializatio
                        WSAGetLastError());
  
        for (name = libraries; *name; name++) {
 -              ipv6_dll = LoadLibrary(*name);
 +              ipv6_dll = LoadLibraryExA(*name, NULL,
 +                                        LOAD_LIBRARY_SEARCH_SYSTEM32);
                if (!ipv6_dll)
                        continue;
  
@@@ -1846,63 -1822,18 +1870,63 @@@ int mingw_getpagesize(void
        return si.dwAllocationGranularity;
  }
  
 +/* See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724435.aspx */
 +enum EXTENDED_NAME_FORMAT {
 +      NameDisplay = 3,
 +      NameUserPrincipal = 8
 +};
 +
 +static char *get_extended_user_info(enum EXTENDED_NAME_FORMAT type)
 +{
 +      DECLARE_PROC_ADDR(secur32.dll, BOOL, GetUserNameExW,
 +              enum EXTENDED_NAME_FORMAT, LPCWSTR, PULONG);
 +      static wchar_t wbuffer[1024];
 +      DWORD len;
 +
 +      if (!INIT_PROC_ADDR(GetUserNameExW))
 +              return NULL;
 +
 +      len = ARRAY_SIZE(wbuffer);
 +      if (GetUserNameExW(type, wbuffer, &len)) {
 +              char *converted = xmalloc((len *= 3));
 +              if (xwcstoutf(converted, wbuffer, len) >= 0)
 +                      return converted;
 +              free(converted);
 +      }
 +
 +      return NULL;
 +}
 +
 +char *mingw_query_user_email(void)
 +{
 +      return get_extended_user_info(NameUserPrincipal);
 +}
 +
  struct passwd *getpwuid(int uid)
  {
 +      static unsigned initialized;
        static char user_name[100];
 -      static struct passwd p;
 +      static struct passwd *p;
 +      DWORD len;
 +
 +      if (initialized)
 +              return p;
  
 -      DWORD len = sizeof(user_name);
 -      if (!GetUserName(user_name, &len))
 +      len = sizeof(user_name);
 +      if (!GetUserName(user_name, &len)) {
 +              initialized = 1;
                return NULL;
 -      p.pw_name = user_name;
 -      p.pw_gecos = "unknown";
 -      p.pw_dir = NULL;
 -      return &p;
 +      }
 +
 +      p = xmalloc(sizeof(*p));
 +      p->pw_name = user_name;
 +      p->pw_gecos = get_extended_user_info(NameDisplay);
 +      if (!p->pw_gecos)
 +              p->pw_gecos = "unknown";
 +      p->pw_dir = NULL;
 +
 +      initialized = 1;
 +      return p;
  }
  
  static HANDLE timer_event;
diff --combined compat/mingw.h
index f31dcff2be1d60ce4f5ae46089d59255f1bc4dff,9419b27e1275e081e30a91e5ee3b87db71725cae..4c14e3083535f58964c6d57e2e15fb4763c129db
@@@ -327,18 -327,41 +327,41 @@@ static inline int getrlimit(int resourc
  }
  
  /*
-  * Use mingw specific stat()/lstat()/fstat() implementations on Windows.
+  * Use mingw specific stat()/lstat()/fstat() implementations on Windows,
+  * including our own struct stat with 64 bit st_size and nanosecond-precision
+  * file times.
   */
  #ifndef __MINGW64_VERSION_MAJOR
  #define off_t off64_t
  #define lseek _lseeki64
+ struct timespec {
+       time_t tv_sec;
+       long tv_nsec;
+ };
  #endif
  
- /* use struct stat with 64 bit st_size */
+ struct mingw_stat {
+     _dev_t st_dev;
+     _ino_t st_ino;
+     _mode_t st_mode;
+     short st_nlink;
+     short st_uid;
+     short st_gid;
+     _dev_t st_rdev;
+     off64_t st_size;
+     struct timespec st_atim;
+     struct timespec st_mtim;
+     struct timespec st_ctim;
+ };
+ #define st_atime st_atim.tv_sec
+ #define st_mtime st_mtim.tv_sec
+ #define st_ctime st_ctim.tv_sec
  #ifdef stat
  #undef stat
  #endif
- #define stat _stati64
+ #define stat mingw_stat
  int mingw_lstat(const char *file_name, struct stat *buf);
  int mingw_stat(const char *file_name, struct stat *buf);
  int mingw_fstat(int fd, struct stat *buf);
  #endif
  #define lstat mingw_lstat
  
- #ifndef _stati64
- # define _stati64(x,y) mingw_stat(x,y)
- #elif defined (_USE_32BIT_TIME_T)
- # define _stat32i64(x,y) mingw_stat(x,y)
- #else
- # define _stat64(x,y) mingw_stat(x,y)
- #endif
  
  int mingw_utime(const char *file_name, const struct utimbuf *times);
  #define utime mingw_utime
@@@ -424,8 -440,6 +440,8 @@@ static inline void convert_slashes(cha
  int mingw_offset_1st_component(const char *path);
  #define offset_1st_component mingw_offset_1st_component
  #define PATH_SEP ';'
 +extern char *mingw_query_user_email(void);
 +#define query_user_email mingw_query_user_email
  #if !defined(__MINGW64_VERSION_MAJOR) && (!defined(_MSC_VER) || _MSC_VER < 1800)
  #define PRIuMAX "I64u"
  #define PRId64 "I64d"