| /* Immutable data. | |
| Copyright (C) 2021-2026 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 <https://www.gnu.org/licenses/>. */ | |
| /* Written by Bruno Haible <bruno@clisp.org>, 2021. */ | |
| #include <config.h> | |
| /* Specification. */ | |
| #include "immutable.h" | |
| #include <errno.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #if IMMUTABLE_EFFECTIVE | |
| /* Real implementation. */ | |
| /* Get CHAR_BIT. */ | |
| # include <limits.h> | |
| /* Get intptr_t, uintptr_t. */ | |
| # include <stdint.h> | |
| # include <stdio.h> | |
| # if defined _WIN32 && !defined __CYGWIN__ | |
| /* Declare VirtualAlloc(), GetSystemInfo. */ | |
| # define WIN32_LEAN_AND_MEAN | |
| # define WIN32_EXTRA_LEAN | |
| # include <windows.h> | |
| /* Don't assume that UNICODE is not defined. */ | |
| # undef CreateFileMapping | |
| # define CreateFileMapping CreateFileMappingA | |
| # else | |
| /* Declare getpagesize(). */ | |
| # include <unistd.h> | |
| /* On HP-UX, getpagesize exists, but it is not declared in <unistd.h> 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 <sys/types.h> | |
| # include <sys/mman.h> | |
| /* Declare open(). */ | |
| # include <unistd.h> | |
| # include <fcntl.h> | |
| /* Get PATH_MAX. */ | |
| # include "pathmax.h" | |
| # ifndef PATH_MAX | |
| # define PATH_MAX 4096 | |
| # endif | |
| # include "glthread/once.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 | |
| <https://msdn.microsoft.com/en-us/library/ms724381.aspx> | |
| <https://msdn.microsoft.com/en-us/library/ms724958.aspx> */ | |
| 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 | |
| <https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createfilemappinga> */ | |
| 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 | |
| <https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-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 | |
| <https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-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 <limits.h>. */ | |
| # 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 _ARCH_PPC) | |
| # define PAGESIZE_MAX 65536 | |
| # else | |
| # define PAGESIZE_MAX 16384 | |
| # endif | |
| # define ALLOC_PAGES alloc_pages | |
| # define FREE_PAGES free_pages | |
| /* AIX defines a macro ALIGNMENT in <sys/socket.h>. Undefine it. */ | |
| # undef ALIGNMENT | |
| # 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); | |
| } |