exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / immutable.c
blob5cd1bf2ee61111bf625395d551a5f88ca426908b
1 /* Immutable data.
3 Copyright (C) 2021-2024 Free Software Foundation, Inc.
5 This file is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as
7 published by the Free Software Foundation; either version 2.1 of the
8 License, or (at your option) any later version.
10 This file is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
18 /* Written by Bruno Haible <bruno@clisp.org>, 2021. */
20 #include <config.h>
22 /* Specification. */
23 #include "immutable.h"
25 #include <errno.h>
26 #include <stdlib.h>
27 #include <string.h>
29 #if IMMUTABLE_EFFECTIVE
30 /* Real implementation. */
32 /* Get CHAR_BIT. */
33 # include <limits.h>
35 /* Get intptr_t, uintptr_t. */
36 # include <stdint.h>
38 # include <stdio.h>
40 # if defined _WIN32 && !defined __CYGWIN__
42 /* Declare VirtualAlloc(), GetSystemInfo. */
43 # define WIN32_LEAN_AND_MEAN
44 # define WIN32_EXTRA_LEAN
45 # include <windows.h>
47 /* Don't assume that UNICODE is not defined. */
48 # undef CreateFileMapping
49 # define CreateFileMapping CreateFileMappingA
51 # else
53 /* Declare getpagesize(). */
54 # include <unistd.h>
55 /* On HP-UX, getpagesize exists, but it is not declared in <unistd.h> even if
56 the compiler options -D_HPUX_SOURCE -D_XOPEN_SOURCE=600 are used. */
57 # ifdef __hpux
58 extern
59 # ifdef __cplusplus
60 "C"
61 # endif
62 int getpagesize (void);
63 # endif
65 /* Declare mmap(), mprotect(). */
66 # include <sys/types.h>
67 # include <sys/mman.h>
69 /* Declare open(). */
70 # include <unistd.h>
71 # include <fcntl.h>
73 /* Get PATH_MAX. */
74 # include "pathmax.h"
75 # ifndef PATH_MAX
76 # define PATH_MAX 4096
77 # endif
79 # include "glthread/lock.h"
81 # endif
84 /* ================= Back end of the malloc implementation ================= */
86 /* The memory page size.
87 Once it is initialized, a power of 2. Typically 4096 or 8192. */
88 static uintptr_t pagesize;
90 /* Initializes pagesize. */
91 static void
92 init_pagesize (void)
94 /* Simultaneous execution of this initialization in multiple threads is OK. */
95 # if defined _WIN32 && !defined __CYGWIN__
96 /* GetSystemInfo
97 <https://msdn.microsoft.com/en-us/library/ms724381.aspx>
98 <https://msdn.microsoft.com/en-us/library/ms724958.aspx> */
99 SYSTEM_INFO info;
100 GetSystemInfo (&info);
101 pagesize = info.dwPageSize;
102 # else
103 pagesize = getpagesize ();
104 # endif
108 # if defined _WIN32 && !defined __CYGWIN__
110 static inline void
111 init_mmap_file (void)
115 # else
117 /* Variables needed for obtaining memory pages via mmap(). */
118 static int file_fd;
119 static long file_length;
121 /* Initialization of these variables. */
122 static void
123 do_init_mmap_file (void)
125 /* Use TMPDIR, except if it is too long. */
126 const char *tmpdir = getenv ("TMPDIR");
127 if (tmpdir == NULL || strlen (tmpdir) > PATH_MAX)
128 tmpdir = "/tmp";
129 /* Now strlen (tmpdir) <= PATH_MAX. */
131 char filename[PATH_MAX + 1 + 41 + 1];
132 sprintf (filename, "%s/glimmdata-%d-%ld", tmpdir, getpid (), random ());
134 file_fd = open (filename, O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0700);
135 if (file_fd < 0)
137 fprintf (stderr, "glimm: Cannot open %s!\n", filename);
138 abort ();
141 /* Remove the file from the file system as soon as possible, to make
142 sure there is no leftover after this process terminates or crashes. */
143 unlink (filename);
145 file_length = 0;
148 /* Once-only initializer for these variables. */
149 gl_once_define (static, for_mmap_once)
151 static inline void
152 init_mmap_file (void)
154 /* Use a once-only initializer here, since simultaneous execution of
155 do_init_mmap_file() in multiple threads must be avoided. */
156 gl_once (for_mmap_once, do_init_mmap_file);
159 # endif
162 /* Size of the (page-aligned) header that links the writable mapping
163 and the read-only mapping together. */
164 # define SHARED_LINK_HEADER_SIZE \
165 (INTPTR_WIDTH / CHAR_BIT) /* = sizeof (void *) */
167 /* Allocates a contiguous set of pages of memory.
168 size > 0, must be a multiple of pagesize.
169 Returns a multiple of PAGESIZE, or 0 upon failure. */
170 static uintptr_t
171 alloc_pages (size_t size)
173 # if defined _WIN32 && !defined __CYGWIN__
174 /* Allocate pages from the system paging file.
175 CreateFileMapping
176 <https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createfilemappinga> */
177 HANDLE h =
178 CreateFileMapping (INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE | SEC_COMMIT,
179 size >> 16 >> 16, size & 0xFFFFFFFFU, NULL);
180 if (h == NULL)
182 fprintf (stderr,
183 "glimm: Cannot allocate file mapping. GetLastError() = 0x%08X\n",
184 (unsigned int) GetLastError ());
185 return 0;
187 /* MapViewOfFile
188 <https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile> */
189 char *mem_w = (char *) MapViewOfFile (h, FILE_MAP_WRITE, 0, 0, size);
190 char *mem_r = (char *) MapViewOfFile (h, FILE_MAP_READ, 0, 0, size);
191 if (mem_w == NULL || mem_r == NULL)
193 /* UnmapViewOfFile
194 <https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-unmapviewoffile> */
195 if (mem_w != NULL)
196 UnmapViewOfFile (mem_w);
197 if (mem_r != NULL)
198 UnmapViewOfFile (mem_r);
199 return 0;
201 /* It is OK to call CloseHandle before UnmapViewOfFile. The file mapping
202 object gets really closed only once all its views are unmapped. */
203 if (!CloseHandle (h))
205 UnmapViewOfFile (mem_w);
206 UnmapViewOfFile (mem_r);
207 CloseHandle (h);
208 return 0;
210 # else
211 /* Extend the file by size/pagesize pages. */
212 long new_file_length = file_length + size;
213 if (ftruncate (file_fd, new_file_length) < 0)
215 fprintf (stderr, "glimm: Cannot extend backing file!\n");
216 return 0;
218 /* Create separate writable mapping and read-only mapping. */
219 char *mem_w = (char *) mmap (NULL, size, PROT_READ | PROT_WRITE,
220 MAP_SHARED, file_fd, file_length);
221 char *mem_r = (char *) mmap (NULL, size, PROT_READ,
222 MAP_SHARED, file_fd, file_length);
223 if (mem_w == (char *)(-1) || mem_r == (char *)(-1))
225 if (mem_w != (char *)(-1))
226 munmap (mem_w, size);
227 if (mem_r != (char *)(-1))
228 munmap (mem_r, size);
229 return 0;
231 file_length = new_file_length;
232 # endif
234 /* Link the two memory areas together. */
235 ((intptr_t *) mem_w)[0] = mem_r - mem_w;
236 return (uintptr_t) mem_w;
239 /* Frees a contiguous set of pages of memory, returned by alloc_pages.
240 size > 0, must be a multiple of pagesize. */
241 static void
242 free_pages (uintptr_t pages, size_t size)
244 pages -= SHARED_LINK_HEADER_SIZE;
245 if ((pages & (pagesize - 1)) != 0)
246 abort ();
247 char *mem_w = (char *) pages;
248 char *mem_r = mem_w + ((intptr_t *) mem_w)[0];
249 # if defined _WIN32 && !defined __CYGWIN__
250 if (!UnmapViewOfFile (mem_w))
251 abort ();
252 if (!UnmapViewOfFile (mem_r))
253 abort ();
254 # else
255 if (munmap (mem_w, size) < 0)
256 abort ();
257 if (munmap (mem_r, size) < 0)
258 abort ();
259 # endif
262 /* Cygwin defines PAGESIZE in <limits.h>. */
263 # undef PAGESIZE
265 /* ======================= Instantiate the front end ======================= */
267 # define PAGESIZE pagesize
268 /* On Cygwin and Linux/PowerPC, PAGESIZE is 65536. On macOS 11, it is 16384.
269 On all other platforms, it is either 4096 or 8192. */
270 # if defined __CYGWIN__ || (defined __linux__ && defined __powerpc__)
271 # define PAGESIZE_MAX 65536
272 # else
273 # define PAGESIZE_MAX 16384
274 # endif
276 # define ALLOC_PAGES alloc_pages
277 # define FREE_PAGES free_pages
278 # define ALIGNMENT sizeof (void *)
279 # define PAGE_RESERVED_HEADER_SIZE SHARED_LINK_HEADER_SIZE
281 # include "ssfmalloc.h"
284 void *
285 immmalloc (size_t size)
287 /* Initializations. */
288 if (!pagesize)
290 init_mmap_file ();
291 init_pagesize ();
294 void *writable_pointer = (void *) allocate_block (size);
295 if (writable_pointer == NULL)
296 errno = ENOMEM;
297 return writable_pointer;
300 const void *
301 immfreeze (void *writable_pointer)
303 uintptr_t mem_w = (uintptr_t) writable_pointer & -(intptr_t)pagesize;
304 return (void *) ((uintptr_t) writable_pointer + ((intptr_t *) mem_w)[0]);
307 void
308 immfree (const void *readonly_pointer)
310 uintptr_t mem_r = (uintptr_t) readonly_pointer & -(intptr_t)pagesize;
311 free_block ((uintptr_t) readonly_pointer - ((intptr_t *) mem_r)[0]);
314 #else
315 /* Dummy implementation. */
317 void *
318 immmalloc (size_t size)
320 void *p = malloc (size);
321 if (p == NULL)
322 errno = ENOMEM;
323 return p;
326 const void *
327 immfreeze (void *writable_pointer)
329 return writable_pointer;
332 void
333 immfree (const void *readonly_pointer)
335 void *writable_pointer = (void *) readonly_pointer;
336 free (writable_pointer);
339 #endif
342 const char *
343 immstrdup (const char *string)
345 size_t size = strlen (string) + 1;
346 void *wp = immmalloc (size);
347 if (wp == NULL)
348 return NULL;
349 memcpy (wp, string, size);
350 return (const char *) immfreeze (wp);