1: /*
2: * $Id: shmmgr.c,v 1.12 2025/04/15 21:57:10 snw Exp $
3: * shared memory manager
4: *
5: *
6: * Author: Serena Willis <snw@coherent-logic.com>
7: * Copyright (C) 1998 MUG Deutschland
8: * Copyright (C) 2020, 2025 Coherent Logic Development LLC
9: *
10: *
11: * This file is part of FreeM.
12: *
13: * FreeM is free software: you can redistribute it and/or modify
14: * it under the terms of the GNU Affero Public License as published by
15: * the Free Software Foundation, either version 3 of the License, or
16: * (at your option) any later version.
17: *
18: * FreeM is distributed in the hope that it will be useful,
19: * but WITHOUT ANY WARRANTY; without even the implied warranty of
20: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21: * GNU Affero Public License for more details.
22: *
23: * You should have received a copy of the GNU Affero Public License
24: * along with FreeM. If not, see <https://www.gnu.org/licenses/>.
25: *
26: * $Log: shmmgr.c,v $
27: * Revision 1.12 2025/04/15 21:57:10 snw
28: * Fix SysV IPC bugs on FreeBSD
29: *
30: * Revision 1.11 2025/04/15 21:08:51 snw
31: * Add some useful debug output
32: *
33: * Revision 1.10 2025/04/15 19:26:13 snw
34: * Remove extra whitespace
35: *
36: * Revision 1.9 2025/04/15 16:49:36 snw
37: * Make use of logprintf throughout codebase
38: *
39: * Revision 1.8 2025/04/14 19:46:18 snw
40: * Add SHM_REMAP flag to shmat on FreeBSD
41: *
42: * Revision 1.7 2025/04/09 19:52:02 snw
43: * Eliminate as many warnings as possible while building with -Wall
44: *
45: * Revision 1.6 2025/04/04 19:43:18 snw
46: * Switch to using environment catalog to determine user and group for environment, and remove -u and -g flags from freem
47: *
48: * Revision 1.5 2025/03/24 02:56:50 snw
49: * Shared memory compatibility fixes for OS/2
50: *
51: * Revision 1.4 2025/03/09 19:50:47 snw
52: * Second phase of REUSE compliance and header reformat
53: *
54: *
55: * SPDX-FileCopyrightText: (C) 2025 Coherent Logic Development LLC
56: * SPDX-License-Identifier: AGPL-3.0-or-later
57: **/
58:
59: #include <stdlib.h>
60: #include <stdio.h>
61: #include <assert.h>
62: #include <string.h>
63: #include <unistd.h>
64: #include <errno.h>
65: #include <signal.h>
66:
67: #include "shmmgr.h"
68: #include "mpsdef.h"
69: #include "locktab.h"
70: #include "log.h"
71:
72: #include <sys/types.h>
73: #include <sys/ipc.h>
74: #include <sys/sem.h>
75:
76: #if !defined(__OpenBSD__) && !defined(__APPLE__) && !defined(__OS2__)
77: union semun {
78: int val; /* Value for SETVAL */
79: struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
80: unsigned short *array; /* Array for GETALL, SETALL */
81: struct seminfo *__buf; /* Buffer for IPC_INFO
82: (Linux-specific) */
83: };
84: #endif
85:
86: int semid_shm;
87: extern int semid_locktab;
88: extern int semid_jobtab;
89: extern int semid_tp;
90: extern int semid_symtab;
91:
92: void shm_daemon_init(void);
93:
94: shm_config_t *shm_config = (shm_config_t *) NULL;
95:
96: #if defined(__FreeBSD__)
97: # define FM_SHM_PERMS 0660
98: #else
99: # define FM_SHM_PERMS 0770
100: #endif
101:
102: short shm_init(const size_t seg_size)
103: {
104: size_t alloc_map_size;
105: long pg_size;
106: key_t shm_sk;
107:
108: shm_sk = ftok (config_file, 5);
109: pg_size = sysconf (_SC_PAGESIZE);
110:
111: shm_config = (shm_config_t *) malloc (sizeof (shm_config_t));
112: NULLPTRCHK(shm_config,"shm_init");
113:
114: /* figure out how many pages we can fit in the segment, accounting for header size */
115: shm_config->pgct = (seg_size / pg_size) - sizeof (shm_hdr_t);
116:
117: /* how big will the alloc map be? */
118: alloc_map_size = shm_config->pgct * sizeof (shm_page_t);
119:
120: shm_config->segsiz = seg_size + alloc_map_size + pg_size;
121: shm_config->key = ftok (config_file, 1);
122: shm_config->pgsiz = pg_size;
123:
124: shm_config->seg_id = shmget (shm_config->key, shm_config->segsiz, FM_SHM_PERMS | IPC_CREAT);
125: if (shm_config->seg_id == -1) {
126: if (errno == 22) {
127: logprintf (FM_LOG_ERROR, "shm_init: cannot get shared memory segment of %ld bytes", (unsigned long) shm_config->segsiz);
128: fprintf (stderr, "\r\nYou may need to tune your kernel parameters, or manually set a smaller shared memory segment size in both the FreeM daemon and each interpreter process by using the `-S` command-line flag.\r\n\r\nPlease refer to the FreeM Platform Notes for your operating system for details.\r\n");
129: }
130: return SHMS_GET_ERR;
131: }
132:
133: #if !defined(__arm__)
134: shm_config->dta = shmat (shm_config->seg_id, NULL, 0);
135: #else
136: shm_config->dta = shmat (shm_config->seg_id, (void *) 0x1000000, 0);
137: #endif
138:
139: if (shm_config->dta == (void *) -1) {
140: return SHMS_ATTACH_ERR;
141: }
142: /* view the first sizeof (shm_hdr_t) bytes of the data area as an shm_hdr_t */
143: shm_config->hdr = (shm_hdr_t *) shm_config->dta;
144:
145: if (shm_config->hdr->magic != shm_config->key) {
146:
147: /* the shm segment is brand new */
148: first_process = TRUE;
149:
150: shm_daemon_init ();
151:
152: }
153: else {
154:
155: /* this shared mem segment was initialized before */
156: int daemon_chk;
157:
158: /* check if the daemon recorded in the header is actually running */
159: daemon_chk = kill (shm_config->hdr->first_process, 0);
160:
161: if (daemon_chk == -1 && errno == ESRCH) {
162:
163: logprintf (FM_LOG_WARNING, "shm_init: recovering from crashed daemon pid %ld", shm_config->hdr->first_process);
164:
165: first_process = TRUE;
166:
167: shm_daemon_init ();
168:
169: }
170: else {
171:
172:
173: first_process = FALSE;
174:
175: semid_shm = semget (shm_sk, 1, 0);
176: if (semid_shm == -1) {
177: logprintf (FM_LOG_FATAL, "shm_init: could not attach to shared memory semaphore [%s]", strerror (errno));
178: }
179:
180: /* we are NOT the initial process. if addresses don't match, re-attach! */
181: /* (again, borrowed from RSM) */
182: if (shm_config->hdr->shmad != shm_config->dta) {
183:
184: /* grab the pointers we need */
185: void *old_addr = shm_config->dta;
186: void *new_addr = shm_config->hdr->shmad;
187:
188: logprintf (FM_LOG_INFO, "shmmgr: remapping shared memory from virtual address %p to %p", old_addr, new_addr);
189:
190: /* detach and reattach */
191: if (shmdt (old_addr) == -1) {
192: logprintf (FM_LOG_FATAL, "shm_init: detach failed during detach/reattach [shmdt error %s]", strerror (errno));
193: }
194:
195: shm_config->dta = shmat (shm_config->seg_id, new_addr, 0);
196:
197: if (shm_config->dta == (void *) -1) {
198: logprintf (FM_LOG_FATAL, "shm_init: fatal error attaching shared memory segment [shmat error '%s']", strerror (errno));
199: }
200:
201: shm_config->hdr = (shm_hdr_t *) shm_config->dta;
202:
203: /* allocator buffer at the next page-aligned address after the header and allocation map */
204: shm_config->buf = SHMALIGN(shm_config->dta + (sizeof (shm_hdr_t) * shm_config->pgct));
205: }
206: else {
207: shm_config->buf = SHMALIGN(shm_config->dta + (sizeof (shm_hdr_t) * shm_config->pgct));
208: }
209:
210: }
211:
212: }
213:
214: locktab_init ();
215:
216: assert(shm_address_to_page_num(shm_page_num_to_address(20)) == 20);
217:
218:
219: return TRUE;
220: }
221:
222: void shm_daemon_init(void)
223: {
224: union semun arg;
225: key_t shm_sk;
226: register int i;
227:
228: shm_sk = ftok (config_file, 5);
229:
230: semid_shm = semget (shm_sk, 1, 0660 | IPC_CREAT);
231: if (semid_shm == -1) {
232: logprintf (FM_LOG_FATAL, "shm_init: failed to create shared memory semaphore [%s]", strerror (errno));
233: }
234:
235: arg.val = 1;
236: if (semctl (semid_shm, 0, SETVAL, arg) == -1) {
237: logprintf (FM_LOG_FATAL, "shm_init: failed to initialize shared memory semaphore [%s]", strerror (errno));
238: }
239:
240: /* zero out the segment */
241: memset (shm_config->dta, 0, shm_config->segsiz);
242:
243: /* we are the process that created the segment: initialize everything */
244: shm_config->hdr->magic = shm_config->key;
245: shm_config->hdr->first_process = pid;
246:
247: /* store the address we got into the shm_hdr (borrowed from RSM) */
248: shm_config->hdr->shmad = shm_config->dta;
249: shm_config->hdr->maintenance_mode = 0;
250:
251: /* alloc_map comes after the header */
252: /*
253: shm_config->alloc_map = (shm_page_t *) (shm_config->dta + sizeof (shm_hdr_t));
254: */
255: shm_config->buf = SHMALIGN(shm_config->dta + (sizeof (shm_hdr_t) * shm_config->pgct));
256: logprintf (FM_LOG_INFO, "shm_daemon_init: allocator buffer aligned at %p (system page size %ld)", shm_config->buf, sysconf (_SC_PAGESIZE));
257:
258: for (i = 0; i < shm_config->pgct; i++) {
259: shm_config->hdr->alloc_map[i].is_first = FALSE;
260: shm_config->hdr->alloc_map[i].is_last = FALSE;
261: shm_config->hdr->alloc_map[i].pg_state = PG_FREE;
262: }
263:
264: }
265:
266: short shm_exit(void)
267: {
268: int res;
269: union semun arg;
270:
271: res = shmdt (shm_config->dta);
272:
273: if (res == -1) {
274: logprintf (FM_LOG_ERROR, "shm_exit: failure in shmdt() [%s]", strerror (errno));
275: return FALSE;
276: }
277:
278: if (first_process) {
279:
280: res = shmctl (shm_config->seg_id, IPC_RMID, 0);
281:
282: if (res == -1) {
283: logprintf (FM_LOG_ERROR, "shm_exit: failure in shmctl() [%s]", strerror (errno));
284: return FALSE;
285: }
286:
287: semctl (semid_shm, 0, IPC_RMID, arg);
288: semctl (semid_locktab, 0, IPC_RMID, arg);
289: semctl (semid_jobtab, 0, IPC_RMID, arg);
290: semctl (semid_tp, 0, IPC_RMID, arg);
291: semctl (semid_symtab, 0, IPC_RMID, arg);
292:
293: }
294:
295: return TRUE;
296:
297: }
298:
299: short shm_get_sem(void)
300: {
301:
302: int tries;
303: struct sembuf s = {0, -1, 0};
304:
305: for (tries = 0; tries < 3; tries++) {
306:
307: if (semop (semid_shm, &s, 1) != -1) {
308: return TRUE;
309: }
310:
311: sleep (1);
312:
313: }
314:
315: return FALSE;
316:
317: }
318:
319: short shm_release_sem(void)
320: {
321: struct sembuf s = {0, 1, 0};
322:
323: if (semop (semid_shm, &s, 1) != -1) {
324: return TRUE;
325: }
326:
327: return FALSE;
328: }
329:
330: void shm_set_maintenance_mode (const short maintenance_mode)
331: {
332: if (shm_get_sem () == TRUE) {
333: shm_config->hdr->maintenance_mode = maintenance_mode;
334:
335: shm_release_sem ();
336: }
337: }
338:
339: shm_page_t *shm_get_alloc_map_entry(const int page_number)
340: {
341: return &(shm_config->hdr->alloc_map[page_number]);
342: }
343:
344: void *shm_page_num_to_address(const int page_num)
345: {
346: return (void *) shm_config->buf + (shm_config->pgsiz * page_num);
347: }
348:
349: int shm_address_to_page_num(const void *address)
350: {
351: unsigned long val = (unsigned long) address - (unsigned long) shm_config->buf;
352: unsigned long new_val = val / shm_config->pgsiz;
353:
354: return (int) new_val;
355: }
356:
357: void *shm_alloc_pages(const int page_count)
358: {
359:
360: register int i;
361: register int j;
362:
363: int candidate_page = 0;
364: int free_pages_gotten = 0;
365:
366: shm_page_t *pg;
367:
368: if (shm_get_sem () == FALSE) {
369: logprintf (FM_LOG_FATAL, "shm_alloc_pages: could not get exclusive access to shared memory");
370: }
371:
372:
373: for (i = 0; i < shm_config->pgct; i++) {
374:
375: pg = shm_get_alloc_map_entry (i);
376: NULLPTRCHK(pg,"shm_alloc_pages");
377:
378: free_pages_gotten = 0;
379:
380: if (pg->pg_state == PG_FREE) {
381:
382: candidate_page = i;
383:
384: for (j = i; ((j < (i + page_count)) && (j < shm_config->pgct)); j++) {
385: pg = shm_get_alloc_map_entry (j);
386:
387: if (pg->pg_state == PG_FREE) free_pages_gotten++;
388:
389: }
390:
391: if (free_pages_gotten == page_count) {
392:
393: for (j = candidate_page; j < (candidate_page + page_count); j++) {
394: pg = shm_get_alloc_map_entry (j);
395:
396: pg->pg_state = PG_ALLOC;
397: pg->pid = pid;
398:
399: if (j == candidate_page) {
400: pg->is_first = TRUE;
401: }
402:
403: if (j == candidate_page + (page_count - 1)) {
404: pg->is_last = TRUE;
405: }
406:
407: }
408:
409: shm_release_sem ();
410:
411: return (void *) shm_config->buf + (shm_config->pgsiz * candidate_page);
412:
413: }
414:
415: }
416:
417: }
418:
419: shm_release_sem ();
420:
421: return (void *) NULL;
422:
423: }
424:
425:
426: void *shm_alloc(const size_t bytes)
427: {
428: int pages_needed = bytes / shm_config->pgsiz;
429: float extra = bytes % shm_config->pgsiz;
430:
431: if (extra > 0) {
432: pages_needed++;
433: }
434:
435: return shm_alloc_pages (pages_needed);
436: }
437:
438: void shm_free_page(const int page_number)
439: {
440: register int i;
441: shm_page_t *a = shm_get_alloc_map_entry (page_number);
442:
443: if (a->is_first == FALSE) {
444: logprintf (FM_LOG_ERROR, "shm_free_page: attempt to free page in the middle of allocation chain");
445: return;
446: }
447:
448: if (a->pg_state == PG_FREE) {
449: logprintf (FM_LOG_FATAL, "shm_free_page: double free attempted in page %d", page_number);
450: }
451:
452: if (shm_get_sem () == FALSE) {
453: logprintf (FM_LOG_FATAL, "shm_free_page: could not get exclusive access to shared memory");
454: }
455:
456:
457: for (i = page_number; i < shm_config->pgct; i++) {
458:
459: a = shm_get_alloc_map_entry (i);
460:
461: if (a->is_last) {
462: a->is_first = FALSE;
463: a->pg_state = PG_FREE;
464: a->pid = 0;
465: a->is_last = FALSE;
466:
467: shm_release_sem ();
468:
469: return;
470: }
471: else {
472: a->is_first = FALSE;
473: a->pg_state = PG_FREE;
474: a->pid = 0;
475: a->is_last = FALSE;
476: }
477:
478: }
479:
480: shm_release_sem ();
481:
482: }
483:
484: void shm_free(const void *addr)
485: {
486: shm_free_page (shm_address_to_page_num (addr));
487: }
488:
489: void shm_dump(void)
490: {
491:
492: printf ("SHARED MEMORY CONFIGURATION\r\n");
493: printf (" pgsiz %ld\r\n", (unsigned long) shm_config->pgsiz);
494: printf (" pgct %d\r\n", shm_config->pgct);
495: printf (" key %d\r\n", shm_config->key);
496: printf (" segid %d\r\n", shm_config->seg_id);
497: printf (" sizeof shm_page_t %ld\r\n", (long) sizeof (shm_page_t));
498: printf (" segsiz %ld\r\n", (long) shm_config->segsiz);
499: printf (" shm address %p\r\n", shm_config->dta);
500: printf (" alloc_map size %ld\r\n", (unsigned long) sizeof (shm_page_t) * shm_config->pgct);
501: printf (" buf address %p\r\n", shm_config->buf);
502: }
503:
504: void shm_dump_pages(void)
505: {
506:
507: register int i;
508: shm_page_t *p;
509:
510: printf ("%-10s%-10s%-10s%-10s%-10s\r\n", "PAGE", "PID", "BMHEAD", "BMTAIL", "STATE");
511:
512: for (i = 0; i < shm_config->pgct; i++) {
513:
514: p = shm_get_alloc_map_entry (i);
515:
516: printf ("%-10d%-10d%-10s%-10s%-10s\r\n",
517: i,
518: p->pid,
519: (p->is_first == TRUE) ? "Y" : "N",
520: (p->is_last == TRUE) ? "Y" : "N",
521: (p->pg_state == PG_FREE) ? "PG_FREE" : "PG_ALLOC");
522:
523: }
524:
525: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>