/* 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); }