/* Copyright (C) 2025-2026 Free Software Foundation, Inc.
   Contributed by Thomas Koenig, Nicolas Koenig, Andre Vehreschild

This file is part of the GNU Fortran Shmem Coarray Library (caf_shmem).

Caf_shmem 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.

Caf_shmem 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.

Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.

You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
<http://www.gnu.org/licenses/>.  */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "libgfortran.h"
#include "allocator.h"
#include "shared_memory.h"
#include "supervisor.h"

#include <assert.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>
#elif defined(WIN32)
#include <windows.h>
#include <memoryapi.h>
#endif
#include <unistd.h>

/* This implements shared memory based on POSIX mmap.  We start with
   memory block of the size of the global shared memory data, rounded
   up to one pagesize, and enlarge as needed.

   We address the memory via a shared_memory_ptr, which is an offset into
   the shared memory block.  The metadata is situated at offset 0.

   In order to be able to resize the memory and to keep pointers
   valid, we keep the old mapping around, so the memory is actually
   visible several times to the process.  Thus, pointers returned by
   shared_memory_get_mem_with_alignment remain valid even when
   resizing.  */

static const char *ENV_PPID = "GFORTRAN_SHMEM_PPID";
static const char *ENV_BASE = "GFORTRAN_SHMEM_BASE";

void
shared_memory_set_env (pid_t pid)
{
#if defined(HAVE_SETENV)
  char val[20];
  snprintf (val, 20, "%d", pid);
  setenv (ENV_PPID, val, 1);
#elif defined(WIN32)
  char val[20];
  snprintf (val, 20, "%d", pid);
  SetEnvironmentVariable (ENV_PPID, val);
#else
  static char buffer[28];
  int res;

  /* HP-UX / Legacy Fallback using putenv */
  res = snprintf (buffer, 28, "%s=%d", "ENV_PPID", (int)pid);
  if (res != -1)
    putenv (buffer);
#endif
}

char *
shared_memory_get_env (void)
{
  return getenv (ENV_PPID);
}

/* Get a pointer into the shared memory block with alignemnt
   (works similar to sbrk).  */

shared_mem_ptr
shared_memory_get_mem_with_alignment (shared_memory_act *mem, size_t size,
				      size_t align)
{
  size_t aligned_curr_size = alignto (mem->glbl.meta->used, align);
  mem->glbl.meta->used = aligned_curr_size + size;
  return (shared_mem_ptr) {aligned_curr_size};
}

shared_mem_ptr
shared_memory_get_master (shared_memory_act *mem, size_t size, size_t align)
{
  if (mem->glbl.meta->master)
    return (shared_mem_ptr) {mem->glbl.meta->master};
  else
    {
      ptrdiff_t loc = mem->glbl.meta->used;
      shared_mem_ptr p
	= shared_memory_get_mem_with_alignment (mem, size, align);
      mem->glbl.meta->master = loc;
      return p;
    }
}

/* If another image changed the size, update the size accordingly.  */

void
shared_memory_prepare (shared_memory_act *)
{
  asm volatile ("" ::: "memory");
}

#define SHM_NAME_MAX 255

/* Initialize the memory with one page, the shared metadata of the
   shared memory is stored at the beginning.  */

void
shared_memory_init (shared_memory_act *mem, size_t size)
{
  char shm_name[SHM_NAME_MAX];
  const char *env_val = getenv (ENV_PPID), *base = getenv (ENV_BASE);
  pid_t ppid = getpid ();
  void *base_ptr;

  if (env_val)
    {
      int n = sscanf (env_val, "%d", &ppid);
      assert (n == 1);
    }
  snprintf (shm_name, SHM_NAME_MAX, "/gfor-shm-%d", ppid);
  if (base)
    {
      int n = sscanf (base, "%p", &base_ptr);
      assert (n == 1);
    }
  else
    base_ptr = NULL;

  if (!env_val)
    {
#ifdef HAVE_MMAP
      int res;

      mem->shm_fd = shm_open (shm_name, O_CREAT | O_RDWR | O_EXCL, 0600);
      if (mem->shm_fd == -1)
	{
	  perror ("creating shared memory segment failed.");
	  exit (1);
	}

      res = ftruncate (mem->shm_fd, size);
      if (res == -1)
	{
	  perror ("resizing shared memory segment failed.");
	  exit (1);
	}
#elif defined(WIN32)
      mem->shm_fd
	= CreateFileMapping (INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
			     size >> (sizeof (DWORD) * 8),
			     (DWORD) (size & ~((DWORD) 0)), shm_name);
      if (mem->shm_fd == NULL)
	{
	  LPVOID lpMsgBuf;
	  DWORD dw = GetLastError ();

	  if (FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER
			       | FORMAT_MESSAGE_FROM_SYSTEM
			       | FORMAT_MESSAGE_IGNORE_INSERTS,
			     NULL, dw,
			     MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
			     (LPTSTR) &lpMsgBuf, 0, NULL)
	      == 0)
	    {
	      fprintf (stderr, "formatting the error message failed.\n");
	      ExitProcess (dw);
	    }

	  fprintf (stderr, "creating shared memory segment failed: %d, %s\n",
		   dw, (LPCTSTR) lpMsgBuf);

	  LocalFree (lpMsgBuf);
	  exit (1);
	}
#else
#error "no way to map shared memory."
#endif
    }
  else
    {
#ifdef HAVE_MMAP
      mem->shm_fd = shm_open (shm_name, O_RDWR, 0600);
      if (mem->shm_fd == -1)
	{
	  perror ("opening shared memory segment failed.");
	  exit (1);
	}
#elif defined(WIN32)
      mem->shm_fd = OpenFileMapping (FILE_MAP_ALL_ACCESS, FALSE, shm_name);
      if (mem->shm_fd == NULL)
	{
	  perror ("opening shared memory segment failed.");
	  exit (1);
	}
#endif
    }
#ifdef HAVE_MMAP
  mem->glbl.base
    = mmap (base_ptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, mem->shm_fd, 0);
  if (base_ptr && mem->glbl.base != base_ptr)
    {
      /* The supervisor will start us again.  */
      close (mem->shm_fd);
      free (local);
      exit (210);
    }
  else if (!base_ptr && !mem->glbl.base)
    {
      perror ("mmap failed");
      exit (1);
    }
#elif defined(WIN32)
  mem->glbl.base
    = (LPTSTR) MapViewOfFileExNuma (mem->shm_fd, FILE_MAP_ALL_ACCESS, 0, 0,
				    size, base_ptr, NUMA_NO_PREFERRED_NODE);
  if (mem->glbl.base == NULL)
    {
      perror ("MapViewOfFile failed");
      exit (1);
    }
#endif
  if (!base_ptr)
    {
#if defined(HAVE_SETENV)
      char val[20];
      snprintf (val, 20, "%p", mem->glbl.base);
      setenv (ENV_BASE, val, 1);
#elif defined(WIN32)
      char val[20];
      snprintf (val, 20, "%p", mem->glbl.base);
      SetEnvironmentVariable (ENV_BASE, val);
#else
      static char buffer[28];
      int res;

      /* HP-UX / Legacy Fallback using putenv */
      res = snprintf (buffer, 28, "%s=%p", "ENV_BASE", mem->glbl.base);
      if (res != -1)
	putenv (buffer);
#endif
    }
  mem->size = size;
  if (!env_val)
    *mem->glbl.meta
      = (global_shared_memory_meta) {sizeof (global_shared_memory_meta), 0};
}

void
shared_memory_cleanup (shared_memory_act *mem)
{
#ifdef HAVE_MMAP
  int res = munmap (mem->glbl.base, mem->size);
  if (res)
    {
      perror ("unmapping shared memory segment failed");
    }
  res = close (mem->shm_fd);
  if (res)
    {
      perror ("closing shm file handle failed. Trying to continue...");
    }
  if (this_image.image_num == -1)
    {
      char shm_name[SHM_NAME_MAX];

      snprintf (shm_name, SHM_NAME_MAX, "/gfor-shm-%s", shared_memory_get_env ());
      /* Only the supervisor is to delete the shm-file.  */
      res = shm_unlink (shm_name);
      if (res == -1)
	{
	  perror ("shm_unlink failed");
	  exit (1);
	}
    }
#elif defined(WIN32)
  if (!UnmapViewOfFile (mem->glbl.base))
    {
      perror ("unmapping shared memory segment failed");
    }
  CloseHandle (mem->shm_fd);
#endif
}
#undef SHM_NAME_MAX
