From a137bb56674eca0b8d2592e870a1d2595a9eb934 Mon Sep 17 00:00:00 2001
From: Jeremy Tan <jtanx@outlook.com>
Date: Sat, 15 Jun 2019 14:40:52 +1000
Subject: [PATCH] Update g_fopen, g_open and g_creat to open with
 FILE_SHARE_DELETE sharing access

Very loosely based on the patches in
https://gitlab.gnome.org/GNOME/glib/issues/539

but with much more robust file mode parsing and error handling.
Implements most of the definition as provided on msdn for fopen.

If charcter conversion is requested (via _O_U8TEXT, _O_U16TEXT, _O_WTEXT or any
of the 'ccs=utf8/utf-16le/unicode'), g_fopen and g_open fall back to use
_wfopen and _wopen respectively, as there is no easy way to replicate the
expected behaviour of those modes, particularly around BOM handling.
---
 glib/gstdio.c | 304 ++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 267 insertions(+), 37 deletions(-)

diff --git a/glib/gstdio.c b/glib/gstdio.c
index 653c8a3a1..c34440424 100644
--- a/glib/gstdio.c
+++ b/glib/gstdio.c
@@ -1035,21 +1035,146 @@ g_open (const gchar *filename,
 	int          mode)
 {
 #ifdef G_OS_WIN32
-  wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL);
+  HANDLE file_handle;
+  DWORD disposition;
+  DWORD desired_access;
+  DWORD flags_and_attributes;
+  DWORD last_error;
+  SECURITY_ATTRIBUTES security_attributes;
+  wchar_t *wfilename;
+  int r;
   int retval;
   int save_errno;
-    
+
+  wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL);
   if (wfilename == NULL)
     {
       errno = EINVAL;
       return -1;
     }
 
-  retval = _wopen (wfilename, flags, mode);
-  save_errno = errno;
+  /* If any of these flags are specified, fall back to _wopen */
+  if (flags & (_O_U8TEXT | _O_U16TEXT | _O_WTEXT))
+    {
+      retval = _wopen (wfilename, flags, mode);
+      save_errno = errno;
+
+      g_free (wfilename);
+      errno = save_errno;
+      return retval;
+    }
+
+  /* Set up the access mode; exactly one of these must be specified */
+  switch (flags & (_O_RDONLY | _O_WRONLY | _O_RDWR))
+    {
+    case _O_RDONLY:
+      desired_access = GENERIC_READ;
+      break;
+    case _O_WRONLY:
+      desired_access = GENERIC_WRITE;
+      break;
+    case _O_RDWR:
+      desired_access = GENERIC_READ | GENERIC_WRITE;
+      break;
+    default:
+      g_free (wfilename);
+      errno = EINVAL;
+      return -1;
+    }
+
+  /* Parse the creation disposition */
+  switch (flags & (_O_CREAT | _O_EXCL | _O_TRUNC))
+    {
+    case _O_CREAT:
+      disposition = OPEN_ALWAYS;
+      break;
+    case _O_CREAT | _O_TRUNC:
+      disposition = CREATE_ALWAYS;
+      break;
+    case _O_CREAT | _O_EXCL:
+    case _O_CREAT | _O_TRUNC | _O_EXCL:
+      disposition = CREATE_NEW;
+      break;
+    case _O_TRUNC:
+    case _O_TRUNC | _O_EXCL:
+      disposition = TRUNCATE_EXISTING;
+      break;
+    default:
+      disposition = OPEN_EXISTING;
+    }
 
+  if (!(desired_access & GENERIC_WRITE) && disposition == TRUNCATE_EXISTING)
+    {
+      /* Must have GENERIC_WRITE to be able to truncate */
+      g_free (wfilename);
+      errno = EINVAL;
+      return -1;
+    }
+
+  /* Set up the security descriptor */
+  security_attributes.nLength = sizeof(security_attributes);
+  security_attributes.lpSecurityDescriptor = NULL;
+  security_attributes.bInheritHandle = (flags & _O_NOINHERIT) != _O_NOINHERIT;
+
+  flags_and_attributes = 0;
+  if ((flags & _O_CREAT) && !(mode & _S_IWRITE))
+    flags_and_attributes |= FILE_ATTRIBUTE_READONLY;
+  if (flags & _O_TEMPORARY)
+    {
+      flags_and_attributes |= FILE_FLAG_DELETE_ON_CLOSE;
+      desired_access |= DELETE;
+    }
+  if (flags & _O_SHORT_LIVED)
+    flags_and_attributes |= FILE_ATTRIBUTE_TEMPORARY;
+  if (flags & _O_SEQUENTIAL)
+    flags_and_attributes |= FILE_FLAG_SEQUENTIAL_SCAN;
+  if (flags & _O_RANDOM)
+    flags_and_attributes |= FILE_FLAG_RANDOM_ACCESS;
+
+  file_handle = CreateFileW (wfilename,
+                             desired_access,
+                             FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+                             &security_attributes,
+                             disposition,
+                             flags_and_attributes,
+                             NULL);
+  last_error = GetLastError ();
   g_free (wfilename);
 
+  if (file_handle == INVALID_HANDLE_VALUE)
+  {
+    errno = w32_error_to_errno (last_error);
+    return -1;
+  }
+
+  retval = _open_osfhandle ((intptr_t)file_handle, flags);
+  save_errno = errno;
+
+  if (retval == -1)
+    {
+      CloseHandle (file_handle);
+      errno = save_errno;
+      return -1;
+    }
+
+  flags &= _O_BINARY | _O_TEXT;
+  if (!flags)
+    /* No explicit specification; try the global mode */
+    flags = _fmode & (_O_BINARY | _O_TEXT);
+
+  if (flags & _O_BINARY)
+    r = _setmode (retval, _O_BINARY);
+  else
+    r = _setmode (retval, _O_TEXT);
+
+  if (r == -1)
+    {
+      save_errno = errno;
+      close (retval);
+      errno = save_errno;
+      return -1;
+    }
+
   errno = save_errno;
   return retval;
 #else
@@ -1098,23 +1223,7 @@ g_creat (const gchar *filename,
 	 int          mode)
 {
 #ifdef G_OS_WIN32
-  wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL);
-  int retval;
-  int save_errno;
-    
-  if (wfilename == NULL)
-    {
-      errno = EINVAL;
-      return -1;
-    }
-
-  retval = _wcreat (wfilename, mode);
-  save_errno = errno;
-
-  g_free (wfilename);
-
-  errno = save_errno;
-  return retval;
+  return g_open (filename, _O_WRONLY|_O_CREAT|_O_TRUNC, mode);
 #else
   return creat (filename, mode);
 #endif
@@ -1550,35 +1659,155 @@ g_fopen (const gchar *filename,
 	 const gchar *mode)
 {
 #ifdef G_OS_WIN32
-  wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL);
-  wchar_t *wmode;
-  FILE *retval;
+  const gchar *orig_mode = mode;
+  char filtered_mode[5];
+  char *filtered_mode_iter;
+  gboolean has_ccs;
+  gboolean has_commit;
+  int fd;
+  int flags;
   int save_errno;
+  FILE *retval;
 
-  if (wfilename == NULL)
+  if (filename == NULL || mode == NULL)
+    goto err_einval;
+
+  filtered_mode_iter = filtered_mode;
+
+  while (*mode == ' ') ++mode;
+
+  switch (*mode)
     {
-      errno = EINVAL;
-      return NULL;
+    case 'r':
+      flags = _O_RDONLY;
+      break;
+    case 'w':
+      flags = _O_WRONLY | _O_CREAT | _O_TRUNC;
+      break;
+    case 'a':
+      flags = _O_WRONLY | _O_CREAT | _O_APPEND;
+      break;
+    default:
+      goto err_einval;
     }
 
-  wmode = g_utf8_to_utf16 (mode, -1, NULL, NULL, NULL);
+  *filtered_mode_iter++ = *mode++;
 
-  if (wmode == NULL)
+#define CHECK_AND_ADD(chk, add) \
+  do { \
+    if (flags & (chk)) \
+      goto err_einval; \
+    flags |= (add); \
+  } while (0)
+
+  for (has_ccs = FALSE, has_commit = FALSE; *mode && !has_ccs; ++mode)
+    {
+    switch (*mode)
+      {
+      case '+':
+        CHECK_AND_ADD (_O_RDWR, _O_RDWR);
+        flags &= ~(_O_RDONLY | _O_WRONLY);
+        if ((filtered_mode_iter - filtered_mode) > 1)
+          {
+            *filtered_mode_iter++ = filtered_mode[1];
+            filtered_mode[1] = '+';
+          }
+        else
+          *filtered_mode_iter++ = '+';
+        break;
+      case 't':
+        CHECK_AND_ADD (_O_TEXT | _O_BINARY, _O_TEXT);
+        *filtered_mode_iter++ = 't';
+        break;
+      case 'b':
+        CHECK_AND_ADD (_O_TEXT | _O_BINARY, _O_BINARY);
+        *filtered_mode_iter++ = 'b';
+        break;
+      case 'N':
+        CHECK_AND_ADD (_O_NOINHERIT, _O_NOINHERIT);
+        break;
+      case 'S':
+        CHECK_AND_ADD (_O_SEQUENTIAL, _O_SEQUENTIAL);
+        break;
+      case 'R':
+        CHECK_AND_ADD (_O_RANDOM, _O_RANDOM);
+        break;
+      case 'T':
+        CHECK_AND_ADD (_O_SHORT_LIVED, _O_SHORT_LIVED);
+        break;
+      case 'D':
+        CHECK_AND_ADD (_O_TEMPORARY, _O_TEMPORARY);
+        break;
+      case 'x':
+        CHECK_AND_ADD (_O_EXCL, _O_EXCL);
+        break;
+
+      case 'c':
+      case 'n':
+        if (has_commit)
+          goto err_einval;
+        has_commit = TRUE;
+        *filtered_mode_iter++ = *mode;
+      /* fallthrough */
+      case ' ':
+        break;
+
+      case ',':
+        has_ccs = TRUE;
+        break;
+
+      default:
+        goto err_einval;
+      }
+    }
+
+#undef CHECK_AND_ADD
+
+  *filtered_mode_iter = 0;
+  while (*mode == ' ') ++mode;
+
+  /*
+   * If a ccs is specified, fall back to using _wfopen, as
+   * there's no easy way to handle this
+   */
+  if (has_ccs)
     {
+      wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL);
+      wchar_t *wmode = g_utf8_to_utf16 (orig_mode, -1, NULL, NULL, NULL);
+
+      _g_win32_fix_mode (wmode);
+      retval = _wfopen (wfilename, wmode);
+      save_errno = errno;
+
+      g_free (wmode);
       g_free (wfilename);
-      errno = EINVAL;
-      return NULL;
+
+      errno = save_errno;
+      return retval;
     }
 
-  _g_win32_fix_mode (wmode);
-  retval = _wfopen (wfilename, wmode);
-  save_errno = errno;
+  if (*mode)
+    goto err_einval;
 
-  g_free (wfilename);
-  g_free (wmode);
+  fd = g_open (filename, flags, (_S_IREAD | _S_IWRITE));
+
+  if (fd == -1)
+    /* 'errno' will have already been set by 'g_open()' */
+    return NULL;
+
+  retval = _fdopen (fd, filtered_mode);
+  if (retval == NULL)
+    {
+      save_errno = errno;
+      close (fd);
+      errno = save_errno;
+    }
 
-  errno = save_errno;
   return retval;
+
+err_einval:
+  errno = EINVAL;
+  return NULL;
 #else
   return fopen (filename, mode);
 #endif
@@ -1619,6 +1848,7 @@ g_freopen (const gchar *filename,
     }
 
   wmode = g_utf8_to_utf16 (mode, -1, NULL, NULL, NULL);
+  _g_win32_fix_mode (wmode);
 
   if (wmode == NULL)
     {
-- 
2.22.0

