/* Functions used by the Windows port of libgccjit.
   Copyright (C) 2020-2025 Free Software Foundation, Inc.
   Contributed by Nicolas Bertolo <nicolasbertolo@gmail.com>.

This file is part of GCC.

GCC 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, or (at your option)
any later version.

GCC 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 GCC; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  */

#include "config.h"

/* Required for rand_s */
#define _CRT_RAND_S

#include <cstdio>
#include <cstdint>

#include "jit-w32.h"

#include "libiberty.h"

#include <accctrl.h>
#include <aclapi.h>

namespace gcc {
namespace jit {
void
print_last_error (void)
{
  LPSTR psz = NULL;
  DWORD dwErrorCode;
  dwErrorCode = GetLastError();
  const DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
                                     | FORMAT_MESSAGE_IGNORE_INSERTS
                                     | FORMAT_MESSAGE_ALLOCATE_BUFFER
                                     | FORMAT_MESSAGE_MAX_WIDTH_MASK,
                                     NULL,
                                     dwErrorCode,
                                     MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                     reinterpret_cast<LPSTR>(&psz),
                                     0,
                                     NULL);
  if (cchMsg > 0)
    {
      fprintf (stderr, "%s\n", psz);
      LocalFree (psz);
    }
  else
    {
      fprintf (stderr, "Failed to retrieve error message string for error %lu\n",
               dwErrorCode);
    }
}

/* Helper function used for getting the SID belonging to the current user. */
static TOKEN_USER*
get_TOKEN_USER_current_user ()
{
  TOKEN_USER *result = NULL;

  HANDLE process_token = INVALID_HANDLE_VALUE;

  DWORD token_user_info_len;
  TOKEN_USER *token_user = NULL;

  /* Get current process access token. */
  if (!OpenProcessToken (GetCurrentProcess (), TOKEN_READ,
                         &process_token))
    return NULL;

  /* Get necessary buffer size. */
  if (!GetTokenInformation(process_token, TokenUser, NULL, 0, &token_user_info_len)
      && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
    goto cleanup;

  token_user = (TOKEN_USER*) new char[token_user_info_len];

  /* Get info about the user of the process */
  if (!GetTokenInformation (process_token, TokenUser, token_user,
                            token_user_info_len, &token_user_info_len))
      goto cleanup;

  result = token_user;

 cleanup:
  if (process_token != INVALID_HANDLE_VALUE)
    CloseHandle(process_token);

  if (token_user != NULL && result == NULL)
    delete[] (char*)token_user;

  return result;
}

/* Helper function to create a directory with permissions such that only the
  current user can access it. */
static bool
create_directory_for_current_user (const char * path)
{
  PACL pACL = NULL;
  EXPLICIT_ACCESS ea;
  SECURITY_ATTRIBUTES sa;
  SECURITY_DESCRIPTOR SD;
  DWORD dwRes;
  bool result = true;
  TOKEN_USER *token_user = NULL;

  token_user = get_TOKEN_USER_current_user();
  if (!token_user)
    return false;

  memset (&ea, 0, sizeof (EXPLICIT_ACCESS));
  ea.grfAccessPermissions = GENERIC_ALL; /* Access to all. */
  ea.grfAccessMode = SET_ACCESS; /* Set access and revoke everything else. */
  /* This is necessary for the Windows Explorer GUI to show the correct tick
     boxes in the "Security" tab. */
  ea.grfInheritance = OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE;
  ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
  ea.Trustee.TrusteeType = TRUSTEE_IS_USER;
  ea.Trustee.ptstrName = (char*) token_user->User.Sid;

  /* Create a new ACL that contains the new ACEs. */
  dwRes = SetEntriesInAcl(1, &ea, NULL, &pACL);
  if (dwRes != ERROR_SUCCESS)
    return false;

  if (!InitializeSecurityDescriptor (&SD,
                                     SECURITY_DESCRIPTOR_REVISION))
    goto cleanup;

  /* Add the ACL to the security descriptor. */
  if (!SetSecurityDescriptorDacl (&SD,
                                  TRUE,     /* use pACL */
                                  pACL,
                                  FALSE))   /* not a default DACL */
    goto cleanup;

  /* Initialize a security attributes structure. */
  sa.nLength = sizeof (SECURITY_ATTRIBUTES);
  sa.lpSecurityDescriptor = &SD;
  sa.bInheritHandle = FALSE;

  /* Finally create the directory */
  if (!CreateDirectoryA (path, &sa))
    result = false;

 cleanup:
  if (pACL)
    LocalFree (pACL);

  if (token_user)
    delete[] (char*)token_user;

  return result;
}


char *
win_mkdtemp (void)
{
  char lpTempPathBuffer[MAX_PATH];

  /* Gets the temp path env string (no guarantee it's a valid path). */
  DWORD dwRetVal = GetTempPath (MAX_PATH, lpTempPathBuffer);
  if (dwRetVal > MAX_PATH || (dwRetVal == 0))
    {
      print_last_error ();
      return NULL;
    }

  /* Check that the directory actually exists. */
  DWORD dwAttrib = GetFileAttributes (lpTempPathBuffer);
  bool temp_path_exists = (dwAttrib != INVALID_FILE_ATTRIBUTES
                           && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
  if (!temp_path_exists)
    {
      fprintf (stderr, "Path returned by GetTempPath does not exist: %s\n",
               lpTempPathBuffer);
    }

  /* Make sure there is enough space in the buffer for the prefix and random
     number.*/
  int temp_path_buffer_len = dwRetVal;
  const int appended_len = strlen ("\\libgccjit-123456");
  if (temp_path_buffer_len + appended_len + 1 >= MAX_PATH)
    {
      fprintf (stderr, "Temporary file path too long for generation of random"
               " directories: %s", lpTempPathBuffer);
    }

  /* This is all the space we have in the buffer to store the random number and
     prefix. */
  int extraspace = MAX_PATH - temp_path_buffer_len - 1;

  int tries;
  const int max_tries = 1000;

  for (tries = 0; tries < max_tries; ++tries)
    {
      /* Get a random number in [0; UINT_MAX]. */
      unsigned int rand_num;
      if (rand_s (&rand_num) != 0)
        {
          fprintf (stderr,
                   "Failed to create a random number using rand_s(): %s\n",
                   _strerror (NULL));
          return NULL;
        }

      /* Create 6 digits random number. */
      rand_num = ((double)rand_num / ((double) UINT_MAX + 1 ) * 1000000);

      /* Copy the prefix and random number to the buffer. */
      snprintf (&lpTempPathBuffer[temp_path_buffer_len], extraspace,
                "\\libgccjit-%06u", rand_num);

      if (create_directory_for_current_user (lpTempPathBuffer))
        break; // success!

      /* If we can't create the directory because we got unlucky and the
         directory already exists retry, otherwise fail. */
      if (GetLastError () != ERROR_ALREADY_EXISTS)
        {
          print_last_error ();
          return NULL;
        }
    }

  if (tries == max_tries)
    {
      fprintf (stderr, "Failed to create a random directory in %s\n",
               lpTempPathBuffer);
      return NULL;
    }

  {
    int allocate_len = temp_path_buffer_len + appended_len + 1;
    char * result = XNEWVEC (char, allocate_len);
    strcpy (result, lpTempPathBuffer);
    return result;
  }
}
}
}
