File:  [Coherent Logic Development] / freem / src / mumps.c
Revision 1.8: download - view: text, annotated - select for diffs
Mon Mar 24 16:10:48 2025 UTC (8 days, 7 hours ago) by snw
Branches: MAIN
CVS tags: v0-62-3, HEAD
Print error message and exit on OS/2 if daemon is run without --nofork

/*
 *   $Id: mumps.c,v 1.8 2025/03/24 16:10:48 snw Exp $
 *    main module of freem
 *
 *  
 *   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: mumps.c,v $
 *   Revision 1.8  2025/03/24 16:10:48  snw
 *   Print error message and exit on OS/2 if daemon is run without --nofork
 *
 *   Revision 1.7  2025/03/24 16:07:55  snw
 *   Force daemon into foreground on OS/2
 *
 *   Revision 1.6  2025/03/24 16:04:49  snw
 *   Force daemon into foreground on OS/2
 *
 *   Revision 1.5  2025/03/22 21:44:32  snw
 *   Make the startup messages fewer and add environment name to direct-mode prompt
 *
 *   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 <stddef.h>
#include "mpsdef.h"
#include "errmsg.h"
#include "iniconf.h"
#include "namespace.h"
#include "transact.h"
#include "init.h"
#include "consttbl.h"
#include <setjmp.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <limits.h>
#include <sys/types.h>
#include <pwd.h>
#include <string.h>
#include <fcntl.h>
#include "version.h"
#include "shmmgr.h"
#include "jobtab.h"
#include <errno.h>
#include <pwd.h>
#include <grp.h>


#if defined(HAVE_GETOPT_H)
# include <getopt.h>
#endif

#if !defined(PATH_MAX) && defined(_SCO_DS)
# define PATH_MAX 1024
#endif

extern int xecline(int typ);
extern char *getenv(const char *name);
void freem_usage(void);
void freem_print_version(void);
void init_ztrap(void);
void m_log (int, const char *);

int main (int argc, char **argv, char **envp)
{
    pid_t fork_pid = 0;
    short dx_mode = 0;
    
    int c;
    int import_env = FALSE;
    short skip_init = 0;
    
    int option_index = 0;

    char dx_mcode[512];
    char startup_routine[256];
    short routine_mode;
    char m_dialect[50];

    char nsnbuf[256];

    char d_username[40];
    char d_groupname[40];
    struct group *d_grp;
    struct passwd *d_user;
    gid_t d_gid;
    uid_t d_uid;

    short custom_user = FALSE;
    short custom_group = FALSE;
    
#if defined(HAVE_GETOPT_LONG)
    struct option long_options[] = {       
        {"help", no_argument, 0, 'h'},
        {"filter", no_argument, &frm_filter, TRUE},
        {"standard", required_argument, 0, 's'},
        {"import", no_argument, &import_env, TRUE},
        {"quiet", no_argument, &quiet_mode, TRUE},
        {"restricted", no_argument, &restricted_mode, TRUE},
        {"routine", required_argument, 0, 'r'},
        {"namespace", required_argument, 0, 'n'},
        {"environment", required_argument, 0, 'e'},
	{"version", no_argument, 0, 'v'},
	{"execute", required_argument, 0, 'x'},
        {"daemon", no_argument, 0, 'd'},
        {"nofork", no_argument, 0, 'k'},
        {"pidfile", required_argument, 0, 'p'},
        {"shmsize", required_argument, 0, 'S'},
        {"user", required_argument, 0, 'u'},
        {"group", required_argument, 0, 'g'},
        {0, 0, 0, 0}
    };
#endif
    
    char **env;      
    char *varname = (char *) calloc(STRLEN, sizeof(char));
    char *varval = (char *) calloc(STRLEN, sizeof(char));

    char *symname = (char *) calloc(STRLEN, sizeof(char));
    char *symval = (char *) calloc(STRLEN, sizeof(char));

    int namelen;
    int vallen;

    char cli_rtn_path[PATH_MAX] = {0};
    char *cli_rtn_file;
    char cli_rtn_name[256];

    routine_mode = FALSE;
    strcpy (m_dialect, "FREEM");
    
    m_argc = argc;			/* save arguments count     */
    m_argv = argv;			/* save arguments string    */
    m_envp = envp;			/* save environment pointer */

    strncpy (shm_env, "DEFAULT", 8);    /* establish default environment name */
    
    strncpy (d_username, "freem", 40);
    strncpy (d_groupname, "freem", 40);
    
    strcpy (zb, argv[0]);		/* name with which mumps has been called */
    stcnv_c2m (zb);
    strcpy (stack0, argv[0]);
    stcnv_c2m (stack0);


    /* strncpy (config_file, SYSCONFDIR"/freem.conf", 100); */



#if defined(HAVE_GETOPT_LONG)
    
    extern char *optarg;
    extern int optind, optopt;
    
    while (1) {
        
        c = getopt_long (argc, argv, "hsfiqRr:n:e:vx:dkpS:u:g:", long_options, &option_index);

        if (c == -1) break;
	if (c == '?') freem_usage ();
	
        switch (c) {
            case 'h':
                freem_usage ();
                break;

            case 'f':
                frm_filter = TRUE;
                break;

            case 'i':
                import_env = TRUE;
                break;

            case 'q':
                quiet_mode = TRUE;
                break;

            case 'e': /* specify FreeM environment */
                strncpy (shm_env, optarg, 255);
                break;

            case 'R':
                restricted_mode = TRUE;
                break;

            case 'r': /* startup routine */
                direct_mode = FALSE;
                usermode = 0;

                strcpy (startup_routine, optarg);
                startup_routine[strlen (startup_routine)] = '\201';

                routine_mode = TRUE;
                break;

            case 'n': /* namespace */
            {
                if (validate_namespace (optarg) == TRUE) {                
                    strcpy (nsname, optarg);
                }
                else {
                    fprintf (stderr, "freem:  invalid namespace '%s'\n", optarg);
                    exit (1);
                }
                
                break;
            }
	    case 'v': /* version */
		freem_print_version ();
		break;

	    case 'x': /* execute */
		direct_mode = FALSE;
		usermode = 0;
		dx_mode = 1;
		
		strncpy (dx_mcode, optarg, 512 - 1);

		stcnv_c2m (dx_mcode);

		break;

            case 's': /* --standard */

                if (strcmp (optarg, "M77") == 0) {
                    standard = D_M77;
                    strcpy (m_dialect, "M 1977");
                }
                else if (strcmp (optarg, "M84") == 0) {
                    standard = D_M84;
                    strcpy (m_dialect, "M 1984");
                }
                else if (strcmp (optarg, "M90") == 0) {
                    standard = D_M90;
                    strcpy (m_dialect, "M 1990");
                }
                else if (strcmp (optarg, "M95") == 0) {
                    standard = D_M95;
                    strcpy (m_dialect, "M 1995");
                }
                else if (strcmp (optarg, "MDS") == 0) {
                    standard = D_MDS;
                    strcpy (m_dialect, "Millennium Draft Standard");
                }
                else if (strcmp (optarg, "M5") == 0) {
                    standard = D_M5;
                    strcpy (m_dialect, "M5");
                }
                else if (strcmp (optarg, "FREEM") == 0) {
                    standard = D_FREEM;
                    strcpy (m_dialect, "FREEM");
                }
                else {
                    freem_usage ();
                }
                
                break;                

            case 'd': /* --daemon */
                run_daemon = TRUE;                
                break;

            case 'k': /* --nofork */
		nofork = TRUE;
                break;

            case 'p': /* --pidfile */
                pid_file_path = strdup (optarg);
                break;

            case 'S': /* --shmsize */
                shm_init_size = atol (optarg);
                break;

            case 'u': /* --user */
                strncpy (d_username, optarg, 40);
                custom_user = TRUE;
                break;

            case 'g': /* --group */
                strncpy (d_groupname, optarg, 40);
                custom_group = TRUE;
                break;
                

        } 

    }

#else
    
    {
        extern char *optarg;
        extern int optind, optopt;

        while ((c = getopt (argc, argv, "hsfiqRr:n:e:vx:dkpS:u:g:")) != -1) {

	    if (c == '?') freem_usage ();
	    
            switch (c) {

		case 'h':
		    freem_usage ();
		    break;
		
                case 'f':
                    frm_filter = TRUE;
                    break;

                case 'i':
                    import_env = TRUE;
                    break;

                case 'q':
                    quiet_mode = TRUE;
                    break;

                case 'e':  /* specify FreeM environment */
                    strncpy (shm_env, optarg, 255);
                    break;

                case 'R':
                    restricted_mode = TRUE;
                    break;
                    
                case 'r': /* startup routine */
                    direct_mode = FALSE;
                    usermode = 0;

                    strcpy (startup_routine, optarg);
                    startup_routine[strlen (startup_routine)] = '\201';

                    routine_mode = TRUE;
                    break;

                case 'n': /* namespace */
                    strcpy (nsname, optarg);               
                    break;

		case 'v':
		    freem_print_version ();
		    break;

		case 'x': /* execute */
		    direct_mode = FALSE;
		    usermode = 0;
		    dx_mode = 1;
		    
		    strncpy (dx_mcode, optarg, 512 - 1);
		    
		    stcnv_c2m (dx_mcode);

                    break;
                    
                case 'd': /* --daemon */
                    run_daemon = TRUE;
                    break;

                case 'k': /* --nofork */
                    nofork = TRUE;
                    break;

                case 'p': /* --pidfile */
                    pid_file_path = strdup (optarg);
                    break;

                case 's': /* --standard */
                    
                    if (strcmp (optarg, "M77") == 0) {
                        standard = D_M77;
                        strcpy (m_dialect, "M 1977");
                    }
                    else if (strcmp (optarg, "M84") == 0) {
                        standard = D_M84;
                        strcpy (m_dialect, "M 1984");
                    }
                    else if (strcmp (optarg, "M90") == 0) {
                        standard = D_M90;
                        strcpy (m_dialect, "M 1990");
                    }
                    else if (strcmp (optarg, "M95") == 0) {
                        standard = D_M95;
                        strcpy (m_dialect, "M 1995");
                    }
                    else if (strcmp (optarg, "MDS") == 0) {
                        standard = D_MDS;
                        strcpy (m_dialect, "Millennium Draft Standard");
                    }
                    else if (strcmp (optarg, "M5") == 0) {
                        standard = D_M5;
                        strcpy (m_dialect, "M5");
                    }
                    else if (strcmp (optarg, "FREEM") == 0) {
                        standard = D_FREEM;
                        strcpy (m_dialect, "FREEM");
                    }
                    else {
                        freem_usage ();
                    }
                
                    break;                

                    
                case 'S': /* --shmsize */
                    shm_init_size = atol (optarg);
                    break;

                case 'u': /* --user */
                    strncpy (d_username, optarg, 40);
                    custom_user = TRUE;
                    break;
                    
                case 'g': /* --group */
                    strncpy (d_groupname, optarg, 40);
                    custom_group = TRUE;
                    break;

                    
            } 
        }
    }
#endif

#if defined(__OS2__)
    if (run_daemon == TRUE && nofork == FALSE) {
        printf ("freem:  running on OS/2; daemon must be run with --nofork or -k\r\n");
        exit (1);
    }
#endif
    
    snprintf (config_file, 4096, "%s/freem/%s/freem.conf", SYSCONFDIR, shm_env);
    
    if (run_daemon == TRUE && geteuid() == 0) {

        if (custom_group) {
            d_grp = getgrnam (d_groupname);

            if (d_grp == NULL) {
                fprintf (stderr, "freem:  invalid group '%s'\n", d_groupname);
                exit (1);
            }
            
            d_gid = d_grp->gr_gid;
        }

        if (custom_user) {
            d_user = getpwnam (d_username);

            if (d_user == NULL) {
                fprintf (stderr, "freem:  invalid user '%s'\n", d_username);
                exit (1);
            }
            
            d_uid = d_user->pw_uid;
        }
        
    }
    
    if ((nofork == TRUE) && (run_daemon == FALSE)) {
        freem_usage ();
        exit (1);
    }

    if ((run_daemon == TRUE) && (nofork == FALSE)) {

        int fork_fd;
        
        /* daemonize */

        fork_pid = fork ();

        if (fork_pid < 0) {
            fprintf (stderr, "freem:  failure in fork()\r\n");
            m_log (1, "failure in initial fork()\r\n");
            exit (1);
        }

        if (fork_pid > 0) {
            exit (0);
        }

        if (setsid () < 0) {
            fprintf (stderr, "freem:  failure in setsid()\r\n");
            m_log (1, "failure in setsid()\r\n");
            exit (1);
        }

        signal (SIGCHLD, SIG_IGN);

        fork_pid = fork ();

        if (fork_pid < 0) {
            fprintf (stderr, "freem:  failure in fork()\r\n");
            m_log (1, "failure in second fork()\r\n");
            exit (1);
        }

        if (fork_pid > 0) {
            exit (0);
            m_log (1, "exiting from second fork");
        }

        umask (0);

        chdir ("/");       

        for (fork_fd = sysconf (_SC_OPEN_MAX); fork_fd > 0; fork_fd--) {
            close (fork_fd);
        }
        
        if (geteuid () == 0) {
            /* shed privileges */

            if (custom_group) {
                fprintf (stderr, "freem:  switching to group %s\n", d_groupname);
                m_log (1, "switching groups");
                
                if (setgid (d_gid) == -1) {
                    fprintf (stderr, "freem:  failure switching GID\n");
                    m_log (1, "failure switching GIDs");
                    exit (1);
                }
            }


            if (custom_user) {                
                fprintf (stderr, "freem:  switching to username %s\n", d_username);
                m_log (1, "switching users");
                
                if (setuid (d_uid) == -1) {
                    fprintf (stderr, "freem:  failure switching UID\n");
                    m_log (1, "failure switching UIDs");
                    exit (1);
                }
                if (chdir (d_user->pw_dir) == -1) {
                    fprintf (stderr, "freem:  chdir failure\n");
                    m_log (1, "failure in chdir");
                    exit (1);
                }            
            }

        }
        else {
            fprintf (stderr, "not euid 0");
        }

	freopen ("/dev/null", "r", stdin);
	freopen ("/dev/null", "w+", stdout);
	freopen ("/dev/null", "w+", stderr);

        run_daemon = TRUE;
        nofork = FALSE;        
        
        if (pid_file_path == NULL) {
            /* no PID file specified. choose one. */
            uid_t pid_uid;
            char *home_directory;
           
            pid_file_path = (char *) calloc (PATH_MAX, sizeof (char));
            NULLPTRCHK(pid_file_path,"main");

            home_directory = (char *) calloc (PATH_MAX, sizeof (char));
            NULLPTRCHK(home_directory,"main");
            
            pid_uid = geteuid ();

            if (pid_uid == 0) {
                /* we're running as root */
                strcpy (pid_file_path, "/var/run/freem.pid");
            }
            else {
                /* our user is a normie */
                struct passwd *pw = getpwuid (pid_uid);

                if (pw == NULL) {
                    m_log (1, "main:  failure in getpwuid()");
                }
                
                strcpy (home_directory, pw->pw_dir);               
                snprintf (pid_file_path, PATH_MAX - 1, "%s/.freem.pid", home_directory);                
            }

            free (home_directory);
            
        }

        {
            char pidfile_buf[256];
            int errsav;

            m_log (1, pid_file_path);

            pid_fd = open (pid_file_path, O_RDWR | O_CREAT, 0640);
            errsav = errno;
            
            if (pid_fd < 0) {
                m_log (1, "freem:  could not open PID file");
                m_log (1, strerror (errsav));
                exit (1);
            }

            if (lockf (pid_fd, F_TLOCK, 0) < 0) {
                errsav = errno;
                m_log (1, "freem: could not lock PID file - perhaps already running?");
                m_log (1, strerror (errsav));
                exit (1);
            }

            sprintf (pidfile_buf, "%d\n", getpid ());
            write (pid_fd, pidfile_buf, strlen (pidfile_buf));
         
        }
       
        
    } /* END of daemonization */
    
    /* handle passing of an arbitrary .m file on the command line */
    /* this is most often used for shebang-line scripts. */
    if (optind < argc) {

        /* not valid for daemon mode */
        if (run_daemon == TRUE) {
            fprintf (stderr, "freem:  cannot pass --daemon flag in shebang line\r\n");
            exit (1);
        }
        
        /* bail if file does not exist */
        if (access (argv[optind], F_OK) == -1) {

            set_io (UNIX);
            
            fprintf (stderr, "Routine %s does not exist.\n", argv[optind]);

            exit (1);

        }

	skip_init = 1;

	/* initialize FreeM environment */
	strncpy (nsnbuf, nsname, 255);    
	if (init (nsnbuf) == FALSE) {
	    
	    set_io (UNIX);
	    fprintf (stderr, "\nError initializing FreeM.\n");
	    
	    exit (1);            
	    
	}        

	
        direct_mode = FALSE;
        usermode = 0;

        /* was a path specified at all? */
        if (strchr (argv[optind], '/') == NULL) {
	    
            /* the entirety of argv[optind] is the filename */
            cli_rtn_file = argv[optind];            

            /* use the current directory */
            sprintf (cli_rtn_path, ".");

        }
        else {

            /* isolate the filename from the path */
            cli_rtn_file = strrchr (argv[optind], '/') + 1;
            
            /* isolate the routine name from the filename */
            strncpy (cli_rtn_name, cli_rtn_file, strchr (cli_rtn_file, '.') - cli_rtn_file);
            
            /* isolate the path from the routine file */
            strncpy (cli_rtn_path, argv[optind], strrchr (argv[optind], '/') - argv[optind]);      
	    
/*	    set_io (UNIX);
	    printf ("cli_rtn_name = '%s' cli_rtn_path = '%s'\n", cli_rtn_name, cli_rtn_path);
	    set_io (MUMPS);
*/	    
        }

        /* do we have a file extension? */
        if (strchr (cli_rtn_file, '.') != NULL) {
            
            /* if so, just remove it */
            strncpy (cli_rtn_name, cli_rtn_file, strchr (cli_rtn_file, '.') - cli_rtn_file);

        }
        else {

            /* otherwise, just take a direct copy */
            strcpy (cli_rtn_name, cli_rtn_file);

        }

        /* make this the startup routine */
        snprintf (startuprou, 256, "^%s\201", cli_rtn_name);

        /* re-work the namespace config to search for the 
           routine in the discovered path */
        if (cli_rtn_name[0] == '%') {

            snprintf (rou0plib, 256, "%s\201", cli_rtn_path);
            snprintf (rou1plib, 256, "%s\201", cli_rtn_path);

        }
        else {

            snprintf (rou0path, 256, "%s\201", cli_rtn_path);
            snprintf (rou1path, 256, "%s\201", cli_rtn_path);

        }

    }


    if (!file_exists (config_file)) {

        set_io (UNIX);
        fprintf (stderr, "\nFreeM has not been configured. Please run 'fmadm configure'.\n\n\n\n");
        
        exit (2);

    }

    if (!skip_init) {
	/* initialize FreeM environment */
	strncpy (nsnbuf, nsname, 255);    
	if (init (nsnbuf) == FALSE) {
	    
	    set_io (UNIX);
	    fprintf (stderr, "\nError initializing FreeM.\n");

	    exit (1);

	}
    }

    if (first_process == TRUE) {
        
        char verstr[500];
        pid_t stop_requester;

        if (run_daemon == FALSE) {
            fprintf (stderr, "freem:  re-run with --daemon or -d command-line flags\r\n");
            cleanup ();
            exit (1);
        }
        
        stcpy (verstr, FREEM_VERSION_STR);
        stcnv_m2c (verstr);
        
        fprintf (stderr, "Coherent Logic Development FreeM version %s\r\n", verstr);
        fprintf (stderr, "freem:  shared memory for environment %s initialized (%ld bytes of shared memory @ '%p')\r\nfreem:  system ready\r\n", shm_env, (long) shm_init_size, shm_config->dta);
        
        for (;;) {

            job_set_status (pid, JSTAT_HOUSEKEEPING);
            

            if (shm_config->hdr->maintenance_mode == 1) {

                job_slot_t *slot;

                fprintf (stderr, "freem:  entering maintenance mode\r\n");
                m_log (1, "freem:  entering maintenance mode");
                
                for (slot = shm_config->hdr->jobtab_head; slot != NULL; slot = slot->next) {

                    if ((slot->pid != pid) && ((slot->flags & JFLG_FMADM) != JFLG_FMADM)) {
                        kill (slot->pid, SIGINT);
                    }

                }
                
            }
            
            if ((stop_requester = job_stop_requested (pid)) != 0) {
                int connected_jobs;
                
                job_set_status (pid, JSTAT_SHUTDOWN);
                
                connected_jobs = job_count ();
                
                fprintf (stderr, "freem:  STOP requested by pid %d\r\n", stop_requester);
                fprintf (stderr, "freem:  there are %d job(s) connected to this environment\r\n", connected_jobs);
                
                if (connected_jobs > 1) {
                    
                    fprintf (stderr, "freem:  asking non-daemon job(s) to disconnect and halt...\r\n");
                    job_request_all_stop ();

                    fprintf (stderr, "freem:  waiting 5 seconds for job(s) to disconnect...\r\n");
                    sleep (5);

                    connected_jobs = job_count ();
                    if (connected_jobs > 1) {
                        fprintf (stderr, "freem:  sending SIGTERM to %d job(s)...\r\n", connected_jobs);
                        job_signal_all (SIGTERM);
                        fprintf (stderr, "freem:  waiting 5 seconds for job(s) to disconnect...\r\n");
                        
                        sleep (5);
                    }
                    
                    connected_jobs = job_count ();
                    if (connected_jobs > 1) {
                        fprintf (stderr, "freem:  sending SIGKILL to %d job(s)...\r\n", connected_jobs);
                        job_signal_all (SIGKILL);
                    }
                   
                    job_gc_mark ();
                    job_gc_sweep ();
                    
                }

                fprintf (stderr, "freem:  terminating\r\n");
                cleanup ();
                exit (0);
                
            }
            
            job_gc_mark ();
            job_set_status (pid, JSTAT_IDLE);
            sleep (1);
            
            job_set_status (pid, JSTAT_HOUSEKEEPING);
            job_gc_sweep ();
            sleep (1);
        }
            
    }
        
    
#if !defined(_AIX)
    if(import_env == TRUE) {

	int i_maxlen = 255;
	
        for(env = envp; *env != 0; env++) {
	    
            namelen = 0;
            vallen = 0;
            
            varname = strtok(*env, "=");
            varval = strtok(NULL, "=");

            if(varval != NULL) {
                namelen = strlen (varname);
                vallen = strlen (varval);

		snprintf (symname, i_maxlen, "ENV.%s\201\201", varname);
                strncpy (symval, varval, i_maxlen);
		
                stcnv_c2m (symval);

                symtab (set_sym, symname, symval);
            }
        }
    }
#endif    

    
    if (direct_mode == TRUE && quiet_mode == FALSE) {

	char verstr[500];
        char version[256];
	
	stcpy (verstr, FREEM_VERSION_STR);
	stcnv_m2c (verstr);
	        
        snprintf (version, 255, "\r\nCoherent Logic Development FreeM version %s [DIALECT: %s%s]\r\n\201", verstr, m_dialect, (restricted_mode == TRUE ? "/RESTRICTED" : ""));
	write_m (version);

	snprintf (version, 255, "Copyright (C) 2014, 2020, 2021, 2023, 2025 Coherent Logic Development LLC\r\n\r\n\201");
	write_m (version);

        /*
        printf ("Environment:       \t%s\r\n", shm_env);
        printf ("Environment Daemon:\tPID %d\r\n", shm_config->hdr->first_process);
        printf ("Interpreter Process:\tPID %d\r\n", pid);
        */

    }
    else {
        write_m ("\r\n\r\n\201");
    }

    if (dx_mode) {
	char k_buf[512];	

	snprintf (k_buf, 512 - 1, "%%TMPINITMCODE\201\201");
	symtab (set_sym, k_buf, dx_mcode);
        const_define (k_buf, dx_mcode);
    }

    if (routine_mode) {
        char k_buf[512];

        snprintf (k_buf, 512 - 1, "%%TMPINITROUTINE\201\201");
        symtab (set_sym, k_buf, startup_routine);
        const_define (k_buf, startup_routine);
    }
    
    /* run mumps */
    xecline (1);

    exit (0);	/* we should never reach that statement */

} /* end of main() */

void freem_usage(void)
{
    fprintf (stdout, "\nusage:  freem [OPTION...]\n\n");
    
    fprintf (stdout, "OPTIONS:\n\n");
    
#if defined(HAVE_GETOPT_LONG)
    fprintf (stdout, "\t-h, --help\n\t\tdisplays this help message\n\n");
    fprintf (stdout, "\t-i, --import\n\t\timports UNIX environment variables as M locals\n\n");
    fprintf (stdout, "\t-e <environment-name>, --environment=<environment-name>\n\t\tsets active environment to <environment-name> (DEFAULT if unspecified)\n\n");
    fprintf (stdout, "\t-f, --filter\n\t\tallows M code to be used as a filter\n\n");
    fprintf (stdout, "\t-n <NAMESPACE>, --namespace=<NAMESPACE>\n\t\tselects <NAMESPACE> as the startup namespace instead of USER\n\n");
    fprintf (stdout, "\t-q, --quiet\n\t\tdisables startup messages and prompt string\n\n");
    fprintf (stdout, "\t-r <LABEL^ROUTINE>, --routine=<LABEL^ROUTINE>\n\t\texecute <LABEL^ROUTINE> on startup instead of entering direct mode\n\n");
    fprintf (stdout, "\t-s, --standard\n\t\trestrict access to FreeM vendor extensions not present in relevant standards*\n\n");
    fprintf (stdout, "\t-v, --version\n\t\tdisplay FreeM version information\n\n");
    fprintf (stdout, "\t-x <MCODE>, --execute=<MCODE>\n\t\texecute M code <MCODE> on startup\n\n");
    fprintf (stdout, "\t-d, --daemon\n\t\trun the FreeM daemon (one and only one FreeM daemon must always be running)\n\n");
    fprintf (stdout, "\t-k, --nofork\n\t\trun the FreeM daemon in foreground (requires --daemon)\n\n");
    fprintf (stdout, "\t-p <PIDFILE>, --pidfile=<PIDFILE>\n\t\tuse <PIDFILE> to record the PID of the FreeM daemon\n\n\n");
    fprintf (stdout, "\t-S <BYTES>, --shmsize=<BYTES>\n\t\tsets the size of the shared memory segment where FreeM stores the job table, lock table, and IPC table.\n");
#else
    fprintf (stdout, "\t-h\n\t\tdisplays this help message\n\n");
    fprintf (stdout, "\t-i\n\t\timports UNIX environment variables as M locals\n\n");
    fprintf (stdout, "\t-e <environment-name>\n\t\tsets active environment to <environment-name> (DEFAULT if unspecified)\n\n");
    fprintf (stdout, "\t-f\n\t\tallows M code to be used as a filter\n\n");
    fprintf (stdout, "\t-n <NAMESPACE>\n\t\tselects <NAMESPACE> as the startup namespace instead of USER\n\n");
    fprintf (stdout, "\t-q\n\t\tdisables startup messages and prompt string\n\n");
    fprintf (stdout, "\t-r <LABEL^ROUTINE>\n\t\texecute <LABEL^ROUTINE> on startup instead of entering direct mode\n\n");
    fprintf (stdout, "\t-s\n\t\trestrict access to FreeM vendor extensions not present in relevant standards*\n\n");
    fprintf (stdout, "\t-v\n\t\tdisplay FreeM version information\n\n");
    fprintf (stdout, "\t-x <MCODE>\n\t\texecute M code <MCODE> on startup\n\n");
    fprintf (stdout, "\t-d\n\t\trun the FreeM daemon (one and only one FreeM daemon must always be running)\n\n");
    fprintf (stdout, "\t-k\n\t\trun the FreeM daemon in foreground (requires --daemon)\n\n");
    fprintf (stdout, "\t-p <PIDFILE>\n\t\tuse <PIDFILE> to record the PID of the FreeM daemon\n\n\n");
    fprintf (stdout, "\t-S <BYTES>\n\t\tsets the size of the shared memory segment where FreeM stores the job table, lock table, and IPC table.\n");
#endif    
    fprintf (stdout, "\t\t  - Each concurrent job takes %d bytes (1 page) of shared memory\n", PG_SIZE);
    fprintf (stdout, "\t\t  - Each LOCK takes %d bytes (2 pages) of shared memory\n", PG_SIZE * 2);
    fprintf (stdout, "\t\t  - Each IPC takes %d bytes (1 page) of shared memory\n\n", PG_SIZE);
    fprintf (stdout, "\t* FreeM attempts to conform (at least loosely) to the Millennium Draft Standard when this mode is selected.\n\n\n");
    fprintf (stdout, "Report bugs to: freem-bugs@coherent-logic.com\n");
    fprintf (stdout, "FreeM home page: <https://freem.coherent-logic.com>\n\n");   
    
    exit (1);
}

void freem_print_version(void)
{
    char verstr[500];
    stcpy (verstr, FREEM_VERSION_STR);
    stcnv_m2c (verstr);
    
    fprintf (stdout, "Coherent Logic Development FreeM %s\n", verstr);
    fprintf (stdout, "Copyright (C) 2014, 2020, 2021, 2023 Coherent Logic Development LLC\n\n");
    fprintf (stdout, "License AGPLv3+: GNU AGPL version 3 or later <https://gnu.org/license/agpl-3.0.html>\n");
    fprintf (stdout, "This is free software: you are free to change and redistribute it.\n");
    fprintf (stdout, "There is NO WARRANTY, to the extent permitted by law.\n");
    
    exit (0);
}

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