/*
* $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>