File:  [Coherent Logic Development] / freem / src / shmmgr.c
Revision 1.5: download - view: text, annotated - select for diffs
Mon Mar 24 02:56:50 2025 UTC (8 days, 21 hours ago) by snw
Branches: MAIN
CVS tags: v0-62-3, HEAD
Shared memory compatibility fixes for OS/2

/*
 *   $Id: shmmgr.c,v 1.5 2025/03/24 02:56:50 snw Exp $
 *    shared memory manager
 *
 *  
 *   Author: Serena Willis <snw@coherent-logic.com>
 *    Copyright (C) 1998 MUG Deutschland
 *    Copyright (C) 2020, 2025 Coherent Logic Development LLC
 *
 *
 *   This file is part of FreeM.
 *
 *   FreeM is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   FreeM 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 Affero Public License for more details.
 *
 *   You should have received a copy of the GNU Affero Public License
 *   along with FreeM.  If not, see <https://www.gnu.org/licenses/>.
 *
 *   $Log: shmmgr.c,v $
 *   Revision 1.5  2025/03/24 02:56:50  snw
 *   Shared memory compatibility fixes for OS/2
 *
 *   Revision 1.4  2025/03/09 19:50:47  snw
 *   Second phase of REUSE compliance and header reformat
 *
 *
 * SPDX-FileCopyrightText:  (C) 2025 Coherent Logic Development LLC
 * SPDX-License-Identifier: AGPL-3.0-or-later
 **/

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>

#include "shmmgr.h"
#include "mpsdef.h"
#include "locktab.h"

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#if !defined(__OpenBSD__) && !defined(__APPLE__) && !defined(__OS2__)
union semun {
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                (Linux-specific) */
};
#endif

int semid_shm;
extern int semid_locktab;
extern int semid_jobtab;
extern int semid_tp;
extern int semid_symtab;

void shm_daemon_init(void);

shm_config_t *shm_config = (shm_config_t *) NULL;

short shm_init(const size_t seg_size)
{
    union semun arg;
    size_t alloc_map_size;
    long pg_size;
    register int i;
    key_t shm_sk;

    shm_sk = ftok (config_file, 5);    
    pg_size = sysconf (_SC_PAGESIZE);
    
    shm_config = (shm_config_t *) malloc (sizeof (shm_config_t));
    NULLPTRCHK(shm_config,"shm_init");    
    
    /* figure out how many pages we can fit in the segment, accounting for header size */
    shm_config->pgct = (seg_size / pg_size) - sizeof (shm_hdr_t);
    
    /* how big will the alloc map be? */
    alloc_map_size = shm_config->pgct * sizeof (shm_page_t);
    
    shm_config->segsiz = seg_size + alloc_map_size + pg_size;
    shm_config->key = ftok (config_file, 1);
    shm_config->pgsiz = pg_size;
    
    shm_config->seg_id = shmget (shm_config->key, shm_config->segsiz, 0660 | IPC_CREAT);
    if (shm_config->seg_id == -1) {
        if (errno == 22) {
            fprintf (stderr, "shm_init:  cannot get shared memory segment of %ld bytes\r\n\r\n", (unsigned long) shm_config->segsiz);
            fprintf (stderr, "You 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\r\n"); 
        }
        return SHMS_GET_ERR;
    }

#if !defined(__arm__)    
    shm_config->dta = shmat (shm_config->seg_id, NULL, 0);
#else
    shm_config->dta = shmat (shm_config->seg_id, (void *) 0x1000000, 0);
#endif
    
    if (shm_config->dta == (void *) -1) {     
        return SHMS_ATTACH_ERR;
    }
    /* view the first sizeof (shm_hdr_t) bytes of the data area as an shm_hdr_t */
    shm_config->hdr = (shm_hdr_t *) shm_config->dta;
    
    if (shm_config->hdr->magic != shm_config->key) {

        /* the shm segment is brand new */
        first_process = TRUE;

        shm_daemon_init ();
        
    }
    else {

        /* this shared mem segment was initialized before */
        int daemon_chk;

        /* check if the daemon recorded in the header is actually running */
        daemon_chk = kill (shm_config->hdr->first_process, 0);

        if (daemon_chk == -1 && errno == ESRCH) {

            fprintf (stderr, "shm_init:  recovering from crashed daemon pid %d\r\n", shm_config->hdr->first_process);

            first_process = TRUE;

            shm_daemon_init ();

        }
        else {
        

            first_process = FALSE;

            semid_shm = semget (shm_sk, 1, 0);
            if (semid_shm == -1) {
                fprintf (stderr, "shm_init:  could not attach to shared memory semaphore\r\n");
                exit (1);
            }
            
            /* we are NOT the initial process. if addresses don't match, re-attach! */
            /* (again, borrowed from RSM) */
            if (shm_config->hdr->shmad != shm_config->dta) {
                
                /* grab the pointers we need */
                void *old_addr = shm_config->dta;
                void *new_addr = shm_config->hdr->shmad;
                
                /* detach and reattach */
                if (shmdt (old_addr) == -1) {
                    fprintf (stderr, "shm_init:  detach failed during detach/reattach [shmdt error %s]\r\n", strerror (errno));
                    exit (1);
                }
                
                shm_config->dta = shmat (shm_config->seg_id, new_addr, 0);

                if (shm_config->dta == (void *) -1) {
                    fprintf (stderr, "shm_init:  fatal error attaching shared memory segment [shmat error '%s']\r\n", strerror (errno));
                    exit (1);
                }
                
                shm_config->hdr = (shm_hdr_t *) shm_config->dta;

                /* allocator buffer at the next page-aligned address after the header and allocation map */
                shm_config->buf = SHMALIGN(shm_config->dta + (sizeof (shm_hdr_t) * shm_config->pgct));
            }
            else {
                shm_config->buf = SHMALIGN(shm_config->dta + (sizeof (shm_hdr_t) * shm_config->pgct));
            }
        
        }

    }
    
    locktab_init ();
    
    assert(shm_address_to_page_num(shm_page_num_to_address(20)) == 20);
    

    return TRUE;
}

void shm_daemon_init(void)
{
    union semun arg;
    size_t alloc_map_size;
    key_t shm_sk;
    register int i;

    shm_sk = ftok (config_file, 5);
    
    semid_shm = semget (shm_sk, 1, 0660 | IPC_CREAT);
    if (semid_shm == -1) {
        fprintf (stderr, "shm_init:  failed to create shared memory semaphore\r\n");
        exit (1);
    }
    
    arg.val = 1;
    if (semctl (semid_shm, 0, SETVAL, arg) == -1) {
        fprintf (stderr, "shm_init:  failed to initialize shared memory semaphore\r\n");
        exit (1);
    }
    
    /* zero out the segment */
    memset (shm_config->dta, 0, shm_config->segsiz);
    
    /* we are the process that created the segment: initialize everything */
    shm_config->hdr->magic = shm_config->key;
    shm_config->hdr->first_process = pid;
    
    /* store the address we got into the shm_hdr (borrowed from RSM) */
    shm_config->hdr->shmad = shm_config->dta;
    shm_config->hdr->maintenance_mode = 0;
    
    /* alloc_map comes after the header */
/*
    shm_config->alloc_map = (shm_page_t *) (shm_config->dta + sizeof (shm_hdr_t));
*/    
    shm_config->buf = SHMALIGN(shm_config->dta + (sizeof (shm_hdr_t) * shm_config->pgct));
    printf ("shm_daemon_init:  allocator buffer aligned at %p (system page size %ld)\r\n", shm_config->buf, sysconf (_SC_PAGESIZE));
    
    for (i = 0; i < shm_config->pgct; i++) {
        shm_config->hdr->alloc_map[i].is_first = FALSE;
        shm_config->hdr->alloc_map[i].is_last = FALSE;
        shm_config->hdr->alloc_map[i].pg_state = PG_FREE;
    }
    
}

short shm_exit(void)
{
    int res;
    union semun arg;

    res = shmdt (shm_config->dta);

    if (res == -1) {
        fprintf (stderr, "shm_exit:  failure in shmdt()\r\n");
        return FALSE;
    }
    
    if (first_process) {

        res = shmctl (shm_config->seg_id, IPC_RMID, 0);

        if (res == -1) {
            fprintf (stderr, "shm_exit:  failure in shmctl()\r\n");
            return FALSE;
        }

        semctl (semid_shm, 0, IPC_RMID, arg);
        semctl (semid_locktab, 0, IPC_RMID, arg);
        semctl (semid_jobtab, 0, IPC_RMID, arg);
        semctl (semid_tp, 0, IPC_RMID, arg);
        semctl (semid_symtab, 0, IPC_RMID, arg);
        
    }

    return TRUE;
    
}

short shm_get_sem(void)
{
    
    int tries;
    struct sembuf s = {0, -1, 0};
    
    for (tries = 0; tries < 3; tries++) {

        if (semop (semid_shm, &s, 1) != -1) {
            return TRUE;
        }

        sleep (1);

    }

    return FALSE;
    
}

short shm_release_sem(void)
{
    struct sembuf s = {0, 1, 0};

    if (semop (semid_shm, &s, 1) != -1) {
        return TRUE;
    }

    return FALSE;
}

void shm_set_maintenance_mode (const short maintenance_mode)
{
    if (shm_get_sem () == TRUE) {
        shm_config->hdr->maintenance_mode = maintenance_mode;

        shm_release_sem ();
    }
}

shm_page_t *shm_get_alloc_map_entry(const int page_number)
{
    return &(shm_config->hdr->alloc_map[page_number]);
}

void *shm_page_num_to_address(const int page_num)
{
    return (void *) shm_config->buf + (shm_config->pgsiz * page_num);
}

int shm_address_to_page_num(const void *address)
{
    unsigned long val = (unsigned long) address - (unsigned long) shm_config->buf;
    unsigned long new_val = val / shm_config->pgsiz;
    
    return (int) new_val;
}

void *shm_alloc_pages(const int page_count)
{
    
    register int i;
    register int j;

    int candidate_page = 0;
    int free_pages_gotten = 0;
    
    shm_page_t *pg;

    if (shm_get_sem () == FALSE) {
        fprintf (stderr, "shm_alloc_pages:  could not get exclusive access to shared memory\r\n");
        exit (1);
    }

    
    for (i = 0; i < shm_config->pgct; i++) {

        pg = shm_get_alloc_map_entry (i);
        NULLPTRCHK(pg,"shm_alloc_pages");

        free_pages_gotten = 0;
        
        if (pg->pg_state == PG_FREE) {

            candidate_page = i;
            
            for (j = i; ((j < (i + page_count)) && (j < shm_config->pgct)); j++) {
                pg = shm_get_alloc_map_entry (j);

                if (pg->pg_state == PG_FREE) free_pages_gotten++;
                
            }

            if (free_pages_gotten == page_count) {

                for (j = candidate_page; j < (candidate_page + page_count); j++) {
                    pg = shm_get_alloc_map_entry (j);

                    pg->pg_state = PG_ALLOC;
                    pg->pid = pid;
                    
                    if (j == candidate_page) {
                        pg->is_first = TRUE;
                    }

                    if (j == candidate_page + (page_count - 1)) {
                        pg->is_last = TRUE;
                    }
                    
                }

                shm_release_sem ();
                
                return (void *) shm_config->buf + (shm_config->pgsiz * candidate_page);
                
            }
            
        }

    }

    shm_release_sem ();
    
    return (void *) NULL;
    
}


void *shm_alloc(const size_t bytes)
{
    int pages_needed = bytes / shm_config->pgsiz;
    float extra = bytes % shm_config->pgsiz;

    if (extra > 0) {
        pages_needed++;
    }

    return shm_alloc_pages (pages_needed);
}

void shm_free_page(const int page_number)
{
    register int i;
    shm_page_t *a = shm_get_alloc_map_entry (page_number);

    if (a->is_first == FALSE) {
        fprintf (stderr, "shm_free_page:  attempt to free page in the middle of allocation chain\r\n");
        return;
    }

    if (a->pg_state == PG_FREE) {
        fprintf (stderr, "shm_free_page:  double free attempted in page %d\r\n", page_number);
        exit (1);
    }
    
    if (shm_get_sem () == FALSE) {
        fprintf (stderr, "shm_free_page:  could not get exclusive access to shared memory\r\n");
        exit (1);
    }

    
    for (i = page_number; i < shm_config->pgct; i++) {

        a = shm_get_alloc_map_entry (i);        
        
        if (a->is_last) {
            a->is_first = FALSE;
            a->pg_state = PG_FREE;
            a->pid = 0;
            a->is_last = FALSE;

            shm_release_sem ();
            
            return;
        }
        else {
            a->is_first = FALSE;
            a->pg_state = PG_FREE;
            a->pid = 0;
            a->is_last = FALSE;
        }
        
    }

    shm_release_sem ();
   
}

void shm_free(const void *addr)
{
    shm_free_page (shm_address_to_page_num (addr));
}

void shm_dump(void)
{

    printf ("SHARED MEMORY CONFIGURATION\r\n");
    printf ("  pgsiz                   %ld\r\n", (unsigned long) shm_config->pgsiz);
    printf ("  pgct                    %d\r\n", shm_config->pgct);
    printf ("  key                     %ld\r\n", shm_config->key);
    printf ("  segid                   %d\r\n", shm_config->seg_id);
    printf ("  sizeof shm_page_t       %ld\r\n", (long) sizeof (shm_page_t));
    printf ("  segsiz                  %ld\r\n", (long) shm_config->segsiz);
    printf ("  shm address             %p\r\n", shm_config->dta);
    printf ("  alloc_map size          %ld\r\n", (unsigned long) sizeof (shm_page_t) * shm_config->pgct);
    printf ("  buf address             %p\r\n", shm_config->buf);
}

void shm_dump_pages(void)
{

    register int i;
    shm_page_t *p;

    printf ("%-10s%-10s%-10s%-10s%-10s\r\n", "PAGE", "PID", "BMHEAD", "BMTAIL", "STATE");
    
    for (i = 0; i < shm_config->pgct; i++) {

        p = shm_get_alloc_map_entry (i);

        printf ("%-10d%-10d%-10s%-10s%-10s\r\n",
                i,
                p->pid,
                (p->is_first == TRUE) ? "Y" : "N",
                (p->is_last == TRUE) ? "Y" : "N",
                (p->pg_state == PG_FREE) ? "PG_FREE" : "PG_ALLOC");
        
    }

}

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>