/*===========================================================================*
* *
* smtlib.c - *
* *
* Copyright (c) 1991-2003 iMatix Corporation *
* *
* ------------------ GPL Licensed Source Code ------------------ *
* iMatix makes this software available under the GNU General *
* Public License (GPL) license for open source projects. For *
* details of the GPL license please see www.gnu.org or read the *
* file license.gpl provided in this package. *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public *
* License along with this program in the file 'license.gpl'; if *
* not, write to the Free Software Foundation, Inc., 59 Temple *
* Place - Suite 330, Boston, MA 02111-1307, USA. *
* *
* You can also license this software under iMatix's General Terms *
* of Business (GTB) for commercial projects. If you have not *
* explicitly licensed this software under the iMatix GTB you may *
* only use it under the terms of the GNU General Public License. *
* *
* For more information, send an email to info@imatix.com. *
* -------------------------------------------------------------- *
*===========================================================================*/
#include "sfl.h" /* SFL Library header file */
#include "smtlib.h" /* Prototypes for functions */
/* -------------------------------------------------------------------------
* Global variables
*/
int
smt_errno = 0; /* Set when API detects an error */
char
*smt_errlist [] = { /* Corresponding error messages */
"No errors",
"Event already declared",
"Agent had internal error",
"Method already declared",
"Unknown event name",
"Unknown method name",
"Event queue not defined",
"Unknown semaphore name",
"Agent not defined",
"Thread not declared",
"SMT API not initialised",
"Not enough heap memory left",
"Event queue is empty",
"Event queue is full",
"Semaphore already declared",
"Agent already declared",
"Agent not initialised",
"Thread already declared",
"Too many threads"
};
event_t
_the_next_event, /* May be set by thread code */
_the_external_event, /* Set by event_wait() */
_the_exception_event; /* May be set by thread code */
Bool
_exception_raised, /* May be set by thread code */
_io_completed, /* Current lazy I/O finished */
_repeat_module; /* Repeat current action module */
/* The signal_raised flag is true when we've received an interrupt, until */
/* the interrupt is handled (in case of a timer interrupt) or until the */
/* application ends. The signal_value is one of SMT_SIGNAL_XXXX. */
/* The shutdown_request flag is true from the moment a fatal signal (kill, */
/* interrupt, or segment violation) is received to the moment the SHUTDOWN */
/* events are broadcast. It's primary use is to halt any ongoing dialog */
/* activity. Basically a SHUTDOWN overrides any other on-going events. */
Bool
signal_raised, /* True after interrupt */
shutdown_request; /* When kill signal in progress */
int
signal_value; /* Value of signal */
/* -------------------------------------------------------------------------
* Global variables local to this source
*/
typedef struct _EXITFCT { /* Exit function */
struct _EXITFCT
*next, *prev; /* Doubly-linked list */
function handler; /* Methods accepted by agent */
} EXITFCT;
static Bool smt_alive = FALSE; /* Have we been initialised? */
static SYMTAB *dict; /* Main dictionary */
static NODE exitfcts; /* List of exit functions */
static NODE agents; /* List of agents */
static NODE semaphs; /* List of semaphores */
static THREAD active_threads; /* Active threads list */
static int cur_threads; /* Number of threads created */
static QID console = { 0, 0 }; /* Console queue, if any */
static QID timer = { 0, 0 }; /* Timer queue, if any */
static int break_wanted; /* Break current module list */
static SEMAPH *break_semaph; /* Break for this semaphore */
static char *cur_agent = ""; /* For error detection */
static char *cur_state = ""; /* For error detection */
static char *cur_event = ""; /* For error detection */
static char *cur_module = ""; /* For error detection */
static char *cur_step = ""; /* For error detection */
#define BREAK_CONTINUE 0 /* Reasons for breaking the list */
#define BREAK_RECYCLE 1 /* of actions for an event */
#define BREAK_WAIT_EVENT 2 /* in a state transition */
#define BREAK_WAIT_SEMAPH 3
/* -------------------------------------------------------------------------
* Local function prototypes and macros
*/
static void handle_signals (void);
static void handle_signal (int the_signal);
static char *get_queue_name (const QID *qid);
static EVENT *event_locate (QUEUE *queue, EVENT *event);
static char *get_entity_name (const char *agent, const char *thread);
static char *get_method_name (const char *agent_name,
const char *method_name);
static THREAD *thread_relink (THREAD *left, THREAD *thread, THREAD *right);
static void activate_thread (THREAD *thread);
static int execute_thread (THREAD *thread);
static void suspend_thread (THREAD *thread, int state);
static int deliver_events (void);
static int deliver_event (QUEUE *queue, THREAD *thread);
static char *get_semaph_name (const char *semaph_name);
static void handle_atexit (void);
static void set_io_completed (int rc);
/* We can relink threads in various interesting ways */
#define thread_relink_after(t,a) thread_relink ((a), (t), (a)-> right)
#define thread_relink_before(t,b) thread_relink ((b)-> left, (t), (b))
#define thread_unlink(t) thread_relink ((t)-> left, (t), (t)-> right)
/* Order these to include or exclude tracing code... */
/* When tracing is enabled, about 20% of the application's time is spent */
/* in the trace() function, so generally we leave this switched off. */
#define SMT_TRACE
#undef SMT_TRACE
/* ---------------------------------------------------------------------[<]-
Function: smt_init
Synopsis: Initialises the SMT. Returns 0 if there were no errors. Else
returns -1, after seting smt_errno to one of these values:
SMT_OUTOFMEMORY Not enough heap memory left
---------------------------------------------------------------------[>]-*/
int
smt_init (void)
{
int feedback = 0;
#if (defined (SMT_TRACE))
trace ("smt_init");
#endif
if (!smt_alive)
{
dict = sym_create_table ();
if (dict == NULL)
{
smt_errno = SMT_OUTOFMEMORY;
feedback = -1;
}
/* Initialise list of agents */
node_reset (&agents);
/* Initialise list of exit functions */
node_reset (&exitfcts);
/* Initialise list of semaphores */
node_reset (&semaphs);
/* Initialise list of active threads */
active_threads.left = &active_threads;
active_threads.right = &active_threads;
cur_threads = 0;
/* Handle interrupt signals */
handle_signals ();
/* On some systems we get a 'broken pipe' when a connection fails */
# if defined (SIGPIPE)
signal (SIGPIPE, SIG_IGN);
# endif
/* We pass through handle_atexit() before closing-down */
atexit (handle_atexit);
smt_alive = TRUE; /* SMT kernel is now active */
}
return (feedback);
}
static void
handle_atexit (void)
{
if (smt_alive)
smt_term ();
}
/* -------------------------------------------------------------------------
* handle_signals -- internal
*
* Directs all handlable signals to handle_signal.
*/
local
handle_signals (void)
{
signal (SIGINT, handle_signal);
signal (SIGTERM, handle_signal);
signal (SIGALRM, handle_signal);
signal (SIGSEGV, handle_signal);
signal_raised = FALSE;
shutdown_request = FALSE;
}
/* -------------------------------------------------------------------------
* handle_signal -- internal
*
* Sets signal_raised to TRUE, and stores the signal value. The signal
* is handled by the deliver_events() function the next time it is called.
* We do not try to handle the signal directly here because it is unsafe
* to play with the kernel's linked lists in an interrupt handler.
*/
local
handle_signal (int the_signal)
{
static time_t
last_interrupt = 0; /* To trap looping code */
#if (defined (SMT_TRACE))
trace ("handle_signal: %d", the_signal);
#endif
/* A normal shutdown should not take more than X seconds. If we get */
/* a second signal after that time, we can assume the application is */
/* looping and will not halt normally; we can then abort it. We do */
/* not abort for alarm signals. */
if (the_signal == SIGALRM)
last_interrupt = 0;
else
{
if (last_interrupt == 0)
last_interrupt = time (NULL);
if (time (NULL) - last_interrupt > SMT_LOOP_DETECTION)
{
coprintf ("Application looping - aborted");
abort ();
}
}
/* Store value of signal */
signal_raised = TRUE;
switch (the_signal)
{
case SIGINT:
signal_value = SMT_SIGNAL_INT;
shutdown_request = TRUE;
break;
case SIGTERM:
signal_value = SMT_SIGNAL_TERM;
shutdown_request = TRUE;
break;
case SIGALRM:
signal_value = SMT_SIGNAL_ALRM;
break;
case SIGSEGV:
/* In principle it's not correct to do a printf inside a signal
* handler, but in this case the program is just about to die.
* Hey, how much more trouble can we get into?
*/
fflush (stderr);
fprintf (stderr, "Fatal error in application - aborted\n");
fprintf (stderr, "%s\n", smt_crash_report ());
abort ();
}
signal (SIGINT, handle_signal);
signal (SIGTERM, handle_signal);
signal (SIGALRM, handle_signal);
signal (SIGSEGV, handle_signal);
}
/* ---------------------------------------------------------------------[<]-
Function: smt_term
Synopsis: Shuts-down the SMT. Destroys all agents, methods, queues,
events, threads. Returns 0 if there were no errors, otherwise returns
-1 and sets smt_errno to one of these values:
SMT_NOTREADY smt_init() was not called, or failed
SMT_OUTOFMEMORY Not enough heap memory left
---------------------------------------------------------------------[>]-*/
int
smt_term (void)
{
EXITFCT *exitfct; /* Exit function address */
#if (defined (SMT_TRACE))
trace ("smt_term");
#endif
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (-1);
}
FORLIST (exitfct, exitfcts) /* Execute all exit functions */
(*exitfct-> handler) ();
while (exitfcts.next != &exitfcts) /* And free the memory */
node_destroy (exitfcts.next);
agent_destroy (NULL); /* Free all agents */
semaph_destroy (NULL); /* Free all semaphores */
sym_delete_table (dict);
smt_alive = FALSE; /* SMT is now disactivated */
return (0);
}
/* ---------------------------------------------------------------------[<]-
Function: smt_exec_full
Synopsis: Executes the current set of threads until there are no more
active threads left, or events to deliver. At this point we say that
the application has 'halted'. Returns 0.
---------------------------------------------------------------------[>]-*/
int
smt_exec_full (void)
{
#if (defined (SMT_TRACE))
trace ("smt_exec_full");
#endif
while (smt_exec_step ());
return (0);
}
/* ---------------------------------------------------------------------[<]-
Function: smt_exec_step
Synopsis: Rebuilds the active list, delivering any events possible, then
executes the first thread in the active list. Returns TRUE so long as
there is something happening; returns FALSE when the application has
'halted'.
---------------------------------------------------------------------[>]-*/
Bool
smt_exec_step (void)
{
#if (defined (SMT_TRACE))
trace ("smt_exec_step");
#endif
deliver_events (); /* Rebuild active thread list */
if (smt_active ()) /* and execute first thread */
{ /* if any is active */
if (execute_thread (active_threads.right))
/* Fatal-error action for a thread is simple: destroy it */
thread_destroy (active_threads.right, FALSE);
return (TRUE);
}
else
return (FALSE);
}
/* -------------------------------------------------------------------------
* deliver_events -- internal
*
* Send-out shutdown events if necessary
* Expires events that went past their timeouts
* Delivers events to threads when possible
* Releases threads waiting on semaphores, if possible
* Returns number of events delivered = number of threads moved to active
* list.
*/
static int
deliver_events (void)
{
AGENT *agent; /* Agent information block */
QUEUE *queue; /* Queue information block */
THREAD *thread; /* Thread information block */
SEMAPH *semaph; /* Semaphore information block */
int feedback = 0; /* Number of events we delivered */
byte msg_body [2]; /* Shutdown message body */
int msg_size; /* Size of formatted msg_body */
Bool cur_raised; /* Is there a signal to handle? */
#if (defined (SMT_TRACE))
trace ("deliver_events");
#endif
/* Handle alarm signal if any */
cur_raised = signal_raised; /* Get value NOW (it may change) */
if (cur_raised && signal_value == SMT_SIGNAL_ALRM)
{
/* We send an event to the timer queue, if any is defined, */
/* so that the timer can generate its alarm events. */
SEND (&timer, "TIMER", "");
handle_signals ();
cur_raised = FALSE;
}
/* Process each agent */
FORLIST (agent, agents)
{
/* Process each queue in the agent */
FORLIST (queue, agent-> queues)
{
if (cur_raised) /* Do we need to handle a signal? */
{
if (!queue-> shutdown)
{
queue-> shutdown = TRUE;
msg_size = exdr_write (msg_body, SMT_SHUTDOWN,
(word) signal_value);
event_send (
&queue-> qid, /* Send to specified queue */
NULL, /* No queue for reply */
"SHUTDOWN", /* Name of event to send */
msg_body, /* Event body */
msg_size, /* Event body size */
/* No response events */
NULL, NULL, NULL,
0); /* No timeout */
}
}
else
if (queue-> timed_events > 0)
queue_flush (queue); /* Flush old events */
/* Deliver events, if any, to passive threads, if any */
if (queue-> cur_events > 0)
{
thread = queue-> threads.next;
while ((NODE *) thread != &queue-> threads)
{
thread = thread-> next;
if (thread-> prev-> state == SMT_THREAD_PASSIVE
|| thread-> prev-> state == SMT_THREAD_WAIT_EVENT)
{
feedback += deliver_event (queue, thread-> prev);
break;
}
}
}
}
}
shutdown_request = FALSE; /* Any SHUTDOWNs were delivered */
FORLIST (semaph, semaphs)
{
if (semaph-> value > 0
&& semaph-> threads.right != &semaph-> threads)
{
thread = semaph-> threads.right;
ASSERT (thread-> state == SMT_THREAD_WAIT_SEMAPH);
semaph-> value--;
activate_thread (thread);
feedback++;
}
}
return (feedback); /* Number of fresh threads */
}
/* -------------------------------------------------------------------------
* deliver_event -- internal
*
* Delivers one event to the specified thread. If the event queue holds
* more than one event, the event with the highest priority is delivered.
* Moves the thread to the active list, if the event was accepted. The
* active list is maintained in descending priority order. A thread is
* attached before the first thread with a lower priority. Returns 1 if
* the event was delivered, zero if not. If the event was not delivered,
* sends an error message to the console, if any is defined.
*/
static int
deliver_event (QUEUE *queue, THREAD *thread)
{
AGENT *agent; /* Agent information block */
EVENT *event; /* Event information block */
EVENT *deliver_event; /* Event to deliver */
METHOD *method; /* Method information block */
int top_priority; /* Highest event priority so far */
#if (defined (SMT_TRACE))
trace ("deliver_event: thread=%s in %s", thread-> name,
queue-> agent-> name);
#endif
/* Get event to deliver - find event with highest method priority */
top_priority = -1;
deliver_event = NULL;
agent = queue-> agent;
event = queue-> events.next;
while ((NODE *) event != &queue-> events)
{
if (event-> priority == SMT_PRIORITY_NULL)
{
/* Lookup method; if method is not declared, reject event */
method = method_lookup (agent, event-> name);
if (method == NULL)
{
/* Reject this event, but keep our place in the queue... */
sendfmt (&console, "ERROR", "Event %s not declared by %s",
event-> name, agent-> name);
event = event-> next;
event_reject (queue, event-> prev);
continue;
}
else
/* If null method, accept event but discard it */
if (method-> event_number == SMT_NULL_EVENT)
{
event = event-> next;
node_destroy (event_accept (queue, event-> prev));
continue;
}
/* Update the event values, to save a lookup next time */
event-> priority = method-> priority;
event-> event_number = method-> event_number;
}
if (event-> priority > top_priority)
{
top_priority = event-> priority;
deliver_event = event;
}
event = event-> next;
}
if (deliver_event)
{
/* Deliver event to thread, and move thread to active queue */
/* If thread is waiting for an event (after event_wait, between */
/* two action modules, we don't change _the_next_event. */
thread-> the_external_event = deliver_event-> event_number;
if (thread-> state == SMT_THREAD_PASSIVE)
thread-> the_next_event = deliver_event-> event_number;
ASSERT (thread-> event == NULL);
thread-> event = event_accept (queue, deliver_event);
activate_thread (thread); /* Move thread to active list */
return (1); /* We delivered the event */
}
else
return (0); /* We did not find an event */
}
/* -------------------------------------------------------------------------
* activate_thread -- internal
*
* Moves the thread onto the active list, according to the priority of the
* thread agents. Sets the thread state to SMT_THREAD_ACTIVE.
*/
static void
activate_thread (THREAD *thread)
{
THREAD *active; /* Thread in active list */
int priority; /* Agent priority */
/* Find first thread with lower priority */
/* We may end-up with active pointing to the list head */
priority = thread-> queue-> agent-> priority;
for (
active = active_threads.right;
active != &active_threads;
active = active-> right)
if (priority > active-> queue-> agent-> priority)
break;
thread_unlink (thread);
thread_relink_before (thread, active);
thread-> state = SMT_THREAD_ACTIVE;
thread-> queue-> agent-> switch_tally++;
}
/* -------------------------------------------------------------------------
* execute_thread -- internal
*
* Executes the specified thread so long as the thread produces internal
* events. When the thread requests an external event it is moved off the
* active list. If a thread fails a lazy i/o, execution stops, but the
* thread remains on the active list. Returns 0 if there were no errors;
* returns -1 if there was a fatal dialog error.
* We send animation to the SFL console (see sflcons.c).
*/
static int
execute_thread (THREAD *thread)
{
HOOK *hook;
AGENT *agent = thread-> queue-> agent;
#if (defined (SMT_TRACE))
trace ("execute_thread: thread='%s' in %s", thread-> name, agent-> name);
#endif
/* Take events from thread context, for duration of function */
_the_next_event = thread-> the_next_event;
_the_external_event = thread-> the_external_event;
/* If first time, initialise_the_thread for initial event */
if (thread-> LR_state == SMT_NULL_STATE)
{
/* Keep track of current agent/state/event for error tracking */
cur_agent = agent-> name;
cur_state = "Initialising";
cur_event = "(None)";
cur_module = "Initialise-The-Thread";
_the_next_event = SMT_NULL_EVENT;
(agent-> initialise) (thread);
thread-> LR_state = 0; /* First state is zero */
/* If initialisation code did not supply an event, we will wait */
/* for an event to arrive in the queue. Typically this supplies */
/* the thread with startup arguments. */
if (_the_next_event == SMT_NULL_EVENT)
{
suspend_thread (thread, SMT_THREAD_PASSIVE);
return (0); /* Signal execution went okay */
}
}
/* Execute thread steps until we have to halt */
FOREVER
{
/* If we just entered a state, process event & get action list */
if (thread-> LR_index == 0)
{
thread-> LR_event = _the_next_event;
if (thread-> LR_event > agent-> maxevent
|| thread-> LR_event < 0)
{
coprintf ("%s: state %s - event %s is out of range",
agent-> name,
agent-> LR_sname [thread-> LR_state],
agent-> LR_ename [thread-> LR_event]);
return (-1);
}
thread-> LR_savest = thread-> LR_state;
thread-> LR_index = agent-> LR_action
[thread-> LR_state * agent-> maxevent + thread-> LR_event];
/* If no action for this event, try the defaults state, if any */
if (thread-> LR_index == 0
&& agent-> LR_defaults != SMT_NULL_STATE)
{
thread-> LR_state = agent-> LR_defaults;
thread-> LR_index = agent-> LR_action
[thread-> LR_state * agent-> maxevent + thread-> LR_event];
}
if (agent-> animate && thread-> animate)
{
coprintf ("%-10s %4ld> %s:",
agent-> name,
thread-> thread_id,
agent-> LR_sname [thread-> LR_state]);
coprintf ("%-10s %4ld> (--) %s",
agent-> name,
thread-> thread_id,
agent-> LR_ename [thread-> LR_event]);
}
/* If we still did not get a valid index, event is bad */
if (thread-> LR_index == 0)
{
coprintf ("%s: state %s - event %s is not accepted",
agent-> name,
agent-> LR_sname [thread-> LR_state],
agent-> LR_ename [thread-> LR_event]);
return (-1);
}
_the_next_event = SMT_NULL_EVENT;
thread-> LR_vecptr = &agent-> LR_vector
[agent-> LR_offset [thread-> LR_index]];
}
_the_exception_event = SMT_NULL_EVENT;
_exception_raised = FALSE;
_io_completed = TRUE;
_repeat_module = FALSE;
break_wanted = BREAK_CONTINUE;
/* Keep track of current agent/state/event for error tracking */
cur_agent = agent-> name;
cur_state = agent-> LR_sname [thread-> LR_state];
cur_event = agent-> LR_ename [thread-> LR_event];
/* Execute module list as far as possible */
while (*thread-> LR_vecptr != SMT_ACTION_STOP)
{
cur_module = agent-> LR_mname [*(thread-> LR_vecptr)];
cur_step = "";
if (agent-> animate && thread-> animate)
coprintf ("%-10s %4ld> + %s",
agent-> name, thread-> thread_id, cur_module);
/* Execute one module step and check for break condition */
hook = agent-> LR_module [*(thread-> LR_vecptr)];
(hook) (thread); /* Execute the module step */
if (break_wanted != BREAK_RECYCLE)
thread-> LR_vecptr++; /* Bump to next module */
if (_exception_raised || break_wanted)
break; /* Any other break after module */
}
/* -----------------------------------------------------------------
Break conditions (in order):
exception_raised
reset LR_index
handle exception
if in defaults state, reset state
loop to next state
break_wanted == BREAK_WAIT_SEMAPH - after semaph_wait() call
suspend_thread (wait_semaphore), break
relink thread to semaphore thread list
break
shutdown_request
suspend_thread (passive), break
*thread-> LR_vecptr == SMT_ACTION_STOP - normal end of modules
reset LR_index
set next state
if in defaults state, reset state
if no event, suspend_thread (passive), break
if terminate event, kill thread, break
loop to next state
break_wanted == BREAK_RECYCLE - recycle_module() called
break
break_wanted == BREAK_WAIT_EVENT - after event_wait() call
suspend_thread (wait_event), break
------------------------------------------------------------------ */
if (_exception_raised)
{
if (_the_exception_event != SMT_NULL_EVENT)
thread-> LR_event = _the_exception_event;
_the_next_event = thread-> LR_event;
if (agent-> animate && thread-> animate)
coprintf ("%-10s %4ld> (=>) %s",
agent-> name, thread-> thread_id,
agent-> LR_ename [thread-> LR_event]);
/* Restore state if we were in the defaults state */
thread-> LR_index = 0; /* Start new module list */
if (thread-> LR_state == agent-> LR_defaults)
thread-> LR_state = thread-> LR_savest;
}
else
if (break_wanted == BREAK_WAIT_SEMAPH)
{
suspend_thread (thread, SMT_THREAD_WAIT_SEMAPH);
thread_relink_before (thread, &break_semaph-> threads);
}
else
if (shutdown_request)
suspend_thread (thread, SMT_THREAD_PASSIVE);
else
if (*thread-> LR_vecptr == SMT_ACTION_STOP)
{
thread-> LR_state = agent-> LR_nextst
[thread-> LR_state * agent-> maxevent + thread-> LR_event];
/* Restore state if we were in the defaults state */
thread-> LR_index = 0; /* Start new module list */
if (thread-> LR_state == agent-> LR_defaults)
thread-> LR_state = thread-> LR_savest;
/* Thread goes passive if no event was supplied */
if (_the_next_event == SMT_NULL_EVENT)
suspend_thread (thread, SMT_THREAD_PASSIVE);
else
/* Thread gets deleted if it terminated */
if (_the_next_event == SMT_TERM_EVENT)
{
thread_destroy (thread, TRUE);
thread = NULL; /* No longer a valid address */
break; /* End thread processing */
}
}
else
if (break_wanted == BREAK_RECYCLE)
break; /* Leave everything as it was */
else
if (break_wanted == BREAK_WAIT_EVENT)
suspend_thread (thread, SMT_THREAD_WAIT_EVENT);
if (thread == NULL || thread-> state != SMT_THREAD_ACTIVE)
break; /* End thread processing */
}
/* Replace thread copy of the next event */
if (thread) /* Unless thread was killed */
thread-> the_next_event = _the_next_event;
return (0); /* Signal execution went okay */
}
/* -------------------------------------------------------------------------
* suspend_thread -- internal
*
* Moves the thread off the active list, frees any allocated event, and
* sets a new state as specified.
*/
static void
suspend_thread (THREAD *thread, int state)
{
if (thread-> event) /* If thread was sitting on an */
{ /* event, release it */
event_destroy (thread-> event);
thread-> event = NULL;
}
thread-> state = state;
thread_unlink (thread); /* Remove thread from active list */
}
/* ---------------------------------------------------------------------[<]-
Function: smt_active
Synopsis: Returns TRUE if the SMT has active threads, else false.
---------------------------------------------------------------------[>]-*/
Bool
smt_active (void)
{
#if (defined (SMT_TRACE))
trace ("smt_active: %d", (active_threads.right != &active_threads));
#endif
return (active_threads.right != &active_threads);
}
/* ---------------------------------------------------------------------[<]-
Function: smt_set_console
Synopsis: Tells the SMT kernel to send error events to the specified
console queue. There can be just one console queue. If you do not
specify a console queue, error events are discarded.
---------------------------------------------------------------------[>]-*/
void
smt_set_console (const QID *qid)
{
#if (defined (SMT_TRACE))
trace ("smt_set_console");
#endif
console = *qid;
}
/* ---------------------------------------------------------------------[<]-
Function: smt_set_timer
Synopsis: Tells the SMT kernel to send alarm events to the specified
timer queue. There can be just one timer queue. If you do not
specify a timer queue, timer events are discarded.
---------------------------------------------------------------------[>]-*/
void
smt_set_timer (const QID *qid)
{
#if (defined (SMT_TRACE))
trace ("smt_set_timer");
#endif
timer = *qid;
}
/* ---------------------------------------------------------------------[<]-
Function: smt_atexit
Synopsis: Registers a termination function. The function is defined as
a void function without arguments. The termination functions are called
in the order that they are declared. Multiple instances of the same
function are ignored. Returns 0 if okay, -1 if there was an error.
In the case of an error, sets smt_errno to one of:
SMT_NOTREADY smt_init() was not called, or failed
SMT_OUTOFMEMORY Not enough heap memory left
The kernel executes termination functions before destroying agents and
other objects. Thus, termination functions can access the object symbol
table ('lookup' functions), but not send or receive events.
---------------------------------------------------------------------[>]-*/
int
smt_atexit (function handler)
{
EXITFCT *exitfct; /* Agent information block */
#if (defined (SMT_TRACE))
trace ("smt_atexit");
#endif
ASSERT (handler);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (-1);
}
/* Check that exit function is not already defined; if so, ignore it */
FORLIST (exitfct, exitfcts)
if (exitfct-> handler == handler)
return (0);
/* Allocate an EXITFCT block and attach it to the exitfcts list */
exitfct = (EXITFCT *) node_create (exitfcts.prev, sizeof (EXITFCT));
if (exitfct == NULL)
{
smt_errno = SMT_OUTOFMEMORY;
return (-1);
}
exitfct-> handler = handler;
return (0);
}
/* ---------------------------------------------------------------------[<]-
Function: smt_shutdown
Synopsis: Ends the current SMT application. Use this if you detect a
fatal error in a thread. Sends a SHUTDOWN event to every thread, so
halting the application.
---------------------------------------------------------------------[>]-*/
void
smt_shutdown (void)
{
signal_raised = TRUE; /* Signal user shutdown signal */
signal_value = SMT_SIGNAL_USER; /* Kernel takes it from here */
}
/* ---------------------------------------------------------------------[<]-
Function: agent_declare
Synopsis: Declares a new agent. Typically you'll do this when you are
initialising a agent. You must declare the agent before you can
create queues, threads, or methods for that agent.
The agent name is an arbitrary text, unique within the application.
Returns the address of the created AGENT block. If there was an error,
returns NULL and sets smt_errno to one of these values:
SMT_NOTREADY smt_init() was not called, or failed
SMT_OUTOFMEMORY Not enough heap memory left
SMT_AGENTEXISTS A agent with this name is already declared
Agents and threads are stored in the dictionary as follows:
a name is built of three parts: s~agent~[thread]. This name
is the key into the dictionary and lets us find a agent, or
thread by name. Then, all agents are chained into a linked list that
is attached to the agents list. Each agent has a sublist of queues,
and each queue has a sublist of threads. Each thread
has a pointer to the parent queue respectively. This
cross-linking lets us browse the list of agents/threads from
any point. Names are always stored in lower-case.
Sets agent priority to SMT_PRIORITY_NORMAL; sets router flag to FALSE,
and max_threads to 0.
---------------------------------------------------------------------[>]-*/
AGENT *
agent_declare (
const char *agent_name /* Name of agent to declare */
)
{
SYMBOL *dict_entry; /* Dictionary symbol */
AGENT *agent; /* Agent information block */
char *full_name; /* Full agent name */
#if (defined (SMT_TRACE))
trace ("agent_declare: agent=%s", agent_name);
#endif
ASSERT (agent_name);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (NULL);
}
/* Check that agent is not already declared */
full_name = get_entity_name (agent_name, NULL);
if (sym_lookup_symbol (dict, full_name))
{
smt_errno = SMT_AGENTEXISTS;
return (NULL);
}
/* Now create entry for the agent */
dict_entry = sym_create_symbol (dict, full_name, NULL);
if (dict_entry == NULL)
{
smt_errno = SMT_OUTOFMEMORY;
return (NULL);
}
/* Allocate an AGENT block and attach it to the agent list */
agent = (AGENT *) node_create (agents.prev, sizeof (AGENT));
if (agent == NULL)
{
sym_delete_symbol (dict, dict_entry);
smt_errno = SMT_OUTOFMEMORY;
return (NULL);
}
/* Point the dictionary entry to the agent information block */
dict_entry-> data = agent;
/* Now initialise the agent - all fields are already set to zero */
node_reset (&agent-> methods);
node_reset (&agent-> queues);
agent-> symbol = dict_entry;
agent-> name = mem_strdup (agent_name);
/* These fields must be set by the calling program */
agent-> tcb_size = 0; /* Size of thread context block */
agent-> maxevent = 0; /* Number of events defined */
agent-> maxmodule = 0; /* Number of modules defined */
agent-> maxstate = 0; /* Number of states defined */
agent-> initialise = NULL; /* Initialise-the-thread */
agent-> LR_nextst = NULL; /* Next state table */
agent-> LR_action = NULL; /* Action table */
agent-> LR_offset = NULL; /* Vector offset table */
agent-> LR_vector = NULL; /* Vector table */
agent-> LR_module = NULL; /* Module table */
agent-> LR_defaults = 0; /* Defaults state */
/* These fields may be changed by the calling program */
agent-> stack_size = 0; /* Subdialog stack size (if reqd) */
agent-> LR_mname = NULL; /* Module name table (if animated) */
agent-> LR_sname = NULL; /* State name table (if animated) */
agent-> LR_ename = NULL; /* Event name table (if animated) */
agent-> priority = SMT_PRIORITY_NORMAL;
agent-> router = FALSE; /* Agent acts as a router */
agent-> animate = FALSE; /* Agent animation enabled */
agent-> max_threads = 0; /* Max. number of threads */
return (agent);
}
/* -------------------------------------------------------------------------
* get_entity_name -- internal
*
* Returns a formatted agent/thread name given the two parts
* of the name. This is used for lookups into the dictionary. The
* returned name is in a static area that is overwritten by each call.
* The name is always converted to lower-case.
*/
static char
*get_entity_name (const char *agent_name, const char *thread_name)
{
static
char full_name [LINE_MAX + 1];
if (thread_name) /* Thread name specified */
{
ASSERT ((strlen (agent_name) + strlen (thread_name) + 3) <= LINE_MAX);
sprintf (full_name, "s~%s~%s", agent_name, thread_name);
}
else /* Just the agent name */
{
ASSERT ((strlen (agent_name) + 2) <= LINE_MAX);
sprintf (full_name, "s~%s", agent_name);
}
strlwc (full_name);
return (full_name);
}
/* ---------------------------------------------------------------------[<]-
Function: agent_lookup
Synopsis: Checks whether a specific agent exists; returns the address
of the agent information block, or NULL if there was an error, setting
smt_errno to one of these values:
SMT_NOTREADY smt_init() was not called, or failed
SMT_NOSUCHAGENT Specified agent was not declared
---------------------------------------------------------------------[>]-*/
AGENT *
agent_lookup (
const char *agent_name /* Name of agent to look for */
)
{
SYMBOL *dict_entry; /* Dictionary symbol */
#if (defined (SMT_TRACE))
trace ("agent_lookup: agent=%s", agent_name);
#endif
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (NULL);
}
dict_entry = sym_lookup_symbol (dict, get_entity_name (agent_name, NULL));
if (dict_entry == NULL)
{
smt_errno = SMT_NOSUCHAGENT;
return (NULL);
}
return (dict_entry-> data); /* Return pointer to AGENT */
}
/* ---------------------------------------------------------------------[<]-
Function: agent_destroy
Synopsis: Destroys the agent. Returns 0 when completed. The agent
argument points to an agent block, or is null. If null, all agents
are destroyed. Returns 0 when completed normally, else returns -1
and sets smt_errno to one of these values:
SMT_NOTREADY smt_init() was not called, or failed
---------------------------------------------------------------------[>]-*/
int
agent_destroy (
AGENT *agent /* Agent to destroy; null = all */
)
{
#if (defined (SMT_TRACE))
trace ("agent_destroy: agent=%s", agent? agent-> name: "ALL");
#endif
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (-1);
}
if (agent == NULL) /* Destroy all agents if wanted */
while (agents.next != &agents)
agent_destroy (agents.next);
else /* Else destroy this agent */
{
/* Destroy all queues and methods declared for the agent */
while (agent-> queues.next != &agent-> queues)
queue_destroy (agent-> queues.next);
while (agent-> methods.next != &agent-> methods)
method_destroy (agent-> methods.next);
/* We have to be a little careful or sym_delete_symbol () will */
/* try to release the symbol's data area; the data area points */
/* to our node, which we want to release ourselves. */
agent-> symbol-> data = NULL;
sym_delete_symbol (dict, agent-> symbol);
/* Now delete the agent */
mem_strfree (&agent-> name); /* First we take its name */
node_destroy (agent); /* ... then we take its life */
}
return (0);
}
/* ---------------------------------------------------------------------[<]-
Function: method_declare
Synopsis: Declares a new method for an agent. All external events that
an agent is prepared to method are declared as methods. The agent must
already have been declared using agent_declare(). The method name is an
arbitrary text, unique within the agent. The event number is the number
of the event assigned by the dialog code generator; if you specify the
event number as SMT_NULL_EVENT, the method is ignored. This discards
any incoming events with that name. The priority may be 0 (meaning
normal priority), SMT_PRIORITY_LOW, SMT_PRIORITY_HIGH, or another
suitable value. Returns the address of the created METHOD block.
If there was an error, returns NULL and sets smt_errno to one of:
SMT_NOTREADY smt_init() was not called, or failed
SMT_OUTOFMEMORY Not enough heap memory left
SMT_NOSUCHAGENT Specified agent was not declared
SMT_METHODEXISTS Method is already declared
---------------------------------------------------------------------[>]-*/
METHOD *
method_declare (
AGENT *agent, /* Create method in this agent */
const char *method_name, /* Name of method to declare */
event_t event_number, /* Method number from dialog */
int priority /* Priority for the method, or 0 */
)
{
SYMBOL *dict_entry; /* Dictionary symbol */
METHOD *method; /* Method information block */
char *full_name; /* Full method name */
#if (defined (SMT_TRACE))
trace ("method_declare: agent=%s method=%s", agent-> name, method_name);
#endif
ASSERT (agent);
ASSERT (method_name);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (NULL);
}
/* Check that method is not already declared */
full_name = get_method_name (agent-> name, method_name);
if (sym_lookup_symbol (dict, full_name))
{
smt_errno = SMT_METHODEXISTS;
return (NULL);
}
/* Now create entry for the method */
dict_entry = sym_create_symbol (dict, full_name, NULL);
if (dict_entry == NULL)
{
smt_errno = SMT_OUTOFMEMORY;
return (NULL);
}
/* Allocate a METHOD block and attach it to the method list */
method = (METHOD *) node_create (&agent-> methods, sizeof (METHOD));
if (method == NULL)
{
sym_delete_symbol (dict, dict_entry);
smt_errno = SMT_OUTOFMEMORY;
return (NULL);
}
/* Point the dictionary entry to the method information block */
dict_entry-> data = method;
/* Now initialise the method - all fields are already set to zero */
method-> symbol = dict_entry;
method-> agent = agent;
method-> name = mem_strdup (method_name);
method-> priority = priority? priority: SMT_PRIORITY_NORMAL;
method-> event_number = event_number;
return (method);
}
/* -------------------------------------------------------------------------
* get_method_name -- internal
*
* Returns a formatted method name for the specified agent method.
* The method name is used for lookups into the dictionary.
* The name is always converted to lower-case.
*/
static char *
get_method_name (const char *agent_name, const char *method_name)
{
static
char full_name [LINE_MAX + 1];
ASSERT ((strlen (agent_name) + strlen (method_name) + 3) <= LINE_MAX);
sprintf (full_name, "m~%s~%s", agent_name, method_name);
strlwc (full_name);
return (full_name);
}
/* ---------------------------------------------------------------------[<]-
Function: method_lookup
Synopsis: Checks whether a specific method exists; returns the address
of the method information block, or NULL if there was an error, setting
smt_errno to one of these values:
SMT_NOTREADY smt_init() was not called, or failed
SMT_NOSUCHMETHOD Specified method was not declared
---------------------------------------------------------------------[>]-*/
METHOD *
method_lookup (
const AGENT *agent, /* Agent to look at */
const char *method_name /* Name of method to look for */
)
{
SYMBOL *dict_entry; /* Dictionary symbol */
char *full_name; /* Full agent/method name */
#if (defined (SMT_TRACE))
trace ("method_lookup: agent=%s", agent-> name, method_name);
#endif
ASSERT (agent);
ASSERT (method_name);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (NULL);
}
full_name = get_method_name (agent-> name, method_name);
dict_entry = sym_lookup_symbol (dict, full_name);
if (dict_entry == NULL)
{
smt_errno = SMT_NOSUCHMETHOD;
return (NULL);
}
return (dict_entry-> data); /* Return pointer to METHOD */
}
/* ---------------------------------------------------------------------[<]-
Function: method_destroy
Synopsis: Destroys the method. Returns 0 when completed.
In case of error, returns -1 and sets smt_errno to one of these values:
SMT_NOTREADY smt_init() was not called, or failed
---------------------------------------------------------------------[>]-*/
int
method_destroy (
METHOD *method /* Method to destroy */
)
{
#if (defined (SMT_TRACE))
trace ("method_destroy: method=%s", method-> name);
#endif
ASSERT (method);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (-1);
}
/* We have to be a little careful or sym_delete_symbol () will */
/* try to release the symbol's data area; the data area points */
/* to our node, which we want to release ourselves. */
method-> symbol-> data = NULL;
sym_delete_symbol (dict, method-> symbol);
/* Delete the method */
mem_strfree (&method-> name);
node_destroy (method);
return (0);
}
/* ---------------------------------------------------------------------[<]-
Function: queue_create
Synopsis: Creates an event queue, and returns a handle to the created
queue. Event queues are unnamed but attached to a agent within an
agent. Queue can also be 'floating', i.e. not attached to a agent.
This is useful for foreign programs. If you specify a agent, the
queue is attached to that agent. If the agent argument is null,
the queue is left floating. You always refer to a queue using its
address (within the owning process) or QID handle (within any process).
The current implementation uses a linked list in heap memory, so QID
handles are only valid within the process. Future implementations may
use other types of shared memory including connections across a
communications protocol. Returns a pointer to the created QUEUE block.
In case of error, returns null and sets smt_errno to one of these values:
SMT_NOTREADY smt_init() was not called, or failed
SMT_OUTOFMEMORY Not enough heap memory left
---------------------------------------------------------------------[>]-*/
QUEUE *
queue_create (
AGENT *agent, /* Parent agent block, or null */
int max_events /* Max. events; 0 = no limit */
)
{
static
long top_id = 0; /* We number queues from 1 up */
QID qid; /* Created queue */
SYMBOL *dict_entry; /* Dictionary symbol */
QUEUE *queue; /* Queue information block */
#if (defined (SMT_TRACE))
trace ("queue_create: agent=%s", agent? agent-> name: "");
#endif
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (NULL);
}
qid.node = 0; /* Queues are local for now */
qid.ident = ++top_id; /* First queue has id = 1 */
dict_entry = sym_create_symbol (dict, get_queue_name (&qid), NULL);
if (dict_entry == NULL)
{
smt_errno = SMT_OUTOFMEMORY;
return (NULL);
}
/* Allocate a QUEUE block and attach it to the queue list */
queue = (QUEUE *) node_create (agent? &agent-> queues: NULL,
sizeof (QUEUE));
if (queue == NULL)
{
sym_delete_symbol (dict, dict_entry);
smt_errno = SMT_OUTOFMEMORY;
return (NULL);
}
/* Point the dictionary entry to the queue information block */
dict_entry-> data = queue;
/* Now initialise the queue info block fields and list heads */
node_reset (&queue-> events);
node_reset (&queue-> threads);
queue-> symbol = dict_entry;
queue-> agent = agent;
queue-> qid = qid;
queue-> max_events = max_events;
queue-> shutdown = FALSE;
return (queue);
}
/* -------------------------------------------------------------------------
* get_queue_name -- internal
*
* Returns a formatted queue name for the specified queue handle. The queue
* name is used for lookups into the dictionary.
*/
static char *
get_queue_name (const QID *qid)
{
static
char queue_name [15]; /* Queue number is 32-bit value */
sprintf (queue_name, "q~%09ld", qid-> ident);
return (queue_name);
}
/* ---------------------------------------------------------------------[<]-
Function: queue_lookup
Synopsis: Returns a pointer to the queue information block for the
specified queue handle. You can change the max_events field, but you
should not change the other fields in the queue information block.
In case of error, returns NULL and sets smt_errno to one of these values:
SMT_NOTREADY smt_init() was not called, or failed
SMT_NOSUCHQUEUE The event queue is not defined
---------------------------------------------------------------------[>]-*/
QUEUE *
queue_lookup (
const QID *qid /* Queue to find */
)
{
SYMBOL *dict_entry; /* Queue entry in dictionary */
#if (defined (SMT_TRACE))
trace ("queue_lookup");
#endif
ASSERT (qid);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (NULL);
}
dict_entry = sym_lookup_symbol (dict, get_queue_name (qid));
if (dict_entry)
return (dict_entry-> data);
else
{
smt_errno = SMT_NOSUCHQUEUE;
return (NULL);
}
}
/* ---------------------------------------------------------------------[<]-
Function: queue_destroy
Synopsis: Deletes any events in the event queue and then destroys the
queue and all its threads. Returns 0 when successfully completed.
In case of error, returns -1 and sets smt_errno to one of these values:
SMT_NOTREADY smt_init() was not called, or failed
SMT_NOSUCHQUEUE The event queue is not defined
---------------------------------------------------------------------[>]-*/
int
queue_destroy (
QUEUE *queue /* Queue to destroy */
)
{
#if (defined (SMT_TRACE))
trace ("queue_destroy");
#endif
ASSERT (queue);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (-1);
}
/* Delete all events in the queue */
while (event_discard (queue, NULL) == 0);
/* Destroy all threads defined for the queue */
while (queue-> threads.next != &queue-> threads)
thread_destroy (queue-> threads.next, FALSE);
/* We have to be a little careful or sym_delete_symbol () will */
/* try to release the symbol's data area; the data area points */
/* to our node, which we want to release ourselves. */
queue-> symbol-> data = NULL;
sym_delete_symbol (dict, queue-> symbol);
/* Now delete the queue itself */
node_destroy (queue);
return (0);
}
/* ---------------------------------------------------------------------[<]-
Function: queue_deliver
Synopsis: Tries to deliver an event from a queue to an agent thread.
The event, if deliverable, is removed from the queue and stored in the
thread-> event block. Returns 1 if an event was delivered, else 0.
---------------------------------------------------------------------[>]-*/
int
queue_deliver (
QUEUE *queue, /* Queue containing events */
THREAD *thread) /* Agent thread to receive event */
{
AGENT *agent; /* Agent to receive event */
EVENT *event; /* Event information block */
EVENT *deliver_event; /* Event to deliver */
METHOD *method; /* Method information block */
int top_priority; /* Highest event priority so far */
#if (defined (SMT_TRACE))
trace ("queue_deliver: thread=%s in %s", thread-> name,
thread-> queue-> agent-> name);
#endif
/* Get event to deliver - find event with highest method priority */
top_priority = -1;
deliver_event = NULL;
agent = thread-> queue-> agent;
event = queue-> events.next;
while ((NODE *) event != &queue-> events)
{
if (event-> priority == SMT_PRIORITY_NULL)
{
/* Lookup method; if method is not declared, reject event */
method = method_lookup (agent, event-> name);
if (method == NULL)
{
/* Reject this event, but keep our place in the queue... */
sendfmt (&console, "ERROR", "Event %s not declared by %s",
event-> name, agent-> name);
event = event-> next;
event_reject (queue, event-> prev);
continue;
}
else
/* If null method, accept event but discard it */
if (method-> event_number == SMT_NULL_EVENT)
{
event = event-> next;
node_destroy (event_accept (queue, event-> prev));
continue;
}
/* Update the event values, to save a lookup next time */
event-> priority = method-> priority;
event-> event_number = method-> event_number;
}
if (event-> priority > top_priority)
{
top_priority = event-> priority;
deliver_event = event;
}
event = event-> next;
}
if (deliver_event)
{
/* Deliver event to thread */
if (thread-> event)
event_destroy (thread-> event);
thread-> the_next_event = deliver_event-> event_number;
thread-> event = event_accept (queue, deliver_event);
return (1); /* We delivered the event */
}
else
return (0); /* We did not find an event */
}
/* ---------------------------------------------------------------------[<]-
Function: queue_flush
Synopsis: Expires any out-of-date events in the queue: calls
event_expire() for each event who's timeout has passed. Returns the
number of events expired.
In case of error, returns -1 and sets smt_errno to one of these values:
SMT_NOTREADY smt_init() was not called, or failed
SMT_NOSUCHQUEUE The event queue is not defined
---------------------------------------------------------------------[>]-*/
int
queue_flush (
QUEUE *queue /* Queue to flush */
)
{
time_t time_now; /* Current time */
int feedback = 0; /* Number of events we delivered */
EVENT *event; /* Event information block */
#if (defined (SMT_TRACE))
trace ("queue_flush");
#endif
ASSERT (queue);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (-1);
}
time_now = time (NULL);
FORLIST (event, queue-> events)
if (event-> timeout && event-> timeout < time_now)
{
event_expire (queue, event);
feedback++;
}
return (feedback);
}
/* ---------------------------------------------------------------------[<]-
Function: event_send
Synopsis: Sends an event to an event queue. The event body - if not
null or empty - is always copied, crossing memory boundaries. The
accept_event, reject_event, and timeout_events are sent back to the
sender event queue as required. These events may be specified as null
or empty strings. The timeout may be 0 for none, or a value in
milliseconds. Returns 0 when successfully completed. The current
implementation provides timeouts accurate to a second only. The
target queue may be null, in which case the event is ignored, and
not sent. This lets you reply to an event without always checking
that the reply queue was specified. The event name and reply event
names are always stored in uppercase.
In case of error, returns -1 and sets smt_errno to one of these values:
SMT_NOTREADY smt_init() was not called, or failed
SMT_OUTOFMEMORY Not enough heap memory left
SMT_NOSUCHQUEUE The target event queue is not known
SMT_QUEUEISFULL The target event queue is full
---------------------------------------------------------------------[>]-*/
int
event_send (
const QID *to_queue, /* Queue to receive event, or NULL */
const QID *from_queue, /* Queue to receive reply, or NULL */
char *name, /* Name of event to send */
byte *body, /* Body of message or NULL */
size_t body_size, /* Size of body >= 0 */
char *accept_event, /* Accept event or NULL */
char *reject_event, /* Reject event or NULL */
char *expire_event, /* Expire event or NULL */
word timeout /* Timeout in seconds: 0 = none */
)
{
static QID
null_queue = {0, 0}; /* Indicates a null sender queue */
QUEUE *queue; /* Queue where we will place event */
EVENT *event; /* Allocated event block */
size_t size; /* Total size of event block */
char *string; /* For storing event strings */
#if (defined (SMT_TRACE))
trace ("event_send: event=%s", name);
#endif
ASSERT (name && name [0]);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (-1);
}
/* If the called did not specify a target queue, ignore */
if (to_queue == NULL || to_queue-> ident == 0)
return (0);
if (from_queue == NULL) /* If the caller did not specify */
{ /* a reply queue, then we can */
accept_event = /* ignore the reply events in */
reject_event = /* any case. */
expire_event = NULL;
from_queue = &null_queue;
}
if ((queue = queue_lookup (to_queue)) == NULL)
{
smt_errno = SMT_NOSUCHQUEUE;
return (-1);
}
/* Check that we're allowed to create a new event */
if (queue-> max_events > 0
&& queue-> max_events == queue-> cur_events)
{
smt_errno = SMT_QUEUEISFULL;
return (-1);
}
/* We allocate the event, body, and return events as a single */
/* block, to reduce access to the heap and make cleaning-up easier. */
size = sizeof (EVENT) + body_size + strlen (name) + 1;
if (accept_event)
size += strlen (accept_event) + 1;
if (reject_event)
size += strlen (reject_event) + 1;
if (expire_event)
size += strlen (expire_event) + 1;
/* Allocate an EVENT block and attach it to the event list */
event = (EVENT *) node_create (queue-> events.prev, size);
if (event == NULL)
{
smt_errno = SMT_OUTOFMEMORY;
return (-1);
}
event-> priority = SMT_PRIORITY_NULL;
event-> size = size; /* Event is self-contained */
event-> queue = queue; /* Set parent queue address */
event-> sender = *from_queue; /* and sender queue */
event-> body_size = body_size; /* Store body size */
event-> timeout = timeout? time (NULL) + timeout: 0;
/* Store variable-length parts after main event structure */
string = (char *) event + sizeof (EVENT);
event-> name = string;
strcpy (string, name);
strupc (string);
string += strlen (string) + 1;
if (body_size > 0)
{
/* Store event body */
event-> body = (byte *) string;
memcpy (string, body, body_size);
string += body_size;
}
else
event-> body = NULL;
if (accept_event)
{
event-> accept_event = string;
strcpy (string, accept_event);
strupc (string);
string += strlen (string) + 1;
}
else
event-> accept_event = NULL;
if (reject_event)
{
event-> reject_event = string;
strcpy (string, reject_event);
strupc (string);
string += strlen (string) + 1;
}
else
event-> reject_event = NULL;
if (expire_event)
{
event-> expire_event = string;
strcpy (string, expire_event);
strupc (string);
string += strlen (string) + 1;
}
else
event-> expire_event = NULL;
if (timeout)
queue-> timed_events++; /* Count event if timed */
queue-> cur_events++; /* Count the event */
return (0); /* No errors */
}
/* ---------------------------------------------------------------------[<]-
Function: event_accept
Synopsis: Takes an event off an event queue, and sends an 'accept'
reply to the original sender, if required. If the specified event is
null, takes the first (oldest) event in the queue. Otherwise takes the
specified event. Returns the address of the event. You should call
event_destroy() when you have finished processing the event. In case
of error, returns NULL and sets smt_errno to one of these values:
SMT_NOTREADY smt_init() was not called, or failed
SMT_QUEUEISEMPTY The queue was empty
---------------------------------------------------------------------[>]-*/
EVENT *
event_accept (
QUEUE *queue, /* Queue to take event from */
EVENT *event /* Event, or null for first */
)
{
#if (defined (SMT_TRACE))
trace ("event_accept");
#endif
ASSERT (queue);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (NULL);
}
if ((event = event_locate (queue, event)) == NULL)
return (NULL);
/* Reply to original sender if necessary */
if (event-> accept_event)
event_send (
&event-> sender, /* Send back to original sender */
NULL, /* No queue for reply */
event-> accept_event, /* Name of event to send */
NULL, 0, /* Body is empty, size is 0 */
NULL, NULL, NULL, /* No response events */
0); /* No timeout */
if (event-> timeout)
queue-> timed_events++; /* One less timed event, maybe */
queue-> cur_events--; /* One less event in queue */
node_unlink (event); /* Unlink from queue */
return (event);
}
/* -------------------------------------------------------------------------
* event_locate -- internal
*
* Finds the specified or first event in the event queue. If the queue
* is empty, sets smt_errno to SMT_QUEUEISEMPTY and returns NULL. Else
* returns the address of the event wanted.
*/
static EVENT *
event_locate (QUEUE *queue, EVENT *event)
{
if (event == NULL) /* If no event was specified, */
event = queue-> events.next; /* get first event in queue */
if (event == (EVENT *) &queue-> events)
{
smt_errno = SMT_QUEUEISEMPTY;
return (NULL);
}
return (event);
}
/* ---------------------------------------------------------------------[<]-
Function: event_reject
Synopsis: Rejects the next event or a specific event on an event queue.
Sends a 'rejected' event to the original sender if required, then
destroys the event. You can use this to reject one specific message,
or in a loop to cancel the entire queue. Returns 0 if the event was
successfully rejected, else returns -1 and sets smt_errno to one of:
SMT_NOTREADY smt_init() was not called, or failed
SMT_QUEUEISEMPTY The queue was empty
---------------------------------------------------------------------[>]-*/
int
event_reject (
QUEUE *queue, /* Queue to take event from */
EVENT *event /* Event, or null for first */
)
{
#if (defined (SMT_TRACE))
trace ("event_reject");
#endif
ASSERT (queue);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (-1);
}
if ((event = event_locate (queue, event)) == NULL)
return (-1);
/* Reply to original sender if necessary */
if (event-> reject_event)
event_send (
&event-> sender, /* Send back to original sender */
NULL, /* No queue for reply */
event-> reject_event, /* Name of event to send */
NULL, 0, /* Body is empty, size is 0 */
NULL, NULL, NULL, /* No response events */
0); /* No timeout */
/* Unlink and destroy event */
return (event_discard (queue, event));
}
/* ---------------------------------------------------------------------[<]-
Function: event_expire
Synopsis: Expires the next event or a specific event on an event queue.
Sends a 'expired' event to the original sender if required, then
destroys the event. You can use this to expire one specific message,
or in a loop to cancel the entire queue. Returns 0 if the event was
successfully expired, else returns -1 and sets smt_errno to one of:
SMT_NOTREADY smt_init() was not called, or failed
SMT_QUEUEISEMPTY The queue was empty
---------------------------------------------------------------------[>]-*/
int
event_expire (
QUEUE *queue, /* Queue to take event from */
EVENT *event /* Event, or null for first */
)
{
#if (defined (SMT_TRACE))
trace ("event_expire");
#endif
ASSERT (queue);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (-1);
}
if ((event = event_locate (queue, event)) == NULL)
return (-1);
/* Reply to original sender if necessary */
if (event-> expire_event)
event_send (
&event-> sender, /* Send back to original sender */
NULL, /* No queue for reply */
event-> expire_event, /* Name of event to send */
NULL, 0, /* Body is empty, size is 0 */
NULL, NULL, NULL, /* No response events */
0); /* No timeout */
/* Unlink and destroy event */
return (event_discard (queue, event));
}
/* ---------------------------------------------------------------------[<]-
Function: event_discard
Synopsis: Discards the specified event in the specified queue. The
event_iterate (), event_accept () and event_discard () calls let a
thread manipulate its event queue directly. In such cases the thread
takes responsibility for event delivery and acknowledgement. Returns 0
when successfully completed.
In case of error, returns -1 and sets smt_errno to one of these values:
SMT_NOTREADY smt_init() was not called, or failed
SMT_QUEUEISEMPTY The queue was empty
---------------------------------------------------------------------[>]-*/
int
event_discard (
QUEUE *queue, /* Queue to take event from */
EVENT *event /* Event, or null */
)
{
#if (defined (SMT_TRACE))
trace ("event_discard");
#endif
ASSERT (queue);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (-1);
}
if ((event = event_locate (queue, event)) == NULL)
return (-1);
queue-> cur_events--; /* Count the event */
event_destroy (event); /* Unlink and destroy event */
return (0);
}
/* ---------------------------------------------------------------------[<]-
Function: event_iterate
Synopsis: Returns the first or next event in the queue. If the 'after'
argument is null, returns the first event, else returns the next event.
You should not 'walk' the event queue directly, since the implementation
may change arbitrarily. Returns a pointer to the next event, or null if
no (further) events were found. May set smt_errno to one of:
SMT_NOTREADY smt_init() was not called, or failed
SMT_QUEUEISEMPTY The queue was empty
---------------------------------------------------------------------[>]-*/
EVENT *
event_iterate (
QUEUE *queue, /* Queue to search */
EVENT *event /* Event, or null for first */
)
{
#if (defined (SMT_TRACE))
trace ("event_iterate");
#endif
ASSERT (queue);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (NULL);
}
/* If no event specified, get first event in queue */
if (event == NULL)
event = (EVENT *) &queue-> events;
event = event-> next; /* Get next event in queue */
if (event == (EVENT *) &queue-> events)
{
smt_errno = SMT_QUEUEISEMPTY;
return (NULL);
}
return (event);
}
/* ---------------------------------------------------------------------[<]-
Function: event_destroy
Synopsis: Destroys the specified event, which is assumed not to be
linked into any queue. Typically you'll call this after processing an
event you received with event_accept(). Returns 0 if okay.
---------------------------------------------------------------------[>]-*/
int
event_destroy (
EVENT *event /* Event block to destroy */
)
{
#if (defined (SMT_TRACE))
trace ("event_destroy: event=%s", event-> name);
#endif
node_destroy (event); /* Unlink and destroy event */
return (0);
}
/* ---------------------------------------------------------------------[<]-
Function: event_wait
Synopsis: Suspends processing of an action module list, and waits for an
incoming event. The event is received in 'the_external_event' by the
next dialog module. When called in the last module in a list, has no
effect. This call has no effect if you raise an exception or supply a
value in the_next_event.
---------------------------------------------------------------------[>]-*/
void
event_wait (void)
{
#if (defined (SMT_TRACE))
trace ("event_wait");
#endif
break_wanted = BREAK_WAIT_EVENT;
}
/* ---------------------------------------------------------------------[<]-
Function: thread_create
Synopsis: Creates a new thread, and possibly an event queue for the
thread. The caller specifies the agent and thread name. The
agent must already be declared using agent_declare(). If the agent
was defined as a router, you can create multiple threads with the same
name. These threads then share the same event queue on an anonymous
basis. If the agent was not defined as a router, it is illegal to
create multiple threads with the same name unless the name is empty
(meaning "" or NULL).
The function automatically creates an event queue for the thread when
required. Returns a pointer to the created THREAD block, or null if
there was an error. In that case, sets smt_errno to one of:
SMT_NOTREADY smt_init() was not called, or failed
SMT_OUTOFMEMORY Not enough heap memory left
SMT_NOSUCHAGENT The agent was not declared
SMT_AGENTNOTREADY The agent is not initialised
SMT_TOOMANYTHREADS Tried to exceed the maximum permitted threads
SMT_THREADEXISTS The thread already exists
Attaches the thread to the active thread list.
---------------------------------------------------------------------[>]-*/
THREAD *
thread_create (
const char *agent_name, /* Name of agent */
const char *thread_name /* Create thread with this name */
)
{
SYMBOL *dict_entry; /* Dictionary symbol */
AGENT *agent; /* Agent information block */
QUEUE *queue; /* Queue information block */
THREAD *thread; /* Created thread block */
char *full_name; /* Full thread name */
#if (defined (SMT_TRACE))
trace ("thread_create: agent=%s thread=%s", agent_name, thread_name);
#endif
ASSERT (agent_name);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (NULL);
}
/* Find agent, quit if there was an error */
if ((agent = agent_lookup (agent_name)) == NULL)
return (NULL);
/* Check that we're allowed to create a new thread */
if (agent-> max_threads > 0
&& agent-> max_threads == agent-> cur_threads)
{
smt_errno = SMT_TOOMANYTHREADS;
return (NULL);
}
/* Simple check that agent has been initialised */
if (agent-> initialise == NULL)
{
smt_errno = SMT_AGENTNOTREADY;
return (NULL);
}
/* Treat a NULL thread name as an empty string */
if (thread_name == NULL)
thread_name = "";
/* Check if the thread already exists */
full_name = get_entity_name (agent_name, thread_name);
dict_entry = sym_lookup_symbol (dict, full_name);
queue = NULL; /* No queue created yet */
if (dict_entry)
{
/* If it's a router, we'll use the same queue */
if (agent-> router)
queue = ((THREAD *) (dict_entry-> data))-> queue;
else
if (thread_name [0]) /* Otherwise it's an error if the */
{ /* thread was given a name */
smt_errno = SMT_THREADEXISTS;
return (NULL);
}
}
if (!queue) /* Create new queue in agent */
queue = queue_create (agent, 0);
/* Now create entry for the thread */
dict_entry = sym_create_symbol (dict, full_name, NULL);
if (dict_entry == NULL)
{
if (queue) /* Clean-up nicely */
queue_destroy (queue);
smt_errno = SMT_OUTOFMEMORY;
return (NULL);
}
/* Allocate a THREAD block and attach it to the queue's thread list */
/* We also allocate the TCB and subdialog stack if that is required */
thread = (THREAD *) node_create (&queue-> threads, sizeof (THREAD));
if (thread)
{
thread-> tcb = agent-> tcb_size > 0?
mem_alloc (agent-> tcb_size):
NULL;
thread-> LR_stack = agent-> stack_size > 0?
mem_alloc (agent-> stack_size * sizeof (event_t)):
NULL;
}
if (thread == NULL)
{
sym_delete_symbol (dict, dict_entry);
if (queue) /* Clean-up nicely */
queue_destroy (queue);
smt_errno = SMT_OUTOFMEMORY;
return (NULL);
}
/* Point the dictionary entry to the thread information block */
dict_entry-> data = thread;
/* Now initialise the thread fields and list heads */
thread-> symbol = dict_entry;
thread-> name = mem_strdup (thread_name);
thread-> queue = queue;
thread-> thread_id = agent-> thread_tally++;
thread-> animate = agent-> animate;
thread-> left = thread;
thread-> right = thread;
thread-> event = NULL; /* Last event for thread */
cur_threads++; /* Keep count of threads */
agent-> cur_threads++;
if (agent-> top_threads < agent-> cur_threads)
agent-> top_threads = agent-> cur_threads;
activate_thread (thread); /* Move thread to active list */
thread-> state = SMT_THREAD_ACTIVE;
thread-> LR_state = SMT_NULL_STATE;
return (thread);
}
/* -------------------------------------------------------------------------
* thread_relink -- internal
*
* General-purpose function to attach and remove threads from the active
* list. Sets the global variable 'node_unsafe' while the list is being
* changed. Use via the macros thread_unlink, thread_link_after, and
* thread_link_before. Basically unlinks a node if it is linked, or links
* a node if it is unlinked.
*/
static THREAD *
thread_relink (THREAD *left, THREAD *thread, THREAD *right)
{
THREAD *swap;
node_unsafe = TRUE;
swap = left-> right; /* Exchange left pointers */
left-> right = thread-> right;
thread-> right = swap;
swap = right-> left; /* Exchange right pointers */
right-> left = thread-> left;
thread-> left = swap;
node_unsafe = FALSE;
return (thread);
}
/* ---------------------------------------------------------------------[<]-
Function: thread_lookup
Synopsis: Checks whether a specific thread exists; returns the address
of the thread information block, or NULL if there was an error, setting
smt_errno to one of these values:
SMT_NOTREADY smt_init() was not called, or failed
SMT_NOSUCHTHREAD The agent/thread does not exist
If there are multiple threads (routers) with the same name, returns the
earliest thread that was defined.
---------------------------------------------------------------------[>]-*/
THREAD *
thread_lookup (
const char *agent_name, /* Name of agent */
const char *thread_name /* Create thread with this name */
)
{
SYMBOL *dict_entry; /* Dictionary symbol */
char *full_name; /* Full thread name */
#if (defined (SMT_TRACE))
trace ("thread_lookup: agent=%s thread=%s", agent_name, thread_name);
#endif
ASSERT (agent_name);
ASSERT (thread_name);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (NULL);
}
/* Check if the thread already exists */
full_name = get_entity_name (agent_name, thread_name);
dict_entry = sym_lookup_symbol (dict, full_name);
if (dict_entry == NULL)
{
smt_errno = SMT_NOSUCHTHREAD;
return (NULL);
}
/* Get address of thread block, then find first thread defined for */
/* this queue. Usually it will be the same thread; when there are */
/* multiple threads (routers) it may be a different thread. */
return (((THREAD *) (dict_entry-> data))-> queue-> threads.next);
}
/* ---------------------------------------------------------------------[<]-
Function: thread_destroy
Synopsis: Destroys the thread. If this was the last instance of a
router thread, destroys the parent queue as well, if the cleanup
argument is TRUE. Returns 0 if successfully completed, else returns
-1 and sets smt_errno to one of these values:
SMT_NOTREADY smt_init() was not called, or failed
Removes the thread from the active list if it was attached there.
Destroys any event allocated for the thread.
---------------------------------------------------------------------[>]-*/
int
thread_destroy (
THREAD *thread, /* Thread to destroy */
Bool cleanup /* Delete queue if last thread */
)
{
AGENT *agent; /* Agent information block */
QUEUE *queue; /* Queue information block */
#if (defined (SMT_TRACE))
trace ("thread_destroy: thread=%s", thread-> name);
#endif
ASSERT (thread);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (-1);
}
queue = thread-> queue; /* Get parents for thread */
agent = queue-> agent;
/* We have to be a little careful or sym_delete_symbol () will */
/* try to release the symbol's data area; the data area points */
/* to our node, which we want to release ourselves. */
thread-> symbol-> data = NULL;
sym_delete_symbol (dict, thread-> symbol);
/* Destroy event for the thread, if we still need to */
if (thread-> event) /* NULL = no event for thread */
event_destroy (thread-> event);
/* Delete the thread */
thread_unlink (thread); /* Remove thread from active */
mem_free (thread-> tcb); /* Free allocated TCB, */
mem_free (thread-> LR_stack); /* and sub-dialog stack */
mem_strfree (&thread-> name);
node_destroy (thread);
/* Destroy queue if last thread, and we are asked to clean-up */
if (queue-> threads.next == &queue-> threads && cleanup)
queue_destroy (queue);
ASSERT (agent-> cur_threads > 0);
agent-> cur_threads--;
cur_threads--; /* Keep count of threads */
return (0);
}
/* ---------------------------------------------------------------------[<]-
Function: semaph_create
Synopsis: Creates a new semaphore. You must create a semaphore before
you can use it. The value argument specifies the number of parties
that can access the semaphore (or its related resources) at once. The
value must be greated than zero. A 'normal' binary semaphore has an
initial value of 1.
The semaph name is an arbitrary text, unique within the application.
Returns the address of the created SEMAPH block. If there was an error,
returns NULL and sets smt_errno to one of these values:
SMT_NOTREADY smt_init() was not called, or failed
SMT_OUTOFMEMORY Not enough heap memory left
SMT_SEMAPHEXISTS A semaphore with this name is already declared
---------------------------------------------------------------------[>]-*/
SEMAPH *
semaph_create (
const char *semaph_name, /* Name of semaph to create */
int value /* Initial semaphore value */
)
{
SYMBOL *dict_entry; /* Dictionary symbol */
SEMAPH *semaph; /* Agent information block */
char *full_name; /* Full semaph name */
#if (defined (SMT_TRACE))
trace ("semaph_create: semaph=%s", semaph_name);
#endif
ASSERT (semaph_name);
ASSERT (value > 0);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (NULL);
}
/* Check that semaphore is not already declared */
full_name = get_semaph_name (semaph_name);
if (sym_lookup_symbol (dict, full_name))
{
smt_errno = SMT_SEMAPHEXISTS;
return (NULL);
}
/* Now create entry for the semaphore */
dict_entry = sym_create_symbol (dict, full_name, NULL);
if (dict_entry == NULL)
{
smt_errno = SMT_OUTOFMEMORY;
return (NULL);
}
/* Allocate an SEMAPH block and attach it to the semaphore list */
semaph = (SEMAPH *) node_create (semaphs.prev, sizeof (SEMAPH));
if (semaph == NULL)
{
sym_delete_symbol (dict, dict_entry);
smt_errno = SMT_OUTOFMEMORY;
return (NULL);
}
/* Point the dictionary entry to the semaph information block */
dict_entry-> data = semaph;
/* Now initialise the semaph - all fields are already set to zero */
semaph-> symbol = dict_entry;
semaph-> name = mem_strdup (semaph_name);
semaph-> threads.left = &semaph-> threads;
semaph-> threads.right = &semaph-> threads;
semaph-> value = value;
return (semaph);
}
/* -------------------------------------------------------------------------
* get_semaph_name -- internal
*
* Returns a formatted name for the specified semaphore.
* The semaphore name is used for lookups into the dictionary.
* The name is always converted to lower-case.
*/
static char *
get_semaph_name (const char *semaph_name)
{
static
char full_name [LINE_MAX + 1];
ASSERT ((strlen (semaph_name) + 2) <= LINE_MAX);
sprintf (full_name, "x~%s", semaph_name);
strlwc (full_name);
return (full_name);
}
/* ---------------------------------------------------------------------[<]-
Function: semaph_lookup
Synopsis: Checks whether a specific semaphore exists; returns the
address of the semaphore information block, or NULL if there was an
error, setting smt_errno to one of these values:
SMT_NOTREADY smt_init() was not called, or failed
SMT_NOSUCHSEMAPH Specified semaphore was not declared
---------------------------------------------------------------------[>]-*/
SEMAPH *
semaph_lookup (
const char *semaph_name /* Name of semaph to look for */
)
{
SYMBOL *dict_entry; /* Dictionary symbol */
#if (defined (SMT_TRACE))
trace ("semaph_lookup: semaph=%s", semaph_name);
#endif
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (NULL);
}
dict_entry = sym_lookup_symbol (dict, get_semaph_name (semaph_name));
if (dict_entry == NULL)
{
smt_errno = SMT_NOSUCHSEMAPH;
return (NULL);
}
return (dict_entry-> data); /* Return pointer to SEMAPH */
}
/* ---------------------------------------------------------------------[<]-
Function: semaph_destroy
Synopsis: Destroys the semaphore. Returns 0 when completed. The
semaph argument points to an semaph block, or is null. If null, all
semaphores are destroyed. Returns 0 when completed normally, else
returns -1 and sets smt_errno to one of these values:
SMT_NOTREADY smt_init() was not called, or failed
---------------------------------------------------------------------[>]-*/
int
semaph_destroy (
SEMAPH *semaph /* Semaph to destroy; null = all */
)
{
#if (defined (SMT_TRACE))
trace ("semaph_destroy: semaph=%s", semaph? semaph-> name: "ALL");
#endif
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (-1);
}
if (semaph == NULL) /* Destroy all semaphs if wanted */
while (semaphs.next != &semaphs)
semaph_destroy (semaphs.next);
else /* Else destroy this semaph */
{
/* We have to be a little careful or sym_delete_symbol () will */
/* try to release the symbol's data area; the data area points */
/* to our node, which we want to release ourselves. */
semaph-> symbol-> data = NULL;
sym_delete_symbol (dict, semaph-> symbol);
/* Now delete the semaph */
mem_strfree (&semaph-> name); /* First we take its name */
node_destroy (semaph); /* ... then we take its life */
}
return (0);
}
/* ---------------------------------------------------------------------[<]-
Function: semaph_wait
Synopsis: When the semaphore value is > 0, subtracts 1 from the semaphore
value. If necessary, suspends the thread until this happens. Threads are
re-started on a FIFO basis. Call as last statement in an action module.
Returns 0 when completed normally, else returns -1 and sets smt_errno to
one of these values:
SMT_NOTREADY smt_init() was not called, or failed
---------------------------------------------------------------------[>]-*/
int
semaph_wait (
SEMAPH *semaph /* Semaph to wait for */
)
{
#if (defined (SMT_TRACE))
trace ("semaph_wait");
#endif
ASSERT (semaph);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (-1);
}
if (semaph-> value > 0) /* If semaphore is > 0 */
semaph-> value--; /* then we can continue */
else
{ /* Else break on semaphore */
break_wanted = BREAK_WAIT_SEMAPH;
break_semaph = semaph;
}
return (0);
}
/* ---------------------------------------------------------------------[<]-
Function: semaph_signal
Synopsis: Adds 1 to the semaphore value.
Returns 0 when completed normally, else returns -1 and sets smt_errno to
one of these values:
SMT_NOTREADY smt_init() was not called, or failed
---------------------------------------------------------------------[>]-*/
int
semaph_signal (
SEMAPH *semaph /* Semaph to signal */
)
{
#if (defined (SMT_TRACE))
trace ("semaph_signal");
#endif
ASSERT (semaph);
if (!smt_alive) /* If SMT API was not correctly */
{ /* initialised, forget it */
smt_errno = SMT_NOTREADY;
return (-1);
}
semaph-> value++; /* Bump semaphore value */
return (0); /* initialised, forget it */
}
/* ---------------------------------------------------------------------[<]-
Function: lazy_creat
Synopsis: Calls the standard creat() function without blocking.
(Actually, calls the open() function, but with the O_CREAT flag.)
Returns a file handle when the call succeeds, else returns -1 and sets
errno to the cause of the error. The file is always opened in binary
mode, and you must process control characters yourself. We use open()
so that we can force O_NONBLOCK.
If the call would block, returns -1, sets errno to EAGAIN, and calls
recycle_module() to re-execute the current dialog module automatically.
You can override this behavious by calling recycle_module (FALSE) after
the return. Sets the global variable "io_completed" to TRUE if the
i/o access completed, with or without an error.
---------------------------------------------------------------------[>]-*/
int
lazy_creat (char *path, int mode)
{
int rc;
#if (defined (SMT_TRACE))
trace ("lazy_create: path=%s", path);
#endif
rc = open (path, O_CREAT | O_WRONLY | O_TRUNC | O_NONBLOCK | O_BINARY,
mode);
set_io_completed (rc);
return (rc);
}
/* -------------------------------------------------------------------------
* set_io_completed -- internal
*
* If the previous i/o operation completed okay or with a permanent error,
* sets the _io_completed flag to TRUE. If the error was EWOULDBLOCK or
* EAGAIN (which we get depends on the system) sets _io_completed FALSE
* and calls recycle_module().
*/
local
set_io_completed (int rc)
{
if (rc >= 0)
_io_completed = TRUE;
else
if (errno == EAGAIN || errno == EWOULDBLOCK)
{
_io_completed = FALSE;
errno = EAGAIN; /* Always export EAGAIN to caller */
}
else
_io_completed = TRUE;
if (!_io_completed)
recycle_module (TRUE);
}
/* ---------------------------------------------------------------------[<]-
Function: lazy_creat_text
Synopsis: Calls the standard creat() function without blocking.
(Actually, calls the open() function, but with the O_CREAT flag.)
Returns a file handle when the call succeeds, else returns -1 and sets
errno to the cause of the error. The file is always opened in text
mode. We use open() so that we can force O_NONBLOCK.
If the call would block, returns -1, sets errno to EAGAIN, and calls
recycle_module() to re-execute the current dialog module automatically.
You can override this behavious by calling recycle_module (FALSE) after
the return. Sets the global variable "io_completed" to TRUE if the
i/o access completed, with or without an error.
---------------------------------------------------------------------[>]-*/
int
lazy_creat_text (char *path, int mode)
{
int rc;
#if (defined (SMT_TRACE))
trace ("lazy_create_text: path=%s", path);
#endif
rc = open (path, O_CREAT | O_WRONLY | O_TRUNC | O_NONBLOCK, mode);
set_io_completed (rc);
return (rc);
}
/* ---------------------------------------------------------------------[<]-
Function: lazy_open
Synopsis: Calls the standard open() function without blocking. Returns
a file handle when the call succeeds, else returns -1 and sets errno to
the cause of the error. The file is always opened in binary mode, and
you must process control characters yourself.
If the call would block, returns -1, sets errno to EAGAIN, and calls
recycle_module() to re-execute the current dialog module automatically.
You can override this behavious by calling recycle_module (FALSE) after
the return. Sets the global variable "io_completed" to TRUE if the
i/o access completed, with or without an error.
Examples:
handle_input = lazy_open (filename, O_RDONLY);
handle_output = lazy_open (filename, O_WRONLY | O_CREAT | O_TRUNC);
handle_append = lazy_open (filename, O_WRONLY | O_CREAT | O_APPEND);
if (io_completed && handle < 0)
have error on file
---------------------------------------------------------------------[>]-*/
int
lazy_open (char *path, int flags)
{
int rc;
#if (defined (SMT_TRACE))
trace ("lazy_open: path=%s", path);
#endif
rc = open (path, flags | O_NONBLOCK | O_BINARY, S_IREAD | S_IWRITE);
set_io_completed (rc);
return (rc);
}
/* ---------------------------------------------------------------------[<]-
Function: lazy_open_text
Synopsis: As lazy_open(), but opens the file in text mode, on those
platforms where this makes a difference.
Examples:
handle_input = lazy_open (filename, O_RDONLY);
handle_output = lazy_open (filename, O_WRONLY | O_CREAT | O_TRUNC);
handle_append = lazy_open (filename, O_WRONLY | O_CREAT | O_APPEND);
if (io_completed && handle < 0)
have error on file
---------------------------------------------------------------------[>]-*/
int
lazy_open_text (char *path, int flags)
{
int rc;
#if (defined (SMT_TRACE))
trace ("lazy_open_text: path=%s", path);
#endif
rc = open (path, flags | O_NONBLOCK, S_IREAD | S_IWRITE);
set_io_completed (rc);
return (rc);
}
/* ---------------------------------------------------------------------[<]-
Function: lazy_read
Synopsis: Calls the standard read() function without blocking. Returns
the number of bytes read when the call succeeds, else returns -1 and sets
errno to the cause of the error.
If the call would block, returns -1, sets errno to EAGAIN, and calls
recycle_module() to re-execute the current dialog module automatically.
You can override this behavious by calling recycle_module (FALSE) after
the return. Sets the global variable "io_completed" to TRUE if the
i/o access completed, with or without an error.
---------------------------------------------------------------------[>]-*/
int
lazy_read (int handle, char *buffer, size_t count)
{
int rc;
#if (defined (SMT_TRACE))
trace ("lazy_read: handle=%d bytes=%d", handle, count);
#endif
rc = read (handle, buffer, count);
set_io_completed (rc);
return (rc);
}
/* ---------------------------------------------------------------------[<]-
Function: lazy_write
Synopsis: Calls the standard write() function without blocking. Returns
the number of bytes written when the call succeeds, else returns -1 and
sets errno to the cause of the error.
If the call would block, returns -1, sets errno to EAGAIN, and calls
recycle_module() to re-execute the current dialog module automatically.
You can override this behavious by calling recycle_module (FALSE) after
the return. Sets the global variable "io_completed" to TRUE if the
i/o access completed, with or without an error.
---------------------------------------------------------------------[>]-*/
int
lazy_write (int handle, char *buffer, size_t count)
{
int rc;
#if (defined (SMT_TRACE))
trace ("lazy_write: handle=%d bytes=%d", handle, count);
#endif
rc = write (handle, buffer, count);
set_io_completed (rc);
return (rc);
}
/* ---------------------------------------------------------------------[<]-
Function: lazy_close
Synopsis: Calls the standard close() function without blocking.
Returns 0 when the call succeeds, else returns -1 and sets errno to
the cause of the error.
If the call would block, returns -1, sets errno to EAGAIN, and calls
recycle_module() to re-execute the current dialog module automatically.
You can override this behavious by calling recycle_module (FALSE) after
the return. Sets the global variable "io_completed" to TRUE if the
i/o access completed, with or without an error.
---------------------------------------------------------------------[>]-*/
int
lazy_close (int handle)
{
int rc;
#if (defined (SMT_TRACE))
trace ("lazy_close: handle=%d", handle);
#endif
rc = close (handle);
set_io_completed (rc);
return (rc);
}
/* ---------------------------------------------------------------------[<]-
Function: senderr
Synopsis: Sends an "ERROR" event to the specified queue, with the
value of strerror (errno) as event body. Use this to reply after some
i/o access failed. Return values are the same as for event_send().
Examples:
senderr (&thread-> event-> sender);
---------------------------------------------------------------------[>]-*/
int
senderr (const QID *to_queue)
{
char
*message = strerror (errno);
#if (defined (SMT_TRACE))
trace ("senderr: error=%s", message);
#endif
return (event_send (
to_queue, /* Send to specified queue */
NULL, /* No queue for reply */
"ERROR", /* Name of event to send */
(byte *) message, /* Event body to send */
strlen (message), /* Event body size */
NULL, NULL, NULL, /* No response events */
0)); /* No timeout */
}
/* ---------------------------------------------------------------------[<]-
Function: sendfmt
Synopsis: Sends a text message to the specified queue. The caller can
specify a printf()-type format string and insertion values. Return
values are the same as for event_send().
Examples:
sendfmt (&console, "INFO", "Error accessing %s file", filename);
---------------------------------------------------------------------[>]-*/
int
sendfmt (const QID *to_queue, char *name, char *format, ...)
{
static char
formatted [4096]; /* Formatted string */
va_list
argptr; /* Argument list pointer */
#if (defined (SMT_TRACE))
trace ("sendfmt: name=%s format=%s", name, format);
#endif
va_start (argptr, format); /* Start variable args processing */
#if (defined (DOES_SNPRINTF))
vsnprintf (formatted, 4096, format, argptr);
#else
vsprintf (formatted, format, argptr);
#endif
va_end (argptr); /* End variable args processing */
return (event_send (
to_queue, /* Send to specified queue */
NULL, /* No queue for reply */
name, /* Name of event to send */
(byte *) formatted, /* Event body to send */
strlen (formatted) + 1, /* Event body size, including null */
NULL, NULL, NULL, /* No response events */
0)); /* No timeout */
}
/* ---------------------------------------------------------------------[<]-
Function: raise_exception
Synopsis: Sets the exception_raised flag to TRUE and sets the
exception event as specified. To raise an exception without setting
the exception event, just do this: 'exception_raised = TRUE;'.
---------------------------------------------------------------------[>]-*/
void
raise_exception (event_t event)
{
#if (defined (SMT_TRACE))
trace ("raise_exception");
#endif
_exception_raised = TRUE;
_the_exception_event = event;
}
/* ---------------------------------------------------------------------[<]-
Function: recycle_module
Synopsis: Tells the SMT kernel to repeat the current action module.
This is a simple way to re-attempt an i/o that returned a 'EAGAIN' or
'EWOULDBLOCK' code. The lazy file access functions automatically call
this function in case they failed. If you call this function with the
wanted argument as FALSE, any previous recycle request is cancelled.
---------------------------------------------------------------------[>]-*/
void
recycle_module (Bool wanted)
{
#if (defined (SMT_TRACE))
trace ("recycle_module");
#endif
if (wanted)
break_wanted = BREAK_RECYCLE;
else
break_wanted = BREAK_CONTINUE;
}
/* ---------------------------------------------------------------------[<]-
Function: smt_set_step
Synopsis: Sets the current module 'step'. Used to debug applications:
suspect modules can set 'steps': the last of which is displayed when
there is a SEGV crash. Must be called with a string (constant) value.
---------------------------------------------------------------------[>]-*/
void
smt_set_step (const char *step)
{
cur_step = (char *) step;
}
/* ---------------------------------------------------------------------[<]-
Function: smt_crash_report
Synopsis: Returns a formatted string showing the current agent, state,
module, step, event, and catch report, if any.
---------------------------------------------------------------------[>]-*/
char *
smt_crash_report (void)
{
static char
report [512];
sprintf (report, "Abort at %s:%s:%s (%s, %s)",
cur_agent, cur_module, cur_step,
cur_state, cur_event);
return (report);
}