/* Immutable data.
Copyright (C) 2021-2023 Free Software Foundation, Inc.
This file 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 file 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 program. If not, see . */
/* Written by Bruno Haible , 2021. */
#include
/* Specification. */
#include "immutable.h"
#include
#include
#include
#if IMMUTABLE_EFFECTIVE
/* Real implementation. */
/* Get CHAR_BIT. */
# include
/* Get intptr_t, uintptr_t. */
# include
# include
# if defined _WIN32 && !defined __CYGWIN__
/* Declare VirtualAlloc(), GetSystemInfo. */
# define WIN32_LEAN_AND_MEAN
# define WIN32_EXTRA_LEAN
# include
/* Don't assume that UNICODE is not defined. */
# undef CreateFileMapping
# define CreateFileMapping CreateFileMappingA
# else
/* Declare getpagesize(). */
# include
/* On HP-UX, getpagesize exists, but it is not declared in even if
the compiler options -D_HPUX_SOURCE -D_XOPEN_SOURCE=600 are used. */
# ifdef __hpux
extern
# ifdef __cplusplus
"C"
# endif
int getpagesize (void);
# endif
/* Declare mmap(), mprotect(). */
# include
# include
/* Declare open(). */
# include
# include
/* Get PATH_MAX. */
# include "pathmax.h"
# ifndef PATH_MAX
# define PATH_MAX 4096
# endif
# include "glthread/lock.h"
# endif
/* ================= Back end of the malloc implementation ================= */
/* The memory page size.
Once it is initialized, a power of 2. Typically 4096 or 8192. */
static uintptr_t pagesize;
/* Initializes pagesize. */
static void
init_pagesize (void)
{
/* Simultaneous execution of this initialization in multiple threads is OK. */
# if defined _WIN32 && !defined __CYGWIN__
/* GetSystemInfo
*/
SYSTEM_INFO info;
GetSystemInfo (&info);
pagesize = info.dwPageSize;
# else
pagesize = getpagesize ();
# endif
}
# if defined _WIN32 && !defined __CYGWIN__
static inline void
init_mmap_file (void)
{
}
# else
/* Variables needed for obtaining memory pages via mmap(). */
static int file_fd;
static long file_length;
/* Initialization of these variables. */
static void
do_init_mmap_file (void)
{
/* Use TMPDIR, except if it is too long. */
const char *tmpdir = getenv ("TMPDIR");
if (tmpdir == NULL || strlen (tmpdir) > PATH_MAX)
tmpdir = "/tmp";
/* Now strlen (tmpdir) <= PATH_MAX. */
char filename[PATH_MAX + 1 + 41 + 1];
sprintf (filename, "%s/glimmdata-%d-%ld", tmpdir, getpid (), random ());
file_fd = open (filename, O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0700);
if (file_fd < 0)
{
fprintf (stderr, "glimm: Cannot open %s!\n", filename);
abort ();
}
/* Remove the file from the file system as soon as possible, to make
sure there is no leftover after this process terminates or crashes. */
unlink (filename);
file_length = 0;
}
/* Once-only initializer for these variables. */
gl_once_define (static, for_mmap_once)
static inline void
init_mmap_file (void)
{
/* Use a once-only initializer here, since simultaneous execution of
do_init_mmap_file() in multiple threads must be avoided. */
gl_once (for_mmap_once, do_init_mmap_file);
}
# endif
/* Size of the (page-aligned) header that links the writable mapping
and the read-only mapping together. */
# define SHARED_LINK_HEADER_SIZE \
(INTPTR_WIDTH / CHAR_BIT) /* = sizeof (void *) */
/* Allocates a contiguous set of pages of memory.
size > 0, must be a multiple of pagesize.
Returns a multiple of PAGESIZE, or 0 upon failure. */
static uintptr_t
alloc_pages (size_t size)
{
# if defined _WIN32 && !defined __CYGWIN__
/* Allocate pages from the system paging file.
CreateFileMapping
*/
HANDLE h =
CreateFileMapping (INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE | SEC_COMMIT,
size >> 16 >> 16, size & 0xFFFFFFFFU, NULL);
if (h == NULL)
{
fprintf (stderr,
"glimm: Cannot allocate file mapping. GetLastError() = 0x%08X\n",
(unsigned int) GetLastError ());
return 0;
}
/* MapViewOfFile
*/
char *mem_w = (char *) MapViewOfFile (h, FILE_MAP_WRITE, 0, 0, size);
char *mem_r = (char *) MapViewOfFile (h, FILE_MAP_READ, 0, 0, size);
if (mem_w == NULL || mem_r == NULL)
{
/* UnmapViewOfFile
*/
if (mem_w != NULL)
UnmapViewOfFile (mem_w);
if (mem_r != NULL)
UnmapViewOfFile (mem_r);
return 0;
}
/* It is OK to call CloseHandle before UnmapViewOfFile. The file mapping
object gets really closed only once all its views are unmapped. */
if (!CloseHandle (h))
{
UnmapViewOfFile (mem_w);
UnmapViewOfFile (mem_r);
CloseHandle (h);
return 0;
}
# else
/* Extend the file by size/pagesize pages. */
long new_file_length = file_length + size;
if (ftruncate (file_fd, new_file_length) < 0)
{
fprintf (stderr, "glimm: Cannot extend backing file!\n");
return 0;
}
/* Create separate writable mapping and read-only mapping. */
char *mem_w = (char *) mmap (NULL, size, PROT_READ | PROT_WRITE,
MAP_SHARED, file_fd, file_length);
char *mem_r = (char *) mmap (NULL, size, PROT_READ,
MAP_SHARED, file_fd, file_length);
if (mem_w == (char *)(-1) || mem_r == (char *)(-1))
{
if (mem_w != (char *)(-1))
munmap (mem_w, size);
if (mem_r != (char *)(-1))
munmap (mem_r, size);
return 0;
}
file_length = new_file_length;
# endif
/* Link the two memory areas together. */
((intptr_t *) mem_w)[0] = mem_r - mem_w;
return (uintptr_t) mem_w;
}
/* Frees a contiguous set of pages of memory, returned by alloc_pages.
size > 0, must be a multiple of pagesize. */
static void
free_pages (uintptr_t pages, size_t size)
{
pages -= SHARED_LINK_HEADER_SIZE;
if ((pages & (pagesize - 1)) != 0)
abort ();
char *mem_w = (char *) pages;
char *mem_r = mem_w + ((intptr_t *) mem_w)[0];
# if defined _WIN32 && !defined __CYGWIN__
if (!UnmapViewOfFile (mem_w))
abort ();
if (!UnmapViewOfFile (mem_r))
abort ();
# else
if (munmap (mem_w, size) < 0)
abort ();
if (munmap (mem_r, size) < 0)
abort ();
# endif
}
/* Cygwin defines PAGESIZE in . */
# undef PAGESIZE
/* ======================= Instantiate the front end ======================= */
# define PAGESIZE pagesize
/* On Cygwin and Linux/PowerPC, PAGESIZE is 65536. On macOS 11, it is 16384.
On all other platforms, it is either 4096 or 8192. */
# if defined __CYGWIN__ || (defined __linux__ && defined __powerpc__)
# define PAGESIZE_MAX 65536
# else
# define PAGESIZE_MAX 16384
# endif
# define ALLOC_PAGES alloc_pages
# define FREE_PAGES free_pages
# define ALIGNMENT sizeof (void *)
# define PAGE_RESERVED_HEADER_SIZE SHARED_LINK_HEADER_SIZE
# include "ssfmalloc.h"
void *
immmalloc (size_t size)
{
/* Initializations. */
if (!pagesize)
{
init_mmap_file ();
init_pagesize ();
}
void *writable_pointer = (void *) allocate_block (size);
if (writable_pointer == NULL)
errno = ENOMEM;
return writable_pointer;
}
const void *
immfreeze (void *writable_pointer)
{
uintptr_t mem_w = (uintptr_t) writable_pointer & -(intptr_t)pagesize;
return (void *) ((uintptr_t) writable_pointer + ((intptr_t *) mem_w)[0]);
}
void
immfree (const void *readonly_pointer)
{
uintptr_t mem_r = (uintptr_t) readonly_pointer & -(intptr_t)pagesize;
free_block ((uintptr_t) readonly_pointer - ((intptr_t *) mem_r)[0]);
}
#else
/* Dummy implementation. */
void *
immmalloc (size_t size)
{
void *p = malloc (size);
if (p == NULL)
errno = ENOMEM;
return p;
}
const void *
immfreeze (void *writable_pointer)
{
return writable_pointer;
}
void
immfree (const void *readonly_pointer)
{
void *writable_pointer = (void *) readonly_pointer;
free (writable_pointer);
}
#endif
const char *
immstrdup (const char *string)
{
size_t size = strlen (string) + 1;
void *wp = immmalloc (size);
if (wp == NULL)
return NULL;
memcpy (wp, string, size);
return (const char *) immfreeze (wp);
}