summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Withnall <philip@tecnocode.co.uk>2018-10-02 08:55:45 +0000
committerPhilip Withnall <philip@tecnocode.co.uk>2018-10-02 08:55:45 +0000
commit32e049b761d7ea2b9785a3428cf3b60119d14ed9 (patch)
treedfc65fcde428702a37231578296d542948d2c7a4
parent846a61457a63cbf2c88608327ca0c98cf4eecd44 (diff)
parentb9f91437bb3e6c7d6c0b3bf5b34802cff7c3aaa6 (diff)
downloadglib-32e049b761d7ea2b9785a3428cf3b60119d14ed9.tar.gz
Merge branch 'win32-gstat-for-ucrt' into 'master'
W32: gstat fixes for ucrt Closes #1452 See merge request GNOME/glib!257
-rw-r--r--glib/gstdio.c153
-rw-r--r--glib/tests/fileutils.c12
2 files changed, 160 insertions, 5 deletions
diff --git a/glib/gstdio.c b/glib/gstdio.c
index 1a56b1932..4243fd5a7 100644
--- a/glib/gstdio.c
+++ b/glib/gstdio.c
@@ -36,6 +36,7 @@
#include <direct.h>
#include <io.h>
#include <sys/utime.h>
+#include <stdlib.h> /* for MB_CUR_MAX */
#else
#include <utime.h>
#include <errno.h>
@@ -123,6 +124,140 @@ w32_error_to_errno (DWORD error_code)
#include "gstdio-private.c"
+/* From
+ * https://support.microsoft.com/en-ca/help/167296/how-to-convert-a-unix-time-t-to-a-win32-filetime-or-systemtime
+ * FT = UT * 10000000 + 116444736000000000.
+ * Therefore:
+ * UT = (FT - 116444736000000000) / 10000000.
+ */
+static gint64
+_g_win32_filetime_to_unix_time (FILETIME *ft)
+{
+ gint64 result;
+ /* 1 unit of FILETIME is 100ns */
+ const gint64 hundreds_of_usec_per_sec = 10000000;
+ /* The difference between January 1, 1601 UTC (FILETIME epoch) and UNIX epoch
+ * in hundreds of nanoseconds.
+ */
+ const gint64 filetime_unix_epoch_offset = 116444736000000000;
+
+ result = ((gint64) ft->dwLowDateTime) | (((gint64) ft->dwHighDateTime) << 32);
+ return (result - filetime_unix_epoch_offset) / hundreds_of_usec_per_sec;
+}
+
+# ifdef _MSC_VER
+# ifndef S_IXUSR
+# define _S_IRUSR _S_IREAD
+# define _S_IWUSR _S_IWRITE
+# define _S_IXUSR _S_IEXEC
+# define S_IRUSR _S_IRUSR
+# define S_IWUSR _S_IWUSR
+# define S_IXUSR _S_IXUSR
+# define S_IRGRP (S_IRUSR >> 3)
+# define S_IWGRP (S_IWUSR >> 3)
+# define S_IXGRP (S_IXUSR >> 3)
+# define S_IROTH (S_IRGRP >> 3)
+# define S_IWOTH (S_IWGRP >> 3)
+# define S_IXOTH (S_IXGRP >> 3)
+# endif
+# ifndef S_ISDIR
+# define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
+# endif
+# endif
+
+static int
+_g_win32_fill_statbuf_from_handle_info (const wchar_t *filename,
+ const wchar_t *filename_target,
+ BY_HANDLE_FILE_INFORMATION *handle_info,
+ struct __stat64 *statbuf)
+{
+ wchar_t drive_letter_w = 0;
+ size_t drive_letter_size = MB_CUR_MAX;
+ char *drive_letter = _alloca (drive_letter_size);
+
+ /* If filename (target or link) is absolute,
+ * then use the drive letter from it as-is.
+ */
+ if (filename_target != NULL &&
+ filename_target[0] != L'\0' &&
+ filename_target[1] == L':')
+ drive_letter_w = filename_target[0];
+ else if (filename[0] != L'\0' &&
+ filename[1] == L':')
+ drive_letter_w = filename[0];
+
+ if (drive_letter_w > 0 &&
+ iswalpha (drive_letter_w) &&
+ iswascii (drive_letter_w) &&
+ wctomb (drive_letter, drive_letter_w) == 1)
+ statbuf->st_dev = toupper (drive_letter[0]) - 'A'; /* 0 means A: drive */
+ else
+ /* Otherwise use the PWD drive.
+ * Return value of 0 gives us 0 - 1 = -1,
+ * which is the "no idea" value for st_dev.
+ */
+ statbuf->st_dev = _getdrive () - 1;
+
+ statbuf->st_rdev = statbuf->st_dev;
+ /* Theoretically, it's possible to set it for ext-FS. No idea how.
+ * Meaningless for all filesystems that Windows normally uses.
+ */
+ statbuf->st_ino = 0;
+ statbuf->st_mode = 0;
+
+ if ((handle_info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
+ statbuf->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH;
+ else
+ statbuf->st_mode |= S_IFREG;
+ /* No idea what S_IFCHR means here. */
+ /* S_IFIFO is not even mentioned in MSDN */
+ /* S_IFBLK is also not mentioned */
+
+ /* The aim here is to reproduce MS stat() behaviour,
+ * even if it's braindead.
+ */
+ statbuf->st_mode |= S_IRUSR | S_IRGRP | S_IROTH;
+ if ((handle_info->dwFileAttributes & FILE_ATTRIBUTE_READONLY) != FILE_ATTRIBUTE_READONLY)
+ statbuf->st_mode |= S_IWUSR | S_IWGRP | S_IWOTH;
+
+ if (!S_ISDIR (statbuf->st_mode))
+ {
+ const wchar_t *name;
+ const wchar_t *dot = NULL;
+
+ if (filename_target != NULL)
+ name = filename_target;
+ else
+ name = filename;
+
+ do
+ {
+ wchar_t *last_dot = wcschr (name, L'.');
+ if (last_dot == NULL)
+ break;
+ dot = last_dot;
+ name = &last_dot[1];
+ }
+ while (TRUE);
+
+ if ((dot != NULL &&
+ (wcsicmp (dot, L".exe") == 0 ||
+ wcsicmp (dot, L".com") == 0 ||
+ wcsicmp (dot, L".bat") == 0 ||
+ wcsicmp (dot, L".cmd") == 0)))
+ statbuf->st_mode |= S_IXUSR | S_IXGRP | S_IXOTH;
+ }
+
+ statbuf->st_nlink = handle_info->nNumberOfLinks;
+ statbuf->st_uid = statbuf->st_gid = 0;
+ statbuf->st_size = (((guint64) handle_info->nFileSizeHigh) << 32) | handle_info->nFileSizeLow;
+ statbuf->st_ctime = _g_win32_filetime_to_unix_time (&handle_info->ftCreationTime);
+ statbuf->st_mtime = _g_win32_filetime_to_unix_time (&handle_info->ftLastWriteTime);
+ statbuf->st_atime = _g_win32_filetime_to_unix_time (&handle_info->ftLastAccessTime);
+
+ return 0;
+}
+
static int
_g_win32_stat_utf16_no_trailing_slashes (const gunichar2 *filename,
int fd,
@@ -232,11 +367,9 @@ _g_win32_stat_utf16_no_trailing_slashes (const gunichar2 *filename,
if (is_symlink && !for_symlink)
{
- /* If filename is a symlink, _wstat64 obtains information about
- * the symlink (except that st_size will be 0).
+ /* If filename is a symlink, but we need the target.
* 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.
+ * the symlink first.
*/
DWORD filename_target_len;
DWORD new_len;
@@ -326,8 +459,18 @@ _g_win32_stat_utf16_no_trailing_slashes (const gunichar2 *filename,
return -1;
}
+ /*
+ * We can't use _wstat64() here, because with UCRT it now gives
+ * information about the target, even if we want information about
+ * the link itself (unlike MSVCRT, which gave information about
+ * the link, and if we needed information about the target we were
+ * able to resolve it by ourselves prior to calling _wstat64()).
+ */
if (fd < 0)
- result = _wstat64 (filename_target != NULL ? filename_target : filename, &statbuf);
+ result = _g_win32_fill_statbuf_from_handle_info (filename,
+ filename_target,
+ &handle_info,
+ &statbuf);
else
result = _fstat64 (fd, &statbuf);
diff --git a/glib/tests/fileutils.c b/glib/tests/fileutils.c
index c2a553bd5..3ef6615d0 100644
--- a/glib/tests/fileutils.c
+++ b/glib/tests/fileutils.c
@@ -913,9 +913,12 @@ test_stdio_wrappers (void)
cwd = g_get_current_dir ();
path = g_build_filename (cwd, "mkdir-test", NULL);
g_free (cwd);
+#ifndef G_OS_WIN32
+ /* 0666 on directories means nothing to Windows, it only obeys ACLs */
ret = g_chdir (path);
g_assert_cmpint (errno, ==, EACCES);
g_assert_cmpint (ret, ==, -1);
+#endif
ret = g_chmod (path, 0777);
g_assert_cmpint (ret, ==, 0);
ret = g_chdir (path);
@@ -945,6 +948,15 @@ test_stdio_wrappers (void)
g_close (ret, &error);
g_assert_no_error (error);
+#ifdef G_OS_WIN32
+ /* On Windows the 5 permission bit results in a read-only file
+ * that cannot be modified in any way (attribute changes included).
+ * Remove the read-only attribute via chmod().
+ */
+ ret = g_chmod ("test-create", 0666);
+ g_assert_cmpint (ret, ==, 0);
+#endif
+
ut.actime = ut.modtime = (time_t)0;
ret = g_utime ("test-create", &ut);
g_assert_cmpint (ret, ==, 0);