[glib] W32: Add a stat() implementation for private use



commit 53bd6a359f2c48e7729f89902097c892c8aa6fea
Author: Руслан Ижбулатов <lrn1986 gmail com>
Date:   Fri Sep 29 10:14:41 2017 +0000

    W32: Add a stat() implementation for private use
    
    This commit adds new W32-only functions to gstdio.c,
    and a new header file, gstdioprivate.h.
    These functions are:
    g_win32_stat_utf8()
    g_win32_lstat_utf8()
    g_win32_fstat()
    and they fill a private structure, GWin32PrivateStat,
    which has all the fields that normal stat has, as well as some
    extras.
    
    These functions are then used throughout glib and gio to get better
    data about the system. Specifically:
    * Full, 64-bit size, guaranteed (g_stat() is forced to use 32-bit st_size)
    * Full, 64-bit file identifier (st_ino is 0 when normal stat() is used, and still is)
    * W32 File attributes (which stat() doesn't report); in particular, this allows
      symlinks to be correctly identified
    * Full, 64-bit time, guaranteed (g_stat() uses 32-bit st_*time on 32-bit Windows)
    * Allocated file size (as a W32 replacement for the missing st_blocks)
    
    st_mode remains unchanged (thus, no S_ISLNK), so when these are given back to
    glib users (via g_stat(), for example, which is now implemented by calling g_win32_stat_utf8),
    this field does not contain anything unexpected.
    
    g_lstat() now calls g_win32_lstat_utf8(), which works on symlinks the way it's supposed to.
    
    Also adds the g_win32_readlink_utf8() function, which behaves like readlink()
    (including its inability to return 0-terminated strings and inability to say how large
    the output buffer should be; these limitations are purely for compatibility with
    existing glib code).
    
    Thus, symlink support should now be much better, although far from being complete.
    
    A new W32-only test in gio/tests/file.c highlights the following features:
    * allocated size
    * 64-bit time
    * unique file IDs
    
    https://bugzilla.gnome.org/show_bug.cgi?id=788180

 gio/glocalfile.c             |   46 +---
 gio/glocalfileinfo.c         |   97 ++++---
 gio/glocalfileinfo.h         |    5 +-
 gio/glocalfileoutputstream.c |    8 +-
 gio/tests/g-file-info.c      |  367 ++++++++++++++++++++++++++
 glib/Makefile.am             |    1 +
 glib/gfileutils.c            |    7 +-
 glib/glib-private.c          |    7 +
 glib/glib-private.h          |   18 ++
 glib/gstdio.c                |  586 ++++++++++++++++++++++++++++++++++++++++--
 glib/gstdioprivate.h         |   65 +++++
 11 files changed, 1105 insertions(+), 102 deletions(-)
---
diff --git a/gio/glocalfile.c b/gio/glocalfile.c
index 1f78818..81f0fb2 100644
--- a/gio/glocalfile.c
+++ b/gio/glocalfile.c
@@ -62,11 +62,11 @@
 #include "gunixmounts.h"
 #include "gioerror.h"
 #include <glib/gstdio.h>
+#include <glib/gstdioprivate.h>
 #include "glibintl.h"
 #ifdef G_OS_UNIX
 #include "glib-unix.h"
 #endif
-#include "glib-private.h"
 
 #include "glib-private.h"
 
@@ -1395,7 +1395,8 @@ g_local_file_read (GFile         *file,
 #ifdef G_OS_WIN32
       if (errsv == EACCES)
        {
-         ret = _stati64 (local->filename, &buf);
+         /* Exploit the fact that on W32 the glib filename encoding is UTF8 */
+         ret = GLIB_PRIVATE_CALL (g_win32_stat_utf8) (local->filename, &buf);
          if (ret == 0 && S_ISDIR (buf.st_mode))
             errsv = EISDIR;
        }
@@ -1407,7 +1408,7 @@ g_local_file_read (GFile         *file,
     }
 
 #ifdef G_OS_WIN32
-  ret = _fstati64 (fd, &buf);
+  ret = GLIB_PRIVATE_CALL (g_win32_fstat) (fd, &buf);
 #else
   ret = fstat (fd, &buf);
 #endif
@@ -2677,33 +2678,12 @@ g_local_file_measure_size_of_file (gint           parent_fd,
       int errsv = errno;
       return g_local_file_measure_size_error (state->flags, errsv, name, error);
     }
-#else
-  {
-    const char *filename = (const gchar *) name->data;
-    wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL);
-    int retval;
-    int save_errno;
-    int len;
-
-    if (wfilename == NULL)
-      return g_local_file_measure_size_error (state->flags, errno, name, error);
-
-    len = wcslen (wfilename);
-    while (len > 0 && G_IS_DIR_SEPARATOR (wfilename[len-1]))
-      len--;
-    if (len > 0 &&
-        (!g_path_is_absolute (filename) || len > g_path_skip_root (filename) - filename))
-      wfilename[len] = '\0';
-
-    retval = _wstati64 (wfilename, &buf);
-    save_errno = errno;
-
-    g_free (wfilename);
-
-    errno = save_errno;
-    if (retval != 0)
-      return g_local_file_measure_size_error (state->flags, errno, name, error);
-  }
+#else /* !AT_FDCWD && !HAVE_LSTAT && G_OS_WIN32 */
+  if (GLIB_PRIVATE_CALL (g_win32_lstat_utf8) (name->data, &buf) != 0)
+    {
+      int errsv = errno;
+      return g_local_file_measure_size_error (state->flags, errsv, name, error);
+    }
 #endif
 
   if (name->next)
@@ -2722,7 +2702,11 @@ g_local_file_measure_size_of_file (gint           parent_fd,
       state->contained_on = buf.st_dev;
     }
 
-#if defined (HAVE_STRUCT_STAT_ST_BLOCKS)
+#if defined (G_OS_WIN32)
+  if (~state->flags & G_FILE_MEASURE_APPARENT_SIZE)
+    state->disk_usage += buf.allocated_size;
+  else
+#elif defined (HAVE_STRUCT_STAT_ST_BLOCKS)
   if (~state->flags & G_FILE_MEASURE_APPARENT_SIZE)
     state->disk_usage += buf.st_blocks * G_GUINT64_CONSTANT (512);
   else
diff --git a/gio/glocalfileinfo.c b/gio/glocalfileinfo.c
index 35853cc..fad80d3 100644
--- a/gio/glocalfileinfo.c
+++ b/gio/glocalfileinfo.c
@@ -53,6 +53,7 @@
 #endif /* HAVE_XATTR */
 
 #include <glib/gstdio.h>
+#include <glib/gstdioprivate.h>
 #include <gfileattribute-priv.h>
 #include <gfileinfo-priv.h>
 #include <gvfs.h>
@@ -60,9 +61,10 @@
 #ifdef G_OS_UNIX
 #include <unistd.h>
 #include "glib-unix.h"
-#include "glib-private.h"
 #endif
 
+#include "glib-private.h"
+
 #include "thumbnail-verify.h"
 
 #ifdef G_OS_WIN32
@@ -136,9 +138,15 @@ _g_local_file_info_create_etag (GLocalFileStat *statbuf)
 static char *
 _g_local_file_info_create_file_id (GLocalFileStat *statbuf)
 {
+  guint64 ino;
+#ifdef G_OS_WIN32
+  ino = statbuf->file_index;
+#else
+  ino = statbuf->st_ino;
+#endif
   return g_strdup_printf ("l%" G_GUINT64_FORMAT ":%" G_GUINT64_FORMAT,
                          (guint64) statbuf->st_dev, 
-                         (guint64) statbuf->st_ino);
+                         ino);
 }
 
 static char *
@@ -148,13 +156,12 @@ _g_local_file_info_create_fs_id (GLocalFileStat *statbuf)
                          (guint64) statbuf->st_dev);
 }
 
-
-#ifdef S_ISLNK
+#if defined (S_ISLNK) || defined (G_OS_WIN32)
 
 static gchar *
 read_link (const gchar *full_name)
 {
-#ifdef HAVE_READLINK
+#if defined (HAVE_READLINK) || defined (G_OS_WIN32)
   gchar *buffer;
   guint size;
   
@@ -165,7 +172,11 @@ read_link (const gchar *full_name)
     {
       int read_size;
       
+#ifndef G_OS_WIN32
       read_size = readlink (full_name, buffer, size);
+#else
+      read_size = GLIB_PRIVATE_CALL (g_win32_readlink_utf8) (full_name, buffer, size);
+#endif
       if (read_size < 0)
        {
          g_free (buffer);
@@ -184,7 +195,7 @@ read_link (const gchar *full_name)
 #endif
 }
 
-#endif  /* S_ISLNK */
+#endif  /* S_ISLNK || G_OS_WIN32 */
 
 #ifdef HAVE_SELINUX
 /* Get the SELinux security context */
@@ -938,6 +949,9 @@ set_info_from_stat (GFileInfo             *info,
 #ifdef S_ISLNK
   else if (S_ISLNK (statbuf->st_mode))
     file_type = G_FILE_TYPE_SYMBOLIC_LINK;
+#elif defined (G_OS_WIN32)
+  if (statbuf->reparse_tag == IO_REPARSE_TAG_SYMLINK)
+    file_type = G_FILE_TYPE_SYMBOLIC_LINK;
 #endif
 
   g_file_info_set_file_type (info, file_type);
@@ -960,7 +974,11 @@ set_info_from_stat (GFileInfo             *info,
 #if defined (HAVE_STRUCT_STAT_ST_BLOCKS)
   _g_file_info_set_attribute_uint64_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_BLOCKS, statbuf->st_blocks);
   _g_file_info_set_attribute_uint64_by_id (info, G_FILE_ATTRIBUTE_ID_STANDARD_ALLOCATED_SIZE,
-                                          statbuf->st_blocks * G_GUINT64_CONSTANT (512));
+                                           statbuf->st_blocks * G_GUINT64_CONSTANT (512));
+#elif defined (G_OS_WIN32)
+  _g_file_info_set_attribute_uint64_by_id (info, G_FILE_ATTRIBUTE_ID_STANDARD_ALLOCATED_SIZE,
+                                           statbuf->allocated_size);
+
 #endif
   
   _g_file_info_set_attribute_uint64_by_id (info, G_FILE_ATTRIBUTE_ID_TIME_MODIFIED, statbuf->st_mtime);
@@ -1711,13 +1729,12 @@ _g_local_file_info_get (const char             *basename,
   GLocalFileStat statbuf;
 #ifdef S_ISLNK
   struct stat statbuf2;
+#elif defined (G_OS_WIN32)
+  GWin32PrivateStat statbuf2;
 #endif
   int res;
   gboolean stat_ok;
   gboolean is_symlink, symlink_broken;
-#ifdef G_OS_WIN32
-  DWORD dos_attributes;
-#endif
   char *symlink_target;
   GVfs *vfs;
   GVfsClass *class;
@@ -1739,28 +1756,7 @@ _g_local_file_info_get (const char             *basename,
 #ifndef G_OS_WIN32
   res = g_lstat (path, &statbuf);
 #else
-  {
-    wchar_t *wpath = g_utf8_to_utf16 (path, -1, NULL, NULL, error);
-    int len;
-
-    if (wpath == NULL)
-      {
-        g_object_unref (info);
-        return NULL;
-      }
-
-    len = wcslen (wpath);
-    while (len > 0 && G_IS_DIR_SEPARATOR (wpath[len-1]))
-      len--;
-    if (len > 0 &&
-        (!g_path_is_absolute (path) || len > g_path_skip_root (path) - path))
-      wpath[len] = '\0';
-
-    res = _wstati64 (wpath, &statbuf);
-    dos_attributes = GetFileAttributesW (wpath);
-
-    g_free (wpath);
-  }
+  res = GLIB_PRIVATE_CALL (g_win32_lstat_utf8) (path, &statbuf);
 #endif
 
   if (res == -1)
@@ -1791,11 +1787,14 @@ _g_local_file_info_get (const char             *basename,
 
 #ifdef S_ISLNK
   is_symlink = stat_ok && S_ISLNK (statbuf.st_mode);
+#elif defined (G_OS_WIN32)
+  /* glib already checked the FILE_ATTRIBUTE_REPARSE_POINT for us */
+  is_symlink = stat_ok && statbuf.reparse_tag == IO_REPARSE_TAG_SYMLINK; 
 #else
   is_symlink = FALSE;
 #endif
   symlink_broken = FALSE;
-#ifdef S_ISLNK
+
   if (is_symlink)
     {
       g_file_info_set_is_symlink (info, TRUE);
@@ -1803,7 +1802,11 @@ _g_local_file_info_get (const char             *basename,
       /* Unless NOFOLLOW was set we default to following symlinks */
       if (!(flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS))
        {
+#ifndef G_OS_WIN32
          res = stat (path, &statbuf2);
+#else
+         res = GLIB_PRIVATE_CALL (g_win32_stat_utf8) (path, &statbuf2);
+#endif
 
          /* Report broken links as symlinks */
          if (res != -1)
@@ -1815,7 +1818,6 @@ _g_local_file_info_get (const char             *basename,
            symlink_broken = TRUE;
        }
     }
-#endif
 
   if (stat_ok)
     set_info_from_stat (info, &statbuf, attribute_matcher);
@@ -1839,27 +1841,28 @@ _g_local_file_info_get (const char             *basename,
       (stat_ok && S_ISREG (statbuf.st_mode)))
     _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_STANDARD_IS_BACKUP, TRUE);
 #else
-  if (dos_attributes & FILE_ATTRIBUTE_HIDDEN)
+  if (statbuf.attributes & FILE_ATTRIBUTE_HIDDEN)
     g_file_info_set_is_hidden (info, TRUE);
 
-  if (dos_attributes & FILE_ATTRIBUTE_ARCHIVE)
+  if (statbuf.attributes & FILE_ATTRIBUTE_ARCHIVE)
     _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_DOS_IS_ARCHIVE, TRUE);
 
-  if (dos_attributes & FILE_ATTRIBUTE_SYSTEM)
+  if (statbuf.attributes & FILE_ATTRIBUTE_SYSTEM)
     _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_DOS_IS_SYSTEM, TRUE);
 #endif
 
   symlink_target = NULL;
-#ifdef S_ISLNK
   if (is_symlink)
     {
+#if defined (S_ISLNK) || defined (G_OS_WIN32)
       symlink_target = read_link (path);
+#endif
       if (symlink_target &&
           _g_file_attribute_matcher_matches_id (attribute_matcher,
                                                 G_FILE_ATTRIBUTE_ID_STANDARD_SYMLINK_TARGET))
         g_file_info_set_symlink_target (info, symlink_target);
     }
-#endif
+
   if (_g_file_attribute_matcher_matches_id (attribute_matcher,
                                            G_FILE_ATTRIBUTE_ID_STANDARD_CONTENT_TYPE) ||
       _g_file_attribute_matcher_matches_id (attribute_matcher,
@@ -2014,7 +2017,7 @@ _g_local_file_info_get_from_fd (int         fd,
   GFileInfo *info;
   
 #ifdef G_OS_WIN32
-#define FSTAT _fstati64
+#define FSTAT GLIB_PRIVATE_CALL (g_win32_fstat)
 #else
 #define FSTAT fstat
 #endif
@@ -2148,16 +2151,26 @@ set_unix_mode (char                       *filename,
   if (!get_uint32 (value, &val, error))
     return FALSE;
 
-#ifdef HAVE_SYMLINK
+#if defined (HAVE_SYMLINK) || defined (G_OS_WIN32)
   if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) {
 #ifdef HAVE_LCHMOD
     res = lchmod (filename, val);
 #else
+    gboolean is_symlink;
+#ifndef G_OS_WIN32
     struct stat statbuf;
     /* Calling chmod on a symlink changes permissions on the symlink.
      * We don't want to do this, so we need to check for a symlink */
     res = g_lstat (filename, &statbuf);
-    if (res == 0 && S_ISLNK (statbuf.st_mode))
+    is_symlink = (res == 0 && S_ISLNK (statbuf.st_mode));
+#else
+    /* FIXME: implement lchmod for W32, should be doable */
+    GWin32PrivateStat statbuf;
+
+    res = GLIB_PRIVATE_CALL (g_win32_lstat_utf8) (filename, &statbuf);
+    is_symlink = (res == 0 && statbuf.reparse_tag == IO_REPARSE_TAG_SYMLINK);
+#endif
+    if (is_symlink)
       {
         g_set_error_literal (error, G_IO_ERROR,
                              G_IO_ERROR_NOT_SUPPORTED,
diff --git a/gio/glocalfileinfo.h b/gio/glocalfileinfo.h
index f036288..a231c24 100644
--- a/gio/glocalfileinfo.h
+++ b/gio/glocalfileinfo.h
@@ -23,6 +23,7 @@
 
 #include <gio/gfileinfo.h>
 #include <gio/gfile.h>
+#include <glib/gstdioprivate.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
@@ -40,8 +41,8 @@ typedef struct
 } GLocalParentFileInfo;
 
 #ifdef G_OS_WIN32
-/* We want 64-bit file size support */
-#define GLocalFileStat struct _stati64
+/* We want 64-bit file size, file ID and symlink support */
+#define GLocalFileStat GWin32PrivateStat
 #else
 #define GLocalFileStat struct stat
 #endif
diff --git a/gio/glocalfileoutputstream.c b/gio/glocalfileoutputstream.c
index 7600571..57d2d5d 100644
--- a/gio/glocalfileoutputstream.c
+++ b/gio/glocalfileoutputstream.c
@@ -40,6 +40,8 @@
 #include "gfiledescriptorbased.h"
 #endif
 
+#include "glib-private.h"
+
 #ifdef G_OS_WIN32
 #include <io.h>
 #ifndef S_ISDIR
@@ -234,7 +236,7 @@ _g_local_file_output_stream_really_close (GLocalFileOutputStream *file,
   /* Must close before renaming on Windows, so just do the close first
    * in all cases for now.
    */
-  if (_fstati64 (file->priv->fd, &final_stat) == 0)
+  if (GLIB_PRIVATE_CALL (g_win32_fstat) (file->priv->fd, &final_stat) == 0)
     file->priv->etag = _g_local_file_info_create_etag (&final_stat);
 
   if (!g_close (file->priv->fd, NULL))
@@ -797,7 +799,7 @@ handle_overwrite_open (const char    *filename,
     }
   
 #ifdef G_OS_WIN32
-  res = _fstati64 (fd, &original_stat);
+  res = GLIB_PRIVATE_CALL (g_win32_fstat) (fd, &original_stat);
 #else
   res = fstat (fd, &original_stat);
 #endif
@@ -891,7 +893,7 @@ handle_overwrite_open (const char    *filename,
           int tres;
 
 #ifdef G_OS_WIN32
-          tres = _fstati64 (tmpfd, &tmp_statbuf);
+          tres = GLIB_PRIVATE_CALL (g_win32_fstat) (tmpfd, &tmp_statbuf);
 #else
           tres = fstat (tmpfd, &tmp_statbuf);
 #endif
diff --git a/gio/tests/g-file-info.c b/gio/tests/g-file-info.c
index 7ed874a..5b3def9 100644
--- a/gio/tests/g-file-info.c
+++ b/gio/tests/g-file-info.c
@@ -20,10 +20,18 @@
  * if advised of the possibility of such damage.
  */
 
+#include "config.h"
+
 #include <glib/glib.h>
 #include <gio/gio.h>
 #include <stdlib.h>
 #include <string.h>
+#ifdef G_OS_WIN32
+#include <stdio.h>
+#include <glib/gstdio.h>
+#include <Windows.h>
+#include <Shlobj.h>
+#endif
 
 #define TEST_NAME                      "Prilis zlutoucky kun"
 #define TEST_DISPLAY_NAME              "UTF-8 p\xc5\x99\xc3\xadli\xc5\xa1 
\xc5\xbelu\xc5\xa5ou\xc4\x8dk\xc3\xbd k\xc5\xaf\xc5\x88"
@@ -132,6 +140,362 @@ test_g_file_info (void)
   g_object_unref (info_copy);
 }
 
+#ifdef G_OS_WIN32
+static void
+test_internal_enhanced_stdio (void)
+{
+  char *p0, *p1, *ps;
+  gboolean try_sparse;
+  gchar *tmp_dir_root;
+  wchar_t *tmp_dir_root_w;
+  gchar *c;
+  DWORD fsflags;
+  FILE *f;
+  SYSTEMTIME st;
+  FILETIME ft;
+  HANDLE h;
+  GStatBuf statbuf_p0, statbuf_p1, statbuf_ps;
+  GFile *gf_p0, *gf_p1, *gf_ps;
+  GFileInfo *fi_p0, *fi_p1, *fi_ps;
+  guint64 size_p0, alsize_p0, size_ps, alsize_ps;
+  const gchar *id_p0;
+  const gchar *id_p1;
+  volatile guint64 time_p0;
+  gchar *tmp_dir;
+  wchar_t *programdata_dir_w;
+  wchar_t *users_dir_w;
+  static const GUID folder_id_programdata = 
+    { 0x62AB5D82, 0xFDC1, 0x4DC3, { 0xA9, 0xDD, 0x07, 0x0D, 0x1D, 0x49, 0x5D, 0x97 } };
+  static const GUID folder_id_users = 
+    { 0x0762D272, 0xC50A, 0x4BB0, { 0xA3, 0x82, 0x69, 0x7D, 0xCD, 0x72, 0x9B, 0x80 } };
+
+  programdata_dir_w = NULL;
+  SHGetKnownFolderPath (&folder_id_programdata, 0, NULL, &programdata_dir_w);
+
+  users_dir_w = NULL;
+  SHGetKnownFolderPath (&folder_id_users, 0, NULL, &users_dir_w);
+
+  if (programdata_dir_w != NULL && users_dir_w != NULL)
+    {
+      gchar *programdata;
+      gchar *users_dir;
+      gchar *allusers;
+      GFile *gf_programdata, *gf_allusers;
+      GFileInfo *fi_programdata, *fi_allusers, *fi_allusers_target;
+      GFileType ft_allusers;
+      GFileType ft_allusers_target;
+      GFileType ft_programdata;
+      gboolean allusers_is_symlink;
+      const gchar *id_allusers;
+      const gchar *id_allusers_target;
+      const gchar *id_programdata;
+      const gchar *allusers_target;
+
+      /* C:/ProgramData */
+      programdata = g_utf16_to_utf8 (programdata_dir_w, -1, NULL, NULL, NULL);
+      g_assert_nonnull (programdata);
+      /* C:/Users */
+      users_dir = g_utf16_to_utf8 (users_dir_w, -1, NULL, NULL, NULL);
+      g_assert_nonnull (users_dir);
+      /* "C:/Users/All Users" is a known directory symlink
+       * for "C:/ProgramData".
+       */
+      allusers = g_build_filename (users_dir, "All Users", NULL);
+      g_assert_nonnull (allusers);
+
+      /* We don't test g_stat() and g_lstat() on these directories,
+       * because it is pointless - there's no way to tell that these
+       * functions behave correctly in this case
+       * (st_ino is useless, so we can't tell apart g_stat() and g_lstat()
+       *  results; st_mode is also useless as it does not support S_ISLNK;
+       *  and these directories have no interesting properties other
+       *  than [not] being symlinks).
+       */
+      gf_programdata = g_file_new_for_path (programdata);
+      gf_allusers = g_file_new_for_path (allusers);
+
+      fi_programdata = g_file_query_info (gf_programdata,
+                                          G_FILE_ATTRIBUTE_ID_FILE ","
+                                          G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                                          G_FILE_QUERY_INFO_NONE,
+                                          NULL, NULL);
+
+      fi_allusers_target = g_file_query_info (gf_allusers,
+                                              G_FILE_ATTRIBUTE_ID_FILE ","
+                                              G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                                              G_FILE_QUERY_INFO_NONE,
+                                              NULL, NULL);
+
+      fi_allusers = g_file_query_info (gf_allusers,
+                                       G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET ","
+                                       G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK ","
+                                       G_FILE_ATTRIBUTE_ID_FILE ","
+                                       G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                                       G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                       NULL, NULL);
+
+      g_assert (g_file_info_has_attribute (fi_programdata, G_FILE_ATTRIBUTE_ID_FILE));
+      g_assert (g_file_info_has_attribute (fi_programdata, G_FILE_ATTRIBUTE_STANDARD_TYPE));
+
+      g_assert (g_file_info_has_attribute (fi_allusers_target, G_FILE_ATTRIBUTE_ID_FILE));
+      g_assert (g_file_info_has_attribute (fi_allusers_target, G_FILE_ATTRIBUTE_STANDARD_TYPE));
+
+      g_assert (g_file_info_has_attribute (fi_allusers, G_FILE_ATTRIBUTE_ID_FILE));
+      g_assert (g_file_info_has_attribute (fi_allusers, G_FILE_ATTRIBUTE_STANDARD_TYPE));
+      g_assert (g_file_info_has_attribute (fi_allusers, G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK));
+      g_assert (g_file_info_has_attribute (fi_allusers, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET));
+
+      ft_allusers = g_file_info_get_file_type (fi_allusers);
+      ft_allusers_target = g_file_info_get_file_type (fi_allusers_target);
+      ft_programdata = g_file_info_get_file_type (fi_programdata);
+
+      g_assert (ft_allusers == G_FILE_TYPE_SYMBOLIC_LINK);
+      g_assert (ft_allusers_target == G_FILE_TYPE_DIRECTORY);
+      g_assert (ft_programdata == G_FILE_TYPE_DIRECTORY);
+
+      allusers_is_symlink = g_file_info_get_attribute_boolean (fi_allusers, 
G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK);
+
+      g_assert_true (allusers_is_symlink);
+
+      id_allusers = g_file_info_get_attribute_string (fi_allusers, G_FILE_ATTRIBUTE_ID_FILE);
+      id_allusers_target = g_file_info_get_attribute_string (fi_allusers_target, G_FILE_ATTRIBUTE_ID_FILE);
+      id_programdata = g_file_info_get_attribute_string (fi_programdata, G_FILE_ATTRIBUTE_ID_FILE);
+
+      g_assert_cmpstr (id_allusers_target, ==, id_programdata);
+      g_assert_cmpstr (id_allusers, !=, id_programdata);
+
+      allusers_target = g_file_info_get_symlink_target (fi_allusers);
+
+      g_assert_true (g_str_has_suffix (allusers_target, "ProgramData"));
+
+      g_object_unref (fi_allusers);
+      g_object_unref (fi_allusers_target);
+      g_object_unref (fi_programdata);
+      g_object_unref (gf_allusers);
+      g_object_unref (gf_programdata);
+
+      g_free (allusers);
+      g_free (users_dir);
+      g_free (programdata);
+    }
+
+  if (programdata_dir_w)
+    CoTaskMemFree (programdata_dir_w);
+
+  if (users_dir_w)
+    CoTaskMemFree (users_dir_w);
+
+  tmp_dir = g_dir_make_tmp ("glib_stdio_testXXXXXX", NULL);
+  g_assert_nonnull (tmp_dir);
+
+  /* Technically, this isn't necessary - we already assume NTFS, because of
+   * symlink support, and NTFS also supports sparse files. Still, given
+   * the amount of unusual I/O APIs called in this test, checking for
+   * sparse file support of the filesystem where temp directory is
+   * doesn't seem to be out of place.
+   */
+  try_sparse = FALSE;
+  tmp_dir_root = g_strdup (tmp_dir);
+  /* We need "C:\\" or "C:/", with a trailing [back]slash */
+  for (c = tmp_dir_root; c && c[0] && c[1]; c++)
+    if (c[0] == ':')
+      {
+        c[2] = '\0';
+        break;
+      }
+  tmp_dir_root_w = g_utf8_to_utf16 (tmp_dir_root, -1, NULL, NULL, NULL);
+  g_assert_nonnull (tmp_dir_root_w);
+  g_free (tmp_dir_root);
+  g_assert_true (GetVolumeInformationW (tmp_dir_root_w, NULL, 0, NULL, NULL, &fsflags, NULL, 0));
+  g_free (tmp_dir_root_w);
+  try_sparse = (fsflags & FILE_SUPPORTS_SPARSE_FILES) == FILE_SUPPORTS_SPARSE_FILES;
+
+  p0 = g_build_filename (tmp_dir, "zool", NULL);
+  p1 = g_build_filename (tmp_dir, "looz", NULL);
+  ps = g_build_filename (tmp_dir, "sparse", NULL);
+
+  if (try_sparse)
+    {
+      FILE_SET_SPARSE_BUFFER ssb;
+      FILE_ZERO_DATA_INFORMATION zdi;
+
+      g_remove (ps);
+
+      f = g_fopen (ps, "wb");
+      g_assert_nonnull (f);
+
+      h = (HANDLE) _get_osfhandle (fileno (f));
+      g_assert (h != INVALID_HANDLE_VALUE);
+
+      ssb.SetSparse = TRUE;
+      g_assert_true (DeviceIoControl (h,
+                     FSCTL_SET_SPARSE,
+                     &ssb, sizeof (ssb),
+                     NULL, 0, NULL, NULL));
+
+      /* Make it a sparse file that starts with 4GBs of zeros */
+      zdi.FileOffset.QuadPart = 0;
+      zdi.BeyondFinalZero.QuadPart = 0xFFFFFFFFULL + 1;
+      g_assert_true (DeviceIoControl (h,
+                     FSCTL_SET_ZERO_DATA,
+                     &zdi, sizeof (zdi),
+                     NULL, 0, NULL, NULL));
+
+      /* Let's not keep this seemingly 4GB monster around
+       * longer than we absolutely need to. Do all operations
+       * without assertions, then remove the file immediately.
+       */
+      _fseeki64 (f, 0xFFFFFFFFULL, SEEK_SET);
+      fprintf (f, "Hello 4GB World!");
+      fflush (f);
+      fclose (f);
+
+      memset (&statbuf_ps, 0, sizeof (statbuf_ps));
+
+      g_stat (ps, &statbuf_ps);
+
+      gf_ps = g_file_new_for_path (ps);
+
+      fi_ps = g_file_query_info (gf_ps,
+                                 G_FILE_ATTRIBUTE_STANDARD_SIZE ","
+                                 G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE,
+                                 G_FILE_QUERY_INFO_NONE,
+                                 NULL, NULL);
+
+      g_remove (ps);
+
+      g_assert (g_file_info_has_attribute (fi_ps, G_FILE_ATTRIBUTE_STANDARD_SIZE));
+      g_assert (g_file_info_has_attribute (fi_ps, G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE));
+
+      size_ps = g_file_info_get_attribute_uint64 (fi_ps, G_FILE_ATTRIBUTE_STANDARD_SIZE);
+      alsize_ps = g_file_info_get_attribute_uint64 (fi_ps, G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE);
+
+      /* allocated size should small (usually - size of the FS cluster,
+       * let's assume it's less than 1 gigabyte),
+       * size should be more than 4 gigabytes,
+       * st_size should not exceed its 0xFFFFFFFF 32-bit limit,
+       * and should be nonzero (this also detects a failed g_stat() earlier).
+       */
+      g_assert_cmpuint (alsize_ps, <, 0x40000000);
+      g_assert_cmpuint (size_ps, >, G_GUINT64_CONSTANT (0xFFFFFFFF));
+      g_assert_cmpuint (statbuf_ps.st_size, >, 0);
+      g_assert_cmpuint (statbuf_ps.st_size, <=, 0xFFFFFFFF);
+
+      g_object_unref (fi_ps);
+      g_object_unref (gf_ps);
+    }
+
+  /* Wa-a-ay past 02/07/2106 @ 6:28am (UTC),
+   * which is the date corresponding to 0xFFFFFFFF + 1.
+   * This is easier to check than Y2038 (0x80000000 + 1),
+   * since there's no need to worry about signedness this way.
+   */
+  st.wYear = 2106;
+  st.wMonth = 2;
+  st.wDay = 9;
+  st.wHour = 0;
+  st.wMinute = 0;
+  st.wSecond = 0;
+  st.wMilliseconds = 0;
+
+  g_assert_true (SystemTimeToFileTime (&st, &ft));
+
+  f = g_fopen (p0, "w");
+  g_assert_nonnull (f);
+
+  h = (HANDLE) _get_osfhandle (fileno (f));
+  g_assert (h != INVALID_HANDLE_VALUE);
+
+  fprintf (f, "1");
+  fflush (f);
+
+  g_assert_true (SetFileTime (h, &ft, &ft, &ft));
+
+  fclose (f);
+
+  f = g_fopen (p1, "w");
+  g_assert_nonnull (f);
+
+  fclose (f);
+
+  memset (&statbuf_p0, 0, sizeof (statbuf_p0));
+  memset (&statbuf_p1, 0, sizeof (statbuf_p1));
+
+  g_assert_cmpint (g_stat (p0, &statbuf_p0), ==, 0);
+  g_assert_cmpint (g_stat (p1, &statbuf_p1), ==, 0);
+
+  gf_p0 = g_file_new_for_path (p0);
+  gf_p1 = g_file_new_for_path (p1);
+
+  fi_p0 = g_file_query_info (gf_p0,
+                             G_FILE_ATTRIBUTE_STANDARD_SIZE ","
+                             G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE ","
+                             G_FILE_ATTRIBUTE_ID_FILE ","
+                             G_FILE_ATTRIBUTE_TIME_MODIFIED,
+                             G_FILE_QUERY_INFO_NONE,
+                             NULL, NULL);
+
+  fi_p1 = g_file_query_info (gf_p1,
+                             G_FILE_ATTRIBUTE_STANDARD_SIZE ","
+                             G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE ","
+                             G_FILE_ATTRIBUTE_ID_FILE ","
+                             G_FILE_ATTRIBUTE_TIME_MODIFIED,
+                             G_FILE_QUERY_INFO_NONE,
+                             NULL, NULL);
+
+  g_assert (g_file_info_has_attribute (fi_p0, G_FILE_ATTRIBUTE_STANDARD_SIZE));
+  g_assert (g_file_info_has_attribute (fi_p0, G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE));
+  g_assert (g_file_info_has_attribute (fi_p0, G_FILE_ATTRIBUTE_ID_FILE));
+  g_assert (g_file_info_has_attribute (fi_p0, G_FILE_ATTRIBUTE_TIME_MODIFIED));
+
+  g_assert (g_file_info_has_attribute (fi_p1, G_FILE_ATTRIBUTE_STANDARD_SIZE));
+  g_assert (g_file_info_has_attribute (fi_p1, G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE));
+  g_assert (g_file_info_has_attribute (fi_p1, G_FILE_ATTRIBUTE_ID_FILE));
+  g_assert (g_file_info_has_attribute (fi_p1, G_FILE_ATTRIBUTE_TIME_MODIFIED));
+
+  size_p0 = g_file_info_get_attribute_uint64 (fi_p0, G_FILE_ATTRIBUTE_STANDARD_SIZE);
+  alsize_p0 = g_file_info_get_attribute_uint64 (fi_p0, G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE);
+
+  /* size should be 1, allocated size should be something else
+   * (could be 0 or the size of the FS cluster, but never 1).
+   */
+  g_assert_cmpuint (size_p0, ==, statbuf_p0.st_size);
+  g_assert_cmpuint (size_p0, ==, 1);
+  g_assert_cmpuint (alsize_p0, !=, size_p0);
+
+  id_p0 = g_file_info_get_attribute_string (fi_p0, G_FILE_ATTRIBUTE_ID_FILE);
+  id_p1 = g_file_info_get_attribute_string (fi_p1, G_FILE_ATTRIBUTE_ID_FILE);
+
+  /* st_ino from W32 stat() is useless for file identification.
+   * It will be either 0, or it will be the same for both files.
+   */
+  g_assert (statbuf_p0.st_ino == statbuf_p1.st_ino);
+  g_assert_cmpstr (id_p0, !=, id_p1);
+
+  time_p0 = g_file_info_get_attribute_uint64 (fi_p0, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+
+  /* Check that GFileInfo doesn't suffer from Y2106 problem.
+   * Don't check stat(), as its contents may vary depending on
+   * the host platform architecture
+   * (time fields are 32-bit on 32-bit Windows,
+   *  and 64-bit on 64-bit Windows, usually),
+   * so it *can* pass this test in some cases.
+   */
+  g_assert (time_p0 > G_GUINT64_CONSTANT (0xFFFFFFFF));
+
+  g_object_unref (fi_p0);
+  g_object_unref (fi_p1);
+  g_object_unref (gf_p0);
+  g_object_unref (gf_p1);
+  g_remove (p0);
+  g_remove (p1);
+  g_free (p0);
+  g_free (p1);
+  g_rmdir (tmp_dir);
+}
+#endif
+
+
 int
 main (int   argc,
       char *argv[])
@@ -139,6 +503,9 @@ main (int   argc,
   g_test_init (&argc, &argv, NULL);
 
   g_test_add_func ("/g-file-info/test_g_file_info", test_g_file_info);
+#ifdef G_OS_WIN32
+  g_test_add_func ("/g-file-info/internal-enhanced-stdio", test_internal_enhanced_stdio);
+#endif
   
   return g_test_run();
 }
diff --git a/glib/Makefile.am b/glib/Makefile.am
index 5478edb..177bea7 100644
--- a/glib/Makefile.am
+++ b/glib/Makefile.am
@@ -161,6 +161,7 @@ libglib_2_0_la_SOURCES =    \
        gslice.c                \
        gslist.c                \
        gstdio.c                \
+       gstdioprivate.h         \
        gstrfuncs.c             \
        gstring.c               \
        gstringchunk.c          \
diff --git a/glib/gfileutils.c b/glib/gfileutils.c
index ddafa6d..5535c30 100644
--- a/glib/gfileutils.c
+++ b/glib/gfileutils.c
@@ -49,6 +49,7 @@
 #include "gfileutils.h"
 
 #include "gstdio.h"
+#include "gstdioprivate.h"
 #include "glibintl.h"
 
 #ifdef HAVE_LINUX_MAGIC_H /* for btrfs check */
@@ -2052,7 +2053,7 @@ gchar *
 g_file_read_link (const gchar  *filename,
                  GError      **error)
 {
-#ifdef HAVE_READLINK
+#if defined (HAVE_READLINK) || defined (G_OS_WIN32)
   gchar *buffer;
   size_t size;
   gssize read_size;
@@ -2065,7 +2066,11 @@ g_file_read_link (const gchar  *filename,
   
   while (TRUE) 
     {
+#ifndef G_OS_WIN32
       read_size = readlink (filename, buffer, size);
+#else
+      read_size = g_win32_readlink_utf8 (filename, buffer, size);
+#endif
       if (read_size < 0)
         {
           int saved_errno = errno;
diff --git a/glib/glib-private.c b/glib/glib-private.c
index 290cc8d..d2aea9f 100644
--- a/glib/glib-private.c
+++ b/glib/glib-private.c
@@ -48,6 +48,13 @@ glib__private__ (void)
     g_dir_new_from_dirp,
 
     glib_init,
+
+#ifdef G_OS_WIN32
+    g_win32_stat_utf8,
+    g_win32_lstat_utf8,
+    g_win32_readlink_utf8,
+    g_win32_fstat,
+#endif
   };
 
   return &table;
diff --git a/glib/glib-private.h b/glib/glib-private.h
index 84f42df..31acd93 100644
--- a/glib/glib-private.h
+++ b/glib/glib-private.h
@@ -20,6 +20,7 @@
 
 #include <glib.h>
 #include "gwakeup.h"
+#include "gstdioprivate.h"
 
 #if defined(__GNUC__)
 # define _g_alignof(type) (__alignof__ (type))
@@ -64,6 +65,23 @@ typedef struct {
   /* See glib-init.c */
   void                  (* glib_init)                   (void);
 
+  /* See gstdio.c */
+#ifdef G_OS_WIN32
+  int                   (* g_win32_stat_utf8)           (const gchar       *filename,
+                                                         GWin32PrivateStat *buf);
+
+  int                   (* g_win32_lstat_utf8)          (const gchar       *filename,
+                                                         GWin32PrivateStat *buf);
+
+  int                   (* g_win32_readlink_utf8)       (const gchar *filename,
+                                                         gchar       *buf,
+                                                         gsize        buf_size);
+
+  int                   (* g_win32_fstat)               (int                fd,
+                                                         GWin32PrivateStat *buf);
+#endif
+
+
   /* Add other private functions here, initialize them in glib-private.c */
 } GLibPrivateVTable;
 
diff --git a/glib/gstdio.c b/glib/gstdio.c
index 1c25870..9ce36f6 100644
--- a/glib/gstdio.c
+++ b/glib/gstdio.c
@@ -42,7 +42,7 @@
 #endif
 
 #include "gstdio.h"
-
+#include "gstdioprivate.h"
 
 #if !defined (G_OS_UNIX) && !defined (G_OS_WIN32)
 #error Please port this to your operating system
@@ -53,6 +53,538 @@
 #define _wstat _wstat32
 #endif
 
+#if defined (G_OS_WIN32)
+
+/* We can't include Windows DDK and Windows SDK simultaneously,
+ * so let's copy this here from MinGW-w64 DDK.
+ * The structure is ultimately documented here:
+ * https://msdn.microsoft.com/en-us/library/ff552012(v=vs.85).aspx
+ */
+typedef struct _REPARSE_DATA_BUFFER
+{
+  ULONG  ReparseTag;
+  USHORT ReparseDataLength;
+  USHORT Reserved;
+  union
+  {
+    struct
+    {
+      USHORT SubstituteNameOffset;
+      USHORT SubstituteNameLength;
+      USHORT PrintNameOffset;
+      USHORT PrintNameLength;
+      ULONG  Flags;
+      WCHAR  PathBuffer[1];
+    } SymbolicLinkReparseBuffer;
+    struct
+    {
+      USHORT SubstituteNameOffset;
+      USHORT SubstituteNameLength;
+      USHORT PrintNameOffset;
+      USHORT PrintNameLength;
+      WCHAR  PathBuffer[1];
+    } MountPointReparseBuffer;
+    struct
+    {
+      UCHAR  DataBuffer[1];
+    } GenericReparseBuffer;
+  };
+} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
+
+static int
+w32_error_to_errno (DWORD error_code)
+{
+  switch (error_code)
+    {
+    case ERROR_ACCESS_DENIED:
+      return EACCES;
+      break;
+    case ERROR_INVALID_HANDLE:
+      return EBADF;
+      break;
+    case ERROR_INVALID_FUNCTION:
+      return EFAULT;
+      break;
+    case ERROR_FILE_NOT_FOUND:
+      return ENOENT;
+      break;
+    case ERROR_PATH_NOT_FOUND:
+      return ENOENT; /* or ELOOP, or ENAMETOOLONG */
+      break;
+    case ERROR_NOT_ENOUGH_MEMORY:
+    case ERROR_OUTOFMEMORY:
+      return ENOMEM;
+      break;
+    default:
+      return EIO;
+      break;
+    }
+}
+
+static int
+_g_win32_stat_utf16_no_trailing_slashes (const gunichar2    *filename,
+                                         int                 fd,
+                                         GWin32PrivateStat  *buf,
+                                         gboolean            for_symlink)
+{
+  HANDLE file_handle;
+  gboolean succeeded_so_far;
+  DWORD error_code;
+  struct __stat64 statbuf;
+  BY_HANDLE_FILE_INFORMATION handle_info;
+  FILE_STANDARD_INFO std_info;
+  WIN32_FIND_DATAW finddata;
+  DWORD immediate_attributes;
+  gboolean is_symlink = FALSE;
+  gboolean is_directory;
+  DWORD open_flags;
+  wchar_t *filename_target = NULL;
+  int result;
+
+  if (fd < 0)
+    {
+      immediate_attributes = GetFileAttributesW (filename);
+
+      if (immediate_attributes == INVALID_FILE_ATTRIBUTES)
+        {
+          error_code = GetLastError ();
+          errno = w32_error_to_errno (error_code);
+
+          return -1;
+        }
+
+      is_symlink = (immediate_attributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT;
+      is_directory = (immediate_attributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY;
+
+      open_flags = FILE_ATTRIBUTE_NORMAL;
+
+      if (for_symlink && is_symlink)
+        open_flags |= FILE_FLAG_OPEN_REPARSE_POINT;
+
+      if (is_directory)
+        open_flags |= FILE_FLAG_BACKUP_SEMANTICS;
+
+      file_handle = CreateFileW (filename, FILE_READ_ATTRIBUTES,
+                                 FILE_SHARE_READ, NULL, OPEN_EXISTING,
+                                 open_flags,
+                                 NULL);
+
+      if (file_handle == INVALID_HANDLE_VALUE)
+        {
+          error_code = GetLastError ();
+          errno = w32_error_to_errno (error_code);
+          return -1;
+        }
+    }
+  else
+    {
+      file_handle = (HANDLE) _get_osfhandle (fd);
+
+      if (file_handle == INVALID_HANDLE_VALUE)
+        return -1;
+    }
+
+  succeeded_so_far = GetFileInformationByHandle (file_handle,
+                                                 &handle_info);
+  error_code = GetLastError ();
+
+  if (succeeded_so_far)
+    {
+      succeeded_so_far = GetFileInformationByHandleEx (file_handle,
+                                                       FileStandardInfo,
+                                                       &std_info,
+                                                       sizeof (std_info));
+      error_code = GetLastError ();
+    }
+
+  if (!succeeded_so_far)
+    {
+      CloseHandle (file_handle);
+      errno = w32_error_to_errno (error_code);
+      return -1;
+    }
+
+  /* It's tempting to use GetFileInformationByHandleEx(FileAttributeTagInfo),
+   * but it always reports that the ReparseTag is 0.
+   */
+  if (fd < 0)
+    {
+      HANDLE tmp = FindFirstFileW (filename,
+                                   &finddata);
+
+      if (tmp == INVALID_HANDLE_VALUE)
+        {
+          error_code = GetLastError ();
+          errno = w32_error_to_errno (error_code);
+          CloseHandle (file_handle);
+          return -1;
+        }
+
+      FindClose (tmp);
+
+      if (is_symlink && !for_symlink)
+        {
+          /* If filename is a symlink, _wstat64 obtains information about
+           * the symlink (except that st_size will be 0).
+           * To get information about the target we need to resolve
+           * the symlink first. And we need _wstat64() to get st_dev,
+           * it's a bother to try finding it ourselves.
+           */
+          DWORD filename_target_len;
+          DWORD new_len;
+
+          /* Just in case, give it a real memory location instead of NULL */
+          new_len = GetFinalPathNameByHandleW (file_handle,
+                                               (wchar_t *) &filename_target_len,
+                                               0,
+                                               FILE_NAME_NORMALIZED);
+
+#define SANE_LIMIT 1024 * 10
+          if (new_len >= SANE_LIMIT)
+#undef SANE_LIMIT
+            {
+              new_len = 0;
+              error_code = ERROR_BUFFER_OVERFLOW;
+            }
+          else if (new_len == 0)
+            {
+              error_code = GetLastError ();
+            }
+
+          if (new_len > 0)
+            {
+              const wchar_t *extended_prefix = L"\\\\?\\";
+              const gsize    extended_prefix_len = wcslen (extended_prefix);
+              const gsize    extended_prefix_len_bytes = sizeof (wchar_t) * extended_prefix_len;
+
+              /* Pretend that new_len doesn't count the terminating NUL char,
+               * and ask for a bit more space than is needed.
+               */
+              filename_target_len = new_len + 5;
+              filename_target = g_malloc (filename_target_len * sizeof (wchar_t));
+
+              new_len = GetFinalPathNameByHandleW (file_handle,
+                                                   filename_target,
+                                                   filename_target_len,
+                                                   FILE_NAME_NORMALIZED);
+
+              /* filename_target_len is already larger than needed,
+               * new_len should be smaller than that, even if the size
+               * is off by 1 for some reason.
+               */
+              if (new_len >= filename_target_len - 1)
+                {
+                  new_len = 0;
+                  error_code = ERROR_BUFFER_OVERFLOW;
+                  g_clear_pointer (&filename_target, g_free);
+                }
+              /* GetFinalPathNameByHandle() is documented to return extended paths,
+               * strip the extended prefix.
+               */
+              else if (new_len > extended_prefix_len &&
+                       memcmp (filename_target, extended_prefix, extended_prefix_len_bytes) == 0)
+                {
+                  new_len -= extended_prefix_len;
+                  memmove (filename_target,
+                           filename_target + extended_prefix_len,
+                           (new_len + 1) * sizeof (wchar_t));
+                }
+            }
+
+          if (new_len == 0)
+            succeeded_so_far = FALSE;
+        }
+
+      CloseHandle (file_handle);
+    }
+  /* else if fd >= 0 the file_handle was obtained via _get_osfhandle()
+   * and must not be closed, it is owned by fd.
+   */
+
+  if (!succeeded_so_far)
+    {
+      errno = w32_error_to_errno (error_code);
+      return -1;
+    }
+
+  if (fd < 0)
+    result = _wstat64 (filename_target != NULL ? filename_target : filename, &statbuf);
+  else
+    result = _fstat64 (fd, &statbuf);
+
+  if (result != 0)
+    {
+      int errsv = errno;
+
+      g_free (filename_target);
+      errno = errsv;
+
+      return -1;
+    }
+
+  g_free (filename_target);
+
+  buf->st_dev = statbuf.st_dev;
+  buf->st_mode = statbuf.st_mode;
+  buf->volume_serial = handle_info.dwVolumeSerialNumber;
+  buf->file_index = (((guint64) handle_info.nFileIndexHigh) << 32) | handle_info.nFileIndexLow;
+  /* Note that immediate_attributes is for the symlink
+   * (if it's a symlink), while handle_info contains info
+   * about the symlink or the target, depending on the flags
+   * we used earlier.
+   */
+  buf->attributes = handle_info.dwFileAttributes;
+  buf->st_nlink = handle_info.nNumberOfLinks;
+  buf->st_size = (((guint64) handle_info.nFileSizeHigh) << 32) | handle_info.nFileSizeLow;
+  buf->allocated_size = std_info.AllocationSize.QuadPart;
+
+  if (fd < 0 && buf->attributes & FILE_ATTRIBUTE_REPARSE_POINT)
+    buf->reparse_tag = finddata.dwReserved0;
+  else
+    buf->reparse_tag = 0;
+
+  buf->st_ctime = statbuf.st_ctime;
+  buf->st_atime = statbuf.st_atime;
+  buf->st_mtime = statbuf.st_mtime;
+
+  return 0;
+}
+
+static int
+_g_win32_stat_utf8 (const gchar       *filename,
+                    GWin32PrivateStat *buf,
+                    gboolean           for_symlink)
+{
+  wchar_t *wfilename;
+  int result;
+  gsize len;
+
+  len = strlen (filename);
+
+  while (len > 0 && G_IS_DIR_SEPARATOR (filename[len - 1]))
+    len--;
+
+  if (len <= 0 ||
+      (g_path_is_absolute (filename) && len <= g_path_skip_root (filename) - filename))
+    len = strlen (filename);
+
+  wfilename = g_utf8_to_utf16 (filename, len, NULL, NULL, NULL);
+
+  if (wfilename == NULL)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  result = _g_win32_stat_utf16_no_trailing_slashes (wfilename, -1, buf, for_symlink);
+
+  g_free (wfilename);
+
+  return result;
+}
+
+int
+g_win32_stat_utf8 (const gchar       *filename,
+                   GWin32PrivateStat *buf)
+{
+  return _g_win32_stat_utf8 (filename, buf, FALSE);
+}
+
+int
+g_win32_lstat_utf8 (const gchar       *filename,
+                    GWin32PrivateStat *buf)
+{
+  return _g_win32_stat_utf8 (filename, buf, TRUE);
+}
+
+int
+g_win32_fstat (int                fd,
+               GWin32PrivateStat *buf)
+{
+  return _g_win32_stat_utf16_no_trailing_slashes (NULL, fd, buf, FALSE);
+}
+
+static int
+_g_win32_readlink_utf16_raw (const gunichar2 *filename,
+                             gunichar2       *buf,
+                             gsize            buf_size)
+{
+  DWORD returned_bytes;
+  BYTE returned_data[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; /* This is 16k, by the way */
+  HANDLE h;
+  DWORD attributes;
+  REPARSE_DATA_BUFFER *rep_buf;
+  DWORD to_copy;
+  DWORD error_code;
+
+  if (buf_size > G_MAXSIZE / sizeof (wchar_t))
+    {
+      /* "buf_size * sizeof (wchar_t)" overflows */
+      errno = EFAULT;
+      return -1;
+    }
+
+  if ((attributes = GetFileAttributesW (filename)) == 0)
+    {
+      error_code = GetLastError ();
+      errno = w32_error_to_errno (error_code);
+      return -1;
+    }
+
+  if ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  /* To read symlink target we need to open the file as a reparse
+   * point and use DeviceIoControl() on it.
+   */
+  h = CreateFileW (filename,
+                   FILE_READ_ATTRIBUTES | SYNCHRONIZE | GENERIC_READ,
+                   FILE_SHARE_READ, NULL, OPEN_EXISTING,
+                   FILE_ATTRIBUTE_NORMAL
+                   | FILE_FLAG_OPEN_REPARSE_POINT
+                   | (attributes & FILE_ATTRIBUTE_DIRECTORY ? FILE_FLAG_BACKUP_SEMANTICS : 0),
+                   NULL);
+
+  if (h == INVALID_HANDLE_VALUE)
+    {
+      error_code = GetLastError ();
+      errno = w32_error_to_errno (error_code);
+      return -1;
+    }
+
+  if (!DeviceIoControl (h, FSCTL_GET_REPARSE_POINT, NULL, 0,
+                        returned_data, MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
+                        &returned_bytes, NULL))
+    {
+      error_code = GetLastError ();
+      errno = w32_error_to_errno (error_code);
+      CloseHandle (h);
+      return -1;
+    }
+
+  rep_buf = (REPARSE_DATA_BUFFER *) returned_data;
+  to_copy = 0;
+
+  if (rep_buf->ReparseTag == IO_REPARSE_TAG_SYMLINK)
+    {
+      to_copy = rep_buf->SymbolicLinkReparseBuffer.SubstituteNameLength;
+
+      if (to_copy > buf_size * sizeof (wchar_t))
+        to_copy = buf_size * sizeof (wchar_t);
+
+      memcpy (buf,
+              &((BYTE *) 
rep_buf->SymbolicLinkReparseBuffer.PathBuffer)[rep_buf->SymbolicLinkReparseBuffer.SubstituteNameOffset],
+              to_copy);
+    }
+  else if (rep_buf->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
+    {
+      to_copy = rep_buf->MountPointReparseBuffer.SubstituteNameLength;
+
+      if (to_copy > buf_size * sizeof (wchar_t))
+        to_copy = buf_size * sizeof (wchar_t);
+
+      memcpy (buf,
+              &((BYTE *) 
rep_buf->MountPointReparseBuffer.PathBuffer)[rep_buf->MountPointReparseBuffer.SubstituteNameOffset],
+              to_copy);
+    }
+
+  CloseHandle (h);
+
+  return to_copy;
+}
+
+static int
+_g_win32_readlink_utf16 (const gunichar2 *filename,
+                         gunichar2       *buf,
+                         gsize            buf_size)
+{
+  const wchar_t *ntobjm_prefix = L"\\??\\";
+  const gsize    ntobjm_prefix_len_unichar2 = wcslen (ntobjm_prefix);
+  const gsize    ntobjm_prefix_len_bytes = sizeof (gunichar2) * ntobjm_prefix_len_unichar2;
+  int            result = _g_win32_readlink_utf16_raw (filename, buf, buf_size);
+
+  if (result <= 0)
+    return result;
+
+  /* Ensure that output is a multiple of sizeof (gunichar2),
+   * cutting any trailing partial gunichar2, if present.
+   */
+  result -= result % sizeof (gunichar2);
+
+  if (result <= 0)
+    return result;
+
+  /* DeviceIoControl () tends to return filenames as NT Object Manager
+   * names , i.e. "\\??\\C:\\foo\\bar".
+   * Remove the leading 4-byte \??\ prefix, as glib (as well as many W32 API
+   * functions) is unprepared to deal with it.
+   */
+  if (result > ntobjm_prefix_len_bytes &&
+      memcmp (buf, ntobjm_prefix, ntobjm_prefix_len_bytes) == 0)
+    {
+      result -= ntobjm_prefix_len_bytes;
+      memmove (buf, buf + ntobjm_prefix_len_unichar2, result);
+    }
+
+  return result;
+}
+
+int
+g_win32_readlink_utf8 (const gchar *filename,
+                       gchar       *buf,
+                       gsize        buf_size)
+{
+  wchar_t *wfilename;
+  int result;
+
+  wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL);
+
+  if (wfilename == NULL)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  result = _g_win32_readlink_utf16 (wfilename, (gunichar2 *) buf, buf_size);
+
+  g_free (wfilename);
+
+  if (result > 0)
+    {
+      glong tmp_len;
+      gchar *tmp = g_utf16_to_utf8 ((const gunichar2 *) buf,
+                                    result / sizeof (gunichar2),
+                                    NULL,
+                                    &tmp_len,
+                                    NULL);
+
+      if (tmp == NULL)
+        {
+          errno = EINVAL;
+          return -1;
+        }
+
+      if (tmp_len > buf_size - 1)
+        tmp_len = buf_size - 1;
+
+      memcpy (buf, tmp, tmp_len);
+      /* readlink() doesn't NUL-terminate, but we do.
+       * To be compliant, however, we return the
+       * number of bytes without the NUL-terminator.
+       */
+      buf[tmp_len] = '\0';
+      result = tmp_len;
+      g_free (tmp);
+    }
+
+  return result;
+}
+
+#endif
+
 /**
  * g_access:
  * @filename: (type filename): a pathname in the GLib file name encoding
@@ -482,30 +1014,21 @@ g_stat (const gchar *filename,
        GStatBuf    *buf)
 {
 #ifdef G_OS_WIN32
-  wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL);
-  int retval;
-  int save_errno;
-  int len;
-
-  if (wfilename == NULL)
-    {
-      errno = EINVAL;
-      return -1;
-    }
-
-  len = wcslen (wfilename);
-  while (len > 0 && G_IS_DIR_SEPARATOR (wfilename[len-1]))
-    len--;
-  if (len > 0 &&
-      (!g_path_is_absolute (filename) || len > g_path_skip_root (filename) - filename))
-    wfilename[len] = '\0';
-
-  retval = _wstat (wfilename, buf);
-  save_errno = errno;
+  GWin32PrivateStat w32_buf;
+  int retval = g_win32_stat_utf8 (filename, &w32_buf);
+
+  buf->st_dev = w32_buf.st_dev;
+  buf->st_ino = w32_buf.st_ino;
+  buf->st_mode = w32_buf.st_mode;
+  buf->st_nlink = w32_buf.st_nlink;
+  buf->st_uid = w32_buf.st_uid;
+  buf->st_gid = w32_buf.st_gid;
+  buf->st_rdev = w32_buf.st_dev;
+  buf->st_size = w32_buf.st_size;
+  buf->st_atime = w32_buf.st_atime;
+  buf->st_mtime = w32_buf.st_mtime;
+  buf->st_ctime = w32_buf.st_ctime;
 
-  g_free (wfilename);
-
-  errno = save_errno;
   return retval;
 #else
   return stat (filename, buf);
@@ -539,6 +1062,23 @@ g_lstat (const gchar *filename,
 #ifdef HAVE_LSTAT
   /* This can't be Win32, so don't do the widechar dance. */
   return lstat (filename, buf);
+#elif defined (G_OS_WIN32)
+  GWin32PrivateStat w32_buf;
+  int retval = g_win32_lstat_utf8 (filename, &w32_buf);
+
+  buf->st_dev = w32_buf.st_dev;
+  buf->st_ino = w32_buf.st_ino;
+  buf->st_mode = w32_buf.st_mode;
+  buf->st_nlink = w32_buf.st_nlink;
+  buf->st_uid = w32_buf.st_uid;
+  buf->st_gid = w32_buf.st_gid;
+  buf->st_rdev = w32_buf.st_dev;
+  buf->st_size = w32_buf.st_size;
+  buf->st_atime = w32_buf.st_atime;
+  buf->st_mtime = w32_buf.st_mtime;
+  buf->st_ctime = w32_buf.st_ctime;
+
+  return retval;
 #else
   return g_stat (filename, buf);
 #endif
diff --git a/glib/gstdioprivate.h b/glib/gstdioprivate.h
new file mode 100644
index 0000000..a100abb
--- /dev/null
+++ b/glib/gstdioprivate.h
@@ -0,0 +1,65 @@
+/* gstdioprivate.h - Private GLib stdio functions
+ *
+ * Copyright 2017 Руслан Ижбулатов
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __G_STDIOPRIVATE_H__
+#define __G_STDIOPRIVATE_H__
+
+G_BEGIN_DECLS
+
+#if defined (G_OS_WIN32)
+
+struct _GWin32PrivateStat
+{
+  guint32 volume_serial;
+  guint64 file_index;
+  guint64 attributes;
+  guint64 allocated_size;
+  guint32 reparse_tag;
+
+  guint32 st_dev;
+  guint32 st_ino;
+  guint16 st_mode;
+  guint16 st_uid;
+  guint16 st_gid;
+  guint32 st_nlink;
+  guint64 st_size;
+  guint64 st_ctime;
+  guint64 st_atime;
+  guint64 st_mtime;
+};
+
+typedef struct _GWin32PrivateStat GWin32PrivateStat;
+
+int g_win32_stat_utf8     (const gchar       *filename,
+                           GWin32PrivateStat *buf);
+
+int g_win32_lstat_utf8    (const gchar       *filename,
+                           GWin32PrivateStat *buf);
+
+int g_win32_readlink_utf8 (const gchar *filename,
+                           gchar       *buf,
+                           gsize        buf_size);
+
+int g_win32_fstat         (int                fd,
+                           GWin32PrivateStat *buf);
+
+#endif
+
+G_END_DECLS
+
+#endif /* __G_STDIOPRIVATE_H__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]